utils.tsx 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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/datasetSelectorTabs';
  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.DAILYTOP5:
  215. case DisplayModes.DAILY:
  216. case DisplayModes.BAR:
  217. return DisplayType.BAR;
  218. case DisplayModes.TOP5:
  219. return DisplayType.TOP_N;
  220. case DisplayModes.PREVIOUS:
  221. case DisplayModes.DEFAULT:
  222. return DisplayType.AREA;
  223. default:
  224. return DisplayType.LINE;
  225. }
  226. }
  227. export function getSavedQueryDataset(
  228. organization: Organization,
  229. location: Location | undefined,
  230. savedQuery: SavedQuery | NewQuery | undefined,
  231. splitDecision?: SavedQueryDatasets
  232. ): SavedQueryDatasets {
  233. const dataset = decodeScalar(location?.query?.[DATASET_PARAM]);
  234. if (dataset) {
  235. return dataset as SavedQueryDatasets;
  236. }
  237. if (savedQuery?.queryDataset === SavedQueryDatasets.DISCOVER && splitDecision) {
  238. return splitDecision;
  239. }
  240. if (
  241. savedQuery?.queryDataset &&
  242. savedQuery?.queryDataset !== SavedQueryDatasets.DISCOVER
  243. ) {
  244. return savedQuery.queryDataset;
  245. }
  246. if (hasDatasetSelector(organization)) {
  247. return SavedQueryDatasets.ERRORS;
  248. }
  249. return SavedQueryDatasets.DISCOVER;
  250. }
  251. export function getSavedQueryWithDataset(
  252. savedQuery?: SavedQuery | NewQuery
  253. ): SavedQuery | NewQuery | undefined {
  254. if (!savedQuery) {
  255. return undefined;
  256. }
  257. return {
  258. ...savedQuery,
  259. dataset: getDatasetFromLocationOrSavedQueryDataset(
  260. undefined,
  261. savedQuery?.queryDataset
  262. ),
  263. };
  264. }
  265. export function getDatasetFromLocationOrSavedQueryDataset(
  266. location: Location | undefined,
  267. queryDataset: SavedQueryDatasets | undefined
  268. ): DiscoverDatasets | undefined {
  269. const dataset = decodeScalar(location?.query?.dataset);
  270. if (dataset) {
  271. return dataset as DiscoverDatasets;
  272. }
  273. const savedQueryDataset = decodeScalar(location?.query?.queryDataset) ?? queryDataset;
  274. switch (savedQueryDataset) {
  275. case SavedQueryDatasets.ERRORS:
  276. return DiscoverDatasets.ERRORS;
  277. case SavedQueryDatasets.TRANSACTIONS:
  278. return DiscoverDatasets.TRANSACTIONS;
  279. case SavedQueryDatasets.DISCOVER:
  280. return DiscoverDatasets.DISCOVER;
  281. default:
  282. return undefined;
  283. }
  284. }
  285. export function getSavedQueryDatasetFromLocationOrDataset(
  286. location: Location | undefined,
  287. dataset: DiscoverDatasets | undefined
  288. ): SavedQueryDatasets | undefined {
  289. const savedQueryDataset = decodeScalar(location?.query?.queryDataset);
  290. if (savedQueryDataset) {
  291. return savedQueryDataset as SavedQueryDatasets;
  292. }
  293. const discoverDataset = decodeScalar(location?.query?.dataset) ?? dataset;
  294. switch (discoverDataset) {
  295. case DiscoverDatasets.ERRORS:
  296. return SavedQueryDatasets.ERRORS;
  297. case DiscoverDatasets.TRANSACTIONS:
  298. return SavedQueryDatasets.TRANSACTIONS;
  299. case DiscoverDatasets.DISCOVER:
  300. return SavedQueryDatasets.DISCOVER;
  301. default:
  302. return undefined;
  303. }
  304. }