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:
---
.../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)};
`;