mirror of https://github.com/grafana/grafana.git
Datasource view: Show a not-found message (#110417)
This commit is contained in:
parent
0c44a0c14a
commit
13baef080c
|
@ -52,6 +52,18 @@ export function useDataSourceSettingsNav(pageIdParam?: string) {
|
||||||
pageNav = getNavModel(navIndex, navIndexId, getDataSourceLoadingNav('settings'));
|
pageNav = getNavModel(navIndex, navIndexId, getDataSourceLoadingNav('settings'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!datasource.uid) {
|
||||||
|
const node: NavModelItem = {
|
||||||
|
text: t('connections.use-data-source-settings-nav.node.subTitle.data-source-error', 'Data Source Error'),
|
||||||
|
icon: 'exclamation-triangle',
|
||||||
|
};
|
||||||
|
|
||||||
|
pageNav = {
|
||||||
|
node: node,
|
||||||
|
main: node,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (plugin) {
|
if (plugin) {
|
||||||
pageNav = getNavModel(
|
pageNav = getNavModel(
|
||||||
navIndex,
|
navIndex,
|
||||||
|
@ -64,8 +76,8 @@ export function useDataSourceSettingsNav(pageIdParam?: string) {
|
||||||
...pageNav.main,
|
...pageNav.main,
|
||||||
dataSourcePluginName: datasourcePlugin?.name || plugin?.meta.name || '',
|
dataSourcePluginName: datasourcePlugin?.name || plugin?.meta.name || '',
|
||||||
active: true,
|
active: true,
|
||||||
text: datasource.name,
|
text: datasource.name || '',
|
||||||
subTitle: `Type: ${dataSourceMeta.name}`,
|
subTitle: dataSourceMeta.name ? `Type: ${dataSourceMeta.name}` : '',
|
||||||
children: (pageNav.main.children || []).map((navModelItem) => ({
|
children: (pageNav.main.children || []).map((navModelItem) => ({
|
||||||
...navModelItem,
|
...navModelItem,
|
||||||
url: navModelItem.url?.replace('datasources/edit/', '/connections/datasources/edit/'),
|
url: navModelItem.url?.replace('datasources/edit/', '/connections/datasources/edit/'),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Trans } from '@grafana/i18n';
|
import { t, Trans } from '@grafana/i18n';
|
||||||
import { Button } from '@grafana/ui';
|
import { Button, EmptyState } from '@grafana/ui';
|
||||||
|
|
||||||
import { DataSourceRights } from '../types';
|
import { DataSourceRights } from '../types';
|
||||||
|
|
||||||
|
@ -8,9 +8,10 @@ import { DataSourceReadOnlyMessage } from './DataSourceReadOnlyMessage';
|
||||||
export type Props = {
|
export type Props = {
|
||||||
dataSourceRights: DataSourceRights;
|
dataSourceRights: DataSourceRights;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
|
notFound: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DataSourceLoadError({ dataSourceRights, onDelete }: Props) {
|
export function DataSourceLoadError({ dataSourceRights, onDelete, notFound }: Props) {
|
||||||
const { readOnly, hasDeleteRights } = dataSourceRights;
|
const { readOnly, hasDeleteRights } = dataSourceRights;
|
||||||
const canDelete = !readOnly && hasDeleteRights;
|
const canDelete = !readOnly && hasDeleteRights;
|
||||||
const navigateBack = () => window.history.back();
|
const navigateBack = () => window.history.back();
|
||||||
|
@ -20,6 +21,12 @@ export function DataSourceLoadError({ dataSourceRights, onDelete }: Props) {
|
||||||
{readOnly && <DataSourceReadOnlyMessage />}
|
{readOnly && <DataSourceReadOnlyMessage />}
|
||||||
|
|
||||||
<div className="gf-form-button-row">
|
<div className="gf-form-button-row">
|
||||||
|
{notFound && (
|
||||||
|
<EmptyState
|
||||||
|
variant="not-found"
|
||||||
|
message={t('datasources.data-source-load-error.not-found', 'Data source not found')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{canDelete && (
|
{canDelete && (
|
||||||
<Button type="submit" variant="destructive" onClick={onDelete}>
|
<Button type="submit" variant="destructive" onClick={onDelete}>
|
||||||
<Trans i18nKey="datasources.data-source-load-error.delete">Delete</Trans>
|
<Trans i18nKey="datasources.data-source-load-error.delete">Delete</Trans>
|
||||||
|
|
|
@ -108,6 +108,14 @@ describe('<EditDataSource>', () => {
|
||||||
|
|
||||||
expect(screen.queryByText(readOnlyMessage)).toBeVisible();
|
expect(screen.queryByText(readOnlyMessage)).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render a message if the datasource is not found', () => {
|
||||||
|
setup({
|
||||||
|
dataSource: getMockDataSource({ uid: undefined, id: 0 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByText('Data source not found')).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('On loading', () => {
|
describe('On loading', () => {
|
||||||
|
|
|
@ -114,7 +114,7 @@ export function EditDataSourceView({
|
||||||
}: ViewProps) {
|
}: ViewProps) {
|
||||||
const { plugin, loadError, testingStatus, loading } = dataSourceSettings;
|
const { plugin, loadError, testingStatus, loading } = dataSourceSettings;
|
||||||
const { readOnly, hasWriteRights, hasDeleteRights } = dataSourceRights;
|
const { readOnly, hasWriteRights, hasDeleteRights } = dataSourceRights;
|
||||||
const hasDataSource = dataSource.id > 0;
|
const hasDataSource = dataSource.id > 0 && dataSource.uid;
|
||||||
const { components, isLoading } = useDataSourceConfigPluginExtensions();
|
const { components, isLoading } = useDataSourceConfigPluginExtensions();
|
||||||
// This is a workaround to avoid race-conditions between the `setSecureJsonData()` and `setJsonData()` calls instantiated by the extension components.
|
// This is a workaround to avoid race-conditions between the `setSecureJsonData()` and `setJsonData()` calls instantiated by the extension components.
|
||||||
// Both those exposed functions are calling `onOptionsChange()` with the new jsonData and secureJsonData, and if they are called in the same tick, the Redux store
|
// Both those exposed functions are calling `onOptionsChange()` with the new jsonData and secureJsonData, and if they are called in the same tick, the Redux store
|
||||||
|
@ -150,9 +150,14 @@ export function EditDataSourceView({
|
||||||
onTest();
|
onTest();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loadError) {
|
if (loading || isLoading) {
|
||||||
|
return <PageLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadError || !hasDataSource || !dsi) {
|
||||||
return (
|
return (
|
||||||
<DataSourceLoadError
|
<DataSourceLoadError
|
||||||
|
notFound={!hasDataSource || !dsi}
|
||||||
dataSourceRights={dataSourceRights}
|
dataSourceRights={dataSourceRights}
|
||||||
onDelete={() => {
|
onDelete={() => {
|
||||||
trackDsConfigClicked('delete');
|
trackDsConfigClicked('delete');
|
||||||
|
@ -162,15 +167,6 @@ export function EditDataSourceView({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || isLoading) {
|
|
||||||
return <PageLoader />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - is this needed?
|
|
||||||
if (!hasDataSource || !dsi) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageId) {
|
if (pageId) {
|
||||||
return (
|
return (
|
||||||
<DataSourcePluginContextProvider instanceSettings={dsi}>
|
<DataSourcePluginContextProvider instanceSettings={dsi}>
|
||||||
|
|
|
@ -90,7 +90,7 @@ const mockDataSource = getMockDataSource({
|
||||||
|
|
||||||
// Mock useDataSource hook
|
// Mock useDataSource hook
|
||||||
jest.mock('../state/hooks', () => ({
|
jest.mock('../state/hooks', () => ({
|
||||||
useDataSource: () => mockDataSource,
|
useDataSource: (uid: string) => (uid === 'not-found' ? {} : mockDataSource),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('EditDataSourceActions', () => {
|
describe('EditDataSourceActions', () => {
|
||||||
|
@ -374,6 +374,14 @@ describe('EditDataSourceActions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DataSource Not Found', () => {
|
||||||
|
it('should not render actions when data source is not found', () => {
|
||||||
|
render(<EditDataSourceActions uid="not-found" />);
|
||||||
|
expect(screen.queryByText('Explore data')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Build a dashboard')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Favorite Actions', () => {
|
describe('Favorite Actions', () => {
|
||||||
it('should not render favorite button when feature toggle is disabled', () => {
|
it('should not render favorite button when feature toggle is disabled', () => {
|
||||||
config.featureToggles.favoriteDatasources = false;
|
config.featureToggles.favoriteDatasources = false;
|
||||||
|
|
|
@ -90,6 +90,10 @@ export function EditDataSourceActions({ uid }: Props) {
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!dataSource.uid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FavoriteButton uid={uid} />
|
<FavoriteButton uid={uid} />
|
||||||
|
|
|
@ -11,6 +11,10 @@ export const useDataSourceInfo = (dataSourceInfo: DataSourceInfo): PageInfoItem[
|
||||||
const info: PageInfoItem[] = [];
|
const info: PageInfoItem[] = [];
|
||||||
const alertingEnabled = dataSourceInfo.alertingSupported;
|
const alertingEnabled = dataSourceInfo.alertingSupported;
|
||||||
|
|
||||||
|
if (!dataSourceInfo.dataSourcePluginName) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
info.push({
|
info.push({
|
||||||
label: t('datasources.use-data-source-info.label.type', 'Type'),
|
label: t('datasources.use-data-source-info.label.type', 'Type'),
|
||||||
value: dataSourceInfo.dataSourcePluginName,
|
value: dataSourceInfo.dataSourcePluginName,
|
||||||
|
|
|
@ -6543,7 +6543,8 @@
|
||||||
},
|
},
|
||||||
"data-source-load-error": {
|
"data-source-load-error": {
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"delete": "Delete"
|
"delete": "Delete",
|
||||||
|
"not-found": "Data source not found"
|
||||||
},
|
},
|
||||||
"data-source-missing-rights-message": {
|
"data-source-missing-rights-message": {
|
||||||
"title-missing-rights": "Missing rights"
|
"title-missing-rights": "Missing rights"
|
||||||
|
|
Loading…
Reference in New Issue