diff --git a/public/app/api/createBaseQuery.ts b/public/app/api/createBaseQuery.ts new file mode 100644 index 00000000000..29327703bf9 --- /dev/null +++ b/public/app/api/createBaseQuery.ts @@ -0,0 +1,43 @@ +import { BaseQueryFn } from '@reduxjs/toolkit/query'; +import { lastValueFrom } from 'rxjs'; + +import { BackendSrvRequest, getBackendSrv, isFetchError } from '@grafana/runtime'; + +interface RequestOptions extends BackendSrvRequest { + manageError?: (err: unknown) => { error: unknown }; + body?: BackendSrvRequest['data']; +} + +export function createBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn { + async function backendSrvBaseQuery(requestOptions: RequestOptions) { + try { + const { data: responseData, ...meta } = await lastValueFrom( + getBackendSrv().fetch({ + ...requestOptions, + url: baseURL + requestOptions.url, + showErrorAlert: requestOptions.showErrorAlert ?? false, + data: requestOptions.body, + }) + ); + return { data: responseData, meta }; + } catch (error) { + if (requestOptions.manageError) { + return requestOptions.manageError(error); + } else { + return handleRequestError(error); + } + } + } + + return backendSrvBaseQuery; +} + +export function handleRequestError(error: unknown) { + if (isFetchError(error)) { + return { error: new Error(error.data.message) }; + } else if (error instanceof Error) { + return { error }; + } else { + return { error: new Error('Unknown error') }; + } +} diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index bbe79f9cdb3..e14cae0ef31 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -1,10 +1,10 @@ -import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react'; -import { lastValueFrom } from 'rxjs'; +import { createApi } from '@reduxjs/toolkit/query/react'; import { AppEvents, isTruthy, locationUtil } from '@grafana/data'; -import { BackendSrvRequest, config, getBackendSrv, locationService } from '@grafana/runtime'; +import { config, getBackendSrv, locationService } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; +import { createBaseQuery, handleRequestError } from 'app/api/createBaseQuery'; import appEvents from 'app/core/app_events'; import { contextSrv } from 'app/core/core'; import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; @@ -28,11 +28,6 @@ import { DashboardTreeSelection } from '../types'; import { PAGE_SIZE } from './services'; -interface RequestOptions extends BackendSrvRequest { - manageError?: (err: unknown) => { error: unknown }; - showErrorAlert?: boolean; -} - interface DeleteItemsArgs { selectedItems: Omit; } @@ -64,28 +59,6 @@ interface HardDeleteDashboardArgs { dashboardUID: string; } -function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn { - async function backendSrvBaseQuery(requestOptions: RequestOptions) { - // Suppress error pop-up for root (aka 'general') folder - const isGeneralFolder = requestOptions.url === `/folders/general`; - requestOptions = isGeneralFolder ? { ...requestOptions, showErrorAlert: false } : requestOptions; - - try { - const { data: responseData, ...meta } = await lastValueFrom( - getBackendSrv().fetch({ - ...requestOptions, - url: baseURL + requestOptions.url, - }) - ); - return { data: responseData, meta }; - } catch (error) { - return requestOptions.manageError ? requestOptions.manageError(error) : { error }; - } - } - - return backendSrvBaseQuery; -} - export interface ListFolderQueryArgs { page: number; parentUid: string | undefined; @@ -96,7 +69,7 @@ export interface ListFolderQueryArgs { export const browseDashboardsAPI = createApi({ tagTypes: ['getFolder'], reducerPath: 'browseDashboardsAPI', - baseQuery: createBackendSrvBaseQuery({ baseURL: '/api' }), + baseQuery: createBaseQuery({ baseURL: '/api' }), endpoints: (builder) => ({ listFolders: builder.query({ providesTags: (result) => result?.map((folder) => ({ type: 'getFolder', id: folder.uid })) ?? [], @@ -117,7 +90,7 @@ export const browseDashboardsAPI = createApi({ query: ({ title, parentUid }) => ({ method: 'POST', url: '/folders', - data: { + body: { title, parentUid, }, @@ -144,7 +117,7 @@ export const browseDashboardsAPI = createApi({ query: ({ uid, title, version }) => ({ method: 'PUT', url: `/folders/${uid}`, - data: { + body: { title, version, }, @@ -167,7 +140,7 @@ export const browseDashboardsAPI = createApi({ query: ({ folder, destinationUID }) => ({ url: `/folders/${folder.uid}/move`, method: 'POST', - data: { parentUID: destinationUID }, + body: { parentUID: destinationUID }, }), onQueryStarted: ({ folder, destinationUID }, { queryFulfilled, dispatch }) => { const { parentUid } = folder; @@ -256,7 +229,7 @@ export const browseDashboardsAPI = createApi({ await baseQuery({ url: `/folders/${folderUID}/move`, method: 'POST', - data: { parentUID: destinationUID }, + body: { parentUID: destinationUID }, }); } @@ -363,7 +336,7 @@ export const browseDashboardsAPI = createApi({ } throw new Error('Invalid dashboard version'); } catch (error) { - return { error }; + return handleRequestError(error); } }, @@ -385,7 +358,7 @@ export const browseDashboardsAPI = createApi({ query: ({ dashboard, overwrite, inputs, folderUid }) => ({ method: 'POST', url: '/dashboards/import', - data: { + body: { dashboard, overwrite, inputs, @@ -410,7 +383,7 @@ export const browseDashboardsAPI = createApi({ restoreDashboard: builder.mutation({ query: ({ dashboardUID, targetFolderUID }) => ({ url: `/dashboards/uid/${dashboardUID}/trash`, - data: { + body: { folderUid: targetFolderUID, }, method: 'PATCH', diff --git a/public/app/features/dashboard/api/publicDashboardApi.ts b/public/app/features/dashboard/api/publicDashboardApi.ts index 063b55e4d79..40bda98f4c5 100644 --- a/public/app/features/dashboard/api/publicDashboardApi.ts +++ b/public/app/features/dashboard/api/publicDashboardApi.ts @@ -1,7 +1,7 @@ -import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react'; -import { lastValueFrom } from 'rxjs'; +import { createApi } from '@reduxjs/toolkit/query/react'; -import { BackendSrvRequest, config, FetchError, getBackendSrv, isFetchError } from '@grafana/runtime/src'; +import { config, FetchError, isFetchError } from '@grafana/runtime/src'; +import { createBaseQuery } from 'app/api/createBaseQuery'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; import { t } from 'app/core/internationalization'; @@ -19,39 +19,17 @@ import { PublicDashboardListWithPaginationResponse, } from 'app/features/manage-dashboards/types'; -type ReqOptions = { - manageError?: (err: unknown) => { error: unknown }; - showErrorAlert?: boolean; -}; - function isFetchBaseQueryError(error: unknown): error is { error: FetchError } { return typeof error === 'object' && error != null && 'error' in error; } -const backendSrvBaseQuery = - ({ baseUrl }: { baseUrl: string }): BaseQueryFn => - async (requestOptions) => { - try { - const { data: responseData, ...meta } = await lastValueFrom( - getBackendSrv().fetch({ - ...requestOptions, - url: baseUrl + requestOptions.url, - showErrorAlert: requestOptions.showErrorAlert, - }) - ); - return { data: responseData, meta }; - } catch (error) { - return requestOptions.manageError ? requestOptions.manageError(error) : { error }; - } - }; - -const getConfigError = (err: unknown) => ({ +export const getConfigError = (err: unknown) => ({ error: isFetchError(err) && err.data.messageId !== 'publicdashboards.notFound' ? err : null, }); export const publicDashboardApi = createApi({ reducerPath: 'publicDashboardApi', - baseQuery: backendSrvBaseQuery({ baseUrl: '/api' }), + baseQuery: createBaseQuery({ baseURL: '/api' }), tagTypes: ['PublicDashboard', 'AuditTablePublicDashboard', 'UsersWithActiveSessions', 'ActiveUserDashboards'], refetchOnMountOrArgChange: true, endpoints: (builder) => ({ @@ -81,7 +59,7 @@ export const publicDashboardApi = createApi({ return { url: `/dashboards/uid/${dashUid}/public-dashboards`, method: 'POST', - data: params.payload, + body: params.payload, }; }, async onQueryStarted({ dashboard, payload: { share } }, { dispatch, queryFulfilled }) { @@ -121,7 +99,7 @@ export const publicDashboardApi = createApi({ return { url: `/dashboards/uid/${dashUid}/public-dashboards/${payload.uid}`, method: 'PATCH', - data: payload, + body: payload, }; }, async onQueryStarted({ dashboard }, { dispatch, queryFulfilled }) { @@ -153,7 +131,7 @@ export const publicDashboardApi = createApi({ return { url: `/dashboards/uid/${dashUid}/public-dashboards/${payload.uid}`, method: 'PATCH', - data: payload, + body: payload, }; }, async onQueryStarted({ dashboard, payload: { isEnabled } }, { dispatch, queryFulfilled }) { @@ -193,7 +171,7 @@ export const publicDashboardApi = createApi({ return { url: `/dashboards/uid/${dashUid}/public-dashboards/${payload.uid}`, method: 'PATCH', - data: payload, + body: payload, }; }, async onQueryStarted({ dashboard, payload: { share } }, { dispatch, queryFulfilled }) { diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx index 54dc5951b88..076e1664cb4 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx @@ -53,7 +53,7 @@ export function QueryTemplatesList(props: QueryTemplatesListProps) { return uniqBy(loadQueryMetadataResult.value, 'datasourceName').map((row) => row.datasourceName); }, [loadQueryMetadataResult.value]); - if (error) { + if (error instanceof Error) { return ( {error.message} diff --git a/public/app/features/migrate-to-cloud/api/baseAPI.ts b/public/app/features/migrate-to-cloud/api/baseAPI.ts index 6a140906631..60c18dfd3e3 100644 --- a/public/app/features/migrate-to-cloud/api/baseAPI.ts +++ b/public/app/features/migrate-to-cloud/api/baseAPI.ts @@ -1,38 +1,9 @@ -import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react'; -import { lastValueFrom } from 'rxjs'; +import { createApi } from '@reduxjs/toolkit/query/react'; -import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime'; - -interface RequestOptions extends BackendSrvRequest { - manageError?: (err: unknown) => { error: unknown }; - showErrorAlert?: boolean; - - // rtk codegen sets this - body?: BackendSrvRequest['data']; -} - -function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn { - async function backendSrvBaseQuery(requestOptions: RequestOptions) { - try { - const { data: responseData, ...meta } = await lastValueFrom( - getBackendSrv().fetch({ - ...requestOptions, - url: baseURL + requestOptions.url, - showErrorAlert: false, - data: requestOptions.body, - }) - ); - return { data: responseData, meta }; - } catch (error) { - return requestOptions.manageError ? requestOptions.manageError(error) : { error }; - } - } - - return backendSrvBaseQuery; -} +import { createBaseQuery } from 'app/api/createBaseQuery'; export const baseAPI = createApi({ reducerPath: 'migrateToCloudGeneratedAPI', - baseQuery: createBackendSrvBaseQuery({ baseURL: '/api' }), + baseQuery: createBaseQuery({ baseURL: '/api' }), endpoints: () => ({}), }); diff --git a/public/app/features/migrate-to-cloud/api/index.ts b/public/app/features/migrate-to-cloud/api/index.ts index 60a2d7a7b87..0e5c1d8b453 100644 --- a/public/app/features/migrate-to-cloud/api/index.ts +++ b/public/app/features/migrate-to-cloud/api/index.ts @@ -1,11 +1,14 @@ -export * from './endpoints.gen'; import { BaseQueryFn, EndpointDefinition } from '@reduxjs/toolkit/query'; import { getLocalPlugins } from 'app/features/plugins/admin/api'; import { LocalPlugin } from 'app/features/plugins/admin/types'; +import { handleRequestError } from '../../../api/createBaseQuery'; + import { generatedAPI } from './endpoints.gen'; +export * from './endpoints.gen'; + export const cloudMigrationAPI = generatedAPI .injectEndpoints({ endpoints: (build) => ({ @@ -16,7 +19,7 @@ export const cloudMigrationAPI = generatedAPI const list = await getLocalPlugins(); return { data: list }; } catch (error) { - return { error: error }; + return handleRequestError(error); } }, }), diff --git a/public/app/features/preferences/api/user/baseAPI.ts b/public/app/features/preferences/api/user/baseAPI.ts index b1b75e7b036..33316419f6e 100644 --- a/public/app/features/preferences/api/user/baseAPI.ts +++ b/public/app/features/preferences/api/user/baseAPI.ts @@ -1,36 +1,9 @@ -import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react'; -import { lastValueFrom } from 'rxjs'; +import { createApi } from '@reduxjs/toolkit/query/react'; -import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime'; - -interface RequestOptions extends BackendSrvRequest { - manageError?: (err: unknown) => { error: unknown }; - showErrorAlert?: boolean; - body?: BackendSrvRequest['data']; -} - -function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn { - async function backendSrvBaseQuery(requestOptions: RequestOptions) { - try { - const { data: responseData, ...meta } = await lastValueFrom( - getBackendSrv().fetch({ - ...requestOptions, - url: baseURL + requestOptions.url, - showErrorAlert: requestOptions.showErrorAlert, - data: requestOptions.body, - }) - ); - return { data: responseData, meta }; - } catch (error) { - return requestOptions.manageError ? requestOptions.manageError(error) : { error }; - } - } - - return backendSrvBaseQuery; -} +import { createBaseQuery } from 'app/api/createBaseQuery'; export const baseAPI = createApi({ reducerPath: 'userPreferencesAPI', - baseQuery: createBackendSrvBaseQuery({ baseURL: '/api' }), + baseQuery: createBaseQuery({ baseURL: '/api' }), endpoints: () => ({}), }); diff --git a/public/app/features/query-library/api/factory.ts b/public/app/features/query-library/api/factory.ts index 00e6115db7d..d03074092d6 100644 --- a/public/app/features/query-library/api/factory.ts +++ b/public/app/features/query-library/api/factory.ts @@ -1,12 +1,14 @@ import { createApi } from '@reduxjs/toolkit/query/react'; -import { baseQuery } from './query'; +import { createBaseQuery } from '../../../api/createBaseQuery'; + +import { BASE_URL } from './query'; // Currently, we are loading all query templates // Organizations can have maximum of 1000 query templates export const QUERY_LIBRARY_GET_LIMIT = 1000; export const queryLibraryApi = createApi({ - baseQuery, + baseQuery: createBaseQuery({ baseURL: BASE_URL }), endpoints: () => ({}), }); diff --git a/public/app/features/query-library/api/query.ts b/public/app/features/query-library/api/query.ts index 610c1556387..d0cc6db0209 100644 --- a/public/app/features/query-library/api/query.ts +++ b/public/app/features/query-library/api/query.ts @@ -1,8 +1,3 @@ -import { BaseQueryFn } from '@reduxjs/toolkit/query/react'; -import { lastValueFrom } from 'rxjs'; - -import { BackendSrvRequest, getBackendSrv, isFetchError } from '@grafana/runtime/src/services/backendSrv'; - import { getAPINamespace } from '../../../api/utils'; /** @@ -23,29 +18,3 @@ export enum QueryTemplateKinds { * @alpha */ export const BASE_URL = `/apis/${API_VERSION}/namespaces/${getAPINamespace()}`; - -interface QueryLibraryBackendRequest extends BackendSrvRequest { - body?: BackendSrvRequest['data']; -} - -export const baseQuery: BaseQueryFn = async (requestOptions) => { - try { - const responseObservable = getBackendSrv().fetch({ - url: `${BASE_URL}/${requestOptions.url ?? ''}`, - showErrorAlert: true, - method: requestOptions.method || 'GET', - data: requestOptions.body, - params: requestOptions.params, - headers: { ...requestOptions.headers }, - }); - return await lastValueFrom(responseObservable); - } catch (error) { - if (isFetchError(error)) { - return { error: new Error(error.data.message) }; - } else if (error instanceof Error) { - return { error }; - } else { - return { error: new Error('Unknown error') }; - } - } -};