Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add metric timeline to metrics details #384

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions .changeset/popular-mirrors-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@krud-dev/boost': minor
---

Added metric timeline to metrics details
9 changes: 6 additions & 3 deletions src/renderer/components/widget/DashboardWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { ItemRO } from '../../definitions/daemon';
import PercentCircleDashboardWidget from './card/PercentCircleDashboardWidget';
import CountdownDashboardWidget from './card/CountdownDashboardWidget';
import HealthStatusDashboardWidget from './card/HealthStatusDashboardWidget';
import { SxProps } from '@mui/system';
import { Theme } from '@mui/material/styles';

interface DashboardWidgetProps {
widget: Widget;
item: ItemRO;
intervalSeconds: number;
variant?: 'outlined' | 'elevation';
sx?: SxProps<Theme>;
}

const DashboardWidget: FunctionComponent<DashboardWidgetProps> = ({ widget, item, intervalSeconds }) => {
const DashboardWidget: FunctionComponent<DashboardWidgetProps> = ({ widget, item, variant, sx }) => {
const DashboardCard = useMemo<ComponentType<DashboardWidgetCardProps<any>>>(() => {
switch (widget.type) {
case 'progress-circle':
Expand All @@ -35,6 +38,6 @@ const DashboardWidget: FunctionComponent<DashboardWidgetProps> = ({ widget, item
}
}, [widget]);

return <DashboardCard widget={widget} item={item} intervalSeconds={intervalSeconds} />;
return <DashboardCard widget={widget} item={item} variant={variant} sx={sx} />;
};
export default DashboardWidget;
10 changes: 7 additions & 3 deletions src/renderer/components/widget/card/CountdownDashboardWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import { CardContent, Typography } from '@mui/material';
import useWidgetSubscribeToMetrics from 'renderer/components/widget/hooks/useWidgetSubscribeToMetrics';
import { InstanceMetricRO } from '../../../../common/generated_definitions';
import { EMPTY_STRING } from '../../../constants/ui';
import { FormattedMessage } from 'react-intl';
import CountdownValue from '../metric/CountdownValue';

const CountdownDashboardWidget: FunctionComponent<DashboardWidgetCardProps<CountdownWidget>> = ({ widget, item }) => {
const CountdownDashboardWidget: FunctionComponent<DashboardWidgetCardProps<CountdownWidget>> = ({
widget,
item,
variant,
sx,
}) => {
const [data, setData] = useState<InstanceMetricRO | undefined>(undefined);
const loading = useMemo<boolean>(() => !data, [data]);

Expand All @@ -30,7 +34,7 @@ const CountdownDashboardWidget: FunctionComponent<DashboardWidgetCardProps<Count
useWidgetSubscribeToMetrics(item.id, metricNames, onMetricUpdate);

return (
<DashboardGenericCard title={<FormattedMessage id={widget.titleId} />} loading={loading}>
<DashboardGenericCard title={widget.title} loading={loading} variant={variant} sx={sx}>
<CardContent>
<Typography variant={'h3'} noWrap sx={{ textAlign: 'center' }}>
{!isNil(seconds) ? <CountdownValue seconds={seconds} /> : EMPTY_STRING}
Expand Down
22 changes: 17 additions & 5 deletions src/renderer/components/widget/card/DashboardGenericCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@ import { Card, CardContent, CardHeader } from '@mui/material';
import EmptyContent from 'renderer/components/help/EmptyContent';
import { FormattedMessage } from 'react-intl';
import LogoLoader from '../../common/LogoLoader';
import { SxProps } from '@mui/system';
import { Theme } from '@mui/material/styles';

type DashboardGenericCardProps = {
title: ReactNode;
loading: boolean;
title?: ReactNode;
loading?: boolean;
empty?: boolean;
variant?: 'outlined' | 'elevation';
sx?: SxProps<Theme>;
} & PropsWithChildren;

const DashboardGenericCard: FunctionComponent<DashboardGenericCardProps> = ({ title, loading, empty, children }) => {
const DashboardGenericCard: FunctionComponent<DashboardGenericCardProps> = ({
title,
loading,
empty,
variant,
sx,
children,
}) => {
const cardState = useMemo<'loading' | 'empty' | 'content'>(() => {
if (empty) {
return 'empty';
Expand All @@ -20,9 +31,10 @@ const DashboardGenericCard: FunctionComponent<DashboardGenericCardProps> = ({ ti
}
return 'content';
}, [loading, empty]);

return (
<Card sx={{ height: '100%' }}>
<CardHeader title={title} />
<Card variant={variant} sx={{ height: '100%', ...sx }}>
{title && <CardHeader title={title} />}
{cardState === 'loading' && (
<CardContent sx={{ textAlign: 'center' }}>
<LogoLoader />
Expand Down
12 changes: 8 additions & 4 deletions src/renderer/components/widget/card/DataBarDashboardWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import MetricValue from 'renderer/components/widget/metric/MetricValue';
import useWidgetSubscribeToMetrics from 'renderer/components/widget/hooks/useWidgetSubscribeToMetrics';
import { InstanceMetricRO } from '../../../../common/generated_definitions';
import { EMPTY_STRING } from '../../../constants/ui';
import { FormattedMessage } from 'react-intl';

const DataBarDashboardWidget: FunctionComponent<DashboardWidgetCardProps<DataBarWidget>> = ({ widget, item }) => {
const DataBarDashboardWidget: FunctionComponent<DashboardWidgetCardProps<DataBarWidget>> = ({
widget,
item,
variant,
sx,
}) => {
const [data, setData] = useState<{ [key: string]: InstanceMetricRO }>({});
const loading = useMemo<boolean>(() => Object.keys(data).length < widget.metrics.length, [data]);
const empty = useMemo<boolean>(() => !loading && Object.keys(data).length < widget.metrics.length, [loading, data]);
Expand All @@ -31,7 +35,7 @@ const DataBarDashboardWidget: FunctionComponent<DashboardWidgetCardProps<DataBar
useWidgetSubscribeToMetrics(item.id, metricNames, onMetricUpdate);

return (
<DashboardGenericCard title={<FormattedMessage id={widget.titleId} />} loading={loading} empty={empty}>
<DashboardGenericCard title={widget.title} loading={loading} empty={empty} variant={variant} sx={sx}>
<CardContent>
<Stack direction={'row'}>
{metrics.map((metric, index, array) => {
Expand All @@ -47,7 +51,7 @@ const DataBarDashboardWidget: FunctionComponent<DashboardWidgetCardProps<DataBar
variant={'subtitle2'}
sx={{ color: 'text.secondary', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
<FormattedMessage id={metric.titleId} />
{metric.title}
<HelpIcon title={tooltip} sx={{ ml: 0.5 }} />
</Typography>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import { getItemHealthStatusColor, getItemHealthStatusTextId } from '../../../ut
const HealthStatusDashboardWidget: FunctionComponent<DashboardWidgetCardProps<HealthStatusWidget>> = ({
widget,
item,
variant,
sx,
}) => {
const healthStatusColor = useMemo<string | undefined>(() => getItemHealthStatusColor(item), [item]);
const healthTextId = useMemo<string | undefined>(() => getItemHealthStatusTextId(item), [item]);

const loading = useMemo<boolean>(() => !healthTextId, [healthTextId]);

return (
<DashboardGenericCard title={<FormattedMessage id={widget.titleId} />} loading={loading}>
<DashboardGenericCard title={widget.title} loading={loading} variant={variant} sx={sx}>
<CardContent>
<Typography variant={'h3'} noWrap sx={{ textAlign: 'center', color: healthStatusColor }}>
{!isNil(healthTextId) ? <FormattedMessage id={healthTextId} /> : EMPTY_STRING}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import DashboardGenericCard from 'renderer/components/widget/card/DashboardGener
import { FormattedMessage } from 'react-intl';
import { CardContent, Typography } from '@mui/material';

const NotSupportedDashboardWidget: FunctionComponent<DashboardWidgetCardProps<Widget>> = ({ widget, item }) => {
const NotSupportedDashboardWidget: FunctionComponent<DashboardWidgetCardProps<Widget>> = ({
widget,
item,
variant,
sx,
}) => {
return (
<DashboardGenericCard title={<FormattedMessage id={widget.titleId} />} loading={false}>
<DashboardGenericCard title={widget.title} loading={false} variant={variant} sx={sx}>
<CardContent>
<Typography variant="body2" sx={{ color: 'error.main' }}>
<FormattedMessage id="notSupported" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ import useWidgetSubscribeToMetrics from 'renderer/components/widget/hooks/useWid
import { chain, isEmpty } from 'lodash';
import RadialBarSingle from 'renderer/components/widget/pure/RadialBarSingle';
import { InstanceMetricRO } from '../../../../common/generated_definitions';
import { FormattedMessage, useIntl } from 'react-intl';

const PercentCircleDashboardWidget: FunctionComponent<DashboardWidgetCardProps<PercentCircleWidget>> = ({
widget,
item,
variant,
sx,
}) => {
const intl = useIntl();

const [data, setData] = useState<{ percent?: number }>({ percent: undefined });

const title = useMemo<string>(() => intl.formatMessage({ id: widget.titleId }), [widget.titleId]);
const loading = useMemo<boolean>(() => data.percent === undefined, [data]);
const percent = useMemo<number>(() => Math.round((data.percent ?? 0) * 10000) / 100, [data]);
const color = useMemo<string>(() => {
Expand Down Expand Up @@ -43,8 +41,8 @@ const PercentCircleDashboardWidget: FunctionComponent<DashboardWidgetCardProps<P
useWidgetSubscribeToMetrics(item.id, metricNames, onMetricUpdate);

return (
<DashboardGenericCard title={<FormattedMessage id={widget.titleId} />} loading={loading}>
<RadialBarSingle title={title} color={color} percent={percent} />
<DashboardGenericCard title={widget.title} loading={loading} variant={variant} sx={sx}>
<RadialBarSingle title={widget.title} color={color} percent={percent} />
</DashboardGenericCard>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ import useWidgetSubscribeToMetrics from 'renderer/components/widget/hooks/useWid
import { chain, isEmpty } from 'lodash';
import RadialBarSingle from 'renderer/components/widget/pure/RadialBarSingle';
import { InstanceMetricRO } from '../../../../common/generated_definitions';
import { FormattedMessage, useIntl } from 'react-intl';

const ProgressCircleDashboardWidget: FunctionComponent<DashboardWidgetCardProps<ProgressCircleWidget>> = ({
widget,
item,
variant,
sx,
}) => {
const intl = useIntl();

const [data, setData] = useState<{ current?: number; max?: number }>({ current: undefined, max: undefined });

const title = useMemo<string>(() => intl.formatMessage({ id: widget.titleId }), [widget.titleId]);
const loading = useMemo<boolean>(() => data.current === undefined || data.max === undefined, [data]);
const percent = useMemo<number>(() => Math.round(((data.current ?? 0) / (data.max ?? 1)) * 10000) / 100, [data]);
const color = useMemo<string>(() => {
Expand Down Expand Up @@ -47,8 +45,8 @@ const ProgressCircleDashboardWidget: FunctionComponent<DashboardWidgetCardProps<
useWidgetSubscribeToMetrics(item.id, metricNames, onMetricUpdate);

return (
<DashboardGenericCard title={<FormattedMessage id={widget.titleId} />} loading={loading}>
<RadialBarSingle title={title} color={color} percent={percent} />
<DashboardGenericCard title={widget.title} loading={loading} variant={variant} sx={sx}>
<RadialBarSingle title={widget.title} color={color} percent={percent} />
</DashboardGenericCard>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { FunctionComponent, useCallback, useMemo, useRef, useState } from
import { DashboardWidgetCardProps, StackedTimelineWidget } from 'renderer/components/widget/widget';
import DashboardGenericCard from 'renderer/components/widget/card/DashboardGenericCard';
import { chain, every, isEmpty, isNaN, isNil, takeRight } from 'lodash';
import { FormattedMessage, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import useWidgetSubscribeToMetrics from 'renderer/components/widget/hooks/useWidgetSubscribeToMetrics';
import AreaMultiple from 'renderer/components/widget/pure/AreaMultiple';
import { formatWidgetChartValue } from 'renderer/utils/formatUtils';
Expand All @@ -14,12 +14,13 @@ type DataPoint = { values: number[]; timestamp: number };
const StackedTimelineDashboardWidget: FunctionComponent<DashboardWidgetCardProps<StackedTimelineWidget>> = ({
widget,
item,
intervalSeconds,
variant,
sx,
}) => {
const intl = useIntl();

const [data, setData] = useState<{ name: string; data: number[] }[]>(
widget.metrics.map((metric) => ({ name: intl.formatMessage({ id: metric.titleId }), data: [] }))
widget.metrics.map((metric) => ({ name: metric.title, data: [] }))
);
const [chartLabels, setChartLabels] = useState<string[]>([]);
const loading = useMemo<boolean>(() => isEmpty(chartLabels), [chartLabels]);
Expand All @@ -43,7 +44,7 @@ const StackedTimelineDashboardWidget: FunctionComponent<DashboardWidgetCardProps
});

const addDataPoint = useCallback((dataPointToAdd: DataPoint): void => {
if (dataPointToAdd.timestamp - lastDataPointTimestamp.current < intervalSeconds * 1000) {
if (dataPointToAdd.timestamp - lastDataPointTimestamp.current < 1000) {
return;
}
lastDataPointTimestamp.current = dataPointToAdd.timestamp;
Expand All @@ -64,7 +65,7 @@ const StackedTimelineDashboardWidget: FunctionComponent<DashboardWidgetCardProps
const chartColors = useMemo<string[]>(() => metrics.map((m) => m.color), [metrics]);

return (
<DashboardGenericCard title={<FormattedMessage id={widget.titleId} />} loading={loading} empty={empty}>
<DashboardGenericCard title={widget.title} loading={loading} empty={empty} variant={variant} sx={sx}>
<AreaMultiple series={data} labels={chartLabels} colors={chartColors} tickAmount={MAX_DATA_POINTS / 4} />
</DashboardGenericCard>
);
Expand Down
15 changes: 9 additions & 6 deletions src/renderer/components/widget/widget.d.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { ItemRO } from '../../definitions/daemon';
import { SxProps } from '@mui/system';
import { Theme } from '@mui/material/styles';

export interface DashboardWidgetCardProps<W extends Widget> {
widget: W;
item: ItemRO;
intervalSeconds: number;
variant?: 'outlined' | 'elevation';
sx?: SxProps<Theme>;
}

export type BaseWidgetDefinition = {
id: string;
type: string;
titleId: string;
title: string;
};

export type StackedTimelineWidget = BaseWidgetDefinition & {
type: 'stacked-timeline';
metrics: {
name: string;
titleId: string;
title: string;
order: number;
color: string;
valueType: WidgetValueType;
Expand All @@ -27,7 +30,7 @@ export type DataBarWidget = BaseWidgetDefinition & {
type: 'data-bar';
metrics: {
name: string;
titleId: string;
title: string;
order: number;
valueType: WidgetValueType;
}[];
Expand All @@ -47,7 +50,7 @@ export type ProgressCircleWidget = BaseWidgetDefinition & {
valueType: WidgetValueType;
maxMetricName: string;
currentMetricName: string;
titleId: string;
title: string;
color: string;
colorThresholds: {
value: number;
Expand All @@ -58,7 +61,7 @@ export type ProgressCircleWidget = BaseWidgetDefinition & {
export type PercentCircleWidget = BaseWidgetDefinition & {
type: 'percent-circle';
metricName: string;
titleId: string;
title: string;
color: string;
colorThresholds: {
value: number;
Expand Down
1 change: 1 addition & 0 deletions src/renderer/lang/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,5 @@ export default {
somethingWentWrong: 'Something went wrong',
somethingWentWrongExplanation: "Sorry, we couldn't complete the requested action.",
goToHome: 'Go to Home',
timeline: 'Timeline',
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const ApplicationDashboard: FunctionComponent = () => {
<Stack direction={'column'} spacing={COMPONENTS_SPACING}>
{`Application ${item.id}`}
{widgets.map((widget) => (
<DashboardWidget widget={widget} item={item} intervalSeconds={10} key={widget.id} />
<DashboardWidget widget={widget} item={item} key={widget.id} />
))}
</Stack>
</Page>
Expand Down
Loading