columnEditModal.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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. obj[key] = aggregations[key];
  72. return obj;
  73. }, {}),
  74. });
  75. } else if (dataset === DiscoverDatasets.TRANSACTIONS) {
  76. fieldOptions = generateFieldOptions({
  77. organization,
  78. tagKeys,
  79. measurementKeys,
  80. spanOperationBreakdownKeys,
  81. customMeasurements: Object.values(customMeasurements ?? {}).map(
  82. ({key, functions}) => ({
  83. key,
  84. functions,
  85. })
  86. ),
  87. fieldKeys: TRANSACTION_FIELDS,
  88. });
  89. } else {
  90. fieldOptions = generateFieldOptions({
  91. organization,
  92. tagKeys,
  93. measurementKeys,
  94. spanOperationBreakdownKeys,
  95. customMeasurements: Object.values(customMeasurements ?? {}).map(
  96. ({key, functions}) => ({
  97. key,
  98. functions,
  99. })
  100. ),
  101. });
  102. }
  103. return (
  104. <Fragment>
  105. <Header closeButton>
  106. <h4>{t('Edit Columns')}</h4>
  107. </Header>
  108. <Body>
  109. <Instruction>
  110. {tct(
  111. '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).',
  112. {
  113. functionLink: (
  114. <ExternalLink href="https://docs.sentry.io/product/discover-queries/query-builder/#filter-by-table-columns" />
  115. ),
  116. fieldTagLink: (
  117. <ExternalLink href="https://docs.sentry.io/product/sentry-basics/search/searchable-properties/#event-properties" />
  118. ),
  119. }
  120. )}
  121. </Instruction>
  122. <ColumnEditCollection
  123. columns={columns}
  124. fieldOptions={fieldOptions}
  125. filterAggregateParameters={option =>
  126. option.value.meta.name !== FieldKey.TOTAL_COUNT
  127. }
  128. // Performance Score is not supported in Discover because
  129. // INP is not stored on sampled transactions.
  130. filterPrimaryOptions={option =>
  131. option.value.meta.name !== AggregationKey.PERFORMANCE_SCORE
  132. }
  133. onChange={setColumns}
  134. organization={organization}
  135. supportsEquations
  136. />
  137. </Body>
  138. <Footer>
  139. <ButtonBar gap={1}>
  140. <LinkButton priority="default" href={DISCOVER2_DOCS_URL} external>
  141. {t('Read the Docs')}
  142. </LinkButton>
  143. <Button aria-label={t('Apply')} priority="primary" onClick={handleApply}>
  144. {t('Apply')}
  145. </Button>
  146. </ButtonBar>
  147. </Footer>
  148. </Fragment>
  149. );
  150. }
  151. const Instruction = styled('div')`
  152. margin-bottom: ${space(4)};
  153. `;
  154. const modalCss = css`
  155. @media (min-width: ${theme.breakpoints.medium}) {
  156. width: auto;
  157. max-width: 900px;
  158. }
  159. `;
  160. export default ColumnEditModal;
  161. export {modalCss};