|
@@ -1,22 +1,35 @@
|
|
|
import {Fragment} from 'react';
|
|
|
import {css} from '@emotion/react';
|
|
|
|
|
|
+import useReleaseSessions from 'sentry/components/devtoolbar/components/releases/useReleaseSessions';
|
|
|
import useToolbarRelease from 'sentry/components/devtoolbar/components/releases/useToolbarRelease';
|
|
|
import SentryAppLink from 'sentry/components/devtoolbar/components/sentryAppLink';
|
|
|
import {listItemPlaceholderWrapperCss} from 'sentry/components/devtoolbar/styles/listItem';
|
|
|
+import {
|
|
|
+ infoHeaderCss,
|
|
|
+ subtextCss,
|
|
|
+} from 'sentry/components/devtoolbar/styles/releasesPanel';
|
|
|
import {
|
|
|
resetFlexColumnCss,
|
|
|
resetFlexRowCss,
|
|
|
} from 'sentry/components/devtoolbar/styles/reset';
|
|
|
+import type {ApiResult} from 'sentry/components/devtoolbar/types';
|
|
|
+import EmptyStateWarning from 'sentry/components/emptyStateWarning';
|
|
|
import ProjectBadge from 'sentry/components/idBadge/projectBadge';
|
|
|
+import PanelItem from 'sentry/components/panels/panelItem';
|
|
|
import Placeholder from 'sentry/components/placeholder';
|
|
|
-import TextOverflow from 'sentry/components/textOverflow';
|
|
|
import TimeSince from 'sentry/components/timeSince';
|
|
|
+import {IconArrow} from 'sentry/icons/iconArrow';
|
|
|
+import type {SessionApiResponse} from 'sentry/types/organization';
|
|
|
import type {PlatformKey} from 'sentry/types/project';
|
|
|
import type {Release} from 'sentry/types/release';
|
|
|
+import {defined} from 'sentry/utils';
|
|
|
import {formatVersion} from 'sentry/utils/versions/formatVersion';
|
|
|
import {
|
|
|
- PackageName,
|
|
|
+ Change,
|
|
|
+ type ReleaseComparisonRow,
|
|
|
+} from 'sentry/views/releases/detail/overview/releaseComparisonChart';
|
|
|
+import {
|
|
|
ReleaseInfoHeader,
|
|
|
ReleaseInfoSubheader,
|
|
|
VersionWrapper,
|
|
@@ -28,10 +41,39 @@ import {panelInsetContentCss, panelSectionCss} from '../../styles/panel';
|
|
|
import {smallCss} from '../../styles/typography';
|
|
|
import PanelLayout from '../panelLayout';
|
|
|
|
|
|
-function ReleaseHeader({release, orgSlug}: {orgSlug: string; release: Release}) {
|
|
|
+const estimateSize = 81;
|
|
|
+const placeholderHeight = `${estimateSize - 8}px`; // The real height of the items, minus the padding-block value
|
|
|
+
|
|
|
+function getCrashFreeRate(data: ApiResult<SessionApiResponse>): number {
|
|
|
+ // if `crash_free_rate(session)` is undefined
|
|
|
+ // (sometimes the case for brand new releases),
|
|
|
+ // assume it is 100%.
|
|
|
+ // round to 2 decimal points
|
|
|
+ return parseFloat(
|
|
|
+ ((data?.json.groups[0].totals['crash_free_rate(session)'] ?? 1) * 100).toFixed(2)
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function getDiff(
|
|
|
+ diff: string,
|
|
|
+ diffColor: ReleaseComparisonRow['diffColor'],
|
|
|
+ diffDirection: 'up' | 'down' | undefined
|
|
|
+) {
|
|
|
+ return (
|
|
|
+ <Change
|
|
|
+ color={defined(diffColor) ? diffColor : 'black'}
|
|
|
+ css={[resetFlexRowCss, {alignItems: 'center', gap: 'var(--space25)'}]}
|
|
|
+ >
|
|
|
+ {diff}
|
|
|
+ {defined(diffDirection) ? <IconArrow direction={diffDirection} size="xs" /> : null}
|
|
|
+ </Change>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function ReleaseSummary({orgSlug, release}: {orgSlug: string; release: Release}) {
|
|
|
return (
|
|
|
- <div style={{padding: '12px'}}>
|
|
|
- <ReleaseInfoHeader>
|
|
|
+ <PanelItem css={{width: '100%', alignItems: 'flex-start'}}>
|
|
|
+ <ReleaseInfoHeader css={infoHeaderCss}>
|
|
|
<SentryAppLink
|
|
|
to={{
|
|
|
url: `/organizations/${orgSlug}/releases/${encodeURIComponent(release.version)}/`,
|
|
@@ -45,36 +87,150 @@ function ReleaseHeader({release, orgSlug}: {orgSlug: string; release: Release})
|
|
|
)}
|
|
|
</ReleaseInfoHeader>
|
|
|
<ReleaseInfoSubheader
|
|
|
- style={{display: 'flex', flexDirection: 'column', alignItems: 'flex-start'}}
|
|
|
+ css={[resetFlexColumnCss, subtextCss, {alignItems: 'flex-start'}]}
|
|
|
>
|
|
|
- {release.versionInfo?.package && (
|
|
|
- <PackageName>
|
|
|
- <TextOverflow ellipsisDirection="left">
|
|
|
- {release.versionInfo.package}
|
|
|
- </TextOverflow>
|
|
|
- </PackageName>
|
|
|
- )}
|
|
|
- <span style={{display: 'flex', flexDirection: 'row', gap: '3px'}}>
|
|
|
+ <span css={[resetFlexRowCss, {gap: 'var(--space25)'}]}>
|
|
|
<TimeSince date={release.lastDeploy?.dateFinished || release.dateCreated} />
|
|
|
{release.lastDeploy?.dateFinished &&
|
|
|
` \u007C ${release.lastDeploy.environment}`}
|
|
|
</span>
|
|
|
</ReleaseInfoSubheader>
|
|
|
- </div>
|
|
|
+ </PanelItem>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function CrashFreeRate({
|
|
|
+ prevReleaseVersion,
|
|
|
+ currReleaseVersion,
|
|
|
+}: {
|
|
|
+ currReleaseVersion: string;
|
|
|
+ prevReleaseVersion: string | undefined;
|
|
|
+}) {
|
|
|
+ const {
|
|
|
+ data: currSessionData,
|
|
|
+ isLoading: isCurrLoading,
|
|
|
+ isError: isCurrError,
|
|
|
+ } = useReleaseSessions({
|
|
|
+ releaseVersion: currReleaseVersion,
|
|
|
+ });
|
|
|
+ const {
|
|
|
+ data: prevSessionData,
|
|
|
+ isLoading: isPrevLoading,
|
|
|
+ isError: isPrevError,
|
|
|
+ } = useReleaseSessions({
|
|
|
+ releaseVersion: prevReleaseVersion,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (isCurrError || isPrevError) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isCurrLoading || isPrevLoading) {
|
|
|
+ return (
|
|
|
+ <PanelItem css={{width: '100%', padding: 'var(--space150)'}}>
|
|
|
+ <Placeholder
|
|
|
+ height={placeholderHeight}
|
|
|
+ css={[
|
|
|
+ resetFlexColumnCss,
|
|
|
+ panelSectionCss,
|
|
|
+ panelInsetContentCss,
|
|
|
+ listItemPlaceholderWrapperCss,
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ </PanelItem>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ const currCrashFreeRate = getCrashFreeRate(currSessionData);
|
|
|
+ const prevCrashFreeRate = getCrashFreeRate(prevSessionData);
|
|
|
+ const diff = currCrashFreeRate - prevCrashFreeRate;
|
|
|
+ const sign = Math.sign(diff);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <PanelItem>
|
|
|
+ <div css={infoHeaderCss}>Crash free session rate</div>
|
|
|
+ <ReleaseInfoSubheader css={subtextCss}>
|
|
|
+ <span css={[resetFlexRowCss, {gap: 'var(--space200)'}]}>
|
|
|
+ <span css={resetFlexColumnCss}>
|
|
|
+ <span>This release</span> {currCrashFreeRate}%
|
|
|
+ </span>
|
|
|
+ <span css={resetFlexColumnCss}>
|
|
|
+ <span>Prev release</span> {prevCrashFreeRate}%
|
|
|
+ </span>
|
|
|
+ <span css={resetFlexColumnCss}>
|
|
|
+ Change
|
|
|
+ {getDiff(
|
|
|
+ Math.abs(diff).toFixed(2) + '%',
|
|
|
+ sign === 0 ? 'black' : sign === 1 ? 'green400' : 'red400',
|
|
|
+ sign === 0 ? undefined : sign === 1 ? 'up' : 'down'
|
|
|
+ )}
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ </ReleaseInfoSubheader>
|
|
|
+ </PanelItem>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export default function ReleasesPanel() {
|
|
|
- const {data, isLoading, isError} = useToolbarRelease();
|
|
|
+ const {
|
|
|
+ data: releaseData,
|
|
|
+ isLoading: isReleaseDataLoading,
|
|
|
+ isError: isReleaseDataError,
|
|
|
+ } = useToolbarRelease();
|
|
|
+
|
|
|
const {organizationSlug, projectSlug, projectId, projectPlatform, trackAnalytics} =
|
|
|
useConfiguration();
|
|
|
|
|
|
- const estimateSize = 515;
|
|
|
- const placeholderHeight = `${estimateSize - 8}px`; // The real height of the items, minus the padding-block value
|
|
|
+ if (isReleaseDataError) {
|
|
|
+ return <EmptyStateWarning small>No data to show</EmptyStateWarning>;
|
|
|
+ }
|
|
|
|
|
|
return (
|
|
|
<PanelLayout title="Latest Release">
|
|
|
- {isLoading || isError ? (
|
|
|
+ <span
|
|
|
+ css={[
|
|
|
+ smallCss,
|
|
|
+ panelSectionCss,
|
|
|
+ panelInsetContentCss,
|
|
|
+ resetFlexRowCss,
|
|
|
+ {gap: 'var(--space50)', flexGrow: 0},
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ Latest release for{' '}
|
|
|
+ <SentryAppLink
|
|
|
+ to={{
|
|
|
+ url: `/releases/`,
|
|
|
+ query: {project: projectId},
|
|
|
+ }}
|
|
|
+ onClick={() => {
|
|
|
+ trackAnalytics?.({
|
|
|
+ eventKey: `devtoolbar.releases-list.header.click`,
|
|
|
+ eventName: `devtoolbar: Click releases-list header`,
|
|
|
+ });
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ css={[
|
|
|
+ resetFlexRowCss,
|
|
|
+ {display: 'inline-flex', gap: 'var(--space50)', alignItems: 'center'},
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <ProjectBadge
|
|
|
+ css={css({'&& img': {boxShadow: 'none'}})}
|
|
|
+ project={{
|
|
|
+ slug: projectSlug,
|
|
|
+ id: projectId,
|
|
|
+ platform: projectPlatform as PlatformKey,
|
|
|
+ }}
|
|
|
+ avatarSize={16}
|
|
|
+ hideName
|
|
|
+ avatarProps={{hasTooltip: false}}
|
|
|
+ />
|
|
|
+ {projectSlug}
|
|
|
+ </div>
|
|
|
+ </SentryAppLink>
|
|
|
+ </span>
|
|
|
+ {isReleaseDataLoading ? (
|
|
|
<div
|
|
|
css={[
|
|
|
resetFlexColumnCss,
|
|
@@ -84,48 +240,18 @@ export default function ReleasesPanel() {
|
|
|
]}
|
|
|
>
|
|
|
<Placeholder height={placeholderHeight} />
|
|
|
+ <Placeholder height={placeholderHeight} />
|
|
|
</div>
|
|
|
) : (
|
|
|
<Fragment>
|
|
|
- <div css={[smallCss, panelSectionCss, panelInsetContentCss]}>
|
|
|
- <span css={[resetFlexRowCss, {gap: 'var(--space50)'}]}>
|
|
|
- Latest release for{' '}
|
|
|
- <SentryAppLink
|
|
|
- to={{
|
|
|
- url: `/releases/`,
|
|
|
- query: {project: projectId},
|
|
|
- }}
|
|
|
- onClick={() => {
|
|
|
- trackAnalytics?.({
|
|
|
- eventKey: `devtoolbar.releases-list.header.click`,
|
|
|
- eventName: `devtoolbar: Click releases-list header`,
|
|
|
- });
|
|
|
- }}
|
|
|
- >
|
|
|
- <div
|
|
|
- css={[
|
|
|
- resetFlexRowCss,
|
|
|
- {display: 'inline-flex', gap: 'var(--space50)', alignItems: 'center'},
|
|
|
- ]}
|
|
|
- >
|
|
|
- <ProjectBadge
|
|
|
- css={css({'&& img': {boxShadow: 'none'}})}
|
|
|
- project={{
|
|
|
- slug: projectSlug,
|
|
|
- id: projectId,
|
|
|
- platform: projectPlatform as PlatformKey,
|
|
|
- }}
|
|
|
- avatarSize={16}
|
|
|
- hideName
|
|
|
- avatarProps={{hasTooltip: false}}
|
|
|
- />
|
|
|
- {projectSlug}
|
|
|
- </div>
|
|
|
- </SentryAppLink>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
<div style={{alignItems: 'start'}}>
|
|
|
- <ReleaseHeader release={data.json[0]} orgSlug={organizationSlug} />
|
|
|
+ <ReleaseSummary release={releaseData.json[0]} orgSlug={organizationSlug} />
|
|
|
+ <CrashFreeRate
|
|
|
+ currReleaseVersion={releaseData.json[0].version}
|
|
|
+ prevReleaseVersion={
|
|
|
+ releaseData.json.length > 1 ? releaseData.json[1].version : undefined
|
|
|
+ }
|
|
|
+ />
|
|
|
</div>
|
|
|
</Fragment>
|
|
|
)}
|