Skip to content

Commit 9f56c7f

Browse files
shruthilayajandrewshie-sentry
authored andcommitted
feat(multi-query): Add skeleton for multi-query mode (#84313)
This PR adds a skeleton for multi-query mode, not much going on, so no tests yet. Trying to get the different parts of the query constructor to behave well on smaller screens. ![Screenshot 2025-01-30 at 10 53 39 AM](https://github.com/user-attachments/assets/5b39810f-70c2-4561-a0b3-3ff02046ebd8) ![Screenshot 2025-01-30 at 10 53 31 AM](https://github.com/user-attachments/assets/fcbfcbde-a19d-4fa9-8b42-14a47ae10810) These are Dora's designs, for reference ![Screenshot 2025-01-30 at 10 48 13 AM](https://github.com/user-attachments/assets/7e10f2e9-2547-4970-b25f-3dca399f21e0)
1 parent 6d4a915 commit 9f56c7f

File tree

10 files changed

+365
-0
lines changed

10 files changed

+365
-0
lines changed

static/app/components/organizations/pageFilterBar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import styled from '@emotion/styled';
22

33
import {space} from 'sentry/styles/space';
44

5+
// Note: This component is also used in Explore multi-query mode
6+
// static/app/views/explore/multiQueryMode/queryConstructors/sortBy.tsx
7+
// and static/app/views/explore/multiQueryMode/queryConstructors/visualize.tsx
8+
// and not just for PageFilters as the name indicates.
59
const PageFilterBar = styled('div')<{condensed?: boolean}>`
610
display: flex;
711
position: relative;

static/app/routes.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,6 +1901,10 @@ function buildRoutes() {
19011901
>
19021902
<IndexRoute component={make(() => import('sentry/views/traces/content'))} />
19031903
{traceViewRoute}
1904+
<Route
1905+
path="multi-query/"
1906+
component={make(() => import('sentry/views/explore/multiQueryMode'))}
1907+
/>
19041908
</Route>
19051909
);
19061910

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import styled from '@emotion/styled';
2+
3+
import * as Layout from 'sentry/components/layouts/thirds';
4+
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
5+
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
6+
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
7+
import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
8+
import {space} from 'sentry/styles/space';
9+
import {useExploreDataset} from 'sentry/views/explore/contexts/pageParamsContext';
10+
import {SpanTagsProvider} from 'sentry/views/explore/contexts/spanTagsContext';
11+
import {QueryRow} from 'sentry/views/explore/multiQueryMode/queryRow';
12+
13+
function Content() {
14+
return (
15+
<Layout.Body>
16+
<Layout.Main fullWidth>
17+
<StyledPageFilterBar condensed>
18+
<ProjectPageFilter />
19+
<EnvironmentPageFilter />
20+
<DatePageFilter />
21+
</StyledPageFilterBar>
22+
<QueryRow />
23+
</Layout.Main>
24+
</Layout.Body>
25+
);
26+
}
27+
28+
export function MultiQueryModeContent() {
29+
const dataset = useExploreDataset();
30+
return (
31+
<SpanTagsProvider dataset={dataset} enabled>
32+
<Content />
33+
</SpanTagsProvider>
34+
);
35+
}
36+
37+
const StyledPageFilterBar = styled(PageFilterBar)`
38+
margin-bottom: ${space(2)};
39+
`;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Feature from 'sentry/components/acl/feature';
2+
import Breadcrumbs from 'sentry/components/breadcrumbs';
3+
import * as Layout from 'sentry/components/layouts/thirds';
4+
import {NoAccess} from 'sentry/components/noAccess';
5+
import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
6+
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
7+
import {t} from 'sentry/locale';
8+
import useOrganization from 'sentry/utils/useOrganization';
9+
import {MultiQueryModeContent} from 'sentry/views/explore/multiQueryMode/content';
10+
import {generateTracesRoute} from 'sentry/views/traces/utils';
11+
12+
export default function MultiQueryMode() {
13+
const organization = useOrganization();
14+
15+
return (
16+
<Feature
17+
features="explore-multi-query"
18+
organization={organization}
19+
renderDisabled={NoAccess}
20+
>
21+
<SentryDocumentTitle title={t('Multi Query Mode')} orgSlug={organization.slug}>
22+
<Layout.Header>
23+
<Layout.HeaderContent>
24+
<Breadcrumbs
25+
crumbs={[
26+
{
27+
label: t('Explore'),
28+
to: generateTracesRoute({orgSlug: organization.slug}),
29+
},
30+
{
31+
label: t('Multi Query Mode'),
32+
},
33+
]}
34+
/>
35+
<Layout.Title>{t('Multi Query Mode')}</Layout.Title>
36+
</Layout.HeaderContent>
37+
</Layout.Header>
38+
<Layout.Page>
39+
<PageFiltersContainer>
40+
<MultiQueryModeContent />
41+
</PageFiltersContainer>
42+
</Layout.Page>
43+
</SentryDocumentTitle>
44+
</Feature>
45+
);
46+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {useMemo} from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import {CompactSelect, type SelectOption} from 'sentry/components/compactSelect';
5+
import {t} from 'sentry/locale';
6+
import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
7+
import {
8+
Section,
9+
SectionHeader,
10+
SectionLabel,
11+
} from 'sentry/views/explore/multiQueryMode/queryConstructors/styles';
12+
13+
export function GroupBySection() {
14+
const tags = useSpanTags();
15+
16+
const enabledOptions: Array<SelectOption<string>> = useMemo(() => {
17+
const potentialOptions = Object.keys(tags).filter(key => key !== 'id');
18+
potentialOptions.sort();
19+
20+
return potentialOptions.map(key => ({
21+
label: key,
22+
value: key,
23+
textValue: key,
24+
}));
25+
}, [tags]);
26+
27+
return (
28+
<Section data-test-id="section-group-by">
29+
<SectionHeader>
30+
<SectionLabel underlined={false}>{t('Group By')}</SectionLabel>
31+
</SectionHeader>
32+
<StyledCompactSelect multiple options={enabledOptions} clearable searchable />
33+
</Section>
34+
);
35+
}
36+
37+
const StyledCompactSelect = styled(CompactSelect)`
38+
width: 100%;
39+
> button {
40+
width: 100%;
41+
}
42+
`;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {EAPSpanSearchQueryBuilder} from 'sentry/components/performance/spanSearchQueryBuilder';
2+
import {t} from 'sentry/locale';
3+
import usePageFilters from 'sentry/utils/usePageFilters';
4+
import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
5+
import {
6+
Section,
7+
SectionHeader,
8+
SectionLabel,
9+
} from 'sentry/views/explore/multiQueryMode/queryConstructors/styles';
10+
11+
export function SearchBarSection() {
12+
const {selection} = usePageFilters();
13+
const numberTags = useSpanTags('number');
14+
const stringTags = useSpanTags('string');
15+
16+
return (
17+
<Section data-test-id="section-filter">
18+
<SectionHeader>
19+
<SectionLabel underlined={false}>{t('Filter')}</SectionLabel>
20+
</SectionHeader>
21+
<EAPSpanSearchQueryBuilder
22+
projects={selection.projects}
23+
initialQuery={''}
24+
onSearch={() => {}}
25+
searchSource="explore"
26+
numberTags={numberTags}
27+
stringTags={stringTags}
28+
/>
29+
</Section>
30+
);
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {Fragment, useMemo} from 'react';
2+
3+
import {CompactSelect, type SelectOption} from 'sentry/components/compactSelect';
4+
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
5+
import {Tooltip} from 'sentry/components/tooltip';
6+
import {t} from 'sentry/locale';
7+
import type {Sort} from 'sentry/utils/discover/fields';
8+
import {
9+
Section,
10+
SectionHeader,
11+
SectionLabel,
12+
} from 'sentry/views/explore/multiQueryMode/queryConstructors/styles';
13+
14+
export function SortBySection() {
15+
const kindOptions: Array<SelectOption<Sort['kind']>> = useMemo(() => {
16+
return [
17+
{
18+
label: 'Desc',
19+
value: 'desc',
20+
textValue: t('Descending'),
21+
},
22+
{
23+
label: 'Asc',
24+
value: 'asc',
25+
textValue: t('Ascending'),
26+
},
27+
];
28+
}, []);
29+
30+
return (
31+
<Section data-test-id="section-sort-by">
32+
<SectionHeader>
33+
<Tooltip
34+
position="right"
35+
title={t('Results you see first and last in your samples or aggregates.')}
36+
>
37+
<SectionLabel>{t('Sort By')}</SectionLabel>
38+
</Tooltip>
39+
</SectionHeader>
40+
<Fragment>
41+
<PageFilterBar>
42+
<CompactSelect options={[]} value={undefined} onChange={_newSortField => {}} />
43+
<CompactSelect
44+
options={kindOptions}
45+
value={undefined}
46+
onChange={_newSortKind => {}}
47+
/>
48+
</PageFilterBar>
49+
</Fragment>
50+
</Section>
51+
);
52+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import styled from '@emotion/styled';
2+
3+
import {space} from 'sentry/styles/space';
4+
import {defined} from 'sentry/utils';
5+
6+
export const Section = styled('div')`
7+
margin-bottom: ${space(2)};
8+
`;
9+
10+
export const SectionHeader = styled('div')`
11+
display: flex;
12+
flex-direction: row;
13+
justify-content: space-between;
14+
align-items: baseline;
15+
margin-bottom: ${space(0.5)};
16+
`;
17+
18+
export const SectionLabel = styled('h6')<{disabled?: boolean; underlined?: boolean}>`
19+
color: ${p => (p.disabled ? p.theme.gray300 : p.theme.gray500)};
20+
height: ${p => p.theme.form.md.height};
21+
min-height: ${p => p.theme.form.md.minHeight};
22+
font-size: ${p => p.theme.form.md.fontSize};
23+
margin: 0;
24+
${p =>
25+
!defined(p.underlined) || p.underlined
26+
? `text-decoration: underline dotted ${p.disabled ? p.theme.gray300 : p.theme.gray300}`
27+
: ''};
28+
`;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {Fragment, useMemo} from 'react';
2+
3+
import {CompactSelect, type SelectOption} from 'sentry/components/compactSelect';
4+
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
5+
import {Tooltip} from 'sentry/components/tooltip';
6+
import {t} from 'sentry/locale';
7+
import {ALLOWED_EXPLORE_VISUALIZE_AGGREGATES} from 'sentry/utils/fields';
8+
import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
9+
import {
10+
Section,
11+
SectionHeader,
12+
SectionLabel,
13+
} from 'sentry/views/explore/multiQueryMode/queryConstructors/styles';
14+
15+
export function VisualizeSection() {
16+
const numberTags = useSpanTags('number');
17+
18+
const fieldOptions: Array<SelectOption<string>> = useMemo(() => {
19+
const options = Object.values(numberTags).map(tag => {
20+
return {
21+
label: tag.name,
22+
value: tag.key,
23+
textValue: tag.name,
24+
};
25+
});
26+
27+
options.sort((a, b) => {
28+
if (a.label < b.label) {
29+
return -1;
30+
}
31+
32+
if (a.label > b.label) {
33+
return 1;
34+
}
35+
36+
return 0;
37+
});
38+
39+
return options;
40+
}, [numberTags]);
41+
42+
const aggregateOptions: Array<SelectOption<string>> = useMemo(() => {
43+
return ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.map(aggregate => {
44+
return {
45+
label: aggregate,
46+
value: aggregate,
47+
textValue: aggregate,
48+
};
49+
});
50+
}, []);
51+
52+
return (
53+
<Section data-test-id="section-visualize">
54+
<SectionHeader>
55+
<Tooltip
56+
position="right"
57+
title={t(
58+
'Primary metric that appears in your chart. You can also overlay a series onto an existing chart or add an equation.'
59+
)}
60+
>
61+
<SectionLabel>{t('Visualize')}</SectionLabel>
62+
</Tooltip>
63+
</SectionHeader>
64+
<Fragment>
65+
<PageFilterBar>
66+
<CompactSelect
67+
searchable
68+
options={fieldOptions}
69+
value={''}
70+
onChange={_newField => {}}
71+
/>
72+
<CompactSelect
73+
options={aggregateOptions}
74+
value={''}
75+
onChange={_newAggregate => {}}
76+
/>
77+
</PageFilterBar>
78+
</Fragment>
79+
</Section>
80+
);
81+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import styled from '@emotion/styled';
2+
3+
import {space} from 'sentry/styles/space';
4+
import {GroupBySection} from 'sentry/views/explore/multiQueryMode/queryConstructors/groupBy';
5+
import {SearchBarSection} from 'sentry/views/explore/multiQueryMode/queryConstructors/search';
6+
import {SortBySection} from 'sentry/views/explore/multiQueryMode/queryConstructors/sortBy';
7+
import {VisualizeSection} from 'sentry/views/explore/multiQueryMode/queryConstructors/visualize';
8+
9+
export function QueryRow() {
10+
return (
11+
<QueryConstructionSection>
12+
<SearchBarSection />
13+
<DropDownGrid>
14+
<VisualizeSection />
15+
<GroupBySection />
16+
<SortBySection />
17+
</DropDownGrid>
18+
</QueryConstructionSection>
19+
);
20+
}
21+
22+
const QueryConstructionSection = styled('div')`
23+
display: grid;
24+
width: 100%;
25+
26+
@media (min-width: ${p => p.theme.breakpoints.large}) {
27+
grid-template-columns: minmax(400px, 1fr) 1fr;
28+
margin-bottom: 0;
29+
gap: ${space(2)};
30+
}
31+
`;
32+
33+
const DropDownGrid = styled('div')`
34+
display: grid;
35+
grid-template-columns: repeat(3, 1fr);
36+
margin-bottom: 0;
37+
gap: ${space(2)};
38+
`;

0 commit comments

Comments
 (0)