index.tsx 25 KB

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