Scopes: Implement message for when there are no dashboards found (#89675)
This commit is contained in:
@@ -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ę ƒįľŧęřş",
|
||||
|
||||
Reference in New Issue
Block a user