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')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to fix this endpoint for off-cluster mode since we won't have a service account token.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to fix this endpoint for off-cluster mode since we won't have a service account token.

@kyoto I opened #4407 to make it work off-cluster for development

.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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid as when possible. It usually means missing or wrong types. This is slightly better:

Suggested change
_.each(data?.templating?.list as TemplateVariable[], (v) => {
_.each(data?.templating?.list, (v: TemplateVariable) => {

Do we have a type we can assign to data?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by adding a Board type in PR #4454

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