123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- import React from 'react';
- import styled from '@emotion/styled';
- import {Location} from 'history';
- import GuideAnchor from 'app/components/assistant/guideAnchor';
- import Button from 'app/components/button';
- import Collapsible from 'app/components/collapsible';
- import Count from 'app/components/count';
- import GlobalSelectionLink from 'app/components/globalSelectionLink';
- import ProjectBadge from 'app/components/idBadge/projectBadge';
- import NotAvailable from 'app/components/notAvailable';
- import {PanelItem} from 'app/components/panels';
- import Placeholder from 'app/components/placeholder';
- import Tooltip from 'app/components/tooltip';
- import {t, tct} from 'app/locale';
- import overflowEllipsis from 'app/styles/overflowEllipsis';
- import space from 'app/styles/space';
- import {Organization, Release, ReleaseProject} from 'app/types';
- import {defined} from 'app/utils';
- import {getReleaseNewIssuesUrl, getReleaseUnhandledIssuesUrl} from '../../utils';
- import {ReleaseHealthRequestRenderProps} from '../../utils/releaseHealthRequest';
- import CrashFree from '../crashFree';
- import HealthStatsChart from '../healthStatsChart';
- import HealthStatsPeriod from '../healthStatsPeriod';
- import ReleaseAdoption from '../releaseAdoption';
- import {DisplayOption} from '../utils';
- import Header from './header';
- import ProjectLink from './projectLink';
- type Props = {
- projects: Array<ReleaseProject>;
- releaseVersion: Release['version'];
- organization: Organization;
- activeDisplay: DisplayOption;
- location: Location;
- showPlaceholders: boolean;
- isTopRelease: boolean;
- getHealthData: ReleaseHealthRequestRenderProps['getHealthData'];
- };
- const Content = ({
- projects,
- releaseVersion,
- location,
- organization,
- activeDisplay,
- showPlaceholders,
- isTopRelease,
- getHealthData,
- }: Props) => {
- return (
- <React.Fragment>
- <Header>
- <Layout>
- <Column>{t('Project Name')}</Column>
- <AdoptionColumn>
- <GuideAnchor
- target="release_adoption"
- position="bottom"
- disabled={!(isTopRelease && window.innerWidth >= 800)}
- >
- {t('Adoption')}
- </GuideAnchor>
- </AdoptionColumn>
- <CrashFreeRateColumn>{t('Crash Free Rate')}</CrashFreeRateColumn>
- <CountColumn>
- <span>{t('Count')}</span>
- <HealthStatsPeriod location={location} />
- </CountColumn>
- <CrashesColumn>{t('Crashes')}</CrashesColumn>
- <NewIssuesColumn>{t('New Issues')}</NewIssuesColumn>
- <ViewColumn />
- </Layout>
- </Header>
- <ProjectRows>
- <Collapsible
- expandButton={({onExpand, numberOfHiddenItems}) => (
- <ExpandButtonWrapper>
- <Button priority="primary" size="xsmall" onClick={onExpand}>
- {tct('Show [numberOfHiddenItems] More', {numberOfHiddenItems})}
- </Button>
- </ExpandButtonWrapper>
- )}
- collapseButton={({onCollapse}) => (
- <CollapseButtonWrapper>
- <Button priority="primary" size="xsmall" onClick={onCollapse}>
- {t('Collapse')}
- </Button>
- </CollapseButtonWrapper>
- )}
- >
- {projects.map((project, index) => {
- const {id, slug, newGroups} = project;
- const crashCount = getHealthData.getCrashCount(
- releaseVersion,
- id,
- DisplayOption.SESSIONS
- );
- const crashFreeRate = getHealthData.getCrashFreeRate(
- releaseVersion,
- id,
- activeDisplay
- );
- const get24hCountByRelease = getHealthData.get24hCountByRelease(
- releaseVersion,
- id,
- activeDisplay
- );
- const get24hCountByProject = getHealthData.get24hCountByProject(
- id,
- activeDisplay
- );
- const timeSeries = getHealthData.getTimeSeries(
- releaseVersion,
- id,
- activeDisplay
- );
- const adoption = getHealthData.getAdoption(releaseVersion, id, activeDisplay);
- // we currently don't support sub-hour session intervals, we rather hide the count histogram than to show only two bars
- const hasCountHistogram =
- timeSeries?.[0].data.length > 7 &&
- timeSeries[0].data.some(item => item.value > 0);
- return (
- <ProjectRow key={`${releaseVersion}-${slug}-health`}>
- <Layout>
- <Column>
- <ProjectBadge project={project} avatarSize={16} />
- </Column>
- <AdoptionColumn>
- {showPlaceholders ? (
- <StyledPlaceholder width="150px" />
- ) : get24hCountByProject ? (
- <AdoptionWrapper>
- <ReleaseAdoption
- adoption={adoption ?? 0}
- releaseCount={get24hCountByRelease ?? 0}
- projectCount={get24hCountByProject ?? 0}
- displayOption={activeDisplay}
- />
- <Count value={get24hCountByRelease ?? 0} />
- </AdoptionWrapper>
- ) : (
- <NotAvailable />
- )}
- </AdoptionColumn>
- <CrashFreeRateColumn>
- {showPlaceholders ? (
- <StyledPlaceholder width="60px" />
- ) : defined(crashFreeRate) ? (
- <CrashFree percent={crashFreeRate} />
- ) : (
- <NotAvailable />
- )}
- </CrashFreeRateColumn>
- <CountColumn>
- {showPlaceholders ? (
- <StyledPlaceholder />
- ) : hasCountHistogram ? (
- <ChartWrapper>
- <HealthStatsChart
- data={timeSeries}
- height={20}
- activeDisplay={activeDisplay}
- />
- </ChartWrapper>
- ) : (
- <NotAvailable />
- )}
- </CountColumn>
- <CrashesColumn>
- {showPlaceholders ? (
- <StyledPlaceholder width="30px" />
- ) : defined(crashCount) ? (
- <Tooltip title={t('Open in Issues')}>
- <GlobalSelectionLink
- to={getReleaseUnhandledIssuesUrl(
- organization.slug,
- project.id,
- releaseVersion
- )}
- >
- <Count value={crashCount} />
- </GlobalSelectionLink>
- </Tooltip>
- ) : (
- <NotAvailable />
- )}
- </CrashesColumn>
- <NewIssuesColumn>
- <Tooltip title={t('Open in Issues')}>
- <GlobalSelectionLink
- to={getReleaseNewIssuesUrl(
- organization.slug,
- project.id,
- releaseVersion
- )}
- >
- <Count value={newGroups || 0} />
- </GlobalSelectionLink>
- </Tooltip>
- </NewIssuesColumn>
- <ViewColumn>
- <GuideAnchor
- disabled={!isTopRelease || index !== 0}
- target="view_release"
- >
- <ProjectLink
- orgSlug={organization.slug}
- project={project}
- releaseVersion={releaseVersion}
- location={location}
- />
- </GuideAnchor>
- </ViewColumn>
- </Layout>
- </ProjectRow>
- );
- })}
- </Collapsible>
- </ProjectRows>
- </React.Fragment>
- );
- };
- export default Content;
- const ProjectRows = styled('div')`
- position: relative;
- `;
- const ExpandButtonWrapper = styled('div')`
- position: absolute;
- width: 100%;
- bottom: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background-image: linear-gradient(
- 180deg,
- hsla(0, 0%, 100%, 0.15) 0,
- ${p => p.theme.white}
- );
- background-repeat: repeat-x;
- border-bottom: ${space(1)} solid ${p => p.theme.white};
- border-top: ${space(1)} solid transparent;
- border-bottom-right-radius: ${p => p.theme.borderRadius};
- @media (max-width: ${p => p.theme.breakpoints[1]}) {
- border-bottom-left-radius: ${p => p.theme.borderRadius};
- }
- `;
- const CollapseButtonWrapper = styled('div')`
- display: flex;
- align-items: center;
- justify-content: center;
- height: 41px;
- `;
- const ProjectRow = styled(PanelItem)`
- padding: ${space(1)} ${space(2)};
- @media (min-width: ${p => p.theme.breakpoints[1]}) {
- font-size: ${p => p.theme.fontSizeMedium};
- }
- `;
- const Layout = styled('div')`
- display: grid;
- grid-template-columns: 1fr 1.4fr 0.6fr 0.7fr;
- grid-column-gap: ${space(1)};
- align-items: center;
- width: 100%;
- @media (min-width: ${p => p.theme.breakpoints[0]}) {
- grid-template-columns: 1fr 1fr 1fr 0.5fr 0.5fr 0.5fr;
- }
- @media (min-width: ${p => p.theme.breakpoints[1]}) {
- grid-template-columns: 1fr 0.8fr 1fr 0.5fr 0.5fr 0.6fr;
- }
- @media (min-width: ${p => p.theme.breakpoints[3]}) {
- grid-template-columns: 1fr 0.8fr 1fr 1fr 0.5fr 0.5fr 0.5fr;
- }
- `;
- const Column = styled('div')`
- ${overflowEllipsis};
- line-height: 20px;
- `;
- const NewIssuesColumn = styled(Column)`
- @media (min-width: ${p => p.theme.breakpoints[0]}) {
- text-align: right;
- }
- `;
- const AdoptionColumn = styled(Column)`
- display: none;
- @media (min-width: ${p => p.theme.breakpoints[0]}) {
- display: flex;
- /* Chart tooltips need overflow */
- overflow: visible;
- }
- `;
- const AdoptionWrapper = styled('span')`
- display: inline-grid;
- grid-template-columns: 70px 1fr;
- grid-gap: ${space(1)};
- align-items: center;
- @media (min-width: ${p => p.theme.breakpoints[3]}) {
- grid-template-columns: 90px 1fr;
- }
- `;
- const CrashFreeRateColumn = styled(Column)`
- @media (min-width: ${p => p.theme.breakpoints[0]}) {
- text-align: center;
- }
- `;
- const CountColumn = styled(Column)`
- display: none;
- @media (min-width: ${p => p.theme.breakpoints[3]}) {
- display: flex;
- /* Chart tooltips need overflow */
- overflow: visible;
- }
- `;
- const CrashesColumn = styled(Column)`
- display: none;
- @media (min-width: ${p => p.theme.breakpoints[0]}) {
- display: block;
- text-align: right;
- }
- `;
- const ViewColumn = styled(Column)`
- text-align: right;
- `;
- const ChartWrapper = styled('div')`
- flex: 1;
- g > .barchart-rect {
- background: ${p => p.theme.gray200};
- fill: ${p => p.theme.gray200};
- }
- `;
- const StyledPlaceholder = styled(Placeholder)`
- height: 15px;
- display: inline-block;
- position: relative;
- top: ${space(0.25)};
- `;
|