columnEditModal.tsx 4.9 KB

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