Skip to content

Commit

Permalink
feat: Add Coverage card
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeyshevch committed Aug 24, 2023
1 parent c7ee300 commit 0946e52
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 15 deletions.
25 changes: 24 additions & 1 deletion packages/gitlab-backend/src/service/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,30 @@ export async function createRouter(
return req.method === 'GET';
};

for (const { host, apiBaseUrl, token } of gitlabIntegrations) {
const postFilter = (_pathname: string, req: IncomingMessage): boolean => {
if (req.headers['authorization']) delete req.headers['authorization'];
return req.method === 'POST';
};

for (const { host, apiBaseUrl, baseUrl, token } of gitlabIntegrations) {
const graphqlBaseUrl = new URL(baseUrl);
router.use(
`/${host}/graphql`,
createProxyMiddleware(postFilter, {
target: graphqlBaseUrl.origin,
changeOrigin: true,
headers: {
...(token ? { 'PRIVATE-TOKEN': token } : {}),
},
secure,
logProvider: () => logger,
logLevel: 'info',
pathRewrite: {
[`^${basePath}/api/gitlab/${host}/graphql`]: `/api/graphql`,
},
})
);

const apiUrl = new URL(apiBaseUrl);
router.use(
`/${host}`,
Expand Down
1 change: 1 addition & 0 deletions packages/gitlab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@mui/x-charts": "^6.0.0-alpha.7",
"dayjs": "^1.11.7",
"react-use": "^17.2.4",
"semver": "^7.3.8"
Expand Down
20 changes: 18 additions & 2 deletions packages/gitlab/src/api/GitlabCIApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ export type GitlabCIBuilder = {
build(gitlabInstance: string): GitlabCIApi;
};

export type GitlabProjectCoverageResponse = {
data: {
project: {
name: string;
webUrl: string;
pipelines: {
nodes: {
coverage: number;
createdAt: string;
}[];
};
};
};
};

export type GitlabCIApi = {
getPipelineSummary(
projectID: string | number
Expand All @@ -42,6 +57,9 @@ export type GitlabCIApi = {
projectID: string | number
): Promise<LanguagesSummary | undefined>;
getProjectDetails(projectSlug: string): Promise<ProjectSchema | undefined>;
getProjectCoverage(
projectSlug: string
): Promise<GitlabProjectCoverageResponse | undefined>;
getIssuesSummary(
projectID: string | number
): Promise<IssueSchema[] | undefined>;
Expand All @@ -53,7 +71,6 @@ export type GitlabCIApi = {
getReleasesSummary(
projectID: string | number
): Promise<ReleaseSchema[] | undefined>;

getContributorsLink(
projectWebUrl: string,
projectDefaultBranch: string
Expand All @@ -63,7 +80,6 @@ export type GitlabCIApi = {
projectDefaultBranch: string,
codeOwnersPath?: string
): string;

getReadme(
projectID: string | number,
branch?: string,
Expand Down
72 changes: 63 additions & 9 deletions packages/gitlab/src/api/GitlabCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parseCodeOwners } from '../components/utils';
import {
ContributorsSummary,
GitlabCIApi,
GitlabProjectCoverageResponse,
LanguagesSummary,
} from './GitlabCIApi';

Expand All @@ -17,6 +18,7 @@ import type {
RepositoryContributorSchema,
UserSchema,
} from '@gitbeaker/rest';
import dayjs from 'dayjs';

export class GitlabCIClient implements GitlabCIApi {
discoveryApi: DiscoveryApi;
Expand Down Expand Up @@ -70,19 +72,24 @@ export class GitlabCIClient implements GitlabCIApi {

protected async callApi<T>(
path: string,
query: { [key in string]: string }
query: { [key in string]: string },
options: RequestInit = {}
): Promise<T | undefined> {
const apiUrl = `${await this.discoveryApi.getBaseUrl('gitlab')}/${
this.gitlabInstance
}`;
const token = (await this.identityApi.getCredentials()).token;
const options = token
? {
headers: {
Authorization: `Bearer ${token}`,
},
}
: {};

if (token) {
options = {
...options,
headers: {
...options?.headers,
Authorization: `Bearer ${token}`,
},
};
}

const response = await fetch(
`${apiUrl}/${path}?${new URLSearchParams(query).toString()}`,
options
Expand Down Expand Up @@ -263,6 +270,51 @@ export class GitlabCIClient implements GitlabCIApi {
);
}

async getProjectCoverage(
projectSlug: string
): Promise<GitlabProjectCoverageResponse | undefined> {
if (!projectSlug) return undefined;

return await this.callApi<GitlabProjectCoverageResponse>(
'graphql',
{},
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
variables: {
projectSlug,
updatedAfter: dayjs()
.subtract(30, 'days')
.format('YYYY-MM-DD'),
},
query: /* GraphQL */ `
query getProjectCoverage(
$projectSlug: ID!
$updatedAfter: Time
) {
project(fullPath: $projectSlug) {
name
webUrl
pipelines(
ref: "main"
updatedAfter: $updatedAfter
) {
nodes {
coverage
createdAt
}
}
}
}
`,
}),
}
);
}

async getCodeOwners(
projectID: string | number,
branch = 'HEAD',
Expand Down Expand Up @@ -317,7 +369,9 @@ export class GitlabCIClient implements GitlabCIApi {
if (filePath.startsWith('./')) filePath = filePath.slice(2);

const readmeStr = await this.callApi<string>(
`projects/${projectID}/repository/files/${encodeURIComponent(filePath)}/raw`,
`projects/${projectID}/repository/files/${encodeURIComponent(
filePath
)}/raw`,
{ ref: branch }
);

Expand Down
111 changes: 111 additions & 0 deletions packages/gitlab/src/components/widgets/CoverageCard/CoverageCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import { Progress } from '@backstage/core-components';
import { Box, makeStyles } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { useAsync } from 'react-use';
import {
gitlabInstance,
gitlabProjectId,
gitlabProjectSlug,
} from '../../gitlabAppData';
import { GitlabCIApiRef } from '../../../api';
import { useApi } from '@backstage/core-plugin-api';
import { InfoCard, InfoCardVariants } from '@backstage/core-components';
import { LineChart } from '@mui/x-charts/LineChart';
import dayjs from 'dayjs';

const useStyles = makeStyles(() => ({
lineChartContainer: {
'& .v5-MuiChartsAxis-directionX .v5-MuiChartsAxis-tickLabel': {
transform: 'rotate(90deg)',
textAnchor: 'start',
},
'& .v5-MuiLineElement-root, .MuiMarkElement-root': {
strokeWidth: 1,
},
},
}));

type Props = {
variant?: InfoCardVariants;
};

const CoverageCard = (props: Props) => {
const project_id = gitlabProjectId();
const project_slug = gitlabProjectSlug();
const gitlab_instance = gitlabInstance();

const classes = useStyles();

const GitlabCIAPI = useApi(GitlabCIApiRef).build(
gitlab_instance || 'gitlab.com'
);

const { value, loading, error } = useAsync(async () => {
const coverageDetails = await GitlabCIAPI.getProjectCoverage(
project_slug || project_id
);

return coverageDetails;
}, []);

if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
}

const dataset = value
? [
...new Map(
value.data.project.pipelines.nodes
.filter((node) => !!node.coverage)
.sort((a, b) =>
new Date(a.createdAt) > new Date(b.createdAt) ? 1 : -1
)
.map((node) => ({
x: dayjs(node.createdAt).format('YYYY-MM-DD'),
y: node.coverage,
}))
.map((node) => [`${node.x}_${node.y}`, node])
).values(),
]
: [];

return value ? (
<InfoCard
title="Coverage statistics"
deepLink={{
link: `${value.data.project.webUrl}/-/graphs/main/charts`,
title: 'go to Analytics',
onClick: (e) => {
e.preventDefault();
window.open(`${value.data.project.webUrl}/-/graphs/main/charts`);
},
}}
variant={props.variant}
>
<Box position="relative">
<div className={classes.lineChartContainer}>
<LineChart
xAxis={[{ scaleType: 'point', dataKey: 'x' }]}
series={[{ dataKey: 'y' }]}
dataset={dataset}
height={300}
margin={{ bottom: 90, top: 20, left: 40, right: 20 }}
/>
</div>

<div>
{' '}
<b>Last Coverage: </b>
{`${value.data.project.pipelines.nodes[0].coverage}%`}
</div>
</Box>
</InfoCard>
) : (
<InfoCard title="Coverage Statistics" />
);
};

export default CoverageCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as CoverageCard } from './CoverageCard';
1 change: 1 addition & 0 deletions packages/gitlab/src/components/widgets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { PipelinesTable } from './PipelinesTable';
export { ReleasesCard, type ReleasesCardProps } from './ReleasesCard';
export { IssuesTable } from './IssuesTable';
export { ReadmeCard } from './ReadmeCard';
export { CoverageCard } from './CoverageCard';
1 change: 1 addition & 0 deletions packages/gitlab/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
EntityGitlabPipelinesTable,
EntityGitlabReleasesCard,
EntityGitlabReadmeCard,
EntityGitlabCoverageCard,
gitlabPlugin,
} from './plugin';
export * from './api';
Expand Down
12 changes: 12 additions & 0 deletions packages/gitlab/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ export const EntityGitlabReleasesCard = gitlabPlugin.provide(
})
);

export const EntityGitlabCoverageCard = gitlabPlugin.provide(
createComponentExtension({
name: 'EntityGitlabCoverageCard',
component: {
lazy: () =>
import('./components/widgets/index').then(
(m) => m.CoverageCard
),
},
})
);

export const EntityGitlabIssuesTable = gitlabPlugin.provide(
createComponentExtension({
name: 'EntityGitlabIssuesTable',
Expand Down
Loading

0 comments on commit 0946e52

Please sign in to comment.