diff --git a/.betterer.results b/.betterer.results index 742646bc1ba..c3afe7fd90f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3947,7 +3947,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Do not use any type assertions.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Do not use any type assertions.", "6"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"], [0, 0, 0, "Unexpected any. Specify a different type.", "9"], @@ -3955,9 +3955,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "11"], [0, 0, 0, "Unexpected any. Specify a different type.", "12"], [0, 0, 0, "Unexpected any. Specify a different type.", "13"], - [0, 0, 0, "Unexpected any. Specify a different type.", "14"], - [0, 0, 0, "Unexpected any. Specify a different type.", "15"], - [0, 0, 0, "Unexpected any. Specify a different type.", "16"] + [0, 0, 0, "Unexpected any. Specify a different type.", "14"] ], "public/app/features/manage-dashboards/state/reducers.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index 315381d1632..be06306717b 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -3,12 +3,20 @@ import { lastValueFrom } from 'rxjs'; import { isTruthy, locationUtil } from '@grafana/data'; import { BackendSrvRequest, getBackendSrv, locationService } from '@grafana/runtime'; +import { Dashboard } from '@grafana/schema'; import { notifyApp } from 'app/core/actions'; import { createSuccessNotification } from 'app/core/copy/appNotification'; import { contextSrv } from 'app/core/core'; import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; -import { DashboardDTO, DescendantCount, DescendantCountDTO, FolderDTO, SaveDashboardResponseDTO } from 'app/types'; +import { + DashboardDTO, + DescendantCount, + DescendantCountDTO, + FolderDTO, + ImportDashboardResponseDTO, + SaveDashboardResponseDTO, +} from 'app/types'; import { refetchChildren, refreshParents } from '../state'; import { DashboardTreeSelection } from '../types'; @@ -28,6 +36,20 @@ interface MoveItemsArgs extends DeleteItemsArgs { destinationUID: string; } +export interface ImportInputs { + name: string; + type: string; + value: string; + pluginId?: string; +} + +interface ImportOptions { + dashboard: Dashboard; + overwrite: boolean; + inputs: ImportInputs[]; + folderUid: string; +} + function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn { async function backendSrvBaseQuery(requestOptions: RequestOptions) { try { @@ -299,6 +321,30 @@ export const browseDashboardsAPI = createApi({ }); }, }), + importDashboard: builder.mutation({ + query: ({ dashboard, overwrite, inputs, folderUid }) => ({ + method: 'POST', + url: '/dashboards/import', + data: { + dashboard, + overwrite, + inputs, + folderUid, + }, + }), + onQueryStarted: ({ folderUid }, { queryFulfilled, dispatch }) => { + queryFulfilled.then(async (response) => { + dispatch( + refetchChildren({ + parentUID: folderUid, + pageSize: PAGE_SIZE, + }) + ); + const dashboardUrl = locationUtil.stripBaseFromUrl(response.data.importedUrl); + locationService.push(dashboardUrl); + }); + }, + }), }), }); diff --git a/public/app/features/manage-dashboards/state/actions.test.ts b/public/app/features/manage-dashboards/state/actions.test.ts index 52d4c894ac0..209bae2ec9e 100644 --- a/public/app/features/manage-dashboards/state/actions.test.ts +++ b/public/app/features/manage-dashboards/state/actions.test.ts @@ -1,8 +1,8 @@ import { thunkTester } from 'test/core/thunk/thunkTester'; import { DataSourceInstanceSettings, ThresholdsMode } from '@grafana/data'; -import { BackendSrv, setBackendSrv } from '@grafana/runtime'; import { defaultDashboard, FieldColorModeId } from '@grafana/schema'; +import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { getLibraryPanel } from 'app/features/library-panels/state/api'; import { PanelModel } from '../../dashboard/state'; @@ -20,6 +20,15 @@ const mocks = { describe('importDashboard', () => { it('Should send data source uid', async () => { + // note: the actual action returned is more complicated + // but we don't really care about the return type in this test + // we're only testing that the correct data is passed to initiate + const mockAction = jest.fn().mockImplementation(() => ({ + type: 'foo', + })); + const importDashboardRtkQueryMock = jest + .spyOn(browseDashboardsAPI.endpoints.importDashboard, 'initiate') + .mockImplementation(mockAction); const form: ImportDashboardDTO = { title: 'Asda', uid: '12', @@ -40,17 +49,6 @@ describe('importDashboard', () => { }, }; - let postArgs: unknown; - - setBackendSrv({ - post: (url, args) => { - postArgs = args; - return Promise.resolve({ - importedUrl: '/my/dashboard', - }); - }, - } as BackendSrv); - await thunkTester({ importDashboard: { ...initialImportDashboardState, @@ -70,7 +68,7 @@ describe('importDashboard', () => { .givenThunk(importDashboard) .whenThunkIsDispatched(form); - expect(postArgs).toEqual({ + expect(importDashboardRtkQueryMock).toHaveBeenCalledWith({ dashboard: { title: 'Asda', uid: '12', diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index 15193dc2f23..8d0cae65376 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -1,7 +1,8 @@ -import { DataSourceInstanceSettings, locationUtil } from '@grafana/data'; -import { getBackendSrv, getDataSourceSrv, isFetchError, locationService } from '@grafana/runtime'; +import { DataSourceInstanceSettings } from '@grafana/data'; +import { getBackendSrv, getDataSourceSrv, isFetchError } from '@grafana/runtime'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification } from 'app/core/copy/appNotification'; +import { browseDashboardsAPI, ImportInputs } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { FolderInfo, PermissionLevelString, SearchQueryType, ThunkResult } from 'app/types'; @@ -200,7 +201,7 @@ export function importDashboard(importDashboardForm: ImportDashboardDTO): ThunkR const dashboard = getState().importDashboard.dashboard; const inputs = getState().importDashboard.inputs; - let inputsToPersist = [] as any[]; + const inputsToPersist: ImportInputs[] = []; importDashboardForm.dataSources?.forEach((dataSource: DataSourceInstanceSettings, index: number) => { const input = inputs.dataSources[index]; inputsToPersist.push({ @@ -221,18 +222,17 @@ export function importDashboard(importDashboardForm: ImportDashboardDTO): ThunkR }); }); - const result = await getBackendSrv().post('api/dashboards/import', { - // uid: if user changed it, take the new uid from importDashboardForm, - // else read it from original dashboard - // by default the uid input is disabled, onSubmit ignores values from disabled inputs - dashboard: { ...dashboard, title: importDashboardForm.title, uid: importDashboardForm.uid || dashboard.uid }, - overwrite: true, - inputs: inputsToPersist, - folderUid: importDashboardForm.folder.uid, - }); - - const dashboardUrl = locationUtil.stripBaseFromUrl(result.importedUrl); - locationService.push(dashboardUrl); + dispatch( + browseDashboardsAPI.endpoints.importDashboard.initiate({ + // uid: if user changed it, take the new uid from importDashboardForm, + // else read it from original dashboard + // by default the uid input is disabled, onSubmit ignores values from disabled inputs + dashboard: { ...dashboard, title: importDashboardForm.title, uid: importDashboardForm.uid || dashboard.uid }, + overwrite: true, + inputs: inputsToPersist, + folderUid: importDashboardForm.folder.uid, + }) + ); }; } diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts index 6f896de205e..91f38f13631 100644 --- a/public/app/types/dashboard.ts +++ b/public/app/types/dashboard.ts @@ -8,6 +8,23 @@ export interface DashboardDTO { meta: DashboardMeta; } +export interface ImportDashboardResponseDTO { + uid: string; + pluginId: string; + title: string; + imported: boolean; + importedRevision?: number; + importedUri: string; + importedUrl: string; + slug: string; + dashboardId: number; + folderId: number; + folderUid: string; + description: string; + path: string; + removed: boolean; +} + export interface SaveDashboardResponseDTO { id: number; slug: string;