Browse Source

feat(new-widget-builder-experience): Add to Dashboard modal saving (#33461)

Nar Saynorath 2 years ago
parent
commit
a73972b366

+ 3 - 2
static/app/components/modals/addDashboardWidgetModal.tsx

@@ -58,6 +58,7 @@ import {generateReleaseWidgetFieldOptions} from 'sentry/views/dashboardsV2/widge
 import {
   getMetricFields,
   mapErrors,
+  NEW_DASHBOARD_ID,
   normalizeQueries,
 } from 'sentry/views/dashboardsV2/widgetBuilder/utils';
 import WidgetCard from 'sentry/views/dashboardsV2/widgetCard';
@@ -284,7 +285,7 @@ class AddDashboardWidgetModal extends React.Component<Props, State> {
       !(
         dashboards.find(({title, id}) => {
           return title === selectedDashboard?.label && id === selectedDashboard?.value;
-        }) || selectedDashboard.value === 'new'
+        }) || selectedDashboard.value === NEW_DASHBOARD_ID
       )
     ) {
       errors.dashboard = t('This field may not be blank');
@@ -326,7 +327,7 @@ class AddDashboardWidgetModal extends React.Component<Props, State> {
         organization,
       });
 
-      if (selectedDashboard.value === 'new') {
+      if (selectedDashboard.value === NEW_DASHBOARD_ID) {
         browserHistory.push({
           pathname: `/organizations/${organization.slug}/dashboards/new/`,
           query: pathQuery,

+ 68 - 8
static/app/components/modals/widgetBuilder/addToDashboardModal.tsx

@@ -1,9 +1,16 @@
 import {Fragment, useEffect, useState} from 'react';
+import {InjectedRouter} from 'react-router';
 import {OptionProps} from 'react-select';
 import {css} from '@emotion/react';
 import styled from '@emotion/styled';
-
-import {fetchDashboards} from 'sentry/actionCreators/dashboards';
+import {Query} from 'history';
+
+import {
+  fetchDashboard,
+  fetchDashboards,
+  updateDashboard,
+} from 'sentry/actionCreators/dashboards';
+import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
 import {ModalRenderProps} from 'sentry/actionCreators/modal';
 import Button from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
@@ -12,15 +19,37 @@ import SelectOption from 'sentry/components/forms/selectOption';
 import Tooltip from 'sentry/components/tooltip';
 import {t, tct} from 'sentry/locale';
 import space from 'sentry/styles/space';
-import {Organization, PageFilters, SelectValue} from 'sentry/types';
+import {DateString, Organization, PageFilters, SelectValue} from 'sentry/types';
+import handleXhrErrorResponse from 'sentry/utils/handleXhrErrorResponse';
 import useApi from 'sentry/utils/useApi';
-import {DashboardListItem, MAX_WIDGETS, Widget} from 'sentry/views/dashboardsV2/types';
+import {
+  DashboardListItem,
+  DisplayType,
+  MAX_WIDGETS,
+  Widget,
+} from 'sentry/views/dashboardsV2/types';
+import {NEW_DASHBOARD_ID} from 'sentry/views/dashboardsV2/widgetBuilder/utils';
 import WidgetCard from 'sentry/views/dashboardsV2/widgetCard';
 
+type WidgetAsQueryParams = Query & {
+  defaultTableColumns: string[];
+  defaultTitle: string;
+  defaultWidgetQuery: string;
+  displayType: DisplayType;
+  environment: string[];
+  project: number[];
+  source: string;
+  end?: DateString;
+  start?: DateString;
+  statsPeriod?: string | null;
+};
+
 export type AddToDashboardModalProps = {
   organization: Organization;
+  router: InjectedRouter;
   selection: PageFilters;
   widget: Widget;
+  widgetAsQueryParams: WidgetAsQueryParams;
 };
 
 type Props = ModalRenderProps & AddToDashboardModalProps;
@@ -31,8 +60,10 @@ function AddToDashboardModal({
   Footer,
   closeModal,
   organization,
+  router,
   selection,
   widget,
+  widgetAsQueryParams,
 }: Props) {
   const api = useApi();
   const [dashboards, setDashboards] = useState<DashboardListItem[] | null>(null);
@@ -43,13 +74,42 @@ function AddToDashboardModal({
   }, []);
 
   function handleGoToBuilder() {
+    const pathname =
+      selectedDashboardId === NEW_DASHBOARD_ID
+        ? `/organizations/${organization.slug}/dashboards/new/widget/new/`
+        : `/organizations/${organization.slug}/dashboard/${selectedDashboardId}/widget/new/`;
+
+    router.push({
+      pathname,
+      query: widgetAsQueryParams,
+    });
     closeModal();
-    return;
   }
 
-  function handleAddAndStayInDiscover() {
-    closeModal();
-    return;
+  async function handleAddAndStayInDiscover() {
+    if (selectedDashboardId === null) {
+      return;
+    }
+
+    try {
+      const dashboard = await fetchDashboard(api, organization.slug, selectedDashboardId);
+      const newDashboard = {
+        ...dashboard,
+        widgets: [
+          ...dashboard.widgets,
+          {...widget, title: widget.title === '' ? t('All Events') : widget.title},
+        ],
+      };
+
+      await updateDashboard(api, organization.slug, newDashboard);
+
+      closeModal();
+      addSuccessMessage(t('Successfully added widget to dashboard'));
+    } catch (e) {
+      const errorMessage = t('Unable to add widget to dashboard');
+      handleXhrErrorResponse(errorMessage)(e);
+      addErrorMessage(errorMessage);
+    }
   }
 
   const canSubmit = selectedDashboardId !== null;

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

@@ -110,8 +110,13 @@ class DashboardDetail extends Component<Props, State> {
     this.checkIfShouldMountWidgetViewerModal();
   }
 
-  componentDidUpdate() {
+  componentDidUpdate(prevProps: Props) {
     this.checkIfShouldMountWidgetViewerModal();
+
+    if (prevProps.initialState !== this.props.initialState) {
+      // Widget builder can toggle Edit state when saving
+      this.setState({dashboardState: this.props.initialState});
+    }
   }
 
   componentWillUnmount() {

+ 4 - 2
static/app/views/dashboardsV2/view.tsx

@@ -36,6 +36,7 @@ function ViewEditDashboard(props: Props) {
   const dashboardId = params.dashboardId;
   const orgSlug = organization.slug;
   const [newWidget, setNewWidget] = useState<Widget | undefined>();
+  const [dashboardInitialState, setDashboardInitialState] = useState(DashboardState.VIEW);
 
   useEffect(() => {
     if (dashboardId && dashboardId !== 'default-overview') {
@@ -46,12 +47,13 @@ function ViewEditDashboard(props: Props) {
     setNewWidget(constructedWidget);
     // Clean up url after constructing widget from query string, only allow GHS params
     if (constructedWidget) {
+      setDashboardInitialState(DashboardState.EDIT);
       browserHistory.replace({
         pathname: location.pathname,
         query: pick(location.query, ALLOWED_PARAMS),
       });
     }
-  }, [api, orgSlug, dashboardId, location.query]);
+  }, [api, orgSlug, dashboardId, location.pathname]);
 
   return (
     <DashboardBasicFeature organization={organization}>
@@ -68,7 +70,7 @@ function ViewEditDashboard(props: Props) {
             <ErrorBoundary>
               <DashboardDetail
                 {...props}
-                initialState={newWidget ? DashboardState.EDIT : DashboardState.VIEW}
+                initialState={dashboardInitialState}
                 dashboard={dashboard}
                 dashboards={dashboards}
                 onDashboardUpdate={onDashboardUpdate}

+ 1 - 6
static/app/views/dashboardsV2/widgetBuilder/buildSteps/visualizationStep.tsx

@@ -31,13 +31,8 @@ export function VisualizationStep({
   displayType,
   error,
   onChange,
-  widgetBuilderNewDesign,
   widget,
 }: Props) {
-  const options = widgetBuilderNewDesign
-    ? Object.keys(displayTypes).filter(key => key !== DisplayType.TOP_N)
-    : Object.keys(displayTypes);
-
   return (
     <BuildStep
       title={t('Choose your visualization')}
@@ -48,7 +43,7 @@ export function VisualizationStep({
       <Field error={error} inline={false} flexibleControlStateSize stacked>
         <SelectControl
           name="displayType"
-          options={options.map(value => ({
+          options={Object.keys(displayTypes).map(value => ({
             label: displayTypes[value],
             value,
           }))}

+ 3 - 0
static/app/views/dashboardsV2/widgetBuilder/utils.tsx

@@ -29,6 +29,9 @@ import {FlatValidationError, ValidationError} from '../utils';
 export const DEFAULT_RESULTS_LIMIT = 5;
 export const RESULTS_LIMIT = 10;
 
+// Both dashboards and widgets use the 'new' keyword when creating
+export const NEW_DASHBOARD_ID = 'new';
+
 export enum DataSet {
   EVENTS = 'events',
   ISSUES = 'issues',

+ 14 - 6
static/app/views/dashboardsV2/widgetBuilder/widgetBuilder.tsx

@@ -78,13 +78,11 @@ import {
   getMetricFields,
   getParsedDefaultWidgetQuery,
   mapErrors,
+  NEW_DASHBOARD_ID,
   normalizeQueries,
 } from './utils';
 import {WidgetLibrary} from './widgetLibrary';
 
-// Both dashboards and widgets use the 'new' keyword when creating
-const NEW_DASHBOARD_ID = 'new';
-
 function getDataSetQuery(widgetBuilderNewDesign: boolean): Record<DataSet, WidgetQuery> {
   return {
     [DataSet.EVENTS]: {
@@ -295,6 +293,16 @@ function WidgetBuilder({
     if (notDashboardsOrigin) {
       fetchDashboards();
     }
+
+    if (widgetBuilderNewDesign) {
+      setState(prevState => ({
+        ...prevState,
+        selectedDashboard: {
+          label: dashboard.title,
+          value: dashboard.id || NEW_DASHBOARD_ID,
+        },
+      }));
+    }
   }, [source]);
 
   useEffect(() => {
@@ -779,12 +787,12 @@ function WidgetBuilder({
 
     try {
       const dashboards = await promise;
-      setState({...state, dashboards, loading: false});
+      setState(prevState => ({...prevState, dashboards, loading: false}));
     } catch (error) {
       const errorMessage = t('Unable to fetch dashboards');
       addErrorMessage(errorMessage);
       handleXhrErrorResponse(errorMessage)(error);
-      setState({...state, loading: false});
+      setState(prevState => ({...prevState, loading: false}));
     }
   }
 
@@ -1014,7 +1022,7 @@ function WidgetBuilder({
                         widgetType={widgetType}
                       />
                     )}
-                    {notDashboardsOrigin && (
+                    {notDashboardsOrigin && !widgetBuilderNewDesign && (
                       <DashboardStep
                         error={state.errors?.dashboard}
                         dashboards={state.dashboards}

+ 1 - 0
static/app/views/eventsV2/data.tsx

@@ -50,6 +50,7 @@ export const WEB_VITALS_VIEWS: Readonly<Array<NewQuery>> = [
     projects: [],
     version: 2,
     range: '24h',
+    yAxis: ['epm()'],
   },
 ];
 

+ 6 - 4
static/app/views/eventsV2/queryList.tsx

@@ -134,7 +134,7 @@ class QueryList extends React.Component<Props> {
   }
 
   renderPrebuiltQueries() {
-    const {location, organization, savedQuerySearchQuery} = this.props;
+    const {location, organization, savedQuerySearchQuery, router} = this.props;
     const views = getPrebuiltQueries(organization);
 
     const hasSearchQuery =
@@ -184,6 +184,7 @@ class QueryList extends React.Component<Props> {
                     query: view,
                     organization,
                     yAxis: view?.yAxis,
+                    router,
                   }),
               }),
         },
@@ -228,7 +229,7 @@ class QueryList extends React.Component<Props> {
   }
 
   renderSavedQueries() {
-    const {savedQueries, location, organization} = this.props;
+    const {savedQueries, location, organization, router} = this.props;
 
     if (!savedQueries || !Array.isArray(savedQueries) || savedQueries.length === 0) {
       return [];
@@ -259,7 +260,7 @@ class QueryList extends React.Component<Props> {
                         eventView,
                         query: savedQuery,
                         organization,
-                        yAxis: savedQuery?.yAxis,
+                        yAxis: savedQuery?.yAxis ?? eventView.yAxis,
                         location,
                       }),
                     }
@@ -269,7 +270,8 @@ class QueryList extends React.Component<Props> {
                           eventView,
                           query: savedQuery,
                           organization,
-                          yAxis: savedQuery?.yAxis,
+                          yAxis: savedQuery?.yAxis ?? eventView.yAxis,
+                          router,
                         }),
                     }),
               },

+ 2 - 1
static/app/views/eventsV2/savedQuery/index.tsx

@@ -342,7 +342,7 @@ class SavedQueryButtonGroup extends React.PureComponent<Props, State> {
   }
 
   renderButtonAddToDashboard() {
-    const {organization, location, eventView, savedQuery, yAxis} = this.props;
+    const {organization, location, eventView, savedQuery, yAxis, router} = this.props;
     return (
       <Button
         key="add-dashboard-widget-from-discover"
@@ -365,6 +365,7 @@ class SavedQueryButtonGroup extends React.PureComponent<Props, State> {
                   eventView,
                   query: savedQuery,
                   yAxis,
+                  router,
                 }),
             })}
       >

Some files were not shown because too many files changed in this diff