From 139933b0ca2f72cdfde26a0b7b50c7df71278099 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:02:21 +0100 Subject: [PATCH] [v11.0.x] EmptyState: use `not-found` empty state variant everywhere in core (#86311) * EmptyState: use `not-found` empty state variant everywhere in core (#85007) * use EmptyState component everywhere in core * remove GrotNotFound core component * update unit tests * search -> not-found * fix 3 more (cherry picked from commit 7ad2ec9665028259a5b229d8cbe0be60a58eb2ac) * EmptyState: Provide custom messages for existing empty states (#86021) provide custom messages for existing empty states --------- Co-authored-by: Ashley Harrison --- .betterer.results | 6 -- .../components/GrotNotFound/GrotNotFound.tsx | 62 ----------------- .../GrotNotFound/useMousePosition.ts | 29 -------- .../PageNotFound/EntityNotFound.tsx | 38 ++--------- .../app/features/admin/UserListAdminPage.tsx | 25 ++++--- public/app/features/api-keys/ApiKeysPage.tsx | 47 +++++++------ .../components/SearchView.tsx | 17 +++-- .../commandPalette/CommandPalette.tsx | 5 +- .../features/commandPalette/EmptyState.tsx | 23 ------- .../features/connections/Connections.test.tsx | 4 +- .../tabs/ConnectData/ConnectData.test.tsx | 6 +- .../tabs/ConnectData/ConnectData.tsx | 10 ++- .../tabs/ConnectData/NoResults/NoResults.tsx | 18 ----- .../tabs/ConnectData/NoResults/index.tsx | 1 - .../components/DataSourcesList.tsx | 9 ++- .../LibraryPanelsSearch.test.tsx | 12 ++-- .../LibraryPanelsView/LibraryPanelsView.tsx | 9 +-- .../playlist/EmptyQueryListBanner.tsx | 21 ------ public/app/features/playlist/PlaylistPage.tsx | 5 +- .../plugins/admin/components/PluginList.tsx | 7 +- .../ServiceAccountsListPage.tsx | 8 +++ public/app/features/teams/TeamList.tsx | 31 ++++++--- public/img/grot-404-dark.svg | 67 ------------------- public/img/grot-404-light.svg | 67 ------------------- public/locales/en-US/grafana.json | 45 ++++++++++++- public/locales/pseudo-LOCALE/grafana.json | 45 ++++++++++++- 26 files changed, 204 insertions(+), 413 deletions(-) delete mode 100644 public/app/core/components/GrotNotFound/GrotNotFound.tsx delete mode 100644 public/app/core/components/GrotNotFound/useMousePosition.ts delete mode 100644 public/app/features/commandPalette/EmptyState.tsx delete mode 100644 public/app/features/connections/tabs/ConnectData/NoResults/NoResults.tsx delete mode 100644 public/app/features/connections/tabs/ConnectData/NoResults/index.tsx delete mode 100644 public/app/features/playlist/EmptyQueryListBanner.tsx delete mode 100644 public/img/grot-404-dark.svg delete mode 100644 public/img/grot-404-light.svg diff --git a/.betterer.results b/.betterer.results index b8ef806d303..8f4990a615c 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2376,9 +2376,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "7"], [0, 0, 0, "Styles should be written using objects.", "8"] ], - "public/app/features/connections/tabs/ConnectData/NoResults/NoResults.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/correlations/CorrelationsPage.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -3753,9 +3750,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], - "public/app/features/playlist/EmptyQueryListBanner.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/playlist/PlaylistForm.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] diff --git a/public/app/core/components/GrotNotFound/GrotNotFound.tsx b/public/app/core/components/GrotNotFound/GrotNotFound.tsx deleted file mode 100644 index f0c4c16f68c..00000000000 --- a/public/app/core/components/GrotNotFound/GrotNotFound.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { css } from '@emotion/css'; -import React, { SVGProps } from 'react'; -import SVG from 'react-inlinesvg'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2, useTheme2 } from '@grafana/ui'; - -import dark404 from '../../../../img/grot-404-dark.svg'; -import light404 from '../../../../img/grot-404-light.svg'; - -import useMousePosition from './useMousePosition'; - -const MIN_ARM_ROTATION = -20; -const MAX_ARM_ROTATION = 5; -const MIN_ARM_TRANSLATION = -5; -const MAX_ARM_TRANSLATION = 5; - -export interface Props { - width?: SVGProps['width']; - height?: SVGProps['height']; - show404?: boolean; -} - -export const GrotNotFound = ({ width = 'auto', height, show404 = false }: Props) => { - const theme = useTheme2(); - const { x, y } = useMousePosition(); - const styles = useStyles2(getStyles, x, y, show404); - return ; -}; - -GrotNotFound.displayName = 'GrotNotFound'; - -const getStyles = (theme: GrafanaTheme2, xPos: number | null, yPos: number | null, show404: boolean) => { - const { innerWidth, innerHeight } = window; - const heightRatio = yPos && yPos / innerHeight; - const widthRatio = xPos && xPos / innerWidth; - const rotation = heightRatio !== null ? getIntermediateValue(heightRatio, MIN_ARM_ROTATION, MAX_ARM_ROTATION) : 0; - const translation = - widthRatio !== null ? getIntermediateValue(widthRatio, MIN_ARM_TRANSLATION, MAX_ARM_TRANSLATION) : 0; - - return { - svg: css({ - '#grot-404-arm, #grot-404-magnifier': { - transform: `rotate(${rotation}deg) translateX(${translation}%)`, - transformOrigin: 'center', - transition: 'transform 50ms linear', - }, - '#grot-404-text': { - display: show404 ? 'block' : 'none', - }, - }), - }; -}; - -/** - * Given a start value, end value, and a ratio, return the intermediate value - * Works with negative and inverted start/end values - */ -const getIntermediateValue = (ratio: number, start: number, end: number) => { - const value = ratio * (end - start) + start; - return value; -}; diff --git a/public/app/core/components/GrotNotFound/useMousePosition.ts b/public/app/core/components/GrotNotFound/useMousePosition.ts deleted file mode 100644 index 15ee04b4b9e..00000000000 --- a/public/app/core/components/GrotNotFound/useMousePosition.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { throttle } from 'lodash'; -import { useState, useEffect } from 'react'; - -interface MousePosition { - x: number | null; - y: number | null; -} - -// For performance reasons, we throttle the mouse position updates -const DEFAULT_THROTTLE_INTERVAL_MS = 50; - -const useMousePosition = (throttleInterval = DEFAULT_THROTTLE_INTERVAL_MS) => { - const [mousePosition, setMousePosition] = useState({ x: null, y: null }); - - useEffect(() => { - const updateMousePosition = throttle((event: MouseEvent) => { - setMousePosition({ x: event.clientX, y: event.clientY }); - }, throttleInterval); - window.addEventListener('mousemove', updateMousePosition); - - return () => { - window.removeEventListener('mousemove', updateMousePosition); - }; - }, [throttleInterval]); - - return mousePosition; -}; - -export default useMousePosition; diff --git a/public/app/core/components/PageNotFound/EntityNotFound.tsx b/public/app/core/components/PageNotFound/EntityNotFound.tsx index 13bc119e5c7..24edfa76f5c 100644 --- a/public/app/core/components/PageNotFound/EntityNotFound.tsx +++ b/public/app/core/components/PageNotFound/EntityNotFound.tsx @@ -2,9 +2,7 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; - -import { GrotNotFound } from '../GrotNotFound/GrotNotFound'; +import { EmptyState, TextLink, useStyles2 } from '@grafana/ui'; export interface Props { /** @@ -18,20 +16,13 @@ export function EntityNotFound({ entity = 'Page' }: Props) { return (
-

{entity} not found

-
+ We're looking but can't seem to find this {entity.toLowerCase()}. Try returning{' '} - - home - {' '} - or seeking help on the{' '} - + home or seeking help on the{' '} + community site. - -
-
- -
+ +
); } @@ -39,24 +30,7 @@ export function EntityNotFound({ entity = 'Page' }: Props) { export function getStyles(theme: GrafanaTheme2) { return { container: css({ - display: 'flex', - flexDirection: 'column', padding: theme.spacing(8, 2, 2, 2), - h1: { - textAlign: 'center', - }, - }), - subtitle: css({ - color: theme.colors.text.secondary, - fontSize: theme.typography.h5.fontSize, - padding: theme.spacing(2, 0), - textAlign: 'center', - }), - grot: css({ - alignSelf: 'center', - maxWidth: '450px', - paddingTop: theme.spacing(8), - width: '100%', }), }; } diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index a675cef2c2b..6fbbc543a56 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -4,9 +4,10 @@ import { connect, ConnectedProps } from 'react-redux'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; -import { LinkButton, RadioButtonGroup, useStyles2, FilterInput } from '@grafana/ui'; +import { LinkButton, RadioButtonGroup, useStyles2, FilterInput, EmptyState } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; +import { t } from 'app/core/internationalization'; import { AccessControlAction, StoreState, UserFilter } from '../../types'; @@ -40,6 +41,7 @@ const mapStateToProps = (state: StoreState) => ({ totalPages: state.userListAdmin.totalPages, page: state.userListAdmin.page, filters: state.userListAdmin.filters, + isLoading: state.userListAdmin.isLoading, }); const connector = connect(mapStateToProps, mapDispatchToProps); @@ -60,6 +62,7 @@ const UserListAdminPageUnConnected = ({ page, changePage, changeSort, + isLoading, }: Props) => { const styles = useStyles2(getStyles); @@ -96,14 +99,18 @@ const UserListAdminPageUnConnected = ({ )} - + {!isLoading && users.length === 0 ? ( + + ) : ( + + )} ); }; diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 4067439b511..ea9d42c8c71 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -2,9 +2,10 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; // Utils -import { InlineField, InlineSwitch, VerticalGroup, Modal, Button } from '@grafana/ui'; +import { InlineField, InlineSwitch, Modal, Button, EmptyState } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; +import { t } from 'app/core/internationalization'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { AccessControlAction, ApiKey, ApikeyMigrationResult, StoreState } from 'app/types'; @@ -117,29 +118,27 @@ export class ApiKeysPageUnconnected extends PureComponent { return ( - <> - - {showTable ? ( - - ) : null} - {showTable ? ( - - - - - - - ) : null} - + + {showTable ? ( + + ) : null} + + + + {apiKeys.length > 0 ? ( + + ) : ( + + )} {migrationResult && ( - - - No results found for your query. - - + Clear search and filters - - + } + message={t('browse-dashboards.no-results.text', 'No results found for your query')} + variant="not-found" + /> ); } diff --git a/public/app/features/commandPalette/CommandPalette.tsx b/public/app/features/commandPalette/CommandPalette.tsx index 9e2222bedcd..a1e36d3a20b 100644 --- a/public/app/features/commandPalette/CommandPalette.tsx +++ b/public/app/features/commandPalette/CommandPalette.tsx @@ -16,10 +16,9 @@ import React, { useEffect, useMemo, useRef } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; -import { Icon, LoadingBar, useStyles2 } from '@grafana/ui'; +import { EmptyState, Icon, LoadingBar, useStyles2 } from '@grafana/ui'; import { t } from 'app/core/internationalization'; -import { EmptyState } from './EmptyState'; import { KBarResults } from './KBarResults'; import { ResultItem } from './ResultItem'; import { useSearchResults } from './actions/dashboardActions'; @@ -122,7 +121,7 @@ const RenderResults = ({ isFetchingSearchResults, searchResults }: RenderResults const showEmptyState = !isFetchingSearchResults && items.length === 0; return showEmptyState ? ( - + ) : ( { - return ( - - - - - No results found - - - - ); -}; - -EmptyState.displayName = 'EmptyState'; diff --git a/public/app/features/connections/Connections.test.tsx b/public/app/features/connections/Connections.test.tsx index 1eae465d761..c246c7f6630 100644 --- a/public/app/features/connections/Connections.test.tsx +++ b/public/app/features/connections/Connections.test.tsx @@ -64,7 +64,7 @@ describe('Connections', () => { // We expect to see no results and "Data sources" as a header (we only have data sources in OSS Grafana at this point) expect(await screen.findByText('Data sources')).toBeVisible(); - expect(await screen.findByText('No results matching your query were found.')).toBeVisible(); + expect(await screen.findByText('No results matching your query were found')).toBeVisible(); }); test('does not render anything for the "Add new connection" page in case it is displayed by a standalone plugin page', async () => { @@ -97,6 +97,6 @@ describe('Connections', () => { // We expect not to see the text that would be rendered by the core "Add new connection" page expect(screen.queryByText('Data sources')).not.toBeInTheDocument(); - expect(screen.queryByText('No results matching your query were found.')).not.toBeInTheDocument(); + expect(screen.queryByText('No results matching your query were found')).not.toBeInTheDocument(); }); }); diff --git a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx index 44e8ff024d9..2b18442fcf3 100644 --- a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx +++ b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx @@ -63,13 +63,13 @@ describe('Add new connection', () => { test('renders no results if the plugins list is empty', async () => { renderPage(); - expect(screen.queryByText('No results matching your query were found.')).toBeInTheDocument(); + expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument(); }); test('renders no results if there is no data source plugin in the list', async () => { renderPage([getCatalogPluginMock()]); - expect(screen.queryByText('No results matching your query were found.')).toBeInTheDocument(); + expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument(); }); test('renders only data source plugins when list is populated', async () => { @@ -87,7 +87,7 @@ describe('Add new connection', () => { await userEvent.clear(searchField); await userEvent.type(searchField, 'cramp'); - expect(screen.queryByText('No results matching your query were found.')).toBeInTheDocument(); + expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument(); await userEvent.clear(searchField); expect(await screen.findByText('Sample data source')).toBeVisible(); diff --git a/public/app/features/connections/tabs/ConnectData/ConnectData.tsx b/public/app/features/connections/tabs/ConnectData/ConnectData.tsx index edd64e78f5e..ef8b788f2eb 100644 --- a/public/app/features/connections/tabs/ConnectData/ConnectData.tsx +++ b/public/app/features/connections/tabs/ConnectData/ConnectData.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import React, { useMemo, useState } from 'react'; import { GrafanaTheme2, PluginType } from '@grafana/data'; -import { useStyles2, LoadingPlaceholder } from '@grafana/ui'; +import { useStyles2, LoadingPlaceholder, EmptyState } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { t } from 'app/core/internationalization'; @@ -14,7 +14,6 @@ import { ROUTES } from '../../constants'; import { CardGrid, type CardGridItem } from './CardGrid'; import { CategoryHeader } from './CategoryHeader'; import { NoAccessModal } from './NoAccessModal'; -import { NoResults } from './NoResults'; import { Search } from './Search'; const getStyles = (theme: GrafanaTheme2) => ({ @@ -97,7 +96,12 @@ export function AddNewConnection() { ) : ( )} - {showNoResults && } + {showNoResults && ( + + )} ); } diff --git a/public/app/features/connections/tabs/ConnectData/NoResults/NoResults.tsx b/public/app/features/connections/tabs/ConnectData/NoResults/NoResults.tsx deleted file mode 100644 index 43501a5bd47..00000000000 --- a/public/app/features/connections/tabs/ConnectData/NoResults/NoResults.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; - -import { useStyles2 } from '@grafana/ui'; - -const getStyles = () => ({ - noResults: css` - text-align: center; - padding: 50px 0; - font-style: italic; - `, -}); - -export const NoResults = () => { - const styles = useStyles2(getStyles); - - return

No results matching your query were found.

; -}; diff --git a/public/app/features/connections/tabs/ConnectData/NoResults/index.tsx b/public/app/features/connections/tabs/ConnectData/NoResults/index.tsx deleted file mode 100644 index 20eae4db4b6..00000000000 --- a/public/app/features/connections/tabs/ConnectData/NoResults/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './NoResults'; diff --git a/public/app/features/datasources/components/DataSourcesList.tsx b/public/app/features/datasources/components/DataSourcesList.tsx index 6cad171ec84..ac15fd93cb6 100644 --- a/public/app/features/datasources/components/DataSourcesList.tsx +++ b/public/app/features/datasources/components/DataSourcesList.tsx @@ -4,9 +4,10 @@ import { useLocation } from 'react-router-dom'; import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { useStyles2 } from '@grafana/ui'; +import { EmptyState, useStyles2 } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { contextSrv } from 'app/core/core'; +import { t } from 'app/core/internationalization'; import { StoreState, AccessControlAction, useSelector } from 'app/types'; import { getDataSources, getDataSourcesCount, useDataSourcesRoutes, useLoadDataSources } from '../state'; @@ -104,7 +105,11 @@ export function DataSourcesListView({ {/* List */} -
    {getDataSourcesList()}
+ {dataSources.length === 0 && !isLoading ? ( + + ) : ( +
    {getDataSourcesList()}
+ )} ); } diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx index 382d7786734..862f34739d9 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx @@ -105,7 +105,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext(); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found./i)).toBeInTheDocument(); + expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); }); describe('and user searches for library panel by name or description', () => { @@ -132,7 +132,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext({ showSort: true }); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found./i)).toBeInTheDocument(); + expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); expect(screen.getByText(/sort \(default a–z\)/i)).toBeInTheDocument(); }); @@ -160,7 +160,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext({ showPanelFilter: true }); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found./i)).toBeInTheDocument(); + expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); expect(screen.getByRole('combobox', { name: /panel type filter/i })).toBeInTheDocument(); }); @@ -188,7 +188,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext({ showFolderFilter: true }); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found./i)).toBeInTheDocument(); + expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); expect(screen.getByRole('combobox', { name: /folder filter/i })).toBeInTheDocument(); }); @@ -274,7 +274,7 @@ describe('LibraryPanelsSearch', () => { const card = () => screen.getByLabelText(/plugin visualization item time series/i); - expect(screen.queryByText(/no library panels found./i)).not.toBeInTheDocument(); + expect(screen.queryByText(/no library panels found/i)).not.toBeInTheDocument(); expect(card()).toBeInTheDocument(); expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument(); expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument(); @@ -315,7 +315,7 @@ describe('LibraryPanelsSearch', () => { const card = () => screen.getByLabelText(/plugin visualization item time series/i); - expect(screen.queryByText(/no library panels found./i)).not.toBeInTheDocument(); + expect(screen.queryByText(/no library panels found/i)).not.toBeInTheDocument(); expect(card()).toBeInTheDocument(); expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument(); expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument(); diff --git a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx index 85123c7f7d4..a401ba5bf72 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx @@ -3,7 +3,8 @@ import React, { useMemo, useReducer } from 'react'; import { useDebounce } from 'react-use'; import { GrafanaTheme2, LoadingState } from '@grafana/data'; -import { Pagination, Stack, useStyles2 } from '@grafana/ui'; +import { EmptyState, Pagination, Stack, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; import { LibraryElementDTO } from '../../types'; import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard'; @@ -83,7 +84,7 @@ export const LibraryPanelsView = ({ ) : libraryPanels.length < 1 ? ( -

No library panels found.

+ ) : ( libraryPanels?.map((item, i) => ( { alignSelf: 'center', marginTop: theme.spacing(1), }), - noPanelsFound: css({ - label: 'noPanelsFound', - minHeight: 200, - }), }; }; diff --git a/public/app/features/playlist/EmptyQueryListBanner.tsx b/public/app/features/playlist/EmptyQueryListBanner.tsx deleted file mode 100644 index 2a2e7967cb1..00000000000 --- a/public/app/features/playlist/EmptyQueryListBanner.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; - -export const EmptyQueryListBanner = () => { - const styles = useStyles2(getStyles); - return
No playlist found!
; -}; - -const getStyles = (theme: GrafanaTheme2) => { - return { - noResult: css` - padding: ${theme.spacing(2)}; - background: ${theme.colors.secondary.main}; - font-style: italic; - margin-top: ${theme.spacing(2)}; - `, - }; -}; diff --git a/public/app/features/playlist/PlaylistPage.tsx b/public/app/features/playlist/PlaylistPage.tsx index d8231f0f7ff..bebb5f3c906 100644 --- a/public/app/features/playlist/PlaylistPage.tsx +++ b/public/app/features/playlist/PlaylistPage.tsx @@ -1,14 +1,13 @@ import React, { useMemo, useState } from 'react'; import { useAsync } from 'react-use'; -import { ConfirmModal, LinkButton } from '@grafana/ui'; +import { ConfirmModal, EmptyState, LinkButton } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { Page } from 'app/core/components/Page/Page'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; import { Trans, t } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; -import { EmptyQueryListBanner } from './EmptyQueryListBanner'; import { PlaylistPageList } from './PlaylistPageList'; import { StartModal } from './StartModal'; import { getPlaylistAPI, searchPlaylists } from './api'; @@ -71,7 +70,7 @@ export const PlaylistPage = () => { ) : ( <> {!hasPlaylists && searchQuery ? ( - + ) : ( { const { pathname } = useLocation(); const pathName = config.appSubUrl + (pathname.endsWith('/') ? pathname.slice(0, -1) : pathname); + if (!isLoading && plugins.length === 0) { + return ; + } + return ( {isLoading diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx index 38096370c78..46abdecf551 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx @@ -13,11 +13,13 @@ import { InlineField, Pagination, Stack, + EmptyState, } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { Page } from 'app/core/components/Page/Page'; import config from 'app/core/config'; import { contextSrv } from 'app/core/core'; +import { t } from 'app/core/internationalization'; import { StoreState, ServiceAccountDTO, AccessControlAction, ServiceAccountStateFilter } from 'app/types'; import { CreateTokenModal, ServiceAccountToken } from './components/CreateTokenModal'; @@ -219,6 +221,12 @@ export const ServiceAccountsListPageUnconnected = ({ className={styles.filter} /> + {!isLoading && !noServiceAccountsCreated && serviceAccounts.length === 0 && ( + + )} {!isLoading && noServiceAccountsCreated && ( <> - - String(team.id)} - fetchData={changeSort} - /> - - + {hasFetched && teams.length === 0 ? ( + + ) : ( + + String(team.id)} + fetchData={changeSort} + /> + + + - + )} )} diff --git a/public/img/grot-404-dark.svg b/public/img/grot-404-dark.svg deleted file mode 100644 index 4ae37d64cef..00000000000 --- a/public/img/grot-404-dark.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/img/grot-404-light.svg b/public/img/grot-404-light.svg deleted file mode 100644 index b2e54046cb8..00000000000 --- a/public/img/grot-404-light.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index ffbcd86e32b..d3ff914a833 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -25,6 +25,11 @@ "user": "User" } }, + "api-keys": { + "empty-state": { + "message": "No API keys found" + } + }, "bouncing-loader": { "label": "Loading" }, @@ -94,7 +99,7 @@ }, "no-results": { "clear": "Clear search and filters", - "text": "No results found for your query." + "text": "No results found for your query" } }, "clipboard-button": { @@ -109,7 +114,7 @@ "light-theme": "Light" }, "empty-state": { - "title": "No results found" + "message": "No results found" }, "search-box": { "placeholder": "Search or jump to..." @@ -131,7 +136,8 @@ }, "connections": { "connect-data": { - "category-header-label": "Data sources" + "category-header-label": "Data sources", + "empty-message": "No results matching your query were found" }, "search": { "placeholder": "Search all" @@ -419,6 +425,9 @@ "data-sources": { "datasource-add-button": { "label": "Add new data source" + }, + "empty-state": { + "message": "No data sources found" } }, "explore": { @@ -650,6 +659,9 @@ } }, "library-panels": { + "empty-state": { + "message": "No library panels found" + }, "modal": { "body_one": "This panel is being used in {{count}} dashboard. Please choose which dashboard to view the panel in:", "body_other": "This panel is being used in {{count}} dashboards. Please choose which dashboard to view the panel in:", @@ -712,6 +724,8 @@ "connecting": "Connecting to this stack...", "stack-required-error": "Stack URL is required", "title": "Connect to a cloud stack", + "token-error-description": "There was an error saving the token. See the Grafana server logs for more details.", + "token-error-title": "Error saving token", "token-required-error": "Migration token is required" }, "cta": { @@ -1211,6 +1225,16 @@ "title": "There are no playlists created yet" } }, + "playlists": { + "empty-state": { + "message": "No playlists found" + } + }, + "plugins": { + "empty-state": { + "message": "No plugins found" + } + }, "profile": { "change-password": { "cancel-button": "Cancel", @@ -1413,6 +1437,11 @@ "placeholder": "Search for dashboards and folders" } }, + "service-accounts": { + "empty-state": { + "message": "No services accounts found" + } + }, "share-modal": { "dashboard": { "title": "Share" @@ -1541,6 +1570,11 @@ "no-tags": "No tags found", "placeholder": "Filter by tag" }, + "teams": { + "empty-state": { + "message": "No teams found" + } + }, "time-picker": { "absolute": { "recent-title": "Recently used absolute ranges", @@ -1634,6 +1668,11 @@ "user-sessions": { "loading": "Loading sessions..." }, + "users": { + "empty-state": { + "message": "No users found" + } + }, "users-access-list": { "tabs": { "public-dashboard-users-tab-title": "Public dashboard users" diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index bc54a2fbd02..102f8dbd0a3 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -25,6 +25,11 @@ "user": "Ůşęř" } }, + "api-keys": { + "empty-state": { + "message": "Ńő ÅPĨ ĸęyş ƒőūʼnđ" + } + }, "bouncing-loader": { "label": "Ŀőäđįʼnģ" }, @@ -94,7 +99,7 @@ }, "no-results": { "clear": "Cľęäř şęäřčĥ äʼnđ ƒįľŧęřş", - "text": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy." + "text": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy" } }, "clipboard-button": { @@ -109,7 +114,7 @@ "light-theme": "Ŀįģĥŧ" }, "empty-state": { - "title": "Ńő řęşūľŧş ƒőūʼnđ" + "message": "Ńő řęşūľŧş ƒőūʼnđ" }, "search-box": { "placeholder": "Ŝęäřčĥ őř ĵūmp ŧő..." @@ -131,7 +136,8 @@ }, "connections": { "connect-data": { - "category-header-label": "Đäŧä şőūřčęş" + "category-header-label": "Đäŧä şőūřčęş", + "empty-message": "Ńő řęşūľŧş mäŧčĥįʼnģ yőūř qūęřy ŵęřę ƒőūʼnđ" }, "search": { "placeholder": "Ŝęäřčĥ äľľ" @@ -419,6 +425,9 @@ "data-sources": { "datasource-add-button": { "label": "Åđđ ʼnęŵ đäŧä şőūřčę" + }, + "empty-state": { + "message": "Ńő đäŧä şőūřčęş ƒőūʼnđ" } }, "explore": { @@ -650,6 +659,9 @@ } }, "library-panels": { + "empty-state": { + "message": "Ńő ľįþřäřy päʼnęľş ƒőūʼnđ" + }, "modal": { "body_one": "Ŧĥįş päʼnęľ įş þęįʼnģ ūşęđ įʼn {{count}} đäşĥþőäřđ. Pľęäşę čĥőőşę ŵĥįčĥ đäşĥþőäřđ ŧő vįęŵ ŧĥę päʼnęľ įʼn:", "body_other": "Ŧĥįş päʼnęľ įş þęįʼnģ ūşęđ įʼn {{count}} đäşĥþőäřđş. Pľęäşę čĥőőşę ŵĥįčĥ đäşĥþőäřđ ŧő vįęŵ ŧĥę päʼnęľ įʼn:", @@ -712,6 +724,8 @@ "connecting": "Cőʼnʼnęčŧįʼnģ ŧő ŧĥįş şŧäčĸ...", "stack-required-error": "Ŝŧäčĸ ŮŖĿ įş řęqūįřęđ", "title": "Cőʼnʼnęčŧ ŧő ä čľőūđ şŧäčĸ", + "token-error-description": "Ŧĥęřę ŵäş äʼn ęřřőř şävįʼnģ ŧĥę ŧőĸęʼn. Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş.", + "token-error-title": "Ēřřőř şävįʼnģ ŧőĸęʼn", "token-required-error": "Mįģřäŧįőʼn ŧőĸęʼn įş řęqūįřęđ" }, "cta": { @@ -1211,6 +1225,16 @@ "title": "Ŧĥęřę äřę ʼnő pľäyľįşŧş čřęäŧęđ yęŧ" } }, + "playlists": { + "empty-state": { + "message": "Ńő pľäyľįşŧş ƒőūʼnđ" + } + }, + "plugins": { + "empty-state": { + "message": "Ńő pľūģįʼnş ƒőūʼnđ" + } + }, "profile": { "change-password": { "cancel-button": "Cäʼnčęľ", @@ -1413,6 +1437,11 @@ "placeholder": "Ŝęäřčĥ ƒőř đäşĥþőäřđş äʼnđ ƒőľđęřş" } }, + "service-accounts": { + "empty-state": { + "message": "Ńő şęřvįčęş äččőūʼnŧş ƒőūʼnđ" + } + }, "share-modal": { "dashboard": { "title": "Ŝĥäřę" @@ -1541,6 +1570,11 @@ "no-tags": "Ńő ŧäģş ƒőūʼnđ", "placeholder": "Fįľŧęř þy ŧäģ" }, + "teams": { + "empty-state": { + "message": "Ńő ŧęämş ƒőūʼnđ" + } + }, "time-picker": { "absolute": { "recent-title": "Ŗęčęʼnŧľy ūşęđ äþşőľūŧę řäʼnģęş", @@ -1634,6 +1668,11 @@ "user-sessions": { "loading": "Ŀőäđįʼnģ şęşşįőʼnş..." }, + "users": { + "empty-state": { + "message": "Ńő ūşęřş ƒőūʼnđ" + } + }, "users-access-list": { "tabs": { "public-dashboard-users-tab-title": "Pūþľįč đäşĥþőäřđ ūşęřş"