Skip to content

Commit 8857e10

Browse files
authored
feat(widget-builder): Create blank widget preview for templates (#83509)
Before when we didn't have a template selected the widget preview would error out. The mockup had a default or blank preview that I've implemented. Now when you open templates you will see the blank preview. I've also done some quick fixes for the draggable widget preview. TLDR customizing templates was not allowing draggable previews due to `ref` issues. Here's a before and after: | Before | After | |--------|--------| | <img width="1285" alt="image" src="https://github.com/user-attachments/assets/f288c319-dde9-4806-8d52-5ea9d542e854" /> | <img width="1296" alt="image" src="https://github.com/user-attachments/assets/137a5e16-37eb-4155-a4c6-86a9cc2d026c" /> |
1 parent f80e3f2 commit 8857e10

File tree

5 files changed

+128
-22
lines changed

5 files changed

+128
-22
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,23 @@ describe('NewWidgetBuiler', function () {
240240
expect(await screen.findByText('Select group')).toBeInTheDocument();
241241
expect(await screen.findByText('Add Group')).toBeInTheDocument();
242242
});
243+
244+
it('renders empty widget preview when no widget selected from templates', async function () {
245+
render(
246+
<WidgetBuilderV2
247+
isOpen
248+
onClose={onCloseMock}
249+
dashboard={DashboardFixture([])}
250+
dashboardFilters={{}}
251+
onSave={onSaveMock}
252+
openWidgetTemplates
253+
setOpenWidgetTemplates={jest.fn()}
254+
/>,
255+
{router, organization}
256+
);
257+
258+
expect(await screen.findByText('Add from Widget Library')).toBeInTheDocument();
259+
260+
expect(await screen.findByText('Select a widget to preview')).toBeInTheDocument();
261+
});
243262
});

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

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {css, useTheme} from '@emotion/react';
44
import styled from '@emotion/styled';
55
import {AnimatePresence, motion} from 'framer-motion';
66
import cloneDeep from 'lodash/cloneDeep';
7+
import omit from 'lodash/omit';
78

89
import {t} from 'sentry/locale';
910
import {space} from 'sentry/styles/space';
@@ -169,6 +170,7 @@ function WidgetBuilderV2({
169170
isDraggable={isPreviewDraggable}
170171
isWidgetInvalid={!queryConditionsValid}
171172
onDataFetched={handleWidgetDataFetched}
173+
openWidgetTemplates={openWidgetTemplates}
172174
/>
173175
</DndContext>
174176
)}
@@ -192,13 +194,15 @@ export function WidgetPreviewContainer({
192194
dragPosition,
193195
isDraggable,
194196
onDataFetched,
197+
openWidgetTemplates,
195198
}: {
196199
dashboard: DashboardDetails;
197200
dashboardFilters: DashboardFilters;
198201
isWidgetInvalid: boolean;
199202
dragPosition?: WidgetDragPositioning;
200203
isDraggable?: boolean;
201204
onDataFetched?: (tableData: TableDataWithTitle[]) => void;
205+
openWidgetTemplates?: boolean;
202206
}) {
203207
const {state} = useWidgetBuilderContext();
204208
const organization = useOrganization();
@@ -209,7 +213,6 @@ export function WidgetPreviewContainer({
209213
useRpc: decodeBoolean,
210214
},
211215
});
212-
213216
const isSmallScreen = useMedia(`(max-width: ${theme.breakpoints.small})`);
214217
// if small screen and draggable, enable dragging
215218
const isDragEnabled = isSmallScreen && isDraggable;
@@ -224,7 +227,7 @@ export function WidgetPreviewContainer({
224227

225228
const draggableStyle: CSSProperties = {
226229
transform: isDragEnabled
227-
? `translate3d(${translate?.x ?? 0}px, ${translate?.y ?? 0}px, 0)`
230+
? `translate3d(${isDragging ? translate?.x : 0}px, ${isDragging ? translate?.y : 0}px, 0)`
228231
: undefined,
229232
top: isDragEnabled ? top ?? 0 : undefined,
230233
left: isDragEnabled ? left ?? 0 : undefined,
@@ -236,10 +239,27 @@ export function WidgetPreviewContainer({
236239
position: isDragEnabled ? 'fixed' : undefined,
237240
};
238241

242+
// check if the state is in the url because the state variable has default values
243+
const hasUrlParams =
244+
Object.keys(
245+
omit(location.query, [
246+
'environment',
247+
'project',
248+
'release',
249+
'start',
250+
'end',
251+
'statsPeriod',
252+
])
253+
).length > 0;
254+
239255
const getPreviewHeight = () => {
240256
if (isDragEnabled) {
241257
return DRAGGABLE_PREVIEW_HEIGHT_PX;
242258
}
259+
// if none of the widget templates are selected
260+
if (openWidgetTemplates && !hasUrlParams) {
261+
return PREVIEW_HEIGHT_PX;
262+
}
243263
if (state.displayType === DisplayType.TABLE) {
244264
return 'auto';
245265
}
@@ -289,14 +309,25 @@ export function WidgetPreviewContainer({
289309
: undefined,
290310
}}
291311
>
292-
<WidgetPreview
293-
// While we test out RPC for spans, force a re-render if the spans toggle changes
294-
key={state.dataset === WidgetType.SPANS && useRpc ? 'spans' : 'other'}
295-
dashboardFilters={dashboardFilters}
296-
dashboard={dashboard}
297-
isWidgetInvalid={isWidgetInvalid}
298-
onDataFetched={onDataFetched}
299-
/>
312+
{openWidgetTemplates && !hasUrlParams ? (
313+
<WidgetPreviewPlaceholder>
314+
<h6 style={{margin: 0}}>{t('Widget Title')}</h6>
315+
<TemplateWidgetPreviewPlaceholder>
316+
<p style={{margin: 0}}>{t('Select a widget to preview')}</p>
317+
</TemplateWidgetPreviewPlaceholder>
318+
</WidgetPreviewPlaceholder>
319+
) : (
320+
<WidgetPreview
321+
// While we test out RPC for spans, force a re-render if the spans toggle changes
322+
key={
323+
state.dataset === WidgetType.SPANS && useRpc ? 'spans' : 'other'
324+
}
325+
dashboardFilters={dashboardFilters}
326+
dashboard={dashboard}
327+
isWidgetInvalid={isWidgetInvalid}
328+
onDataFetched={onDataFetched}
329+
/>
330+
)}
300331
</SampleWidgetCard>
301332
</DraggableWidgetContainer>
302333
</MEPSettingProvider>
@@ -420,3 +451,22 @@ const DroppableGrid = styled('div')`
420451
bottom: ${space(2)};
421452
left: 0;
422453
`;
454+
455+
const TemplateWidgetPreviewPlaceholder = styled('div')`
456+
display: flex;
457+
flex-direction: column;
458+
align-items: center;
459+
justify-content: center;
460+
width: 100%;
461+
height: 95%;
462+
color: ${p => p.theme.subText};
463+
font-style: italic;
464+
font-size: ${p => p.theme.fontSizeMedium};
465+
font-weight: ${p => p.theme.fontWeightNormal};
466+
`;
467+
468+
const WidgetPreviewPlaceholder = styled('div')`
469+
width: 100%;
470+
height: 100%;
471+
padding: ${space(2)};
472+
`;

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ function WidgetBuilderSlideout({
8484
state.displayType !== DisplayType.BIG_NUMBER &&
8585
state.displayType !== DisplayType.TABLE;
8686

87-
const previewRef = useRef<HTMLDivElement>(null);
87+
const customPreviewRef = useRef<HTMLDivElement>(null);
88+
const templatesPreviewRef = useRef<HTMLDivElement>(null);
8889

8990
const isSmallScreen = useMedia(`(max-width: ${theme.breakpoints.small})`);
9091

@@ -100,12 +101,17 @@ function WidgetBuilderSlideout({
100101
{threshold: 0}
101102
);
102103

103-
if (previewRef.current) {
104-
observer.observe(previewRef.current);
104+
// need two different refs to account for preview when customizing templates
105+
if (customPreviewRef.current) {
106+
observer.observe(customPreviewRef.current);
107+
}
108+
109+
if (templatesPreviewRef.current) {
110+
observer.observe(templatesPreviewRef.current);
105111
}
106112

107113
return () => observer.disconnect();
108-
}, [setIsPreviewDraggable]);
114+
}, [setIsPreviewDraggable, openWidgetTemplates]);
109115

110116
return (
111117
<SlideOverPanel
@@ -151,14 +157,15 @@ function WidgetBuilderSlideout({
151157
<Section>
152158
<WidgetBuilderTypeSelector error={error} setError={setError} />
153159
</Section>
154-
<div ref={previewRef}>
160+
<div ref={customPreviewRef}>
155161
{isSmallScreen && (
156162
<Section>
157163
<WidgetPreviewContainer
158164
dashboard={dashboard}
159165
dashboardFilters={dashboardFilters}
160166
isWidgetInvalid={isWidgetInvalid}
161167
onDataFetched={onDataFetched}
168+
openWidgetTemplates={openWidgetTemplates}
162169
/>
163170
</Section>
164171
)}
@@ -196,21 +203,23 @@ function WidgetBuilderSlideout({
196203
</Fragment>
197204
) : (
198205
<Fragment>
199-
<div ref={previewRef}>
206+
<div ref={templatesPreviewRef}>
200207
{isSmallScreen && (
201208
<Section>
202209
<WidgetPreviewContainer
203210
dashboard={dashboard}
204211
dashboardFilters={dashboardFilters}
205212
isWidgetInvalid={isWidgetInvalid}
206213
onDataFetched={onDataFetched}
214+
openWidgetTemplates={openWidgetTemplates}
207215
/>
208216
</Section>
209217
)}
210218
</div>
211219
<WidgetTemplatesList
212220
onSave={onSave}
213221
setOpenWidgetTemplates={setOpenWidgetTemplates}
222+
setIsPreviewDraggable={setIsPreviewDraggable}
214223
/>
215224
</Fragment>
216225
)}

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ describe('WidgetTemplatesList', () => {
5555
it('should render the widget templates list', async () => {
5656
render(
5757
<WidgetBuilderProvider>
58-
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
58+
<WidgetTemplatesList
59+
onSave={onSave}
60+
setOpenWidgetTemplates={jest.fn()}
61+
setIsPreviewDraggable={jest.fn()}
62+
/>
5963
</WidgetBuilderProvider>
6064
);
6165

@@ -66,7 +70,11 @@ describe('WidgetTemplatesList', () => {
6670
it('should render buttons when the user clicks on a widget template', async () => {
6771
render(
6872
<WidgetBuilderProvider>
69-
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
73+
<WidgetTemplatesList
74+
onSave={onSave}
75+
setOpenWidgetTemplates={jest.fn()}
76+
setIsPreviewDraggable={jest.fn()}
77+
/>
7078
</WidgetBuilderProvider>
7179
);
7280

@@ -83,7 +91,11 @@ describe('WidgetTemplatesList', () => {
8391

8492
render(
8593
<WidgetBuilderProvider>
86-
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
94+
<WidgetTemplatesList
95+
onSave={onSave}
96+
setOpenWidgetTemplates={jest.fn()}
97+
setIsPreviewDraggable={jest.fn()}
98+
/>
8799
</WidgetBuilderProvider>,
88100
{router}
89101
);
@@ -107,7 +119,11 @@ describe('WidgetTemplatesList', () => {
107119
it('should show error message when the widget fails to save', async () => {
108120
render(
109121
<WidgetBuilderProvider>
110-
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
122+
<WidgetTemplatesList
123+
onSave={onSave}
124+
setOpenWidgetTemplates={jest.fn()}
125+
setIsPreviewDraggable={jest.fn()}
126+
/>
111127
</WidgetBuilderProvider>
112128
);
113129

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ import {getWidgetIcon} from 'sentry/views/dashboards/widgetLibrary/widgetCard';
1919

2020
interface WidgetTemplatesListProps {
2121
onSave: ({index, widget}: {index: number; widget: Widget}) => void;
22+
setIsPreviewDraggable: (isPreviewDraggable: boolean) => void;
2223
setOpenWidgetTemplates: (openWidgetTemplates: boolean) => void;
2324
}
2425

25-
function WidgetTemplatesList({onSave, setOpenWidgetTemplates}: WidgetTemplatesListProps) {
26+
function WidgetTemplatesList({
27+
onSave,
28+
setOpenWidgetTemplates,
29+
setIsPreviewDraggable,
30+
}: WidgetTemplatesListProps) {
2631
const organization = useOrganization();
2732
const [selectedWidget, setSelectedWidget] = useState<number | null>(null);
2833

@@ -72,7 +77,14 @@ function WidgetTemplatesList({onSave, setOpenWidgetTemplates}: WidgetTemplatesLi
7277
<WidgetDescription>{widget.description}</WidgetDescription>
7378
{selectedWidget === index && (
7479
<ButtonsWrapper>
75-
<Button size="sm" onClick={() => setOpenWidgetTemplates(false)}>
80+
<Button
81+
size="sm"
82+
onClick={() => {
83+
setOpenWidgetTemplates(false);
84+
// reset preview when customizing templates
85+
setIsPreviewDraggable(false);
86+
}}
87+
>
7688
{t('Customize')}
7789
</Button>
7890
<Button size="sm" onClick={() => handleSave(widget)}>

0 commit comments

Comments
 (0)