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