From 9bd46bed58803b2eacf25dec4e88a2d94cd6a3ed Mon Sep 17 00:00:00 2001 From: Nikki Kapadia <72356613+nikkikapadia@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:03:39 -0500 Subject: [PATCH] feat(widget-builder): Add filters bar to widget builder (#81467) Adding the same filters bar that's on the dashboards detail page to the slide-out. Not connected with any hooks yet. It automatically has url query parameter functionality built in so that works without hooks. It looks like this so far: image --- .../widgetBuilder/components/filtersBar.tsx | 43 +++++++++++++ .../components/newWidgetBuilder.spec.tsx | 62 ++++++++++++++++++- .../components/newWidgetBuilder.tsx | 2 - .../components/widgetBuilderSlideout.tsx | 21 +++++-- 4 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 static/app/views/dashboards/widgetBuilder/components/filtersBar.tsx diff --git a/static/app/views/dashboards/widgetBuilder/components/filtersBar.tsx b/static/app/views/dashboards/widgetBuilder/components/filtersBar.tsx new file mode 100644 index 00000000000000..a0e11b452161b5 --- /dev/null +++ b/static/app/views/dashboards/widgetBuilder/components/filtersBar.tsx @@ -0,0 +1,43 @@ +import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; +import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; +import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; +import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; +import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; +import {DEFAULT_STATS_PERIOD} from 'sentry/constants'; +import {ReleasesProvider} from 'sentry/utils/releases/releasesProvider'; +import useOrganization from 'sentry/utils/useOrganization'; +import usePageFilters from 'sentry/utils/usePageFilters'; +import ReleasesSelectControl from 'sentry/views/dashboards/releasesSelectControl'; + +function WidgetBuilderFilterBar() { + const organization = useOrganization(); + const {selection} = usePageFilters(); + return ( + + + {}} /> + {}} /> + {}} /> + + {}} + selectedReleases={[]} + /> + + + + ); +} + +export default WidgetBuilderFilterBar; diff --git a/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.spec.tsx b/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.spec.tsx index 75903243817654..a48305a5fd4673 100644 --- a/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.spec.tsx +++ b/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.spec.tsx @@ -1,14 +1,74 @@ +import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen} from 'sentry-test/reactTestingLibrary'; +import OrganizationStore from 'sentry/stores/organizationStore'; +import PageFiltersStore from 'sentry/stores/pageFiltersStore'; +import ProjectsStore from 'sentry/stores/projectsStore'; import DevWidgetBuilder from 'sentry/views/dashboards/widgetBuilder/components/newWidgetBuilder'; +const {organization, projects, router} = initializeOrg({ + organization: {features: ['global-views', 'open-membership']}, + projects: [ + {id: '1', slug: 'project-1', isMember: true}, + {id: '2', slug: 'project-2', isMember: true}, + {id: '3', slug: 'project-3', isMember: false}, + ], + router: { + location: { + pathname: '/organizations/org-slug/dashboard/1/', + query: {project: '-1'}, + }, + params: {}, + }, +}); + describe('NewWidgetBuiler', function () { const onCloseMock = jest.fn(); + + beforeEach(function () { + OrganizationStore.init(); + + PageFiltersStore.init(); + PageFiltersStore.onInitializeUrlState( + { + projects: [], + environments: [], + datetime: {start: null, end: null, period: '14d', utc: null}, + }, + new Set(['projects']) + ); + + OrganizationStore.onUpdate(organization, {replace: true}); + ProjectsStore.loadInitialData(projects); + + MockApiClient.addMockResponse({ + url: '/organizations/org-slug/releases/', + body: [], + }); + + MockApiClient.addMockResponse({ + url: '/organizations/org-slug/dashboard/1/', + body: [], + }); + }); + + afterEach(() => PageFiltersStore.reset()); + it('renders', async function () { - render(); + render(, { + router, + organization, + }); expect(await screen.findByText('Create Custom Widget')).toBeInTheDocument(); expect(await screen.findByLabelText('Close Widget Builder')).toBeInTheDocument(); + + expect(await screen.findByRole('button', {name: 'All Projects'})).toBeInTheDocument(); + expect(await screen.findByRole('button', {name: 'All Envs'})).toBeInTheDocument(); + expect(await screen.findByRole('button', {name: '14D'})).toBeInTheDocument(); + expect(await screen.findByRole('button', {name: 'All Releases'})).toBeInTheDocument(); + + expect(await screen.findByText('TEST WIDGET')).toBeInTheDocument(); }); }); diff --git a/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.tsx b/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.tsx index c27a56f295a98f..b49f19d7308d19 100644 --- a/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.tsx +++ b/static/app/views/dashboards/widgetBuilder/components/newWidgetBuilder.tsx @@ -14,8 +14,6 @@ type DevWidgetBuilderProps = { function DevWidgetBuilder({isOpen, onClose}: DevWidgetBuilderProps) { const escapeKeyPressed = useKeyPress('Escape'); - // TODO(nikki): be able to handle clicking outside widget to close - useEffect(() => { if (escapeKeyPressed) { if (isOpen) { diff --git a/static/app/views/dashboards/widgetBuilder/components/widgetBuilderSlideout.tsx b/static/app/views/dashboards/widgetBuilder/components/widgetBuilderSlideout.tsx index e3c01770f4531d..21aec2998a835a 100644 --- a/static/app/views/dashboards/widgetBuilder/components/widgetBuilderSlideout.tsx +++ b/static/app/views/dashboards/widgetBuilder/components/widgetBuilderSlideout.tsx @@ -5,14 +5,13 @@ import SlideOverPanel from 'sentry/components/slideOverPanel'; import {IconClose} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; +import WidgetBuilderFilterBar from 'sentry/views/dashboards/widgetBuilder/components/filtersBar'; type WidgetBuilderSlideoutProps = { isOpen: boolean; onClose: () => void; }; -export default WidgetBuilderSlideout; - function WidgetBuilderSlideout({isOpen, onClose}: WidgetBuilderSlideoutProps) { return ( @@ -29,22 +28,36 @@ function WidgetBuilderSlideout({isOpen, onClose}: WidgetBuilderSlideoutProps) { {t('Close')} + + + ); } +export default WidgetBuilderSlideout; + const CloseButton = styled(Button)` color: ${p => p.theme.gray300}; height: fit-content; &:hover { color: ${p => p.theme.gray400}; } + z-index: 100; `; -const SlideoutTitle = styled('h5')``; +const SlideoutTitle = styled('h5')` + margin: 0; +`; const SlideoutHeaderWrapper = styled('div')` - padding: ${space(4)}; + padding: ${space(3)} ${space(4)}; display: flex; + align-items: center; justify-content: space-between; + border-bottom: 1px solid ${p => p.theme.border}; +`; + +const SlideoutBodyWrapper = styled('div')` + padding: ${space(4)}; `;