Skip to content

Commit f7c13f9

Browse files
authored
feat(widget-builder): Templates add to dashboard functionality (#83302)
Now clicking the "Add to dashboard" button on the widget templates are functional. It will validate the widget and then save it to your dashboard or display an error message. (Note the PR for hooking up the templates to the widget preview is not merged yet so it will not show the preview before adding to dashboard).
1 parent 50a65ae commit f7c13f9

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

static/app/views/dashboards/widgetBuilder/components/widgetBuilderSlideout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ function WidgetBuilderSlideout({
188188
</Section>
189189
)}
190190
</div>
191-
<WidgetTemplatesList />
191+
<WidgetTemplatesList onSave={onSave} />
192192
</Fragment>
193193
)}
194194
</SlideoutBodyWrapper>

static/app/views/dashboards/widgetBuilder/components/widgetTemplatesList.spec.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import {LocationFixture} from 'sentry-fixture/locationFixture';
22
import {RouterFixture} from 'sentry-fixture/routerFixture';
33

4-
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
4+
import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
55

6+
import {addErrorMessage} from 'sentry/actionCreators/indicator';
67
import {useNavigate} from 'sentry/utils/useNavigate';
78
import WidgetTemplatesList from 'sentry/views/dashboards/widgetBuilder/components/widgetTemplatesList';
89
import {WidgetBuilderProvider} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
@@ -30,10 +31,21 @@ const router = RouterFixture({
3031
location: LocationFixture({query: {}}),
3132
});
3233

34+
jest.mock('sentry/actionCreators/indicator');
35+
3336
describe('WidgetTemplatesList', () => {
37+
const onSave = jest.fn();
38+
3439
beforeEach(() => {
3540
const mockNavigate = jest.fn();
3641
mockUseNavigate.mockReturnValue(mockNavigate);
42+
43+
MockApiClient.addMockResponse({
44+
url: '/organizations/org-slug/dashboards/widgets/',
45+
method: 'POST',
46+
body: {},
47+
statusCode: 400,
48+
});
3749
});
3850

3951
afterEach(() => {
@@ -43,7 +55,7 @@ describe('WidgetTemplatesList', () => {
4355
it('should render the widget templates list', async () => {
4456
render(
4557
<WidgetBuilderProvider>
46-
<WidgetTemplatesList />
58+
<WidgetTemplatesList onSave={onSave} />
4759
</WidgetBuilderProvider>
4860
);
4961

@@ -54,7 +66,7 @@ describe('WidgetTemplatesList', () => {
5466
it('should render buttons when the user clicks on a widget template', async () => {
5567
render(
5668
<WidgetBuilderProvider>
57-
<WidgetTemplatesList />
69+
<WidgetTemplatesList onSave={onSave} />
5870
</WidgetBuilderProvider>
5971
);
6072

@@ -71,7 +83,7 @@ describe('WidgetTemplatesList', () => {
7183

7284
render(
7385
<WidgetBuilderProvider>
74-
<WidgetTemplatesList />
86+
<WidgetTemplatesList onSave={onSave} />
7587
</WidgetBuilderProvider>,
7688
{router}
7789
);
@@ -91,4 +103,24 @@ describe('WidgetTemplatesList', () => {
91103
})
92104
);
93105
});
106+
107+
it('should show error message when the widget fails to save', async () => {
108+
render(
109+
<WidgetBuilderProvider>
110+
<WidgetTemplatesList onSave={onSave} />
111+
</WidgetBuilderProvider>
112+
);
113+
114+
const widgetTemplate = await screen.findByText('Duration Distribution');
115+
await userEvent.click(widgetTemplate);
116+
117+
await userEvent.click(await screen.findByText('Add to dashboard'));
118+
119+
await waitFor(() => {
120+
expect(addErrorMessage).toHaveBeenCalledWith('Unable to add widget');
121+
});
122+
123+
// show we're still on the widget templates list
124+
expect(await screen.findByText('Add to dashboard')).toBeInTheDocument();
125+
});
94126
});

static/app/views/dashboards/widgetBuilder/components/widgetTemplatesList.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
1-
import {Fragment, useState} from 'react';
1+
import {Fragment, useCallback, useState} from 'react';
2+
import {useParams} from 'react-router-dom';
23
import styled from '@emotion/styled';
34

5+
import {validateWidget} from 'sentry/actionCreators/dashboards';
6+
import {addErrorMessage} from 'sentry/actionCreators/indicator';
47
import {Button} from 'sentry/components/button';
58
import {t} from 'sentry/locale';
69
import {space} from 'sentry/styles/space';
710
import theme from 'sentry/utils/theme';
11+
import useApi from 'sentry/utils/useApi';
812
import useOrganization from 'sentry/utils/useOrganization';
13+
import type {Widget} from 'sentry/views/dashboards/types';
914
import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
1015
import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
1116
import {convertWidgetToBuilderStateParams} from 'sentry/views/dashboards/widgetBuilder/utils/convertWidgetToBuilderStateParams';
1217
import {getTopNConvertedDefaultWidgets} from 'sentry/views/dashboards/widgetLibrary/data';
1318
import {getWidgetIcon} from 'sentry/views/dashboards/widgetLibrary/widgetCard';
1419

15-
function WidgetTemplatesList() {
20+
type WidgetTemplatesListProps = {
21+
onSave: ({index, widget}: {index: number; widget: Widget}) => void;
22+
};
23+
24+
function WidgetTemplatesList({onSave}: WidgetTemplatesListProps) {
1625
const organization = useOrganization();
1726
const [selectedWidget, setSelectedWidget] = useState<number | null>(null);
1827

1928
const {dispatch} = useWidgetBuilderContext();
29+
const {widgetIndex} = useParams();
30+
const api = useApi();
2031

2132
const widgets = getTopNConvertedDefaultWidgets(organization);
2233

34+
const handleSave = useCallback(
35+
async (widget: Widget) => {
36+
try {
37+
const newWidget = {...widget, id: undefined};
38+
await validateWidget(api, organization.slug, newWidget);
39+
onSave({index: Number(widgetIndex), widget: newWidget});
40+
} catch (error) {
41+
addErrorMessage(t('Unable to add widget'));
42+
}
43+
},
44+
[api, organization.slug, widgetIndex, onSave]
45+
);
46+
2347
return (
2448
<Fragment>
2549
{widgets.map((widget, index) => {
@@ -48,7 +72,9 @@ function WidgetTemplatesList() {
4872
{selectedWidget === index && (
4973
<ButtonsWrapper>
5074
<Button size="sm">{t('Customize')}</Button>
51-
<Button size="sm">{t('Add to dashboard')}</Button>
75+
<Button size="sm" onClick={() => handleSave(widget)}>
76+
{t('Add to dashboard')}
77+
</Button>
5278
</ButtonsWrapper>
5379
)}
5480
</div>

0 commit comments

Comments
 (0)