Skip to content

Commit

Permalink
feat(explore): Make numeric values queryable in explore (#78786)
Browse files Browse the repository at this point in the history
This only exposes the ability to query the column and sort on it.
Filtering and aggregations is planned but not working yet.
  • Loading branch information
Zylphrex authored Oct 9, 2024
1 parent 0f258d7 commit 29b24ac
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 52 deletions.
18 changes: 18 additions & 0 deletions static/app/views/explore/components/typeBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import BadgeTag from 'sentry/components/badge/tag';
import {t} from 'sentry/locale';
import type {Tag} from 'sentry/types/group';
import {FieldKind} from 'sentry/utils/fields';

interface TypeBadgeProps {
tag?: Tag;
}

export function TypeBadge({tag}: TypeBadgeProps) {
if (tag?.kind === FieldKind.MEASUREMENT) {
return <BadgeTag type="success">{t('number')}</BadgeTag>;
}
if (tag?.kind === FieldKind.TAG) {
return <BadgeTag type="highlight">{t('string')}</BadgeTag>;
}
return null;
}
58 changes: 48 additions & 10 deletions static/app/views/explore/contexts/spanTagsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {createContext, useContext, useMemo} from 'react';
import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
import type {Tag, TagCollection} from 'sentry/types/group';
import {DiscoverDatasets} from 'sentry/utils/discover/types';
import {FieldKind} from 'sentry/utils/fields';
import {useApiQuery} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';
import {SpanIndexedField} from 'sentry/views/insights/types';
import {
useSpanFieldStaticTags,
useSpanFieldSupportedTags,
Expand All @@ -25,6 +27,21 @@ interface SpanTagsProviderProps {
}

export function SpanTagsProvider({children, dataset}: SpanTagsProviderProps) {
const numericSpanFields: Set<string> = useMemo(() => {
return new Set([
SpanIndexedField.SPAN_DURATION,
SpanIndexedField.SPAN_SELF_TIME,
SpanIndexedField.INP,
SpanIndexedField.INP_SCORE,
SpanIndexedField.INP_SCORE_WEIGHT,
SpanIndexedField.TOTAL_SCORE,
SpanIndexedField.CACHE_ITEM_SIZE,
SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE,
SpanIndexedField.MESSAGING_MESSAGE_RECEIVE_LATENCY,
SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT,
]);
}, []);

const supportedTags = useSpanFieldSupportedTags();

const numberTags: TagCollection = useTypedSpanTags({
Expand All @@ -44,8 +61,15 @@ export function SpanTagsProvider({children, dataset}: SpanTagsProviderProps) {
return {};
}

return numberTags;
}, [dataset, numberTags]);
return {
...numberTags,
...Object.fromEntries(
Object.entries(staticTags)
.filter(([key, _]) => numericSpanFields.has(key))
.map(([key, tag]) => [key, {...tag, kind: FieldKind.MEASUREMENT}])
),
};
}, [dataset, numberTags, numericSpanFields, staticTags]);

const allStringTags = useMemo(() => {
if (dataset === DiscoverDatasets.SPANS_INDEXED) {
Expand All @@ -54,9 +78,13 @@ export function SpanTagsProvider({children, dataset}: SpanTagsProviderProps) {

return {
...stringTags,
...staticTags,
...Object.fromEntries(
Object.entries(staticTags)
.filter(([key, _]) => !numericSpanFields.has(key))
.map(([key, tag]) => [key, {...tag, kind: FieldKind.TAG}])
),
};
}, [dataset, supportedTags, stringTags, staticTags]);
}, [dataset, supportedTags, stringTags, staticTags, numericSpanFields]);

const tags = {
number: allNumberTags,
Expand All @@ -66,7 +94,7 @@ export function SpanTagsProvider({children, dataset}: SpanTagsProviderProps) {
return <SpanTagsContext.Provider value={tags}>{children}</SpanTagsContext.Provider>;
}

export const useSpanTags = (type?: 'number' | 'string') => {
export function useSpanTags(type?: 'number' | 'string') {
const typedTags = useContext(SpanTagsContext);

if (typedTags === undefined) {
Expand All @@ -77,7 +105,14 @@ export const useSpanTags = (type?: 'number' | 'string') => {
return typedTags.number;
}
return typedTags.string;
};
}

export function useSpanTag(key: string) {
const numberTags = useSpanTags('number');
const stringTags = useSpanTags('string');

return stringTags[key] ?? numberTags[key] ?? null;
}

function useTypedSpanTags({
enabled,
Expand Down Expand Up @@ -123,14 +158,17 @@ function useTypedSpanTags({
continue;
}

allTags[tag.key] = {
key: tag.key,
name: tag.name,
const key = type === 'number' ? `tags[${tag.key},number]` : tag.key;

allTags[key] = {
key,
name: tag.key,
kind: type === 'number' ? FieldKind.MEASUREMENT : FieldKind.TAG,
};
}

return allTags;
}, [result]);
}, [result, type]);

return tags;
}
4 changes: 2 additions & 2 deletions static/app/views/explore/hooks/useResultsMode.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('useResultMode', function () {
expect(resultMode).toEqual('samples'); // default
expect(sampleFields).toEqual([
'project',
'id',
'span_id',
'span.op',
'span.description',
'span.duration',
Expand All @@ -57,7 +57,7 @@ describe('useResultMode', function () {

expect(sampleFields).toEqual([
'project',
'id',
'span_id',
'span.op',
'span.description',
'span.duration',
Expand Down
4 changes: 2 additions & 2 deletions static/app/views/explore/hooks/useSampleFields.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('useSampleFields', function () {

expect(sampleFields).toEqual([
'project',
'id',
'span_id',
'span.op',
'span.description',
'span.duration',
Expand All @@ -28,7 +28,7 @@ describe('useSampleFields', function () {
act(() => setSampleFields([]));
expect(sampleFields).toEqual([
'project',
'id',
'span_id',
'span.op',
'span.description',
'span.duration',
Expand Down
9 changes: 8 additions & 1 deletion static/app/views/explore/hooks/useSampleFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ function useSampleFieldsImpl({
return fields;
}

return ['project', 'id', 'span.op', 'span.description', 'span.duration', 'timestamp'];
return [
'project',
'span_id',
'span.op',
'span.description',
'span.duration',
'timestamp',
];
}, [location.query.field]);

const setSampleFields = useCallback(
Expand Down
36 changes: 24 additions & 12 deletions static/app/views/explore/tables/columnEditorModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import {act, renderGlobalModal, screen, userEvent} from 'sentry-test/reactTestingLibrary';

import {openModal} from 'sentry/actionCreators/modal';
import type {TagCollection} from 'sentry/types/group';
import {ColumnEditorModal} from 'sentry/views/explore/tables/columnEditorModal';

const tagOptions = {
const stringTags: TagCollection = {
id: {
key: 'id',
name: 'ID',
name: 'id',
},
project: {
key: 'project',
name: 'Project',
name: 'project',
},
'span.op': {
key: 'span.op',
name: 'Span OP',
name: 'span.op',
},
};

const numberTags: TagCollection = {
'span.duration': {
key: 'span.duration',
name: 'span.duration',
},
};

Expand All @@ -36,7 +44,8 @@ describe('ColumnEditorModal', function () {
{...modalProps}
columns={['id', 'project']}
onColumnsChange={() => {}}
tags={tagOptions}
stringTags={stringTags}
numberTags={numberTags}
/>
),
{onClose}
Expand All @@ -60,7 +69,8 @@ describe('ColumnEditorModal', function () {
{...modalProps}
columns={['id', 'project']}
onColumnsChange={onColumnsChange}
tags={tagOptions}
stringTags={stringTags}
numberTags={numberTags}
/>
),
{onClose: jest.fn()}
Expand Down Expand Up @@ -98,7 +108,8 @@ describe('ColumnEditorModal', function () {
{...modalProps}
columns={['id', 'project']}
onColumnsChange={onColumnsChange}
tags={tagOptions}
stringTags={stringTags}
numberTags={numberTags}
/>
),
{onClose: jest.fn()}
Expand All @@ -117,14 +128,14 @@ describe('ColumnEditorModal', function () {
expect(column).toHaveTextContent(columns2[i]);
});

const options = ['id', 'project', 'span.op'];
const options = ['id', 'project', 'span.duration', 'span.op'];
await userEvent.click(screen.getByRole('button', {name: 'None'}));
const columnOptions = await screen.findAllByRole('option');
columnOptions.forEach((option, i) => {
expect(option).toHaveTextContent(options[i]);
});

await userEvent.click(columnOptions[2]);
await userEvent.click(columnOptions[3]);
const columns3 = ['id', 'project', 'span.op'];
screen.getAllByTestId('editor-column').forEach((column, i) => {
expect(column).toHaveTextContent(columns3[i]);
Expand All @@ -146,7 +157,8 @@ describe('ColumnEditorModal', function () {
{...modalProps}
columns={['id', 'project']}
onColumnsChange={onColumnsChange}
tags={tagOptions}
stringTags={stringTags}
numberTags={numberTags}
/>
),
{onClose: jest.fn()}
Expand All @@ -158,14 +170,14 @@ describe('ColumnEditorModal', function () {
expect(column).toHaveTextContent(columns1[i]);
});

const options = ['id', 'project', 'span.op'];
const options = ['id', 'project', 'span.duration', 'span.op'];
await userEvent.click(screen.getByRole('button', {name: 'project'}));
const columnOptions = await screen.findAllByRole('option');
columnOptions.forEach((option, i) => {
expect(option).toHaveTextContent(options[i]);
});

await userEvent.click(columnOptions[2]);
await userEvent.click(columnOptions[3]);
const columns2 = ['id', 'span.op'];
screen.getAllByTestId('editor-column').forEach((column, i) => {
expect(column).toHaveTextContent(columns2[i]);
Expand Down
Loading

0 comments on commit 29b24ac

Please sign in to comment.