|
@@ -7,12 +7,16 @@ import {SectionHeading} from 'app/components/charts/styles';
|
|
import Count from 'app/components/count';
|
|
import Count from 'app/components/count';
|
|
import DeployBadge from 'app/components/deployBadge';
|
|
import DeployBadge from 'app/components/deployBadge';
|
|
import GlobalSelectionLink from 'app/components/globalSelectionLink';
|
|
import GlobalSelectionLink from 'app/components/globalSelectionLink';
|
|
|
|
+import NotAvailable from 'app/components/notAvailable';
|
|
|
|
+import ProgressBar from 'app/components/progressBar';
|
|
import QuestionTooltip from 'app/components/questionTooltip';
|
|
import QuestionTooltip from 'app/components/questionTooltip';
|
|
import TimeSince from 'app/components/timeSince';
|
|
import TimeSince from 'app/components/timeSince';
|
|
import Tooltip from 'app/components/tooltip';
|
|
import Tooltip from 'app/components/tooltip';
|
|
|
|
+import NOT_AVAILABLE_MESSAGES from 'app/constants/notAvailableMessages';
|
|
import {t} from 'app/locale';
|
|
import {t} from 'app/locale';
|
|
import space from 'app/styles/space';
|
|
import space from 'app/styles/space';
|
|
import {GlobalSelection, Organization, Release, ReleaseProject} from 'app/types';
|
|
import {GlobalSelection, Organization, Release, ReleaseProject} from 'app/types';
|
|
|
|
+import {defined} from 'app/utils';
|
|
import DiscoverQuery from 'app/utils/discover/discoverQuery';
|
|
import DiscoverQuery from 'app/utils/discover/discoverQuery';
|
|
import {getAggregateAlias} from 'app/utils/discover/fields';
|
|
import {getAggregateAlias} from 'app/utils/discover/fields';
|
|
import {getTermHelp, PERFORMANCE_TERM} from 'app/views/performance/data';
|
|
import {getTermHelp, PERFORMANCE_TERM} from 'app/views/performance/data';
|
|
@@ -22,6 +26,8 @@ import {
|
|
sessionTerm,
|
|
sessionTerm,
|
|
} from 'app/views/releases/utils/sessionTerm';
|
|
} from 'app/views/releases/utils/sessionTerm';
|
|
|
|
|
|
|
|
+import AdoptionTooltip from '../../list/adoptionTooltip';
|
|
|
|
+import CrashFree from '../../list/crashFree';
|
|
import {getReleaseNewIssuesUrl, getReleaseUnhandledIssuesUrl} from '../../utils';
|
|
import {getReleaseNewIssuesUrl, getReleaseUnhandledIssuesUrl} from '../../utils';
|
|
import {getReleaseEventView} from '../utils';
|
|
import {getReleaseEventView} from '../utils';
|
|
|
|
|
|
@@ -35,19 +41,28 @@ type Props = {
|
|
|
|
|
|
function ReleaseStats({organization, release, project, location, selection}: Props) {
|
|
function ReleaseStats({organization, release, project, location, selection}: Props) {
|
|
const {lastDeploy, dateCreated, newGroups, version} = release;
|
|
const {lastDeploy, dateCreated, newGroups, version} = release;
|
|
- const {hasHealthData} = project;
|
|
|
|
- const {sessionsCrashed} = project.healthData;
|
|
|
|
|
|
+ const {hasHealthData, healthData} = project;
|
|
|
|
+ const {
|
|
|
|
+ sessionsCrashed,
|
|
|
|
+ adoption,
|
|
|
|
+ crashFreeUsers,
|
|
|
|
+ crashFreeSessions,
|
|
|
|
+ totalUsers,
|
|
|
|
+ totalUsers24h,
|
|
|
|
+ totalSessions,
|
|
|
|
+ totalSessions24h,
|
|
|
|
+ } = healthData;
|
|
|
|
|
|
return (
|
|
return (
|
|
<Container>
|
|
<Container>
|
|
- <DateStatWrapper>
|
|
|
|
|
|
+ <div>
|
|
<SectionHeading>
|
|
<SectionHeading>
|
|
{lastDeploy?.dateFinished ? t('Date Deployed') : t('Date Created')}
|
|
{lastDeploy?.dateFinished ? t('Date Deployed') : t('Date Created')}
|
|
</SectionHeading>
|
|
</SectionHeading>
|
|
<div>
|
|
<div>
|
|
<TimeSince date={lastDeploy?.dateFinished ?? dateCreated} />
|
|
<TimeSince date={lastDeploy?.dateFinished ?? dateCreated} />
|
|
</div>
|
|
</div>
|
|
- </DateStatWrapper>
|
|
|
|
|
|
+ </div>
|
|
|
|
|
|
<div>
|
|
<div>
|
|
<SectionHeading>{t('Last Deploy')}</SectionHeading>
|
|
<SectionHeading>{t('Last Deploy')}</SectionHeading>
|
|
@@ -60,107 +75,159 @@ function ReleaseStats({organization, release, project, location, selection}: Pro
|
|
projectId={project.id}
|
|
projectId={project.id}
|
|
/>
|
|
/>
|
|
) : (
|
|
) : (
|
|
- '\u2014'
|
|
|
|
|
|
+ <NotAvailable />
|
|
)}
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div>
|
|
<div>
|
|
- <SectionHeading>{t('New Issues')}</SectionHeading>
|
|
|
|
|
|
+ <SectionHeading>{t('Crash Free Users')}</SectionHeading>
|
|
<div>
|
|
<div>
|
|
- <Tooltip title={t('Open in Issues')}>
|
|
|
|
- <GlobalSelectionLink
|
|
|
|
- to={getReleaseNewIssuesUrl(organization.slug, project.id, version)}
|
|
|
|
- >
|
|
|
|
- <Count value={newGroups} />
|
|
|
|
- </GlobalSelectionLink>
|
|
|
|
- </Tooltip>
|
|
|
|
|
|
+ {defined(crashFreeUsers) ? (
|
|
|
|
+ <CrashFree percent={crashFreeUsers} iconSize="md" />
|
|
|
|
+ ) : (
|
|
|
|
+ <NotAvailable tooltip={NOT_AVAILABLE_MESSAGES.releaseHealth} />
|
|
|
|
+ )}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div>
|
|
<div>
|
|
- <SectionHeading>
|
|
|
|
- {t('Apdex')}
|
|
|
|
- <QuestionTooltip
|
|
|
|
- position="top"
|
|
|
|
- title={getTermHelp(organization, PERFORMANCE_TERM.APDEX)}
|
|
|
|
- size="sm"
|
|
|
|
- />
|
|
|
|
- </SectionHeading>
|
|
|
|
|
|
+ <SectionHeading>{t('Crash Free Sessions')}</SectionHeading>
|
|
<div>
|
|
<div>
|
|
- <Feature features={['performance-view']}>
|
|
|
|
- {hasFeature =>
|
|
|
|
- hasFeature ? (
|
|
|
|
- <DiscoverQuery
|
|
|
|
- eventView={getReleaseEventView(
|
|
|
|
- selection,
|
|
|
|
- release?.version,
|
|
|
|
- organization
|
|
|
|
- )}
|
|
|
|
- location={location}
|
|
|
|
- orgSlug={organization.slug}
|
|
|
|
- >
|
|
|
|
- {({isLoading, error, tableData}) => {
|
|
|
|
- if (isLoading || error || !tableData || tableData.data.length === 0) {
|
|
|
|
- return '\u2014';
|
|
|
|
- }
|
|
|
|
- return (
|
|
|
|
- <GlobalSelectionLink
|
|
|
|
- to={{
|
|
|
|
- pathname: `/organizations/${organization.slug}/performance/`,
|
|
|
|
- query: {
|
|
|
|
- query: `release:${release?.version}`,
|
|
|
|
- },
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
- <Count
|
|
|
|
- value={
|
|
|
|
- tableData.data[0][
|
|
|
|
- getAggregateAlias(`apdex(${organization.apdexThreshold})`)
|
|
|
|
- ]
|
|
|
|
- }
|
|
|
|
- />
|
|
|
|
- </GlobalSelectionLink>
|
|
|
|
- );
|
|
|
|
- }}
|
|
|
|
- </DiscoverQuery>
|
|
|
|
- ) : (
|
|
|
|
- <Tooltip
|
|
|
|
- title={t('This view is only available with Performance Monitoring.')}
|
|
|
|
- >
|
|
|
|
- {'\u2014'}
|
|
|
|
- </Tooltip>
|
|
|
|
- )
|
|
|
|
- }
|
|
|
|
- </Feature>
|
|
|
|
|
|
+ {defined(crashFreeSessions) ? (
|
|
|
|
+ <CrashFree percent={crashFreeSessions} iconSize="md" />
|
|
|
|
+ ) : (
|
|
|
|
+ <NotAvailable tooltip={NOT_AVAILABLE_MESSAGES.releaseHealth} />
|
|
|
|
+ )}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
- <div>
|
|
|
|
- <SectionHeading>
|
|
|
|
- {sessionTerm.crashes}
|
|
|
|
- <QuestionTooltip
|
|
|
|
- position="top"
|
|
|
|
- title={getSessionTermDescription(SessionTerm.CRASHES, project.platform)}
|
|
|
|
- size="sm"
|
|
|
|
- />
|
|
|
|
- </SectionHeading>
|
|
|
|
|
|
+ <AdoptionWrapper>
|
|
|
|
+ <SectionHeading>{t('User Adoption')}</SectionHeading>
|
|
|
|
+ {defined(adoption) ? (
|
|
|
|
+ <Tooltip
|
|
|
|
+ containerDisplayMode="block"
|
|
|
|
+ title={
|
|
|
|
+ <AdoptionTooltip
|
|
|
|
+ totalUsers={totalUsers}
|
|
|
|
+ totalSessions={totalSessions}
|
|
|
|
+ totalUsers24h={totalUsers24h}
|
|
|
|
+ totalSessions24h={totalSessions24h}
|
|
|
|
+ />
|
|
|
|
+ }
|
|
|
|
+ >
|
|
|
|
+ <ProgressBar value={Math.ceil(adoption)} />
|
|
|
|
+ </Tooltip>
|
|
|
|
+ ) : (
|
|
|
|
+ <NotAvailable tooltip={NOT_AVAILABLE_MESSAGES.releaseHealth} />
|
|
|
|
+ )}
|
|
|
|
+ </AdoptionWrapper>
|
|
|
|
+
|
|
|
|
+ <LinkedStatsWrapper>
|
|
<div>
|
|
<div>
|
|
- {hasHealthData ? (
|
|
|
|
|
|
+ <SectionHeading>{t('New Issues')}</SectionHeading>
|
|
|
|
+ <div>
|
|
<Tooltip title={t('Open in Issues')}>
|
|
<Tooltip title={t('Open in Issues')}>
|
|
<GlobalSelectionLink
|
|
<GlobalSelectionLink
|
|
- to={getReleaseUnhandledIssuesUrl(organization.slug, project.id, version)}
|
|
|
|
|
|
+ to={getReleaseNewIssuesUrl(organization.slug, project.id, version)}
|
|
>
|
|
>
|
|
- <Count value={sessionsCrashed} />
|
|
|
|
|
|
+ <Count value={newGroups} />
|
|
</GlobalSelectionLink>
|
|
</GlobalSelectionLink>
|
|
</Tooltip>
|
|
</Tooltip>
|
|
- ) : (
|
|
|
|
- <Tooltip title={t('This view is only available with release health data.')}>
|
|
|
|
- {'\u2014'}
|
|
|
|
- </Tooltip>
|
|
|
|
- )}
|
|
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
|
|
+
|
|
|
|
+ <div>
|
|
|
|
+ <SectionHeading>
|
|
|
|
+ {sessionTerm.crashes}
|
|
|
|
+ <QuestionTooltip
|
|
|
|
+ position="top"
|
|
|
|
+ title={getSessionTermDescription(SessionTerm.CRASHES, project.platform)}
|
|
|
|
+ size="sm"
|
|
|
|
+ />
|
|
|
|
+ </SectionHeading>
|
|
|
|
+ <div>
|
|
|
|
+ {hasHealthData ? (
|
|
|
|
+ <Tooltip title={t('Open in Issues')}>
|
|
|
|
+ <GlobalSelectionLink
|
|
|
|
+ to={getReleaseUnhandledIssuesUrl(
|
|
|
|
+ organization.slug,
|
|
|
|
+ project.id,
|
|
|
|
+ version
|
|
|
|
+ )}
|
|
|
|
+ >
|
|
|
|
+ <Count value={sessionsCrashed} />
|
|
|
|
+ </GlobalSelectionLink>
|
|
|
|
+ </Tooltip>
|
|
|
|
+ ) : (
|
|
|
|
+ <NotAvailable tooltip={NOT_AVAILABLE_MESSAGES.releaseHealth} />
|
|
|
|
+ )}
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div>
|
|
|
|
+ <SectionHeading>
|
|
|
|
+ {t('Apdex')}
|
|
|
|
+ <QuestionTooltip
|
|
|
|
+ position="top"
|
|
|
|
+ title={getTermHelp(organization, PERFORMANCE_TERM.APDEX)}
|
|
|
|
+ size="sm"
|
|
|
|
+ />
|
|
|
|
+ </SectionHeading>
|
|
|
|
+ <div>
|
|
|
|
+ <Feature features={['performance-view']}>
|
|
|
|
+ {hasFeature =>
|
|
|
|
+ hasFeature ? (
|
|
|
|
+ <DiscoverQuery
|
|
|
|
+ eventView={getReleaseEventView(
|
|
|
|
+ selection,
|
|
|
|
+ release?.version,
|
|
|
|
+ organization
|
|
|
|
+ )}
|
|
|
|
+ location={location}
|
|
|
|
+ orgSlug={organization.slug}
|
|
|
|
+ >
|
|
|
|
+ {({isLoading, error, tableData}) => {
|
|
|
|
+ if (
|
|
|
|
+ isLoading ||
|
|
|
|
+ error ||
|
|
|
|
+ !tableData ||
|
|
|
|
+ tableData.data.length === 0
|
|
|
|
+ ) {
|
|
|
|
+ return <NotAvailable />;
|
|
|
|
+ }
|
|
|
|
+ return (
|
|
|
|
+ <GlobalSelectionLink
|
|
|
|
+ to={{
|
|
|
|
+ pathname: `/organizations/${organization.slug}/performance/`,
|
|
|
|
+ query: {
|
|
|
|
+ query: `release:${release?.version}`,
|
|
|
|
+ },
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <Tooltip title={t('Open in Performance')}>
|
|
|
|
+ <Count
|
|
|
|
+ value={
|
|
|
|
+ tableData.data[0][
|
|
|
|
+ getAggregateAlias(
|
|
|
|
+ `apdex(${organization.apdexThreshold})`
|
|
|
|
+ )
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+ />
|
|
|
|
+ </Tooltip>
|
|
|
|
+ </GlobalSelectionLink>
|
|
|
|
+ );
|
|
|
|
+ }}
|
|
|
|
+ </DiscoverQuery>
|
|
|
|
+ ) : (
|
|
|
|
+ <NotAvailable tooltip={NOT_AVAILABLE_MESSAGES.performance} />
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ </Feature>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </LinkedStatsWrapper>
|
|
</Container>
|
|
</Container>
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -172,7 +239,13 @@ const Container = styled('div')`
|
|
margin-bottom: ${space(3)};
|
|
margin-bottom: ${space(3)};
|
|
`;
|
|
`;
|
|
|
|
|
|
-const DateStatWrapper = styled('div')`
|
|
|
|
|
|
+const LinkedStatsWrapper = styled('div')`
|
|
|
|
+ grid-column: 1/3;
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const AdoptionWrapper = styled('div')`
|
|
grid-column: 1/3;
|
|
grid-column: 1/3;
|
|
`;
|
|
`;
|
|
|
|
|