actionDropdown.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import {browserHistory} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import {Button} from 'sentry/components/button';
  5. import type {MenuItemProps} from 'sentry/components/dropdownMenu';
  6. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  7. import {IconEllipsis} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {Organization} from 'sentry/types';
  11. import {trackAnalytics} from 'sentry/utils/analytics';
  12. import type {EventData} from 'sentry/utils/discover/eventView';
  13. import type EventView from 'sentry/utils/discover/eventView';
  14. import toArray from 'sentry/utils/toArray';
  15. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  16. import {addToFilter, excludeFromFilter} from '../cellAction';
  17. export enum ContextValueType {
  18. STRING = 'string',
  19. NUMBER = 'number',
  20. DURATION = 'duration',
  21. }
  22. enum QueryUpdateActions {
  23. ADD = 'add',
  24. EXCLUDE = 'exclude',
  25. SHOW_MORE_THAN = 'show-more-than',
  26. SHOW_LESS_THAN = 'show-less-than',
  27. }
  28. type Props = {
  29. contextValueType: ContextValueType;
  30. dataRow: EventData;
  31. eventView: EventView;
  32. location: Location;
  33. organization: Organization;
  34. queryKey: string;
  35. value: React.ReactText | string[];
  36. };
  37. function ActionDropDown(props: Props) {
  38. const menuItems: MenuItemProps[] = [];
  39. const {location, eventView, queryKey, value, organization, contextValueType, dataRow} =
  40. props;
  41. const addAsColumn = () => {
  42. trackAnalytics('discover_v2.quick_context_add_column', {
  43. organization,
  44. column: queryKey,
  45. });
  46. const oldField = eventView?.fields.map(field => field.field);
  47. const newField = toArray(oldField).concat(queryKey);
  48. browserHistory.push({
  49. ...location,
  50. query: {
  51. ...location?.query,
  52. field: newField,
  53. },
  54. });
  55. };
  56. function handleQueryUpdate(actionType: QueryUpdateActions) {
  57. trackAnalytics('discover_v2.quick_context_update_query', {
  58. organization,
  59. queryKey,
  60. });
  61. const oldFilters = eventView?.query || '';
  62. const newFilters = new MutableSearch(oldFilters);
  63. switch (actionType) {
  64. case QueryUpdateActions.SHOW_MORE_THAN:
  65. newFilters.setFilterValues(queryKey, [`>${value}`]);
  66. break;
  67. case QueryUpdateActions.SHOW_LESS_THAN:
  68. newFilters.setFilterValues(queryKey, [`<${value}`]);
  69. break;
  70. case QueryUpdateActions.ADD:
  71. addToFilter(newFilters, queryKey, value);
  72. break;
  73. case QueryUpdateActions.EXCLUDE:
  74. excludeFromFilter(newFilters, queryKey, value);
  75. break;
  76. default:
  77. throw new Error(`Unknown quick context action type. ${actionType}`);
  78. }
  79. browserHistory.push({
  80. ...location,
  81. query: {
  82. ...location?.query,
  83. query: newFilters.formatString(),
  84. },
  85. });
  86. }
  87. if (!(queryKey in dataRow)) {
  88. menuItems.push({
  89. key: 'add-as-column',
  90. label: t('Add as column'),
  91. onAction: () => {
  92. addAsColumn();
  93. },
  94. });
  95. }
  96. if (
  97. contextValueType === ContextValueType.NUMBER ||
  98. contextValueType === ContextValueType.DURATION
  99. ) {
  100. menuItems.push(
  101. {
  102. key: 'show-more-than',
  103. label: t('Show values greater than'),
  104. onAction: () => {
  105. handleQueryUpdate(QueryUpdateActions.SHOW_MORE_THAN);
  106. },
  107. },
  108. {
  109. key: 'show-less-than',
  110. label: t('Show values less than'),
  111. onAction: () => {
  112. handleQueryUpdate(QueryUpdateActions.SHOW_LESS_THAN);
  113. },
  114. }
  115. );
  116. } else {
  117. menuItems.push(
  118. {
  119. key: 'add-to-filter',
  120. label: t('Add to filter'),
  121. onAction: () => {
  122. handleQueryUpdate(QueryUpdateActions.ADD);
  123. },
  124. },
  125. {
  126. key: 'exclude-from-filter',
  127. label: t('Exclude from filter'),
  128. onAction: () => {
  129. handleQueryUpdate(QueryUpdateActions.EXCLUDE);
  130. },
  131. }
  132. );
  133. }
  134. return (
  135. <DropdownMenu
  136. items={menuItems}
  137. trigger={triggerProps => (
  138. <StyledTrigger
  139. {...triggerProps}
  140. aria-label={t('Quick Context Action Menu')}
  141. data-test-id="quick-context-action-trigger"
  142. borderless
  143. size="zero"
  144. onClick={e => {
  145. e.stopPropagation();
  146. e.preventDefault();
  147. triggerProps.onClick?.(e);
  148. }}
  149. icon={<IconEllipsis size="sm" />}
  150. />
  151. )}
  152. />
  153. );
  154. }
  155. const StyledTrigger = styled(Button)`
  156. margin-left: ${space(0.5)};
  157. `;
  158. export default ActionDropDown;