mirror of https://github.com/grafana/grafana
Dashboard: Redirect between `v1alpha1` and `v2alpha1` depending on stored version (#101292)
* wip: Create a proxy state manager to avoid complexity * Read path redirecting * add tests for unified dashboard API * add tests * Contemplate both formats in DashboardProxy * Fix force old * Fix tests for proxy * catch errors * Save as V2 when dynamic dashboard is enabled * Improve tests * Remove feature toggle * Use kubernetesDashboards for e2e suite * Fix issue when loading snapshots * Fix typescript errors * Integrate with backend conversion error * Remove legacy annotation * fix snapshot loading; lint * Add missing hideTimeControls * fix test * make setupDashboardAPI to all suites * refactor getDashboardAPI * Add tests * fix DashboardScenePage tests * fix tests * fix go tests * Refactor to understand better the need of transforming to v2 to compare * Fix detect changes logic * yes status from schema gen --------- Co-authored-by: alexandra vargas <alexa1866@gmail.com> Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>pull/102058/head
parent
0233c39a7f
commit
bfedf0b512
|
@ -0,0 +1,112 @@ |
|||||||
|
import { Dashboard } from '@grafana/schema/dist/esm/index'; |
||||||
|
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/types.gen'; |
||||||
|
import { DashboardDTO } from 'app/types'; |
||||||
|
|
||||||
|
import { SaveDashboardCommand } from '../components/SaveDashboard/types'; |
||||||
|
|
||||||
|
import { UnifiedDashboardAPI } from './UnifiedDashboardAPI'; |
||||||
|
import { DashboardVersionError, DashboardWithAccessInfo } from './types'; |
||||||
|
import { isV2DashboardCommand } from './utils'; |
||||||
|
import { K8sDashboardAPI } from './v1'; |
||||||
|
import { K8sDashboardV2API } from './v2'; |
||||||
|
|
||||||
|
jest.mock('./v1'); |
||||||
|
jest.mock('./v2'); |
||||||
|
|
||||||
|
describe('UnifiedDashboardAPI', () => { |
||||||
|
let api: UnifiedDashboardAPI; |
||||||
|
let v1Client: jest.Mocked<K8sDashboardAPI>; |
||||||
|
let v2Client: jest.Mocked<K8sDashboardV2API>; |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
jest.clearAllMocks(); |
||||||
|
api = new UnifiedDashboardAPI(); |
||||||
|
v1Client = api['v1Client'] as jest.Mocked<K8sDashboardAPI>; |
||||||
|
v2Client = api['v2Client'] as jest.Mocked<K8sDashboardV2API>; |
||||||
|
}); |
||||||
|
|
||||||
|
describe('getDashboardDTO', () => { |
||||||
|
it('should try v1 first and return result if successful', async () => { |
||||||
|
const mockResponse = { dashboard: { title: 'test' } }; |
||||||
|
v1Client.getDashboardDTO.mockResolvedValue(mockResponse as DashboardDTO); |
||||||
|
|
||||||
|
const result = await api.getDashboardDTO('123'); |
||||||
|
|
||||||
|
expect(result).toBe(mockResponse); |
||||||
|
expect(v1Client.getDashboardDTO).toHaveBeenCalledWith('123'); |
||||||
|
expect(v2Client.getDashboardDTO).not.toHaveBeenCalled(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should fallback to v2 if v1 throws DashboardVersionError', async () => { |
||||||
|
const mockV2Response = { spec: { title: 'test' } }; |
||||||
|
v1Client.getDashboardDTO.mockRejectedValue(new DashboardVersionError('v2alpha1', 'Dashboard is V1 format')); |
||||||
|
v2Client.getDashboardDTO.mockResolvedValue(mockV2Response as DashboardWithAccessInfo<DashboardV2Spec>); |
||||||
|
|
||||||
|
const result = await api.getDashboardDTO('123'); |
||||||
|
|
||||||
|
expect(result).toBe(mockV2Response); |
||||||
|
expect(v2Client.getDashboardDTO).toHaveBeenCalledWith('123'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('saveDashboard', () => { |
||||||
|
it('should use v1 client for v1 dashboard', async () => { |
||||||
|
const mockCommand = { dashboard: { title: 'test' } }; |
||||||
|
v1Client.saveDashboard.mockResolvedValue({ id: 1, status: 'success', slug: '', uid: '', url: '', version: 1 }); |
||||||
|
|
||||||
|
await api.saveDashboard(mockCommand as SaveDashboardCommand<Dashboard>); |
||||||
|
|
||||||
|
expect(v1Client.saveDashboard).toHaveBeenCalledWith(mockCommand); |
||||||
|
expect(v2Client.saveDashboard).not.toHaveBeenCalled(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should use v2 client for v2 dashboard', async () => { |
||||||
|
const mockCommand: SaveDashboardCommand<DashboardV2Spec> = { |
||||||
|
dashboard: { |
||||||
|
title: 'test', |
||||||
|
elements: {}, |
||||||
|
annotations: [], |
||||||
|
cursorSync: 'Crosshair', |
||||||
|
layout: { |
||||||
|
kind: 'GridLayout', |
||||||
|
spec: { items: [] }, |
||||||
|
}, |
||||||
|
liveNow: false, |
||||||
|
tags: [], |
||||||
|
links: [], |
||||||
|
preload: false, |
||||||
|
timeSettings: { |
||||||
|
from: 'now-1h', |
||||||
|
to: 'now', |
||||||
|
autoRefresh: '5s', |
||||||
|
autoRefreshIntervals: ['5s', '1m', '5m', '15m', '30m', '1h', '4h', '8h', '12h', '24h'], |
||||||
|
timezone: 'utc', |
||||||
|
hideTimepicker: false, |
||||||
|
fiscalYearStartMonth: 0, |
||||||
|
}, |
||||||
|
variables: [], |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
v2Client.saveDashboard.mockResolvedValue({ id: 1, status: 'success', slug: '', uid: '', url: '', version: 1 }); |
||||||
|
|
||||||
|
await api.saveDashboard(mockCommand as SaveDashboardCommand<DashboardV2Spec>); |
||||||
|
|
||||||
|
expect(isV2DashboardCommand(mockCommand)).toBe(true); |
||||||
|
expect(v2Client.saveDashboard).toHaveBeenCalledWith(mockCommand); |
||||||
|
expect(v1Client.saveDashboard).not.toHaveBeenCalled(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('deleteDashboard', () => { |
||||||
|
it('should not try other version if fails', async () => { |
||||||
|
v1Client.deleteDashboard.mockRejectedValue(new DashboardVersionError('v2alpha1', 'Dashboard is V1 format')); |
||||||
|
|
||||||
|
try { |
||||||
|
await api.deleteDashboard('123', true); |
||||||
|
} catch (error) {} |
||||||
|
expect(v1Client.deleteDashboard).toHaveBeenCalledWith('123', true); |
||||||
|
expect(v2Client.deleteDashboard).not.toHaveBeenCalled(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,50 @@ |
|||||||
|
import { Dashboard } from '@grafana/schema/dist/esm/index'; |
||||||
|
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; |
||||||
|
import { DashboardDTO } from 'app/types'; |
||||||
|
|
||||||
|
import { SaveDashboardCommand } from '../components/SaveDashboard/types'; |
||||||
|
|
||||||
|
import { DashboardAPI, DashboardVersionError, DashboardWithAccessInfo } from './types'; |
||||||
|
import { isV1DashboardCommand, isV2DashboardCommand } from './utils'; |
||||||
|
import { K8sDashboardAPI } from './v1'; |
||||||
|
import { K8sDashboardV2API } from './v2'; |
||||||
|
|
||||||
|
export class UnifiedDashboardAPI |
||||||
|
implements DashboardAPI<DashboardDTO | DashboardWithAccessInfo<DashboardV2Spec>, Dashboard | DashboardV2Spec> |
||||||
|
{ |
||||||
|
private v1Client: K8sDashboardAPI; |
||||||
|
private v2Client: K8sDashboardV2API; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
this.v1Client = new K8sDashboardAPI(); |
||||||
|
this.v2Client = new K8sDashboardV2API(); |
||||||
|
} |
||||||
|
|
||||||
|
// Get operation depends on the dashboard format to use one of the two clients
|
||||||
|
async getDashboardDTO(uid: string) { |
||||||
|
try { |
||||||
|
return await this.v1Client.getDashboardDTO(uid); |
||||||
|
} catch (error) { |
||||||
|
if (error instanceof DashboardVersionError && error.data.storedVersion === 'v2alpha1') { |
||||||
|
return await this.v2Client.getDashboardDTO(uid); |
||||||
|
} |
||||||
|
throw error; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Save operation depends on the dashboard format to use one of the two clients
|
||||||
|
async saveDashboard(options: SaveDashboardCommand<Dashboard | DashboardV2Spec>) { |
||||||
|
if (isV2DashboardCommand(options)) { |
||||||
|
return await this.v2Client.saveDashboard(options); |
||||||
|
} |
||||||
|
if (isV1DashboardCommand(options)) { |
||||||
|
return await this.v1Client.saveDashboard(options); |
||||||
|
} |
||||||
|
throw new Error('Invalid dashboard command'); |
||||||
|
} |
||||||
|
|
||||||
|
// Delete operation for any version is supported in the v1 client
|
||||||
|
async deleteDashboard(uid: string, showSuccessAlert: boolean) { |
||||||
|
return await this.v1Client.deleteDashboard(uid, showSuccessAlert); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue