EmptyState: Apply call-to-action variant in core (#86448)

* apply empty state in a bunch of places

* fix unit tests

* put alert back on top

* add data-testids so e2e tests keep working

* remove info boxes

* fix annotations empty state alignment with new maxWidth
This commit is contained in:
Ashley Harrison
2024-04-22 17:01:24 +01:00
committed by GitHub
parent 224d61746a
commit fc45b56d9d
24 changed files with 554 additions and 271 deletions
+2 -6
View File
@@ -2513,9 +2513,6 @@ exports[`better eslint`] = {
"public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/dashboard-scene/settings/annotations/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsList\`)", "1"]
@@ -2565,8 +2562,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Styles should be written using objects.", "5"]
],
"public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Styles should be written using objects.", "1"]
[0, 0, 0, "Styles should be written using objects.", "0"]
],
"public/app/features/dashboard/components/AnnotationSettings/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"],
@@ -3923,7 +3919,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Styles should be written using objects.", "1"]
],
"public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Styles should be written using objects.", "1"],
[0, 0, 0, "Styles should be written using objects.", "2"],
[0, 0, 0, "Styles should be written using objects.", "3"],
@@ -1,7 +1,7 @@
import React from 'react';
import { CallToActionCard } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { CallToActionCard, EmptyState, LinkButton } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
import { getInstancesPermissions } from '../../utils/access-control';
@@ -16,11 +16,14 @@ export const NoSilencesSplash = ({ alertManagerSourceName }: Props) => {
if (contextSrv.hasPermission(permissions.create)) {
return (
<EmptyListCTA
title="You haven't created any silences yet"
buttonIcon="bell-slash"
buttonLink={makeAMLink('alerting/silence/new', alertManagerSourceName)}
buttonTitle="Create silence"
<EmptyState
variant="call-to-action"
button={
<LinkButton href={makeAMLink('alerting/silence/new', alertManagerSourceName)} icon="bell-slash" size="lg">
<Trans i18nKey="silences.empty-state.button-title">Create silence</Trans>
</LinkButton>
}
message={t('silences.empty-state.title', "You haven't created any silences yet")}
/>
);
}
@@ -149,7 +149,7 @@ describe('browse-dashboards BrowseView', () => {
describe('when there is no item in the folder', () => {
it('shows a CTA for creating a dashboard if the user has editor rights', async () => {
render(<BrowseView canSelect={true} folderUID={folderB_empty.item.uid} width={WIDTH} height={HEIGHT} />);
expect(await screen.findByText('Create Dashboard')).toBeInTheDocument();
expect(await screen.findByText('Create dashboard')).toBeInTheDocument();
});
it('shows a simple message if the user has viewer rights', async () => {
@@ -1,7 +1,7 @@
import React, { useCallback } from 'react';
import { CallToActionCard } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { CallToActionCard, EmptyState, LinkButton, TextLink } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { DashboardViewItem } from 'app/features/search/types';
import { useDispatch } from 'app/types';
@@ -117,16 +117,32 @@ export function BrowseView({ folderUID, width, height, canSelect }: BrowseViewPr
return (
<div style={{ width }}>
{canSelect ? (
<EmptyListCTA
title={folderUID ? "This folder doesn't have any dashboards yet" : 'No dashboards yet. Create your first!'}
buttonIcon="plus"
buttonTitle="Create Dashboard"
buttonLink={folderUID ? `dashboard/new?folderUid=${folderUID}` : 'dashboard/new'}
proTip={folderUID && 'Add/move dashboards to your folder at ->'}
proTipLink={folderUID && 'dashboards'}
proTipLinkTitle={folderUID && 'Browse dashboards'}
proTipTarget=""
/>
<EmptyState
variant="call-to-action"
button={
<LinkButton
href={folderUID ? `dashboard/new?folderUid=${folderUID}` : 'dashboard/new'}
icon="plus"
size="lg"
>
<Trans i18nKey="browse-dashboards.empty-state.button-title">Create dashboard</Trans>
</LinkButton>
}
message={
folderUID
? t('browse-dashboards.empty-state.title-folder', "This folder doesn't have any dashboards yet")
: t('browse-dashboards.empty-state.title', "You haven't created any dashboards yet")
}
>
{folderUID && (
<Trans i18nKey="browse-dashboards.empty-state.pro-tip">
Add/move dashboards to your folder at{' '}
<TextLink external={false} href="/dashboards">
Browse dashboards
</TextLink>
</Trans>
)}
</EmptyState>
) : (
<CallToActionCard callToActionElement={<span>This folder is empty</span>} />
)}
@@ -1,7 +1,7 @@
import React from 'react';
import { Card } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { Button, Card, EmptyState } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
interface Props {
onClick?: () => void;
@@ -11,13 +11,19 @@ export const EmptyCorrelationsCTA = ({ onClick, canWriteCorrelations }: Props) =
// TODO: if there are no datasources show a different message
return canWriteCorrelations ? (
<EmptyListCTA
title="You haven't defined any correlation yet."
buttonIcon="gf-glue"
onClick={onClick}
buttonTitle="Add correlation"
proTip="you can also define correlations via datasource provisioning"
/>
<EmptyState
variant="call-to-action"
button={
<Button icon="gf-glue" onClick={onClick} size="lg">
<Trans i18nKey="correlations.empty-state.button-title">Add correlation</Trans>
</Button>
}
message={t('correlations.empty-state.title', "You haven't defined any correlations yet")}
>
<Trans i18nKey="correlations.empty-state.pro-tip">
You can also define correlations via datasource provisioning
</Trans>
</EmptyState>
) : (
<Card>
<Card.Heading>There are no correlations configured yet.</Card.Heading>
@@ -65,21 +65,21 @@ describe('AnnotationSettingsEdit', () => {
it('should render with empty list message', async () => {
const {
renderer: { getByTestId },
renderer: { getByRole },
} = await setup(true);
const emptyListBtn = getByTestId(selectors.components.CallToActionCard.buttonV2(BUTTON_TITLE));
const emptyListBtn = getByRole('button', { name: BUTTON_TITLE });
expect(emptyListBtn).toBeInTheDocument();
});
it('should create new annotation when empty list button is pressed', async () => {
const {
renderer: { getByTestId },
renderer: { getByRole },
user,
} = await setup(true);
const emptyListBtn = getByTestId(selectors.components.CallToActionCard.buttonV2(BUTTON_TITLE));
const emptyListBtn = getByRole('button', { name: BUTTON_TITLE });
await user.click(emptyListBtn);
@@ -4,8 +4,8 @@ import React from 'react';
import { AnnotationQuery } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { getDataSourceSrv } from '@grafana/runtime';
import { Button, DeleteButton, IconButton, useStyles2, VerticalGroup } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { Button, DeleteButton, EmptyState, IconButton, Stack, TextLink, useStyles2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { ListNewButton } from 'app/features/dashboard/components/DashboardSettings/ListNewButton';
import { MoveDirection } from '../AnnotationsEditView';
@@ -39,7 +39,7 @@ export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onD
const dataSourceSrv = getDataSourceSrv();
return (
<VerticalGroup>
<Stack direction="column">
{annotations.length > 0 && (
<div className={styles.table}>
<table role="grid" className="filter-table filter-table--hover">
@@ -99,25 +99,38 @@ export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onD
</div>
)}
{showEmptyListCTA && (
<EmptyListCTA
onClick={onNew}
title="There are no custom annotation queries added yet"
buttonIcon="comment-alt"
buttonTitle={BUTTON_TITLE}
infoBoxTitle="What are annotation queries?"
infoBox={{
__html: `<p>Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines
and icons on all graph panels. When you hover over an annotation icon you can get event text &amp; tags for
the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or
drag region). These will be stored in Grafana's annotation database.
</p>
Checkout the
<a class='external-link' target='_blank' href='http://docs.grafana.org/reference/annotations/'
>Annotations documentation</a
>
for more information.`,
}}
/>
<Stack direction="column">
<EmptyState
variant="call-to-action"
button={
<Button
data-testid={selectors.components.CallToActionCard.buttonV2('Add annotation query')}
icon="comment-alt"
onClick={onNew}
size="lg"
>
<Trans i18nKey="annotations.empty-state.button-title">Add annotation query</Trans>
</Button>
}
message={t('annotations.empty-state.title', 'There are no custom annotation queries added yet')}
>
<Trans i18nKey="annotations.empty-state.info-box-content">
<p>
Annotations provide a way to integrate event data into your graphs. They are visualized as vertical
lines and icons on all graph panels. When you hover over an annotation icon you can get event text &amp;
tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click
on graph (or drag region). These will be stored in Grafana&apos;s annotation database.
</p>
</Trans>
<Trans i18nKey="annotations.empty-state.info-box-content-2">
Checkout the{' '}
<TextLink external href="http://docs.grafana.org/reference/annotations/">
Annotations documentation
</TextLink>{' '}
for more information.
</Trans>
</EmptyState>
</Stack>
)}
{!showEmptyListCTA && (
<ListNewButton
@@ -127,7 +140,7 @@ export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onD
New query
</ListNewButton>
)}
</VerticalGroup>
</Stack>
);
};
@@ -3,8 +3,19 @@ import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { DashboardLink } from '@grafana/schema';
import { Button, DeleteButton, HorizontalGroup, Icon, IconButton, TagList, useStyles2 } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import {
Button,
DeleteButton,
EmptyState,
HorizontalGroup,
Icon,
IconButton,
Stack,
TagList,
TextLink,
useStyles2,
} from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
interface DashboardLinkListProps {
links: DashboardLink[];
@@ -28,19 +39,28 @@ export function DashboardLinkList({
if (isEmptyList) {
return (
<div>
<EmptyListCTA
onClick={onNew}
title="There are no dashboard links added yet"
buttonIcon="link"
buttonTitle="Add dashboard link"
infoBoxTitle="What are dashboard links?"
infoBox={{
__html:
'<p>Dashboard Links allow you to place links to other dashboards and web sites directly below the dashboard header.</p>',
}}
/>
</div>
<Stack direction="column">
<EmptyState
variant="call-to-action"
button={
<Button onClick={onNew} size="lg">
<Trans i18nKey="dashboard-links.empty-state.button-title">Add dashboard link</Trans>
</Button>
}
message={t('dashboard-links.empty-state.title', 'There are no dashboard links added yet')}
>
<Trans i18nKey="dashboard-links.empty-state.info-box-content">
Dashboard links allow you to place links to other dashboards and web sites directly below the dashboard
header.{' '}
<TextLink
external
href="https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/manage-dashboard-links/"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
</Stack>
);
}
@@ -5,8 +5,8 @@ import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
import { selectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime';
import { SceneVariable, SceneVariableState } from '@grafana/scenes';
import { useStyles2, Stack, Button } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { useStyles2, Stack, Button, EmptyState, TextLink } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { VariableEditorListRow } from './VariableEditorListRow';
@@ -98,30 +98,38 @@ export function VariableEditorList({
function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement {
return (
<div>
<EmptyListCTA
title="There are no variables yet"
buttonIcon="calculator-alt"
buttonTitle="Add variable"
infoBox={{
__html: ` <p>
Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server
or sensor names in your metric queries you can use variables in their place. Variables are shown as
list boxes at the top of the dashboard. These drop-down lists make it easy to change the data
being displayed in your dashboard. Check out the
<a class="external-link" href="https://grafana.com/docs/grafana/latest/variables/" target="_blank">
Templates and variables documentation
</a>
for more information.
</p>`,
}}
infoBoxTitle="What do variables do?"
onClick={(event) => {
event.preventDefault();
onAdd();
}}
/>
</div>
<Stack direction="column">
<EmptyState
variant="call-to-action"
button={
<Button
data-testid={selectors.components.CallToActionCard.buttonV2('Add variable')}
icon="calculator-alt"
onClick={onAdd}
size="lg"
>
<Trans i18nKey="variables.empty-state.button-title">Add variable</Trans>
</Button>
}
message={t('variables.empty-state.title', 'There are no variables added yet')}
>
<p>
<Trans i18nKey="variables.empty-state.info-box-content">
Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or
sensor names in your metric queries you can use variables in their place. Variables are shown as list boxes
at the top of the dashboard. These drop-down lists make it easy to change the data being displayed in your
dashboard.
</Trans>
</p>
<Trans i18nKey="variables.empty-state.info-box-content-2">
Check out the{' '}
<TextLink external href="https://grafana.com/docs/grafana/latest/variables/">
Templates and variables documentation
</TextLink>{' '}
for more information.
</Trans>
</EmptyState>
</Stack>
);
}
@@ -4,8 +4,8 @@ import React, { useState } from 'react';
import { arrayUtils, AnnotationQuery } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { getDataSourceSrv } from '@grafana/runtime';
import { Button, DeleteButton, IconButton, useStyles2, VerticalGroup } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { Button, DeleteButton, EmptyState, IconButton, Stack, TextLink, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { DashboardModel } from '../../state/DashboardModel';
import { ListNewButton } from '../DashboardSettings/ListNewButton';
@@ -54,7 +54,7 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
const dataSourceSrv = getDataSourceSrv();
return (
<VerticalGroup>
<Stack direction="column">
{annotations.length > 0 && (
<div className={styles.table}>
<table role="grid" className="filter-table filter-table--hover">
@@ -108,25 +108,38 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
</div>
)}
{showEmptyListCTA && (
<EmptyListCTA
onClick={onNew}
title="There are no custom annotation queries added yet"
buttonIcon="comment-alt"
buttonTitle="Add annotation query"
infoBoxTitle="What are annotation queries?"
infoBox={{
__html: `<p>Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines
and icons on all graph panels. When you hover over an annotation icon you can get event text &amp; tags for
the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or
drag region). These will be stored in Grafana's annotation database.
</p>
Checkout the
<a class='external-link' target='_blank' href='http://docs.grafana.org/reference/annotations/'
>Annotations documentation</a
>
for more information.`,
}}
/>
<Stack direction="column">
<EmptyState
variant="call-to-action"
button={
<Button
data-testid={selectors.components.CallToActionCard.buttonV2('Add annotation query')}
icon="comment-alt"
onClick={onNew}
size="lg"
>
<Trans i18nKey="annotations.empty-state.button-title">Add annotation query</Trans>
</Button>
}
message={t('annotations.empty-state.title', 'There are no custom annotation queries added yet')}
>
<Trans i18nKey="annotations.empty-state.info-box-content">
<p>
Annotations provide a way to integrate event data into your graphs. They are visualized as vertical
lines and icons on all graph panels. When you hover over an annotation icon you can get event text &amp;
tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click
on graph (or drag region). These will be stored in Grafana&apos;s annotation database.
</p>
</Trans>
<Trans i18nKey="annotations.empty-state.info-box-content-2">
Checkout the{' '}
<TextLink external href="http://docs.grafana.org/reference/annotations/">
Annotations documentation
</TextLink>{' '}
for more information.
</Trans>
</EmptyState>
</Stack>
)}
{!showEmptyListCTA && (
<ListNewButton
@@ -136,7 +149,7 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
New query
</ListNewButton>
)}
</VerticalGroup>
</Stack>
);
};
@@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
import { selectors } from '@grafana/e2e-selectors';
import { locationService, setAngularLoader, setDataSourceSrv } from '@grafana/runtime';
import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks';
@@ -89,9 +88,7 @@ describe('AnnotationsSettings', () => {
expect(screen.queryByRole('grid')).toBeInTheDocument();
expect(screen.getByRole('row', { name: /annotations & alerts \(built-in\) -- grafana --/i })).toBeInTheDocument();
expect(
screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query'))
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Add annotation query' })).toBeInTheDocument();
expect(screen.queryByRole('link', { name: /annotations documentation/i })).toBeInTheDocument();
});
@@ -99,9 +96,7 @@ describe('AnnotationsSettings', () => {
dashboard.annotations.list = [];
setup(dashboard);
expect(
screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query'))
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Add annotation query' })).toBeInTheDocument();
});
test('it renders the annotation names or uid if annotation does not exist', async () => {
@@ -185,7 +180,7 @@ describe('AnnotationsSettings', () => {
test('Adding a new annotation', async () => {
setup(dashboard);
await userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query')));
await userEvent.click(screen.getByRole('button', { name: 'Add annotation query' }));
expect(locationService.getSearchObject().editIndex).toBe('1');
expect(dashboard.annotations.list.length).toBe(2);
@@ -5,7 +5,6 @@ import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { selectors } from '@grafana/e2e-selectors';
import { locationService } from '@grafana/runtime';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
@@ -93,9 +92,7 @@ describe('LinksSettings', () => {
const linksTab = screen.getByRole('tab', { name: 'Tab Links' });
expect(linksTab).toBeInTheDocument();
expect(linksTab).toHaveAttribute('aria-selected', 'true');
expect(
screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add dashboard link'))
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Add dashboard link' })).toBeInTheDocument();
expect(screen.queryByRole('table')).not.toBeInTheDocument();
});
@@ -104,9 +101,7 @@ describe('LinksSettings', () => {
setup(dashboard);
expect(getTableBodyRows().length).toBe(dashboard.links.length);
expect(
screen.queryByTestId(selectors.components.CallToActionCard.buttonV2('Add dashboard link'))
).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Add dashboard link' })).not.toBeInTheDocument();
});
test('it rearranges the order of dashboard links', async () => {
@@ -4,10 +4,9 @@ import { useLocation } from 'react-router-dom';
import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { EmptyState, useStyles2 } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { EmptyState, LinkButton, TextLink, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { t } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { StoreState, AccessControlAction, useSelector } from 'app/types';
import { getDataSources, getDataSourcesCount, useDataSourcesRoutes, useLoadDataSources } from '../state';
@@ -67,17 +66,25 @@ export function DataSourcesListView({
if (!isLoading && dataSourcesCount === 0) {
return (
<EmptyListCTA
buttonDisabled={!hasCreateRights}
title="No data sources defined"
buttonIcon="database"
buttonLink={dataSourcesRoutes.New}
buttonTitle="Add data source"
proTip="You can also define data sources through configuration files."
proTipLink="http://docs.grafana.org/administration/provisioning/?utm_source=grafana_ds_list#data-sources"
proTipLinkTitle="Learn more"
proTipTarget="_blank"
/>
<EmptyState
variant="call-to-action"
button={
<LinkButton disabled={!hasCreateRights} href={dataSourcesRoutes.New} icon="database" size="lg">
<Trans i18nKey="data-source-list.empty-state.button-title">Add data source</Trans>
</LinkButton>
}
message={t('data-source-list.empty-state.title', 'No data sources defined')}
>
<Trans i18nKey="data-source-list.empty-state.pro-tip">
You can also define data sources through configuration files.{' '}
<TextLink
external
href="http://docs.grafana.org/administration/provisioning/?utm_source=grafana_ds_list#data-sources"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
);
}
@@ -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(/you haven\'t created any library panels yet/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(/you haven\'t created any library panels yet/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(/you haven\'t created any library panels yet/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(/you haven\'t created any library panels yet/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(/you haven\'t created any library panels yet/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(/you haven\'t created any library panels yet/i)).not.toBeInTheDocument();
expect(card()).toBeInTheDocument();
expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument();
expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument();
@@ -3,8 +3,8 @@ import React, { useMemo, useReducer } from 'react';
import { useDebounce } from 'react-use';
import { GrafanaTheme2, LoadingState } from '@grafana/data';
import { EmptyState, Pagination, Stack, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { EmptyState, Pagination, Stack, TextLink, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { LibraryElementDTO } from '../../types';
import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard';
@@ -74,6 +74,26 @@ export const LibraryPanelsView = ({
})
);
const onPageChange = (page: number) => asyncDispatch(changePage({ page }));
const hasFilter = searchString || panelFilter?.length || folderFilter?.length;
if (!hasFilter && loadingState === LoadingState.Done && libraryPanels.length < 1) {
return (
<EmptyState
variant="call-to-action"
message={t('library-panel.empty-state.message', "You haven't created any library panels yet")}
>
<Trans i18nKey="library-panel.empty-state.more-info">
Create a library panel from any existing dashboard panel through the panel context menu.{' '}
<TextLink
external
href="https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/manage-library-panels/#create-a-library-panel"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
);
}
return (
<Stack direction="column" wrap="nowrap">
@@ -99,13 +99,13 @@ const renderPublicDashboardTable = async (waitForListRendering?: boolean) => {
</TestProvider>
);
waitForListRendering && (await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')[1], { timeout: 3000 }));
waitForListRendering && (await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')[0], { timeout: 3000 }));
};
describe('Show table', () => {
it('renders loader spinner while loading', async () => {
await renderPublicDashboardTable();
const spinner = screen.getAllByTestId('Spinner')[1];
const spinner = screen.getAllByTestId('Spinner')[0];
expect(spinner).toBeInTheDocument();
await waitForElementToBeRemoved(spinner);
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import React, { useMemo, useState } from 'react';
import { useMedia } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data/src';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { reportInteraction } from '@grafana/runtime';
import {
@@ -16,7 +16,9 @@ import {
Switch,
Pagination,
HorizontalGroup,
} from '@grafana/ui/src';
EmptyState,
TextLink,
} from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans, t } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
@@ -141,28 +143,50 @@ export const PublicDashboardListTable = () => {
const [page, setPage] = useState(1);
const styles = useStyles2(getStyles);
const { data: paginatedPublicDashboards, isLoading, isFetching, isError } = useListPublicDashboardsQuery(page);
const { data: paginatedPublicDashboards, isLoading, isError } = useListPublicDashboardsQuery(page);
return (
<Page navId="dashboards/public" actions={isFetching && <Spinner />}>
<Page navId="dashboards/public">
<Page.Contents isLoading={isLoading}>
{!isLoading && !isError && !!paginatedPublicDashboards && (
<div>
<ul className={styles.list}>
{paginatedPublicDashboards.publicDashboards.map((pd: PublicDashboardListResponse) => (
<li key={pd.uid}>
<PublicDashboardCard pd={pd} />
</li>
))}
</ul>
<HorizontalGroup justify="flex-end">
<Pagination
onNavigate={setPage}
currentPage={paginatedPublicDashboards.page}
numberOfPages={paginatedPublicDashboards.totalPages}
hideWhenSinglePage
/>
</HorizontalGroup>
{paginatedPublicDashboards.publicDashboards.length === 0 ? (
<EmptyState
variant="call-to-action"
message={t(
'public-dashboard-list.empty-state.message',
"You haven't created any public dashboards yet"
)}
>
<Trans i18nKey="public-dashboard-list.empty-state.more-info">
Create a public dashboard from any existing dashboard through the <b>Share</b> modal.{' '}
<TextLink
external
href="https://grafana.com/docs/grafana/latest/dashboards/dashboard-public/#make-a-dashboard-public"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
) : (
<>
<ul className={styles.list}>
{paginatedPublicDashboards.publicDashboards.map((pd: PublicDashboardListResponse) => (
<li key={pd.uid}>
<PublicDashboardCard pd={pd} />
</li>
))}
</ul>
<HorizontalGroup justify="flex-end">
<Pagination
onNavigate={setPage}
currentPage={paginatedPublicDashboards.page}
numberOfPages={paginatedPublicDashboards.totalPages}
hideWhenSinglePage
/>
</HorizontalGroup>
</>
)}
</div>
)}
</Page.Contents>
@@ -2,13 +2,13 @@ import React, { useState, useCallback } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { config } from '@grafana/runtime';
import { ConfirmModal } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { ConfirmModal, EmptyState, TextLink } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { getDashboardSnapshotSrv, Snapshot } from 'app/features/dashboard/services/SnapshotSrv';
import { SnapshotListTableRow } from './SnapshotListTableRow';
export function getSnapshots() {
export async function getSnapshots() {
return getDashboardSnapshotSrv()
.getSnapshots()
.then((result: Snapshot[]) => {
@@ -42,6 +42,25 @@ export const SnapshotListTable = () => {
[snapshots]
);
if (!isFetching && snapshots.length === 0) {
return (
<EmptyState
variant="call-to-action"
message={t('snapshot.empty-state.message', "You haven't created any snapshots yet")}
>
<Trans i18nKey="snapshot.empty-state.more-info">
You can create a snapshot of any dashboard through the <b>Share</b> modal.{' '}
<TextLink
external
href="https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels/#publish-a-snapshot"
>
Learn more
</TextLink>
</Trans>
</EmptyState>
);
}
return (
<div>
<table className="filter-table">
+19 -17
View File
@@ -1,8 +1,7 @@
import React, { useMemo, useState } from 'react';
import { useAsync } from 'react-use';
import { ConfirmModal, EmptyState, LinkButton } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { ConfirmModal, EmptyState, LinkButton, TextLink } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
import { Trans, t } from 'app/core/internationalization';
@@ -35,20 +34,6 @@ export const PlaylistPage = () => {
});
};
const emptyListBanner = (
<EmptyListCTA
title={t('playlist-page.empty.title', 'There are no playlists created yet')}
buttonIcon="plus"
buttonLink="playlists/new"
buttonTitle={t('playlist-page.empty.button', 'Create Playlist')}
buttonDisabled={!contextSrv.isEditor}
proTip={t('playlist-page.empty.pro-tip', 'You can use playlists to cycle dashboards on TVs without user control')}
proTipLink="http://docs.grafana.org/reference/playlist/"
proTipLinkTitle={t('playlist-page.empty.pro-tip-link-title', 'Learn more')}
proTipTarget="_blank"
/>
);
const showSearch = allPlaylists.loading || playlists.length > 0 || searchQuery.length > 0;
return (
@@ -78,7 +63,24 @@ export const PlaylistPage = () => {
setPlaylistToDelete={setPlaylistToDelete}
/>
)}
{!showSearch && emptyListBanner}
{!showSearch && (
<EmptyState
variant="call-to-action"
button={
<LinkButton disabled={!contextSrv.isEditor} href="playlists/new" icon="plus" size="lg">
<Trans i18nKey="playlist-page.empty.button">Create playlist</Trans>
</LinkButton>
}
message={t('playlist-page.empty.title', 'There are no playlists created yet')}
>
<Trans i18nKey="playlist-page.empty.pro-tip">
You can use playlists to cycle dashboards on TVs without user control.{' '}
<TextLink external href="https://docs.grafana.org/reference/playlist/">
Learn more
</TextLink>
</Trans>
</EmptyState>
)}
{playlistToDelete && (
<ConfirmModal
title={playlistToDelete.name}
@@ -4,11 +4,10 @@ import { connect, ConnectedProps } from 'react-redux';
import { OrgRole } from '@grafana/data';
import { ConfirmModal, FilterInput, LinkButton, RadioButtonGroup, InlineField, EmptyState, Box } 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 { Trans, t } from 'app/core/internationalization';
import { StoreState, ServiceAccountDTO, AccessControlAction, ServiceAccountStateFilter } from 'app/types';
import { ServiceAccountTable } from './ServiceAccountTable';
@@ -217,19 +216,24 @@ export const ServiceAccountsListPageUnconnected = ({
/>
)}
{!isLoading && noServiceAccountsCreated && (
<>
<EmptyListCTA
title="You haven't created any service accounts yet."
buttonIcon="key-skeleton-alt"
buttonLink="org/serviceaccounts/create"
buttonTitle="Add service account"
buttonDisabled={!contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate)}
proTip="Remember, you can provide specific permissions for API access to other applications."
proTipLink=""
proTipLinkTitle=""
proTipTarget="_blank"
/>
</>
<EmptyState
variant="call-to-action"
button={
<LinkButton
disabled={!contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate)}
href="org/serviceaccounts/create"
icon="key-skeleton-alt"
size="lg"
>
<Trans i18nKey="service-accounts.empty-state.button-title">Add service account</Trans>
</LinkButton>
}
message={t('service-accounts.empty-state.title', "You haven't created any service accounts yet")}
>
<Trans i18nKey="service-accounts.empty-state.more-info">
Remember, you can provide specific permissions for API access to other applications
</Trans>
</EmptyState>
)}
{(isLoading || serviceAccounts.length !== 0) && (
+22 -16
View File
@@ -19,10 +19,9 @@ import {
TextLink,
useStyles2,
} from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { Page } from 'app/core/components/Page/Page';
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
import { t } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction, Role, StoreState, Team } from 'app/types';
@@ -208,24 +207,31 @@ export const TeamList = ({
<Page
navId="teams"
actions={
<LinkButton href={canCreate ? 'org/teams/new' : '#'} disabled={!canCreate}>
New Team
</LinkButton>
!noTeams ? (
<LinkButton href={canCreate ? 'org/teams/new' : '#'} disabled={!canCreate}>
New Team
</LinkButton>
) : undefined
}
>
<Page.Contents>
{noTeams ? (
<EmptyListCTA
title="You haven't created any teams yet."
buttonIcon="users-alt"
buttonLink="org/teams/new"
buttonTitle=" New team"
buttonDisabled={!contextSrv.hasPermission(AccessControlAction.ActionTeamsCreate)}
proTip="Assign folder and dashboard permissions to teams instead of users to ease administration."
proTipLink=""
proTipLinkTitle=""
proTipTarget="_blank"
/>
<EmptyState
variant="call-to-action"
button={
<LinkButton disabled={!canCreate} href="org/teams/new" icon="users-alt" size="lg">
<Trans i18nKey="teams.empty-state.button-title">New team</Trans>
</LinkButton>
}
message={t('teams.empty-state.title', "You haven't created any teams yet")}
>
<Trans i18nKey="teams.empty-state.pro-tip">
Assign folder and dashboard permissions to teams instead of users to ease administration.{' '}
<TextLink external href="https://grafana.com/docs/grafana/latest/administration/team-management">
Learn more
</TextLink>
</Trans>
</EmptyState>
) : (
<>
<div className="page-action-bar">
@@ -5,8 +5,8 @@ import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
import { TypedVariableModel } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime';
import { Button, useStyles2, Stack } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { Button, useStyles2, Stack, EmptyState, TextLink } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { VariablesDependenciesButton } from '../inspect/VariablesDependenciesButton';
import { UsagesToNetwork, VariableUsageTree } from '../inspect/utils';
@@ -107,30 +107,38 @@ export function VariableEditorList({
function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement {
return (
<div>
<EmptyListCTA
title="There are no variables yet"
buttonIcon="calculator-alt"
buttonTitle="Add variable"
infoBox={{
__html: ` <p>
Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server
or sensor names in your metric queries you can use variables in their place. Variables are shown as
list boxes at the top of the dashboard. These drop-down lists make it easy to change the data
being displayed in your dashboard. Check out the
<a class="external-link" href="https://grafana.com/docs/grafana/latest/variables/" target="_blank">
Templates and variables documentation
</a>
for more information.
</p>`,
}}
infoBoxTitle="What do variables do?"
onClick={(event) => {
event.preventDefault();
onAdd();
}}
/>
</div>
<Stack direction="column">
<EmptyState
variant="call-to-action"
button={
<Button
data-testid={selectors.components.CallToActionCard.buttonV2('Add variable')}
icon="calculator-alt"
onClick={onAdd}
size="lg"
>
<Trans i18nKey="variables.empty-state.button-title">Add variable</Trans>
</Button>
}
message={t('variables.empty-state.title', 'There are no variables added yet')}
>
<p>
<Trans i18nKey="variables.empty-state.info-box-content">
Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or
sensor names in your metric queries you can use variables in their place. Variables are shown as list boxes
at the top of the dashboard. These drop-down lists make it easy to change the data being displayed in your
dashboard.
</Trans>
</p>
<Trans i18nKey="variables.empty-state.info-box-content-2">
Check out the{' '}
<TextLink external href="https://grafana.com/docs/grafana/latest/variables/">
Templates and variables documentation
</TextLink>{' '}
for more information.
</Trans>
</EmptyState>
</Stack>
);
}
+69 -5
View File
@@ -25,6 +25,14 @@
"user": "User"
}
},
"annotations": {
"empty-state": {
"button-title": "Add annotation query",
"info-box-content": "<0>Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines and icons on all graph panels. When you hover over an annotation icon you can get event text & tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or drag region). These will be stored in Grafana's annotation database.</0>",
"info-box-content-2": "Checkout the <2>Annotations documentation</2> for more information.",
"title": "There are no custom annotation queries added yet"
}
},
"api-keys": {
"empty-state": {
"message": "No API keys found"
@@ -72,6 +80,12 @@
"select-checkbox": "Select",
"tags-column": "Tags"
},
"empty-state": {
"button-title": "Create dashboard",
"pro-tip": "Add/move dashboards to your folder at <2>Browse dashboards</2>",
"title": "You haven't created any dashboards yet",
"title-folder": "This folder doesn't have any dashboards yet"
},
"folder-actions-button": {
"delete": "Delete",
"folder-actions": "Folder actions",
@@ -159,6 +173,11 @@
"sub-text": "<0>Define text that will describe the correlation.</0>",
"title": "Define correlation label (Step 1 of 3)"
},
"empty-state": {
"button-title": "Add correlation",
"pro-tip": "You can also define correlations via datasource provisioning",
"title": "You haven't defined any correlations yet"
},
"list": {
"delete": "delete correlation",
"label": "Label",
@@ -355,6 +374,13 @@
"validation-required": "Need a dashboard JSON model"
}
},
"dashboard-links": {
"empty-state": {
"button-title": "Add dashboard link",
"info-box-content": "Dashboard links allow you to place links to other dashboards and web sites directly below the dashboard header. <2>Learn more</2>",
"title": "There are no dashboard links added yet"
}
},
"dashboard-settings": {
"annotations": {
"title": "Annotations"
@@ -405,6 +431,13 @@
"title": "Versions"
}
},
"data-source-list": {
"empty-state": {
"button-title": "Add data source",
"pro-tip": "You can also define data sources through configuration files. <2>Learn more</2>",
"title": "No data sources defined"
}
},
"data-source-picker": {
"add-new-data-source": "Configure a new data source",
"built-in-list": {
@@ -657,6 +690,10 @@
},
"add-widget": {
"title": "Add panel from panel library"
},
"empty-state": {
"message": "You haven't created any library panels yet",
"more-info": "Create a library panel from any existing dashboard panel through the panel context menu. <2>Learn more</2>"
}
},
"library-panels": {
@@ -1230,9 +1267,8 @@
"confirm-text": "Delete"
},
"empty": {
"button": "Create Playlist",
"pro-tip": "You can use playlists to cycle dashboards on TVs without user control",
"pro-tip-link-title": "Learn more",
"button": "Create playlist",
"pro-tip": "You can use playlists to cycle dashboards on TVs without user control. <2>Learn more</2>",
"title": "There are no playlists created yet"
}
},
@@ -1346,6 +1382,10 @@
"orphaned-title": "<0>Orphaned public dashboard</0>",
"orphaned-tooltip": "The linked dashboard has already been deleted"
},
"empty-state": {
"message": "You haven't created any public dashboards yet",
"more-info": "Create a public dashboard from any existing dashboard through the <1>Share</1> modal. <4>Learn more</4>"
},
"toggle": {
"pause-sharing-toggle-text": "Pause sharing"
}
@@ -1450,7 +1490,10 @@
},
"service-accounts": {
"empty-state": {
"message": "No services accounts found"
"button-title": "Add service account",
"message": "No services accounts found",
"more-info": "Remember, you can provide specific permissions for API access to other applications",
"title": "You haven't created any service accounts yet"
}
},
"share-modal": {
@@ -1570,7 +1613,17 @@
},
"title": "Preferences"
},
"silences": {
"empty-state": {
"button-title": "Create silence",
"title": "You haven't created any silences yet"
}
},
"snapshot": {
"empty-state": {
"message": "You haven't created any snapshots yet",
"more-info": "You can create a snapshot of any dashboard through the <1>Share</1> modal. <4>Learn more</4>"
},
"external-badge": "External",
"name-column-header": "Name",
"url-column-header": "Snapshot url",
@@ -1583,7 +1636,10 @@
},
"teams": {
"empty-state": {
"message": "No teams found"
"button-title": "New team",
"message": "No teams found",
"pro-tip": "Assign folder and dashboard permissions to teams instead of users to ease administration. <2>Learn more</2>",
"title": "You haven't created any teams yet"
}
},
"time-picker": {
@@ -1705,5 +1761,13 @@
"textbox": {
"placeholder": "Enter variable value"
}
},
"variables": {
"empty-state": {
"button-title": "Add variable",
"info-box-content": "Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or sensor names in your metric queries you can use variables in their place. Variables are shown as list boxes at the top of the dashboard. These drop-down lists make it easy to change the data being displayed in your dashboard.",
"info-box-content-2": "Check out the <2>Templates and variables documentation</2> for more information.",
"title": "There are no variables added yet"
}
}
}
+69 -5
View File
@@ -25,6 +25,14 @@
"user": "Ůşęř"
}
},
"annotations": {
"empty-state": {
"button-title": "Åđđ äʼnʼnőŧäŧįőʼn qūęřy",
"info-box-content": "<0>Åʼnʼnőŧäŧįőʼnş přővįđę ä ŵäy ŧő įʼnŧęģřäŧę ęvęʼnŧ đäŧä įʼnŧő yőūř ģřäpĥş. Ŧĥęy äřę vįşūäľįžęđ äş vęřŧįčäľ ľįʼnęş äʼnđ įčőʼnş őʼn äľľ ģřäpĥ päʼnęľş. Ŵĥęʼn yőū ĥővęř ővęř äʼn äʼnʼnőŧäŧįőʼn įčőʼn yőū čäʼn ģęŧ ęvęʼnŧ ŧęχŧ & ŧäģş ƒőř ŧĥę ęvęʼnŧ. Ÿőū čäʼn äđđ äʼnʼnőŧäŧįőʼn ęvęʼnŧş đįřęčŧľy ƒřőm ģřäƒäʼnä þy ĥőľđįʼnģ CŦŖĿ őř CMĐ + čľįčĸ őʼn ģřäpĥ (őř đřäģ řęģįőʼn). Ŧĥęşę ŵįľľ þę şŧőřęđ įʼn Ğřäƒäʼnä'ş äʼnʼnőŧäŧįőʼn đäŧäþäşę.</0>",
"info-box-content-2": "Cĥęčĸőūŧ ŧĥę <2>Åʼnʼnőŧäŧįőʼnş đőčūmęʼnŧäŧįőʼn</2> ƒőř mőřę įʼnƒőřmäŧįőʼn.",
"title": "Ŧĥęřę äřę ʼnő čūşŧőm äʼnʼnőŧäŧįőʼn qūęřįęş äđđęđ yęŧ"
}
},
"api-keys": {
"empty-state": {
"message": "Ńő ÅPĨ ĸęyş ƒőūʼnđ"
@@ -72,6 +80,12 @@
"select-checkbox": "Ŝęľęčŧ",
"tags-column": "Ŧäģş"
},
"empty-state": {
"button-title": "Cřęäŧę đäşĥþőäřđ",
"pro-tip": "Åđđ/mővę đäşĥþőäřđş ŧő yőūř ƒőľđęř äŧ <2>ßřőŵşę đäşĥþőäřđş</2>",
"title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny đäşĥþőäřđş yęŧ",
"title-folder": "Ŧĥįş ƒőľđęř đőęşʼn'ŧ ĥävę äʼny đäşĥþőäřđş yęŧ"
},
"folder-actions-button": {
"delete": "Đęľęŧę",
"folder-actions": "Főľđęř äčŧįőʼnş",
@@ -159,6 +173,11 @@
"sub-text": "<0>Đęƒįʼnę ŧęχŧ ŧĥäŧ ŵįľľ đęşčřįþę ŧĥę čőřřęľäŧįőʼn.</0>",
"title": "Đęƒįʼnę čőřřęľäŧįőʼn ľäþęľ (Ŝŧęp 1 őƒ 3)"
},
"empty-state": {
"button-title": "Åđđ čőřřęľäŧįőʼn",
"pro-tip": "Ÿőū čäʼn äľşő đęƒįʼnę čőřřęľäŧįőʼnş vįä đäŧäşőūřčę přővįşįőʼnįʼnģ",
"title": "Ÿőū ĥävęʼn'ŧ đęƒįʼnęđ äʼny čőřřęľäŧįőʼnş yęŧ"
},
"list": {
"delete": "đęľęŧę čőřřęľäŧįőʼn",
"label": "Ŀäþęľ",
@@ -355,6 +374,13 @@
"validation-required": "Ńęęđ ä đäşĥþőäřđ ĴŜØŃ mőđęľ"
}
},
"dashboard-links": {
"empty-state": {
"button-title": "Åđđ đäşĥþőäřđ ľįʼnĸ",
"info-box-content": "Đäşĥþőäřđ ľįʼnĸş äľľőŵ yőū ŧő pľäčę ľįʼnĸş ŧő őŧĥęř đäşĥþőäřđş äʼnđ ŵęþ şįŧęş đįřęčŧľy þęľőŵ ŧĥę đäşĥþőäřđ ĥęäđęř. <2>Ŀęäřʼn mőřę</2>",
"title": "Ŧĥęřę äřę ʼnő đäşĥþőäřđ ľįʼnĸş äđđęđ yęŧ"
}
},
"dashboard-settings": {
"annotations": {
"title": "Åʼnʼnőŧäŧįőʼnş"
@@ -405,6 +431,13 @@
"title": "Vęřşįőʼnş"
}
},
"data-source-list": {
"empty-state": {
"button-title": "Åđđ đäŧä şőūřčę",
"pro-tip": "Ÿőū čäʼn äľşő đęƒįʼnę đäŧä şőūřčęş ŧĥřőūģĥ čőʼnƒįģūřäŧįőʼn ƒįľęş. <2>Ŀęäřʼn mőřę</2>",
"title": "Ńő đäŧä şőūřčęş đęƒįʼnęđ"
}
},
"data-source-picker": {
"add-new-data-source": "Cőʼnƒįģūřę ä ʼnęŵ đäŧä şőūřčę",
"built-in-list": {
@@ -657,6 +690,10 @@
},
"add-widget": {
"title": "Åđđ päʼnęľ ƒřőm päʼnęľ ľįþřäřy"
},
"empty-state": {
"message": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny ľįþřäřy päʼnęľş yęŧ",
"more-info": "Cřęäŧę ä ľįþřäřy päʼnęľ ƒřőm äʼny ęχįşŧįʼnģ đäşĥþőäřđ päʼnęľ ŧĥřőūģĥ ŧĥę päʼnęľ čőʼnŧęχŧ męʼnū. <2>Ŀęäřʼn mőřę</2>"
}
},
"library-panels": {
@@ -1230,9 +1267,8 @@
"confirm-text": "Đęľęŧę"
},
"empty": {
"button": "Cřęäŧę Pľäyľįşŧ",
"pro-tip": "Ÿőū čäʼn ūşę pľäyľįşŧş ŧő čyčľę đäşĥþőäřđş őʼn ŦVş ŵįŧĥőūŧ ūşęř čőʼnŧřőľ",
"pro-tip-link-title": "Ŀęäřʼn mőřę",
"button": "Cřęäŧę pľäyľįşŧ",
"pro-tip": "Ÿőū čäʼn ūşę pľäyľįşŧş ŧő čyčľę đäşĥþőäřđş őʼn ŦVş ŵįŧĥőūŧ ūşęř čőʼnŧřőľ. <2>Ŀęäřʼn mőřę</2>",
"title": "Ŧĥęřę äřę ʼnő pľäyľįşŧş čřęäŧęđ yęŧ"
}
},
@@ -1346,6 +1382,10 @@
"orphaned-title": "<0>Øřpĥäʼnęđ pūþľįč đäşĥþőäřđ</0>",
"orphaned-tooltip": "Ŧĥę ľįʼnĸęđ đäşĥþőäřđ ĥäş äľřęäđy þęęʼn đęľęŧęđ"
},
"empty-state": {
"message": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny pūþľįč đäşĥþőäřđş yęŧ",
"more-info": "Cřęäŧę ä pūþľįč đäşĥþőäřđ ƒřőm äʼny ęχįşŧįʼnģ đäşĥþőäřđ ŧĥřőūģĥ ŧĥę <1>Ŝĥäřę</1> mőđäľ. <4>Ŀęäřʼn mőřę</4>"
},
"toggle": {
"pause-sharing-toggle-text": "Päūşę şĥäřįʼnģ"
}
@@ -1450,7 +1490,10 @@
},
"service-accounts": {
"empty-state": {
"message": "Ńő şęřvįčęş äččőūʼnŧş ƒőūʼnđ"
"button-title": "Åđđ şęřvįčę äččőūʼnŧ",
"message": "Ńő şęřvįčęş äččőūʼnŧş ƒőūʼnđ",
"more-info": "Ŗęmęmþęř, yőū čäʼn přővįđę şpęčįƒįč pęřmįşşįőʼnş ƒőř ÅPĨ äččęşş ŧő őŧĥęř äppľįčäŧįőʼnş",
"title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny şęřvįčę äččőūʼnŧş yęŧ"
}
},
"share-modal": {
@@ -1570,7 +1613,17 @@
},
"title": "Přęƒęřęʼnčęş"
},
"silences": {
"empty-state": {
"button-title": "Cřęäŧę şįľęʼnčę",
"title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny şįľęʼnčęş yęŧ"
}
},
"snapshot": {
"empty-state": {
"message": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny şʼnäpşĥőŧş yęŧ",
"more-info": "Ÿőū čäʼn čřęäŧę ä şʼnäpşĥőŧ őƒ äʼny đäşĥþőäřđ ŧĥřőūģĥ ŧĥę <1>Ŝĥäřę</1> mőđäľ. <4>Ŀęäřʼn mőřę</4>"
},
"external-badge": "Ēχŧęřʼnäľ",
"name-column-header": "Ńämę",
"url-column-header": "Ŝʼnäpşĥőŧ ūřľ",
@@ -1583,7 +1636,10 @@
},
"teams": {
"empty-state": {
"message": "Ńő ŧęämş ƒőūʼnđ"
"button-title": "Ńęŵ ŧęäm",
"message": "Ńő ŧęämş ƒőūʼnđ",
"pro-tip": "Åşşįģʼn ƒőľđęř äʼnđ đäşĥþőäřđ pęřmįşşįőʼnş ŧő ŧęämş įʼnşŧęäđ őƒ ūşęřş ŧő ęäşę äđmįʼnįşŧřäŧįőʼn. <2>Ŀęäřʼn mőřę</2>",
"title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny ŧęämş yęŧ"
}
},
"time-picker": {
@@ -1705,5 +1761,13 @@
"textbox": {
"placeholder": "Ēʼnŧęř väřįäþľę väľūę"
}
},
"variables": {
"empty-state": {
"button-title": "Åđđ väřįäþľę",
"info-box-content": "Väřįäþľęş ęʼnäþľę mőřę įʼnŧęřäčŧįvę äʼnđ đyʼnämįč đäşĥþőäřđş. Ĩʼnşŧęäđ őƒ ĥäřđ-čőđįʼnģ ŧĥįʼnģş ľįĸę şęřvęř őř şęʼnşőř ʼnämęş įʼn yőūř męŧřįč qūęřįęş yőū čäʼn ūşę väřįäþľęş įʼn ŧĥęįř pľäčę. Väřįäþľęş äřę şĥőŵʼn äş ľįşŧ þőχęş äŧ ŧĥę ŧőp őƒ ŧĥę đäşĥþőäřđ. Ŧĥęşę đřőp-đőŵʼn ľįşŧş mäĸę įŧ ęäşy ŧő čĥäʼnģę ŧĥę đäŧä þęįʼnģ đįşpľäyęđ įʼn yőūř đäşĥþőäřđ.",
"info-box-content-2": "Cĥęčĸ őūŧ ŧĥę <2>Ŧęmpľäŧęş äʼnđ väřįäþľęş đőčūmęʼnŧäŧįőʼn</2> ƒőř mőřę įʼnƒőřmäŧįőʼn.",
"title": "Ŧĥęřę äřę ʼnő väřįäþľęş äđđęđ yęŧ"
}
}
}