import * as React from 'react'; import { PageLayoutType, dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data'; import { config } from '@grafana/runtime'; import { SceneComponentProps, SceneObjectBase, sceneGraph } from '@grafana/scenes'; import { Spinner, Stack } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { DashboardScene } from '../scene/DashboardScene'; import { NavToolbarActions } from '../scene/NavToolbarActions'; import { getDashboardSceneFor } from '../utils/utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; import { RevisionsModel, VersionHistoryComparison, VersionHistoryHeader, VersionHistoryTable, VersionsHistoryButtons, historySrv, } from './version-history'; export const VERSIONS_FETCH_LIMIT = 10; export type DecoratedRevisionModel = RevisionsModel & { createdDateString: string; ageString: string; }; export interface VersionsEditViewState extends DashboardEditViewState { versions?: DecoratedRevisionModel[]; isLoading?: boolean; isAppending?: boolean; viewMode?: 'list' | 'compare'; diffData?: { lhs: string; rhs: string }; newInfo?: DecoratedRevisionModel; baseInfo?: DecoratedRevisionModel; isNewLatest?: boolean; } export class VersionsEditView extends SceneObjectBase implements DashboardEditView { public static Component = VersionsEditorSettingsListView; private _limit: number = VERSIONS_FETCH_LIMIT; private _start = 0; private _continueToken = ''; constructor(state: VersionsEditViewState) { super({ ...state, versions: [], isLoading: true, isAppending: true, viewMode: 'list', isNewLatest: false, diffData: { lhs: '', rhs: '', }, }); this.addActivationHandler(() => { this.fetchVersions(); }); } private get _dashboard(): DashboardScene { return getDashboardSceneFor(this); } public get diffData(): { lhs: string; rhs: string } { return this.state.diffData ?? { lhs: '', rhs: '' }; } public get versions(): DecoratedRevisionModel[] { return this.state.versions ?? []; } public get limit(): number { return this._limit; } public get start(): number { return this._start; } public getUrlKey(): string { return 'versions'; } public getDashboard(): DashboardScene { return this._dashboard; } public getTimeRange() { return sceneGraph.getTimeRange(this._dashboard); } public fetchVersions = (append = false): void => { const uid = this._dashboard.state.uid; if (!uid) { return; } this.setState({ isAppending: append }); const requestOptions = this._continueToken ? { limit: this._limit, start: this._start, continueToken: this._continueToken } : { limit: this._limit, start: this._start }; historySrv .getHistoryList(uid, requestOptions) .then((result) => { this.setState({ isLoading: false, versions: [...(this.state.versions ?? []), ...this.decorateVersions(result.versions)], }); this._start += this._limit; // Update the continueToken for the next request, if available this._continueToken = result.continueToken ?? ''; }) .catch((err) => console.log(err)) .finally(() => this.setState({ isAppending: false })); }; public getDiff = async () => { const selectedVersions = this.versions.filter((version) => version.checked); const [newInfo, baseInfo] = selectedVersions; const isNewLatest = newInfo.version === this._dashboard.state.version; this.setState({ isLoading: true, }); if (!this._dashboard.state.uid) { return; } let lhs, rhs; if (config.featureToggles.kubernetesClientDashboardsFolders) { // the id here is the resource version in k8s, use this instead to get the specific version lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.id); rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.id); } else { lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.version); rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.version); } this.setState({ baseInfo, isLoading: false, isNewLatest, newInfo, viewMode: 'compare', diffData: { lhs: lhs.data, rhs: rhs.data, }, }); }; public reset = () => { this._continueToken = ''; this.setState({ baseInfo: undefined, diffData: { lhs: '', rhs: '', }, isNewLatest: false, newInfo: undefined, versions: this.versions.map((version) => ({ ...version, checked: false })), viewMode: 'list', }); }; public onCheck = (ev: React.FormEvent, versionId: number) => { this.setState({ versions: this.versions.map((version) => version.id === versionId ? { ...version, checked: ev.currentTarget.checked } : version ), }); }; private decorateVersions(versions: RevisionsModel[]): DecoratedRevisionModel[] { const timeZone = this.getTimeRange().getTimeZone(); return versions.map((version) => { return { ...version, createdDateString: dateTimeFormat(version.created, { timeZone: timeZone }), ageString: dateTimeFormatTimeAgo(version.created, { timeZone: timeZone }), checked: false, }; }); } } function VersionsEditorSettingsListView({ model }: SceneComponentProps) { const dashboard = model.getDashboard(); const { isLoading, isAppending, viewMode, baseInfo, newInfo, isNewLatest } = model.useState(); const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey()); const canCompare = model.versions.filter((version) => version.checked).length === 2; const showButtons = model.versions.length > 1; const hasMore = model.versions.length >= model.limit; const isLastPage = model.versions.find((rev) => rev.version === 1); const viewModeCompare = ( <> {isLoading ? ( ) : ( )} ); const viewModeList = ( <> {isLoading ? ( ) : ( )} {isAppending && } {showButtons && ( )} ); return ( {viewMode === 'compare' ? viewModeCompare : viewModeList} ); } const VersionsHistorySpinner = ({ msg }: { msg: string }) => ( {msg} );