diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 7e8d5ca263fd1..62da8b2f0e4a8 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -745,20 +745,18 @@ func (hs *HTTPServer) GetDashboardVersion(c *models.ReqContext) response.Respons creator = hs.getUserLogin(c.Req.Context(), res.CreatedBy) } - dashVersionMeta := &dashver.DashboardVersionMeta{ - ID: res.ID, - DashboardID: res.DashboardID, - DashboardUID: dashUID, - Data: res.Data, - ParentVersion: res.ParentVersion, - RestoredFrom: res.RestoredFrom, - Version: res.Version, - Created: res.Created, - Message: res.Message, - CreatedBy: creator, - } - - return response.JSON(http.StatusOK, dashVersionMeta) + meta := dtos.DashboardMeta{ + Type: models.DashTypeDB, + CreatedBy: creator, + Version: res.Version, + } + + dto := dtos.DashboardFullWithMeta{ + Dashboard: res.Data, + Meta: meta, + } + + return response.JSON(http.StatusOK, dto) } // swagger:route POST /dashboards/validate dashboards alpha validateDashboard diff --git a/public/app/core/reducers/fn-slice.ts b/public/app/core/reducers/fn-slice.ts index d6d6c0949d060..5281b9ca5d578 100644 --- a/public/app/core/reducers/fn-slice.ts +++ b/public/app/core/reducers/fn-slice.ts @@ -8,6 +8,7 @@ export interface FnGlobalState { FNDashboard: boolean; uid: string; slug: string; + version: number; mode: GrafanaThemeType.Light | GrafanaThemeType.Dark; controlsContainer: string | null; pageTitle: string; @@ -20,7 +21,7 @@ export type UpdateFNGlobalStateAction = PayloadAction>; export type SetFnStateAction = PayloadAction>; -export type FnPropMappedFromState = Extract; +export type FnPropMappedFromState = Extract; export type FnStateProp = keyof FnGlobalState; export type FnPropsMappedFromState = Pick; @@ -34,6 +35,7 @@ export const fnStateProps: FnStateProp[] = [ 'queryParams', 'slug', 'uid', + 'version', ]; export const fnPropsMappedFromState: readonly FnPropMappedFromState[] = [ @@ -43,6 +45,7 @@ export const fnPropsMappedFromState: readonly FnPropMappedFromState[] = [ 'uid', 'queryParams', 'slug', + 'version', ] as const; const INITIAL_MODE = GrafanaThemeType.Light; @@ -54,6 +57,7 @@ export const INITIAL_FN_STATE: FnGlobalState = { FNDashboard: false, uid: '', slug: '', + version: 1, mode: INITIAL_MODE, controlsContainer: null, pageTitle: '', diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 02b97dda9aa6d..066b2213aee80 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -464,6 +464,10 @@ export class BackendSrv implements BackendService { return this.get(`/api/dashboards/uid/${uid}`); } + getDashboardByUidVersion(uid: string, version: number): Promise { + return this.get(`/api/dashboards/uid/${uid}/versions/${version}`); + } + validateDashboard(dashboard: DashboardModel) { // We want to send the dashboard as a JSON string (in the JSON body payload) so we can get accurate error line numbers back const dashboardJson = JSON.stringify(dashboard, replaceJsonNulls, 2); diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index d8d6bd8707a02..3fafc287401c2 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -102,6 +102,7 @@ const connector = connect(mapStateToProps, mapDispatchToProps); type OwnProps = { isPublic?: boolean; controlsContainer?: string | null; + version?: FNDashboardProps['version']; fnLoader?: FNDashboardProps['fnLoader']; isLoading?: FNDashboardProps['isLoading'] }; @@ -176,6 +177,7 @@ export class UnthemedDashboardPage extends PureComponent { urlUid: match.params.uid, urlType: match.params.type, urlFolderId: queryParams.folderId, + version: match.params.version, panelType: queryParams.panelType, routeName: this.props.route.routeName, fixUrl: !isPublic && !FNDashboard, @@ -199,6 +201,7 @@ export class UnthemedDashboardPage extends PureComponent { if ( prevProps.match.params.uid !== match.params.uid || + prevProps.match.params.version !== match.params.version || (routeReloadCounter !== undefined && this.forceRouteReloadCounter !== routeReloadCounter) ) { this.initDashboard(); diff --git a/public/app/features/dashboard/services/DashboardLoaderSrv.ts b/public/app/features/dashboard/services/DashboardLoaderSrv.ts index 986cec7d14de8..c61aa4087d0f3 100644 --- a/public/app/features/dashboard/services/DashboardLoaderSrv.ts +++ b/public/app/features/dashboard/services/DashboardLoaderSrv.ts @@ -32,7 +32,7 @@ export class DashboardLoaderSrv { }; } - loadDashboard(type: UrlQueryValue, slug: any, uid: any) { + loadDashboard(type: UrlQueryValue, slug: any, uid: any, version: any) { let promise; if (type === 'script') { @@ -54,6 +54,19 @@ export class DashboardLoaderSrv { .catch(() => { return this._dashboardLoadFailed('Public Dashboard Not found', true); }); + } else if (version !== undefined) { + promise = backendSrv + .getDashboardByUidVersion(uid, version) + .then((result: any) => { + if (result.meta.isFolder) { + appEvents.emit(AppEvents.alertError, ['Dashboard with version not found']); + throw new Error('Dashboard with version not found'); + } + return result; + }) + .catch(() => { + return this._dashboardLoadFailed('Not found', true); + }); } else { promise = backendSrv .getDashboardByUid(uid) diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index dbab175a6d4e8..dbedfa4a8d961 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -28,6 +28,7 @@ export interface InitDashboardArgs { urlSlug?: string; urlType?: string; urlFolderId?: string; + version?: number; panelType?: string; accessToken?: string; routeName?: string; @@ -67,10 +68,10 @@ async function fetchDashboard( return dashDTO; } case DashboardRoutes.Public: { - return await dashboardLoaderSrv.loadDashboard('public', args.urlSlug, args.accessToken); + return await dashboardLoaderSrv.loadDashboard('public', args.urlSlug, args.accessToken, args.version); } case DashboardRoutes.Normal: { - const dashDTO: DashboardDTO = await dashboardLoaderSrv.loadDashboard(args.urlType, args.urlSlug, args.urlUid); + const dashDTO: DashboardDTO = await dashboardLoaderSrv.loadDashboard(args.urlType, args.urlSlug, args.urlUid, args.version); if (args.fixUrl && dashDTO.meta.url && !playlistSrv.isPlaying) { // check if the current url is correct (might be old slug) @@ -93,7 +94,7 @@ async function fetchDashboard( } case DashboardRoutes.Path: { const path = args.urlSlug ?? ''; - return await dashboardLoaderSrv.loadDashboard(DashboardRoutes.Path, path, path); + return await dashboardLoaderSrv.loadDashboard(DashboardRoutes.Path, path, path, args.version); } default: throw { message: 'Unknown route ' + args.routeName }; diff --git a/public/app/fn-app/create-mfe.ts b/public/app/fn-app/create-mfe.ts index 365b12cdd9223..3d8e23434824c 100644 --- a/public/app/fn-app/create-mfe.ts +++ b/public/app/fn-app/create-mfe.ts @@ -292,6 +292,7 @@ class createMfe { uid: other.uid, hiddenVariables: other.hiddenVariables, slug: other.slug, + version: other.version, queryParams: other.queryParams, }) ); diff --git a/public/app/fn-app/types.ts b/public/app/fn-app/types.ts index 37e1143ff4492..e11a71962d0c7 100644 --- a/public/app/fn-app/types.ts +++ b/public/app/fn-app/types.ts @@ -22,6 +22,7 @@ export interface FNDashboardProps { name: string; uid: string; slug: string; + version: number; mode: GrafanaThemeType.Dark | GrafanaThemeType.Light; queryParams: ParsedQuery; fnError?: ReactNode;