index.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  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 EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
  17. import * as Layout from 'sentry/components/layouts/thirds';
  18. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  19. import {ChangeData} from 'sentry/components/organizations/timeRangeSelector';
  20. import {TimeRangeSelector} from 'sentry/components/timeRangeSelector';
  21. import {t} from 'sentry/locale';
  22. import {space} from 'sentry/styles/space';
  23. import {
  24. NewQuery,
  25. Organization,
  26. PageFilters,
  27. ReleaseProject,
  28. SessionFieldWithOperation,
  29. } from 'sentry/types';
  30. import {getUtcDateString} from 'sentry/utils/dates';
  31. import {TableDataRow} from 'sentry/utils/discover/discoverQuery';
  32. import EventView from 'sentry/utils/discover/eventView';
  33. import {MobileVital, SpanOpBreakdown, WebVital} from 'sentry/utils/fields';
  34. import {formatVersion} from 'sentry/utils/formatters';
  35. import {decodeScalar} from 'sentry/utils/queryString';
  36. import routeTitleGen from 'sentry/utils/routeTitle';
  37. import withApi from 'sentry/utils/withApi';
  38. import withOrganization from 'sentry/utils/withOrganization';
  39. import withPageFilters from 'sentry/utils/withPageFilters';
  40. import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
  41. import {
  42. DisplayModes,
  43. transactionSummaryRouteWithQuery,
  44. } from 'sentry/views/performance/transactionSummary/utils';
  45. import {TrendChangeType, TrendView} from 'sentry/views/performance/trends/types';
  46. import {
  47. platformToPerformanceType,
  48. ProjectPerformanceType,
  49. } from 'sentry/views/performance/utils';
  50. import {
  51. getReleaseParams,
  52. isReleaseArchived,
  53. ReleaseBounds,
  54. searchReleaseVersion,
  55. } from '../../utils';
  56. import {ReleaseContext} from '..';
  57. import CommitAuthorBreakdown from './sidebar/commitAuthorBreakdown';
  58. import Deploys from './sidebar/deploys';
  59. import OtherProjects from './sidebar/otherProjects';
  60. import ProjectReleaseDetails from './sidebar/projectReleaseDetails';
  61. import ReleaseAdoption from './sidebar/releaseAdoption';
  62. import ReleaseStats from './sidebar/releaseStats';
  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. return (
  330. <ReleaseContext.Consumer>
  331. {({
  332. release,
  333. project,
  334. deploys,
  335. releaseMeta,
  336. refetchData,
  337. hasHealthData,
  338. releaseBounds,
  339. }) => {
  340. const {commitCount, version} = release;
  341. const hasDiscover = organization.features.includes('discover-basic');
  342. const hasPerformance = organization.features.includes('performance-view');
  343. const hasReleaseComparisonPerformance = organization.features.includes(
  344. 'release-comparison-performance'
  345. );
  346. const {environments} = selection;
  347. const performanceType = platformToPerformanceType([project], [project.id]);
  348. const {selectedSort, sortOptions} = getTransactionsListSort(location);
  349. const releaseEventView = this.getReleaseEventView(
  350. version,
  351. project.id,
  352. selectedSort,
  353. releaseBounds
  354. );
  355. const titles =
  356. selectedSort.value !== TransactionsListOption.SLOW_LCP
  357. ? [t('transaction'), t('failure_count()'), t('tpm()'), t('p50()')]
  358. : [t('transaction'), t('failure_count()'), t('tpm()'), t('p75(lcp)')];
  359. const releaseTrendView = this.getReleaseTrendView(
  360. version,
  361. project.id,
  362. releaseMeta.released,
  363. releaseBounds
  364. );
  365. const allReleasesPerformanceView = this.getAllReleasesPerformanceView(
  366. project.id,
  367. performanceType,
  368. releaseBounds
  369. );
  370. const releasePerformanceView = this.getReleasePerformanceView(
  371. version,
  372. project.id,
  373. performanceType,
  374. releaseBounds
  375. );
  376. const generateLink = {
  377. transaction: generateTransactionLink(
  378. version,
  379. project.id,
  380. selection,
  381. location.query.showTransactions
  382. ),
  383. };
  384. const sessionsRequestProps: Omit<SessionsRequest['props'], 'children'> = {
  385. api,
  386. organization,
  387. field: [
  388. SessionFieldWithOperation.USERS,
  389. SessionFieldWithOperation.SESSIONS,
  390. SessionFieldWithOperation.DURATION,
  391. ],
  392. groupBy: ['session.status'],
  393. ...getReleaseParams({location, releaseBounds}),
  394. shouldFilterSessionsInTimeWindow: true,
  395. };
  396. const defaultDateTimeSelected = !period && !start && !end;
  397. const releaseBoundsLabel =
  398. releaseBounds.type === 'clamped'
  399. ? t('Clamped Release Period')
  400. : t('Entire Release Period');
  401. return (
  402. <SessionsRequest {...sessionsRequestProps}>
  403. {({
  404. loading: allReleasesLoading,
  405. reloading: allReleasesReloading,
  406. errored: allReleasesErrored,
  407. response: allReleases,
  408. }) => (
  409. <SessionsRequest
  410. {...sessionsRequestProps}
  411. query={searchReleaseVersion(version)}
  412. >
  413. {({
  414. loading: thisReleaseLoading,
  415. reloading: thisReleaseReloading,
  416. errored: thisReleaseErrored,
  417. response: thisRelease,
  418. }) => {
  419. const loading = allReleasesLoading || thisReleaseLoading;
  420. const reloading = allReleasesReloading || thisReleaseReloading;
  421. const errored = allReleasesErrored || thisReleaseErrored;
  422. return (
  423. <Layout.Body>
  424. <Layout.Main>
  425. {isReleaseArchived(release) && (
  426. <ReleaseArchivedNotice
  427. onRestore={() => this.handleRestore(project, refetchData)}
  428. />
  429. )}
  430. <ReleaseDetailsPageFilters>
  431. <EnvironmentPageFilter />
  432. <TimeRangeSelector
  433. relative={
  434. period ??
  435. (defaultDateTimeSelected ? RELEASE_PERIOD_KEY : null)
  436. }
  437. start={start ?? null}
  438. end={end ?? null}
  439. utc={utc ?? null}
  440. onChange={this.handleDateChange}
  441. menuTitle={t('Filter Time Range')}
  442. triggerLabel={
  443. defaultDateTimeSelected ? releaseBoundsLabel : null
  444. }
  445. relativeOptions={({defaultOptions, arbitraryOptions}) =>
  446. releaseBounds.type !== 'ancient'
  447. ? {
  448. [RELEASE_PERIOD_KEY]: (
  449. <Fragment>
  450. {releaseBoundsLabel}
  451. <br />
  452. <ReleaseBoundsDescription
  453. primary={defaultDateTimeSelected}
  454. >
  455. <DateTime date={releaseBounds.releaseStart} />
  456. –<DateTime date={releaseBounds.releaseEnd} />
  457. </ReleaseBoundsDescription>
  458. </Fragment>
  459. ),
  460. ...defaultOptions,
  461. ...arbitraryOptions,
  462. }
  463. : {...defaultOptions, ...arbitraryOptions}
  464. }
  465. defaultPeriod={
  466. releaseBounds.type !== 'ancient'
  467. ? RELEASE_PERIOD_KEY
  468. : '90d'
  469. }
  470. defaultAbsolute={{
  471. start: moment(releaseBounds.releaseStart)
  472. .subtract(1, 'hour')
  473. .toDate(),
  474. end: releaseBounds.releaseEnd
  475. ? moment(releaseBounds.releaseEnd)
  476. .add(1, 'hour')
  477. .toDate()
  478. : undefined,
  479. }}
  480. />
  481. </ReleaseDetailsPageFilters>
  482. {(hasDiscover || hasPerformance || hasHealthData) && (
  483. <ReleaseComparisonChart
  484. release={release}
  485. releaseSessions={thisRelease}
  486. allSessions={allReleases}
  487. platform={project.platform}
  488. location={location}
  489. loading={loading}
  490. reloading={reloading}
  491. errored={errored}
  492. project={project}
  493. organization={organization}
  494. api={api}
  495. hasHealthData={hasHealthData}
  496. />
  497. )}
  498. <ReleaseIssues
  499. organization={organization}
  500. version={version}
  501. location={location}
  502. releaseBounds={releaseBounds}
  503. queryFilterDescription={t('In this release')}
  504. withChart
  505. />
  506. <Feature features={['performance-view']}>
  507. {hasReleaseComparisonPerformance ? (
  508. <PerformanceCardTable
  509. organization={organization}
  510. project={project}
  511. location={location}
  512. allReleasesEventView={allReleasesPerformanceView}
  513. releaseEventView={releasePerformanceView}
  514. performanceType={performanceType}
  515. />
  516. ) : (
  517. <TransactionsList
  518. location={location}
  519. organization={organization}
  520. eventView={releaseEventView}
  521. trendView={releaseTrendView}
  522. selected={selectedSort}
  523. options={sortOptions}
  524. handleDropdownChange={
  525. this.handleTransactionsListSortChange
  526. }
  527. titles={titles}
  528. generateLink={generateLink}
  529. />
  530. )}
  531. </Feature>
  532. </Layout.Main>
  533. <Layout.Side>
  534. <ReleaseStats
  535. organization={organization}
  536. release={release}
  537. project={project}
  538. />
  539. {hasHealthData && (
  540. <ReleaseAdoption
  541. releaseSessions={thisRelease}
  542. allSessions={allReleases}
  543. loading={loading}
  544. reloading={reloading}
  545. errored={errored}
  546. release={release}
  547. project={project}
  548. environment={environments}
  549. />
  550. )}
  551. <ProjectReleaseDetails
  552. release={release}
  553. releaseMeta={releaseMeta}
  554. projectSlug={project.slug}
  555. />
  556. {commitCount > 0 && (
  557. <CommitAuthorBreakdown
  558. version={version}
  559. orgId={organization.slug}
  560. projectSlug={project.slug}
  561. />
  562. )}
  563. {releaseMeta.projects.length > 1 && (
  564. <OtherProjects
  565. projects={releaseMeta.projects.filter(
  566. p => p.slug !== project.slug
  567. )}
  568. location={location}
  569. version={version}
  570. organization={organization}
  571. />
  572. )}
  573. {hasHealthData && (
  574. <TotalCrashFreeUsers
  575. organization={organization}
  576. version={version}
  577. projectSlug={project.slug}
  578. location={location}
  579. />
  580. )}
  581. {deploys.length > 0 && (
  582. <Deploys
  583. version={version}
  584. orgSlug={organization.slug}
  585. deploys={deploys}
  586. projectId={project.id}
  587. />
  588. )}
  589. </Layout.Side>
  590. </Layout.Body>
  591. );
  592. }}
  593. </SessionsRequest>
  594. )}
  595. </SessionsRequest>
  596. );
  597. }}
  598. </ReleaseContext.Consumer>
  599. );
  600. }
  601. }
  602. function generateTransactionLink(
  603. version: string,
  604. projectId: number,
  605. selection: PageFilters,
  606. value: string
  607. ) {
  608. return (
  609. organization: Organization,
  610. tableRow: TableDataRow,
  611. _query: Query
  612. ): LocationDescriptor => {
  613. const {transaction} = tableRow;
  614. const trendTransaction = ['regression', 'improved'].includes(value);
  615. const {environments, datetime} = selection;
  616. const {start, end, period} = datetime;
  617. return transactionSummaryRouteWithQuery({
  618. orgSlug: organization.slug,
  619. transaction: transaction! as string,
  620. query: {
  621. query: trendTransaction ? '' : `release:${version}`,
  622. environment: environments,
  623. start: start ? getUtcDateString(start) : undefined,
  624. end: end ? getUtcDateString(end) : undefined,
  625. statsPeriod: period,
  626. },
  627. projectID: projectId.toString(),
  628. display: trendTransaction ? DisplayModes.TREND : DisplayModes.DURATION,
  629. });
  630. };
  631. }
  632. function getDropdownOptions(): DropdownOption[] {
  633. return [
  634. {
  635. sort: {kind: 'desc', field: 'failure_count'},
  636. value: TransactionsListOption.FAILURE_COUNT,
  637. label: t('Failing Transactions'),
  638. },
  639. {
  640. sort: {kind: 'desc', field: 'epm'},
  641. value: TransactionsListOption.TPM,
  642. label: t('Frequent Transactions'),
  643. },
  644. {
  645. sort: {kind: 'desc', field: 'p50'},
  646. value: TransactionsListOption.SLOW,
  647. label: t('Slow Transactions'),
  648. },
  649. {
  650. sort: {kind: 'desc', field: 'p75_measurements_lcp'},
  651. value: TransactionsListOption.SLOW_LCP,
  652. label: t('Slow LCP'),
  653. },
  654. {
  655. sort: {kind: 'desc', field: 'trend_percentage()'},
  656. query: [['confidence()', '>6']],
  657. trendType: TrendChangeType.REGRESSION,
  658. value: TransactionsListOption.REGRESSION,
  659. label: t('Trending Regressions'),
  660. },
  661. {
  662. sort: {kind: 'asc', field: 'trend_percentage()'},
  663. query: [['confidence()', '>6']],
  664. trendType: TrendChangeType.IMPROVED,
  665. value: TransactionsListOption.IMPROVEMENT,
  666. label: t('Trending Improvements'),
  667. },
  668. ];
  669. }
  670. function getTransactionsListSort(location: Location): {
  671. selectedSort: DropdownOption;
  672. sortOptions: DropdownOption[];
  673. } {
  674. const sortOptions = getDropdownOptions();
  675. const urlParam = decodeScalar(
  676. location.query.showTransactions,
  677. TransactionsListOption.FAILURE_COUNT
  678. );
  679. const selectedSort = sortOptions.find(opt => opt.value === urlParam) || sortOptions[0];
  680. return {selectedSort, sortOptions};
  681. }
  682. const ReleaseDetailsPageFilters = styled('div')`
  683. display: grid;
  684. grid-template-columns: minmax(0, max-content) 1fr;
  685. gap: ${space(2)};
  686. margin-bottom: ${space(2)};
  687. @media (max-width: ${p => p.theme.breakpoints.small}) {
  688. grid-template-columns: auto;
  689. }
  690. `;
  691. const ReleaseBoundsDescription = styled('span')<{primary: boolean}>`
  692. font-size: ${p => p.theme.fontSizeSmall};
  693. color: ${p => (p.primary ? p.theme.activeText : p.theme.subText)};
  694. `;
  695. export default withApi(withPageFilters(withOrganization(ReleaseOverview)));