Browse Source

feat(dashboard-filters): Save on dashboard creation (#36862)

Pass page filter values along in dashboard create requests
Nar Saynorath 2 years ago
parent
commit
768553fed5

+ 12 - 6
static/app/actionCreators/dashboards.tsx

@@ -40,18 +40,24 @@ export function createDashboard(
   newDashboard: DashboardDetails,
   duplicate?: boolean
 ): Promise<DashboardDetails> {
-  const {title, widgets} = newDashboard;
+  const {title, widgets, projects, environment, period, start, end} = newDashboard;
 
   const promise: Promise<DashboardDetails> = api.requestPromise(
     `/organizations/${orgId}/dashboards/`,
     {
       method: 'POST',
-      data: {title, widgets: widgets.map(widget => omit(widget, ['tempId'])), duplicate},
+      data: {
+        title,
+        widgets: widgets.map(widget => omit(widget, ['tempId'])),
+        duplicate,
+        projects,
+        environment,
+        period,
+        start,
+        end,
+      },
       query: {
-        // TODO: This should be replaced in the future with projects
-        // when we save Dashboard page filters. This is being sent to
-        // bypass validation when creating or updating dashboards
-        project: [ALL_ACCESS_PROJECTS],
+        project: projects,
       },
     }
   );

+ 25 - 1
static/app/views/dashboardsV2/detail.tsx

@@ -497,7 +497,31 @@ class DashboardDetail extends Component<Props, State> {
               was_previewed: true,
             });
           }
-          createDashboard(api, organization.slug, modifiedDashboard, this.isPreview).then(
+          let newModifiedDashboard = modifiedDashboard;
+          if (organization.features.includes('dashboards-top-level-filter')) {
+            const {project, environment, statsPeriod, start, end} = location.query;
+            newModifiedDashboard = {
+              ...cloneDashboard(modifiedDashboard),
+              // Ensure projects and environment are sent as arrays, or undefined in the request
+              // location.query will return a string if there's only one value
+              projects:
+                project === undefined
+                  ? []
+                  : typeof project === 'string'
+                  ? [Number(project)]
+                  : project.map(Number),
+              environment: typeof environment === 'string' ? [environment] : environment,
+              period: statsPeriod,
+              start,
+              end,
+            };
+          }
+          createDashboard(
+            api,
+            organization.slug,
+            newModifiedDashboard,
+            this.isPreview
+          ).then(
             (newDashboard: DashboardDetails) => {
               addSuccessMessage(t('Dashboard created'));
               trackAnalyticsEvent({

+ 96 - 0
tests/js/spec/views/dashboardsV2/gridLayout/detail.spec.jsx

@@ -283,6 +283,10 @@ describe('Dashboards > Detail', function () {
         method: 'GET',
         body: [],
       });
+      MockApiClient.addMockResponse({
+        url: '/organizations/org-slug/events-geo/',
+        body: {data: [], meta: {}},
+      });
     });
 
     afterEach(function () {
@@ -918,5 +922,97 @@ describe('Dashboards > Detail', function () {
         })
       );
     });
+
+    it('saves a new dashboard with the page filters', async () => {
+      const mockPOST = MockApiClient.addMockResponse({
+        url: '/organizations/org-slug/dashboards/',
+        method: 'POST',
+        body: [],
+      });
+      render(
+        <CreateDashboard
+          organization={{
+            ...initialData.organization,
+            features: [
+              ...initialData.organization.features,
+              'dashboards-top-level-filter',
+            ],
+          }}
+          params={{orgId: 'org-slug'}}
+          router={initialData.router}
+          location={{
+            ...initialData.router.location,
+            query: {
+              ...initialData.router.location.query,
+              statsPeriod: '7d',
+              project: [2],
+              environment: ['alpha', 'beta'],
+            },
+          }}
+        />,
+        {
+          context: initialData.routerContext,
+          organization: initialData.organization,
+        }
+      );
+
+      userEvent.click(await screen.findByText('Save and Finish'));
+      expect(mockPOST).toHaveBeenCalledWith(
+        '/organizations/org-slug/dashboards/',
+        expect.objectContaining({
+          data: expect.objectContaining({
+            projects: [2],
+            environment: ['alpha', 'beta'],
+            period: '7d',
+          }),
+        })
+      );
+    });
+
+    it('saves a template with the page filters', async () => {
+      const mockPOST = MockApiClient.addMockResponse({
+        url: '/organizations/org-slug/dashboards/',
+        method: 'POST',
+        body: [],
+      });
+      render(
+        <CreateDashboard
+          organization={{
+            ...initialData.organization,
+            features: [
+              ...initialData.organization.features,
+              'dashboards-top-level-filter',
+            ],
+          }}
+          params={{orgId: 'org-slug', templateId: 'default-template'}}
+          router={initialData.router}
+          location={{
+            ...initialData.router.location,
+            query: {
+              ...initialData.router.location.query,
+              statsPeriod: '7d',
+              project: [2],
+              environment: ['alpha', 'beta'],
+            },
+          }}
+        />,
+        {
+          context: initialData.routerContext,
+          organization: initialData.organization,
+        }
+      );
+
+      userEvent.click(await screen.findByText('Add Dashboard'));
+      expect(mockPOST).toHaveBeenCalledWith(
+        '/organizations/org-slug/dashboards/',
+        expect.objectContaining({
+          data: expect.objectContaining({
+            projects: [2],
+            environment: ['alpha', 'beta'],
+            period: '7d',
+          }),
+        })
+      );
+    });
   });
 });