|
@@ -1,6 +1,4 @@
|
|
|
-import {Component, Fragment} from 'react';
|
|
|
-import type {Theme} from '@emotion/react';
|
|
|
-import {withTheme} from '@emotion/react';
|
|
|
+import {type Theme, useTheme} from '@emotion/react';
|
|
|
import styled from '@emotion/styled';
|
|
|
import Color from 'color';
|
|
|
import type {
|
|
@@ -22,9 +20,8 @@ import {DATA_CATEGORY_INFO} from 'sentry/constants';
|
|
|
import {IconWarning} from 'sentry/icons';
|
|
|
import {t} from 'sentry/locale';
|
|
|
import {space} from 'sentry/styles/space';
|
|
|
-import type {DataCategoryInfo, IntervalPeriod, SelectValue} from 'sentry/types';
|
|
|
+import type {DataCategoryInfo, IntervalPeriod, SelectValue} from 'sentry/types/core';
|
|
|
import {parsePeriodToHours, statsPeriodToDays} from 'sentry/utils/dates';
|
|
|
-import getDynamicText from 'sentry/utils/getDynamicText';
|
|
|
import commonTheme from 'sentry/utils/theme';
|
|
|
|
|
|
import {formatUsageWithUnits} from '../utils';
|
|
@@ -32,7 +29,6 @@ import {formatUsageWithUnits} from '../utils';
|
|
|
import {getTooltipFormatter, getXAxisDates, getXAxisLabelInterval} from './utils';
|
|
|
|
|
|
const GIGABYTE = 10 ** 9;
|
|
|
-type ChartProps = React.ComponentProps<typeof BaseChart>;
|
|
|
|
|
|
const COLOR_ERRORS = Color(commonTheme.dataCategory.errors).lighten(0.25).string();
|
|
|
const COLOR_TRANSACTIONS = Color(commonTheme.dataCategory.transactions)
|
|
@@ -116,38 +112,10 @@ export enum SeriesTypes {
|
|
|
FILTERED = 'Filtered',
|
|
|
}
|
|
|
|
|
|
-type DefaultProps = {
|
|
|
- /**
|
|
|
- * Config for category dropdown options
|
|
|
- */
|
|
|
- categoryOptions: CategoryOption[];
|
|
|
- /**
|
|
|
- * Modify the usageStats using the transformation method selected.
|
|
|
- * 1. This must be a pure function!
|
|
|
- * 2. If the parent component will handle the data transformation, you should
|
|
|
- * replace this prop with "(s) => {return s}"
|
|
|
- */
|
|
|
- handleDataTransformation: (
|
|
|
- stats: ChartStats,
|
|
|
- transform: ChartDataTransform
|
|
|
- ) => ChartStats;
|
|
|
-
|
|
|
- /**
|
|
|
- * Intervals between the x-axis values
|
|
|
- */
|
|
|
- usageDateInterval: IntervalPeriod;
|
|
|
-
|
|
|
- /**
|
|
|
- * Display datetime in UTC
|
|
|
- */
|
|
|
- usageDateShowUtc: boolean;
|
|
|
-};
|
|
|
-
|
|
|
-export type UsageChartProps = DefaultProps & {
|
|
|
+export type UsageChartProps = {
|
|
|
dataCategory: DataCategoryInfo['plural'];
|
|
|
|
|
|
dataTransform: ChartDataTransform;
|
|
|
- theme: Theme;
|
|
|
usageDateEnd: string;
|
|
|
|
|
|
usageDateStart: string;
|
|
@@ -161,27 +129,76 @@ export type UsageChartProps = DefaultProps & {
|
|
|
*/
|
|
|
categoryColors?: string[];
|
|
|
|
|
|
+ /**
|
|
|
+ * Config for category dropdown options
|
|
|
+ */
|
|
|
+ categoryOptions?: CategoryOption[];
|
|
|
/**
|
|
|
* Additional data to draw on the chart alongside usage
|
|
|
*/
|
|
|
chartSeries?: SeriesOption[];
|
|
|
+
|
|
|
/**
|
|
|
* Replace default tooltip
|
|
|
*/
|
|
|
chartTooltip?: TooltipComponentOption;
|
|
|
-
|
|
|
errors?: Record<string, Error>;
|
|
|
- footer?: React.ReactNode;
|
|
|
|
|
|
- isError?: boolean;
|
|
|
+ /**
|
|
|
+ * Modify the usageStats using the transformation method selected.
|
|
|
+ * If the parent component will handle the data transformation, you should
|
|
|
+ * replace this prop with "(s) => {return s}"
|
|
|
+ */
|
|
|
+ handleDataTransformation?: (
|
|
|
+ stats: Readonly<ChartStats>,
|
|
|
+ transform: Readonly<ChartDataTransform>
|
|
|
+ ) => ChartStats;
|
|
|
|
|
|
+ isError?: boolean;
|
|
|
isLoading?: boolean;
|
|
|
|
|
|
- title?: React.ReactNode;
|
|
|
+ /**
|
|
|
+ * Intervals between the x-axis values
|
|
|
+ */
|
|
|
+ usageDateInterval?: IntervalPeriod;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Display datetime in UTC
|
|
|
+ */
|
|
|
+ usageDateShowUtc?: boolean;
|
|
|
};
|
|
|
|
|
|
-type State = {
|
|
|
- xAxisDates: string[];
|
|
|
+/**
|
|
|
+ * When the data transformation is set to cumulative, the chart will display
|
|
|
+ * the total sum of the data points up to that point.
|
|
|
+ */
|
|
|
+const cumulativeTotalDataTransformation: UsageChartProps['handleDataTransformation'] = (
|
|
|
+ stats,
|
|
|
+ transform
|
|
|
+) => {
|
|
|
+ const chartData: ChartStats = {
|
|
|
+ accepted: [],
|
|
|
+ dropped: [],
|
|
|
+ projected: [],
|
|
|
+ filtered: [],
|
|
|
+ };
|
|
|
+ const isCumulative = transform === ChartDataTransform.CUMULATIVE;
|
|
|
+
|
|
|
+ Object.keys(stats).forEach(k => {
|
|
|
+ let count = 0;
|
|
|
+
|
|
|
+ chartData[k] = stats[k].map((stat: any) => {
|
|
|
+ const [x, y] = stat.value;
|
|
|
+ count = isCumulative ? count + y : y;
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...stat,
|
|
|
+ value: [x, count],
|
|
|
+ };
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ return chartData;
|
|
|
};
|
|
|
|
|
|
export type ChartStats = {
|
|
@@ -191,41 +208,75 @@ export type ChartStats = {
|
|
|
filtered?: NonNullable<BarSeriesOption['data']>;
|
|
|
};
|
|
|
|
|
|
-export class UsageChart extends Component<UsageChartProps, State> {
|
|
|
- static defaultProps: DefaultProps = {
|
|
|
- categoryOptions: CHART_OPTIONS_DATACATEGORY,
|
|
|
- usageDateShowUtc: true,
|
|
|
- usageDateInterval: '1d',
|
|
|
- handleDataTransformation: (stats, transform) => {
|
|
|
- const chartData: ChartStats = {
|
|
|
- accepted: [],
|
|
|
- dropped: [],
|
|
|
- projected: [],
|
|
|
- filtered: [],
|
|
|
- };
|
|
|
- const isCumulative = transform === ChartDataTransform.CUMULATIVE;
|
|
|
+function chartMetadata({
|
|
|
+ categoryOptions,
|
|
|
+ dataCategory,
|
|
|
+ usageStats,
|
|
|
+ dataTransform,
|
|
|
+ usageDateStart,
|
|
|
+ usageDateEnd,
|
|
|
+ usageDateInterval,
|
|
|
+ usageDateShowUtc,
|
|
|
+ handleDataTransformation,
|
|
|
+}: Required<
|
|
|
+ Pick<
|
|
|
+ UsageChartProps,
|
|
|
+ | 'categoryOptions'
|
|
|
+ | 'dataCategory'
|
|
|
+ | 'handleDataTransformation'
|
|
|
+ | 'usageStats'
|
|
|
+ | 'dataTransform'
|
|
|
+ | 'usageDateStart'
|
|
|
+ | 'usageDateEnd'
|
|
|
+ | 'usageDateInterval'
|
|
|
+ | 'usageDateShowUtc'
|
|
|
+ >
|
|
|
+>): {
|
|
|
+ chartData: ChartStats;
|
|
|
+ chartLabel: React.ReactNode;
|
|
|
+ tooltipValueFormatter: (val?: number) => string;
|
|
|
+ xAxisData: string[];
|
|
|
+ xAxisLabelInterval: number;
|
|
|
+ xAxisTickInterval: number;
|
|
|
+ yAxisFormatter: (val: number) => string;
|
|
|
+ yAxisMinInterval: number;
|
|
|
+} {
|
|
|
+ const selectDataCategory = categoryOptions.find(o => o.value === dataCategory);
|
|
|
+ if (!selectDataCategory) {
|
|
|
+ throw new Error('Selected item is not supported');
|
|
|
+ }
|
|
|
|
|
|
- Object.keys(stats).forEach(k => {
|
|
|
- let count = 0;
|
|
|
+ // Do not assume that handleDataTransformation is a pure function
|
|
|
+ const chartData: ChartStats = {
|
|
|
+ ...handleDataTransformation(usageStats, dataTransform),
|
|
|
+ };
|
|
|
|
|
|
- chartData[k] = stats[k].map(stat => {
|
|
|
- const [x, y] = stat.value;
|
|
|
- count = isCumulative ? count + y : y;
|
|
|
+ Object.keys(chartData).forEach(k => {
|
|
|
+ const isProjected = k === SeriesTypes.PROJECTED;
|
|
|
|
|
|
- return {
|
|
|
- ...stat,
|
|
|
- value: [x, count],
|
|
|
- };
|
|
|
- });
|
|
|
- });
|
|
|
+ // Map the array and destructure elements to avoid side-effects
|
|
|
+ chartData[k] = chartData[k]?.map((stat: any) => {
|
|
|
+ return {
|
|
|
+ ...stat,
|
|
|
+ tooltip: {show: false},
|
|
|
+ itemStyle: {opacity: isProjected ? 0.6 : 1},
|
|
|
+ };
|
|
|
+ });
|
|
|
+ });
|
|
|
|
|
|
- return chartData;
|
|
|
- },
|
|
|
- };
|
|
|
+ // Use hours as common units
|
|
|
+ const dataPeriod = statsPeriodToDays(undefined, usageDateStart, usageDateEnd) * 24;
|
|
|
+ const barPeriod = parsePeriodToHours(usageDateInterval);
|
|
|
+ if (dataPeriod < 0 || barPeriod < 0) {
|
|
|
+ throw new Error('UsageChart: Unable to parse data time period');
|
|
|
+ }
|
|
|
|
|
|
- state: State = {
|
|
|
- xAxisDates: [],
|
|
|
- };
|
|
|
+ const {xAxisTickInterval, xAxisLabelInterval} = getXAxisLabelInterval(
|
|
|
+ dataPeriod,
|
|
|
+ dataPeriod / barPeriod
|
|
|
+ );
|
|
|
+
|
|
|
+ const {label, yAxisMinInterval} = selectDataCategory;
|
|
|
|
|
|
/**
|
|
|
* UsageChart needs to generate the X-Axis dates as props.usageStats may
|
|
@@ -234,157 +285,103 @@ export class UsageChart extends Component<UsageChartProps, State> {
|
|
|
* E.g. usageStats.accepted covers day 1-15 of a month, usageStats.projected
|
|
|
* either covers day 16-30 or may not be available at all.
|
|
|
*/
|
|
|
- static getDerivedStateFromProps(
|
|
|
- nextProps: Readonly<UsageChartProps>,
|
|
|
- prevState: State
|
|
|
- ): State {
|
|
|
- const {usageDateStart, usageDateEnd, usageDateShowUtc, usageDateInterval} = nextProps;
|
|
|
-
|
|
|
- return {
|
|
|
- ...prevState,
|
|
|
- xAxisDates: getXAxisDates(
|
|
|
- usageDateStart,
|
|
|
- usageDateEnd,
|
|
|
- usageDateShowUtc,
|
|
|
- usageDateInterval
|
|
|
- ),
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- get chartColors() {
|
|
|
- const {dataCategory, theme} = this.props;
|
|
|
- const COLOR_PROJECTED = theme.chartOther;
|
|
|
-
|
|
|
- if (dataCategory === DATA_CATEGORY_INFO.error.plural) {
|
|
|
- return [COLOR_ERRORS, COLOR_FILTERED, COLOR_DROPPED, COLOR_PROJECTED];
|
|
|
- }
|
|
|
+ const xAxisDates = getXAxisDates(
|
|
|
+ usageDateStart,
|
|
|
+ usageDateEnd,
|
|
|
+ usageDateShowUtc,
|
|
|
+ usageDateInterval
|
|
|
+ );
|
|
|
+
|
|
|
+ return {
|
|
|
+ chartLabel: label,
|
|
|
+ chartData,
|
|
|
+ xAxisData: xAxisDates,
|
|
|
+ xAxisTickInterval,
|
|
|
+ xAxisLabelInterval,
|
|
|
+ yAxisMinInterval,
|
|
|
+ yAxisFormatter: (val: number) =>
|
|
|
+ formatUsageWithUnits(val, dataCategory, {
|
|
|
+ isAbbreviated: true,
|
|
|
+ useUnitScaling: true,
|
|
|
+ }),
|
|
|
+ tooltipValueFormatter: getTooltipFormatter(dataCategory),
|
|
|
+ };
|
|
|
+}
|
|
|
|
|
|
- if (dataCategory === DATA_CATEGORY_INFO.attachment.plural) {
|
|
|
- return [COLOR_ATTACHMENTS, COLOR_FILTERED, COLOR_DROPPED, COLOR_PROJECTED];
|
|
|
- }
|
|
|
+function chartColors(theme: Theme, dataCategory: UsageChartProps['dataCategory']) {
|
|
|
+ const COLOR_PROJECTED = theme.chartOther;
|
|
|
|
|
|
- return [COLOR_TRANSACTIONS, COLOR_FILTERED, COLOR_DROPPED, COLOR_PROJECTED];
|
|
|
+ if (dataCategory === DATA_CATEGORY_INFO.error.plural) {
|
|
|
+ return [COLOR_ERRORS, COLOR_FILTERED, COLOR_DROPPED, COLOR_PROJECTED];
|
|
|
}
|
|
|
|
|
|
- get chartMetadata(): {
|
|
|
- chartData: ChartStats;
|
|
|
- chartLabel: React.ReactNode;
|
|
|
- tooltipValueFormatter: (val?: number) => string;
|
|
|
- xAxisData: string[];
|
|
|
- xAxisLabelInterval: number;
|
|
|
- xAxisTickInterval: number;
|
|
|
- yAxisFormatter: (val: number) => string;
|
|
|
- yAxisMinInterval: number;
|
|
|
- } {
|
|
|
- const {categoryOptions, usageDateStart, usageDateEnd} = this.props;
|
|
|
- const {
|
|
|
- usageDateInterval,
|
|
|
- usageStats,
|
|
|
- dataCategory,
|
|
|
- dataTransform,
|
|
|
- handleDataTransformation,
|
|
|
- } = this.props;
|
|
|
- const {xAxisDates} = this.state;
|
|
|
-
|
|
|
- const selectDataCategory = categoryOptions.find(o => o.value === dataCategory);
|
|
|
- if (!selectDataCategory) {
|
|
|
- throw new Error('Selected item is not supported');
|
|
|
- }
|
|
|
-
|
|
|
- // Do not assume that handleDataTransformation is a pure function
|
|
|
- const chartData: ChartStats = {
|
|
|
- ...handleDataTransformation(usageStats, dataTransform),
|
|
|
- };
|
|
|
-
|
|
|
- Object.keys(chartData).forEach(k => {
|
|
|
- const isProjected = k === SeriesTypes.PROJECTED;
|
|
|
-
|
|
|
- // Map the array and destructure elements to avoid side-effects
|
|
|
- chartData[k] = chartData[k].map(stat => {
|
|
|
- return {
|
|
|
- ...stat,
|
|
|
- tooltip: {show: false},
|
|
|
- itemStyle: {opacity: isProjected ? 0.6 : 1},
|
|
|
- };
|
|
|
- });
|
|
|
- });
|
|
|
+ if (dataCategory === DATA_CATEGORY_INFO.attachment.plural) {
|
|
|
+ return [COLOR_ATTACHMENTS, COLOR_FILTERED, COLOR_DROPPED, COLOR_PROJECTED];
|
|
|
+ }
|
|
|
|
|
|
- // Use hours as common units
|
|
|
- const dataPeriod = statsPeriodToDays(undefined, usageDateStart, usageDateEnd) * 24;
|
|
|
- const barPeriod = parsePeriodToHours(usageDateInterval);
|
|
|
- if (dataPeriod < 0 || barPeriod < 0) {
|
|
|
- throw new Error('UsageChart: Unable to parse data time period');
|
|
|
- }
|
|
|
+ return [COLOR_TRANSACTIONS, COLOR_FILTERED, COLOR_DROPPED, COLOR_PROJECTED];
|
|
|
+}
|
|
|
|
|
|
- const {xAxisTickInterval, xAxisLabelInterval} = getXAxisLabelInterval(
|
|
|
- dataPeriod,
|
|
|
- dataPeriod / barPeriod
|
|
|
+function UsageChartBody({
|
|
|
+ usageDateStart,
|
|
|
+ usageDateEnd,
|
|
|
+ usageStats,
|
|
|
+ dataCategory,
|
|
|
+ dataTransform,
|
|
|
+ chartSeries,
|
|
|
+ chartTooltip,
|
|
|
+ categoryColors,
|
|
|
+ isLoading,
|
|
|
+ isError,
|
|
|
+ errors,
|
|
|
+ categoryOptions = CHART_OPTIONS_DATACATEGORY,
|
|
|
+ usageDateInterval = '1d',
|
|
|
+ usageDateShowUtc = true,
|
|
|
+ handleDataTransformation = cumulativeTotalDataTransformation,
|
|
|
+}: UsageChartProps) {
|
|
|
+ const theme = useTheme();
|
|
|
+
|
|
|
+ if (isLoading) {
|
|
|
+ return (
|
|
|
+ <Placeholder height="200px">
|
|
|
+ <LoadingIndicator mini />
|
|
|
+ </Placeholder>
|
|
|
);
|
|
|
-
|
|
|
- const {label, yAxisMinInterval} = selectDataCategory;
|
|
|
-
|
|
|
- return {
|
|
|
- chartLabel: label,
|
|
|
- chartData,
|
|
|
- xAxisData: xAxisDates,
|
|
|
- xAxisTickInterval,
|
|
|
- xAxisLabelInterval,
|
|
|
- yAxisMinInterval,
|
|
|
- yAxisFormatter: (val: number) =>
|
|
|
- formatUsageWithUnits(val, dataCategory, {
|
|
|
- isAbbreviated: true,
|
|
|
- useUnitScaling: true,
|
|
|
- }),
|
|
|
- tooltipValueFormatter: getTooltipFormatter(dataCategory),
|
|
|
- };
|
|
|
}
|
|
|
|
|
|
- get chartSeries() {
|
|
|
- const {chartSeries} = this.props;
|
|
|
- const {chartData} = this.chartMetadata;
|
|
|
-
|
|
|
- let series: SeriesOption[] = [
|
|
|
- barSeries({
|
|
|
- name: SeriesTypes.ACCEPTED,
|
|
|
- data: chartData.accepted,
|
|
|
- barMinHeight: 1,
|
|
|
- stack: 'usage',
|
|
|
- legendHoverLink: false,
|
|
|
- }),
|
|
|
- barSeries({
|
|
|
- name: SeriesTypes.FILTERED,
|
|
|
- data: chartData.filtered,
|
|
|
- barMinHeight: 1,
|
|
|
- stack: 'usage',
|
|
|
- legendHoverLink: false,
|
|
|
- }),
|
|
|
- barSeries({
|
|
|
- name: SeriesTypes.DROPPED,
|
|
|
- data: chartData.dropped,
|
|
|
- stack: 'usage',
|
|
|
- legendHoverLink: false,
|
|
|
- }),
|
|
|
- barSeries({
|
|
|
- name: SeriesTypes.PROJECTED,
|
|
|
- data: chartData.projected,
|
|
|
- barMinHeight: 1,
|
|
|
- stack: 'usage',
|
|
|
- legendHoverLink: false,
|
|
|
- }),
|
|
|
- ];
|
|
|
-
|
|
|
- // Additional series passed by parent component
|
|
|
- if (chartSeries) {
|
|
|
- series = series.concat(chartSeries as SeriesOption[]);
|
|
|
- }
|
|
|
-
|
|
|
- return series;
|
|
|
+ if (isError) {
|
|
|
+ return (
|
|
|
+ <Placeholder height="200px">
|
|
|
+ <IconWarning size="sm" />
|
|
|
+ <ErrorMessages data-test-id="error-messages">
|
|
|
+ {errors &&
|
|
|
+ Object.keys(errors).map(k => <span key={k}>{errors[k]?.message}</span>)}
|
|
|
+ </ErrorMessages>
|
|
|
+ </Placeholder>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- get chartLegendData() {
|
|
|
- const {chartSeries} = this.props;
|
|
|
- const {chartData} = this.chartMetadata;
|
|
|
-
|
|
|
+ const {
|
|
|
+ chartData,
|
|
|
+ tooltipValueFormatter,
|
|
|
+ xAxisData,
|
|
|
+ xAxisTickInterval,
|
|
|
+ xAxisLabelInterval,
|
|
|
+ yAxisMinInterval,
|
|
|
+ yAxisFormatter,
|
|
|
+ } = chartMetadata({
|
|
|
+ categoryOptions,
|
|
|
+ dataCategory,
|
|
|
+ handleDataTransformation: handleDataTransformation!,
|
|
|
+ usageStats,
|
|
|
+ dataTransform,
|
|
|
+ usageDateStart,
|
|
|
+ usageDateEnd,
|
|
|
+ usageDateInterval,
|
|
|
+ usageDateShowUtc,
|
|
|
+ });
|
|
|
+
|
|
|
+ function chartLegendData() {
|
|
|
const legend: LegendComponentOption['data'] = [
|
|
|
{
|
|
|
name: SeriesTypes.ACCEPTED,
|
|
@@ -418,116 +415,109 @@ export class UsageChart extends Component<UsageChartProps, State> {
|
|
|
return legend;
|
|
|
}
|
|
|
|
|
|
- get chartTooltip(): ChartProps['tooltip'] {
|
|
|
- const {chartTooltip} = this.props;
|
|
|
-
|
|
|
- if (chartTooltip) {
|
|
|
- return chartTooltip;
|
|
|
- }
|
|
|
-
|
|
|
- const {tooltipValueFormatter} = this.chartMetadata;
|
|
|
-
|
|
|
- return {
|
|
|
- // Trigger to axis prevents tooltip from redrawing when hovering
|
|
|
- // over individual bars
|
|
|
- trigger: 'axis',
|
|
|
- valueFormatter: tooltipValueFormatter,
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- renderChart() {
|
|
|
- const {categoryColors, theme, title, isLoading, isError, errors} = this.props;
|
|
|
- if (isLoading) {
|
|
|
- return (
|
|
|
- <Placeholder height="200px">
|
|
|
- <LoadingIndicator mini />
|
|
|
- </Placeholder>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- if (isError) {
|
|
|
- return (
|
|
|
- <Placeholder height="200px">
|
|
|
- <IconWarning size="sm" />
|
|
|
- <ErrorMessages data-test-id="error-messages">
|
|
|
- {errors &&
|
|
|
- Object.keys(errors).map(k => <span key={k}>{errors[k]?.message}</span>)}
|
|
|
- </ErrorMessages>
|
|
|
- </Placeholder>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- const {
|
|
|
- xAxisData,
|
|
|
- xAxisTickInterval,
|
|
|
- xAxisLabelInterval,
|
|
|
- yAxisMinInterval,
|
|
|
- yAxisFormatter,
|
|
|
- } = this.chartMetadata;
|
|
|
+ const colors = categoryColors?.length
|
|
|
+ ? categoryColors
|
|
|
+ : chartColors(theme, dataCategory);
|
|
|
+
|
|
|
+ const series: SeriesOption[] = [
|
|
|
+ barSeries({
|
|
|
+ name: SeriesTypes.ACCEPTED,
|
|
|
+ data: chartData.accepted,
|
|
|
+ barMinHeight: 1,
|
|
|
+ stack: 'usage',
|
|
|
+ legendHoverLink: false,
|
|
|
+ }),
|
|
|
+ barSeries({
|
|
|
+ name: SeriesTypes.FILTERED,
|
|
|
+ data: chartData.filtered,
|
|
|
+ barMinHeight: 1,
|
|
|
+ stack: 'usage',
|
|
|
+ legendHoverLink: false,
|
|
|
+ }),
|
|
|
+ barSeries({
|
|
|
+ name: SeriesTypes.DROPPED,
|
|
|
+ data: chartData.dropped,
|
|
|
+ stack: 'usage',
|
|
|
+ legendHoverLink: false,
|
|
|
+ }),
|
|
|
+ barSeries({
|
|
|
+ name: SeriesTypes.PROJECTED,
|
|
|
+ data: chartData.projected,
|
|
|
+ barMinHeight: 1,
|
|
|
+ stack: 'usage',
|
|
|
+ legendHoverLink: false,
|
|
|
+ }),
|
|
|
+ // Additional series passed by parent component
|
|
|
+ ...(chartSeries || []),
|
|
|
+ ];
|
|
|
+
|
|
|
+ return (
|
|
|
+ <BaseChart
|
|
|
+ colors={colors}
|
|
|
+ grid={{bottom: '3px', left: '0px', right: '10px', top: '40px'}}
|
|
|
+ xAxis={xAxis({
|
|
|
+ show: true,
|
|
|
+ type: 'category',
|
|
|
+ name: 'Date',
|
|
|
+ data: xAxisData,
|
|
|
+ axisTick: {
|
|
|
+ interval: xAxisTickInterval,
|
|
|
+ alignWithLabel: true,
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ interval: xAxisLabelInterval,
|
|
|
+ formatter: (label: string) => label.slice(0, 6), // Limit label to 6 chars
|
|
|
+ },
|
|
|
+ theme,
|
|
|
+ })}
|
|
|
+ yAxis={{
|
|
|
+ min: 0,
|
|
|
+ minInterval: yAxisMinInterval,
|
|
|
+ axisLabel: {
|
|
|
+ formatter: yAxisFormatter,
|
|
|
+ color: theme.chartLabel,
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ series={series}
|
|
|
+ tooltip={
|
|
|
+ chartTooltip
|
|
|
+ ? chartTooltip
|
|
|
+ : {
|
|
|
+ // Trigger to axis prevents tooltip from redrawing when hovering
|
|
|
+ // over individual bars
|
|
|
+ trigger: 'axis',
|
|
|
+ valueFormatter: tooltipValueFormatter,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ onLegendSelectChanged={() => {}}
|
|
|
+ legend={Legend({
|
|
|
+ right: 10,
|
|
|
+ top: 5,
|
|
|
+ data: chartLegendData(),
|
|
|
+ theme,
|
|
|
+ })}
|
|
|
+ />
|
|
|
+ );
|
|
|
+}
|
|
|
|
|
|
- const colors = categoryColors?.length ? categoryColors : this.chartColors;
|
|
|
+interface UsageChartPanelProps extends UsageChartProps {
|
|
|
+ footer?: React.ReactNode;
|
|
|
+ title?: React.ReactNode;
|
|
|
+}
|
|
|
|
|
|
- return (
|
|
|
- <Fragment>
|
|
|
+function UsageChart({title, footer, ...props}: UsageChartPanelProps) {
|
|
|
+ return (
|
|
|
+ <Panel id="usage-chart" data-test-id="usage-chart">
|
|
|
+ <ChartContainer>
|
|
|
<HeaderTitleLegend>{title || t('Current Usage Period')}</HeaderTitleLegend>
|
|
|
- {getDynamicText({
|
|
|
- value: (
|
|
|
- <BaseChart
|
|
|
- colors={colors}
|
|
|
- grid={{bottom: '3px', left: '0px', right: '10px', top: '40px'}}
|
|
|
- xAxis={xAxis({
|
|
|
- show: true,
|
|
|
- type: 'category',
|
|
|
- name: 'Date',
|
|
|
- data: xAxisData,
|
|
|
- axisTick: {
|
|
|
- interval: xAxisTickInterval,
|
|
|
- alignWithLabel: true,
|
|
|
- },
|
|
|
- axisLabel: {
|
|
|
- interval: xAxisLabelInterval,
|
|
|
- formatter: (label: string) => label.slice(0, 6), // Limit label to 6 chars
|
|
|
- },
|
|
|
- theme,
|
|
|
- })}
|
|
|
- yAxis={{
|
|
|
- min: 0,
|
|
|
- minInterval: yAxisMinInterval,
|
|
|
- axisLabel: {
|
|
|
- formatter: yAxisFormatter,
|
|
|
- color: theme.chartLabel,
|
|
|
- },
|
|
|
- }}
|
|
|
- series={this.chartSeries}
|
|
|
- tooltip={this.chartTooltip}
|
|
|
- onLegendSelectChanged={() => {}}
|
|
|
- legend={Legend({
|
|
|
- right: 10,
|
|
|
- top: 5,
|
|
|
- data: this.chartLegendData,
|
|
|
- theme,
|
|
|
- })}
|
|
|
- />
|
|
|
- ),
|
|
|
- fixed: <Placeholder height="200px" />,
|
|
|
- })}
|
|
|
- </Fragment>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- render() {
|
|
|
- const {footer} = this.props;
|
|
|
-
|
|
|
- return (
|
|
|
- <Panel id="usage-chart" data-test-id="usage-chart">
|
|
|
- <ChartContainer>{this.renderChart()}</ChartContainer>
|
|
|
- {footer}
|
|
|
- </Panel>
|
|
|
- );
|
|
|
- }
|
|
|
+ <UsageChartBody {...props} />
|
|
|
+ </ChartContainer>
|
|
|
+ {footer}
|
|
|
+ </Panel>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-export default withTheme(UsageChart);
|
|
|
+export default UsageChart;
|
|
|
|
|
|
const ErrorMessages = styled('div')`
|
|
|
display: flex;
|