123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- import type {Theme} from '@emotion/react';
- import type {Location} from 'history';
- import pick from 'lodash/pick';
- import type {Moment} from 'moment-timezone';
- import moment from 'moment-timezone';
- import MarkLine from 'sentry/components/charts/components/markLine';
- import {parseStatsPeriod} from 'sentry/components/timeRangeSelector/utils';
- import {URL_PARAM} from 'sentry/constants/pageFilters';
- import {t} from 'sentry/locale';
- import type {
- Commit,
- CommitFile,
- FilesByRepository,
- ReleaseProject,
- ReleaseWithHealth,
- Repository,
- } from 'sentry/types';
- import {ReleaseComparisonChartType} from 'sentry/types';
- import type {Series} from 'sentry/types/echarts';
- import {decodeList} from 'sentry/utils/queryString';
- import {getReleaseBounds, getReleaseParams, isMobileRelease} from '../utils';
- import {commonTermsDescription, SessionTerm} from '../utils/sessionTerm';
- type CommitsByRepository = Record<string, Commit[]>;
- /**
- * Convert list of individual file changes into a per-file summary grouped by repository
- */
- export function getFilesByRepository(fileList: CommitFile[]) {
- return fileList.reduce<FilesByRepository>((filesByRepository, file) => {
- const {filename, repoName, author, type} = file;
- if (!filesByRepository.hasOwnProperty(repoName)) {
- filesByRepository[repoName] = {};
- }
- if (!filesByRepository[repoName].hasOwnProperty(filename)) {
- filesByRepository[repoName][filename] = {
- authors: {},
- types: new Set(),
- };
- }
- if (author.email) {
- filesByRepository[repoName][filename].authors[author.email] = author;
- }
- filesByRepository[repoName][filename].types.add(type);
- return filesByRepository;
- }, {});
- }
- /**
- * Convert list of individual commits into a summary grouped by repository
- */
- export function getCommitsByRepository(commitList: Commit[]): CommitsByRepository {
- return commitList.reduce<CommitsByRepository>((commitsByRepository, commit) => {
- const repositoryName = commit.repository?.name ?? t('unknown');
- if (!commitsByRepository.hasOwnProperty(repositoryName)) {
- commitsByRepository[repositoryName] = [];
- }
- commitsByRepository[repositoryName].push(commit);
- return commitsByRepository;
- }, {});
- }
- /**
- * Get request query according to the url params and active repository
- */
- type GetQueryProps = {
- location: Location;
- activeRepository?: Repository;
- perPage?: number;
- };
- export function getQuery({location, perPage = 40, activeRepository}: GetQueryProps) {
- const query = {
- ...pick(location.query, [...Object.values(URL_PARAM), 'cursor']),
- per_page: perPage,
- };
- if (!activeRepository) {
- return query;
- }
- return {
- ...query,
- repo_id: activeRepository.externalId,
- repo_name: activeRepository.name,
- };
- }
- /**
- * Get repositories to render according to the activeRepository
- */
- export function getReposToRender(repos: Array<string>, activeRepository?: Repository) {
- if (!activeRepository) {
- return repos;
- }
- return [activeRepository.name];
- }
- export const releaseComparisonChartLabels = {
- [ReleaseComparisonChartType.CRASH_FREE_SESSIONS]: t('Crash Free Session Rate'),
- [ReleaseComparisonChartType.HEALTHY_SESSIONS]: t('Healthy'),
- [ReleaseComparisonChartType.ABNORMAL_SESSIONS]: t('Abnormal'),
- [ReleaseComparisonChartType.ERRORED_SESSIONS]: t('Errored'),
- [ReleaseComparisonChartType.CRASHED_SESSIONS]: t('Crashed Session Rate'),
- [ReleaseComparisonChartType.CRASH_FREE_USERS]: t('Crash Free User Rate'),
- [ReleaseComparisonChartType.HEALTHY_USERS]: t('Healthy'),
- [ReleaseComparisonChartType.ABNORMAL_USERS]: t('Abnormal'),
- [ReleaseComparisonChartType.ERRORED_USERS]: t('Errored'),
- [ReleaseComparisonChartType.CRASHED_USERS]: t('Crashed User Rate'),
- [ReleaseComparisonChartType.SESSION_COUNT]: t('Session Count'),
- [ReleaseComparisonChartType.USER_COUNT]: t('User Count'),
- [ReleaseComparisonChartType.ERROR_COUNT]: t('Error Count'),
- [ReleaseComparisonChartType.TRANSACTION_COUNT]: t('Transaction Count'),
- [ReleaseComparisonChartType.FAILURE_RATE]: t('Failure Rate'),
- };
- export const releaseComparisonChartTitles = {
- [ReleaseComparisonChartType.CRASH_FREE_SESSIONS]: t('Crash Free Session Rate'),
- [ReleaseComparisonChartType.HEALTHY_SESSIONS]: t('Healthy Session Rate'),
- [ReleaseComparisonChartType.ABNORMAL_SESSIONS]: t('Abnormal Session Rate'),
- [ReleaseComparisonChartType.ERRORED_SESSIONS]: t('Errored Session Rate'),
- [ReleaseComparisonChartType.CRASHED_SESSIONS]: t('Crashed Session Rate'),
- [ReleaseComparisonChartType.CRASH_FREE_USERS]: t('Crash Free User Rate'),
- [ReleaseComparisonChartType.HEALTHY_USERS]: t('Healthy User Rate'),
- [ReleaseComparisonChartType.ABNORMAL_USERS]: t('Abnormal User Rate'),
- [ReleaseComparisonChartType.ERRORED_USERS]: t('Errored User Rate'),
- [ReleaseComparisonChartType.CRASHED_USERS]: t('Crashed User Rate'),
- [ReleaseComparisonChartType.SESSION_COUNT]: t('Session Count'),
- [ReleaseComparisonChartType.USER_COUNT]: t('User Count'),
- [ReleaseComparisonChartType.ERROR_COUNT]: t('Error Count'),
- [ReleaseComparisonChartType.TRANSACTION_COUNT]: t('Transaction Count'),
- [ReleaseComparisonChartType.FAILURE_RATE]: t('Failure Rate'),
- };
- export const releaseComparisonChartHelp = {
- [ReleaseComparisonChartType.CRASH_FREE_SESSIONS]:
- commonTermsDescription[SessionTerm.CRASH_FREE_SESSIONS],
- [ReleaseComparisonChartType.CRASH_FREE_USERS]:
- commonTermsDescription[SessionTerm.CRASH_FREE_USERS],
- [ReleaseComparisonChartType.SESSION_COUNT]: t(
- 'The number of sessions in a given period.'
- ),
- [ReleaseComparisonChartType.USER_COUNT]: t('The number of users in a given period.'),
- };
- type GenerateReleaseMarklineOptions = {
- axisIndex?: number;
- hideLabel?: boolean;
- };
- function generateReleaseMarkLine(
- title: string,
- position: number,
- theme: Theme,
- options?: GenerateReleaseMarklineOptions
- ) {
- const {hideLabel, axisIndex} = options || {};
- return {
- seriesName: title,
- type: 'line',
- data: [{name: position, value: null as any}], // TODO(ts): echart types
- yAxisIndex: axisIndex ?? undefined,
- xAxisIndex: axisIndex ?? undefined,
- color: theme.gray300,
- markLine: MarkLine({
- silent: true,
- lineStyle: {color: theme.gray300, type: 'solid'},
- label: {
- position: 'insideEndBottom',
- formatter: hideLabel ? '' : title,
- // @ts-expect-error weird echart types
- font: 'Rubik',
- fontSize: 14,
- color: theme.chartLabel,
- backgroundColor: theme.chartOther,
- },
- data: [
- {
- xAxis: position,
- },
- ],
- }),
- };
- }
- export const releaseMarkLinesLabels = {
- created: t('Release Created'),
- adopted: t('Adopted'),
- unadopted: t('Replaced'),
- };
- export function generateReleaseMarkLines(
- release: ReleaseWithHealth,
- project: ReleaseProject,
- theme: Theme,
- location: Location,
- options?: GenerateReleaseMarklineOptions
- ) {
- const markLines: Series[] = [];
- const adoptionStages = release.adoptionStages?.[project.slug];
- const isSingleEnv = decodeList(location.query.environment).length === 1;
- const releaseBounds = getReleaseBounds(release);
- const {statsPeriod, ...releaseParamsRest} = getReleaseParams({
- location,
- releaseBounds,
- });
- let {start, end} = releaseParamsRest;
- const isDefaultPeriod = !(
- location.query.pageStart ||
- location.query.pageEnd ||
- location.query.pageStatsPeriod
- );
- if (statsPeriod) {
- const parsedStatsPeriod = parseStatsPeriod(statsPeriod, null);
- start = parsedStatsPeriod.start;
- end = parsedStatsPeriod.end;
- }
- const releaseCreated = moment(release.dateCreated).startOf('minute');
- if (
- releaseCreated.isBetween(start, end) ||
- (isDefaultPeriod && releaseBounds.type === 'normal')
- ) {
- markLines.push(
- generateReleaseMarkLine(
- releaseMarkLinesLabels.created,
- releaseCreated.valueOf(),
- theme,
- options
- )
- );
- }
- if (!isSingleEnv || !isMobileRelease(project.platform)) {
- // for now want to show marklines only on mobile platforms with single environment selected
- return markLines;
- }
- const releaseAdopted: Moment | undefined = adoptionStages?.adopted
- ? moment(adoptionStages.adopted)
- : undefined;
- if (releaseAdopted?.isBetween(start, end)) {
- markLines.push(
- generateReleaseMarkLine(
- releaseMarkLinesLabels.adopted,
- releaseAdopted.valueOf(),
- theme,
- options
- )
- );
- }
- const releaseReplaced: Moment | undefined = adoptionStages?.unadopted
- ? moment(adoptionStages.unadopted)
- : undefined;
- if (releaseReplaced?.isBetween(start, end)) {
- markLines.push(
- generateReleaseMarkLine(
- releaseMarkLinesLabels.unadopted,
- releaseReplaced.valueOf(),
- theme,
- options
- )
- );
- }
- return markLines;
- }
|