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