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

feat(perf): Show link to docs when some web vitals data is missing (#31764)

In the Transaction Vitals view, sometimes some of the vitals details are not available, possibly due to the fact that some of these web vitals are not supported by all browsers. We should link troubleshooting docs to explain to the users in these cases why they may not be getting vitals.

Fixes VIS-740
Ash Anand 3 лет назад
Родитель
Сommit
6adfab0fa0

+ 97 - 54
static/app/views/performance/transactionSummary/transactionVitals/content.tsx

@@ -1,22 +1,28 @@
+import React from 'react';
 import {browserHistory} from 'react-router';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
+import Alert from 'sentry/components/alert';
 import Button from 'sentry/components/button';
 import DropdownControl, {DropdownItem} from 'sentry/components/dropdownControl';
 import SearchBar from 'sentry/components/events/searchBar';
 import * as Layout from 'sentry/components/layouts/thirds';
+import ExternalLink from 'sentry/components/links/externalLink';
 import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
-import {t} from 'sentry/locale';
+import {IconInfo} from 'sentry/icons';
+import {t, tct} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {Organization} from 'sentry/types';
 import {trackAnalyticsEvent} from 'sentry/utils/analytics';
 import EventView from 'sentry/utils/discover/eventView';
+import {WebVital} from 'sentry/utils/discover/fields';
 import Histogram from 'sentry/utils/performance/histogram';
 import {FILTER_OPTIONS} from 'sentry/utils/performance/histogram/constants';
+import VitalsCardsDiscoverQuery from 'sentry/utils/performance/vitals/vitalsCardsDiscoverQuery';
 import {decodeScalar} from 'sentry/utils/queryString';
 
-import {ZOOM_KEYS} from './constants';
+import {VITAL_GROUPS, ZOOM_KEYS} from './constants';
 import VitalsPanel from './vitalsPanel';
 
 type Props = {
@@ -44,63 +50,100 @@ function VitalsContent(props: Props) {
     });
   };
 
+  const allVitals = VITAL_GROUPS.reduce((keys: WebVital[], {vitals}) => {
+    return keys.concat(vitals);
+  }, []);
+
   return (
     <Histogram location={location} zoomKeys={ZOOM_KEYS}>
       {({activeFilter, handleFilterChange, handleResetView, isZoomed}) => (
         <Layout.Main fullWidth>
-          <StyledActions>
-            <StyledSearchBar
-              organization={organization}
-              projectIds={eventView.project}
-              query={query}
-              fields={eventView.fields}
-              onSearch={handleSearch}
-            />
-            <DropdownControl
-              buttonProps={{prefix: t('Outliers')}}
-              label={activeFilter.label}
-            >
-              {FILTER_OPTIONS.map(({label, value}) => (
-                <DropdownItem
-                  key={value}
-                  onSelect={(filterOption: string) => {
-                    trackAnalyticsEvent({
-                      eventKey: 'performance_views.vitals.filter_changed',
-                      eventName: 'Performance Views: Change vitals filter',
-                      organization_id: organization.id,
-                      value: filterOption,
-                    });
-                    handleFilterChange(filterOption);
-                  }}
-                  eventKey={value}
-                  isActive={value === activeFilter.value}
-                >
-                  {label}
-                </DropdownItem>
-              ))}
-            </DropdownControl>
-            <Button
-              onClick={() => {
-                trackAnalyticsEvent({
-                  eventKey: 'performance_views.vitals.reset_view',
-                  eventName: 'Performance Views: Reset vitals view',
-                  organization_id: organization.id,
-                });
-
-                handleResetView();
-              }}
-              disabled={!isZoomed}
-              data-test-id="reset-view"
-            >
-              {t('Reset View')}
-            </Button>
-          </StyledActions>
-          <VitalsPanel
-            organization={organization}
-            location={location}
+          <VitalsCardsDiscoverQuery
             eventView={eventView}
-            dataFilter={activeFilter.value}
-          />
+            orgSlug={organization.slug}
+            location={location}
+            vitals={allVitals}
+          >
+            {results => {
+              const isMissingVitalsData =
+                !results.isLoading &&
+                allVitals.some(vital => !results.vitalsData?.[vital]);
+
+              return (
+                <React.Fragment>
+                  {isMissingVitalsData && (
+                    <Alert type="info" icon={<IconInfo size="md" />}>
+                      {tct(
+                        'If this page is looking a little bare, keep in mind not all browsers support these vitals. [link]',
+                        {
+                          link: (
+                            <ExternalLink href="https://docs.sentry.io/product/performance/web-vitals/#browser-support">
+                              {t('Read more about browser support.')}
+                            </ExternalLink>
+                          ),
+                        }
+                      )}
+                    </Alert>
+                  )}
+
+                  <StyledActions>
+                    <StyledSearchBar
+                      organization={organization}
+                      projectIds={eventView.project}
+                      query={query}
+                      fields={eventView.fields}
+                      onSearch={handleSearch}
+                    />
+                    <DropdownControl
+                      buttonProps={{prefix: t('Outliers')}}
+                      label={activeFilter.label}
+                    >
+                      {FILTER_OPTIONS.map(({label, value}) => (
+                        <DropdownItem
+                          key={value}
+                          onSelect={(filterOption: string) => {
+                            trackAnalyticsEvent({
+                              eventKey: 'performance_views.vitals.filter_changed',
+                              eventName: 'Performance Views: Change vitals filter',
+                              organization_id: organization.id,
+                              value: filterOption,
+                            });
+                            handleFilterChange(filterOption);
+                          }}
+                          eventKey={value}
+                          isActive={value === activeFilter.value}
+                        >
+                          {label}
+                        </DropdownItem>
+                      ))}
+                    </DropdownControl>
+                    <Button
+                      onClick={() => {
+                        trackAnalyticsEvent({
+                          eventKey: 'performance_views.vitals.reset_view',
+                          eventName: 'Performance Views: Reset vitals view',
+                          organization_id: organization.id,
+                        });
+
+                        handleResetView();
+                      }}
+                      disabled={!isZoomed}
+                      data-test-id="reset-view"
+                    >
+                      {t('Reset View')}
+                    </Button>
+                  </StyledActions>
+                  <VitalsPanel
+                    organization={organization}
+                    location={location}
+                    eventView={eventView}
+                    dataFilter={activeFilter.value}
+                    results={results}
+                  />
+                </React.Fragment>
+              );
+            }}
+          </VitalsCardsDiscoverQuery>
         </Layout.Main>
       )}
     </Histogram>

+ 9 - 23
static/app/views/performance/transactionSummary/transactionVitals/vitalsPanel.tsx

@@ -9,9 +9,7 @@ import HistogramQuery from 'sentry/utils/performance/histogram/histogramQuery';
 import {DataFilter, HistogramData} from 'sentry/utils/performance/histogram/types';
 import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
 import {VitalGroup} from 'sentry/utils/performance/vitals/types';
-import VitalsCardDiscoverQuery, {
-  VitalData,
-} from 'sentry/utils/performance/vitals/vitalsCardsDiscoverQuery';
+import {VitalData} from 'sentry/utils/performance/vitals/vitalsCardsDiscoverQuery';
 import {decodeScalar} from 'sentry/utils/queryString';
 
 import {NUM_BUCKETS, VITAL_GROUPS} from './constants';
@@ -21,6 +19,7 @@ type Props = {
   eventView: EventView;
   location: Location;
   organization: Organization;
+  results: object;
   dataFilter?: DataFilter;
 };
 
@@ -148,30 +147,17 @@ class VitalsPanel extends Component<Props> {
   }
 
   render() {
-    const {location, organization, eventView} = this.props;
-
-    const allVitals = VITAL_GROUPS.reduce((keys: WebVital[], {vitals}) => {
-      return keys.concat(vitals);
-    }, []);
+    const {results} = this.props;
 
     return (
       <Panel>
-        <VitalsCardDiscoverQuery
-          eventView={eventView}
-          orgSlug={organization.slug}
-          location={location}
-          vitals={allVitals}
-        >
-          {results => (
-            <Fragment>
-              {VITAL_GROUPS.map(vitalGroup => (
-                <Fragment key={vitalGroup.vitals.join('')}>
-                  {this.renderVitalGroup(vitalGroup, results)}
-                </Fragment>
-              ))}
+        <Fragment>
+          {VITAL_GROUPS.map(vitalGroup => (
+            <Fragment key={vitalGroup.vitals.join('')}>
+              {this.renderVitalGroup(vitalGroup, results)}
             </Fragment>
-          )}
-        </VitalsCardDiscoverQuery>
+          ))}
+        </Fragment>
       </Panel>
     );
   }

+ 52 - 0
tests/js/spec/views/performance/transactionVitals.spec.jsx

@@ -341,5 +341,57 @@ describe('Performance > Web Vitals', function () {
         ),
       });
     });
+
+    it('renders an info alert when missing web vitals data', async function () {
+      MockApiClient.addMockResponse({
+        url: '/organizations/org-slug/events-vitals/',
+        body: {
+          'measurements.fp': {poor: 1, meh: 2, good: 3, total: 6, p75: 4567},
+          'measurements.fcp': {poor: 1, meh: 2, good: 3, total: 6, p75: 1456},
+        },
+      });
+
+      const {organization, router, routerContext} = initialize({
+        query: {
+          lcpStart: '20',
+        },
+      });
+
+      const wrapper = mountWithTheme(
+        <WrappedComponent
+          organization={organization}
+          location={router.location}
+          router={router}
+        />,
+        routerContext
+      );
+
+      await tick();
+      wrapper.update();
+
+      expect(wrapper.find('Alert')).toHaveLength(1);
+    });
+
+    it('does not render an info alert when data from all web vitals is present', async function () {
+      const {organization, router, routerContext} = initialize({
+        query: {
+          lcpStart: '20',
+        },
+      });
+
+      const wrapper = mountWithTheme(
+        <WrappedComponent
+          organization={organization}
+          location={router.location}
+          router={router}
+        />,
+        routerContext
+      );
+
+      await tick();
+      wrapper.update();
+
+      expect(wrapper.find('Alert')).toHaveLength(0);
+    });
   });
 });