Browse Source

feat(discover): Allow selecting custom performance metrics in Discover Column Edit Modal (#38071)

Adds ability to select custom performance metrics in discover columns modal
edwardgou-sentry 2 years ago
parent
commit
058c77b97b

+ 71 - 65
static/app/views/eventsV2/results.tsx

@@ -32,6 +32,7 @@ import space from 'sentry/styles/space';
 import {Organization, PageFilters, SavedQuery} from 'sentry/types';
 import {defined, generateQueryWithTag} from 'sentry/utils';
 import {trackAnalyticsEvent} from 'sentry/utils/analytics';
+import {CustomMeasurementsProvider} from 'sentry/utils/customMeasurements/customMeasurementsProvider';
 import EventView, {isAPIPayloadSimilar} from 'sentry/utils/discover/eventView';
 import {formatTagKey, generateAggregateFields} from 'sentry/utils/discover/fields';
 import {
@@ -489,7 +490,7 @@ class Results extends Component<Props, State> {
   }
 
   render() {
-    const {organization, location, router} = this.props;
+    const {organization, location, router, selection} = this.props;
     const {
       eventView,
       error,
@@ -519,73 +520,78 @@ class Results extends Component<Props, State> {
               router={router}
             />
             <Layout.Body>
-              <Top fullWidth>
-                {this.renderMetricsFallbackBanner()}
-                {this.renderError(error)}
-                <StyledPageFilterBar condensed>
-                  <ProjectPageFilter />
-                  <EnvironmentPageFilter />
-                  <DatePageFilter alignDropdown="left" />
-                </StyledPageFilterBar>
-                <StyledSearchBar
-                  searchSource="eventsv2"
-                  organization={organization}
-                  projectIds={eventView.project}
-                  query={query}
-                  fields={fields}
-                  onSearch={this.handleSearch}
-                  maxQueryLength={MAX_QUERY_LENGTH}
-                />
-                <ResultsChart
-                  router={router}
-                  organization={organization}
-                  eventView={eventView}
-                  location={location}
-                  onAxisChange={this.handleYAxisChange}
-                  onDisplayChange={this.handleDisplayChange}
-                  onTopEventsChange={this.handleTopEventsChange}
-                  total={totalValues}
-                  confirmedQuery={confirmedQuery}
-                  yAxis={yAxisArray}
-                />
-              </Top>
-              <Layout.Main fullWidth={!showTags}>
-                <Table
-                  organization={organization}
-                  eventView={eventView}
-                  location={location}
-                  title={title}
-                  setError={this.setError}
-                  onChangeShowTags={this.handleChangeShowTags}
-                  showTags={showTags}
-                  confirmedQuery={confirmedQuery}
-                />
-              </Layout.Main>
-              {showTags ? this.renderTagsTable() : null}
-              <Confirm
-                priority="primary"
-                header={<strong>{t('May lead to thumb twiddling')}</strong>}
-                confirmText={t('Do it')}
-                cancelText={t('Nevermind')}
-                onConfirm={this.handleConfirmed}
-                onCancel={this.handleCancelled}
-                message={
-                  <p>
-                    {tct(
-                      `You've created a query that will search for events made
+              <CustomMeasurementsProvider
+                organization={organization}
+                selection={selection}
+              >
+                <Top fullWidth>
+                  {this.renderMetricsFallbackBanner()}
+                  {this.renderError(error)}
+                  <StyledPageFilterBar condensed>
+                    <ProjectPageFilter />
+                    <EnvironmentPageFilter />
+                    <DatePageFilter alignDropdown="left" />
+                  </StyledPageFilterBar>
+                  <StyledSearchBar
+                    searchSource="eventsv2"
+                    organization={organization}
+                    projectIds={eventView.project}
+                    query={query}
+                    fields={fields}
+                    onSearch={this.handleSearch}
+                    maxQueryLength={MAX_QUERY_LENGTH}
+                  />
+                  <ResultsChart
+                    router={router}
+                    organization={organization}
+                    eventView={eventView}
+                    location={location}
+                    onAxisChange={this.handleYAxisChange}
+                    onDisplayChange={this.handleDisplayChange}
+                    onTopEventsChange={this.handleTopEventsChange}
+                    total={totalValues}
+                    confirmedQuery={confirmedQuery}
+                    yAxis={yAxisArray}
+                  />
+                </Top>
+                <Layout.Main fullWidth={!showTags}>
+                  <Table
+                    organization={organization}
+                    eventView={eventView}
+                    location={location}
+                    title={title}
+                    setError={this.setError}
+                    onChangeShowTags={this.handleChangeShowTags}
+                    showTags={showTags}
+                    confirmedQuery={confirmedQuery}
+                  />
+                </Layout.Main>
+                {showTags ? this.renderTagsTable() : null}
+                <Confirm
+                  priority="primary"
+                  header={<strong>{t('May lead to thumb twiddling')}</strong>}
+                  confirmText={t('Do it')}
+                  cancelText={t('Nevermind')}
+                  onConfirm={this.handleConfirmed}
+                  onCancel={this.handleCancelled}
+                  message={
+                    <p>
+                      {tct(
+                        `You've created a query that will search for events made
                       [dayLimit:over more than 30 days] for [projectLimit:more than 10 projects].
                       A lot has happened during that time, so this might take awhile.
                       Are you sure you want to do this?`,
-                      {
-                        dayLimit: <strong />,
-                        projectLimit: <strong />,
-                      }
-                    )}
-                  </p>
-                }
-              >
-                {this.setOpenFunction}
-              </Confirm>
+                        {
+                          dayLimit: <strong />,
+                          projectLimit: <strong />,
+                        }
+                      )}
+                    </p>
+                  }
+                >
+                  {this.setOpenFunction}
+                </Confirm>
+              </CustomMeasurementsProvider>
             </Layout.Body>
           </NoProjectMessage>
         </StyledPageContent>

+ 11 - 0
static/app/views/eventsV2/table/columnEditModal.tsx

@@ -11,6 +11,7 @@ import {t, tct} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {Organization} from 'sentry/types';
 import {trackAnalyticsEvent} from 'sentry/utils/analytics';
+import {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
 import {Column} from 'sentry/utils/discover/fields';
 import theme from 'sentry/utils/theme';
 import useTags from 'sentry/utils/useTags';
@@ -24,6 +25,7 @@ type Props = {
   // Fired when column selections have been applied.
   onApply: (columns: Column[]) => void;
   organization: Organization;
+  customMeasurements?: CustomMeasurementCollection;
   spanOperationBreakdownKeys?: string[];
 } & ModalRenderProps;
 
@@ -37,6 +39,7 @@ function ColumnEditModal(props: Props) {
     organization,
     onApply,
     closeModal,
+    customMeasurements,
   } = props;
 
   // Only run once for each organization.id.
@@ -63,6 +66,14 @@ function ColumnEditModal(props: Props) {
     tagKeys,
     measurementKeys,
     spanOperationBreakdownKeys,
+    customMeasurements:
+      organization.features.includes('dashboards-mep') ||
+      organization.features.includes('mep-rollout-flag')
+        ? Object.values(customMeasurements ?? {}).map(({key, functions}) => ({
+            key,
+            functions,
+          }))
+        : undefined,
   });
   return (
     <Fragment>

+ 16 - 10
static/app/views/eventsV2/table/index.tsx

@@ -8,6 +8,7 @@ import Pagination from 'sentry/components/pagination';
 import {t} from 'sentry/locale';
 import {Organization} from 'sentry/types';
 import {metric, trackAnalyticsEvent} from 'sentry/utils/analytics';
+import {CustomMeasurementsContext} from 'sentry/utils/customMeasurements/customMeasurementsContext';
 import {TableData} from 'sentry/utils/discover/discoverQuery';
 import EventView, {
   isAPIPayloadSimilar,
@@ -192,16 +193,21 @@ class Table extends PureComponent<TableProps, TableState> {
             const measurementKeys = Object.values(measurements).map(({key}) => key);
 
             return (
-              <TableView
-                {...this.props}
-                isLoading={isLoading}
-                isFirstPage={isFirstPage}
-                error={error}
-                eventView={eventView}
-                tableData={tableData}
-                measurementKeys={measurementKeys}
-                spanOperationBreakdownKeys={SPAN_OP_BREAKDOWN_FIELDS}
-              />
+              <CustomMeasurementsContext.Consumer>
+                {contextValue => (
+                  <TableView
+                    {...this.props}
+                    isLoading={isLoading}
+                    isFirstPage={isFirstPage}
+                    error={error}
+                    eventView={eventView}
+                    tableData={tableData}
+                    measurementKeys={measurementKeys}
+                    spanOperationBreakdownKeys={SPAN_OP_BREAKDOWN_FIELDS}
+                    customMeasurements={contextValue?.customMeasurements ?? undefined}
+                  />
+                )}
+              </CustomMeasurementsContext.Consumer>
             );
           }}
         </Measurements>

+ 10 - 2
static/app/views/eventsV2/table/tableView.tsx

@@ -18,6 +18,7 @@ import {t} from 'sentry/locale';
 import {Organization, Project} from 'sentry/types';
 import {defined} from 'sentry/utils';
 import {trackAnalyticsEvent} from 'sentry/utils/analytics';
+import {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
 import {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery';
 import EventView, {
   isFieldSortable,
@@ -65,6 +66,7 @@ export type TableViewProps = {
   tableData: TableData | null | undefined;
 
   title: string;
+  customMeasurements?: CustomMeasurementCollection;
   spanOperationBreakdownKeys?: string[];
 };
 
@@ -329,8 +331,13 @@ class TableView extends Component<TableViewProps> {
   };
 
   handleEditColumns = () => {
-    const {organization, eventView, measurementKeys, spanOperationBreakdownKeys} =
-      this.props;
+    const {
+      organization,
+      eventView,
+      measurementKeys,
+      spanOperationBreakdownKeys,
+      customMeasurements,
+    } = this.props;
 
     const hasBreakdownFeature = organization.features.includes(
       'performance-ops-breakdown'
@@ -347,6 +354,7 @@ class TableView extends Component<TableViewProps> {
           }
           columns={eventView.getColumns().map(col => col.column)}
           onApply={this.handleUpdateColumns}
+          customMeasurements={customMeasurements}
         />
       ),
       {modalCss, backdrop: 'static'}

+ 41 - 2
tests/js/spec/views/eventsV2/table/columnEditModal.spec.js → tests/js/spec/views/eventsV2/table/columnEditModal.spec.jsx

@@ -1,5 +1,6 @@
 import {enforceActOnUseLegacyStoreHook, mountWithTheme} from 'sentry-test/enzyme';
 import {initializeOrg} from 'sentry-test/initializeOrg';
+import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 import {changeInputValue, openMenu, selectByLabel} from 'sentry-test/select-new';
 
 import TagStore from 'sentry/stores/tagStore';
@@ -7,7 +8,7 @@ import ColumnEditModal from 'sentry/views/eventsV2/table/columnEditModal';
 
 const stubEl = props => <div>{props.children}</div>;
 
-function mountModal({columns, onApply}, initialData) {
+function mountModal({columns, onApply, customMeasurements}, initialData) {
   return mountWithTheme(
     <ColumnEditModal
       Header={stubEl}
@@ -17,6 +18,7 @@ function mountModal({columns, onApply}, initialData) {
       columns={columns}
       onApply={onApply}
       closeModal={() => void 0}
+      customMeasurements={customMeasurements}
     />,
     initialData.routerContext
   );
@@ -35,7 +37,7 @@ describe('EventsV2 -> ColumnEditModal', function () {
   });
   const initialData = initializeOrg({
     organization: {
-      features: ['performance-view'],
+      features: ['performance-view', 'dashboards-mep'],
     },
   });
   const columns = [
@@ -712,4 +714,41 @@ describe('EventsV2 -> ColumnEditModal', function () {
       expect(onApply).toHaveBeenCalledWith([columns[1], {kind: 'field', field: 'title'}]);
     });
   });
+
+  describe('custom performance metrics', function () {
+    it('allows selecting custom performance metrics in dropdown', function () {
+      render(
+        <ColumnEditModal
+          Header={stubEl}
+          Footer={stubEl}
+          Body={stubEl}
+          organization={initialData.organization}
+          columns={[columns[0]]}
+          onApply={() => undefined}
+          closeModal={() => undefined}
+          customMeasurements={{
+            'measurements.custom.kibibyte': {
+              key: 'measurements.custom.kibibyte',
+              name: 'measurements.custom.kibibyte',
+              functions: ['p99'],
+            },
+            'measurements.custom.minute': {
+              key: 'measurements.custom.minute',
+              name: 'measurements.custom.minute',
+              functions: ['p99'],
+            },
+            'measurements.custom.ratio': {
+              key: 'measurements.custom.ratio',
+              name: 'measurements.custom.ratio',
+              functions: ['p99'],
+            },
+          }}
+        />
+      );
+      expect(screen.getByText('event.type')).toBeInTheDocument();
+      userEvent.click(screen.getByText('event.type'));
+      userEvent.type(screen.getAllByText('event.type')[0], 'custom');
+      expect(screen.getByText('measurements.custom.kibibyte')).toBeInTheDocument();
+    });
+  });
 });