diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx index 665561937ce..d9bc1cc88d5 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx @@ -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 0, }); } @@ -62,11 +65,29 @@ export class ScopesDashboardsScene extends SceneObjectBase) { - 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 ( +

+ No scopes selected +

+ ); + } else if (dashboards.length === 0) { + return ( +

+ + No dashboards found for the selected scopes + +

+ ); + } + } + return ( <>
@@ -85,7 +106,7 @@ export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps - ) : ( + ) : filteredDashboards.length > 0 ? ( {filteredDashboards.map(({ dashboard, dashboardTitle }) => ( ))} + ) : ( +

+ No results found for your query + + +

)} ); @@ -105,6 +138,14 @@ export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps { return { + noResultsContainer: css({ + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + justifyContent: 'center', + textAlign: 'center', + }), searchInputContainer: css({ flex: '0 1 auto', }), diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx index bbda7e05cac..84cea5de184 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx @@ -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', () => { diff --git a/public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx b/public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx index dc1fbe0d9fc..cdc9053a163 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx @@ -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 = [ 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(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(selectors.tree.search('applications')); @@ -357,6 +387,8 @@ export const getClustersSlothClusterNorthRadio = () => screen.getByTestId(selectors.tree.radio('clusters-slothClusterNorth')); export const getClustersSlothClusterSouthRadio = () => screen.getByTestId(selectors.tree.radio('clusters-slothClusterSouth')); +export const getClustersSlothClusterEastRadio = () => + screen.getByTestId(selectors.tree.radio('clusters-slothClusterEast')); export function buildTestScene(overrides: Partial = {}) { return new DashboardScene({ diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 7d5a1812623..afbd474048c 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -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", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index cee6b19f34e..c3eb128f08d 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -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ę ƒįľŧęřş",