metricField.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import {Fragment} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {Button} from 'sentry/components/button';
  5. import type {FormFieldProps} from 'sentry/components/forms/formField';
  6. import FormField from 'sentry/components/forms/formField';
  7. import type {Organization} from 'sentry/types/organization';
  8. import type {Aggregation} from 'sentry/utils/discover/fields';
  9. import {
  10. AGGREGATIONS,
  11. explodeFieldString,
  12. generateFieldAsString,
  13. } from 'sentry/utils/discover/fields';
  14. import type {AlertType} from 'sentry/views/alerts/wizard/options';
  15. import {
  16. hideParameterSelectorSet,
  17. hidePrimarySelectorSet,
  18. } from 'sentry/views/alerts/wizard/options';
  19. import {QueryField} from 'sentry/views/discover/table/queryField';
  20. import {FieldValueKind} from 'sentry/views/discover/table/types';
  21. import {generateFieldOptions} from 'sentry/views/discover/utils';
  22. import type {OptionConfig} from './constants';
  23. import {
  24. errorFieldConfig,
  25. getWizardAlertFieldConfig,
  26. transactionFieldConfig,
  27. } from './constants';
  28. import {Dataset} from './types';
  29. type Props = Omit<FormFieldProps, 'children'> & {
  30. organization: Organization;
  31. alertType?: AlertType;
  32. /**
  33. * Optionally set a width for each column of selector
  34. */
  35. columnWidth?: number;
  36. inFieldLabels?: boolean;
  37. };
  38. export const getFieldOptionConfig = ({
  39. dataset,
  40. alertType,
  41. }: {
  42. dataset: Dataset;
  43. alertType?: AlertType;
  44. }) => {
  45. let config: OptionConfig;
  46. let hidePrimarySelector = false;
  47. let hideParameterSelector = false;
  48. if (alertType) {
  49. config = getWizardAlertFieldConfig(alertType, dataset);
  50. hidePrimarySelector = hidePrimarySelectorSet.has(alertType);
  51. hideParameterSelector = hideParameterSelectorSet.has(alertType);
  52. } else {
  53. config = dataset === Dataset.ERRORS ? errorFieldConfig : transactionFieldConfig;
  54. }
  55. const aggregations = Object.fromEntries<Aggregation>(
  56. config.aggregations.map(key => {
  57. // TODO(scttcper): Temporary hack for default value while we handle the translation of user
  58. if (key === 'count_unique') {
  59. const agg = AGGREGATIONS[key] as Aggregation;
  60. agg.getFieldOverrides = () => {
  61. return {defaultValue: 'tags[sentry:user]'};
  62. };
  63. return [key, agg];
  64. }
  65. return [key, AGGREGATIONS[key]];
  66. })
  67. );
  68. const fieldKeys = config.fields.map(key => {
  69. // XXX(epurkhiser): Temporary hack while we handle the translation of user ->
  70. // tags[sentry:user].
  71. if (key === 'user') {
  72. return 'tags[sentry:user]';
  73. }
  74. return key;
  75. });
  76. const {measurementKeys, spanOperationBreakdownKeys} = config;
  77. return {
  78. fieldOptionsConfig: {
  79. aggregations,
  80. fieldKeys,
  81. measurementKeys,
  82. spanOperationBreakdownKeys,
  83. },
  84. hidePrimarySelector,
  85. hideParameterSelector,
  86. };
  87. };
  88. function MetricField({
  89. organization,
  90. columnWidth,
  91. inFieldLabels,
  92. alertType,
  93. ...props
  94. }: Props) {
  95. return (
  96. <FormField {...props}>
  97. {({onChange, value, model, disabled}) => {
  98. const dataset = model.getValue('dataset');
  99. const {fieldOptionsConfig, hidePrimarySelector, hideParameterSelector} =
  100. getFieldOptionConfig({
  101. dataset: dataset as Dataset,
  102. alertType,
  103. });
  104. const fieldOptions = generateFieldOptions({organization, ...fieldOptionsConfig});
  105. const fieldValue = explodeFieldString(value ?? '');
  106. const fieldKey =
  107. fieldValue?.kind === FieldValueKind.FUNCTION
  108. ? `function:${fieldValue.function[0]}`
  109. : '';
  110. const selectedField = fieldOptions[fieldKey]?.value;
  111. const numParameters: number =
  112. selectedField?.kind === FieldValueKind.FUNCTION
  113. ? selectedField.meta.parameters.length
  114. : 0;
  115. const parameterColumns =
  116. numParameters - (hideParameterSelector ? 1 : 0) - (hidePrimarySelector ? 1 : 0);
  117. return (
  118. <Fragment>
  119. <StyledQueryField
  120. filterPrimaryOptions={option =>
  121. option.value.kind === FieldValueKind.FUNCTION
  122. }
  123. fieldOptions={fieldOptions}
  124. fieldValue={fieldValue}
  125. onChange={v => onChange(generateFieldAsString(v), {})}
  126. columnWidth={columnWidth}
  127. gridColumns={parameterColumns + 1}
  128. inFieldLabels={inFieldLabels}
  129. shouldRenderTag={false}
  130. disabled={disabled}
  131. hideParameterSelector={hideParameterSelector}
  132. hidePrimarySelector={hidePrimarySelector}
  133. />
  134. </Fragment>
  135. );
  136. }}
  137. </FormField>
  138. );
  139. }
  140. const StyledQueryField = styled(QueryField)<{gridColumns: number; columnWidth?: number}>`
  141. ${p =>
  142. p.columnWidth &&
  143. css`
  144. width: ${p.gridColumns * p.columnWidth}px;
  145. `}
  146. `;
  147. const PresetButton = styled(Button)<{disabled: boolean}>`
  148. ${p =>
  149. p.disabled &&
  150. css`
  151. color: ${p.theme.textColor};
  152. &:hover,
  153. &:focus {
  154. color: ${p.theme.textColor};
  155. }
  156. `}
  157. `;
  158. PresetButton.defaultProps = {
  159. priority: 'link',
  160. borderless: true,
  161. };
  162. export default MetricField;