Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 85 additions & 100 deletions frontend/public/components/monitoring/dashboards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<CardBodyProps> = ({ 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],
);
Expand Down Expand Up @@ -370,98 +360,94 @@ const Card: React.FC<CardProps> = ({ panel }) => {
);
};

const Board: React.FC<BoardProps> = ({ board, patchVariable }) => {
const [data, setData] = React.useState();
const [error, setError] = React.useState<string>();
const Board: React.FC<BoardProps> = ({ rows }) => (
<>
{_.map(rows, (row, i) => (
<div className="row monitoring-dashboards__row" key={i}>
{_.map(row.panels, (panel) => (
<Card key={panel.id} panel={panel} />
))}
</div>
))}
</>
);

const MonitoringDashboardsPage_: React.FC<MonitoringDashboardsPageProps> = ({
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 <ErrorAlert message={error} />;
}
if (!data) {
return <LoadingInline />;
}
}, [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) => (
<div className="row monitoring-dashboards__row" key={i}>
{_.map(row.panels, (panel, j) => (
<Card key={j} panel={panel} />
))}
</div>
))}
</>
);
};
const changeBoard = (newBoard: string) => {
if (newBoard !== board) {
clearVariables();

const MonitoringDashboardsPage_: React.FC<MonitoringDashboardsPageProps> = ({
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 <ErrorAlert message={error} />;
}

const data = _.find(boards, { name: board })?.data;
const rows = _.isEmpty(data?.rows) ? [{ panels: data?.panels }] : data?.rows;

return (
<>
<Helmet>
Expand All @@ -474,18 +460,18 @@ const MonitoringDashboardsPage_: React.FC<MonitoringDashboardsPageProps> = ({
</div>
<h1 className="co-m-pane__heading">Dashboards</h1>
<div className="monitoring-dashboards__variables">
<VariableDropdown
items={boardItems}
label="Dashboard"
onChange={setBoard}
selectedKey={board}
/>
<AllVariableDropdowns />
{!_.isEmpty(boardItems) && (
<VariableDropdown
items={boardItems}
label="Dashboard"
onChange={changeBoard}
selectedKey={board}
/>
)}
<AllVariableDropdowns key={board} />
</div>
</div>
<Dashboard>
<Board board={board} patchVariable={patchVariable} />
</Dashboard>
<Dashboard>{isLoading ? <LoadingInline /> : <Board key={board} rows={rows} />}</Dashboard>
</>
);
};
Expand Down Expand Up @@ -533,8 +519,7 @@ type SingleVariableDropdownProps = {
};

type BoardProps = {
board: string;
patchVariable: (key: string, patch: Variable) => undefined;
rows: Panel[];
};

type AllVariableDropdownsProps = {
Expand Down