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

ref(dashboards): Add customFieldRenderer and fieldHeaderMap to config (#35397)

Nar Saynorath 2 лет назад
Родитель
Сommit
49ce55a953

+ 5 - 45
static/app/components/charts/simpleTableChart.tsx

@@ -2,28 +2,22 @@ import {Fragment} from 'react';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
-import Link from 'sentry/components/links/link';
 import PanelTable, {
   PanelTableHeader,
   PanelTableProps,
 } from 'sentry/components/panels/panelTable';
 import Tooltip from 'sentry/components/tooltip';
 import Truncate from 'sentry/components/truncate';
-import {t} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {Organization} from 'sentry/types';
 import {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery';
 import EventView, {MetaType} from 'sentry/utils/discover/eventView';
 import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
 import {fieldAlignment} from 'sentry/utils/discover/fields';
-import {
-  eventDetailsRouteWithEventView,
-  generateEventSlug,
-} from 'sentry/utils/discover/urls';
 import withOrganization from 'sentry/utils/withOrganization';
+import {ContextualProps} from 'sentry/views/dashboardsV2/datasetConfig/base';
 import TopResultsIndicator from 'sentry/views/eventsV2/table/topResultsIndicator';
 import {decodeColumnOrder} from 'sentry/views/eventsV2/utils';
-import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
 
 type Props = {
   eventView: EventView;
@@ -38,7 +32,8 @@ type Props = {
   fieldHeaderMap?: Record<string, string>;
   getCustomFieldRenderer?: (
     field: string,
-    meta: MetaType
+    meta: MetaType,
+    contextualProps?: ContextualProps
   ) => ReturnType<typeof getFieldRenderer> | null;
   loader?: PanelTableProps['loader'];
   metadata?: TableData['meta'];
@@ -71,50 +66,15 @@ function SimpleTableChart({
   ) {
     return columns.map((column, columnIndex) => {
       const fieldRenderer =
-        getCustomFieldRenderer?.(column.key, tableMeta) ??
+        getCustomFieldRenderer?.(column.key, tableMeta, {organization}) ??
         getFieldRenderer(column.key, tableMeta);
-      let rendered = fieldRenderer(row, {organization, location});
-      if (column.key === 'id') {
-        const eventSlug = generateEventSlug(row);
 
-        const target = eventDetailsRouteWithEventView({
-          orgSlug: organization.slug,
-          eventSlug,
-          eventView,
-        });
-
-        rendered = (
-          <Tooltip title={t('View Event')}>
-            <Link data-test-id="view-event" to={target}>
-              {rendered}
-            </Link>
-          </Tooltip>
-        );
-      } else if (column.key === 'trace') {
-        const dateSelection = eventView.normalizeDateSelection(location);
-        if (row.trace) {
-          const target = getTraceDetailsUrl(
-            organization,
-            String(row.trace),
-            dateSelection,
-            {}
-          );
-
-          rendered = (
-            <Tooltip title={t('View Trace')}>
-              <Link data-test-id="view-trace" to={target}>
-                {rendered}
-              </Link>
-            </Tooltip>
-          );
-        }
-      }
       return (
         <TableCell key={`${index}-${columnIndex}:${column.name}`}>
           {topResultsIndicators && columnIndex === 0 && (
             <TopResultsIndicator count={topResultsIndicators} index={index} />
           )}
-          {rendered}
+          {fieldRenderer(row, {organization, location, eventView})}
         </TableCell>
       );
     });

+ 1 - 1
static/app/utils/discover/fieldRenderers.tsx

@@ -54,7 +54,7 @@ import TeamKeyTransactionField from './teamKeyTransactionField';
 /**
  * Types, functions and definitions for rendering fields in discover results.
  */
-type RenderFunctionBaggage = {
+export type RenderFunctionBaggage = {
   location: Location;
   organization: Organization;
   eventView?: EventView;

+ 15 - 0
static/app/views/dashboardsV2/datasetConfig/base.tsx

@@ -1,6 +1,8 @@
 import {OrganizationSummary, PageFilters} from 'sentry/types';
 import {Series} from 'sentry/types/echarts';
 import {TableData} from 'sentry/utils/discover/discoverQuery';
+import {MetaType} from 'sentry/utils/discover/eventView';
+import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
 
 import {WidgetQuery, WidgetType} from '../types';
 
@@ -23,6 +25,19 @@ export interface DatasetConfig<SeriesResponse, TableResponse> {
     widgetQuery: WidgetQuery,
     contextualProps?: ContextualProps
   ) => TableData;
+  /**
+   * Used for mapping column names to more desirable
+   * values in tables.
+   */
+  fieldHeaderMap?: Record<string, string>;
+  /**
+   * Used to select custom renderers for field types.
+   */
+  getCustomFieldRenderer?: (
+    field: string,
+    meta: MetaType,
+    contextualProps?: ContextualProps
+  ) => ReturnType<typeof getFieldRenderer> | null;
   /**
    * Transforms timeseries API results into series data that is
    * ingestable by echarts for timeseries visualizations.

+ 79 - 0
static/app/views/dashboardsV2/datasetConfig/errorsAndTransactions.tsx

@@ -1,6 +1,21 @@
+import Link from 'sentry/components/links/link';
+import Tooltip from 'sentry/components/tooltip';
+import {t} from 'sentry/locale';
 import {EventsStats, MultiSeriesEventsStats} from 'sentry/types';
 import {Series} from 'sentry/types/echarts';
 import {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
+import {MetaType} from 'sentry/utils/discover/eventView';
+import {
+  getFieldRenderer,
+  RenderFunctionBaggage,
+} from 'sentry/utils/discover/fieldRenderers';
+import {Container} from 'sentry/utils/discover/styles';
+import {
+  eventDetailsRouteWithEventView,
+  generateEventSlug,
+} from 'sentry/utils/discover/urls';
+import {getShortEventId} from 'sentry/utils/events';
+import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
 
 import {WidgetQuery} from '../types';
 
@@ -10,6 +25,7 @@ export const ErrorsAndTransactionsConfig: DatasetConfig<
   EventsStats | MultiSeriesEventsStats,
   TableData | EventsTableData
 > = {
+  getCustomFieldRenderer: getCustomEventsFieldRenderer,
   transformSeries: (_data: EventsStats | MultiSeriesEventsStats) => {
     return [] as Series[];
   },
@@ -36,3 +52,66 @@ function transformEventsResponseToTable(
   }
   return tableData as TableData;
 }
+
+function renderEventIdAsLinkable(data, {eventView, organization}: RenderFunctionBaggage) {
+  const id: string | unknown = data?.id;
+  if (!eventView || typeof id !== 'string') {
+    return null;
+  }
+
+  const eventSlug = generateEventSlug(data);
+
+  const target = eventDetailsRouteWithEventView({
+    orgSlug: organization.slug,
+    eventSlug,
+    eventView,
+  });
+
+  return (
+    <Tooltip title={t('View Event')}>
+      <Link data-test-id="view-event" to={target}>
+        <Container>{getShortEventId(id)}</Container>
+      </Link>
+    </Tooltip>
+  );
+}
+
+function renderTraceAsLinkable(
+  data,
+  {eventView, organization, location}: RenderFunctionBaggage
+) {
+  const id: string | unknown = data?.trace;
+  if (!eventView || typeof id !== 'string') {
+    return null;
+  }
+  const dateSelection = eventView.normalizeDateSelection(location);
+  const target = getTraceDetailsUrl(organization, String(data.trace), dateSelection, {});
+
+  return (
+    <Tooltip title={t('View Trace')}>
+      <Link data-test-id="view-trace" to={target}>
+        <Container>{getShortEventId(id)}</Container>
+      </Link>
+    </Tooltip>
+  );
+}
+
+export function getCustomEventsFieldRenderer(
+  field: string,
+  meta: MetaType,
+  contextualProps?: ContextualProps
+) {
+  const isAlias = !contextualProps?.organization?.features.includes(
+    'discover-frontend-use-events-endpoint'
+  );
+
+  if (field === 'id') {
+    return renderEventIdAsLinkable;
+  }
+
+  if (field === 'trace') {
+    return renderTraceAsLinkable;
+  }
+
+  return getFieldRenderer(field, meta, isAlias);
+}

+ 4 - 0
static/app/views/dashboardsV2/datasetConfig/issues.tsx

@@ -1,15 +1,19 @@
 import GroupStore from 'sentry/stores/groupStore';
 import {Group} from 'sentry/types';
+import {getIssueFieldRenderer} from 'sentry/utils/dashboards/issueFieldRenderers';
 import {getUtcDateString} from 'sentry/utils/dates';
 import {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery';
 import {queryToObj} from 'sentry/utils/stream';
 import {DISCOVER_EXCLUSION_FIELDS} from 'sentry/views/issueList/utils';
 
 import {WidgetQuery} from '../types';
+import {ISSUE_FIELD_TO_HEADER_MAP} from '../widgetBuilder/issueWidget/fields';
 
 import {ContextualProps, DatasetConfig} from './base';
 
 export const IssuesConfig: DatasetConfig<never, Group[]> = {
+  getCustomFieldRenderer: getIssueFieldRenderer,
+  fieldHeaderMap: ISSUE_FIELD_TO_HEADER_MAP,
   transformTable: transformIssuesResponseToTable,
 };
 

+ 2 - 0
static/app/views/dashboardsV2/datasetConfig/releases.tsx

@@ -3,6 +3,7 @@ import omit from 'lodash/omit';
 import {MetricsApiResponse, SessionApiResponse} from 'sentry/types';
 import {Series} from 'sentry/types/echarts';
 import {TableData} from 'sentry/utils/discover/discoverQuery';
+import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
 
 import {WidgetQuery} from '../types';
 import {resolveDerivedStatusFields} from '../widgetCard/releaseWidgetQueries';
@@ -18,6 +19,7 @@ export const ReleasesConfig: DatasetConfig<
   SessionApiResponse | MetricsApiResponse,
   SessionApiResponse | MetricsApiResponse
 > = {
+  getCustomFieldRenderer: (field, meta) => getFieldRenderer(field, meta, false),
   transformSeries: (_data: SessionApiResponse | MetricsApiResponse) => {
     return [] as Series[];
   },

+ 5 - 5
static/app/views/dashboardsV2/widgetCard/chart.tsx

@@ -25,7 +25,7 @@ import space from 'sentry/styles/space';
 import {Organization, PageFilters} from 'sentry/types';
 import {EChartDataZoomHandler, EChartEventHandler} from 'sentry/types/echarts';
 import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
-import {getFieldFormatter, getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
+import {getFieldFormatter} from 'sentry/utils/discover/fieldRenderers';
 import {
   getAggregateArg,
   getEquation,
@@ -39,6 +39,7 @@ import getDynamicText from 'sentry/utils/getDynamicText';
 import {Theme} from 'sentry/utils/theme';
 import {eventViewFromWidget} from 'sentry/views/dashboardsV2/utils';
 
+import {getDatasetConfig} from '../datasetConfig/base';
 import {DisplayType, Widget, WidgetType} from '../types';
 
 import WidgetQueries from './widgetQueries';
@@ -129,9 +130,6 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
     tableResults,
   }: TableResultProps): React.ReactNode {
     const {location, widget, organization, selection} = this.props;
-    const isAlias =
-      !organization.features.includes('discover-frontend-use-events-endpoint') &&
-      widget.widgetType !== WidgetType.RELEASE;
     if (errorMessage) {
       return (
         <StyledErrorPanel>
@@ -145,6 +143,8 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
       return <LoadingPlaceholder />;
     }
 
+    const datasetConfig = getDatasetConfig(widget.widgetType);
+
     return tableResults.map((result, i) => {
       const fields = widget.queries[i]?.fields?.map(stripDerivedMetricsPrefix) ?? [];
       const fieldAliases = widget.queries[i]?.fieldAliases ?? [];
@@ -169,7 +169,7 @@ class WidgetCardChart extends Component<WidgetCardChartProps, State> {
           data={result.data}
           organization={organization}
           stickyHeaders
-          getCustomFieldRenderer={(field, meta) => getFieldRenderer(field, meta, isAlias)}
+          getCustomFieldRenderer={datasetConfig.getCustomFieldRenderer}
         />
       );
     });

+ 7 - 8
static/app/views/dashboardsV2/widgetCard/issueWidgetCard.tsx

@@ -8,15 +8,12 @@ import {IconWarning} from 'sentry/icons';
 import space from 'sentry/styles/space';
 import {Organization, PageFilters} from 'sentry/types';
 import {defined} from 'sentry/utils';
-import {getIssueFieldRenderer} from 'sentry/utils/dashboards/issueFieldRenderers';
 import {TableDataRow} from 'sentry/utils/discover/discoverQuery';
 import {eventViewFromWidget} from 'sentry/views/dashboardsV2/utils';
 
-import {Widget} from '../types';
-import {
-  ISSUE_FIELD_TO_HEADER_MAP,
-  ISSUE_FIELDS,
-} from '../widgetBuilder/issueWidget/fields';
+import {getDatasetConfig} from '../datasetConfig/base';
+import {Widget, WidgetType} from '../types';
+import {ISSUE_FIELDS} from '../widgetBuilder/issueWidget/fields';
 
 type Props = {
   loading: boolean;
@@ -37,6 +34,8 @@ export function IssueWidgetCard({
   transformedResults,
   location,
 }: Props) {
+  const datasetConfig = getDatasetConfig(WidgetType.ISSUE);
+
   if (errorMessage) {
     return (
       <ErrorPanel>
@@ -73,8 +72,8 @@ export function IssueWidgetCard({
       metadata={ISSUE_FIELDS}
       data={transformedResults}
       organization={organization}
-      getCustomFieldRenderer={getIssueFieldRenderer}
-      fieldHeaderMap={ISSUE_FIELD_TO_HEADER_MAP}
+      getCustomFieldRenderer={datasetConfig.getCustomFieldRenderer}
+      fieldHeaderMap={datasetConfig.fieldHeaderMap}
       stickyHeaders
     />
   );

+ 22 - 23
tests/js/spec/components/charts/simpleTableChart.spec.jsx → tests/js/spec/views/dashboardsV2/datasetConfig/errorsAndTransactions.spec.jsx

@@ -1,26 +1,25 @@
 import {initializeOrg} from 'sentry-test/initializeOrg';
 import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
-import SimpleTableChart from 'sentry/components/charts/simpleTableChart';
 import EventView from 'sentry/utils/discover/eventView';
+import {getCustomEventsFieldRenderer} from 'sentry/views/dashboardsV2/datasetConfig/errorsAndTransactions';
 
-describe('simpleTableChart', function () {
-  const {router, routerContext} = initializeOrg();
+describe('getCustomFieldRenderer', function () {
+  const {organization, router, routerContext} = initializeOrg();
 
   it('links trace ids to performance', async function () {
+    const customFieldRenderer = getCustomEventsFieldRenderer('trace', {});
     render(
-      <SimpleTableChart
-        data={[{trace: 'abcd'}]}
-        eventView={
-          new EventView({
+      customFieldRenderer(
+        {trace: 'abcd'},
+        {
+          organization,
+          location: router.location,
+          eventView: new EventView({
             fields: [{field: 'trace'}],
-          })
+          }),
         }
-        fields={['trace']}
-        fieldAliases={['']}
-        loading={false}
-        location={{query: {}}}
-      />,
+      ),
       {context: routerContext}
     );
     userEvent.click(await screen.findByText('abcd'));
@@ -36,22 +35,22 @@ describe('simpleTableChart', function () {
 
   it('links event ids to event details', async function () {
     const project = TestStubs.Project();
+    const customFieldRenderer = getCustomEventsFieldRenderer('id', {});
     render(
-      <SimpleTableChart
-        data={[{id: 'defg', 'project.name': project.slug}]}
-        eventView={
-          new EventView({
+      customFieldRenderer(
+        {id: 'defg', 'project.name': project.slug},
+        {
+          organization,
+          location: router.location,
+          eventView: new EventView({
             fields: [{field: 'id'}],
             project: [project.id],
-          })
+          }),
         }
-        fields={['id']}
-        fieldAliases={['']}
-        loading={false}
-        location={{query: {}}}
-      />,
+      ),
       {context: routerContext}
     );
+
     userEvent.click(await screen.findByText('defg'));
     expect(router.push).toHaveBeenCalledWith({
       pathname: `/organizations/org-slug/discover/${project.slug}:defg/`,