Skip to content
Open
2 changes: 0 additions & 2 deletions knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ const productionEntryPoints = [
'static/app/chartcuterie/**/*.{js,ts,tsx}',
// TODO: Remove when used
'static/app/views/seerExplorer/contexts/**/*.{js,ts,tsx}',
// TODO: Remove when integration into Explore has started
'static/app/views/dashboards/widgets/heatMapWidget/**/*.{ts,tsx}',
];

const testingEntryPoints = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {FALLBACK_TYPE} from 'sentry/views/dashboards/widgets/timeSeriesWidget/se

import type {HeatMapPlottable, PlottableTimeSeriesValueType} from './heatMapPlottable';

export type HeatMapPlottingOptions = {
type HeatMapPlottingOptions = {
theme: Theme;
/**
* The Z-axis scale type. `'log'` applies `Math.log1p` to Z values so the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export function determineDefaultChartType(yAxes: readonly string[]): ChartType {
[ChartType.BAR]: 0,
[ChartType.LINE]: 0,
[ChartType.AREA]: 0,
[ChartType.HEATMAP]: 0,
};

for (const yAxis of yAxes) {
Expand Down
5 changes: 4 additions & 1 deletion static/app/views/explore/hooks/useAddToDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ import {
import {useSpansDataset} from 'sentry/views/explore/spans/spansQueryParams';
import {ChartType} from 'sentry/views/insights/common/components/chart';

export const CHART_TYPE_TO_DISPLAY_TYPE = {
export const CHART_TYPE_TO_DISPLAY_TYPE: Record<ChartType, DisplayType> = {
[ChartType.LINE]: DisplayType.LINE,
[ChartType.BAR]: DisplayType.BAR,
[ChartType.AREA]: DisplayType.AREA,
// Heatmaps are filtered out before reaching dashboard code, but the
// mapping must be exhaustive because other consumers index by ChartType.
[ChartType.HEATMAP]: DisplayType.LINE,
};

export function useAddToDashboard() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {skipToken} from '@tanstack/react-query';

import {normalizeDateTimeParams} from 'sentry/components/pageFilters/parse';
import type {PageFilters} from 'sentry/types/core';
import type {Organization} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {apiOptions} from 'sentry/utils/api/apiOptions';
import {DiscoverDatasets} from 'sentry/utils/discover/types';
import {intervalToMilliseconds} from 'sentry/utils/duration/intervalToMilliseconds';
import type {HeatMapSeries} from 'sentry/views/dashboards/widgets/common/types';
import type {TraceMetric} from 'sentry/views/explore/metrics/metricQuery';
import {createTraceMetricEventsFilter} from 'sentry/views/explore/metrics/utils';

interface MetricHeatmapApiOptions {
enabled: boolean;
interval: string;
organization: Organization;
query: string;
selection: PageFilters;
traceMetric: TraceMetric;
yBuckets: number;
}

export function metricHeatmapApiOptions({
traceMetric,
enabled,
organization,
selection,
query,
interval,
yBuckets,
}: MetricHeatmapApiOptions) {
const traceMetricFilter = createTraceMetricEventsFilter([traceMetric]);
const combinedQuery = query ? `${traceMetricFilter} ${query}` : traceMetricFilter;

const intervalInMilliseconds = intervalToMilliseconds(interval);
const {start, end, period} = normalizeDateTimeParams(selection.datetime);
const usesRelativeDateRange = !defined(start) && !defined(end) && defined(period);

return apiOptions.as<HeatMapSeries>()(
'/organizations/$organizationIdOrSlug/events-heatmap/',
{
Comment on lines +40 to +42
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i'm getting a bunch of 500s when hitting this endpoint (on the vercel deploy) but not too sure what it is 🤔

path: enabled ? {organizationIdOrSlug: organization.slug} : skipToken,
query: {
dataset: DiscoverDatasets.TRACEMETRICS,
xAxis: 'time',
yAxis: 'value',
zAxis: 'count()',
yBuckets,
interval,
query: combinedQuery,
project: selection.projects,
environment: selection.environments,
start,
end,
...(period ? {statsPeriod: period} : {}),
referrer: 'api.explore.tracemetrics-heatmap',
},
staleTime:
usesRelativeDateRange && intervalInMilliseconds !== 0
? intervalInMilliseconds
: Infinity,
}
);
}
106 changes: 29 additions & 77 deletions static/app/views/explore/metrics/metricGraph/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import {Fragment, useMemo} from 'react';
import {useMemo} from 'react';

import {CompactSelect} from '@sentry/scraps/compactSelect';
import {ExternalLink} from '@sentry/scraps/link';
import {OverlayTrigger} from '@sentry/scraps/overlayTrigger';

import {IconClock, IconGraph} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import type {Organization} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {parseFunction} from 'sentry/utils/discover/fields';
import {DiscoverDatasets} from 'sentry/utils/discover/types';
import {useChartInterval} from 'sentry/utils/useChartInterval';
import {determineSeriesSampleCountAndIsSampled} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
import {formatTimeSeriesLabel} from 'sentry/views/dashboards/widgets/timeSeriesWidget/formatters/formatTimeSeriesLabel';
import {Widget} from 'sentry/views/dashboards/widgets/widget/widget';
import {ChartVisualization} from 'sentry/views/explore/components/chart/chartVisualization';
import {ConfidenceFooter} from 'sentry/views/explore/metrics/confidenceFooter';
import {canUseMetricsHeatMap} from 'sentry/views/explore/metrics/metricsFlags';
import {
useMetricLabel,
useMetricName,
useMetricVisualize,
useMetricVisualizes,
useSetMetricVisualizes,
useTraceMetric,
} from 'sentry/views/explore/metrics/metricsQueryParams';
import {METRICS_CHART_GROUP} from 'sentry/views/explore/metrics/metricsTab';
import {useMultiMetricsQueryParams} from 'sentry/views/explore/metrics/multiMetricsQueryParams';
import {
MINIMIZED_GRAPH_HEIGHT,
STACKED_GRAPH_HEIGHT,
} from 'sentry/views/explore/metrics/settings';
import {
createTraceMetricEventsFilter,
getEquationMetricsTotalFilter,
Expand All @@ -49,62 +50,65 @@ import {GenericWidgetEmptyStateWarning} from 'sentry/views/performance/landing/w

import {WidgetWrapper} from './styles';

const MINIMIZED_GRAPH_HEIGHT = 50;
const STACKED_GRAPH_HEIGHT = 362;
export function getMetricsChartTypeOptions(organization: Organization) {
if (canUseMetricsHeatMap(organization)) {
return [
...EXPLORE_CHART_TYPE_OPTIONS,
{value: ChartType.HEATMAP, label: t('Heat Map')},
];
}
return EXPLORE_CHART_TYPE_OPTIONS;
}
Comment thread
gggritso marked this conversation as resolved.

interface MetricsGraphProps {
actions: React.ReactNode;
timeseriesResult: ReturnType<typeof useSortedTimeSeries>;
additionalActions?: React.ReactNode;
isMetricOptionsEmpty?: boolean;
title?: string;
}

export function MetricsGraph({
timeseriesResult,
additionalActions,
actions,
isMetricOptionsEmpty,
title,
}: MetricsGraphProps) {
const metricQueries = useMultiMetricsQueryParams();
const visualize = useMetricVisualize();
const visualizes = useMetricVisualizes();
const setVisualizes = useSetMetricVisualizes();

useSynchronizeCharts(
metricQueries.length,
!timeseriesResult.isPending,
METRICS_CHART_GROUP
);

function handleChartTypeChange(newChartType: ChartType) {
setVisualizes(visualizes.map(v => v.replace({chartType: newChartType})));
}

return (
<Graph
visualize={visualize}
visualizes={visualizes}
timeseriesResult={timeseriesResult}
onChartTypeChange={handleChartTypeChange}
additionalActions={additionalActions}
actions={actions}
isMetricOptionsEmpty={isMetricOptionsEmpty}
title={title}
/>
);
}

interface GraphProps extends MetricsGraphProps {
onChartTypeChange: (chartType: ChartType) => void;
interface GraphProps {
actions: React.ReactNode;
timeseriesResult: ReturnType<typeof useSortedTimeSeries>;
visualize: ReturnType<typeof useMetricVisualize>;
visualizes: ReturnType<typeof useMetricVisualizes>;
isMetricOptionsEmpty?: boolean;
title?: string;
}

function Graph({
onChartTypeChange,
timeseriesResult,
visualize,
visualizes,
additionalActions,
actions,
isMetricOptionsEmpty,
title,
}: GraphProps) {
Expand All @@ -113,7 +117,6 @@ function Graph({
const metricLabel = useMetricLabel();
const metricName = useMetricName();
const userQuery = useQueryParamsQuery();
const [interval, setInterval, intervalOptions] = useChartInterval();
const traceMetric = useTraceMetric();
const rawMetricCounts = useRawCounts({
dataset: DiscoverDatasets.TRACEMETRICS,
Expand Down Expand Up @@ -181,57 +184,6 @@ function Graph({
return title ?? metricLabel ?? prettifyAggregation(aggregate) ?? aggregate;
}, [aggregate, metricLabel, metricName, visualizes.length, title]);

const Title = <Widget.WidgetTitle title={chartTitle} />;

const chartIcon =
visualize.chartType === ChartType.LINE
? 'line'
: visualize.chartType === ChartType.AREA
? 'area'
: 'bar';

const Actions = (
<Fragment>
<CompactSelect
trigger={triggerProps => (
<OverlayTrigger.Button
{...triggerProps}
tooltipProps={{
title: t('Type of chart displayed in this visualization (ex. line)'),
}}
icon={<IconGraph type={chartIcon} />}
variant="transparent"
showChevron={false}
size="xs"
/>
)}
value={visualize.chartType}
menuTitle="Type"
options={EXPLORE_CHART_TYPE_OPTIONS}
onChange={option => onChartTypeChange(option.value)}
/>
<CompactSelect
value={interval}
onChange={({value}) => setInterval(value)}
trigger={triggerProps => (
<OverlayTrigger.Button
tooltipProps={{
title: t('Time interval displayed in this visualization (ex. 5m)'),
}}
{...triggerProps}
icon={<IconClock />}
variant="transparent"
showChevron={false}
size="xs"
/>
)}
menuTitle="Interval"
options={intervalOptions}
/>
{additionalActions}
</Fragment>
);

const showEmptyState = isMetricOptionsEmpty && visualize.visible;
const showChart = visualize.visible && !isMetricOptionsEmpty;

Expand All @@ -240,8 +192,8 @@ function Graph({
return (
<WidgetWrapper hideFooterBorder>
<Widget
Title={Title}
Actions={Actions}
Title={<Widget.WidgetTitle title={chartTitle} />}
Actions={actions}
Visualization={
showEmptyState ? (
<GenericWidgetEmptyStateWarning
Expand All @@ -261,14 +213,14 @@ function Graph({
) : undefined
}
Footer={
showChart && (
showChart ? (
<ConfidenceFooter
chartInfo={chartInfo}
isLoading={timeseriesResult.isPending || timeseriesResult.isFetching}
hasUserQuery={!!userQuery}
rawMetricCounts={rawMetricCounts}
/>
)
) : undefined
}
height={height}
revealActions="always"
Expand Down
Loading
Loading