Skip to content
Merged
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
29 changes: 22 additions & 7 deletions static/app/views/explore/hooks/useMetricOptions.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ describe('useMetricOptions', () => {
it('fetches metric options from tracemetrics dataset', async () => {
const mockData = {
data: [
{['metric.name']: 'metric.c', ['metric.type']: 'counter'},
{['metric.name']: 'metric.a', ['metric.type']: 'distribution'},
{['metric.name']: 'metric.b', ['metric.type']: 'distribution'},
{['metric.name']: 'metric.c', ['metric.type']: 'counter'},
],
};

Expand All @@ -63,9 +63,8 @@ describe('useMetricOptions', () => {
match: [
MockApiClient.matchQuery({
dataset: DiscoverDatasets.TRACEMETRICS,
field: ['metric.name', 'metric.type', 'count(metric.name)'],
field: ['metric.name', 'metric.type', 'metric.unit', 'count(metric.name)'],
referrer: 'api.explore.metric-options',
orderby: 'metric.name',
}),
],
});
Expand All @@ -86,9 +85,13 @@ describe('useMetricOptions', () => {
);
});

it('sorts metrics alphabetically by name', () => {
it('sorts metrics alphabetically by name', async () => {
const mockData = {
data: [],
data: [
{['metric.name']: 'metric.z', ['metric.type']: 'counter'},
{['metric.name']: 'metric.a', ['metric.type']: 'distribution'},
{['metric.name']: 'metric.m', ['metric.type']: 'gauge'},
],
};

const mockRequest = MockApiClient.addMockResponse({
Expand All @@ -97,19 +100,31 @@ describe('useMetricOptions', () => {
body: mockData,
});

renderHookWithProviders(useMetricOptions, {
const {result} = renderHookWithProviders(useMetricOptions, {
...context,
});

await waitFor(() => expect(result.current.isSuccess).toBe(true));

expect(mockRequest).toHaveBeenCalledTimes(1);
expect(mockRequest).toHaveBeenCalledWith(
`/organizations/${organization.slug}/events/`,
expect.objectContaining({
query: expect.objectContaining({
orderby: 'metric.name',
dataset: 'tracemetrics',
field: ['metric.name', 'metric.type', 'metric.unit', 'count(metric.name)'],
referrer: 'api.explore.metric-options',
}),
})
);

await waitFor(() =>
expect(result.current.data?.data).toEqual([
{['metric.name']: 'metric.a', ['metric.type']: 'distribution'},
{['metric.name']: 'metric.m', ['metric.type']: 'gauge'},
{['metric.name']: 'metric.z', ['metric.type']: 'counter'},
])
);
});

it('handles empty response', async () => {
Expand Down
72 changes: 51 additions & 21 deletions static/app/views/explore/hooks/useMetricOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,62 @@
import {useMemo} from 'react';
import {useEffect, useMemo} from 'react';

import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
import type {PageFilters} from 'sentry/types/core';
import {DiscoverDatasets} from 'sentry/utils/discover/types';
import {useApiQuery, type ApiQueryKey} from 'sentry/utils/queryClient';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';

interface EventsMetricResult {
data: Array<{
['metric.name']: string;
['metric.type']: string;
}>;
meta?: {
fields?: Record<string, string>;
};
}
import {
TraceMetricKnownFieldKey,
type TraceMetricEventsResult,
} from 'sentry/views/explore/metrics/types';

interface UseMetricOptionsProps {
datetime?: PageFilters['datetime'];
enabled?: boolean;
orgSlug?: string;
projectIds?: PageFilters['projects'];
search?: string;
}

function metricOptionsQueryKey({
orgSlug,
projectIds,
datetime,
}: {
orgSlug: string;
datetime?: PageFilters['datetime'];
projectIds?: number[];
}): ApiQueryKey {
search,
}: UseMetricOptionsProps = {}): ApiQueryKey {
const searchValue = new MutableSearch('');
if (search) {
searchValue.addStringContainsFilter(
`${TraceMetricKnownFieldKey.METRIC_NAME}:${search}`
);
}
const query: Record<string, string | string[] | number[]> = {
dataset: DiscoverDatasets.TRACEMETRICS,
field: ['metric.name', 'metric.type', 'count(metric.name)'],
field: [
TraceMetricKnownFieldKey.METRIC_NAME,
TraceMetricKnownFieldKey.METRIC_TYPE,
TraceMetricKnownFieldKey.METRIC_UNIT,
`count(${TraceMetricKnownFieldKey.METRIC_NAME})`,
],
query: searchValue.formatString(),
referrer: 'api.explore.metric-options',
orderby: 'metric.name',
};

if (projectIds?.length) {
query.project = projectIds.map(String);
}

if (datetime) {
if (search && datetime) {
// If searching we use the full filters in order to not miss the result.
Object.entries(normalizeDateTimeParams(datetime)).forEach(([key, value]) => {
if (value !== undefined) {
query[key] = value as string | string[];
}
});
} else {
query.statsPeriod = '24h'; // Default to a much smaller time window if not searching.
}

return [`/organizations/${orgSlug}/events/`, {query}];
Expand All @@ -59,6 +67,7 @@ function metricOptionsQueryKey({
* This is used to populate metric selection options in the Explore interface.
*/
export function useMetricOptions({
search,
projectIds,
datetime,
enabled = true,
Expand All @@ -69,18 +78,39 @@ export function useMetricOptions({
const queryKey = useMemo(
() =>
metricOptionsQueryKey({
search,
orgSlug: organization.slug,
projectIds: projectIds ?? selection.projects,
datetime: datetime ?? selection.datetime,
}),
[organization.slug, projectIds, selection.projects, selection.datetime, datetime]
[
organization.slug,
projectIds,
search,
selection.projects,
selection.datetime,
datetime,
]
);

return useApiQuery<EventsMetricResult>(queryKey, {
const result = useApiQuery<TraceMetricEventsResult>(queryKey, {
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
refetchOnWindowFocus: false,
refetchOnMount: false,
retry: false,
enabled,
});

// This replaces order-by metric.name as that will never be performant over large time periods with high numbers of metrics.
useEffect(() => {
if (result.data?.data) {
result.data.data.sort((a, b) => {
return a[TraceMetricKnownFieldKey.METRIC_NAME].localeCompare(
b[TraceMetricKnownFieldKey.METRIC_NAME]
);
});
}
}, [result.data]);

return result;
}
38 changes: 38 additions & 0 deletions static/app/views/explore/metrics/constants.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {SelectOption} from 'sentry/components/core/compactSelect';
import {
TraceMetricKnownFieldKey,
VirtualTableSampleColumnKey,
type TraceMetricFieldKey,
} from 'sentry/views/explore/metrics/types';

Expand All @@ -13,6 +14,17 @@ const AlwaysHiddenTraceMetricFields: TraceMetricFieldKey[] = [
TraceMetricKnownFieldKey.OLD_PROJECT_ID,
];

export const AlwaysPresentTraceMetricFields: TraceMetricFieldKey[] = [
TraceMetricKnownFieldKey.ID,
TraceMetricKnownFieldKey.PROJECT_ID,
TraceMetricKnownFieldKey.TRACE,
TraceMetricKnownFieldKey.SPAN_ID,
TraceMetricKnownFieldKey.OLD_SPAN_ID,
TraceMetricKnownFieldKey.METRIC_TYPE,
TraceMetricKnownFieldKey.METRIC_NAME,
TraceMetricKnownFieldKey.TIMESTAMP,
];

/**
* These are fields that should be hidden in metric details view when receiving all data from the API.
*/
Expand All @@ -26,6 +38,7 @@ export const HiddenTraceMetricDetailFields: TraceMetricFieldKey[] = [
TraceMetricKnownFieldKey.TRACE_FLAGS,
TraceMetricKnownFieldKey.METRIC_NAME,
TraceMetricKnownFieldKey.METRIC_TYPE,
TraceMetricKnownFieldKey.CLIENT_SAMPLE_RATE,
];

export const HiddenTraceMetricSearchFields: TraceMetricFieldKey[] = [
Expand All @@ -38,6 +51,31 @@ export const HiddenTraceMetricGroupByFields: TraceMetricFieldKey[] = [
...HiddenTraceMetricSearchFields,
];

export const TraceSamplesTableStatColumns: VirtualTableSampleColumnKey[] = [
VirtualTableSampleColumnKey.LOGS,
VirtualTableSampleColumnKey.SPANS,
VirtualTableSampleColumnKey.ERRORS,
];

export const TraceSamplesTableColumns: Array<
TraceMetricFieldKey | VirtualTableSampleColumnKey
> = [
VirtualTableSampleColumnKey.EXPAND_ROW,
TraceMetricKnownFieldKey.TIMESTAMP,
TraceMetricKnownFieldKey.TRACE,
...TraceSamplesTableStatColumns,
TraceMetricKnownFieldKey.METRIC_VALUE,
];

export const TraceSamplesTableEmbeddedColumns: Array<
TraceMetricFieldKey | VirtualTableSampleColumnKey
> = [
VirtualTableSampleColumnKey.EXPAND_ROW,
TraceMetricKnownFieldKey.TIMESTAMP,
TraceMetricKnownFieldKey.METRIC_NAME,
TraceMetricKnownFieldKey.METRIC_VALUE,
];

export const OPTIONS_BY_TYPE: Record<string, Array<SelectOption<string>>> = {
counter: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ describe('useMetricSamplesTable', () => {
fields: [],
limit: 100,
ingestionDelaySeconds: 0,
enabled: true,
}),
{
additionalWrapper: MockMetricQueryParamsContext,
Expand Down
Loading
Loading