|
@@ -16,7 +16,7 @@ import {LineChart} from 'sentry/components/charts/lineChart';
|
|
import SimpleTableChart from 'sentry/components/charts/simpleTableChart';
|
|
import SimpleTableChart from 'sentry/components/charts/simpleTableChart';
|
|
import TransitionChart from 'sentry/components/charts/transitionChart';
|
|
import TransitionChart from 'sentry/components/charts/transitionChart';
|
|
import TransparentLoadingMask from 'sentry/components/charts/transparentLoadingMask';
|
|
import TransparentLoadingMask from 'sentry/components/charts/transparentLoadingMask';
|
|
-import {getSeriesSelection} from 'sentry/components/charts/utils';
|
|
|
|
|
|
+import {getSeriesSelection, isChartHovered} from 'sentry/components/charts/utils';
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import type {PlaceholderProps} from 'sentry/components/placeholder';
|
|
import type {PlaceholderProps} from 'sentry/components/placeholder';
|
|
import Placeholder from 'sentry/components/placeholder';
|
|
import Placeholder from 'sentry/components/placeholder';
|
|
@@ -24,7 +24,12 @@ import {Tooltip} from 'sentry/components/tooltip';
|
|
import {IconWarning} from 'sentry/icons';
|
|
import {IconWarning} from 'sentry/icons';
|
|
import {space} from 'sentry/styles/space';
|
|
import {space} from 'sentry/styles/space';
|
|
import type {Organization, PageFilters} from 'sentry/types';
|
|
import type {Organization, PageFilters} from 'sentry/types';
|
|
-import type {EChartDataZoomHandler, EChartEventHandler} from 'sentry/types/echarts';
|
|
|
|
|
|
+import type {
|
|
|
|
+ EChartDataZoomHandler,
|
|
|
|
+ EChartEventHandler,
|
|
|
|
+ ReactEchartsRef,
|
|
|
|
+ Series,
|
|
|
|
+} from 'sentry/types/echarts';
|
|
import {
|
|
import {
|
|
axisLabelFormatter,
|
|
axisLabelFormatter,
|
|
axisLabelFormatterUsingAggregateOutputType,
|
|
axisLabelFormatterUsingAggregateOutputType,
|
|
@@ -50,6 +55,7 @@ import {
|
|
} from 'sentry/views/dashboards/datasetConfig/metrics';
|
|
} from 'sentry/views/dashboards/datasetConfig/metrics';
|
|
import {eventViewFromWidget} from 'sentry/views/dashboards/utils';
|
|
import {eventViewFromWidget} from 'sentry/views/dashboards/utils';
|
|
|
|
|
|
|
|
+import {getFormatter} from '../../../components/charts/components/tooltip';
|
|
import {getDatasetConfig} from '../datasetConfig/base';
|
|
import {getDatasetConfig} from '../datasetConfig/base';
|
|
import type {Widget} from '../types';
|
|
import type {Widget} from '../types';
|
|
import {DisplayType, WidgetType} from '../types';
|
|
import {DisplayType, WidgetType} from '../types';
|
|
@@ -82,6 +88,7 @@ type WidgetCardChartProps = Pick<
|
|
selection: PageFilters;
|
|
selection: PageFilters;
|
|
theme: Theme;
|
|
theme: Theme;
|
|
widget: Widget;
|
|
widget: Widget;
|
|
|
|
+ chartGroup?: string;
|
|
chartZoomOptions?: DataZoomComponentOption;
|
|
chartZoomOptions?: DataZoomComponentOption;
|
|
expandNumbers?: boolean;
|
|
expandNumbers?: boolean;
|
|
isMobile?: boolean;
|
|
isMobile?: boolean;
|
|
@@ -257,6 +264,23 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ chartRef: ReactEchartsRef | null = null;
|
|
|
|
+
|
|
|
|
+ handleRef = (chartRef: ReactEchartsRef): void => {
|
|
|
|
+ if (chartRef && !this.chartRef) {
|
|
|
|
+ this.chartRef = chartRef;
|
|
|
|
+ // add chart to the group so that it has synced cursors
|
|
|
|
+ const instance = chartRef.getEchartsInstance?.();
|
|
|
|
+ if (instance && !instance.group && this.props.chartGroup) {
|
|
|
|
+ instance.group = this.props.chartGroup;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!chartRef) {
|
|
|
|
+ this.chartRef = null;
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
chartComponent(chartProps): React.ReactNode {
|
|
chartComponent(chartProps): React.ReactNode {
|
|
const {widget} = this.props;
|
|
const {widget} = this.props;
|
|
const stacked = widget.queries[0]?.columns.length > 0;
|
|
const stacked = widget.queries[0]?.columns.length > 0;
|
|
@@ -368,6 +392,20 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
|
|
const durationUnit = isDurationChart
|
|
const durationUnit = isDurationChart
|
|
? timeseriesResults && getDurationUnit(timeseriesResults, legendOptions)
|
|
? timeseriesResults && getDurationUnit(timeseriesResults, legendOptions)
|
|
: undefined;
|
|
: undefined;
|
|
|
|
+ const bucketSize = getBucketSize(timeseriesResults);
|
|
|
|
+
|
|
|
|
+ const valueFormatter = (value: number, seriesName?: string) => {
|
|
|
|
+ if (widget.widgetType === WidgetType.METRICS) {
|
|
|
|
+ return formatMetricAxisValue(axisField, value);
|
|
|
|
+ }
|
|
|
|
+ const aggregateName = seriesName?.split(':').pop()?.trim();
|
|
|
|
+ if (aggregateName) {
|
|
|
|
+ return timeseriesResultsTypes
|
|
|
|
+ ? tooltipFormatter(value, timeseriesResultsTypes[aggregateName])
|
|
|
|
+ : tooltipFormatter(value, aggregateOutputType(aggregateName));
|
|
|
|
+ }
|
|
|
|
+ return tooltipFormatter(value, 'number');
|
|
|
|
+ };
|
|
|
|
|
|
const chartOptions = {
|
|
const chartOptions = {
|
|
autoHeightResize,
|
|
autoHeightResize,
|
|
@@ -382,17 +420,20 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
|
|
},
|
|
},
|
|
tooltip: {
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
trigger: 'axis',
|
|
- valueFormatter: (value: number, seriesName: string) => {
|
|
|
|
- if (widget.widgetType === WidgetType.METRICS) {
|
|
|
|
- return formatMetricAxisValue(axisField, value);
|
|
|
|
|
|
+ formatter: (params, asyncTicket) => {
|
|
|
|
+ // tooltip is triggered whenever any chart in the group is hovered,
|
|
|
|
+ // so we need to check if the mouse is actually over this chart
|
|
|
|
+ if (!isChartHovered(this.chartRef)) {
|
|
|
|
+ return '';
|
|
}
|
|
}
|
|
- const aggregateName = seriesName?.split(':').pop()?.trim();
|
|
|
|
- if (aggregateName) {
|
|
|
|
- return timeseriesResultsTypes
|
|
|
|
- ? tooltipFormatter(value, timeseriesResultsTypes[aggregateName])
|
|
|
|
- : tooltipFormatter(value, aggregateOutputType(aggregateName));
|
|
|
|
- }
|
|
|
|
- return tooltipFormatter(value, 'number');
|
|
|
|
|
|
+
|
|
|
|
+ return getFormatter({
|
|
|
|
+ valueFormatter,
|
|
|
|
+ isGroupedByDate: true,
|
|
|
|
+ bucketSize,
|
|
|
|
+ addSecondsToTimeFormat: false,
|
|
|
|
+ showTimeInTooltip: true,
|
|
|
|
+ })(params, asyncTicket);
|
|
},
|
|
},
|
|
},
|
|
},
|
|
yAxis: {
|
|
yAxis: {
|
|
@@ -415,6 +456,11 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
|
|
},
|
|
},
|
|
minInterval: durationUnit ?? 0,
|
|
minInterval: durationUnit ?? 0,
|
|
},
|
|
},
|
|
|
|
+ xAxis: {
|
|
|
|
+ axisPointer: {
|
|
|
|
+ snap: true,
|
|
|
|
+ },
|
|
|
|
+ },
|
|
};
|
|
};
|
|
|
|
|
|
return (
|
|
return (
|
|
@@ -469,6 +515,9 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
|
|
|
|
|
|
const seriesStart = series[0]?.data[0]?.name;
|
|
const seriesStart = series[0]?.data[0]?.name;
|
|
const seriesEnd = series[0]?.data[series[0].data.length - 1]?.name;
|
|
const seriesEnd = series[0]?.data[series[0].data.length - 1]?.name;
|
|
|
|
+
|
|
|
|
+ const forwardedRef = this.props.chartGroup ? this.handleRef : undefined;
|
|
|
|
+
|
|
return (
|
|
return (
|
|
<TransitionChart loading={loading} reloading={loading}>
|
|
<TransitionChart loading={loading} reloading={loading}>
|
|
<LoadingScreen loading={loading} />
|
|
<LoadingScreen loading={loading} />
|
|
@@ -490,6 +539,7 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
|
|
legend,
|
|
legend,
|
|
series,
|
|
series,
|
|
onLegendSelectChanged,
|
|
onLegendSelectChanged,
|
|
|
|
+ forwardedRef,
|
|
}),
|
|
}),
|
|
fixed: <Placeholder height="200px" testId="skeleton-ui" />,
|
|
fixed: <Placeholder height="200px" testId="skeleton-ui" />,
|
|
})}
|
|
})}
|
|
@@ -502,6 +552,14 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+const getBucketSize = (series: Series[] | undefined) => {
|
|
|
|
+ if (!series || series.length < 2) {
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return Number(series[0].data[1]?.name) - Number(series[0].data[0]?.name);
|
|
|
|
+};
|
|
|
|
+
|
|
export default withTheme(WidgetCardChart);
|
|
export default withTheme(WidgetCardChart);
|
|
|
|
|
|
const StyledTransparentLoadingMask = styled(props => (
|
|
const StyledTransparentLoadingMask = styled(props => (
|