From 867c842577d37db53cd4cadeb35e366b45f5120b Mon Sep 17 00:00:00 2001 From: Andy Pickering Date: Thu, 20 Feb 2020 20:47:03 +0900 Subject: [PATCH] Monitoring Dashboards: Load available dashboards dynamically Load the list of available dashboards from the recently added `/api/console/monitoring-dashboard-config` endpoint. --- .../monitoring/dashboards/index.tsx | 185 ++++++++---------- 1 file changed, 85 insertions(+), 100 deletions(-) diff --git a/frontend/public/components/monitoring/dashboards/index.tsx b/frontend/public/components/monitoring/dashboards/index.tsx index 99f404da5d0..5d050d47ddf 100644 --- a/frontend/public/components/monitoring/dashboards/index.tsx +++ b/frontend/public/components/monitoring/dashboards/index.tsx @@ -14,7 +14,6 @@ import DashboardCardHeader from '@console/shared/src/components/dashboard/dashbo import DashboardCardTitle from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardTitle'; import * as UIActions from '../../../actions/ui'; -import { k8sBasePath } from '../../../module/k8s'; import { ErrorBoundaryFallback } from '../../error'; import { RootState } from '../../../redux'; import { getPrometheusURL, PrometheusEndpoint } from '../../graphs/helpers'; @@ -268,30 +267,21 @@ const PollIntervalDropdown = connect( }, )(PollIntervalDropdown_); -// TODO: Dynamically load the list of dashboards -const boards = [ - 'etcd', - 'k8s-resources-cluster', - 'k8s-resources-namespace', - 'k8s-resources-workloads-namespace', - 'k8s-resources-node', - 'k8s-resources-pod', - 'k8s-resources-workload', - 'cluster-total', - 'prometheus', - 'node-cluster-rsrc-use', - 'node-rsrc-use', -]; -const boardItems = _.zipObject(boards, boards); - // Matches Prometheus labels surrounded by {{ }} in the graph legend label templates const legendTemplateOptions = { interpolate: /{{([a-zA-Z_][a-zA-Z0-9_]*)}}/g }; const CardBody_: React.FC = ({ panel, pollInterval, variables }) => { const formatLegendLabel = React.useCallback( (labels, i) => { - const compiled = _.template(panel.targets?.[i]?.legendFormat, legendTemplateOptions); - return compiled(labels); + const legendFormat = panel.targets?.[i]?.legendFormat; + const compiled = _.template(legendFormat, legendTemplateOptions); + try { + return compiled(labels); + } catch (e) { + // If we can't format the label (e.g. if one of it's variables is missing from `labels`), + // show the template string instead + return legendFormat; + } }, [panel], ); @@ -370,98 +360,94 @@ const Card: React.FC = ({ panel }) => { ); }; -const Board: React.FC = ({ board, patchVariable }) => { - const [data, setData] = React.useState(); - const [error, setError] = React.useState(); +const Board: React.FC = ({ rows }) => ( + <> + {_.map(rows, (row, i) => ( +
+ {_.map(row.panels, (panel) => ( + + ))} +
+ ))} + +); + +const MonitoringDashboardsPage_: React.FC = ({ + clearVariables, + deleteAll, + match, + patchVariable, +}) => { + const [board, setBoard] = React.useState(); + const [boards, setBoards] = React.useState([]); + const [error, setError] = React.useState(); + const [isLoading, , , setLoaded] = useBoolean(true); const safeFetch = React.useCallback(useSafeFetch(), []); + // Clear queries on unmount + React.useEffect(() => deleteAll, [deleteAll]); + React.useEffect(() => { - if (!board) { - return; - } - setData(undefined); - setError(undefined); - const path = `${k8sBasePath}/api/v1/namespaces/openshift-monitoring/configmaps/grafana-dashboard-${board}`; - safeFetch(path) + safeFetch('/api/console/monitoring-dashboard-config') .then((response) => { - const json = _.get(response, ['data', `${board}.json`]); - if (!json) { - setError('Dashboard definition JSON not found'); - } else { - const newData = JSON.parse(json); - - _.each(newData?.templating?.list as TemplateVariable[], (v) => { - if (v.type === 'query' || v.type === 'interval') { - patchVariable(v.name, { - isHidden: v.hide !== 0, - options: _.map(v.options, 'value'), - query: v.type === 'query' ? v.query : undefined, - value: _.find(v.options, { selected: true })?.value || v.options?.[0]?.value, - }); - } - }); + setLoaded(); + setError(undefined); - // Call this after creating the board's variables so we don't trigger a component update - // that will fail due to missing variables - setData(newData); - } + const getBoardData = (item) => ({ + data: JSON.parse(_.values(item?.data)[0]), + name: item.metadata.name, + }); + const newBoards = _.sortBy(_.map(response.items, getBoardData), (v) => + _.toLower(v?.data?.title), + ); + setBoards(newBoards); }) .catch((err) => { + setLoaded(); if (err.name !== 'AbortError') { setError(_.get(err, 'json.error', err.message)); } }); - }, [board, patchVariable, safeFetch]); - - if (!board) { - return null; - } - if (error) { - return ; - } - if (!data) { - return ; - } + }, [safeFetch, setLoaded]); - const rows = _.isEmpty(data.rows) ? [{ panels: data.panels }] : data.rows; + const boardItems = React.useMemo(() => _.mapValues(_.mapKeys(boards, 'name'), 'data.title'), [ + boards, + ]); - return ( - <> - {_.map(rows, (row, i) => ( -
- {_.map(row.panels, (panel, j) => ( - - ))} -
- ))} - - ); -}; + const changeBoard = (newBoard: string) => { + if (newBoard !== board) { + clearVariables(); -const MonitoringDashboardsPage_: React.FC = ({ - clearVariables, - deleteAll, - match, - patchVariable, -}) => { - const { board } = match.params; + const data = _.find(boards, { name: newBoard })?.data; - // Clear queries on unmount - React.useEffect(() => deleteAll, [deleteAll]); + _.each(data?.templating?.list as TemplateVariable[], (v) => { + if (v.type === 'query' || v.type === 'interval') { + patchVariable(v.name, { + isHidden: v.hide !== 0, + options: _.map(v.options, 'value'), + query: v.type === 'query' ? v.query : undefined, + value: _.find(v.options, { selected: true })?.value || v.options?.[0]?.value, + }); + } + }); - const setBoard = (newBoard: string) => { - if (newBoard !== board) { - clearVariables(); + setBoard(newBoard); history.replace(`/monitoring/dashboards/${newBoard}`); } }; - if (!board && boards?.[0]) { - setBoard(boards[0]); - return null; + if (!board && !_.isEmpty(boards)) { + changeBoard(match.params.board || boards?.[0]?.name); } + if (error) { + return ; + } + + const data = _.find(boards, { name: board })?.data; + const rows = _.isEmpty(data?.rows) ? [{ panels: data?.panels }] : data?.rows; + return ( <> @@ -474,18 +460,18 @@ const MonitoringDashboardsPage_: React.FC = ({

Dashboards

- - + {!_.isEmpty(boardItems) && ( + + )} +
- - - + {isLoading ? : } ); }; @@ -533,8 +519,7 @@ type SingleVariableDropdownProps = { }; type BoardProps = { - board: string; - patchVariable: (key: string, patch: Variable) => undefined; + rows: Panel[]; }; type AllVariableDropdownsProps = {