index.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. import {Fragment} from 'react';
  2. import {browserHistory, RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Location, LocationDescriptor, Query} from 'history';
  5. import moment from 'moment';
  6. import {restoreRelease} from 'sentry/actionCreators/release';
  7. import {Client} from 'sentry/api';
  8. import Feature from 'sentry/components/acl/feature';
  9. import SessionsRequest from 'sentry/components/charts/sessionsRequest';
  10. import {DateTimeObject} from 'sentry/components/charts/utils';
  11. import DateTime from 'sentry/components/dateTime';
  12. import PerformanceCardTable from 'sentry/components/discover/performanceCardTable';
  13. import TransactionsList, {
  14. DropdownOption,
  15. } from 'sentry/components/discover/transactionsList';
  16. import * as Layout from 'sentry/components/layouts/thirds';
  17. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  18. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  19. import {ChangeData, TimeRangeSelector} from 'sentry/components/timeRangeSelector';
  20. import {t} from 'sentry/locale';
  21. import {space} from 'sentry/styles/space';
  22. import {
  23. NewQuery,
  24. Organization,
  25. PageFilters,
  26. ReleaseProject,
  27. SessionFieldWithOperation,
  28. } from 'sentry/types';
  29. import {getUtcDateString} from 'sentry/utils/dates';
  30. import {TableDataRow} from 'sentry/utils/discover/discoverQuery';
  31. import EventView from 'sentry/utils/discover/eventView';
  32. import {MobileVital, SpanOpBreakdown, WebVital} from 'sentry/utils/fields';
  33. import {formatVersion} from 'sentry/utils/formatters';
  34. import {decodeScalar} from 'sentry/utils/queryString';
  35. import routeTitleGen from 'sentry/utils/routeTitle';
  36. import withApi from 'sentry/utils/withApi';
  37. import withOrganization from 'sentry/utils/withOrganization';
  38. import withPageFilters from 'sentry/utils/withPageFilters';
  39. import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
  40. import {
  41. DisplayModes,
  42. transactionSummaryRouteWithQuery,
  43. } from 'sentry/views/performance/transactionSummary/utils';
  44. import {TrendChangeType, TrendView} from 'sentry/views/performance/trends/types';
  45. import {
  46. platformToPerformanceType,
  47. ProjectPerformanceType,
  48. } from 'sentry/views/performance/utils';
  49. import {
  50. getReleaseParams,
  51. isReleaseArchived,
  52. ReleaseBounds,
  53. searchReleaseVersion,
  54. } from '../../utils';
  55. import {ReleaseContext} from '..';
  56. import CommitAuthorBreakdown from './sidebar/commitAuthorBreakdown';
  57. import Deploys from './sidebar/deploys';
  58. import OtherProjects from './sidebar/otherProjects';
  59. import ProjectReleaseDetails from './sidebar/projectReleaseDetails';
  60. import ReleaseAdoption from './sidebar/releaseAdoption';
  61. import ReleaseStats from './sidebar/releaseStats';
  62. import ThresholdStatuses from './sidebar/thresholdStatuses';
  63. import TotalCrashFreeUsers from './sidebar/totalCrashFreeUsers';
  64. import ReleaseArchivedNotice from './releaseArchivedNotice';
  65. import ReleaseComparisonChart from './releaseComparisonChart';
  66. import ReleaseIssues from './releaseIssues';
  67. const RELEASE_PERIOD_KEY = 'release';
  68. export enum TransactionsListOption {
  69. FAILURE_COUNT = 'failure_count',
  70. TPM = 'tpm',
  71. SLOW = 'slow',
  72. SLOW_LCP = 'slow_lcp',
  73. REGRESSION = 'regression',
  74. IMPROVEMENT = 'improved',
  75. }
  76. type RouteParams = {
  77. orgId: string;
  78. release: string;
  79. };
  80. type Props = RouteComponentProps<RouteParams, {}> & {
  81. api: Client;
  82. organization: Organization;
  83. selection: PageFilters;
  84. };
  85. class ReleaseOverview extends DeprecatedAsyncView<Props> {
  86. getTitle() {
  87. const {params, organization} = this.props;
  88. return routeTitleGen(
  89. t('Release %s', formatVersion(params.release)),
  90. organization.slug,
  91. false
  92. );
  93. }
  94. handleRestore = async (project: ReleaseProject, successCallback: () => void) => {
  95. const {params, organization} = this.props;
  96. try {
  97. await restoreRelease(new Client(), {
  98. orgSlug: organization.slug,
  99. projectSlug: project.slug,
  100. releaseVersion: params.release,
  101. });
  102. successCallback();
  103. } catch {
  104. // do nothing, action creator is already displaying error message
  105. }
  106. };
  107. getReleaseEventView(
  108. version: string,
  109. projectId: number,
  110. selectedSort: DropdownOption,
  111. releaseBounds: ReleaseBounds
  112. ): EventView {
  113. const {selection, location} = this.props;
  114. const {environments} = selection;
  115. const {start, end, statsPeriod} = getReleaseParams({
  116. location,
  117. releaseBounds,
  118. });
  119. const baseQuery: NewQuery = {
  120. id: undefined,
  121. version: 2,
  122. name: `Release ${formatVersion(version)}`,
  123. query: `event.type:transaction ${searchReleaseVersion(version)}`,
  124. fields: ['transaction', 'failure_count()', 'epm()', 'p50()'],
  125. orderby: '-failure_count',
  126. range: statsPeriod || undefined,
  127. environment: environments,
  128. projects: [projectId],
  129. start: start ? getUtcDateString(start) : undefined,
  130. end: end ? getUtcDateString(end) : undefined,
  131. };
  132. switch (selectedSort.value) {
  133. case TransactionsListOption.SLOW_LCP:
  134. return EventView.fromSavedQuery({
  135. ...baseQuery,
  136. query: `event.type:transaction release:${version} epm():>0.01 has:measurements.lcp`,
  137. fields: ['transaction', 'failure_count()', 'epm()', 'p75(measurements.lcp)'],
  138. orderby: 'p75_measurements_lcp',
  139. });
  140. case TransactionsListOption.SLOW:
  141. return EventView.fromSavedQuery({
  142. ...baseQuery,
  143. query: `event.type:transaction release:${version} epm():>0.01`,
  144. });
  145. case TransactionsListOption.FAILURE_COUNT:
  146. return EventView.fromSavedQuery({
  147. ...baseQuery,
  148. query: `event.type:transaction release:${version} failure_count():>0`,
  149. });
  150. default:
  151. return EventView.fromSavedQuery(baseQuery);
  152. }
  153. }
  154. getReleaseTrendView(
  155. version: string,
  156. projectId: number,
  157. versionDate: string,
  158. releaseBounds: ReleaseBounds
  159. ): EventView {
  160. const {selection, location} = this.props;
  161. const {environments} = selection;
  162. const {start, end, statsPeriod} = getReleaseParams({
  163. location,
  164. releaseBounds,
  165. });
  166. const trendView = EventView.fromSavedQuery({
  167. id: undefined,
  168. version: 2,
  169. name: `Release ${formatVersion(version)}`,
  170. fields: ['transaction'],
  171. query: 'tpm():>0.01 trend_percentage():>0%',
  172. range: statsPeriod || undefined,
  173. environment: environments,
  174. projects: [projectId],
  175. start: start ? getUtcDateString(start) : undefined,
  176. end: end ? getUtcDateString(end) : undefined,
  177. }) as TrendView;
  178. trendView.middle = versionDate;
  179. return trendView;
  180. }
  181. getReleasePerformanceEventView(
  182. performanceType: string,
  183. baseQuery: NewQuery
  184. ): EventView {
  185. const eventView =
  186. performanceType === ProjectPerformanceType.FRONTEND
  187. ? (EventView.fromSavedQuery({
  188. ...baseQuery,
  189. fields: [
  190. ...baseQuery.fields,
  191. `p75(${WebVital.FCP})`,
  192. `p75(${WebVital.FID})`,
  193. `p75(${WebVital.LCP})`,
  194. `p75(${WebVital.CLS})`,
  195. `p75(${SpanOpBreakdown.SPANS_HTTP})`,
  196. `p75(${SpanOpBreakdown.SPANS_BROWSER})`,
  197. `p75(${SpanOpBreakdown.SPANS_RESOURCE})`,
  198. ],
  199. }) as EventView)
  200. : performanceType === ProjectPerformanceType.BACKEND
  201. ? (EventView.fromSavedQuery({
  202. ...baseQuery,
  203. fields: [...baseQuery.fields, 'apdex()', 'p75(spans.http)', 'p75(spans.db)'],
  204. }) as EventView)
  205. : performanceType === ProjectPerformanceType.MOBILE
  206. ? (EventView.fromSavedQuery({
  207. ...baseQuery,
  208. fields: [
  209. ...baseQuery.fields,
  210. `p75(${MobileVital.APP_START_COLD})`,
  211. `p75(${MobileVital.APP_START_WARM})`,
  212. `p75(${MobileVital.FRAMES_SLOW})`,
  213. `p75(${MobileVital.FRAMES_FROZEN})`,
  214. ],
  215. }) as EventView)
  216. : (EventView.fromSavedQuery({
  217. ...baseQuery,
  218. }) as EventView);
  219. return eventView;
  220. }
  221. getAllReleasesPerformanceView(
  222. projectId: number,
  223. performanceType: string,
  224. releaseBounds: ReleaseBounds
  225. ) {
  226. const {selection, location} = this.props;
  227. const {environments} = selection;
  228. const {start, end, statsPeriod} = getReleaseParams({
  229. location,
  230. releaseBounds,
  231. });
  232. const baseQuery: NewQuery = {
  233. id: undefined,
  234. version: 2,
  235. name: 'All Releases',
  236. query: 'event.type:transaction',
  237. fields: ['user_misery()'],
  238. range: statsPeriod || undefined,
  239. environment: environments,
  240. projects: [projectId],
  241. start: start ? getUtcDateString(start) : undefined,
  242. end: end ? getUtcDateString(end) : undefined,
  243. };
  244. return this.getReleasePerformanceEventView(performanceType, baseQuery);
  245. }
  246. getReleasePerformanceView(
  247. version: string,
  248. projectId: number,
  249. performanceType: string,
  250. releaseBounds: ReleaseBounds
  251. ) {
  252. const {selection, location} = this.props;
  253. const {environments} = selection;
  254. const {start, end, statsPeriod} = getReleaseParams({
  255. location,
  256. releaseBounds,
  257. });
  258. const baseQuery: NewQuery = {
  259. id: undefined,
  260. version: 2,
  261. name: `Release:${version}`,
  262. query: `event.type:transaction release:${version}`,
  263. fields: ['user_misery()'],
  264. range: statsPeriod || undefined,
  265. environment: environments,
  266. projects: [projectId],
  267. start: start ? getUtcDateString(start) : undefined,
  268. end: end ? getUtcDateString(end) : undefined,
  269. };
  270. return this.getReleasePerformanceEventView(performanceType, baseQuery);
  271. }
  272. get pageDateTime(): DateTimeObject {
  273. const query = this.props.location.query;
  274. const {start, end, statsPeriod} = normalizeDateTimeParams(query, {
  275. allowEmptyPeriod: true,
  276. allowAbsoluteDatetime: true,
  277. allowAbsolutePageDatetime: true,
  278. });
  279. if (statsPeriod) {
  280. return {period: statsPeriod};
  281. }
  282. if (start && end) {
  283. return {
  284. start: moment.utc(start).format(),
  285. end: moment.utc(end).format(),
  286. };
  287. }
  288. return {};
  289. }
  290. handleTransactionsListSortChange = (value: string) => {
  291. const {location} = this.props;
  292. const target = {
  293. pathname: location.pathname,
  294. query: {...location.query, showTransactions: value, transactionCursor: undefined},
  295. };
  296. browserHistory.push(target);
  297. };
  298. handleDateChange = (datetime: ChangeData) => {
  299. const {router, location} = this.props;
  300. const {start, end, relative, utc} = datetime;
  301. if (start && end) {
  302. const parser = utc ? moment.utc : moment;
  303. router.push({
  304. ...location,
  305. query: {
  306. ...location.query,
  307. pageStatsPeriod: undefined,
  308. pageStart: parser(start).format(),
  309. pageEnd: parser(end).format(),
  310. pageUtc: utc ?? undefined,
  311. },
  312. });
  313. return;
  314. }
  315. router.push({
  316. ...location,
  317. query: {
  318. ...location.query,
  319. pageStatsPeriod: relative === RELEASE_PERIOD_KEY ? undefined : relative,
  320. pageStart: undefined,
  321. pageEnd: undefined,
  322. pageUtc: undefined,
  323. },
  324. });
  325. };
  326. render() {
  327. const {organization, selection, location, api} = this.props;
  328. const {start, end, period, utc} = this.pageDateTime;
  329. const hasV2ReleaseUIEnabled =
  330. organization.features.includes('releases-v2-internal') ||
  331. organization.features.includes('releases-v2') ||
  332. organization.features.includes('releases-v2-st');
  333. return (
  334. <ReleaseContext.Consumer>
  335. {({
  336. release,
  337. project,
  338. deploys,
  339. releaseMeta,
  340. refetchData,
  341. hasHealthData,
  342. releaseBounds,
  343. }) => {
  344. const {commitCount, version} = release;
  345. const hasDiscover = organization.features.includes('discover-basic');
  346. const hasPerformance = organization.features.includes('performance-view');
  347. const hasReleaseComparisonPerformance = organization.features.includes(
  348. 'release-comparison-performance'
  349. );
  350. const {environments} = selection;
  351. const performanceType = platformToPerformanceType([project], [project.id]);
  352. const {selectedSort, sortOptions} = getTransactionsListSort(location);
  353. const releaseEventView = this.getReleaseEventView(
  354. version,
  355. project.id,
  356. selectedSort,
  357. releaseBounds
  358. );
  359. const titles =
  360. selectedSort.value !== TransactionsListOption.SLOW_LCP
  361. ? [t('transaction'), t('failure_count()'), t('tpm()'), t('p50()')]
  362. : [t('transaction'), t('failure_count()'), t('tpm()'), t('p75(lcp)')];
  363. const releaseTrendView = this.getReleaseTrendView(
  364. version,
  365. project.id,
  366. releaseMeta.released,
  367. releaseBounds
  368. );
  369. const allReleasesPerformanceView = this.getAllReleasesPerformanceView(
  370. project.id,
  371. performanceType,
  372. releaseBounds
  373. );
  374. const releasePerformanceView = this.getReleasePerformanceView(
  375. version,
  376. project.id,
  377. performanceType,
  378. releaseBounds
  379. );
  380. const generateLink = {
  381. transaction: generateTransactionLink(
  382. version,
  383. project.id,
  384. selection,
  385. location.query.showTransactions
  386. ),
  387. };
  388. const sessionsRequestProps: Omit<SessionsRequest['props'], 'children'> = {
  389. api,
  390. organization,
  391. field: [
  392. SessionFieldWithOperation.USERS,
  393. SessionFieldWithOperation.SESSIONS,
  394. SessionFieldWithOperation.DURATION,
  395. ],
  396. groupBy: ['session.status'],
  397. ...getReleaseParams({location, releaseBounds}),
  398. shouldFilterSessionsInTimeWindow: true,
  399. };
  400. const defaultDateTimeSelected = !period && !start && !end;
  401. const releaseBoundsLabel =
  402. releaseBounds.type === 'clamped'
  403. ? t('Clamped Release Period')
  404. : t('Entire Release Period');
  405. return (
  406. <SessionsRequest {...sessionsRequestProps}>
  407. {({
  408. loading: allReleasesLoading,
  409. reloading: allReleasesReloading,
  410. errored: allReleasesErrored,
  411. response: allReleases,
  412. }) => (
  413. <SessionsRequest
  414. {...sessionsRequestProps}
  415. query={searchReleaseVersion(version)}
  416. >
  417. {({
  418. loading: thisReleaseLoading,
  419. reloading: thisReleaseReloading,
  420. errored: thisReleaseErrored,
  421. response: thisRelease,
  422. }) => {
  423. const loading = allReleasesLoading || thisReleaseLoading;
  424. const reloading = allReleasesReloading || thisReleaseReloading;
  425. const errored = allReleasesErrored || thisReleaseErrored;
  426. return (
  427. <Layout.Body>
  428. <Layout.Main>
  429. {isReleaseArchived(release) && (
  430. <ReleaseArchivedNotice
  431. onRestore={() => this.handleRestore(project, refetchData)}
  432. />
  433. )}
  434. <ReleaseDetailsPageFilters>
  435. <EnvironmentPageFilter />
  436. <TimeRangeSelector
  437. relative={
  438. period ??
  439. (defaultDateTimeSelected ? RELEASE_PERIOD_KEY : null)
  440. }
  441. start={start ?? null}
  442. end={end ?? null}
  443. utc={utc ?? null}
  444. onChange={this.handleDateChange}
  445. menuTitle={t('Filter Time Range')}
  446. triggerLabel={
  447. defaultDateTimeSelected ? releaseBoundsLabel : null
  448. }
  449. relativeOptions={({defaultOptions, arbitraryOptions}) =>
  450. releaseBounds.type !== 'ancient'
  451. ? {
  452. [RELEASE_PERIOD_KEY]: (
  453. <Fragment>
  454. {releaseBoundsLabel}
  455. <br />
  456. <ReleaseBoundsDescription
  457. primary={defaultDateTimeSelected}
  458. >
  459. <DateTime date={releaseBounds.releaseStart} />
  460. –<DateTime date={releaseBounds.releaseEnd} />
  461. </ReleaseBoundsDescription>
  462. </Fragment>
  463. ),
  464. ...defaultOptions,
  465. ...arbitraryOptions,
  466. }
  467. : {...defaultOptions, ...arbitraryOptions}
  468. }
  469. defaultPeriod={
  470. releaseBounds.type !== 'ancient'
  471. ? RELEASE_PERIOD_KEY
  472. : '90d'
  473. }
  474. defaultAbsolute={{
  475. start: moment(releaseBounds.releaseStart)
  476. .subtract(1, 'hour')
  477. .toDate(),
  478. end: releaseBounds.releaseEnd
  479. ? moment(releaseBounds.releaseEnd)
  480. .add(1, 'hour')
  481. .toDate()
  482. : undefined,
  483. }}
  484. />
  485. </ReleaseDetailsPageFilters>
  486. {(hasDiscover || hasPerformance || hasHealthData) && (
  487. <ReleaseComparisonChart
  488. release={release}
  489. releaseSessions={thisRelease}
  490. allSessions={allReleases}
  491. platform={project.platform}
  492. location={location}
  493. loading={loading}
  494. reloading={reloading}
  495. errored={errored}
  496. project={project}
  497. organization={organization}
  498. api={api}
  499. hasHealthData={hasHealthData}
  500. />
  501. )}
  502. <ReleaseIssues
  503. organization={organization}
  504. version={version}
  505. location={location}
  506. releaseBounds={releaseBounds}
  507. queryFilterDescription={t('In this release')}
  508. withChart
  509. />
  510. <Feature features="performance-view">
  511. {hasReleaseComparisonPerformance ? (
  512. <PerformanceCardTable
  513. organization={organization}
  514. project={project}
  515. location={location}
  516. allReleasesEventView={allReleasesPerformanceView}
  517. releaseEventView={releasePerformanceView}
  518. performanceType={performanceType}
  519. />
  520. ) : (
  521. <TransactionsList
  522. location={location}
  523. organization={organization}
  524. eventView={releaseEventView}
  525. trendView={releaseTrendView}
  526. selected={selectedSort}
  527. options={sortOptions}
  528. handleDropdownChange={
  529. this.handleTransactionsListSortChange
  530. }
  531. titles={titles}
  532. generateLink={generateLink}
  533. supportsInvestigationRule={false}
  534. />
  535. )}
  536. </Feature>
  537. </Layout.Main>
  538. <Layout.Side>
  539. <ReleaseStats
  540. organization={organization}
  541. release={release}
  542. project={project}
  543. />
  544. {hasV2ReleaseUIEnabled && (
  545. <ThresholdStatuses
  546. project={project}
  547. release={release}
  548. organization={organization}
  549. selectedEnvs={selection.environments}
  550. />
  551. )}
  552. {hasHealthData && (
  553. <ReleaseAdoption
  554. releaseSessions={thisRelease}
  555. allSessions={allReleases}
  556. loading={loading}
  557. reloading={reloading}
  558. errored={errored}
  559. release={release}
  560. project={project}
  561. environment={environments}
  562. />
  563. )}
  564. <ProjectReleaseDetails
  565. release={release}
  566. releaseMeta={releaseMeta}
  567. projectSlug={project.slug}
  568. />
  569. {commitCount > 0 && (
  570. <CommitAuthorBreakdown
  571. version={version}
  572. orgId={organization.slug}
  573. projectSlug={project.slug}
  574. />
  575. )}
  576. {releaseMeta.projects.length > 1 && (
  577. <OtherProjects
  578. projects={releaseMeta.projects.filter(
  579. p => p.slug !== project.slug
  580. )}
  581. location={location}
  582. version={version}
  583. organization={organization}
  584. />
  585. )}
  586. {hasHealthData && (
  587. <TotalCrashFreeUsers
  588. organization={organization}
  589. version={version}
  590. projectSlug={project.slug}
  591. location={location}
  592. />
  593. )}
  594. {deploys.length > 0 && (
  595. <Deploys
  596. version={version}
  597. orgSlug={organization.slug}
  598. deploys={deploys}
  599. projectId={project.id}
  600. />
  601. )}
  602. </Layout.Side>
  603. </Layout.Body>
  604. );
  605. }}
  606. </SessionsRequest>
  607. )}
  608. </SessionsRequest>
  609. );
  610. }}
  611. </ReleaseContext.Consumer>
  612. );
  613. }
  614. }
  615. function generateTransactionLink(
  616. version: string,
  617. projectId: number,
  618. selection: PageFilters,
  619. value: string
  620. ) {
  621. return (
  622. organization: Organization,
  623. tableRow: TableDataRow,
  624. _query: Query
  625. ): LocationDescriptor => {
  626. const {transaction} = tableRow;
  627. const trendTransaction = ['regression', 'improved'].includes(value);
  628. const {environments, datetime} = selection;
  629. const {start, end, period} = datetime;
  630. return transactionSummaryRouteWithQuery({
  631. orgSlug: organization.slug,
  632. transaction: transaction! as string,
  633. query: {
  634. query: trendTransaction ? '' : `release:${version}`,
  635. environment: environments,
  636. start: start ? getUtcDateString(start) : undefined,
  637. end: end ? getUtcDateString(end) : undefined,
  638. statsPeriod: period,
  639. },
  640. projectID: projectId.toString(),
  641. display: trendTransaction ? DisplayModes.TREND : DisplayModes.DURATION,
  642. });
  643. };
  644. }
  645. function getDropdownOptions(): DropdownOption[] {
  646. return [
  647. {
  648. sort: {kind: 'desc', field: 'failure_count'},
  649. value: TransactionsListOption.FAILURE_COUNT,
  650. label: t('Failing Transactions'),
  651. },
  652. {
  653. sort: {kind: 'desc', field: 'epm'},
  654. value: TransactionsListOption.TPM,
  655. label: t('Frequent Transactions'),
  656. },
  657. {
  658. sort: {kind: 'desc', field: 'p50'},
  659. value: TransactionsListOption.SLOW,
  660. label: t('Slow Transactions'),
  661. },
  662. {
  663. sort: {kind: 'desc', field: 'p75_measurements_lcp'},
  664. value: TransactionsListOption.SLOW_LCP,
  665. label: t('Slow LCP'),
  666. },
  667. {
  668. sort: {kind: 'desc', field: 'trend_percentage()'},
  669. query: [['confidence()', '>6']],
  670. trendType: TrendChangeType.REGRESSION,
  671. value: TransactionsListOption.REGRESSION,
  672. label: t('Trending Regressions'),
  673. },
  674. {
  675. sort: {kind: 'asc', field: 'trend_percentage()'},
  676. query: [['confidence()', '>6']],
  677. trendType: TrendChangeType.IMPROVED,
  678. value: TransactionsListOption.IMPROVEMENT,
  679. label: t('Trending Improvements'),
  680. },
  681. ];
  682. }
  683. function getTransactionsListSort(location: Location): {
  684. selectedSort: DropdownOption;
  685. sortOptions: DropdownOption[];
  686. } {
  687. const sortOptions = getDropdownOptions();
  688. const urlParam = decodeScalar(
  689. location.query.showTransactions,
  690. TransactionsListOption.FAILURE_COUNT
  691. );
  692. const selectedSort = sortOptions.find(opt => opt.value === urlParam) || sortOptions[0];
  693. return {selectedSort, sortOptions};
  694. }
  695. const ReleaseDetailsPageFilters = styled('div')`
  696. display: grid;
  697. grid-template-columns: minmax(0, max-content) 1fr;
  698. gap: ${space(2)};
  699. margin-bottom: ${space(2)};
  700. @media (max-width: ${p => p.theme.breakpoints.small}) {
  701. grid-template-columns: auto;
  702. }
  703. `;
  704. const ReleaseBoundsDescription = styled('span')<{primary: boolean}>`
  705. font-size: ${p => p.theme.fontSizeSmall};
  706. color: ${p => (p.primary ? p.theme.activeText : p.theme.subText)};
  707. `;
  708. ReleaseOverview.contextType = ReleaseContext;
  709. export default withApi(withPageFilters(withOrganization(ReleaseOverview)));