Skip to content

Commit

Permalink
feat: add empty state to the charts
Browse files Browse the repository at this point in the history
  • Loading branch information
jrea committed Nov 21, 2022
1 parent 90842ef commit b3fe2e1
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Bar } from 'react-chartjs-2';

import DefaultEmptyState from '../DefaultEmptyState';
import {
AggregateMetricsRequest,
AggregationType,
Expand All @@ -15,7 +16,7 @@ export default function AggregateBarChart(
aggregation: AggregateMetricsRequest;
} & MetricsBarChartComponentProps
) {
const { chartOptions, dataset } = props;
const { emptyState, chartOptions, dataset } = props;
const aggregationType: AggregationType = props.aggregation.aggregationType;
const { isLoading, buckets } = useAggregation(
// removed `startTime`, since it is possible to come from `useMetricsTime()`
Expand All @@ -28,6 +29,13 @@ export default function AggregateBarChart(
return null;
}

if (buckets && buckets?.length === 0) {
if (emptyState) {
return <>{emptyState}</>;
}
return <DefaultEmptyState />;
}

return (
<Bar
options={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MetricsLineChartComponentProps,
UseAggregationProps,
} from '../types';
import DefaultEmptyState from '../DefaultEmptyState';

import { useAggregation, useFormatData, useMinMax } from './hooks';

Expand All @@ -14,16 +15,25 @@ export default function AggregateLineChart(
aggregation: AggregateMetricsRequest;
} & MetricsLineChartComponentProps
) {
const { chartOptions, dataset } = props;
const { chartOptions, dataset, emptyState } = props;
const aggregationType = props.aggregation.aggregationType;
const { isLoading, buckets } = useAggregation(
props as unknown as UseAggregationProps
);
const data = useFormatData(buckets, aggregationType);
const minMax = useMinMax();

if (isLoading) {
return null;
}

if (buckets && buckets?.length === 0) {
if (emptyState) {
return <>{emptyState}</>;
}
return <DefaultEmptyState />;
}

return (
<Line
options={{
Expand Down
22 changes: 22 additions & 0 deletions packages/react/src/components/Metrics/DefaultEmptyState/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import TroubleshootIcon from '@mui/icons-material/Troubleshoot';
import Stack from '@mui/joy/Stack';
import Typography from '@mui/joy/Typography';

export default function DefaultEmptyState() {
return (
<Stack
direction="row"
spacing={1}
sx={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
minHeight: '300px',
}}
>
<TroubleshootIcon sx={{ fontSize: 32 }} />
<Typography>No results</Typography>
</Stack>
);
}
25 changes: 12 additions & 13 deletions packages/react/src/components/Metrics/Filter/FilterBarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import {
Tooltip,
Legend,
TimeScale,
ChartDataset,
ChartOptions,
BarElement,
} from 'chart.js';

import DefaultEmptyState from '../DefaultEmptyState';
import { MetricsBarChartComponentProps } from '../types';

import { useFilter, useFormatData, useMinMax } from './hooks';

Chart.register(
Expand All @@ -26,19 +27,10 @@ Chart.register(
TimeScale
);

export type MetricsComponentProps = {
timeFormat?: string;
dataset?: Omit<ChartDataset<'bar', number[]>, 'data'>;
updateInterval?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
chartOptions?: ChartOptions<any>;
queryKey?: string;
};

export default function FilterBarChart(
props: FilterMetricsRequest & MetricsComponentProps
props: FilterMetricsRequest & MetricsBarChartComponentProps
) {
const { filter, chartOptions, dataset } = props;
const { emptyState, filter, chartOptions, dataset } = props;
const { isLoading, metrics } = useFilter(props);
const metricName = filter.metricName;
const data = useFormatData(metrics);
Expand All @@ -63,6 +55,13 @@ export default function FilterBarChart(
return null;
}

if (metrics && metrics?.length === 0) {
if (emptyState) {
return <>{emptyState}</>;
}
return <DefaultEmptyState />;
}

return (
<Bar
options={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { Line } from 'react-chartjs-2';
import { FilterMetricsRequest } from '@theniledev/js';

import { MetricsLineChartComponentProps } from '../types';
import DefaultEmptyState from '../DefaultEmptyState';

import { useFilter, useFormatData, useMinMax } from './hooks';

export default function FilterLineChart(
props: FilterMetricsRequest & MetricsLineChartComponentProps
) {
const { filter, chartOptions, dataset } = props;
const { emptyState, filter, chartOptions, dataset } = props;
const { isLoading, metrics } = useFilter(props);
const minMax = useMinMax(filter);
const data = useFormatData(metrics);
Expand All @@ -18,6 +19,12 @@ export default function FilterLineChart(
return null;
}

if (metrics && metrics?.length === 0) {
if (emptyState) {
return <>{emptyState}</>;
}
return <DefaultEmptyState />;
}
return (
<Line
options={{
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/components/Metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ This dataset will vary based on the type of chart.

- A configuration object to render the graph. Maps to [chartjs chart configuration](https://www.chartjs.org/docs/latest/configuration/#configuration-object-structure)

**emptyState**

- Overrides the default JSX.Element rendered when the chart is empty.

#### StartTime

If you want to provide users with a time series where they can select a start time to browse metrics, use the `<StartTime />` component. The value set will apply to all charts.
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/Metrics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type HookConfig = {
};

type MetricsChartCommonProps = HookConfig & {
timeFormat?: string;
emptyState?: JSX.Element;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
chartOptions?: ChartOptions<any>;
};
Expand Down
67 changes: 67 additions & 0 deletions packages/react/stories/Metrics/CustomizedEmptyChart.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import React from 'react';
import { AggregationRequestBucketSizeEnum } from '@theniledev/js';
import { Story } from '@storybook/react';
import Stack from '@mui/joy/Stack';
import Typography from '@mui/joy/Typography';

import { MetricsBarChart } from '../../src/components/Metrics';
import { AggregationType } from '../../src/components/Metrics/types';
import { NileProvider } from '../../src/context';

const meta = {
component: MetricsBarChart,
parameters: {
controls: { expanded: false },
},
};

export default meta;

const Empty: Story<null> = () => {
const aggregation = {
aggregationType: AggregationType.Sum,
aggregationRequest: {
organizationId: 'myOrganization',
bucketSize: AggregationRequestBucketSizeEnum._10m,
},
metricName: 'my.metric',
};
return (
<NileProvider basePath="http://localhost:8080" workspace="workspace">
<MetricsBarChart
aggregation={aggregation}
emptyState={
<Stack sx={{ width: '560px' }}>
{/* eslint-disable-next-line react/no-unknown-property*/}
<marquee behavior="alternate" scrollamount="20">
<Typography level="h1">NO METRICS</Typography>
</marquee>
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=true"
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
/>
</Stack>
}
/>
</NileProvider>
);
};

export const CustomizedEmptyChart = Empty.bind({});

CustomizedEmptyChart.parameters = {
mockData: [
{
url: 'http://localhost:8080/workspaces/workspace/metrics/my.metric/aggregate',
method: 'POST',
status: 200,
response: [],
},
],
};
45 changes: 45 additions & 0 deletions packages/react/stories/Metrics/EmptyChart.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { AggregationRequestBucketSizeEnum } from '@theniledev/js';
import { Story } from '@storybook/react';

import { MetricsBarChart } from '../../src/components/Metrics';
import { AggregationType } from '../../src/components/Metrics/types';
import { NileProvider } from '../../src/context';

const meta = {
component: MetricsBarChart,
parameters: {
controls: { expanded: false },
},
};

export default meta;

const Empty: Story<null> = () => {
const aggregation = {
aggregationType: AggregationType.Sum,
aggregationRequest: {
organizationId: 'myOrganization',
bucketSize: AggregationRequestBucketSizeEnum._10m,
},
metricName: 'my.metric',
};
return (
<NileProvider basePath="http://localhost:8080" workspace="workspace">
<MetricsBarChart aggregation={aggregation} />
</NileProvider>
);
};

export const EmptyChart = Empty.bind({});

EmptyChart.parameters = {
mockData: [
{
url: 'http://localhost:8080/workspaces/workspace/metrics/my.metric/aggregate',
method: 'POST',
status: 200,
response: [],
},
],
};

2 comments on commit b3fe2e1

@vercel
Copy link

@vercel vercel bot commented on b3fe2e1 Nov 21, 2022

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

nile-js – ./

nile-js.vercel.app
nile-js-theniledev.vercel.app
nile-js-git-master-theniledev.vercel.app

@vercel
Copy link

@vercel vercel bot commented on b3fe2e1 Nov 21, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.