metricWidgetQueries.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {Component} from 'react';
  2. import isEqual from 'lodash/isEqual';
  3. import omit from 'lodash/omit';
  4. import type {Client} from 'sentry/api';
  5. import {isSelectionEqual} from 'sentry/components/organizations/pageFilters/utils';
  6. import type {MetricsApiResponse, Organization, PageFilters} from 'sentry/types';
  7. import type {Series} from 'sentry/types/echarts';
  8. import type {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
  9. import {TOP_N} from 'sentry/utils/discover/types';
  10. import {mapToMRIFields} from 'sentry/utils/metrics';
  11. import {MetricsConfig} from '../datasetConfig/metrics';
  12. import type {DashboardFilters, Widget} from '../types';
  13. import {DEFAULT_TABLE_LIMIT, DisplayType} from '../types';
  14. import type {
  15. GenericWidgetQueriesChildrenProps,
  16. GenericWidgetQueriesProps,
  17. } from './genericWidgetQueries';
  18. import GenericWidgetQueries from './genericWidgetQueries';
  19. type Props = {
  20. api: Client;
  21. children: (props: GenericWidgetQueriesChildrenProps) => React.ReactNode;
  22. organization: Organization;
  23. selection: PageFilters;
  24. widget: Widget;
  25. cursor?: string;
  26. dashboardFilters?: DashboardFilters;
  27. limit?: number;
  28. onDataFetched?: (results: {
  29. tableResults?: TableDataWithTitle[];
  30. timeseriesResults?: Series[];
  31. }) => void;
  32. };
  33. type State = {
  34. loading: boolean;
  35. errorMessage?: string;
  36. };
  37. class MetricWidgetQueries extends Component<Props, State> {
  38. state: State = {
  39. loading: true,
  40. errorMessage: undefined,
  41. };
  42. config = MetricsConfig;
  43. get limit() {
  44. const {limit} = this.props;
  45. switch (this.props.widget.displayType) {
  46. case DisplayType.TOP_N:
  47. return TOP_N;
  48. case DisplayType.TABLE:
  49. return limit ?? DEFAULT_TABLE_LIMIT;
  50. case DisplayType.BIG_NUMBER:
  51. return 1;
  52. default:
  53. return limit ?? 20; // TODO(dam): Can be changed to undefined once [INGEST-1079] is resolved
  54. }
  55. }
  56. customDidUpdateComparator = (
  57. prevProps: GenericWidgetQueriesProps<MetricsApiResponse, MetricsApiResponse>,
  58. nextProps: GenericWidgetQueriesProps<MetricsApiResponse, MetricsApiResponse>
  59. ) => {
  60. const {loading, limit, widget, cursor, organization, selection, dashboardFilters} =
  61. nextProps;
  62. const ignoredWidgetProps = [
  63. 'queries',
  64. 'title',
  65. 'id',
  66. 'layout',
  67. 'tempId',
  68. 'widgetType',
  69. ];
  70. const ignoredQueryProps = ['name', 'fields', 'aggregates', 'columns'];
  71. return (
  72. limit !== prevProps.limit ||
  73. organization.slug !== prevProps.organization.slug ||
  74. !isEqual(dashboardFilters, prevProps.dashboardFilters) ||
  75. !isSelectionEqual(selection, prevProps.selection) ||
  76. // If the widget changed (ignore unimportant fields, + queries as they are handled lower)
  77. !isEqual(
  78. omit(widget, ignoredWidgetProps),
  79. omit(prevProps.widget, ignoredWidgetProps)
  80. ) ||
  81. // If the queries changed (ignore unimportant name, + fields as they are handled lower)
  82. !isEqual(
  83. widget.queries.map(q => omit(q, ignoredQueryProps)),
  84. prevProps.widget.queries.map(q => omit(q, ignoredQueryProps))
  85. ) ||
  86. // If the fields changed (ignore falsy/empty fields -> they can happen after clicking on Add Overlay)
  87. !isEqual(
  88. widget.queries.flatMap(q => q.fields?.filter(field => !!field)),
  89. prevProps.widget.queries.flatMap(q => q.fields?.filter(field => !!field))
  90. ) ||
  91. !isEqual(
  92. widget.queries.flatMap(q => q.aggregates.filter(aggregate => !!aggregate)),
  93. prevProps.widget.queries.flatMap(q =>
  94. q.aggregates.filter(aggregate => !!aggregate)
  95. )
  96. ) ||
  97. !isEqual(
  98. widget.queries.flatMap(q => q.columns.filter(column => !!column)),
  99. prevProps.widget.queries.flatMap(q => q.columns.filter(column => !!column))
  100. ) ||
  101. loading !== prevProps.loading ||
  102. cursor !== prevProps.cursor
  103. );
  104. };
  105. afterFetchData = (data: MetricsApiResponse) => {
  106. const fields = this.props.widget.queries[0].aggregates;
  107. mapToMRIFields(data, fields);
  108. };
  109. render() {
  110. const {
  111. api,
  112. children,
  113. organization,
  114. selection,
  115. widget,
  116. cursor,
  117. dashboardFilters,
  118. onDataFetched,
  119. } = this.props;
  120. const config = MetricsConfig;
  121. return (
  122. <GenericWidgetQueries<MetricsApiResponse, MetricsApiResponse>
  123. config={config}
  124. api={api}
  125. organization={organization}
  126. selection={selection}
  127. widget={widget}
  128. dashboardFilters={dashboardFilters}
  129. cursor={cursor}
  130. limit={this.limit}
  131. onDataFetched={onDataFetched}
  132. loading={undefined}
  133. customDidUpdateComparator={this.customDidUpdateComparator}
  134. afterFetchTableData={this.afterFetchData}
  135. afterFetchSeriesData={this.afterFetchData}
  136. >
  137. {({errorMessage, ...rest}) =>
  138. children({
  139. errorMessage: this.state.errorMessage ?? errorMessage,
  140. ...rest,
  141. })
  142. }
  143. </GenericWidgetQueries>
  144. );
  145. }
  146. }
  147. export default MetricWidgetQueries;