mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s/Dashboards: Add frontend api to switch between implementations (#88632)
This commit is contained in:
@@ -153,6 +153,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `idForwarding` | Generate signed id token for identity that can be forwarded to plugins and external services |
|
||||
| `enableNativeHTTPHistogram` | Enables native HTTP Histograms |
|
||||
| `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint |
|
||||
| `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards |
|
||||
| `datasourceQueryTypes` | Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) |
|
||||
| `queryService` | Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query |
|
||||
| `queryServiceRewrite` | Rewrite requests targeting /ds/query to the query service |
|
||||
|
||||
@@ -117,6 +117,7 @@ export interface FeatureToggles {
|
||||
transformationsVariableSupport?: boolean;
|
||||
kubernetesPlaylists?: boolean;
|
||||
kubernetesSnapshots?: boolean;
|
||||
kubernetesDashboards?: boolean;
|
||||
datasourceQueryTypes?: boolean;
|
||||
queryService?: boolean;
|
||||
queryServiceRewrite?: boolean;
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -177,8 +178,9 @@ func (a *dashboardSqlAccess) GetDashboards(ctx context.Context, query *Dashboard
|
||||
|
||||
func (a *dashboardSqlAccess) GetDashboard(ctx context.Context, orgId int64, uid string) (*dashboardsV0.Dashboard, error) {
|
||||
r, err := a.GetDashboards(ctx, &DashboardQuery{
|
||||
OrgID: orgId,
|
||||
UID: uid,
|
||||
OrgID: orgId,
|
||||
UID: uid,
|
||||
Labels: labels.Everything(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -740,6 +740,13 @@ var (
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
RequiresRestart: true, // changes the API routing
|
||||
},
|
||||
{
|
||||
Name: "kubernetesDashboards",
|
||||
Description: "Use the kubernetes API in the frontend for dashboards",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
FrontendOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "datasourceQueryTypes",
|
||||
Description: "Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus)",
|
||||
|
||||
@@ -98,6 +98,7 @@ formatString,preview,@grafana/dataviz-squad,false,false,true
|
||||
transformationsVariableSupport,preview,@grafana/dataviz-squad,false,false,true
|
||||
kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false
|
||||
kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true
|
||||
datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
queryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
|
||||
|
@@ -403,6 +403,10 @@ const (
|
||||
// Routes snapshot requests from /api to the /apis endpoint
|
||||
FlagKubernetesSnapshots = "kubernetesSnapshots"
|
||||
|
||||
// FlagKubernetesDashboards
|
||||
// Use the kubernetes API in the frontend for dashboards
|
||||
FlagKubernetesDashboards = "kubernetesDashboards"
|
||||
|
||||
// FlagDatasourceQueryTypes
|
||||
// Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus)
|
||||
FlagDatasourceQueryTypes = "datasourceQueryTypes"
|
||||
|
||||
@@ -2224,6 +2224,19 @@
|
||||
"stage": "GA",
|
||||
"codeowner": "@grafana/plugins-platform-backend"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "kubernetesDashboards",
|
||||
"resourceVersion": "1717593661635",
|
||||
"creationTimestamp": "2024-06-05T13:21:01Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use the kubernetes API in the frontend for dashboards",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/grafana-app-platform-squad",
|
||||
"frontend": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { AsyncSelectProps, AsyncSelect } from '@grafana/ui';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { DashboardSearchItem } from 'app/features/search/types';
|
||||
import { DashboardDTO } from 'app/types';
|
||||
|
||||
@@ -54,7 +55,7 @@ export const DashboardPicker = ({
|
||||
(async () => {
|
||||
// value was manually changed from outside or we are rendering for the first time.
|
||||
// We need to fetch dashboard information.
|
||||
const res = await backendSrv.getDashboardByUid(value);
|
||||
const res = await getDashboardAPI().getDashboardDTO(value);
|
||||
if (res.dashboard) {
|
||||
setCurrent({
|
||||
value: {
|
||||
|
||||
@@ -7,21 +7,26 @@ import { Preferences as UserPreferencesDTO } from '@grafana/schema/src/raw/prefe
|
||||
|
||||
import SharedPreferences from './SharedPreferences';
|
||||
|
||||
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
|
||||
getDashboardAPI: () => ({
|
||||
getDashboardDTO: jest.fn().mockResolvedValue({
|
||||
dashboard: {
|
||||
id: 2,
|
||||
title: 'My Dashboard',
|
||||
uid: 'myDash',
|
||||
templating: {
|
||||
list: [],
|
||||
},
|
||||
panels: [],
|
||||
},
|
||||
meta: {},
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('app/core/services/backend_srv', () => {
|
||||
return {
|
||||
backendSrv: {
|
||||
getDashboardByUid: jest.fn().mockResolvedValue({
|
||||
dashboard: {
|
||||
id: 2,
|
||||
title: 'My Dashboard',
|
||||
uid: 'myDash',
|
||||
templating: {
|
||||
list: [],
|
||||
},
|
||||
panels: [],
|
||||
},
|
||||
meta: {},
|
||||
}),
|
||||
search: jest.fn().mockResolvedValue([
|
||||
{
|
||||
id: 2,
|
||||
|
||||
@@ -28,13 +28,15 @@ function makePromResponse() {
|
||||
|
||||
export const backendSrv = {
|
||||
get: jest.fn(),
|
||||
getDashboardByUid: jest.fn(),
|
||||
getFolderByUid: jest.fn(),
|
||||
post: jest.fn(),
|
||||
resolveCancelerIfExists: jest.fn(),
|
||||
search: jest.fn(),
|
||||
datasourceRequest: jest.fn(() => Promise.resolve(makePromResponse())),
|
||||
|
||||
/** @deprecated Use getDashboardAPI().getDashboardDTO(uid) */
|
||||
getDashboardByUid: jest.fn(),
|
||||
|
||||
// Observable support
|
||||
fetch: (options: BackendSrvRequest) => {
|
||||
return of(makePromResponse() as FetchResponse);
|
||||
|
||||
@@ -15,12 +15,13 @@ import {
|
||||
} from 'rxjs/operators';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { AppEvents, DataQueryErrorType } from '@grafana/data';
|
||||
import { AppEvents, DataQueryErrorType, deprecationWarning } from '@grafana/data';
|
||||
import { BackendSrv as BackendService, BackendSrvRequest, config, FetchError, FetchResponse } from '@grafana/runtime';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { getSessionExpiry, hasSessionExpiry } from 'app/core/utils/auth';
|
||||
import { loadUrlToken } from 'app/core/utils/urlToken';
|
||||
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { DashboardSearchItem } from 'app/features/search/types';
|
||||
import { TokenRevokedModal } from 'app/features/users/TokenRevokedModal';
|
||||
@@ -512,8 +513,12 @@ export class BackendSrv implements BackendService {
|
||||
return this.get('/api/search', query);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
getDashboardByUid(uid: string): Promise<DashboardDTO> {
|
||||
return this.get<DashboardDTO>(`/api/dashboards/uid/${uid}`);
|
||||
// NOTE: When this is removed, we can also remove most instances of:
|
||||
// jest.mock('app/features/live/dashboard/dashboardWatcher
|
||||
deprecationWarning('backend_srv', 'getDashboardByUid(uid)', 'getDashboardAPI().getDashboardDTO(uid)');
|
||||
return getDashboardAPI().getDashboardDTO(uid);
|
||||
}
|
||||
|
||||
validateDashboard(dashboard: DashboardModel): Promise<ValidateDashboardResponse> {
|
||||
@@ -522,7 +527,7 @@ export class BackendSrv implements BackendService {
|
||||
// config.featureToggles.showDashboardValidationWarnings
|
||||
return Promise.resolve({
|
||||
isValid: false,
|
||||
message: 'dashboard validation is supported',
|
||||
message: 'dashboard validation is not supported',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ jest.mock('@grafana/runtime', () => ({
|
||||
getDataSourceSrv: () => getDataSourceSrvMock(),
|
||||
}));
|
||||
|
||||
// Avoids errors caused by circular dependencies
|
||||
jest.mock('app/features/live/dashboard/dashboardWatcher', () => ({
|
||||
ignoreNextSave: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('state functions', () => {
|
||||
describe('serializeStateToUrlParam', () => {
|
||||
it('returns url parameter value for a state object', () => {
|
||||
|
||||
@@ -37,6 +37,10 @@ export class ScopedResourceClient<T = object, K = string> implements ResourceCli
|
||||
return getBackendSrv().get<Resource<T, K>>(`${this.url}/${name}`);
|
||||
}
|
||||
|
||||
public async subresource<S>(name: string, path: string): Promise<S> {
|
||||
return getBackendSrv().get<S>(`${this.url}/${name}/${path}`);
|
||||
}
|
||||
|
||||
public async list(opts?: ListOptions | undefined): Promise<ResourceList<T, K>> {
|
||||
const finalOpts = opts || {};
|
||||
finalOpts.labelSelector = this.parseListOptionsSelector(finalOpts?.labelSelector);
|
||||
|
||||
@@ -144,6 +144,7 @@ export interface MetaStatus {
|
||||
export interface ResourceClient<T = object, K = string> {
|
||||
create(obj: ResourceForCreate<T, K>): Promise<void>;
|
||||
get(name: string): Promise<Resource<T, K>>;
|
||||
subresource<S>(name: string, path: string): Promise<S>;
|
||||
list(opts?: ListOptions): Promise<ResourceList<T, K>>;
|
||||
update(obj: ResourceForCreate<T, K>): Promise<Resource<T, K>>;
|
||||
delete(name: string): Promise<MetaStatus>;
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
import {
|
||||
@@ -244,7 +245,7 @@ export const browseDashboardsAPI = createApi({
|
||||
// Move all the dashboards sequentially
|
||||
// TODO error handling here
|
||||
for (const dashboardUID of selectedDashboards) {
|
||||
const fullDash: DashboardDTO = await getBackendSrv().get(`/api/dashboards/uid/${dashboardUID}`);
|
||||
const fullDash: DashboardDTO = await getDashboardAPI().getDashboardDTO(dashboardUID);
|
||||
|
||||
const options = {
|
||||
dashboard: fullDash.dashboard,
|
||||
|
||||
84
public/app/features/dashboard/api/dashboard_api.ts
Normal file
84
public/app/features/dashboard/api/dashboard_api.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { config, getBackendSrv } from '@grafana/runtime';
|
||||
import { ScopedResourceClient } from 'app/features/apiserver/client';
|
||||
import { ResourceClient } from 'app/features/apiserver/types';
|
||||
import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types';
|
||||
import { DashboardDTO, DashboardDataDTO } from 'app/types';
|
||||
|
||||
export interface DashboardAPI {
|
||||
/** Get a dashboard with the access control metadata */
|
||||
getDashboardDTO(uid: string): Promise<DashboardDTO>;
|
||||
/** Save dashboard */
|
||||
saveDashboard(options: SaveDashboardCommand): Promise<unknown>;
|
||||
/** Delete a dashboard */
|
||||
deleteDashboard(uid: string, showSuccessAlert: boolean): Promise<DeleteDashboardResponse>;
|
||||
}
|
||||
|
||||
// Implemented using /api/dashboards/*
|
||||
class LegacyDashboardAPI implements DashboardAPI {
|
||||
constructor() {}
|
||||
|
||||
saveDashboard(options: SaveDashboardCommand): Promise<unknown> {
|
||||
dashboardWatcher.ignoreNextSave();
|
||||
|
||||
return getBackendSrv().post('/api/dashboards/db/', {
|
||||
dashboard: options.dashboard,
|
||||
message: options.message ?? '',
|
||||
overwrite: options.overwrite ?? false,
|
||||
folderUid: options.folderUid,
|
||||
});
|
||||
}
|
||||
|
||||
deleteDashboard(uid: string, showSuccessAlert: boolean): Promise<DeleteDashboardResponse> {
|
||||
return getBackendSrv().delete<DeleteDashboardResponse>(`/api/dashboards/uid/${uid}`, { showSuccessAlert });
|
||||
}
|
||||
|
||||
getDashboardDTO(uid: string): Promise<DashboardDTO> {
|
||||
return getBackendSrv().get<DashboardDTO>(`/api/dashboards/uid/${uid}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Implemented using /apis/dashboards.grafana.app/*
|
||||
class K8sDashboardAPI implements DashboardAPI {
|
||||
private client: ResourceClient<DashboardDataDTO>;
|
||||
constructor(private legacy: DashboardAPI) {
|
||||
this.client = new ScopedResourceClient<DashboardDataDTO>({
|
||||
group: 'dashboard.grafana.app',
|
||||
version: 'v0alpha1',
|
||||
resource: 'dashboards',
|
||||
});
|
||||
}
|
||||
|
||||
saveDashboard(options: SaveDashboardCommand): Promise<unknown> {
|
||||
return this.legacy.saveDashboard(options);
|
||||
}
|
||||
|
||||
deleteDashboard(uid: string, showSuccessAlert: boolean): Promise<DeleteDashboardResponse> {
|
||||
return this.legacy.deleteDashboard(uid, showSuccessAlert);
|
||||
}
|
||||
|
||||
async getDashboardDTO(uid: string): Promise<DashboardDTO> {
|
||||
const d = await this.client.get(uid);
|
||||
const m = await this.client.subresource<object>(uid, 'access');
|
||||
return {
|
||||
meta: {
|
||||
...m,
|
||||
isNew: false,
|
||||
isFolder: false,
|
||||
uid: d.metadata.name,
|
||||
},
|
||||
dashboard: d.spec,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let instance: DashboardAPI | undefined = undefined;
|
||||
|
||||
export function getDashboardAPI() {
|
||||
if (!instance) {
|
||||
const legacy = new LegacyDashboardAPI();
|
||||
instance = config.featureToggles.kubernetesDashboards ? new K8sDashboardAPI(legacy) : legacy;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
@@ -78,6 +78,12 @@ jest.mock('react-virtualized-auto-sizer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
|
||||
getDashboardAPI: () => ({
|
||||
getDashboardDTO: jest.fn().mockResolvedValue(dashMock),
|
||||
}),
|
||||
}));
|
||||
|
||||
function setup(props: Partial<DashboardPageProxyProps>) {
|
||||
const context = getGrafanaContextMock();
|
||||
const store = configureStore({});
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { DashboardDTO } from 'app/types';
|
||||
|
||||
import { appEvents } from '../../../core/core';
|
||||
import { getDashboardAPI } from '../api/dashboard_api';
|
||||
|
||||
import { getDashboardSrv } from './DashboardSrv';
|
||||
import { getDashboardSnapshotSrv } from './SnapshotSrv';
|
||||
@@ -81,8 +82,8 @@ export class DashboardLoaderSrv {
|
||||
return Promise.resolve(cachedDashboard);
|
||||
}
|
||||
|
||||
promise = backendSrv
|
||||
.getDashboardByUid(uid)
|
||||
promise = getDashboardAPI()
|
||||
.getDashboardDTO(uid)
|
||||
.then((result) => {
|
||||
if (result.meta.isFolder) {
|
||||
appEvents.emit(AppEvents.alertError, ['Dashboard not found']);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Dashboard } from '@grafana/schema';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { saveDashboard } from 'app/features/manage-dashboards/state/actions';
|
||||
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { DashboardMeta } from 'app/types';
|
||||
|
||||
import { RemovePanelEvent } from '../../../types/events';
|
||||
@@ -65,7 +65,7 @@ export class DashboardSrv {
|
||||
|
||||
saveJSONDashboard(json: string) {
|
||||
const parsedJson = JSON.parse(json);
|
||||
return saveDashboard({
|
||||
return getDashboardAPI().saveDashboard({
|
||||
dashboard: parsedJson,
|
||||
folderUid: this.dashboard?.meta.folderUid || parsedJson.folderUid,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TimeZone } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
||||
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
import { removeAllPanels } from 'app/features/panel/state/reducers';
|
||||
import { updateTimeZoneForSession, updateWeekStartForSession } from 'app/features/profile/state/reducers';
|
||||
@@ -24,7 +25,7 @@ export function importDashboard(data: any, dashboardTitle: string): ThunkResult<
|
||||
|
||||
export function removeDashboard(uid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().delete(`/api/dashboards/uid/${uid}`);
|
||||
await getDashboardAPI().deleteDashboard(uid, false);
|
||||
dispatch(loadPluginDashboards());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ import { makeLogs } from '../__mocks__/makeLogs';
|
||||
|
||||
import { LiveLogsWithTheme } from './LiveLogs';
|
||||
|
||||
// Avoids errors caused by circular dependencies
|
||||
jest.mock('app/features/live/dashboard/dashboardWatcher', () => ({
|
||||
ignoreNextSave: jest.fn(),
|
||||
}));
|
||||
|
||||
const setup = (rows: LogRowModel[]) =>
|
||||
render(
|
||||
<LiveLogsWithTheme
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { MutableDataFrame } from '@grafana/data';
|
||||
import { DataQuery, defaultDashboard } from '@grafana/schema';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import * as api from 'app/features/dashboard/state/initDashboard';
|
||||
import { ExplorePanelData } from 'app/types';
|
||||
|
||||
@@ -8,6 +7,15 @@ import { createEmptyQueryResponse } from '../../state/utils';
|
||||
|
||||
import { setDashboardInLocalStorage } from './addToDashboard';
|
||||
|
||||
let mockDashboard = {} as unknown;
|
||||
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
|
||||
getDashboardAPI: () => ({
|
||||
getDashboardDTO: () => {
|
||||
return Promise.resolve(mockDashboard);
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('addPanelToDashboard', () => {
|
||||
let spy: jest.SpyInstance;
|
||||
beforeAll(() => {
|
||||
@@ -71,7 +79,9 @@ describe('addPanelToDashboard', () => {
|
||||
it('Previous panels should not be removed', async () => {
|
||||
const queries: DataQuery[] = [{ refId: 'A' }];
|
||||
const existingPanel = { prop: 'this should be kept' };
|
||||
jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
|
||||
|
||||
// Set the mocked dashboard
|
||||
mockDashboard = {
|
||||
dashboard: {
|
||||
...defaultDashboard,
|
||||
templating: { list: [] },
|
||||
@@ -80,7 +90,7 @@ describe('addPanelToDashboard', () => {
|
||||
panels: [existingPanel],
|
||||
},
|
||||
meta: {},
|
||||
});
|
||||
};
|
||||
|
||||
await setDashboardInLocalStorage({
|
||||
queries,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataFrame, ExplorePanelsState } from '@grafana/data';
|
||||
import { Dashboard, DataQuery, DataSourceRef } from '@grafana/schema';
|
||||
import { DataTransformerConfig } from '@grafana/schema/dist/esm/raw/dashboard/x/dashboard_types.gen';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { setDashboardToFetchFromLocalStorage } from 'app/features/dashboard/state/initDashboard';
|
||||
import { buildNewDashboardSaveModel } from 'app/features/dashboard-scene/serialization/buildNewDashboardSaveModel';
|
||||
import { DashboardDTO, ExplorePanelData } from 'app/types';
|
||||
@@ -80,7 +80,7 @@ export async function setDashboardInLocalStorage(options: AddPanelToDashboardOpt
|
||||
|
||||
if (options.dashboardUid) {
|
||||
try {
|
||||
dto = await backendSrv.getDashboardByUid(options.dashboardUid);
|
||||
dto = await getDashboardAPI().getDashboardDTO(options.dashboardUid);
|
||||
} catch (e) {
|
||||
throw AddToDashboardError.FETCH_DASHBOARD;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@ import { DEFAULT_RANGE } from 'app/features/explore/state/utils';
|
||||
|
||||
import { v0Migrator } from './v0';
|
||||
|
||||
// Avoids errors caused by circular dependencies
|
||||
jest.mock('app/features/live/dashboard/dashboardWatcher', () => ({
|
||||
ignoreNextSave: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('v0 migrator', () => {
|
||||
describe('parse', () => {
|
||||
it('returns default state on empty string', () => {
|
||||
|
||||
@@ -2,6 +2,11 @@ import { DEFAULT_RANGE } from 'app/features/explore/state/utils';
|
||||
|
||||
import { v1Migrator } from './v1';
|
||||
|
||||
// Avoids errors caused by circular dependencies
|
||||
jest.mock('app/features/live/dashboard/dashboardWatcher', () => ({
|
||||
ignoreNextSave: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('app/core/utils/explore', () => ({
|
||||
...jest.requireActual('app/core/utils/explore'),
|
||||
generateExploreId: () => 'ID',
|
||||
|
||||
@@ -3,8 +3,7 @@ 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 { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { FolderInfo, PermissionLevelString, SearchQueryType, ThunkResult } from 'app/types';
|
||||
|
||||
import {
|
||||
@@ -16,7 +15,7 @@ import {
|
||||
import { getLibraryPanel } from '../../library-panels/state/api';
|
||||
import { LibraryElementDTO, LibraryElementKind } from '../../library-panels/types';
|
||||
import { DashboardSearchHit } from '../../search/types';
|
||||
import { DashboardJson, DeleteDashboardResponse } from '../types';
|
||||
import { DashboardJson } from '../types';
|
||||
|
||||
import {
|
||||
clearDashboard,
|
||||
@@ -311,17 +310,6 @@ export function deleteFoldersAndDashboards(folderUids: string[], dashboardUids:
|
||||
return executeInOrder(tasks);
|
||||
}
|
||||
|
||||
export function saveDashboard(options: SaveDashboardCommand) {
|
||||
dashboardWatcher.ignoreNextSave();
|
||||
|
||||
return getBackendSrv().post('/api/dashboards/db/', {
|
||||
dashboard: options.dashboard,
|
||||
message: options.message ?? '',
|
||||
overwrite: options.overwrite ?? false,
|
||||
folderUid: options.folderUid,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteFolder(uid: string, showSuccessAlert: boolean) {
|
||||
return getBackendSrv().delete(`/api/folders/${uid}?forceDeleteRules=false`, undefined, { showSuccessAlert });
|
||||
}
|
||||
@@ -360,7 +348,7 @@ export function getFolderById(id: number): Promise<{ id: number; title: string }
|
||||
}
|
||||
|
||||
export function deleteDashboard(uid: string, showSuccessAlert: boolean) {
|
||||
return getBackendSrv().delete<DeleteDashboardResponse>(`/api/dashboards/uid/${uid}`, { showSuccessAlert });
|
||||
return getDashboardAPI().deleteDashboard(uid, showSuccessAlert);
|
||||
}
|
||||
|
||||
function executeInOrder(tasks: any[]): Promise<unknown> {
|
||||
|
||||
@@ -53,6 +53,12 @@ const setup = (propOverrides?: object) => {
|
||||
);
|
||||
};
|
||||
|
||||
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
|
||||
getDashboardAPI: () => ({
|
||||
getDashboardDTO: jest.fn().mockResolvedValue({}),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Render', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
@@ -22,6 +22,12 @@ jest.mock('app/core/hooks/useQueryParams', () => ({
|
||||
useQueryParams: () => [{}],
|
||||
}));
|
||||
|
||||
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
|
||||
getDashboardAPI: () => ({
|
||||
getDashboardDTO: jest.fn().mockResolvedValue({}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const defaultProps: Props = {
|
||||
...initialUserState,
|
||||
user: {
|
||||
|
||||
Reference in New Issue
Block a user