utils.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. import type {Location} from 'history';
  2. import {
  3. deleteHomepageQuery,
  4. updateHomepageQuery,
  5. } from 'sentry/actionCreators/discoverHomepageQueries';
  6. import {
  7. createSavedQuery,
  8. deleteSavedQuery,
  9. updateSavedQuery,
  10. } from 'sentry/actionCreators/discoverSavedQueries';
  11. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  12. import type {Client} from 'sentry/api';
  13. import {t} from 'sentry/locale';
  14. import type {NewQuery, Organization, SavedQuery} from 'sentry/types/organization';
  15. import {trackAnalytics} from 'sentry/utils/analytics';
  16. import type {SaveQueryEventParameters} from 'sentry/utils/analytics/discoverAnalyticsEvents';
  17. import type EventView from 'sentry/utils/discover/eventView';
  18. import {
  19. DiscoverDatasets,
  20. DisplayModes,
  21. SavedQueryDatasets,
  22. } from 'sentry/utils/discover/types';
  23. import {decodeScalar} from 'sentry/utils/queryString';
  24. import {DisplayType} from 'sentry/views/dashboards/types';
  25. import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
  26. import {DATASET_PARAM} from 'sentry/views/discover/savedQuery/datasetSelector';
  27. export function handleCreateQuery(
  28. api: Client,
  29. organization: Organization,
  30. eventView: EventView,
  31. yAxis: string[],
  32. // True if this is a brand new query being saved
  33. // False if this is a modification from a saved query
  34. isNewQuery: boolean = true
  35. ): Promise<SavedQuery> {
  36. const payload = eventView.toNewQuery();
  37. payload.yAxis = yAxis;
  38. trackAnalytics(getAnalyticsCreateEventKeyName(isNewQuery, 'request'), {
  39. organization,
  40. ...extractAnalyticsQueryFields(payload),
  41. });
  42. const promise = createSavedQuery(api, organization.slug, payload);
  43. promise
  44. .then((savedQuery: SavedQuery) => {
  45. addSuccessMessage(t('Query saved'));
  46. trackAnalytics(getAnalyticsCreateEventKeyName(isNewQuery, 'success'), {
  47. organization,
  48. ...extractAnalyticsQueryFields(payload),
  49. });
  50. return savedQuery;
  51. })
  52. .catch((err: Error) => {
  53. addErrorMessage(t('Query not saved'));
  54. trackAnalytics(getAnalyticsCreateEventKeyName(isNewQuery, 'failed'), {
  55. organization,
  56. ...extractAnalyticsQueryFields(payload),
  57. error:
  58. err?.message || `Could not save a ${isNewQuery ? 'new' : 'existing'} query`,
  59. });
  60. });
  61. return promise;
  62. }
  63. export function handleUpdateQuery(
  64. api: Client,
  65. organization: Organization,
  66. eventView: EventView,
  67. yAxis: string[]
  68. ): Promise<SavedQuery> {
  69. const payload = eventView.toNewQuery();
  70. payload.yAxis = yAxis;
  71. if (!eventView.name) {
  72. addErrorMessage(t('Please name your query'));
  73. return Promise.reject();
  74. }
  75. trackAnalytics('discover_v2.update_query_request', {
  76. organization,
  77. ...extractAnalyticsQueryFields(payload),
  78. });
  79. const promise = updateSavedQuery(api, organization.slug, payload);
  80. promise
  81. .then((savedQuery: SavedQuery) => {
  82. addSuccessMessage(t('Query updated'));
  83. trackAnalytics('discover_v2.update_query_success', {
  84. organization,
  85. ...extractAnalyticsQueryFields(payload),
  86. });
  87. // NOTE: there is no need to convert _saved into an EventView and push it
  88. // to the browser history, since this.props.eventView already
  89. // derives from location.
  90. return savedQuery;
  91. })
  92. .catch((err: Error) => {
  93. addErrorMessage(t('Query not updated'));
  94. trackAnalytics('discover_v2.update_query_failed', {
  95. organization,
  96. ...extractAnalyticsQueryFields(payload),
  97. error: err?.message || 'Failed to update a query',
  98. });
  99. });
  100. return promise;
  101. }
  102. /**
  103. * Essentially the same as handleUpdateQuery, but specifically for changing the
  104. * name of the query
  105. */
  106. export function handleUpdateQueryName(
  107. api: Client,
  108. organization: Organization,
  109. eventView: EventView
  110. ) {
  111. const payload = eventView.toNewQuery();
  112. trackAnalytics('discover_v2.update_query_name_request', {
  113. organization,
  114. ...extractAnalyticsQueryFields(payload),
  115. });
  116. const promise = updateSavedQuery(api, organization.slug, payload);
  117. promise
  118. .then(_saved => {
  119. addSuccessMessage(t('Query name saved'));
  120. trackAnalytics('discover_v2.update_query_name_successs', {
  121. organization,
  122. ...extractAnalyticsQueryFields(payload),
  123. });
  124. })
  125. .catch((err: Error) => {
  126. addErrorMessage(t('Query name not saved'));
  127. trackAnalytics('discover_v2.update_query_failed', {
  128. organization,
  129. ...extractAnalyticsQueryFields(payload),
  130. error: err?.message || 'Failed to update a query name',
  131. });
  132. });
  133. return promise;
  134. }
  135. export function handleDeleteQuery(
  136. api: Client,
  137. organization: Organization,
  138. eventView: EventView
  139. ): Promise<void> {
  140. trackAnalytics('discover_v2.delete_query_request', {
  141. organization,
  142. ...extractAnalyticsQueryFields(eventView.toNewQuery()),
  143. });
  144. const promise = deleteSavedQuery(api, organization.slug, eventView.id!);
  145. promise
  146. .then(() => {
  147. addSuccessMessage(t('Query deleted'));
  148. trackAnalytics('discover_v2.delete_query_success', {
  149. organization,
  150. ...extractAnalyticsQueryFields(eventView.toNewQuery()),
  151. });
  152. })
  153. .catch((err: Error) => {
  154. addErrorMessage(t('Query not deleted'));
  155. trackAnalytics('discover_v2.delete_query_failed', {
  156. organization,
  157. ...extractAnalyticsQueryFields(eventView.toNewQuery()),
  158. error: err?.message || 'Failed to delete query',
  159. });
  160. });
  161. return promise;
  162. }
  163. export function handleUpdateHomepageQuery(
  164. api: Client,
  165. organization: Organization,
  166. query: NewQuery
  167. ) {
  168. const promise = updateHomepageQuery(api, organization.slug, query);
  169. return promise
  170. .then(savedQuery => {
  171. addSuccessMessage(t('Saved as Discover default'));
  172. return savedQuery;
  173. })
  174. .catch(() => {
  175. addErrorMessage(t('Unable to set query as Discover default'));
  176. });
  177. }
  178. export function handleResetHomepageQuery(api: Client, organization: Organization) {
  179. const promise = deleteHomepageQuery(api, organization.slug);
  180. return promise
  181. .then(() => {
  182. addSuccessMessage(t('Successfully removed Discover default'));
  183. })
  184. .catch(() => {
  185. addErrorMessage(t('Unable to remove Discover default'));
  186. });
  187. }
  188. export function getAnalyticsCreateEventKeyName(
  189. // True if this is a brand new query being saved
  190. // False if this is a modification from a saved query
  191. isNewQuery: boolean,
  192. type: 'request' | 'success' | 'failed'
  193. ): keyof SaveQueryEventParameters {
  194. return (
  195. isNewQuery
  196. ? 'discover_v2.save_new_query_' + type
  197. : 'discover_v2.save_existing_query_' + type
  198. ) as keyof SaveQueryEventParameters;
  199. }
  200. /**
  201. * Takes in a DiscoverV2 NewQuery object and returns a Partial containing
  202. * the desired fields to populate into reload analytics
  203. */
  204. export function extractAnalyticsQueryFields(payload: NewQuery): Partial<NewQuery> {
  205. const {projects, fields, query} = payload;
  206. return {
  207. projects,
  208. fields,
  209. query,
  210. };
  211. }
  212. export function displayModeToDisplayType(displayMode: DisplayModes): DisplayType {
  213. switch (displayMode) {
  214. case DisplayModes.BAR:
  215. return DisplayType.BAR;
  216. case DisplayModes.TOP5:
  217. return DisplayType.TOP_N;
  218. default:
  219. return DisplayType.LINE;
  220. }
  221. }
  222. export function getSavedQueryDataset(
  223. organization: Organization,
  224. location: Location | undefined,
  225. savedQuery: SavedQuery | NewQuery | undefined,
  226. splitDecision?: SavedQueryDatasets
  227. ): SavedQueryDatasets {
  228. const dataset = decodeScalar(location?.query?.[DATASET_PARAM]);
  229. if (dataset) {
  230. return dataset as SavedQueryDatasets;
  231. }
  232. if (savedQuery?.queryDataset === SavedQueryDatasets.DISCOVER && splitDecision) {
  233. return splitDecision;
  234. }
  235. if (
  236. savedQuery?.queryDataset &&
  237. savedQuery?.queryDataset !== SavedQueryDatasets.DISCOVER
  238. ) {
  239. return savedQuery.queryDataset;
  240. }
  241. if (hasDatasetSelector(organization)) {
  242. return SavedQueryDatasets.ERRORS;
  243. }
  244. return SavedQueryDatasets.DISCOVER;
  245. }
  246. export function getSavedQueryWithDataset(
  247. savedQuery?: SavedQuery | NewQuery
  248. ): SavedQuery | NewQuery | undefined {
  249. if (!savedQuery) {
  250. return undefined;
  251. }
  252. return {
  253. ...savedQuery,
  254. dataset: getDatasetFromLocationOrSavedQueryDataset(
  255. undefined,
  256. savedQuery?.queryDataset
  257. ),
  258. };
  259. }
  260. export function getDatasetFromLocationOrSavedQueryDataset(
  261. location: Location | undefined,
  262. queryDataset: SavedQueryDatasets | undefined
  263. ): DiscoverDatasets | undefined {
  264. const dataset = decodeScalar(location?.query?.dataset);
  265. if (dataset) {
  266. return dataset as DiscoverDatasets;
  267. }
  268. const savedQueryDataset = decodeScalar(location?.query?.queryDataset) ?? queryDataset;
  269. switch (savedQueryDataset) {
  270. case SavedQueryDatasets.ERRORS:
  271. return DiscoverDatasets.ERRORS;
  272. case SavedQueryDatasets.TRANSACTIONS:
  273. return DiscoverDatasets.TRANSACTIONS;
  274. case SavedQueryDatasets.DISCOVER:
  275. return DiscoverDatasets.DISCOVER;
  276. default:
  277. return undefined;
  278. }
  279. }
  280. export function getSavedQueryDatasetFromLocationOrDataset(
  281. location: Location | undefined,
  282. dataset: DiscoverDatasets | undefined
  283. ): SavedQueryDatasets | undefined {
  284. const savedQueryDataset = decodeScalar(location?.query?.queryDataset);
  285. if (savedQueryDataset) {
  286. return savedQueryDataset as SavedQueryDatasets;
  287. }
  288. const discoverDataset = decodeScalar(location?.query?.dataset) ?? dataset;
  289. switch (discoverDataset) {
  290. case DiscoverDatasets.ERRORS:
  291. return SavedQueryDatasets.ERRORS;
  292. case DiscoverDatasets.TRANSACTIONS:
  293. return SavedQueryDatasets.TRANSACTIONS;
  294. case DiscoverDatasets.DISCOVER:
  295. return SavedQueryDatasets.DISCOVER;
  296. default:
  297. return undefined;
  298. }
  299. }