Skip to content

Commit

Permalink
feat(widget-builder): Create blank widget preview for templates (#83509)
Browse files Browse the repository at this point in the history
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"
/> |
  • Loading branch information
nikkikapadia authored Jan 15, 2025
1 parent f80e3f2 commit 8857e10
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,23 @@ describe('NewWidgetBuiler', function () {
expect(await screen.findByText('Select group')).toBeInTheDocument();
expect(await screen.findByText('Add Group')).toBeInTheDocument();
});

it('renders empty widget preview when no widget selected from templates', async function () {
render(
<WidgetBuilderV2
isOpen
onClose={onCloseMock}
dashboard={DashboardFixture([])}
dashboardFilters={{}}
onSave={onSaveMock}
openWidgetTemplates
setOpenWidgetTemplates={jest.fn()}
/>,
{router, organization}
);

expect(await screen.findByText('Add from Widget Library')).toBeInTheDocument();

expect(await screen.findByText('Select a widget to preview')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {css, useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import {AnimatePresence, motion} from 'framer-motion';
import cloneDeep from 'lodash/cloneDeep';
import omit from 'lodash/omit';

import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
Expand Down Expand Up @@ -169,6 +170,7 @@ function WidgetBuilderV2({
isDraggable={isPreviewDraggable}
isWidgetInvalid={!queryConditionsValid}
onDataFetched={handleWidgetDataFetched}
openWidgetTemplates={openWidgetTemplates}
/>
</DndContext>
)}
Expand All @@ -192,13 +194,15 @@ export function WidgetPreviewContainer({
dragPosition,
isDraggable,
onDataFetched,
openWidgetTemplates,
}: {
dashboard: DashboardDetails;
dashboardFilters: DashboardFilters;
isWidgetInvalid: boolean;
dragPosition?: WidgetDragPositioning;
isDraggable?: boolean;
onDataFetched?: (tableData: TableDataWithTitle[]) => void;
openWidgetTemplates?: boolean;
}) {
const {state} = useWidgetBuilderContext();
const organization = useOrganization();
Expand All @@ -209,7 +213,6 @@ export function WidgetPreviewContainer({
useRpc: decodeBoolean,
},
});

const isSmallScreen = useMedia(`(max-width: ${theme.breakpoints.small})`);
// if small screen and draggable, enable dragging
const isDragEnabled = isSmallScreen && isDraggable;
Expand All @@ -224,7 +227,7 @@ export function WidgetPreviewContainer({

const draggableStyle: CSSProperties = {
transform: isDragEnabled
? `translate3d(${translate?.x ?? 0}px, ${translate?.y ?? 0}px, 0)`
? `translate3d(${isDragging ? translate?.x : 0}px, ${isDragging ? translate?.y : 0}px, 0)`
: undefined,
top: isDragEnabled ? top ?? 0 : undefined,
left: isDragEnabled ? left ?? 0 : undefined,
Expand All @@ -236,10 +239,27 @@ export function WidgetPreviewContainer({
position: isDragEnabled ? 'fixed' : undefined,
};

// check if the state is in the url because the state variable has default values
const hasUrlParams =
Object.keys(
omit(location.query, [
'environment',
'project',
'release',
'start',
'end',
'statsPeriod',
])
).length > 0;

const getPreviewHeight = () => {
if (isDragEnabled) {
return DRAGGABLE_PREVIEW_HEIGHT_PX;
}
// if none of the widget templates are selected
if (openWidgetTemplates && !hasUrlParams) {
return PREVIEW_HEIGHT_PX;
}
if (state.displayType === DisplayType.TABLE) {
return 'auto';
}
Expand Down Expand Up @@ -289,14 +309,25 @@ export function WidgetPreviewContainer({
: undefined,
}}
>
<WidgetPreview
// While we test out RPC for spans, force a re-render if the spans toggle changes
key={state.dataset === WidgetType.SPANS && useRpc ? 'spans' : 'other'}
dashboardFilters={dashboardFilters}
dashboard={dashboard}
isWidgetInvalid={isWidgetInvalid}
onDataFetched={onDataFetched}
/>
{openWidgetTemplates && !hasUrlParams ? (
<WidgetPreviewPlaceholder>
<h6 style={{margin: 0}}>{t('Widget Title')}</h6>
<TemplateWidgetPreviewPlaceholder>
<p style={{margin: 0}}>{t('Select a widget to preview')}</p>
</TemplateWidgetPreviewPlaceholder>
</WidgetPreviewPlaceholder>
) : (
<WidgetPreview
// While we test out RPC for spans, force a re-render if the spans toggle changes
key={
state.dataset === WidgetType.SPANS && useRpc ? 'spans' : 'other'
}
dashboardFilters={dashboardFilters}
dashboard={dashboard}
isWidgetInvalid={isWidgetInvalid}
onDataFetched={onDataFetched}
/>
)}
</SampleWidgetCard>
</DraggableWidgetContainer>
</MEPSettingProvider>
Expand Down Expand Up @@ -420,3 +451,22 @@ const DroppableGrid = styled('div')`
bottom: ${space(2)};
left: 0;
`;

const TemplateWidgetPreviewPlaceholder = styled('div')`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 95%;
color: ${p => p.theme.subText};
font-style: italic;
font-size: ${p => p.theme.fontSizeMedium};
font-weight: ${p => p.theme.fontWeightNormal};
`;

const WidgetPreviewPlaceholder = styled('div')`
width: 100%;
height: 100%;
padding: ${space(2)};
`;
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ function WidgetBuilderSlideout({
state.displayType !== DisplayType.BIG_NUMBER &&
state.displayType !== DisplayType.TABLE;

const previewRef = useRef<HTMLDivElement>(null);
const customPreviewRef = useRef<HTMLDivElement>(null);
const templatesPreviewRef = useRef<HTMLDivElement>(null);

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

Expand All @@ -100,12 +101,17 @@ function WidgetBuilderSlideout({
{threshold: 0}
);

if (previewRef.current) {
observer.observe(previewRef.current);
// need two different refs to account for preview when customizing templates
if (customPreviewRef.current) {
observer.observe(customPreviewRef.current);
}

if (templatesPreviewRef.current) {
observer.observe(templatesPreviewRef.current);
}

return () => observer.disconnect();
}, [setIsPreviewDraggable]);
}, [setIsPreviewDraggable, openWidgetTemplates]);

return (
<SlideOverPanel
Expand Down Expand Up @@ -151,14 +157,15 @@ function WidgetBuilderSlideout({
<Section>
<WidgetBuilderTypeSelector error={error} setError={setError} />
</Section>
<div ref={previewRef}>
<div ref={customPreviewRef}>
{isSmallScreen && (
<Section>
<WidgetPreviewContainer
dashboard={dashboard}
dashboardFilters={dashboardFilters}
isWidgetInvalid={isWidgetInvalid}
onDataFetched={onDataFetched}
openWidgetTemplates={openWidgetTemplates}
/>
</Section>
)}
Expand Down Expand Up @@ -196,21 +203,23 @@ function WidgetBuilderSlideout({
</Fragment>
) : (
<Fragment>
<div ref={previewRef}>
<div ref={templatesPreviewRef}>
{isSmallScreen && (
<Section>
<WidgetPreviewContainer
dashboard={dashboard}
dashboardFilters={dashboardFilters}
isWidgetInvalid={isWidgetInvalid}
onDataFetched={onDataFetched}
openWidgetTemplates={openWidgetTemplates}
/>
</Section>
)}
</div>
<WidgetTemplatesList
onSave={onSave}
setOpenWidgetTemplates={setOpenWidgetTemplates}
setIsPreviewDraggable={setIsPreviewDraggable}
/>
</Fragment>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ describe('WidgetTemplatesList', () => {
it('should render the widget templates list', async () => {
render(
<WidgetBuilderProvider>
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
<WidgetTemplatesList
onSave={onSave}
setOpenWidgetTemplates={jest.fn()}
setIsPreviewDraggable={jest.fn()}
/>
</WidgetBuilderProvider>
);

Expand All @@ -66,7 +70,11 @@ describe('WidgetTemplatesList', () => {
it('should render buttons when the user clicks on a widget template', async () => {
render(
<WidgetBuilderProvider>
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
<WidgetTemplatesList
onSave={onSave}
setOpenWidgetTemplates={jest.fn()}
setIsPreviewDraggable={jest.fn()}
/>
</WidgetBuilderProvider>
);

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

render(
<WidgetBuilderProvider>
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
<WidgetTemplatesList
onSave={onSave}
setOpenWidgetTemplates={jest.fn()}
setIsPreviewDraggable={jest.fn()}
/>
</WidgetBuilderProvider>,
{router}
);
Expand All @@ -107,7 +119,11 @@ describe('WidgetTemplatesList', () => {
it('should show error message when the widget fails to save', async () => {
render(
<WidgetBuilderProvider>
<WidgetTemplatesList onSave={onSave} setOpenWidgetTemplates={jest.fn()} />
<WidgetTemplatesList
onSave={onSave}
setOpenWidgetTemplates={jest.fn()}
setIsPreviewDraggable={jest.fn()}
/>
</WidgetBuilderProvider>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ import {getWidgetIcon} from 'sentry/views/dashboards/widgetLibrary/widgetCard';

interface WidgetTemplatesListProps {
onSave: ({index, widget}: {index: number; widget: Widget}) => void;
setIsPreviewDraggable: (isPreviewDraggable: boolean) => void;
setOpenWidgetTemplates: (openWidgetTemplates: boolean) => void;
}

function WidgetTemplatesList({onSave, setOpenWidgetTemplates}: WidgetTemplatesListProps) {
function WidgetTemplatesList({
onSave,
setOpenWidgetTemplates,
setIsPreviewDraggable,
}: WidgetTemplatesListProps) {
const organization = useOrganization();
const [selectedWidget, setSelectedWidget] = useState<number | null>(null);

Expand Down Expand Up @@ -72,7 +77,14 @@ function WidgetTemplatesList({onSave, setOpenWidgetTemplates}: WidgetTemplatesLi
<WidgetDescription>{widget.description}</WidgetDescription>
{selectedWidget === index && (
<ButtonsWrapper>
<Button size="sm" onClick={() => setOpenWidgetTemplates(false)}>
<Button
size="sm"
onClick={() => {
setOpenWidgetTemplates(false);
// reset preview when customizing templates
setIsPreviewDraggable(false);
}}
>
{t('Customize')}
</Button>
<Button size="sm" onClick={() => handleSave(widget)}>
Expand Down

0 comments on commit 8857e10

Please sign in to comment.