1import React, { ReactElement, useEffect, useState } from 'react';
2import { css } from '@emotion/css';
3import { useAsync } from 'react-use';
4import { CollapsableSection, HorizontalGroup, Icon, Spinner, Tooltip, useStyles, VerticalGroup } from '@grafana/ui';
5import { GrafanaTheme } from '@grafana/data';
6import { reportInteraction } from '@grafana/runtime';
7
8import { VariableModel } from '../types';
9import { DashboardModel } from '../../dashboard/state';
10import { VariablesUnknownButton } from './VariablesUnknownButton';
11import { getUnknownsNetwork, UsagesToNetwork } from './utils';
12
13export const SLOW_VARIABLES_EXPANSION_THRESHOLD = 1000;
14
15export interface VariablesUnknownTableProps {
16  variables: VariableModel[];
17  dashboard: DashboardModel | null;
18}
19
20export function VariablesUnknownTable({ variables, dashboard }: VariablesUnknownTableProps): ReactElement {
21  const [open, setOpen] = useState(false);
22  const [changed, setChanged] = useState(0);
23  const [usages, setUsages] = useState<UsagesToNetwork[]>([]);
24  const style = useStyles(getStyles);
25  useEffect(() => setChanged((prevState) => prevState + 1), [variables, dashboard]);
26  const { loading } = useAsync(async () => {
27    if (open && changed > 0) {
28      // make sure we only fetch when opened and variables or dashboard have changed
29      const start = Date.now();
30      const unknownsNetwork = await getUnknownsNetwork(variables, dashboard);
31      const stop = Date.now();
32      const elapsed = stop - start;
33      if (elapsed >= SLOW_VARIABLES_EXPANSION_THRESHOLD) {
34        reportInteraction('Slow unknown variables expansion', { elapsed });
35      }
36      setChanged(0);
37      setUsages(unknownsNetwork);
38      return unknownsNetwork;
39    }
40
41    return [];
42  }, [variables, dashboard, open, changed]);
43
44  const onToggle = (isOpen: boolean) => {
45    if (isOpen) {
46      reportInteraction('Unknown variables section expanded');
47    }
48
49    setOpen(isOpen);
50  };
51
52  return (
53    <div className={style.container}>
54      <CollapsableSection label={<CollapseLabel />} isOpen={open} onToggle={onToggle}>
55        {loading && (
56          <VerticalGroup justify="center">
57            <HorizontalGroup justify="center">
58              <span>Loading...</span>
59              <Spinner size={16} />
60            </HorizontalGroup>
61          </VerticalGroup>
62        )}
63        {!loading && usages && (
64          <>
65            {usages.length === 0 && <NoUnknowns />}
66            {usages.length > 0 && <UnknownTable usages={usages} />}
67          </>
68        )}
69      </CollapsableSection>
70    </div>
71  );
72}
73
74function CollapseLabel(): ReactElement {
75  const style = useStyles(getStyles);
76  return (
77    <h5>
78      Renamed or missing variables
79      <Tooltip content="Click to expand a list with all variable references that have been renamed or are missing from the dashboard.">
80        <Icon name="info-circle" className={style.infoIcon} />
81      </Tooltip>
82    </h5>
83  );
84}
85
86function NoUnknowns(): ReactElement {
87  return <span>No renamed or missing variables found.</span>;
88}
89
90function UnknownTable({ usages }: { usages: UsagesToNetwork[] }): ReactElement {
91  const style = useStyles(getStyles);
92  return (
93    <table className="filter-table filter-table--hover">
94      <thead>
95        <tr>
96          <th>Variable</th>
97          <th colSpan={5} />
98        </tr>
99      </thead>
100      <tbody>
101        {usages.map((usage) => {
102          const { variable } = usage;
103          const { id, name } = variable;
104          return (
105            <tr key={id}>
106              <td className={style.firstColumn}>
107                <span>{name}</span>
108              </td>
109              <td className={style.defaultColumn} />
110              <td className={style.defaultColumn} />
111              <td className={style.defaultColumn} />
112              <td className={style.lastColumn}>
113                <VariablesUnknownButton id={variable.id} usages={usages} />
114              </td>
115            </tr>
116          );
117        })}
118      </tbody>
119    </table>
120  );
121}
122
123const getStyles = (theme: GrafanaTheme) => ({
124  container: css`
125    margin-top: ${theme.spacing.xl};
126    padding-top: ${theme.spacing.xl};
127  `,
128  infoIcon: css`
129    margin-left: ${theme.spacing.sm};
130  `,
131  defaultColumn: css`
132    width: 1%;
133  `,
134  firstColumn: css`
135    width: 1%;
136    vertical-align: top;
137    color: ${theme.colors.textStrong};
138  `,
139  lastColumn: css`
140    overflow: hidden;
141    text-overflow: ellipsis;
142    white-space: nowrap;
143    width: 100%;
144    text-align: right;
145  `,
146});
147