index.tsx 25 KB

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