Просмотр исходного кода

fix(dashboardsv2): Communicate any error messages for dashboard widget modal (#22949)

Alberto Leal 4 лет назад
Родитель
Сommit
b29ad28349

+ 6 - 0
src/sentry/static/sentry/app/components/modals/addDashboardWidgetModal.tsx

@@ -12,6 +12,7 @@ import Button from 'app/components/button';
 import ButtonBar from 'app/components/buttonBar';
 import WidgetQueryForm from 'app/components/dashboards/widgetQueryForm';
 import SelectControl from 'app/components/forms/selectControl';
+import {PanelAlert} from 'app/components/panels';
 import {t} from 'app/locale';
 import space from 'app/styles/space';
 import {GlobalSelection, Organization, TagCollection} from 'app/types';
@@ -278,6 +279,11 @@ class AddDashboardWidgetModal extends React.Component<Props, State> {
             isEditing={false}
             onDelete={() => undefined}
             onEdit={() => undefined}
+            renderErrorMessage={errorMessage =>
+              typeof errorMessage === 'string' && (
+                <PanelAlert type="error">{errorMessage}</PanelAlert>
+              )
+            }
           />
         </Body>
         <Footer>

+ 57 - 49
src/sentry/static/sentry/app/views/dashboardsV2/widgetCard.tsx

@@ -39,6 +39,7 @@ type Props = ReactRouter.WithRouterProps & {
   selection: GlobalSelection;
   onDelete: () => void;
   onEdit: () => void;
+  renderErrorMessage?: (errorMessage: string | undefined) => React.ReactNode;
 };
 
 type State = {
@@ -73,8 +74,12 @@ class WidgetCard extends React.Component<Props, State> {
     }
   }
 
-  renderVisual() {
-    const {location, router, selection, api, organization, widget} = this.props;
+  renderVisual({
+    results,
+    errorMessage,
+    loading,
+  }: Pick<WidgetQueries['state'], 'results' | 'errorMessage' | 'loading'>) {
+    const {location, router, selection, widget} = this.props;
 
     const {start, end, period} = selection.datetime;
 
@@ -131,50 +136,37 @@ class WidgetCard extends React.Component<Props, State> {
     return (
       <ChartZoom router={router} period={period} start={start} end={end}>
         {zoomRenderProps => {
-          return (
-            <WidgetQueries
-              api={api}
-              organization={organization}
-              widget={widget}
-              selection={selection}
-            >
-              {({results, error, loading}) => {
-                if (error) {
-                  return (
-                    <ErrorPanel>
-                      <IconWarning color="gray500" size="lg" />
-                    </ErrorPanel>
-                  );
-                }
+          if (errorMessage) {
+            return (
+              <ErrorPanel>
+                <IconWarning color="gray500" size="lg" />
+              </ErrorPanel>
+            );
+          }
 
-                const colors = results
-                  ? theme.charts.getColorPalette(results.length - 2)
-                  : [];
+          const colors = results ? theme.charts.getColorPalette(results.length - 2) : [];
 
-                // Create a list of series based on the order of the fields,
-                const series = results
-                  ? results.map((values, i: number) => ({
-                      ...values,
-                      color: colors[i],
-                    }))
-                  : [];
+          // Create a list of series based on the order of the fields,
+          const series = results
+            ? results.map((values, i: number) => ({
+                ...values,
+                color: colors[i],
+              }))
+            : [];
 
-                return (
-                  <TransitionChart loading={loading} reloading={loading}>
-                    <TransparentLoadingMask visible={loading} />
-                    {getDynamicText({
-                      value: this.chartComponent({
-                        ...zoomRenderProps,
-                        ...chartOptions,
-                        legend,
-                        series,
-                      }),
-                      fixed: 'Widget Chart',
-                    })}
-                  </TransitionChart>
-                );
-              }}
-            </WidgetQueries>
+          return (
+            <TransitionChart loading={loading} reloading={loading}>
+              <TransparentLoadingMask visible={loading} />
+              {getDynamicText({
+                value: this.chartComponent({
+                  ...zoomRenderProps,
+                  ...chartOptions,
+                  legend,
+                  series,
+                }),
+                fixed: 'Widget Chart',
+              })}
+            </TransitionChart>
           );
         }}
       </ChartZoom>
@@ -212,7 +204,7 @@ class WidgetCard extends React.Component<Props, State> {
   }
 
   render() {
-    const {widget} = this.props;
+    const {widget, api, organization, selection, renderErrorMessage} = this.props;
     return (
       <ErrorBoundary
         customComponent={<ErrorCard>{t('Error loading widget data')}</ErrorCard>}
@@ -233,11 +225,27 @@ class WidgetCard extends React.Component<Props, State> {
             }
           }}
         >
-          <ChartContainer>
-            <HeaderTitleLegend>{widget.title}</HeaderTitleLegend>
-            {this.renderVisual()}
-            {this.renderEditPanel()}
-          </ChartContainer>
+          <WidgetQueries
+            api={api}
+            organization={organization}
+            widget={widget}
+            selection={selection}
+          >
+            {({results, errorMessage, loading}) => {
+              return (
+                <React.Fragment>
+                  {typeof renderErrorMessage === 'function'
+                    ? renderErrorMessage(errorMessage)
+                    : null}
+                  <ChartContainer>
+                    <HeaderTitleLegend>{widget.title}</HeaderTitleLegend>
+                    {this.renderVisual({results, errorMessage, loading})}
+                    {this.renderEditPanel()}
+                  </ChartContainer>
+                </React.Fragment>
+              );
+            }}
+          </WidgetQueries>
         </StyledPanel>
       </ErrorBoundary>
     );

+ 12 - 7
src/sentry/static/sentry/app/views/dashboardsV2/widgetQueries.tsx

@@ -8,6 +8,7 @@ import {
   getInterval,
   isMultiSeriesStats,
 } from 'app/components/charts/utils';
+import {t} from 'app/locale';
 import {
   EventsStats,
   GlobalSelection,
@@ -83,11 +84,13 @@ type Props = {
   organization: Organization;
   widget: Widget;
   selection: GlobalSelection;
-  children: (props: Pick<State, 'loading' | 'error' | 'results'>) => React.ReactNode;
+  children: (
+    props: Pick<State, 'loading' | 'results' | 'errorMessage'>
+  ) => React.ReactNode;
 };
 
 type State = {
-  error: boolean;
+  errorMessage: undefined | string;
   loading: boolean;
   results: Series[];
 };
@@ -95,7 +98,7 @@ type State = {
 class WidgetQueries extends React.Component<Props, State> {
   state: State = {
     loading: true,
-    error: false,
+    errorMessage: undefined,
     results: [],
   };
 
@@ -160,20 +163,22 @@ class WidgetQueries extends React.Component<Props, State> {
           return {
             ...prevState,
             results,
+            errorMessage: undefined,
             loading: completed === promises.length ? false : true,
           };
         });
-      } catch (e) {
-        this.setState({error: true});
+      } catch (err) {
+        const errorMessage = err?.responseJSON?.detail || t('An unknown error occurred.');
+        this.setState({errorMessage});
       }
     });
   }
 
   render() {
     const {children} = this.props;
-    const {loading, results, error} = this.state;
+    const {loading, results, errorMessage} = this.state;
 
-    return children({loading, results, error});
+    return children({loading, results, errorMessage});
   }
 }