|
@@ -14,13 +14,15 @@ import ExternalLink from 'sentry/components/links/externalLink';
|
|
|
import Link from 'sentry/components/links/link';
|
|
|
import Placeholder from 'sentry/components/placeholder';
|
|
|
import type {PlatformKey} from 'sentry/data/platformCategories';
|
|
|
-import {IconClose, IconWarning} from 'sentry/icons';
|
|
|
+import {IconCircle, IconCircleFill, IconClose, IconWarning} from 'sentry/icons';
|
|
|
import {t} from 'sentry/locale';
|
|
|
import space from 'sentry/styles/space';
|
|
|
import {
|
|
|
CodecovStatusCode,
|
|
|
+ Coverage,
|
|
|
Event,
|
|
|
Frame,
|
|
|
+ LineCoverage,
|
|
|
Organization,
|
|
|
Project,
|
|
|
StacktraceLinkResult,
|
|
@@ -124,18 +126,59 @@ function shouldshowCodecovFeatures(
|
|
|
|
|
|
interface CodecovLinkProps {
|
|
|
event: Event;
|
|
|
+ lineNo: number | null;
|
|
|
organization: Organization;
|
|
|
- codecovStatusCode?: CodecovStatusCode;
|
|
|
- codecovUrl?: string;
|
|
|
+ coverageUrl?: string;
|
|
|
+ lineCoverage?: LineCoverage[];
|
|
|
+ status?: CodecovStatusCode;
|
|
|
+}
|
|
|
+
|
|
|
+function getCoverageIcon(lineCoverage, lineNo) {
|
|
|
+ const covIndex = lineCoverage.findIndex(line => line[0] === lineNo);
|
|
|
+ if (covIndex === -1) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ switch (lineCoverage[covIndex][1]) {
|
|
|
+ case Coverage.COVERED:
|
|
|
+ return (
|
|
|
+ <CoverageIcon>
|
|
|
+ <IconCircleFill size="xs" color="green100" style={{position: 'absolute'}} />
|
|
|
+ <IconCircle size="xs" color="green300" />
|
|
|
+ {t('Covered')}
|
|
|
+ </CoverageIcon>
|
|
|
+ );
|
|
|
+ case Coverage.PARTIAL:
|
|
|
+ return (
|
|
|
+ <CoverageIcon>
|
|
|
+ <IconCircleFill size="xs" color="yellow100" style={{position: 'absolute'}} />
|
|
|
+ <IconCircle size="xs" color="yellow300" />
|
|
|
+ {t('Partially Covered')}
|
|
|
+ </CoverageIcon>
|
|
|
+ );
|
|
|
+ case Coverage.NOT_COVERED:
|
|
|
+ return (
|
|
|
+ <CodecovContainer>
|
|
|
+ <CoverageIcon>
|
|
|
+ <IconCircleFill size="xs" color="red100" style={{position: 'absolute'}} />
|
|
|
+ <IconCircle size="xs" color="red300" />
|
|
|
+ </CoverageIcon>
|
|
|
+ {t('Not Covered')}
|
|
|
+ </CodecovContainer>
|
|
|
+ );
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function CodecovLink({
|
|
|
- codecovUrl,
|
|
|
- codecovStatusCode,
|
|
|
+ coverageUrl,
|
|
|
+ status = CodecovStatusCode.COVERAGE_EXISTS,
|
|
|
+ lineCoverage,
|
|
|
+ lineNo,
|
|
|
organization,
|
|
|
event,
|
|
|
}: CodecovLinkProps) {
|
|
|
- if (codecovStatusCode === CodecovStatusCode.NO_COVERAGE_DATA) {
|
|
|
+ if (status === CodecovStatusCode.NO_COVERAGE_DATA) {
|
|
|
return (
|
|
|
<CodecovWarning>
|
|
|
{t('Code Coverage not found')}
|
|
@@ -144,8 +187,8 @@ function CodecovLink({
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- if (codecovStatusCode === CodecovStatusCode.COVERAGE_EXISTS) {
|
|
|
- if (!codecovUrl) {
|
|
|
+ if (status === CodecovStatusCode.COVERAGE_EXISTS) {
|
|
|
+ if (!coverageUrl || !lineCoverage || !lineNo) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
@@ -158,10 +201,13 @@ function CodecovLink({
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
- <OpenInLink href={codecovUrl} openInNewTab onClick={onOpenCodecovLink}>
|
|
|
- {t('View Coverage Tests on Codecov')}
|
|
|
- <StyledIconWrapper>{getIntegrationIcon('codecov', 'sm')}</StyledIconWrapper>
|
|
|
- </OpenInLink>
|
|
|
+ <CodecovContainer>
|
|
|
+ {getCoverageIcon(lineCoverage, lineNo)}
|
|
|
+ <OpenInLink href={coverageUrl} openInNewTab onClick={onOpenCodecovLink}>
|
|
|
+ <StyledIconWrapper>{getIntegrationIcon('codecov', 'sm')}</StyledIconWrapper>
|
|
|
+ {t('Open in Codecov')}
|
|
|
+ </OpenInLink>
|
|
|
+ </CodecovContainer>
|
|
|
);
|
|
|
}
|
|
|
return null;
|
|
@@ -297,8 +343,10 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
|
|
|
</OpenInLink>
|
|
|
{shouldshowCodecovFeatures(organization, match) && (
|
|
|
<CodecovLink
|
|
|
- codecovUrl={`${match.codecov?.coverageUrl}#L${frame.lineNo}`}
|
|
|
- codecovStatusCode={match.codecov?.status}
|
|
|
+ coverageUrl={`${match.codecov?.coverageUrl}#L${frame.lineNo}`}
|
|
|
+ status={match.codecov?.status}
|
|
|
+ lineCoverage={match.codecov?.lineCoverage}
|
|
|
+ lineNo={frame.lineNo}
|
|
|
organization={organization}
|
|
|
event={event}
|
|
|
/>
|
|
@@ -414,3 +462,14 @@ const CodecovWarning = styled('div')`
|
|
|
gap: ${space(0.75)};
|
|
|
align-items: center;
|
|
|
`;
|
|
|
+
|
|
|
+const CodecovContainer = styled('span')`
|
|
|
+ display: flex;
|
|
|
+ gap: ${space(0.75)};
|
|
|
+`;
|
|
|
+
|
|
|
+const CoverageIcon = styled('span')`
|
|
|
+ display: flex;
|
|
|
+ gap: ${space(0.75)};
|
|
|
+ align-items: center;
|
|
|
+`;
|