Scopes: Implement message for when there are no dashboards found (#89675)

pull/89628/head
Bogdan Matei 11 months ago committed by GitHub
parent 9770ebcd6c
commit 95a8fc1dd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 51
      public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx
  2. 35
      public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx
  3. 32
      public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx
  4. 4
      public/locales/en-US/grafana.json
  5. 4
      public/locales/pseudo-LOCALE/grafana.json

@ -3,9 +3,9 @@ import { Link } from 'react-router-dom';
import { GrafanaTheme2, Scope, urlUtil } from '@grafana/data';
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { CustomScrollbar, FilterInput, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { Button, CustomScrollbar, FilterInput, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { t } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { fetchSuggestedDashboards } from './api';
import { SuggestedDashboard } from './types';
@ -14,6 +14,7 @@ export interface ScopesDashboardsSceneState extends SceneObjectState {
dashboards: SuggestedDashboard[];
filteredDashboards: SuggestedDashboard[];
isLoading: boolean;
scopesSelected: boolean;
searchQuery: string;
}
@ -25,13 +26,14 @@ export class ScopesDashboardsScene extends SceneObjectBase<ScopesDashboardsScene
dashboards: [],
filteredDashboards: [],
isLoading: false,
scopesSelected: false,
searchQuery: '',
});
}
public async fetchDashboards(scopes: Scope[]) {
if (scopes.length === 0) {
return this.setState({ dashboards: [], filteredDashboards: [], isLoading: false });
return this.setState({ dashboards: [], filteredDashboards: [], isLoading: false, scopesSelected: false });
}
this.setState({ isLoading: true });
@ -42,6 +44,7 @@ export class ScopesDashboardsScene extends SceneObjectBase<ScopesDashboardsScene
dashboards,
filteredDashboards: this.filterDashboards(dashboards, this.state.searchQuery),
isLoading: false,
scopesSelected: scopes.length > 0,
});
}
@ -62,11 +65,29 @@ export class ScopesDashboardsScene extends SceneObjectBase<ScopesDashboardsScene
}
export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps<ScopesDashboardsScene>) {
const { filteredDashboards, isLoading, searchQuery } = model.useState();
const { dashboards, filteredDashboards, isLoading, searchQuery, scopesSelected } = model.useState();
const styles = useStyles2(getStyles);
const [queryParams] = useQueryParams();
if (!isLoading) {
if (!scopesSelected) {
return (
<p className={styles.noResultsContainer} data-testid="scopes-dashboards-notFoundNoScopes">
<Trans i18nKey="scopes.suggestedDashboards.noResultsNoScopes">No scopes selected</Trans>
</p>
);
} else if (dashboards.length === 0) {
return (
<p className={styles.noResultsContainer} data-testid="scopes-dashboards-notFoundForScope">
<Trans i18nKey="scopes.suggestedDashboards.noResultsForScopes">
No dashboards found for the selected scopes
</Trans>
</p>
);
}
}
return (
<>
<div className={styles.searchInputContainer}>
@ -85,7 +106,7 @@ export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps<Sco
text={t('scopes.suggestedDashboards.loading', 'Loading dashboards')}
data-testid="scopes-dashboards-loading"
/>
) : (
) : filteredDashboards.length > 0 ? (
<CustomScrollbar>
{filteredDashboards.map(({ dashboard, dashboardTitle }) => (
<Link
@ -98,6 +119,18 @@ export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps<Sco
</Link>
))}
</CustomScrollbar>
) : (
<p className={styles.noResultsContainer} data-testid="scopes-dashboards-notFoundForFilter">
<Trans i18nKey="scopes.suggestedDashboards.noResultsForFilter">No results found for your query</Trans>
<Button
variant="secondary"
onClick={() => model.changeSearchQuery('')}
data-testid="scopes-dashboards-notFoundForFilter-clear"
>
<Trans i18nKey="scopes.suggestedDashboards.noResultsForFilterClear">Clear search</Trans>
</Button>
</p>
)}
</>
);
@ -105,6 +138,14 @@ export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps<Sco
const getStyles = (theme: GrafanaTheme2) => {
return {
noResultsContainer: css({
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
justifyContent: 'center',
textAlign: 'center',
}),
searchInputContainer: css({
flex: '0 1 auto',
}),

@ -45,6 +45,12 @@ import {
queryDashboardsContainer,
queryDashboardsExpand,
renderDashboard,
getNotFoundForScope,
queryDashboardsSearch,
getNotFoundForFilter,
getClustersSlothClusterEastRadio,
getNotFoundForFilterClear,
getNotFoundNoScopes,
} from './testUtils';
jest.mock('@grafana/runtime', () => ({
@ -309,6 +315,35 @@ describe('ScopesScene', () => {
expect(queryAllDashboard('7')).toHaveLength(1);
expect(queryAllDashboard('8')).toHaveLength(1);
});
it('Does show a proper message when no scopes are selected', async () => {
await userEvents.click(getDashboardsExpand());
expect(getNotFoundNoScopes()).toBeInTheDocument();
expect(queryDashboardsSearch()).not.toBeInTheDocument();
});
it('Does not show the input when there are no dashboards found for scope', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getFiltersInput());
await userEvents.click(getClustersExpand());
await userEvents.click(getClustersSlothClusterEastRadio());
await userEvents.click(getFiltersApply());
expect(getNotFoundForScope()).toBeInTheDocument();
expect(queryDashboardsSearch()).not.toBeInTheDocument();
});
it('Does show the input and a message when there are no dashboards found for filter', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getFiltersInput());
await userEvents.click(getApplicationsExpand());
await userEvents.click(getApplicationsSlothPictureFactorySelect());
await userEvents.click(getFiltersApply());
await userEvents.type(getDashboardsSearch(), 'unknown');
expect(queryDashboardsSearch()).toBeInTheDocument();
expect(getNotFoundForFilter()).toBeInTheDocument();
await userEvents.click(getNotFoundForFilterClear());
expect(getDashboardsSearch().value).toBe('');
});
});
describe('View mode', () => {

@ -49,6 +49,16 @@ export const mocksScopes: Scope[] = [
filters: [{ key: 'cluster', value: 'slothClusterSouth', operator: 'equals' }],
},
},
{
metadata: { name: 'slothClusterEast' },
spec: {
title: 'slothClusterEast',
type: 'cluster',
description: 'slothClusterEast',
category: 'clusters',
filters: [{ key: 'cluster', value: 'slothClusterEast', operator: 'equals' }],
},
},
{
metadata: { name: 'slothPictureFactory' },
spec: {
@ -213,6 +223,17 @@ export const mocksNodes: Array<ScopeNode & { parent: string }> = [
linkId: 'slothClusterSouth',
},
},
{
parent: 'clusters',
metadata: { name: 'clusters-slothClusterEast' },
spec: {
nodeType: 'leaf',
title: 'slothClusterEast',
description: 'slothClusterEast',
linkType: 'scope',
linkId: 'slothClusterEast',
},
},
{
parent: 'clusters',
metadata: { name: 'clusters.applications' },
@ -312,6 +333,10 @@ const selectors = {
search: 'scopes-dashboards-search',
loading: 'scopes-dashboards-loading',
dashboard: (uid: string) => `scopes-dashboards-${uid}`,
notFoundNoScopes: 'scopes-dashboards-notFoundNoScopes',
notFoundForScope: 'scopes-dashboards-notFoundForScope',
notFoundForFilter: 'scopes-dashboards-notFoundForFilter',
notFoundForFilterClear: 'scopes-dashboards-notFoundForFilter-clear',
},
};
@ -324,10 +349,15 @@ export const queryDashboardsExpand = () => screen.queryByTestId(selectors.dashbo
export const getDashboardsExpand = () => screen.getByTestId(selectors.dashboards.expand);
export const queryDashboardsContainer = () => screen.queryByTestId(selectors.dashboards.container);
export const getDashboardsContainer = () => screen.getByTestId(selectors.dashboards.container);
export const queryDashboardsSearch = () => screen.queryByTestId(selectors.dashboards.search);
export const getDashboardsSearch = () => screen.getByTestId<HTMLInputElement>(selectors.dashboards.search);
export const queryAllDashboard = (uid: string) => screen.queryAllByTestId(selectors.dashboards.dashboard(uid));
export const queryDashboard = (uid: string) => screen.queryByTestId(selectors.dashboards.dashboard(uid));
export const getDashboard = (uid: string) => screen.getByTestId(selectors.dashboards.dashboard(uid));
export const getNotFoundNoScopes = () => screen.getByTestId(selectors.dashboards.notFoundNoScopes);
export const getNotFoundForScope = () => screen.getByTestId(selectors.dashboards.notFoundForScope);
export const getNotFoundForFilter = () => screen.getByTestId(selectors.dashboards.notFoundForFilter);
export const getNotFoundForFilterClear = () => screen.getByTestId(selectors.dashboards.notFoundForFilterClear);
export const getApplicationsExpand = () => screen.getByTestId(selectors.tree.expand('applications'));
export const getApplicationsSearch = () => screen.getByTestId<HTMLInputElement>(selectors.tree.search('applications'));
@ -357,6 +387,8 @@ export const getClustersSlothClusterNorthRadio = () =>
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterNorth'));
export const getClustersSlothClusterSouthRadio = () =>
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterSouth'));
export const getClustersSlothClusterEastRadio = () =>
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterEast'));
export function buildTestScene(overrides: Partial<DashboardScene> = {}) {
return new DashboardScene({

@ -1657,6 +1657,10 @@
},
"suggestedDashboards": {
"loading": "Loading dashboards",
"noResultsForFilter": "No results found for your query",
"noResultsForFilterClear": "Clear search",
"noResultsForScopes": "No dashboards found for the selected scopes",
"noResultsNoScopes": "No scopes selected",
"search": "Search",
"toggle": {
"collapse": "Collapse scope filters",

@ -1657,6 +1657,10 @@
},
"suggestedDashboards": {
"loading": "Ŀőäđįʼnģ đäşĥþőäřđş",
"noResultsForFilter": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy",
"noResultsForFilterClear": "Cľęäř şęäřčĥ",
"noResultsForScopes": "Ńő đäşĥþőäřđş ƒőūʼnđ ƒőř ŧĥę şęľęčŧęđ şčőpęş",
"noResultsNoScopes": "Ńő şčőpęş şęľęčŧęđ",
"search": "Ŝęäřčĥ",
"toggle": {
"collapse": "Cőľľäpşę şčőpę ƒįľŧęřş",

Loading…
Cancel
Save