columnEditModal.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {Fragment, useEffect, useState} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import type {ModalRenderProps} from 'sentry/actionCreators/modal';
  5. import {Button, LinkButton} from 'sentry/components/button';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import ExternalLink from 'sentry/components/links/externalLink';
  8. import {DISCOVER2_DOCS_URL} from 'sentry/constants';
  9. import {t, tct} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {Organization} from 'sentry/types/organization';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
  14. import {
  15. type Column,
  16. ERROR_FIELDS,
  17. ERRORS_AGGREGATION_FUNCTIONS,
  18. getAggregations,
  19. TRANSACTION_FIELDS,
  20. } from 'sentry/utils/discover/fields';
  21. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  22. import {AggregationKey, FieldKey} from 'sentry/utils/fields';
  23. import theme from 'sentry/utils/theme';
  24. import useTags from 'sentry/utils/useTags';
  25. import {generateFieldOptions} from 'sentry/views/discover/utils';
  26. import ColumnEditCollection from './columnEditCollection';
  27. type Props = {
  28. columns: Column[];
  29. measurementKeys: null | string[];
  30. // Fired when column selections have been applied.
  31. onApply: (columns: Column[]) => void;
  32. organization: Organization;
  33. customMeasurements?: CustomMeasurementCollection;
  34. dataset?: DiscoverDatasets;
  35. spanOperationBreakdownKeys?: string[];
  36. } & ModalRenderProps;
  37. function ColumnEditModal(props: Props) {
  38. const {
  39. Header,
  40. Body,
  41. Footer,
  42. measurementKeys,
  43. spanOperationBreakdownKeys,
  44. organization,
  45. onApply,
  46. closeModal,
  47. customMeasurements,
  48. dataset,
  49. } = props;
  50. // Only run once for each organization.id.
  51. useEffect(() => {
  52. trackAnalytics('discover_v2.column_editor.open', {organization});
  53. }, [organization]);
  54. const tags = useTags();
  55. const tagKeys = Object.keys(tags);
  56. const [columns, setColumns] = useState<Column[]>(props.columns);
  57. function handleApply() {
  58. onApply(columns);
  59. closeModal();
  60. }
  61. let fieldOptions: ReturnType<typeof generateFieldOptions>;
  62. if (dataset === DiscoverDatasets.ERRORS) {
  63. const aggregations = getAggregations(DiscoverDatasets.ERRORS);
  64. fieldOptions = generateFieldOptions({
  65. organization,
  66. tagKeys,
  67. fieldKeys: ERROR_FIELDS,
  68. aggregations: Object.keys(aggregations)
  69. .filter(key => ERRORS_AGGREGATION_FUNCTIONS.includes(key as AggregationKey))
  70. .reduce((obj, key) => {
  71. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  72. obj[key] = aggregations[key];
  73. return obj;
  74. }, {}),
  75. });
  76. } else if (dataset === DiscoverDatasets.TRANSACTIONS) {
  77. fieldOptions = generateFieldOptions({
  78. organization,
  79. tagKeys,
  80. measurementKeys,
  81. spanOperationBreakdownKeys,
  82. customMeasurements: Object.values(customMeasurements ?? {}).map(
  83. ({key, functions}) => ({
  84. key,
  85. functions,
  86. })
  87. ),
  88. fieldKeys: TRANSACTION_FIELDS,
  89. });
  90. } else {
  91. fieldOptions = generateFieldOptions({
  92. organization,
  93. tagKeys,
  94. measurementKeys,
  95. spanOperationBreakdownKeys,
  96. customMeasurements: Object.values(customMeasurements ?? {}).map(
  97. ({key, functions}) => ({
  98. key,
  99. functions,
  100. })
  101. ),
  102. });
  103. }
  104. return (
  105. <Fragment>
  106. <Header closeButton>
  107. <h4>{t('Edit Columns')}</h4>
  108. </Header>
  109. <Body>
  110. <Instruction>
  111. {tct(
  112. 'To group events, add [functionLink: functions] f(x) that may take in additional parameters. [fieldTagLink: Tag and field] columns will help you view more details about the events (i.e. title).',
  113. {
  114. functionLink: (
  115. <ExternalLink href="https://docs.sentry.io/product/discover-queries/query-builder/#filter-by-table-columns" />
  116. ),
  117. fieldTagLink: (
  118. <ExternalLink href="https://docs.sentry.io/product/sentry-basics/search/searchable-properties/#event-properties" />
  119. ),
  120. }
  121. )}
  122. </Instruction>
  123. <ColumnEditCollection
  124. columns={columns}
  125. fieldOptions={fieldOptions}
  126. filterAggregateParameters={option =>
  127. option.value.meta.name !== FieldKey.TOTAL_COUNT
  128. }
  129. // Performance Score is not supported in Discover because
  130. // INP is not stored on sampled transactions.
  131. filterPrimaryOptions={option =>
  132. option.value.meta.name !== AggregationKey.PERFORMANCE_SCORE
  133. }
  134. onChange={setColumns}
  135. organization={organization}
  136. supportsEquations
  137. />
  138. </Body>
  139. <Footer>
  140. <ButtonBar gap={1}>
  141. <LinkButton priority="default" href={DISCOVER2_DOCS_URL} external>
  142. {t('Read the Docs')}
  143. </LinkButton>
  144. <Button aria-label={t('Apply')} priority="primary" onClick={handleApply}>
  145. {t('Apply')}
  146. </Button>
  147. </ButtonBar>
  148. </Footer>
  149. </Fragment>
  150. );
  151. }
  152. const Instruction = styled('div')`
  153. margin-bottom: ${space(4)};
  154. `;
  155. const modalCss = css`
  156. @media (min-width: ${theme.breakpoints.medium}) {
  157. width: auto;
  158. max-width: 900px;
  159. }
  160. `;
  161. export default ColumnEditModal;
  162. export {modalCss};