mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
761 lines
27 KiB
TypeScript
761 lines
27 KiB
TypeScript
import {Fragment} from 'react';
|
||
import type {RouteComponentProps} from 'react-router';
|
||
import {browserHistory} from 'react-router';
|
||
import styled from '@emotion/styled';
|
||
import type {Location, LocationDescriptor, Query} from 'history';
|
||
import moment from 'moment';
|
||
|
||
import {restoreRelease} from 'sentry/actionCreators/release';
|
||
import {Client} from 'sentry/api';
|
||
import Feature from 'sentry/components/acl/feature';
|
||
import SessionsRequest from 'sentry/components/charts/sessionsRequest';
|
||
import type {DateTimeObject} from 'sentry/components/charts/utils';
|
||
import DateTime from 'sentry/components/dateTime';
|
||
import PerformanceCardTable from 'sentry/components/discover/performanceCardTable';
|
||
import type {DropdownOption} from 'sentry/components/discover/transactionsList';
|
||
import TransactionsList from 'sentry/components/discover/transactionsList';
|
||
import * as Layout from 'sentry/components/layouts/thirds';
|
||
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
|
||
import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
|
||
import type {ChangeData} from 'sentry/components/timeRangeSelector';
|
||
import {TimeRangeSelector} from 'sentry/components/timeRangeSelector';
|
||
import {t} from 'sentry/locale';
|
||
import {space} from 'sentry/styles/space';
|
||
import type {NewQuery, Organization, PageFilters, ReleaseProject} from 'sentry/types';
|
||
import {SessionFieldWithOperation} from 'sentry/types';
|
||
import {getUtcDateString} from 'sentry/utils/dates';
|
||
import type {TableDataRow} from 'sentry/utils/discover/discoverQuery';
|
||
import EventView from 'sentry/utils/discover/eventView';
|
||
import {MobileVital, SpanOpBreakdown, WebVital} from 'sentry/utils/fields';
|
||
import {formatVersion} from 'sentry/utils/formatters';
|
||
import {decodeScalar} from 'sentry/utils/queryString';
|
||
import routeTitleGen from 'sentry/utils/routeTitle';
|
||
import withApi from 'sentry/utils/withApi';
|
||
import withOrganization from 'sentry/utils/withOrganization';
|
||
import withPageFilters from 'sentry/utils/withPageFilters';
|
||
import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
|
||
import {
|
||
DisplayModes,
|
||
transactionSummaryRouteWithQuery,
|
||
} from 'sentry/views/performance/transactionSummary/utils';
|
||
import type {TrendView} from 'sentry/views/performance/trends/types';
|
||
import {TrendChangeType} from 'sentry/views/performance/trends/types';
|
||
import {
|
||
platformToPerformanceType,
|
||
ProjectPerformanceType,
|
||
} from 'sentry/views/performance/utils';
|
||
|
||
import type {ReleaseBounds} from '../../utils';
|
||
import {getReleaseParams, isReleaseArchived, searchReleaseVersion} from '../../utils';
|
||
import {ReleaseContext} from '..';
|
||
|
||
import CommitAuthorBreakdown from './sidebar/commitAuthorBreakdown';
|
||
import Deploys from './sidebar/deploys';
|
||
import OtherProjects from './sidebar/otherProjects';
|
||
import ProjectReleaseDetails from './sidebar/projectReleaseDetails';
|
||
import ReleaseAdoption from './sidebar/releaseAdoption';
|
||
import ReleaseStats from './sidebar/releaseStats';
|
||
import ThresholdStatuses from './sidebar/thresholdStatuses';
|
||
import TotalCrashFreeUsers from './sidebar/totalCrashFreeUsers';
|
||
import ReleaseArchivedNotice from './releaseArchivedNotice';
|
||
import ReleaseComparisonChart from './releaseComparisonChart';
|
||
import ReleaseIssues from './releaseIssues';
|
||
|
||
const RELEASE_PERIOD_KEY = 'release';
|
||
|
||
export enum TransactionsListOption {
|
||
FAILURE_COUNT = 'failure_count',
|
||
TPM = 'tpm',
|
||
SLOW = 'slow',
|
||
SLOW_LCP = 'slow_lcp',
|
||
REGRESSION = 'regression',
|
||
IMPROVEMENT = 'improved',
|
||
}
|
||
|
||
type RouteParams = {
|
||
orgId: string;
|
||
release: string;
|
||
};
|
||
|
||
type Props = RouteComponentProps<RouteParams, {}> & {
|
||
api: Client;
|
||
organization: Organization;
|
||
selection: PageFilters;
|
||
};
|
||
|
||
class ReleaseOverview extends DeprecatedAsyncView<Props> {
|
||
getTitle() {
|
||
const {params, organization} = this.props;
|
||
return routeTitleGen(
|
||
t('Release %s', formatVersion(params.release)),
|
||
organization.slug,
|
||
false
|
||
);
|
||
}
|
||
|
||
handleRestore = async (project: ReleaseProject, successCallback: () => void) => {
|
||
const {params, organization} = this.props;
|
||
|
||
try {
|
||
await restoreRelease(new Client(), {
|
||
orgSlug: organization.slug,
|
||
projectSlug: project.slug,
|
||
releaseVersion: params.release,
|
||
});
|
||
successCallback();
|
||
} catch {
|
||
// do nothing, action creator is already displaying error message
|
||
}
|
||
};
|
||
|
||
getReleaseEventView(
|
||
version: string,
|
||
projectId: number,
|
||
selectedSort: DropdownOption,
|
||
releaseBounds: ReleaseBounds
|
||
): EventView {
|
||
const {selection, location} = this.props;
|
||
const {environments} = selection;
|
||
|
||
const {start, end, statsPeriod} = getReleaseParams({
|
||
location,
|
||
releaseBounds,
|
||
});
|
||
|
||
const baseQuery: NewQuery = {
|
||
id: undefined,
|
||
version: 2,
|
||
name: `Release ${formatVersion(version)}`,
|
||
query: `event.type:transaction ${searchReleaseVersion(version)}`,
|
||
fields: ['transaction', 'failure_count()', 'epm()', 'p50()'],
|
||
orderby: '-failure_count',
|
||
range: statsPeriod || undefined,
|
||
environment: environments,
|
||
projects: [projectId],
|
||
start: start ? getUtcDateString(start) : undefined,
|
||
end: end ? getUtcDateString(end) : undefined,
|
||
};
|
||
|
||
switch (selectedSort.value) {
|
||
case TransactionsListOption.SLOW_LCP:
|
||
return EventView.fromSavedQuery({
|
||
...baseQuery,
|
||
query: `event.type:transaction release:${version} epm():>0.01 has:measurements.lcp`,
|
||
fields: ['transaction', 'failure_count()', 'epm()', 'p75(measurements.lcp)'],
|
||
orderby: 'p75_measurements_lcp',
|
||
});
|
||
case TransactionsListOption.SLOW:
|
||
return EventView.fromSavedQuery({
|
||
...baseQuery,
|
||
query: `event.type:transaction release:${version} epm():>0.01`,
|
||
});
|
||
case TransactionsListOption.FAILURE_COUNT:
|
||
return EventView.fromSavedQuery({
|
||
...baseQuery,
|
||
query: `event.type:transaction release:${version} failure_count():>0`,
|
||
});
|
||
default:
|
||
return EventView.fromSavedQuery(baseQuery);
|
||
}
|
||
}
|
||
|
||
getReleaseTrendView(
|
||
version: string,
|
||
projectId: number,
|
||
versionDate: string,
|
||
releaseBounds: ReleaseBounds
|
||
): EventView {
|
||
const {selection, location} = this.props;
|
||
const {environments} = selection;
|
||
|
||
const {start, end, statsPeriod} = getReleaseParams({
|
||
location,
|
||
releaseBounds,
|
||
});
|
||
|
||
const trendView = EventView.fromSavedQuery({
|
||
id: undefined,
|
||
version: 2,
|
||
name: `Release ${formatVersion(version)}`,
|
||
fields: ['transaction'],
|
||
query: 'tpm():>0.01 trend_percentage():>0%',
|
||
range: statsPeriod || undefined,
|
||
environment: environments,
|
||
projects: [projectId],
|
||
start: start ? getUtcDateString(start) : undefined,
|
||
end: end ? getUtcDateString(end) : undefined,
|
||
}) as TrendView;
|
||
trendView.middle = versionDate;
|
||
return trendView;
|
||
}
|
||
|
||
getReleasePerformanceEventView(
|
||
performanceType: string,
|
||
baseQuery: NewQuery
|
||
): EventView {
|
||
const eventView =
|
||
performanceType === ProjectPerformanceType.FRONTEND
|
||
? (EventView.fromSavedQuery({
|
||
...baseQuery,
|
||
fields: [
|
||
...baseQuery.fields,
|
||
`p75(${WebVital.FCP})`,
|
||
`p75(${WebVital.FID})`,
|
||
`p75(${WebVital.LCP})`,
|
||
`p75(${WebVital.CLS})`,
|
||
`p75(${SpanOpBreakdown.SPANS_HTTP})`,
|
||
`p75(${SpanOpBreakdown.SPANS_BROWSER})`,
|
||
`p75(${SpanOpBreakdown.SPANS_RESOURCE})`,
|
||
],
|
||
}) as EventView)
|
||
: performanceType === ProjectPerformanceType.BACKEND
|
||
? (EventView.fromSavedQuery({
|
||
...baseQuery,
|
||
fields: [
|
||
...baseQuery.fields,
|
||
'apdex()',
|
||
'p75(spans.http)',
|
||
'p75(spans.db)',
|
||
],
|
||
}) as EventView)
|
||
: performanceType === ProjectPerformanceType.MOBILE
|
||
? (EventView.fromSavedQuery({
|
||
...baseQuery,
|
||
fields: [
|
||
...baseQuery.fields,
|
||
`p75(${MobileVital.APP_START_COLD})`,
|
||
`p75(${MobileVital.APP_START_WARM})`,
|
||
`p75(${MobileVital.FRAMES_SLOW})`,
|
||
`p75(${MobileVital.FRAMES_FROZEN})`,
|
||
],
|
||
}) as EventView)
|
||
: (EventView.fromSavedQuery({
|
||
...baseQuery,
|
||
}) as EventView);
|
||
|
||
return eventView;
|
||
}
|
||
|
||
getAllReleasesPerformanceView(
|
||
projectId: number,
|
||
performanceType: string,
|
||
releaseBounds: ReleaseBounds
|
||
) {
|
||
const {selection, location} = this.props;
|
||
const {environments} = selection;
|
||
|
||
const {start, end, statsPeriod} = getReleaseParams({
|
||
location,
|
||
releaseBounds,
|
||
});
|
||
|
||
const baseQuery: NewQuery = {
|
||
id: undefined,
|
||
version: 2,
|
||
name: 'All Releases',
|
||
query: 'event.type:transaction',
|
||
fields: ['user_misery()'],
|
||
range: statsPeriod || undefined,
|
||
environment: environments,
|
||
projects: [projectId],
|
||
start: start ? getUtcDateString(start) : undefined,
|
||
end: end ? getUtcDateString(end) : undefined,
|
||
};
|
||
|
||
return this.getReleasePerformanceEventView(performanceType, baseQuery);
|
||
}
|
||
|
||
getReleasePerformanceView(
|
||
version: string,
|
||
projectId: number,
|
||
performanceType: string,
|
||
releaseBounds: ReleaseBounds
|
||
) {
|
||
const {selection, location} = this.props;
|
||
const {environments} = selection;
|
||
|
||
const {start, end, statsPeriod} = getReleaseParams({
|
||
location,
|
||
releaseBounds,
|
||
});
|
||
|
||
const baseQuery: NewQuery = {
|
||
id: undefined,
|
||
version: 2,
|
||
name: `Release:${version}`,
|
||
query: `event.type:transaction release:${version}`,
|
||
fields: ['user_misery()'],
|
||
range: statsPeriod || undefined,
|
||
environment: environments,
|
||
projects: [projectId],
|
||
start: start ? getUtcDateString(start) : undefined,
|
||
end: end ? getUtcDateString(end) : undefined,
|
||
};
|
||
|
||
return this.getReleasePerformanceEventView(performanceType, baseQuery);
|
||
}
|
||
|
||
get pageDateTime(): DateTimeObject {
|
||
const query = this.props.location.query;
|
||
|
||
const {start, end, statsPeriod} = normalizeDateTimeParams(query, {
|
||
allowEmptyPeriod: true,
|
||
allowAbsoluteDatetime: true,
|
||
allowAbsolutePageDatetime: true,
|
||
});
|
||
|
||
if (statsPeriod) {
|
||
return {period: statsPeriod};
|
||
}
|
||
|
||
if (start && end) {
|
||
return {
|
||
start: moment.utc(start).format(),
|
||
end: moment.utc(end).format(),
|
||
};
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
handleTransactionsListSortChange = (value: string) => {
|
||
const {location} = this.props;
|
||
|
||
const target = {
|
||
pathname: location.pathname,
|
||
query: {...location.query, showTransactions: value, transactionCursor: undefined},
|
||
};
|
||
browserHistory.push(target);
|
||
};
|
||
|
||
handleDateChange = (datetime: ChangeData) => {
|
||
const {router, location} = this.props;
|
||
const {start, end, relative, utc} = datetime;
|
||
|
||
if (start && end) {
|
||
const parser = utc ? moment.utc : moment;
|
||
|
||
router.push({
|
||
...location,
|
||
query: {
|
||
...location.query,
|
||
pageStatsPeriod: undefined,
|
||
pageStart: parser(start).format(),
|
||
pageEnd: parser(end).format(),
|
||
pageUtc: utc ?? undefined,
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
|
||
router.push({
|
||
...location,
|
||
query: {
|
||
...location.query,
|
||
pageStatsPeriod: relative === RELEASE_PERIOD_KEY ? undefined : relative,
|
||
pageStart: undefined,
|
||
pageEnd: undefined,
|
||
pageUtc: undefined,
|
||
},
|
||
});
|
||
};
|
||
|
||
render() {
|
||
const {organization, selection, location, api} = this.props;
|
||
const {start, end, period, utc} = this.pageDateTime;
|
||
const hasV2ReleaseUIEnabled =
|
||
organization.features.includes('releases-v2-internal') ||
|
||
organization.features.includes('releases-v2') ||
|
||
organization.features.includes('releases-v2-st');
|
||
|
||
return (
|
||
<ReleaseContext.Consumer>
|
||
{({
|
||
release,
|
||
project,
|
||
deploys,
|
||
releaseMeta,
|
||
refetchData,
|
||
hasHealthData,
|
||
releaseBounds,
|
||
}) => {
|
||
const {commitCount, version} = release;
|
||
const hasDiscover = organization.features.includes('discover-basic');
|
||
const hasPerformance = organization.features.includes('performance-view');
|
||
const hasReleaseComparisonPerformance = organization.features.includes(
|
||
'release-comparison-performance'
|
||
);
|
||
const {environments} = selection;
|
||
const performanceType = platformToPerformanceType([project], [project.id]);
|
||
const {selectedSort, sortOptions} = getTransactionsListSort(location);
|
||
const releaseEventView = this.getReleaseEventView(
|
||
version,
|
||
project.id,
|
||
selectedSort,
|
||
releaseBounds
|
||
);
|
||
const titles =
|
||
selectedSort.value !== TransactionsListOption.SLOW_LCP
|
||
? [t('transaction'), t('failure_count()'), t('tpm()'), t('p50()')]
|
||
: [t('transaction'), t('failure_count()'), t('tpm()'), t('p75(lcp)')];
|
||
const releaseTrendView = this.getReleaseTrendView(
|
||
version,
|
||
project.id,
|
||
releaseMeta.released,
|
||
releaseBounds
|
||
);
|
||
const allReleasesPerformanceView = this.getAllReleasesPerformanceView(
|
||
project.id,
|
||
performanceType,
|
||
releaseBounds
|
||
);
|
||
const releasePerformanceView = this.getReleasePerformanceView(
|
||
version,
|
||
project.id,
|
||
performanceType,
|
||
releaseBounds
|
||
);
|
||
|
||
const generateLink = {
|
||
transaction: generateTransactionLink(
|
||
version,
|
||
project.id,
|
||
selection,
|
||
location.query.showTransactions
|
||
),
|
||
};
|
||
|
||
const sessionsRequestProps: Omit<SessionsRequest['props'], 'children'> = {
|
||
api,
|
||
organization,
|
||
field: [
|
||
SessionFieldWithOperation.USERS,
|
||
SessionFieldWithOperation.SESSIONS,
|
||
SessionFieldWithOperation.DURATION,
|
||
],
|
||
groupBy: ['session.status'],
|
||
...getReleaseParams({location, releaseBounds}),
|
||
shouldFilterSessionsInTimeWindow: true,
|
||
};
|
||
|
||
const defaultDateTimeSelected = !period && !start && !end;
|
||
|
||
const releaseBoundsLabel =
|
||
releaseBounds.type === 'clamped'
|
||
? t('Clamped Release Period')
|
||
: t('Entire Release Period');
|
||
|
||
return (
|
||
<SessionsRequest {...sessionsRequestProps}>
|
||
{({
|
||
loading: allReleasesLoading,
|
||
reloading: allReleasesReloading,
|
||
errored: allReleasesErrored,
|
||
response: allReleases,
|
||
}) => (
|
||
<SessionsRequest
|
||
{...sessionsRequestProps}
|
||
query={searchReleaseVersion(version)}
|
||
>
|
||
{({
|
||
loading: thisReleaseLoading,
|
||
reloading: thisReleaseReloading,
|
||
errored: thisReleaseErrored,
|
||
response: thisRelease,
|
||
}) => {
|
||
const loading = allReleasesLoading || thisReleaseLoading;
|
||
const reloading = allReleasesReloading || thisReleaseReloading;
|
||
const errored = allReleasesErrored || thisReleaseErrored;
|
||
return (
|
||
<Layout.Body>
|
||
<Layout.Main>
|
||
{isReleaseArchived(release) && (
|
||
<ReleaseArchivedNotice
|
||
onRestore={() => this.handleRestore(project, refetchData)}
|
||
/>
|
||
)}
|
||
<ReleaseDetailsPageFilters>
|
||
<EnvironmentPageFilter />
|
||
<TimeRangeSelector
|
||
relative={
|
||
period ??
|
||
(defaultDateTimeSelected ? RELEASE_PERIOD_KEY : null)
|
||
}
|
||
start={start ?? null}
|
||
end={end ?? null}
|
||
utc={utc ?? null}
|
||
onChange={this.handleDateChange}
|
||
menuTitle={t('Filter Time Range')}
|
||
triggerLabel={
|
||
defaultDateTimeSelected ? releaseBoundsLabel : null
|
||
}
|
||
relativeOptions={({defaultOptions, arbitraryOptions}) =>
|
||
releaseBounds.type !== 'ancient'
|
||
? {
|
||
[RELEASE_PERIOD_KEY]: (
|
||
<Fragment>
|
||
{releaseBoundsLabel}
|
||
<br />
|
||
<ReleaseBoundsDescription
|
||
primary={defaultDateTimeSelected}
|
||
>
|
||
<DateTime date={releaseBounds.releaseStart} />
|
||
–<DateTime date={releaseBounds.releaseEnd} />
|
||
</ReleaseBoundsDescription>
|
||
</Fragment>
|
||
),
|
||
...defaultOptions,
|
||
...arbitraryOptions,
|
||
}
|
||
: {...defaultOptions, ...arbitraryOptions}
|
||
}
|
||
defaultPeriod={
|
||
releaseBounds.type !== 'ancient'
|
||
? RELEASE_PERIOD_KEY
|
||
: '90d'
|
||
}
|
||
defaultAbsolute={{
|
||
start: moment(releaseBounds.releaseStart)
|
||
.subtract(1, 'hour')
|
||
.toDate(),
|
||
end: releaseBounds.releaseEnd
|
||
? moment(releaseBounds.releaseEnd)
|
||
.add(1, 'hour')
|
||
.toDate()
|
||
: undefined,
|
||
}}
|
||
/>
|
||
</ReleaseDetailsPageFilters>
|
||
{(hasDiscover || hasPerformance || hasHealthData) && (
|
||
<ReleaseComparisonChart
|
||
release={release}
|
||
releaseSessions={thisRelease}
|
||
allSessions={allReleases}
|
||
platform={project.platform}
|
||
location={location}
|
||
loading={loading}
|
||
reloading={reloading}
|
||
errored={errored}
|
||
project={project}
|
||
organization={organization}
|
||
api={api}
|
||
hasHealthData={hasHealthData}
|
||
/>
|
||
)}
|
||
<ReleaseIssues
|
||
organization={organization}
|
||
version={version}
|
||
location={location}
|
||
releaseBounds={releaseBounds}
|
||
queryFilterDescription={t('In this release')}
|
||
withChart
|
||
/>
|
||
<Feature features="performance-view">
|
||
{hasReleaseComparisonPerformance ? (
|
||
<PerformanceCardTable
|
||
organization={organization}
|
||
project={project}
|
||
location={location}
|
||
allReleasesEventView={allReleasesPerformanceView}
|
||
releaseEventView={releasePerformanceView}
|
||
performanceType={performanceType}
|
||
/>
|
||
) : (
|
||
<TransactionsList
|
||
location={location}
|
||
organization={organization}
|
||
eventView={releaseEventView}
|
||
trendView={releaseTrendView}
|
||
selected={selectedSort}
|
||
options={sortOptions}
|
||
handleDropdownChange={
|
||
this.handleTransactionsListSortChange
|
||
}
|
||
titles={titles}
|
||
generateLink={generateLink}
|
||
supportsInvestigationRule={false}
|
||
/>
|
||
)}
|
||
</Feature>
|
||
</Layout.Main>
|
||
<Layout.Side>
|
||
<ReleaseStats
|
||
organization={organization}
|
||
release={release}
|
||
project={project}
|
||
/>
|
||
{hasV2ReleaseUIEnabled && (
|
||
<ThresholdStatuses
|
||
project={project}
|
||
release={release}
|
||
organization={organization}
|
||
selectedEnvs={selection.environments}
|
||
/>
|
||
)}
|
||
{hasHealthData && (
|
||
<ReleaseAdoption
|
||
releaseSessions={thisRelease}
|
||
allSessions={allReleases}
|
||
loading={loading}
|
||
reloading={reloading}
|
||
errored={errored}
|
||
release={release}
|
||
project={project}
|
||
environment={environments}
|
||
/>
|
||
)}
|
||
<ProjectReleaseDetails
|
||
release={release}
|
||
releaseMeta={releaseMeta}
|
||
projectSlug={project.slug}
|
||
/>
|
||
{commitCount > 0 && (
|
||
<CommitAuthorBreakdown
|
||
version={version}
|
||
orgId={organization.slug}
|
||
projectSlug={project.slug}
|
||
/>
|
||
)}
|
||
{releaseMeta.projects.length > 1 && (
|
||
<OtherProjects
|
||
projects={releaseMeta.projects.filter(
|
||
p => p.slug !== project.slug
|
||
)}
|
||
location={location}
|
||
version={version}
|
||
organization={organization}
|
||
/>
|
||
)}
|
||
{hasHealthData && (
|
||
<TotalCrashFreeUsers
|
||
organization={organization}
|
||
version={version}
|
||
projectSlug={project.slug}
|
||
location={location}
|
||
/>
|
||
)}
|
||
{deploys.length > 0 && (
|
||
<Deploys
|
||
version={version}
|
||
orgSlug={organization.slug}
|
||
deploys={deploys}
|
||
projectId={project.id}
|
||
/>
|
||
)}
|
||
</Layout.Side>
|
||
</Layout.Body>
|
||
);
|
||
}}
|
||
</SessionsRequest>
|
||
)}
|
||
</SessionsRequest>
|
||
);
|
||
}}
|
||
</ReleaseContext.Consumer>
|
||
);
|
||
}
|
||
}
|
||
|
||
function generateTransactionLink(
|
||
version: string,
|
||
projectId: number,
|
||
selection: PageFilters,
|
||
value: string
|
||
) {
|
||
return (
|
||
organization: Organization,
|
||
tableRow: TableDataRow,
|
||
_query: Query
|
||
): LocationDescriptor => {
|
||
const {transaction} = tableRow;
|
||
const trendTransaction = ['regression', 'improved'].includes(value);
|
||
const {environments, datetime} = selection;
|
||
const {start, end, period} = datetime;
|
||
|
||
return transactionSummaryRouteWithQuery({
|
||
orgSlug: organization.slug,
|
||
transaction: transaction! as string,
|
||
query: {
|
||
query: trendTransaction ? '' : `release:${version}`,
|
||
environment: environments,
|
||
start: start ? getUtcDateString(start) : undefined,
|
||
end: end ? getUtcDateString(end) : undefined,
|
||
statsPeriod: period,
|
||
},
|
||
projectID: projectId.toString(),
|
||
display: trendTransaction ? DisplayModes.TREND : DisplayModes.DURATION,
|
||
});
|
||
};
|
||
}
|
||
|
||
function getDropdownOptions(): DropdownOption[] {
|
||
return [
|
||
{
|
||
sort: {kind: 'desc', field: 'failure_count'},
|
||
value: TransactionsListOption.FAILURE_COUNT,
|
||
label: t('Failing Transactions'),
|
||
},
|
||
{
|
||
sort: {kind: 'desc', field: 'epm'},
|
||
value: TransactionsListOption.TPM,
|
||
label: t('Frequent Transactions'),
|
||
},
|
||
{
|
||
sort: {kind: 'desc', field: 'p50'},
|
||
value: TransactionsListOption.SLOW,
|
||
label: t('Slow Transactions'),
|
||
},
|
||
{
|
||
sort: {kind: 'desc', field: 'p75_measurements_lcp'},
|
||
value: TransactionsListOption.SLOW_LCP,
|
||
label: t('Slow LCP'),
|
||
},
|
||
{
|
||
sort: {kind: 'desc', field: 'trend_percentage()'},
|
||
query: [['confidence()', '>6']],
|
||
trendType: TrendChangeType.REGRESSION,
|
||
value: TransactionsListOption.REGRESSION,
|
||
label: t('Trending Regressions'),
|
||
},
|
||
{
|
||
sort: {kind: 'asc', field: 'trend_percentage()'},
|
||
query: [['confidence()', '>6']],
|
||
trendType: TrendChangeType.IMPROVED,
|
||
value: TransactionsListOption.IMPROVEMENT,
|
||
label: t('Trending Improvements'),
|
||
},
|
||
];
|
||
}
|
||
|
||
function getTransactionsListSort(location: Location): {
|
||
selectedSort: DropdownOption;
|
||
sortOptions: DropdownOption[];
|
||
} {
|
||
const sortOptions = getDropdownOptions();
|
||
const urlParam = decodeScalar(
|
||
location.query.showTransactions,
|
||
TransactionsListOption.FAILURE_COUNT
|
||
);
|
||
const selectedSort = sortOptions.find(opt => opt.value === urlParam) || sortOptions[0];
|
||
return {selectedSort, sortOptions};
|
||
}
|
||
|
||
const ReleaseDetailsPageFilters = styled('div')`
|
||
display: grid;
|
||
grid-template-columns: minmax(0, max-content) 1fr;
|
||
gap: ${space(2)};
|
||
margin-bottom: ${space(2)};
|
||
|
||
@media (max-width: ${p => p.theme.breakpoints.small}) {
|
||
grid-template-columns: auto;
|
||
}
|
||
`;
|
||
|
||
const ReleaseBoundsDescription = styled('span')<{primary: boolean}>`
|
||
font-size: ${p => p.theme.fontSizeSmall};
|
||
color: ${p => (p.primary ? p.theme.activeText : p.theme.subText)};
|
||
`;
|
||
|
||
ReleaseOverview.contextType = ReleaseContext;
|
||
export default withApi(withPageFilters(withOrganization(ReleaseOverview)));
|