Skip to content

Commit 3bff398

Browse files
authored
ref(ui): Use DropdownMenuV2 for dashboard widget dropdown (#31688)
* ref(ui): Use DropdownMenuV2 for dashboard widget dropdown * fix(test): Update tests related to dashboard widget dropdowns Necessary given the new implementation of DropdownMenuV2. * fix(acceptance): Update acceptance tests for Dashboards * ref(ui): Limit query card dropdown height * ref(test): Use `findBy` instead of `await tick()` in issueWidgetCard * style: Merge similar menuOptions in widgetCardContextMenu * ref(test): Use `findBy` instead of `await tick()` in widgetCard test * ref(test): Remove `data-test-href` attribute Instead, check for href values by clicking on the link and checking for route changes.
1 parent 0515600 commit 3bff398

File tree

8 files changed

+187
-199
lines changed

8 files changed

+187
-199
lines changed

static/app/views/dashboardsV2/widgetCard/widgetCardContextMenu.tsx

Lines changed: 100 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
import * as React from 'react';
21
import styled from '@emotion/styled';
32
import * as qs from 'query-string';
43

54
import {openDashboardWidgetQuerySelectorModal} from 'sentry/actionCreators/modal';
65
import {parseArithmetic} from 'sentry/components/arithmeticInput/parser';
7-
import Confirm from 'sentry/components/confirm';
8-
import Link from 'sentry/components/links/link';
9-
import MenuItem from 'sentry/components/menuItem';
6+
import {openConfirmModal} from 'sentry/components/confirm';
7+
import DropdownMenuControlV2 from 'sentry/components/dropdownMenuControlV2';
8+
import {MenuItemProps} from 'sentry/components/dropdownMenuItemV2';
9+
import {
10+
IconCopy,
11+
IconDelete,
12+
IconEdit,
13+
IconEllipsis,
14+
IconIssues,
15+
IconTelescope,
16+
} from 'sentry/icons';
1017
import {t} from 'sentry/locale';
1118
import space from 'sentry/styles/space';
1219
import {Organization, PageFilters} from 'sentry/types';
@@ -17,7 +24,6 @@ import {DisplayModes} from 'sentry/utils/discover/types';
1724
import {eventViewFromWidget} from 'sentry/views/dashboardsV2/utils';
1825
import {DisplayType} from 'sentry/views/dashboardsV2/widgetBuilder/utils';
1926

20-
import ContextMenu from '../contextMenu';
2127
import {Widget, WidgetType} from '../types';
2228

2329
type Props = {
@@ -47,16 +53,29 @@ function WidgetCardContextMenu({
4753
return null;
4854
}
4955

50-
const menuOptions: React.ReactNode[] = [];
56+
const menuOptions: MenuItemProps[] = [];
57+
const disabledKeys: string[] = [];
5158

5259
if (isPreview) {
5360
return (
5461
<ContextWrapper>
55-
<ContextMenu>
56-
<PreviewMessage>
57-
{t('This is a preview only. To edit, you must add this dashboard.')}
58-
</PreviewMessage>
59-
</ContextMenu>
62+
<DropdownMenuControlV2
63+
items={[
64+
{
65+
key: 'preview',
66+
label: t('This is a preview only. To edit, you must add this dashboard.'),
67+
},
68+
]}
69+
triggerProps={{
70+
'aria-label': t('Widget actions'),
71+
size: 'xsmall',
72+
borderless: true,
73+
showChevron: false,
74+
icon: <IconEllipsis direction="down" size="sm" />,
75+
}}
76+
placement="bottom right"
77+
disabledKeys={['preview']}
78+
/>
6079
</ContextWrapper>
6180
);
6281
}
@@ -118,38 +137,28 @@ function WidgetCardContextMenu({
118137
const discoverPath = `${discoverLocation.pathname}?${qs.stringify({
119138
...discoverLocation.query,
120139
})}`;
121-
if (widget.queries.length === 1) {
122-
menuOptions.push(
123-
<Link
124-
key="open-discover-link"
125-
to={discoverPath}
126-
onClick={() => {
127-
trackAdvancedAnalyticsEvent('dashboards_views.open_in_discover.opened', {
128-
organization,
129-
widget_type: widget.displayType,
130-
});
131-
}}
132-
>
133-
<StyledMenuItem key="open-discover">{t('Open in Discover')}</StyledMenuItem>
134-
</Link>
135-
);
136-
} else {
137-
menuOptions.push(
138-
<StyledMenuItem
139-
key="open-discover"
140-
onClick={event => {
141-
event.preventDefault();
142-
trackAdvancedAnalyticsEvent('dashboards_views.query_selector.opened', {
143-
organization,
144-
widget_type: widget.displayType,
145-
});
146-
openDashboardWidgetQuerySelectorModal({organization, widget});
147-
}}
148-
>
149-
{t('Open in Discover')}
150-
</StyledMenuItem>
151-
);
152-
}
140+
141+
menuOptions.push({
142+
key: 'open-in-discover',
143+
label: t('Open in Discover'),
144+
leadingItems: <IconTelescope />,
145+
to: widget.queries.length === 1 ? discoverPath : undefined,
146+
onAction: () => {
147+
if (widget.queries.length === 1) {
148+
trackAdvancedAnalyticsEvent('dashboards_views.open_in_discover.opened', {
149+
organization,
150+
widget_type: widget.displayType,
151+
});
152+
return;
153+
}
154+
155+
trackAdvancedAnalyticsEvent('dashboards_views.query_selector.opened', {
156+
organization,
157+
widget_type: widget.displayType,
158+
});
159+
openDashboardWidgetQuerySelectorModal({organization, widget});
160+
},
161+
});
153162
}
154163
}
155164

@@ -165,43 +174,42 @@ function WidgetCardContextMenu({
165174
...datetime,
166175
})}`;
167176

168-
menuOptions.push(
169-
<Link to={issuesLocation} key="open-issues-link">
170-
<StyledMenuItem key="open-issues">{t('Open in Issues')}</StyledMenuItem>
171-
</Link>
172-
);
177+
menuOptions.push({
178+
key: 'open-in-issues',
179+
label: t('Open in Issues'),
180+
leadingItems: <IconIssues />,
181+
to: issuesLocation,
182+
});
173183
}
174184

175185
if (organization.features.includes('dashboards-edit')) {
176-
menuOptions.push(
177-
<StyledMenuItem
178-
key="duplicate-widget"
179-
data-test-id="duplicate-widget"
180-
onSelect={onDuplicate}
181-
disabled={widgetLimitReached}
182-
>
183-
{t('Duplicate Widget')}
184-
</StyledMenuItem>
185-
);
186-
187-
menuOptions.push(
188-
<StyledMenuItem key="edit-widget" data-test-id="edit-widget" onSelect={onEdit}>
189-
{t('Edit Widget')}
190-
</StyledMenuItem>
191-
);
192-
193-
menuOptions.push(
194-
<Confirm
195-
key="delete-widget"
196-
priority="danger"
197-
message={t('Are you sure you want to delete this widget?')}
198-
onConfirm={onDelete}
199-
>
200-
<StyledMenuItem data-test-id="delete-widget" danger>
201-
{t('Delete Widget')}
202-
</StyledMenuItem>
203-
</Confirm>
204-
);
186+
menuOptions.push({
187+
key: 'duplicate-widget',
188+
label: t('Duplicate Widget'),
189+
leadingItems: <IconCopy />,
190+
onAction: () => onDuplicate(),
191+
});
192+
widgetLimitReached && disabledKeys.push('duplicate-widget');
193+
194+
menuOptions.push({
195+
key: 'edit-widget',
196+
label: t('Edit Widget'),
197+
leadingItems: <IconEdit />,
198+
onAction: () => onEdit(),
199+
});
200+
201+
menuOptions.push({
202+
key: 'delete-widget',
203+
label: t('Delete Widget'),
204+
leadingItems: <IconDelete />,
205+
onAction: () => {
206+
openConfirmModal({
207+
message: t('Are you sure you want to delete this widget?'),
208+
priority: 'danger',
209+
onConfirm: () => onDelete(),
210+
});
211+
},
212+
});
205213
}
206214

207215
if (!menuOptions.length) {
@@ -210,26 +218,27 @@ function WidgetCardContextMenu({
210218

211219
return (
212220
<ContextWrapper>
213-
<ContextMenu>{menuOptions}</ContextMenu>
221+
<DropdownMenuControlV2
222+
items={menuOptions}
223+
triggerProps={{
224+
'aria-label': t('Widget actions'),
225+
size: 'xsmall',
226+
borderless: true,
227+
showChevron: false,
228+
icon: <IconEllipsis direction="down" size="sm" />,
229+
}}
230+
placement="bottom right"
231+
disabledKeys={disabledKeys}
232+
/>
214233
</ContextWrapper>
215234
);
216235
}
217236

218237
export default WidgetCardContextMenu;
219238

220239
const ContextWrapper = styled('div')`
240+
display: flex;
241+
align-items: center;
242+
height: ${space(3)};
221243
margin-left: ${space(1)};
222244
`;
223-
224-
const StyledMenuItem = styled(MenuItem)<{danger?: boolean}>`
225-
white-space: nowrap;
226-
color: ${p => (p.danger ? p.theme.red300 : p.theme.textColor)};
227-
:hover {
228-
color: ${p => (p.danger ? p.theme.red300 : p.theme.textColor)};
229-
}
230-
`;
231-
232-
const PreviewMessage = styled('span')`
233-
padding: ${space(1)};
234-
display: block;
235-
`;

tests/acceptance/test_organization_dashboards.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ def test_duplicate_widget_in_view_mode(self):
118118
with self.feature(FEATURE_NAMES + EDIT_FEATURE):
119119
self.page.visit_dashboard_detail()
120120

121-
self.browser.element('[data-test-id="context-menu"]').click()
121+
self.browser.element('[aria-haspopup="true"]').click()
122122
self.browser.element('[data-test-id="duplicate-widget"]').click()
123123
self.page.wait_until_loaded()
124124

125-
self.browser.elements('[data-test-id="context-menu"]')[0].click()
125+
self.browser.element('[aria-haspopup="true"]').click()
126126
self.browser.element('[data-test-id="duplicate-widget"]').click()
127127
self.page.wait_until_loaded()
128128

@@ -132,7 +132,7 @@ def test_delete_widget_in_view_mode(self):
132132
with self.feature(FEATURE_NAMES + EDIT_FEATURE):
133133
self.page.visit_dashboard_detail()
134134

135-
self.browser.element('[data-test-id="context-menu"]').click()
135+
self.browser.element('[aria-haspopup="true"]').click()
136136
self.browser.element('[data-test-id="delete-widget"]').click()
137137
self.browser.element('[data-test-id="confirm-button"]').click()
138138

@@ -476,11 +476,11 @@ def test_duplicate_widget_in_view_mode(self):
476476
with self.feature(FEATURE_NAMES + EDIT_FEATURE):
477477
self.page.visit_dashboard_detail()
478478

479-
self.browser.element('[data-test-id="context-menu"]').click()
479+
self.browser.element('[aria-haspopup="true"]').click()
480480
self.browser.element('[data-test-id="duplicate-widget"]').click()
481481
self.page.wait_until_loaded()
482482

483-
self.browser.elements('[data-test-id="context-menu"]')[0].click()
483+
self.browser.element('[aria-haspopup="true"]').click()
484484
self.browser.element('[data-test-id="duplicate-widget"]').click()
485485
self.page.wait_until_loaded()
486486

@@ -507,7 +507,7 @@ def test_delete_widget_in_view_mode(self):
507507
with self.feature(FEATURE_NAMES + EDIT_FEATURE):
508508
self.page.visit_dashboard_detail()
509509

510-
self.browser.element('[data-test-id="context-menu"]').click()
510+
self.browser.element('[aria-haspopup="true"]').click()
511511
self.browser.element('[data-test-id="delete-widget"]').click()
512512
self.browser.element('[data-test-id="confirm-button"]').click()
513513

@@ -643,8 +643,8 @@ def test_deleting_stacked_widgets_by_context_menu_does_not_trigger_confirm_on_ed
643643
):
644644
self.page.visit_dashboard_detail()
645645

646-
context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
647-
context_menu_icon.click()
646+
dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
647+
dropdown_trigger.click()
648648

649649
delete_widget_menu_item = self.browser.element('[data-test-id="delete-widget"]')
650650
delete_widget_menu_item.click()
@@ -695,8 +695,8 @@ def test_changing_number_widget_to_area_updates_widget_height(
695695
self.page.visit_dashboard_detail()
696696

697697
# Open edit modal for first widget
698-
context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
699-
context_menu_icon.click()
698+
dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
699+
dropdown_trigger.click()
700700
edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
701701
edit_widget_menu_item.click()
702702

@@ -743,8 +743,8 @@ def test_changing_number_widget_larger_than_min_height_for_area_chart_keeps_heig
743743
self.page.visit_dashboard_detail()
744744

745745
# Open edit modal for first widget
746-
context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
747-
context_menu_icon.click()
746+
dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
747+
dropdown_trigger.click()
748748
edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
749749
edit_widget_menu_item.click()
750750

@@ -791,8 +791,8 @@ def test_changing_area_widget_larger_than_min_height_for_number_chart_keeps_heig
791791
self.page.visit_dashboard_detail()
792792

793793
# Open edit modal for first widget
794-
context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
795-
context_menu_icon.click()
794+
dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
795+
dropdown_trigger.click()
796796
edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
797797
edit_widget_menu_item.click()
798798

tests/js/sentry-test/dropdownMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function selectDropdownMenuItem({
4646
wrapper,
4747
itemKey,
4848
specifiers,
49-
triggerSelector = 'DropdownTrigger',
49+
triggerSelector = 'Button',
5050
}: SelectDropdownItemProps) {
5151
/**
5252
* Returns a ReactWrapper which we'll use to find the

tests/js/spec/components/actions/resolve.spec.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ describe('ResolveActions', function () {
210210
await selectDropdownMenuItem({
211211
wrapper,
212212
itemKey: 'another-release',
213+
triggerSelector: 'DropdownTrigger',
213214
});
214215

215216
expect(wrapper.find('CustomResolutionModal Select').prop('options')).toEqual([

0 commit comments

Comments
 (0)