Skip to content

Commit 26d86be

Browse files
authored
feat(discover): Persist dataset selection on add to dashboards (#74658)
Add dataset selection to add to dashboards flow
1 parent 34d9dc6 commit 26d86be

File tree

8 files changed

+208
-22
lines changed

8 files changed

+208
-22
lines changed

static/app/components/modals/widgetBuilder/addToDashboardModal.spec.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import selectEvent from 'sentry-test/selectEvent';
77
import type {ModalRenderProps} from 'sentry/actionCreators/modal';
88
import AddToDashboardModal from 'sentry/components/modals/widgetBuilder/addToDashboardModal';
99
import type {DashboardDetails, DashboardListItem} from 'sentry/views/dashboards/types';
10-
import {DashboardWidgetSource, DisplayType} from 'sentry/views/dashboards/types';
10+
import {
11+
DashboardWidgetSource,
12+
DisplayType,
13+
WidgetType,
14+
} from 'sentry/views/dashboards/types';
1115

1216
const stubEl = (props: {children?: React.ReactNode}) => <div>{props.children}</div>;
1317

@@ -366,7 +370,7 @@ describe('add to dashboard modal', () => {
366370
CloseButton={stubEl}
367371
closeModal={() => undefined}
368372
organization={initialData.organization}
369-
widget={widget}
373+
widget={{...widget, widgetType: WidgetType.ERRORS}}
370374
selection={defaultSelection}
371375
router={initialData.router}
372376
widgetAsQueryParams={mockWidgetAsQueryParams}
@@ -387,7 +391,11 @@ describe('add to dashboard modal', () => {
387391
await waitFor(() => {
388392
expect(dashboardDetailPutMock).toHaveBeenCalledWith(
389393
'/organizations/org-slug/dashboards/1/',
390-
expect.objectContaining({data: expect.objectContaining({widgets: [widget]})})
394+
expect.objectContaining({
395+
data: expect.objectContaining({
396+
widgets: [{...widget, widgetType: WidgetType.ERRORS}],
397+
}),
398+
})
391399
);
392400
});
393401
});

static/app/components/modals/widgetBuilder/addToDashboardModal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
getSavedFiltersAsPageFilters,
3535
getSavedPageFilters,
3636
} from 'sentry/views/dashboards/utils';
37-
import {NEW_DASHBOARD_ID} from 'sentry/views/dashboards/widgetBuilder/utils';
37+
import {
38+
type DataSet,
39+
NEW_DASHBOARD_ID,
40+
} from 'sentry/views/dashboards/widgetBuilder/utils';
3841
import WidgetCard from 'sentry/views/dashboards/widgetCard';
3942
import {OrganizationContext} from 'sentry/views/organizationContext';
4043
import {MetricsDataSwitcher} from 'sentry/views/performance/landing/metricsDataSwitcher';
@@ -47,6 +50,7 @@ type WidgetAsQueryParams = Query<{
4750
environment: string[];
4851
project: number[];
4952
source: string;
53+
dataset?: DataSet;
5054
end?: DateString;
5155
start?: DateString;
5256
statsPeriod?: string | null;

static/app/views/dashboards/widgetBuilder/utils.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export enum DataSet {
4242
RELEASES = 'releases',
4343
METRICS = 'metrics',
4444
ERRORS = 'error-events',
45-
TRANSACTIONS = 'transaction-like"',
45+
TRANSACTIONS = 'transaction-like',
4646
}
4747

4848
export enum SortDirection {

static/app/views/discover/homepage.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ class HomepageQueryAPI extends DeprecatedAsyncComponent<Props, HomepageQueryStat
7878

7979
browserHistory.replace({
8080
...this.props.location,
81-
query,
81+
query: {
82+
...query,
83+
queryDataset: this.state.savedQuery?.queryDataset,
84+
},
8285
});
8386
}
8487
}
@@ -97,30 +100,39 @@ class HomepageQueryAPI extends DeprecatedAsyncComponent<Props, HomepageQueryStat
97100
}
98101

99102
onRequestSuccess({stateKey, data}) {
103+
const {organization} = this.props;
100104
// No homepage query results in a 204, returning an empty string
101105
if (stateKey === 'savedQuery' && data === '') {
102106
this.setState({savedQuery: null});
107+
return;
108+
}
109+
if (stateKey === 'savedQuery') {
110+
this.setState({
111+
savedQuery: organization.features.includes(
112+
'performance-discover-dataset-selector'
113+
)
114+
? getSavedQueryWithDataset(data)
115+
: data,
116+
});
103117
}
104118
}
105119

106120
setSavedQuery = (newSavedQuery?: SavedQuery) => {
107-
this.setState({savedQuery: newSavedQuery});
121+
const {organization} = this.props;
122+
this.setState({
123+
savedQuery: organization.features.includes('performance-discover-dataset-selector')
124+
? getSavedQueryWithDataset(newSavedQuery)
125+
: newSavedQuery,
126+
});
108127
};
109128

110129
renderBody(): React.ReactNode {
111130
const {savedQuery, loading} = this.state;
112-
const {organization} = this.props;
113-
let savedQueryWithDataset = savedQuery;
114-
if (
115-
organization.features.includes('performance-discover-dataset-selector') &&
116-
savedQuery
117-
) {
118-
savedQueryWithDataset = getSavedQueryWithDataset(savedQuery);
119-
}
131+
120132
return (
121133
<Results
122134
{...this.props}
123-
savedQuery={savedQueryWithDataset ?? undefined}
135+
savedQuery={savedQuery ?? undefined}
124136
loading={loading}
125137
setSavedQuery={this.setSavedQuery}
126138
isHomepage

static/app/views/discover/queryList.spec.tsx

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313

1414
import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
1515
import {browserHistory} from 'sentry/utils/browserHistory';
16-
import {DisplayModes} from 'sentry/utils/discover/types';
16+
import {DisplayModes, SavedQueryDatasets} from 'sentry/utils/discover/types';
1717
import {DashboardWidgetSource, DisplayType} from 'sentry/views/dashboards/types';
1818
import QueryList from 'sentry/views/discover/queryList';
1919

@@ -503,6 +503,7 @@ describe('Discover > QueryList', function () {
503503
orderby: 'count()',
504504
fields: ['test', 'count()'],
505505
yAxis: ['count()'],
506+
queryDataset: SavedQueryDatasets.TRANSACTIONS,
506507
}),
507508
]}
508509
pageLinks=""
@@ -554,4 +555,76 @@ describe('Discover > QueryList', function () {
554555
});
555556
});
556557
});
558+
559+
it('passes dataset to open modal', async function () {
560+
const featuredOrganization = OrganizationFixture({
561+
features: ['dashboards-edit', 'performance-discover-dataset-selector'],
562+
});
563+
render(
564+
<QueryList
565+
savedQuerySearchQuery=""
566+
router={RouterFixture()}
567+
renderPrebuilt={false}
568+
organization={featuredOrganization}
569+
savedQueries={[
570+
DiscoverSavedQueryFixture({
571+
display: DisplayModes.DEFAULT,
572+
orderby: 'count()',
573+
fields: ['test', 'count()'],
574+
yAxis: ['count()'],
575+
queryDataset: SavedQueryDatasets.TRANSACTIONS,
576+
}),
577+
]}
578+
pageLinks=""
579+
onQueryChange={queryChangeMock}
580+
location={location}
581+
/>
582+
);
583+
584+
const contextMenu = await screen.findByTestId('menu-trigger');
585+
expect(contextMenu).toBeInTheDocument();
586+
587+
expect(screen.queryByTestId('add-to-dashboard')).not.toBeInTheDocument();
588+
589+
await userEvent.click(contextMenu);
590+
591+
const addToDashboardMenuItem = await screen.findByTestId('add-to-dashboard');
592+
593+
await userEvent.click(addToDashboardMenuItem);
594+
595+
await waitFor(() => {
596+
expect(openAddToDashboardModal).toHaveBeenCalledWith(
597+
expect.objectContaining({
598+
widget: {
599+
displayType: 'line',
600+
interval: undefined,
601+
limit: undefined,
602+
queries: [
603+
{
604+
aggregates: ['count()'],
605+
columns: [],
606+
conditions: '',
607+
fields: ['count()'],
608+
name: '',
609+
orderby: '',
610+
},
611+
],
612+
title: 'Saved query #1',
613+
widgetType: 'transaction-like',
614+
},
615+
widgetAsQueryParams: expect.objectContaining({
616+
cursor: '0:1:1',
617+
dataset: 'transaction-like',
618+
defaultTableColumns: ['test', 'count()'],
619+
defaultTitle: 'Saved query #1',
620+
defaultWidgetQuery:
621+
'name=&aggregates=count()&columns=&fields=count()&conditions=&orderby=',
622+
displayType: 'line',
623+
source: 'discoverv2',
624+
statsPeriod: '14d',
625+
}),
626+
})
627+
);
628+
});
629+
});
557630
});

static/app/views/discover/savedQuery/index.spec.tsx

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {OrganizationFixture} from 'sentry-fixture/organization';
22

33
import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
44

5+
import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
56
import type {NewQuery} from 'sentry/types/organization';
67
import EventView from 'sentry/utils/discover/eventView';
7-
import {DisplayModes} from 'sentry/utils/discover/types';
8+
import {DisplayModes, SavedQueryDatasets} from 'sentry/utils/discover/types';
9+
import {WidgetType} from 'sentry/views/dashboards/types';
810
import {ALL_VIEWS} from 'sentry/views/discover/data';
911
import SavedQueryButtonGroup from 'sentry/views/discover/savedQuery';
1012
import * as utils from 'sentry/views/discover/savedQuery/utils';
@@ -133,6 +135,71 @@ describe('Discover > SaveQueryButtonGroup', function () {
133135
).toBeInTheDocument();
134136
});
135137

138+
it('opens dashboard modal with the right props', async () => {
139+
organization = OrganizationFixture({
140+
features: [
141+
'discover-query',
142+
'dashboards-edit',
143+
'performance-discover-dataset-selector',
144+
],
145+
});
146+
mount(
147+
location,
148+
organization,
149+
router,
150+
errorsView,
151+
{...savedQuery, queryDataset: SavedQueryDatasets.ERRORS},
152+
yAxis
153+
);
154+
155+
expect(
156+
screen.getByRole('button', {name: /discover context menu/i})
157+
).toBeInTheDocument();
158+
159+
await userEvent.click(screen.getByRole('button', {name: /discover context menu/i}));
160+
expect(
161+
screen.queryByRole('menuitemradio', {name: /add to dashboard/i})
162+
).toBeInTheDocument();
163+
await userEvent.click(
164+
screen.getByRole('menuitemradio', {name: /add to dashboard/i})
165+
);
166+
167+
expect(openAddToDashboardModal).toHaveBeenCalledWith(
168+
expect.objectContaining({
169+
widget: {
170+
displayType: 'line',
171+
interval: undefined,
172+
limit: undefined,
173+
queries: [
174+
{
175+
aggregates: ['count()', 'failure_count()'],
176+
columns: [],
177+
conditions: 'event.type:error',
178+
fields: ['count()', 'failure_count()'],
179+
name: '',
180+
orderby: '-count()',
181+
},
182+
],
183+
title: 'Errors by Title',
184+
widgetType: WidgetType.ERRORS,
185+
},
186+
widgetAsQueryParams: expect.objectContaining({
187+
dataset: WidgetType.ERRORS,
188+
defaultTableColumns: ['title', 'count()', 'count_unique(user)', 'project'],
189+
defaultTitle: 'Errors by Title',
190+
defaultWidgetQuery:
191+
'name=&aggregates=count()%2Cfailure_count()&columns=&fields=count()%2Cfailure_count()&conditions=event.type%3Aerror&orderby=-count()',
192+
displayType: 'line',
193+
end: undefined,
194+
limit: undefined,
195+
source: 'discoverv2',
196+
start: undefined,
197+
statsPeriod: '24h',
198+
}),
199+
})
200+
);
201+
});
202+
136203
it('hides the banner when save is complete.', async () => {
137204
mount(location, organization, router, errorsView, undefined, yAxis);
138205

static/app/views/discover/savedQuery/utils.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ export function displayModeToDisplayType(displayMode: DisplayModes): DisplayType
253253

254254
export function getSavedQueryDataset(
255255
location: Location | undefined,
256-
savedQuery: SavedQuery | undefined,
256+
savedQuery: SavedQuery | NewQuery | undefined,
257257
splitDecision?: SavedQueryDatasets
258258
): SavedQueryDatasets {
259259
const dataset = decodeScalar(location?.query?.[DATASET_PARAM]);

static/app/views/discover/utils.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,18 @@ import {
4343
PROFILING_FIELDS,
4444
TRACING_FIELDS,
4545
} from 'sentry/utils/discover/fields';
46-
import type {DisplayModes} from 'sentry/utils/discover/types';
47-
import {TOP_N} from 'sentry/utils/discover/types';
46+
import {type DisplayModes, SavedQueryDatasets, TOP_N} from 'sentry/utils/discover/types';
4847
import {getTitle} from 'sentry/utils/events';
4948
import {DISCOVER_FIELDS, FieldValueType, getFieldDefinition} from 'sentry/utils/fields';
5049
import localStorage from 'sentry/utils/localStorage';
5150
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
51+
import {DataSet} from 'sentry/views/dashboards/widgetBuilder/utils';
5252

5353
import type {WidgetQuery} from '../dashboards/types';
5454
import {DashboardWidgetSource, DisplayType} from '../dashboards/types';
5555
import {transactionSummaryRouteWithQuery} from '../performance/transactionSummary/utils';
5656

57-
import {displayModeToDisplayType} from './savedQuery/utils';
57+
import {displayModeToDisplayType, getSavedQueryDataset} from './savedQuery/utils';
5858
import type {FieldValue, TableColumn} from './table/types';
5959
import {FieldValueKind} from './table/types';
6060
import {ALL_VIEWS, TRANSACTION_VIEWS, WEB_VITALS_VIEWS} from './data';
@@ -695,6 +695,8 @@ export function handleAddQueryToDashboard({
695695
yAxis,
696696
});
697697

698+
const dataset = getSavedQueryDataset(location, query);
699+
698700
const {query: widgetAsQueryParams} = constructAddQueryToDashboardLink({
699701
eventView,
700702
query,
@@ -728,6 +730,9 @@ export function handleAddQueryToDashboard({
728730
displayType === DisplayType.TOP_N
729731
? Number(eventView.topEvents) || TOP_N
730732
: undefined,
733+
widgetType: organization.features.includes('performance-discover-dataset-selector')
734+
? getWidgetDataset(dataset)
735+
: undefined,
731736
},
732737
router,
733738
widgetAsQueryParams,
@@ -793,6 +798,8 @@ export function constructAddQueryToDashboardLink({
793798
displayType,
794799
yAxis,
795800
});
801+
const dataset = getSavedQueryDataset(location, query);
802+
796803
const defaultTitle =
797804
query?.name ?? (eventView.name !== 'All Events' ? eventView.name : undefined);
798805

@@ -808,10 +815,25 @@ export function constructAddQueryToDashboardLink({
808815
defaultTableColumns: defaultTableFields,
809816
defaultTitle,
810817
displayType: displayType === DisplayType.TOP_N ? DisplayType.AREA : displayType,
818+
dataset: organization.features.includes('performance-discover-dataset-selector')
819+
? getWidgetDataset(dataset)
820+
: undefined,
811821
limit:
812822
displayType === DisplayType.TOP_N
813823
? Number(eventView.topEvents) || TOP_N
814824
: undefined,
815825
},
816826
};
817827
}
828+
function getWidgetDataset(dataset: SavedQueryDatasets) {
829+
switch (dataset) {
830+
case SavedQueryDatasets.TRANSACTIONS:
831+
return DataSet.TRANSACTIONS;
832+
833+
case SavedQueryDatasets.ERRORS:
834+
return DataSet.ERRORS;
835+
836+
default:
837+
return undefined;
838+
}
839+
}

0 commit comments

Comments
 (0)