mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Datasource Plugins: Allow tracking for configuration usage (#72650)
Datasource Plugins: Allow tracking for configuration usage
This commit is contained in:
parent
867162b64a
commit
6ac3348021
@ -54,7 +54,13 @@ export type DashboardLoadedEventPayload<T> = {
|
|||||||
export class DashboardLoadedEvent<T> extends BusEventWithPayload<DashboardLoadedEventPayload<T>> {
|
export class DashboardLoadedEvent<T> extends BusEventWithPayload<DashboardLoadedEventPayload<T>> {
|
||||||
static type = 'dashboard-loaded';
|
static type = 'dashboard-loaded';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataSourceUpdatedSuccessfully extends BusEventBase {
|
export class DataSourceUpdatedSuccessfully extends BusEventBase {
|
||||||
static type = 'datasource-updated-successfully';
|
static type = 'datasource-updated-successfully';
|
||||||
}
|
}
|
||||||
|
export class DataSourceTestSucceeded extends BusEventBase {
|
||||||
|
static type = 'datasource-test-succeeded';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DataSourceTestFailed extends BusEventBase {
|
||||||
|
static type = 'datasource-test-failed';
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { thunkTester } from 'test/core/thunk/thunkTester';
|
|||||||
|
|
||||||
import { AppPluginMeta, DataSourceSettings, PluginMetaInfo, PluginType } from '@grafana/data';
|
import { AppPluginMeta, DataSourceSettings, PluginMetaInfo, PluginType } from '@grafana/data';
|
||||||
import { FetchError } from '@grafana/runtime';
|
import { FetchError } from '@grafana/runtime';
|
||||||
|
import { appEvents } from 'app/core/core';
|
||||||
import { ThunkResult, ThunkDispatch } from 'app/types';
|
import { ThunkResult, ThunkDispatch } from 'app/types';
|
||||||
|
|
||||||
import { getMockDataSource } from '../__mocks__';
|
import { getMockDataSource } from '../__mocks__';
|
||||||
@ -30,7 +31,12 @@ import {
|
|||||||
|
|
||||||
jest.mock('../api');
|
jest.mock('../api');
|
||||||
jest.mock('app/core/services/backend_srv');
|
jest.mock('app/core/services/backend_srv');
|
||||||
jest.mock('app/core/core');
|
jest.mock('app/core/core', () => ({
|
||||||
|
...jest.requireActual('app/core/core'),
|
||||||
|
appEvents: {
|
||||||
|
publish: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
getDataSourceSrv: jest.fn().mockReturnValue({ reload: jest.fn() }),
|
getDataSourceSrv: jest.fn().mockReturnValue({ reload: jest.fn() }),
|
||||||
@ -331,6 +337,39 @@ describe('testDataSource', () => {
|
|||||||
const dispatchedActions = await failDataSourceTest(error);
|
const dispatchedActions = await failDataSourceTest(error);
|
||||||
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
|
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('publishes an app event when the test succeeds', async () => {
|
||||||
|
const dependencies: TestDataSourceDependencies = {
|
||||||
|
getDatasourceSrv: () => ({
|
||||||
|
get: jest.fn().mockReturnValue({
|
||||||
|
testDatasource: jest.fn().mockReturnValue({
|
||||||
|
status: 'success',
|
||||||
|
message: '',
|
||||||
|
}),
|
||||||
|
type: 'cloudwatch',
|
||||||
|
uid: 'CW1234',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
getBackendSrv: getBackendSrvMock,
|
||||||
|
};
|
||||||
|
await thunkTester({})
|
||||||
|
.givenThunk(testDataSource)
|
||||||
|
.whenThunkIsDispatched('CloudWatch', DATASOURCES_ROUTES.Edit, dependencies);
|
||||||
|
expect(appEvents.publish).toHaveBeenCalledWith({ type: 'datasource-test-succeeded' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('publishes an app event when the test fails', async () => {
|
||||||
|
const error: FetchError = {
|
||||||
|
config: {
|
||||||
|
url: '',
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
statusText: 'Bad Request',
|
||||||
|
status: 400,
|
||||||
|
};
|
||||||
|
await failDataSourceTest(error);
|
||||||
|
expect(appEvents.publish).toHaveBeenCalledWith({ type: 'datasource-test-failed' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
import { DataSourcePluginMeta, DataSourceSettings, locationUtil, TestDataSourceResponse } from '@grafana/data';
|
import {
|
||||||
|
DataSourcePluginMeta,
|
||||||
|
DataSourceSettings,
|
||||||
|
locationUtil,
|
||||||
|
TestDataSourceResponse,
|
||||||
|
DataSourceTestSucceeded,
|
||||||
|
DataSourceTestFailed,
|
||||||
|
} from '@grafana/data';
|
||||||
import {
|
import {
|
||||||
config,
|
config,
|
||||||
|
DataSourceSrv,
|
||||||
DataSourceWithBackend,
|
DataSourceWithBackend,
|
||||||
getDataSourceSrv,
|
|
||||||
HealthCheckError,
|
HealthCheckError,
|
||||||
HealthCheckResultDetails,
|
HealthCheckResultDetails,
|
||||||
isFetchError,
|
isFetchError,
|
||||||
locationService,
|
locationService,
|
||||||
} from '@grafana/runtime';
|
} from '@grafana/runtime';
|
||||||
import { updateNavIndex } from 'app/core/actions';
|
import { updateNavIndex } from 'app/core/actions';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { appEvents, contextSrv } from 'app/core/core';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { ROUTES as CONNECTIONS_ROUTES } from 'app/features/connections/constants';
|
import { ROUTES as CONNECTIONS_ROUTES } from 'app/features/connections/constants';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
@ -53,7 +60,7 @@ export interface InitDataSourceSettingDependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TestDataSourceDependencies {
|
export interface TestDataSourceDependencies {
|
||||||
getDatasourceSrv: typeof getDataSourceSrv;
|
getDatasourceSrv: () => Pick<DataSourceSrv, 'get'>;
|
||||||
getBackendSrv: typeof getBackendSrv;
|
getBackendSrv: typeof getBackendSrv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +157,7 @@ export const testDataSource = (
|
|||||||
success: true,
|
success: true,
|
||||||
path: editLink,
|
path: editLink,
|
||||||
});
|
});
|
||||||
|
appEvents.publish(new DataSourceTestSucceeded());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const formattedError = parseHealthCheckError(err);
|
const formattedError = parseHealthCheckError(err);
|
||||||
|
|
||||||
@ -161,6 +169,7 @@ export const testDataSource = (
|
|||||||
success: false,
|
success: false,
|
||||||
path: editLink,
|
path: editLink,
|
||||||
});
|
});
|
||||||
|
appEvents.publish(new DataSourceTestFailed());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import React from 'react';
|
|||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { AwsAuthType } from '@grafana/aws-sdk';
|
import { AwsAuthType } from '@grafana/aws-sdk';
|
||||||
|
import { PluginContextProvider, PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data';
|
||||||
import { configureStore } from 'app/store/configureStore';
|
import { configureStore } from 'app/store/configureStore';
|
||||||
|
|
||||||
import { CloudWatchSettings, setupMockedDataSource } from '../__mocks__/CloudWatchDataSource';
|
import { CloudWatchSettings, setupMockedDataSource } from '../__mocks__/CloudWatchDataSource';
|
||||||
@ -25,12 +26,16 @@ jest.mock('./XrayLinkConfig', () => ({
|
|||||||
|
|
||||||
const putMock = jest.fn();
|
const putMock = jest.fn();
|
||||||
const getMock = jest.fn();
|
const getMock = jest.fn();
|
||||||
|
const mockAppEvents = {
|
||||||
|
subscribe: () => ({ unsubscribe: jest.fn() }),
|
||||||
|
};
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
getBackendSrv: () => ({
|
getBackendSrv: () => ({
|
||||||
put: putMock,
|
put: putMock,
|
||||||
get: getMock,
|
get: getMock,
|
||||||
}),
|
}),
|
||||||
|
getAppEvents: () => mockAppEvents,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
@ -82,11 +87,21 @@ const setup = (optionOverrides?: Partial<Props['options']>) => {
|
|||||||
...optionOverrides,
|
...optionOverrides,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const meta: PluginMeta = {
|
||||||
|
...newProps.options,
|
||||||
|
id: 'cloudwatch',
|
||||||
|
type: PluginType.datasource,
|
||||||
|
info: {} as PluginMetaInfo,
|
||||||
|
module: '',
|
||||||
|
baseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
render(
|
return render(
|
||||||
<Provider store={store}>
|
<PluginContextProvider meta={meta}>
|
||||||
<ConfigEditor {...newProps} />
|
<Provider store={store}>
|
||||||
</Provider>
|
<ConfigEditor {...newProps} />
|
||||||
|
</Provider>
|
||||||
|
</PluginContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -177,43 +192,19 @@ describe('Render', () => {
|
|||||||
|
|
||||||
it('should load the data source if it was saved before', async () => {
|
it('should load the data source if it was saved before', async () => {
|
||||||
const SAVED_VERSION = 2;
|
const SAVED_VERSION = 2;
|
||||||
const newProps = {
|
setup({ version: SAVED_VERSION });
|
||||||
...props,
|
|
||||||
options: {
|
|
||||||
...props.options,
|
|
||||||
version: SAVED_VERSION,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render(<ConfigEditor {...newProps} />);
|
|
||||||
await waitFor(async () => expect(loadDataSourceMock).toHaveBeenCalled());
|
await waitFor(async () => expect(loadDataSourceMock).toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not load the data source if it wasnt saved before', async () => {
|
it('should not load the data source if it wasnt saved before', async () => {
|
||||||
const SAVED_VERSION = undefined;
|
const SAVED_VERSION = undefined;
|
||||||
const newProps = {
|
setup({ version: SAVED_VERSION });
|
||||||
...props,
|
|
||||||
options: {
|
|
||||||
...props.options,
|
|
||||||
version: SAVED_VERSION,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render(<ConfigEditor {...newProps} />);
|
|
||||||
await waitFor(async () => expect(loadDataSourceMock).not.toHaveBeenCalled());
|
await waitFor(async () => expect(loadDataSourceMock).not.toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show error message if Select log group button is clicked when data source is never saved', async () => {
|
it('should show error message if Select log group button is clicked when data source is never saved', async () => {
|
||||||
const SAVED_VERSION = undefined;
|
const SAVED_VERSION = undefined;
|
||||||
const newProps = {
|
setup({ version: SAVED_VERSION });
|
||||||
...props,
|
|
||||||
options: {
|
|
||||||
...props.options,
|
|
||||||
version: SAVED_VERSION,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render(<ConfigEditor {...newProps} />);
|
|
||||||
|
|
||||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||||
await userEvent.click(screen.getByText('Select log groups'));
|
await userEvent.click(screen.getByText('Select log groups'));
|
||||||
@ -231,7 +222,20 @@ describe('Render', () => {
|
|||||||
version: SAVED_VERSION,
|
version: SAVED_VERSION,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { rerender } = render(<ConfigEditor {...newProps} />);
|
const meta: PluginMeta = {
|
||||||
|
...newProps.options,
|
||||||
|
id: 'cloudwatch',
|
||||||
|
type: PluginType.datasource,
|
||||||
|
info: {} as PluginMetaInfo,
|
||||||
|
module: '',
|
||||||
|
baseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<PluginContextProvider meta={meta}>
|
||||||
|
<ConfigEditor {...newProps} />
|
||||||
|
</PluginContextProvider>
|
||||||
|
);
|
||||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||||
const rerenderProps = {
|
const rerenderProps = {
|
||||||
...newProps,
|
...newProps,
|
||||||
@ -243,7 +247,11 @@ describe('Render', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
rerender(<ConfigEditor {...rerenderProps} />);
|
rerender(
|
||||||
|
<PluginContextProvider meta={meta}>
|
||||||
|
<ConfigEditor {...rerenderProps} />
|
||||||
|
</PluginContextProvider>
|
||||||
|
);
|
||||||
await waitFor(() => expect(screen.getByText('AWS SDK Default')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText('AWS SDK Default')).toBeInTheDocument());
|
||||||
await userEvent.click(screen.getByText('Select log groups'));
|
await userEvent.click(screen.getByText('Select log groups'));
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
@ -264,7 +272,19 @@ describe('Render', () => {
|
|||||||
version: SAVED_VERSION,
|
version: SAVED_VERSION,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { rerender } = render(<ConfigEditor {...newProps} />);
|
const meta: PluginMeta = {
|
||||||
|
...newProps.options,
|
||||||
|
id: 'cloudwatch',
|
||||||
|
type: PluginType.datasource,
|
||||||
|
info: {} as PluginMetaInfo,
|
||||||
|
module: '',
|
||||||
|
baseUrl: '',
|
||||||
|
};
|
||||||
|
const { rerender } = render(
|
||||||
|
<PluginContextProvider meta={meta}>
|
||||||
|
<ConfigEditor {...newProps} />
|
||||||
|
</PluginContextProvider>
|
||||||
|
);
|
||||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||||
const rerenderProps = {
|
const rerenderProps = {
|
||||||
...newProps,
|
...newProps,
|
||||||
@ -273,7 +293,11 @@ describe('Render', () => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
rerender(<ConfigEditor {...rerenderProps} />);
|
rerender(
|
||||||
|
<PluginContextProvider meta={meta}>
|
||||||
|
<ConfigEditor {...rerenderProps} />
|
||||||
|
</PluginContextProvider>
|
||||||
|
);
|
||||||
await userEvent.click(screen.getByText('Select log groups'));
|
await userEvent.click(screen.getByText('Select log groups'));
|
||||||
await waitFor(() => expect(screen.getByText('Log group name prefix')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText('Log group name prefix')).toBeInTheDocument());
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,10 @@ import {
|
|||||||
DataSourcePluginOptionsEditorProps,
|
DataSourcePluginOptionsEditorProps,
|
||||||
onUpdateDatasourceJsonDataOption,
|
onUpdateDatasourceJsonDataOption,
|
||||||
updateDatasourcePluginJsonDataOption,
|
updateDatasourcePluginJsonDataOption,
|
||||||
|
DataSourceTestSucceeded,
|
||||||
|
DataSourceTestFailed,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import { getAppEvents, usePluginInteractionReporter } from '@grafana/runtime';
|
||||||
import { Input, InlineField, FieldProps, SecureSocksProxySettings } from '@grafana/ui';
|
import { Input, InlineField, FieldProps, SecureSocksProxySettings } from '@grafana/ui';
|
||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
@ -37,6 +40,23 @@ export const ConfigEditor = (props: Props) => {
|
|||||||
invalid: false,
|
invalid: false,
|
||||||
});
|
});
|
||||||
useEffect(() => setLogGroupFieldState({ invalid: false }), [props.options]);
|
useEffect(() => setLogGroupFieldState({ invalid: false }), [props.options]);
|
||||||
|
const report = usePluginInteractionReporter();
|
||||||
|
useEffect(() => {
|
||||||
|
const successSubscription = getAppEvents().subscribe<DataSourceTestSucceeded>(DataSourceTestSucceeded, () => {
|
||||||
|
report('grafana_plugin_cloudwatch_save_succeeded', {
|
||||||
|
auth_type: options.jsonData.authType,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const failSubscription = getAppEvents().subscribe<DataSourceTestFailed>(DataSourceTestFailed, () => {
|
||||||
|
report('grafana_plugin_cloudwatch_save_failed', {
|
||||||
|
auth_type: options.jsonData.authType,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
successSubscription.unsubscribe();
|
||||||
|
failSubscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [options.jsonData.authType, report]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
Loading…
Reference in New Issue
Block a user