1import React, { PureComponent } from 'react';
2import { Spinner, HorizontalGroup } from '@grafana/ui';
3import { DashboardModel } from '../../state/DashboardModel';
4import {
5  historySrv,
6  RevisionsModel,
7  VersionHistoryTable,
8  VersionHistoryHeader,
9  VersionsHistoryButtons,
10  VersionHistoryComparison,
11} from '../VersionHistory';
12
13interface Props {
14  dashboard: DashboardModel;
15}
16
17type State = {
18  isLoading: boolean;
19  isAppending: boolean;
20  versions: DecoratedRevisionModel[];
21  viewMode: 'list' | 'compare';
22  diffData: { lhs: any; rhs: any };
23  newInfo?: DecoratedRevisionModel;
24  baseInfo?: DecoratedRevisionModel;
25  isNewLatest: boolean;
26};
27
28export type DecoratedRevisionModel = RevisionsModel & {
29  createdDateString: string;
30  ageString: string;
31};
32
33export const VERSIONS_FETCH_LIMIT = 10;
34
35export class VersionsSettings extends PureComponent<Props, State> {
36  limit: number;
37  start: number;
38
39  constructor(props: Props) {
40    super(props);
41    this.limit = VERSIONS_FETCH_LIMIT;
42    this.start = 0;
43    this.state = {
44      isAppending: true,
45      isLoading: true,
46      versions: [],
47      viewMode: 'list',
48      isNewLatest: false,
49      diffData: {
50        lhs: {},
51        rhs: {},
52      },
53    };
54  }
55
56  componentDidMount() {
57    this.getVersions();
58  }
59
60  getVersions = (append = false) => {
61    this.setState({ isAppending: append });
62    historySrv
63      .getHistoryList(this.props.dashboard, { limit: this.limit, start: this.start })
64      .then((res) => {
65        this.setState({
66          isLoading: false,
67          versions: [...this.state.versions, ...this.decorateVersions(res)],
68        });
69        this.start += this.limit;
70      })
71      .catch((err) => console.log(err))
72      .finally(() => this.setState({ isAppending: false }));
73  };
74
75  getDiff = async () => {
76    const selectedVersions = this.state.versions.filter((version) => version.checked);
77    const [newInfo, baseInfo] = selectedVersions;
78    const isNewLatest = newInfo.version === this.props.dashboard.version;
79
80    this.setState({
81      isLoading: true,
82    });
83
84    const lhs = await historySrv.getDashboardVersion(this.props.dashboard.id, baseInfo.version);
85    const rhs = await historySrv.getDashboardVersion(this.props.dashboard.id, newInfo.version);
86
87    this.setState({
88      baseInfo,
89      isLoading: false,
90      isNewLatest,
91      newInfo,
92      viewMode: 'compare',
93      diffData: {
94        lhs: lhs.data,
95        rhs: rhs.data,
96      },
97    });
98  };
99
100  decorateVersions = (versions: RevisionsModel[]) =>
101    versions.map((version) => ({
102      ...version,
103      createdDateString: this.props.dashboard.formatDate(version.created),
104      ageString: this.props.dashboard.getRelativeTime(version.created),
105      checked: false,
106    }));
107
108  isLastPage() {
109    return this.state.versions.find((rev) => rev.version === 1);
110  }
111
112  onCheck = (ev: React.FormEvent<HTMLInputElement>, versionId: number) => {
113    this.setState({
114      versions: this.state.versions.map((version) =>
115        version.id === versionId ? { ...version, checked: ev.currentTarget.checked } : version
116      ),
117    });
118  };
119
120  reset = () => {
121    this.setState({
122      baseInfo: undefined,
123      diffData: {
124        lhs: {},
125        rhs: {},
126      },
127      isNewLatest: false,
128      newInfo: undefined,
129      versions: this.state.versions.map((version) => ({ ...version, checked: false })),
130      viewMode: 'list',
131    });
132  };
133
134  render() {
135    const { versions, viewMode, baseInfo, newInfo, isNewLatest, isLoading, diffData } = this.state;
136    const canCompare = versions.filter((version) => version.checked).length !== 2;
137    const showButtons = versions.length > 1;
138    const hasMore = versions.length >= this.limit;
139
140    if (viewMode === 'compare') {
141      return (
142        <div>
143          <VersionHistoryHeader
144            isComparing
145            onClick={this.reset}
146            baseVersion={baseInfo?.version}
147            newVersion={newInfo?.version}
148            isNewLatest={isNewLatest}
149          />
150          {isLoading ? (
151            <VersionsHistorySpinner msg="Fetching changes&hellip;" />
152          ) : (
153            <VersionHistoryComparison
154              newInfo={newInfo!}
155              baseInfo={baseInfo!}
156              isNewLatest={isNewLatest}
157              diffData={diffData}
158            />
159          )}
160        </div>
161      );
162    }
163
164    return (
165      <div>
166        <VersionHistoryHeader />
167        {isLoading ? (
168          <VersionsHistorySpinner msg="Fetching history list&hellip;" />
169        ) : (
170          <VersionHistoryTable versions={versions} onCheck={this.onCheck} />
171        )}
172        {this.state.isAppending && <VersionsHistorySpinner msg="Fetching more entries&hellip;" />}
173        {showButtons && (
174          <VersionsHistoryButtons
175            hasMore={hasMore}
176            canCompare={canCompare}
177            getVersions={this.getVersions}
178            getDiff={this.getDiff}
179            isLastPage={!!this.isLastPage()}
180          />
181        )}
182      </div>
183    );
184  }
185}
186
187const VersionsHistorySpinner = ({ msg }: { msg: string }) => (
188  <HorizontalGroup>
189    <Spinner />
190    <em>{msg}</em>
191  </HorizontalGroup>
192);
193