data.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. import {Location} from 'history';
  2. import {COL_WIDTH_UNDEFINED} from 'app/components/gridEditable';
  3. import {ALL_ACCESS_PROJECTS} from 'app/constants/globalSelectionHeader';
  4. import {t} from 'app/locale';
  5. import {NewQuery, Organization, Project, SelectValue} from 'app/types';
  6. import EventView from 'app/utils/discover/eventView';
  7. import {decodeScalar} from 'app/utils/queryString';
  8. import {MutableSearch} from 'app/utils/tokenizeSearch';
  9. import {getCurrentLandingDisplay, LandingDisplayField} from './landing/utils';
  10. import {
  11. getVitalDetailTableMehStatusFunction,
  12. getVitalDetailTablePoorStatusFunction,
  13. vitalNameFromLocation,
  14. } from './vitalDetail/utils';
  15. export const DEFAULT_STATS_PERIOD = '24h';
  16. export const COLUMN_TITLES = [
  17. 'transaction',
  18. 'project',
  19. 'tpm',
  20. 'p50',
  21. 'p95',
  22. 'failure rate',
  23. 'apdex',
  24. 'users',
  25. 'user misery',
  26. ];
  27. export enum PERFORMANCE_TERM {
  28. APDEX = 'apdex',
  29. TPM = 'tpm',
  30. THROUGHPUT = 'throughput',
  31. FAILURE_RATE = 'failureRate',
  32. P50 = 'p50',
  33. P75 = 'p75',
  34. P95 = 'p95',
  35. P99 = 'p99',
  36. LCP = 'lcp',
  37. FCP = 'fcp',
  38. USER_MISERY = 'userMisery',
  39. STATUS_BREAKDOWN = 'statusBreakdown',
  40. DURATION_DISTRIBUTION = 'durationDistribution',
  41. USER_MISERY_NEW = 'userMiseryNew',
  42. APDEX_NEW = 'apdexNew',
  43. APP_START_COLD = 'appStartCold',
  44. APP_START_WARM = 'appStartWarm',
  45. SLOW_FRAMES = 'slowFrames',
  46. FROZEN_FRAMES = 'frozenFrames',
  47. STALL_PERCENTAGE = 'stallPercentage',
  48. MOST_ISSUES = 'mostIssues',
  49. MOST_ERRORS = 'mostErrors',
  50. }
  51. export type TooltipOption = SelectValue<string> & {
  52. tooltip: string;
  53. };
  54. export function getAxisOptions(organization: Organization): TooltipOption[] {
  55. return [
  56. {
  57. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APDEX_NEW),
  58. value: 'apdex()',
  59. label: t('Apdex'),
  60. },
  61. {
  62. tooltip: getTermHelp(organization, PERFORMANCE_TERM.TPM),
  63. value: 'tpm()',
  64. label: t('Transactions Per Minute'),
  65. },
  66. {
  67. tooltip: getTermHelp(organization, PERFORMANCE_TERM.FAILURE_RATE),
  68. value: 'failure_rate()',
  69. label: t('Failure Rate'),
  70. },
  71. {
  72. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P50),
  73. value: 'p50()',
  74. label: t('p50 Duration'),
  75. },
  76. {
  77. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P95),
  78. value: 'p95()',
  79. label: t('p95 Duration'),
  80. },
  81. {
  82. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P99),
  83. value: 'p99()',
  84. label: t('p99 Duration'),
  85. },
  86. ];
  87. }
  88. export type AxisOption = TooltipOption & {
  89. field: string;
  90. backupOption?: AxisOption;
  91. label: string;
  92. isDistribution?: boolean;
  93. isLeftDefault?: boolean;
  94. isRightDefault?: boolean;
  95. };
  96. export function getFrontendAxisOptions(organization: Organization): AxisOption[] {
  97. return [
  98. {
  99. tooltip: getTermHelp(organization, PERFORMANCE_TERM.LCP),
  100. value: `p75(lcp)`,
  101. label: t('LCP p75'),
  102. field: 'p75(measurements.lcp)',
  103. isLeftDefault: true,
  104. backupOption: {
  105. tooltip: getTermHelp(organization, PERFORMANCE_TERM.FCP),
  106. value: `p75(fcp)`,
  107. label: t('FCP p75'),
  108. field: 'p75(measurements.fcp)',
  109. },
  110. },
  111. {
  112. tooltip: getTermHelp(organization, PERFORMANCE_TERM.DURATION_DISTRIBUTION),
  113. value: 'lcp_distribution',
  114. label: t('LCP Distribution'),
  115. field: 'measurements.lcp',
  116. isDistribution: true,
  117. isRightDefault: true,
  118. backupOption: {
  119. tooltip: getTermHelp(organization, PERFORMANCE_TERM.DURATION_DISTRIBUTION),
  120. value: 'fcp_distribution',
  121. label: t('FCP Distribution'),
  122. field: 'measurements.fcp',
  123. isDistribution: true,
  124. },
  125. },
  126. {
  127. tooltip: getTermHelp(organization, PERFORMANCE_TERM.TPM),
  128. value: 'tpm()',
  129. label: t('Transactions Per Minute'),
  130. field: 'tpm()',
  131. },
  132. ];
  133. }
  134. export function getFrontendOtherAxisOptions(organization: Organization): AxisOption[] {
  135. return [
  136. {
  137. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P50),
  138. value: `p50()`,
  139. label: t('Duration p50'),
  140. field: 'p50(transaction.duration)',
  141. },
  142. {
  143. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P75),
  144. value: `p75()`,
  145. label: t('Duration p75'),
  146. field: 'p75(transaction.duration)',
  147. isLeftDefault: true,
  148. },
  149. {
  150. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P95),
  151. value: `p95()`,
  152. label: t('Duration p95'),
  153. field: 'p95(transaction.duration)',
  154. },
  155. {
  156. tooltip: getTermHelp(organization, PERFORMANCE_TERM.DURATION_DISTRIBUTION),
  157. value: 'duration_distribution',
  158. label: t('Duration Distribution'),
  159. field: 'transaction.duration',
  160. isDistribution: true,
  161. isRightDefault: true,
  162. },
  163. ];
  164. }
  165. export function getBackendAxisOptions(organization: Organization): AxisOption[] {
  166. return [
  167. {
  168. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P50),
  169. value: `p50()`,
  170. label: t('Duration p50'),
  171. field: 'p50(transaction.duration)',
  172. },
  173. {
  174. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P75),
  175. value: `p75()`,
  176. label: t('Duration p75'),
  177. field: 'p75(transaction.duration)',
  178. isLeftDefault: true,
  179. },
  180. {
  181. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P95),
  182. value: `p95()`,
  183. label: t('Duration p95'),
  184. field: 'p95(transaction.duration)',
  185. },
  186. {
  187. tooltip: getTermHelp(organization, PERFORMANCE_TERM.P99),
  188. value: `p99()`,
  189. label: t('Duration p99'),
  190. field: 'p99(transaction.duration)',
  191. },
  192. {
  193. tooltip: getTermHelp(organization, PERFORMANCE_TERM.TPM),
  194. value: 'tpm()',
  195. label: t('Transactions Per Minute'),
  196. field: 'tpm()',
  197. },
  198. {
  199. tooltip: getTermHelp(organization, PERFORMANCE_TERM.FAILURE_RATE),
  200. value: 'failure_rate()',
  201. label: t('Failure Rate'),
  202. field: 'failure_rate()',
  203. },
  204. {
  205. tooltip: getTermHelp(organization, PERFORMANCE_TERM.DURATION_DISTRIBUTION),
  206. value: 'duration_distribution',
  207. label: t('Duration Distribution'),
  208. field: 'transaction.duration',
  209. isDistribution: true,
  210. isRightDefault: true,
  211. },
  212. {
  213. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APDEX),
  214. value: 'apdex()',
  215. label: t('Apdex'),
  216. field: 'apdex()',
  217. },
  218. ];
  219. }
  220. export function getMobileAxisOptions(organization: Organization): AxisOption[] {
  221. return [
  222. {
  223. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_COLD),
  224. value: `p50(measurements.app_start_cold)`,
  225. label: t('Cold Start Duration p50'),
  226. field: 'p50(measurements.app_start_cold)',
  227. },
  228. {
  229. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_COLD),
  230. value: `p75(measurements.app_start_cold)`,
  231. label: t('Cold Start Duration p75'),
  232. field: 'p75(measurements.app_start_cold)',
  233. isLeftDefault: true,
  234. },
  235. {
  236. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_COLD),
  237. value: `p95(measurements.app_start_cold)`,
  238. label: t('Cold Start Duration p95'),
  239. field: 'p95(measurements.app_start_cold)',
  240. },
  241. {
  242. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_COLD),
  243. value: `p99(measurements.app_start_cold)`,
  244. label: t('Cold Start Duration p99'),
  245. field: 'p99(measurements.app_start_cold)',
  246. },
  247. {
  248. tooltip: getTermHelp(organization, PERFORMANCE_TERM.DURATION_DISTRIBUTION),
  249. value: 'app_start_cold_distribution',
  250. label: t('Cold Start Distribution'),
  251. field: 'measurements.app_start_cold',
  252. isDistribution: true,
  253. isRightDefault: true,
  254. },
  255. {
  256. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_WARM),
  257. value: `p50(measurements.app_start_warm)`,
  258. label: t('Warm Start Duration p50'),
  259. field: 'p50(measurements.app_start_warm)',
  260. },
  261. {
  262. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_WARM),
  263. value: `p75(measurements.app_start_warm)`,
  264. label: t('Warm Start Duration p75'),
  265. field: 'p75(measurements.app_start_warm)',
  266. },
  267. {
  268. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_WARM),
  269. value: `p95(measurements.app_start_warm)`,
  270. label: t('Warm Start Duration p95'),
  271. field: 'p95(measurements.app_start_warm)',
  272. },
  273. {
  274. tooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_WARM),
  275. value: `p99(measurements.app_start_warm)`,
  276. label: t('Warm Start Duration p99'),
  277. field: 'p99(measurements.app_start_warm)',
  278. },
  279. {
  280. tooltip: getTermHelp(organization, PERFORMANCE_TERM.DURATION_DISTRIBUTION),
  281. value: 'app_start_warm_distribution',
  282. label: t('Warm Start Distribution'),
  283. field: 'measurements.app_start_warm',
  284. isDistribution: true,
  285. },
  286. {
  287. tooltip: getTermHelp(organization, PERFORMANCE_TERM.TPM),
  288. value: 'tpm()',
  289. label: t('Transactions Per Minute'),
  290. field: 'tpm()',
  291. },
  292. {
  293. tooltip: getTermHelp(organization, PERFORMANCE_TERM.FAILURE_RATE),
  294. value: 'failure_rate()',
  295. label: t('Failure Rate'),
  296. field: 'failure_rate()',
  297. },
  298. ];
  299. }
  300. type TermFormatter = (organization: Organization) => string;
  301. export const PERFORMANCE_TERMS: Record<PERFORMANCE_TERM, TermFormatter> = {
  302. apdex: () =>
  303. t(
  304. 'Apdex is the ratio of both satisfactory and tolerable response times to all response times. To adjust the tolerable threshold, go to performance settings.'
  305. ),
  306. tpm: () => t('TPM is the number of recorded transaction events per minute.'),
  307. throughput: () =>
  308. t('Throughput is the number of recorded transaction events per minute.'),
  309. failureRate: () =>
  310. t(
  311. 'Failure rate is the percentage of recorded transactions that had a known and unsuccessful status.'
  312. ),
  313. p50: () => t('p50 indicates the duration that 50% of transactions are faster than.'),
  314. p75: () => t('p75 indicates the duration that 75% of transactions are faster than.'),
  315. p95: () => t('p95 indicates the duration that 95% of transactions are faster than.'),
  316. p99: () => t('p99 indicates the duration that 99% of transactions are faster than.'),
  317. lcp: () =>
  318. t('Largest contentful paint (LCP) is a web vital meant to represent user load times'),
  319. fcp: () =>
  320. t('First contentful paint (FCP) is a web vital meant to represent user load times'),
  321. userMisery: organization =>
  322. t(
  323. "User Misery is a score that represents the number of unique users who have experienced load times 4x your organization's apdex threshold of %sms.",
  324. organization.apdexThreshold
  325. ),
  326. statusBreakdown: () =>
  327. t(
  328. 'The breakdown of transaction statuses. This may indicate what type of failure it is.'
  329. ),
  330. durationDistribution: () =>
  331. t(
  332. 'Distribution buckets counts of transactions at specifics times for your current date range'
  333. ),
  334. userMiseryNew: () =>
  335. t(
  336. "User Misery is a score that represents the number of unique users who have experienced load times 4x the project's configured threshold. Adjust project threshold in project performance settings."
  337. ),
  338. apdexNew: () =>
  339. t(
  340. 'Apdex is the ratio of both satisfactory and tolerable response times to all response times. To adjust the tolerable threshold, go to project performance settings.'
  341. ),
  342. appStartCold: () =>
  343. t('Cold start is a measure of the application start up time from scratch.'),
  344. appStartWarm: () =>
  345. t('Warm start is a measure of the application start up time while still in memory.'),
  346. slowFrames: () => t('The count of the number of slow frames in the transaction.'),
  347. frozenFrames: () => t('The count of the number of frozen frames in the transaction.'),
  348. mostErrors: () => t('Transactions with the most associated errors.'),
  349. mostIssues: () => t('The most instances of an issue for a related transaction.'),
  350. stallPercentage: () =>
  351. t(
  352. 'The percentage of the transaction duration in which the application is in a stalled state.'
  353. ),
  354. };
  355. export function getTermHelp(
  356. organization: Organization,
  357. term: keyof typeof PERFORMANCE_TERMS
  358. ): string {
  359. if (!PERFORMANCE_TERMS.hasOwnProperty(term)) {
  360. return '';
  361. }
  362. return PERFORMANCE_TERMS[term](organization);
  363. }
  364. function generateGenericPerformanceEventView(
  365. _organization: Organization,
  366. location: Location
  367. ): EventView {
  368. const {query} = location;
  369. const fields = [
  370. 'team_key_transaction',
  371. 'transaction',
  372. 'project',
  373. 'tpm()',
  374. 'p50()',
  375. 'p95()',
  376. 'failure_rate()',
  377. 'apdex()',
  378. 'count_unique(user)',
  379. 'count_miserable(user)',
  380. 'user_misery()',
  381. ];
  382. const hasStartAndEnd = query.start && query.end;
  383. const savedQuery: NewQuery = {
  384. id: undefined,
  385. name: t('Performance'),
  386. query: 'event.type:transaction',
  387. projects: [],
  388. fields,
  389. version: 2,
  390. };
  391. const widths = Array(savedQuery.fields.length).fill(COL_WIDTH_UNDEFINED);
  392. widths[savedQuery.fields.length - 1] = '110';
  393. savedQuery.widths = widths;
  394. if (!query.statsPeriod && !hasStartAndEnd) {
  395. savedQuery.range = DEFAULT_STATS_PERIOD;
  396. }
  397. savedQuery.orderby = decodeScalar(query.sort, '-tpm');
  398. const searchQuery = decodeScalar(query.query, '');
  399. const conditions = new MutableSearch(searchQuery);
  400. // This is not an override condition since we want the duration to appear in the search bar as a default.
  401. if (!conditions.hasFilter('transaction.duration')) {
  402. conditions.setFilterValues('transaction.duration', ['<15m']);
  403. }
  404. // If there is a bare text search, we want to treat it as a search
  405. // on the transaction name.
  406. if (conditions.freeText.length > 0) {
  407. // the query here is a user entered condition, no need to escape it
  408. conditions.setFilterValues(
  409. 'transaction',
  410. [`*${conditions.freeText.join(' ')}*`],
  411. false
  412. );
  413. conditions.freeText = [];
  414. }
  415. savedQuery.query = conditions.formatString();
  416. const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
  417. eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
  418. return eventView;
  419. }
  420. function generateBackendPerformanceEventView(
  421. _organization: Organization,
  422. location: Location
  423. ): EventView {
  424. const {query} = location;
  425. const fields = [
  426. 'team_key_transaction',
  427. 'transaction',
  428. 'project',
  429. 'transaction.op',
  430. 'http.method',
  431. 'tpm()',
  432. 'p50()',
  433. 'p95()',
  434. 'failure_rate()',
  435. 'apdex()',
  436. 'count_unique(user)',
  437. 'count_miserable(user)',
  438. 'user_misery()',
  439. ];
  440. const hasStartAndEnd = query.start && query.end;
  441. const savedQuery: NewQuery = {
  442. id: undefined,
  443. name: t('Performance'),
  444. query: 'event.type:transaction',
  445. projects: [],
  446. fields,
  447. version: 2,
  448. };
  449. const widths = Array(savedQuery.fields.length).fill(COL_WIDTH_UNDEFINED);
  450. widths[savedQuery.fields.length - 1] = '110';
  451. savedQuery.widths = widths;
  452. if (!query.statsPeriod && !hasStartAndEnd) {
  453. savedQuery.range = DEFAULT_STATS_PERIOD;
  454. }
  455. savedQuery.orderby = decodeScalar(query.sort, '-tpm');
  456. const searchQuery = decodeScalar(query.query, '');
  457. const conditions = new MutableSearch(searchQuery);
  458. // This is not an override condition since we want the duration to appear in the search bar as a default.
  459. if (!conditions.hasFilter('transaction.duration')) {
  460. conditions.setFilterValues('transaction.duration', ['<15m']);
  461. }
  462. // If there is a bare text search, we want to treat it as a search
  463. // on the transaction name.
  464. if (conditions.freeText.length > 0) {
  465. // the query here is a user entered condition, no need to escape it
  466. conditions.setFilterValues(
  467. 'transaction',
  468. [`*${conditions.freeText.join(' ')}*`],
  469. false
  470. );
  471. conditions.freeText = [];
  472. }
  473. savedQuery.query = conditions.formatString();
  474. const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
  475. eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
  476. return eventView;
  477. }
  478. function generateMobilePerformanceEventView(
  479. _organization: Organization,
  480. location: Location,
  481. projects: Project[],
  482. genericEventView: EventView
  483. ): EventView {
  484. const {query} = location;
  485. const fields = [
  486. 'team_key_transaction',
  487. 'transaction',
  488. 'project',
  489. 'transaction.op',
  490. 'tpm()',
  491. 'p75(measurements.app_start_cold)',
  492. 'p75(measurements.app_start_warm)',
  493. 'p75(measurements.frames_slow_rate)',
  494. 'p75(measurements.frames_frozen_rate)',
  495. ];
  496. // At this point, all projects are mobile projects.
  497. // If in addition to that, all projects are react-native projects,
  498. // then show the stall percentage as well.
  499. const projectIds = genericEventView.project;
  500. if (projectIds.length > 0 && projectIds[0] !== ALL_ACCESS_PROJECTS) {
  501. const selectedProjects = projects.filter(p =>
  502. projectIds.includes(parseInt(p.id, 10))
  503. );
  504. if (
  505. selectedProjects.length > 0 &&
  506. selectedProjects.every(project => project.platform === 'react-native')
  507. ) {
  508. // TODO(tonyx): remove these once the SDKs are ready
  509. fields.pop();
  510. fields.pop();
  511. fields.push('p75(measurements.stall_percentage)');
  512. }
  513. }
  514. const hasStartAndEnd = query.start && query.end;
  515. const savedQuery: NewQuery = {
  516. id: undefined,
  517. name: t('Performance'),
  518. query: 'event.type:transaction',
  519. projects: [],
  520. fields: [...fields, 'count_unique(user)', 'count_miserable(user)', 'user_misery()'],
  521. version: 2,
  522. };
  523. const widths = Array(savedQuery.fields.length).fill(COL_WIDTH_UNDEFINED);
  524. widths[savedQuery.fields.length - 1] = '110';
  525. savedQuery.widths = widths;
  526. if (!query.statsPeriod && !hasStartAndEnd) {
  527. savedQuery.range = DEFAULT_STATS_PERIOD;
  528. }
  529. savedQuery.orderby = decodeScalar(query.sort, '-tpm');
  530. const searchQuery = decodeScalar(query.query, '');
  531. const conditions = new MutableSearch(searchQuery);
  532. // This is not an override condition since we want the duration to appear in the search bar as a default.
  533. if (!conditions.hasFilter('transaction.duration')) {
  534. conditions.setFilterValues('transaction.duration', ['<15m']);
  535. }
  536. // If there is a bare text search, we want to treat it as a search
  537. // on the transaction name.
  538. if (conditions.freeText.length > 0) {
  539. // the query here is a user entered condition, no need to escape it
  540. conditions.setFilterValues(
  541. 'transaction',
  542. [`*${conditions.freeText.join(' ')}*`],
  543. false
  544. );
  545. conditions.freeText = [];
  546. }
  547. savedQuery.query = conditions.formatString();
  548. const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
  549. eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
  550. return eventView;
  551. }
  552. function generateFrontendPageloadPerformanceEventView(
  553. _organization: Organization,
  554. location: Location
  555. ): EventView {
  556. const {query} = location;
  557. const fields = [
  558. 'team_key_transaction',
  559. 'transaction',
  560. 'project',
  561. 'tpm()',
  562. 'p75(measurements.fcp)',
  563. 'p75(measurements.lcp)',
  564. 'p75(measurements.fid)',
  565. 'p75(measurements.cls)',
  566. 'count_unique(user)',
  567. 'count_miserable(user)',
  568. 'user_misery()',
  569. ];
  570. const hasStartAndEnd = query.start && query.end;
  571. const savedQuery: NewQuery = {
  572. id: undefined,
  573. name: t('Performance'),
  574. query: 'event.type:transaction',
  575. projects: [],
  576. fields,
  577. version: 2,
  578. };
  579. const widths = Array(savedQuery.fields.length).fill(COL_WIDTH_UNDEFINED);
  580. widths[savedQuery.fields.length - 1] = '110';
  581. savedQuery.widths = widths;
  582. if (!query.statsPeriod && !hasStartAndEnd) {
  583. savedQuery.range = DEFAULT_STATS_PERIOD;
  584. }
  585. savedQuery.orderby = decodeScalar(query.sort, '-tpm');
  586. const searchQuery = decodeScalar(query.query, '');
  587. const conditions = new MutableSearch(searchQuery);
  588. // This is not an override condition since we want the duration to appear in the search bar as a default.
  589. if (!conditions.hasFilter('transaction.duration')) {
  590. conditions.setFilterValues('transaction.duration', ['<15m']);
  591. }
  592. // If there is a bare text search, we want to treat it as a search
  593. // on the transaction name.
  594. if (conditions.freeText.length > 0) {
  595. // the query here is a user entered condition, no need to escape it
  596. conditions.setFilterValues(
  597. 'transaction',
  598. [`*${conditions.freeText.join(' ')}*`],
  599. false
  600. );
  601. conditions.freeText = [];
  602. }
  603. savedQuery.query = conditions.formatString();
  604. const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
  605. eventView.additionalConditions
  606. .addFilterValues('event.type', ['transaction'])
  607. .addFilterValues('transaction.op', ['pageload']);
  608. return eventView;
  609. }
  610. function generateFrontendOtherPerformanceEventView(
  611. _organization: Organization,
  612. location: Location
  613. ): EventView {
  614. const {query} = location;
  615. const fields = [
  616. 'team_key_transaction',
  617. 'transaction',
  618. 'project',
  619. 'transaction.op',
  620. 'tpm()',
  621. 'p50(transaction.duration)',
  622. 'p75(transaction.duration)',
  623. 'p95(transaction.duration)',
  624. 'count_unique(user)',
  625. 'count_miserable(user)',
  626. 'user_misery()',
  627. ];
  628. const hasStartAndEnd = query.start && query.end;
  629. const savedQuery: NewQuery = {
  630. id: undefined,
  631. name: t('Performance'),
  632. query: 'event.type:transaction',
  633. projects: [],
  634. fields,
  635. version: 2,
  636. };
  637. const widths = Array(savedQuery.fields.length).fill(COL_WIDTH_UNDEFINED);
  638. widths[savedQuery.fields.length - 1] = '110';
  639. savedQuery.widths = widths;
  640. if (!query.statsPeriod && !hasStartAndEnd) {
  641. savedQuery.range = DEFAULT_STATS_PERIOD;
  642. }
  643. savedQuery.orderby = decodeScalar(query.sort, '-tpm');
  644. const searchQuery = decodeScalar(query.query, '');
  645. const conditions = new MutableSearch(searchQuery);
  646. // This is not an override condition since we want the duration to appear in the search bar as a default.
  647. if (!conditions.hasFilter('transaction.duration')) {
  648. conditions.setFilterValues('transaction.duration', ['<15m']);
  649. }
  650. // If there is a bare text search, we want to treat it as a search
  651. // on the transaction name.
  652. if (conditions.freeText.length > 0) {
  653. // the query here is a user entered condition, no need to escape it
  654. conditions.setFilterValues(
  655. 'transaction',
  656. [`*${conditions.freeText.join(' ')}*`],
  657. false
  658. );
  659. conditions.freeText = [];
  660. }
  661. savedQuery.query = conditions.formatString();
  662. const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
  663. eventView.additionalConditions
  664. .addFilterValues('event.type', ['transaction'])
  665. .addFilterValues('!transaction.op', ['pageload']);
  666. return eventView;
  667. }
  668. export function generatePerformanceEventView(
  669. organization,
  670. location,
  671. projects,
  672. isTrends = false
  673. ) {
  674. const eventView = generateGenericPerformanceEventView(organization, location);
  675. if (isTrends) {
  676. return eventView;
  677. }
  678. const display = getCurrentLandingDisplay(location, projects, eventView);
  679. switch (display?.field) {
  680. case LandingDisplayField.FRONTEND_PAGELOAD:
  681. return generateFrontendPageloadPerformanceEventView(organization, location);
  682. case LandingDisplayField.FRONTEND_OTHER:
  683. return generateFrontendOtherPerformanceEventView(organization, location);
  684. case LandingDisplayField.BACKEND:
  685. return generateBackendPerformanceEventView(organization, location);
  686. case LandingDisplayField.MOBILE:
  687. return generateMobilePerformanceEventView(
  688. organization,
  689. location,
  690. projects,
  691. eventView
  692. );
  693. default:
  694. return eventView;
  695. }
  696. }
  697. export function generatePerformanceVitalDetailView(
  698. _organization: Organization,
  699. location: Location
  700. ): EventView {
  701. const {query} = location;
  702. const vitalName = vitalNameFromLocation(location);
  703. const hasStartAndEnd = query.start && query.end;
  704. const savedQuery: NewQuery = {
  705. id: undefined,
  706. name: t('Vitals Performance Details'),
  707. query: 'event.type:transaction',
  708. projects: [],
  709. fields: [
  710. 'team_key_transaction',
  711. 'transaction',
  712. 'project',
  713. 'count_unique(user)',
  714. 'count()',
  715. `p50(${vitalName})`,
  716. `p75(${vitalName})`,
  717. `p95(${vitalName})`,
  718. getVitalDetailTablePoorStatusFunction(vitalName),
  719. getVitalDetailTableMehStatusFunction(vitalName),
  720. ],
  721. version: 2,
  722. };
  723. if (!query.statsPeriod && !hasStartAndEnd) {
  724. savedQuery.range = DEFAULT_STATS_PERIOD;
  725. }
  726. savedQuery.orderby = decodeScalar(query.sort, '-count');
  727. const searchQuery = decodeScalar(query.query, '');
  728. const conditions = new MutableSearch(searchQuery);
  729. // If there is a bare text search, we want to treat it as a search
  730. // on the transaction name.
  731. if (conditions.freeText.length > 0) {
  732. // the query here is a user entered condition, no need to escape it
  733. conditions.setFilterValues(
  734. 'transaction',
  735. [`*${conditions.freeText.join(' ')}*`],
  736. false
  737. );
  738. conditions.freeText = [];
  739. }
  740. savedQuery.query = conditions.formatString();
  741. const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
  742. eventView.additionalConditions
  743. .addFilterValues('event.type', ['transaction'])
  744. .addFilterValues('has', [vitalName]);
  745. return eventView;
  746. }