Create additional stats for Stats component
Add button for fetching and calculating average release times Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
"recharts": "^1.8.5",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -39,6 +40,7 @@
|
||||
"@backstage/dev-utils": "^0.1.13",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@types/recharts": "^1.8.15",
|
||||
"@testing-library/react-hooks": "^3.4.2",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -591,7 +591,6 @@ ${selectedPatchCommit.commit.message}`,
|
||||
});
|
||||
|
||||
return releases.map(release => ({
|
||||
release,
|
||||
id: release.id,
|
||||
name: release.name,
|
||||
tagName: release.tag_name,
|
||||
|
||||
@@ -19,7 +19,6 @@ import { Alert } from '@material-ui/lab';
|
||||
import {
|
||||
Box,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -29,11 +28,11 @@ import {
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
|
||||
import { getMappedReleases } from './helpers/mapReleases';
|
||||
import { getReleasesWithTags } from './helpers/getReleasesWithTags';
|
||||
import { getSummary } from './helpers/getSummary';
|
||||
import { getMappedReleases } from './helpers/getMappedReleases';
|
||||
import { getReleaseStats } from './helpers/getReleaseStats';
|
||||
import { Info } from './Info/Info';
|
||||
import { ReleaseStatsContext } from './contexts/ReleaseStatsContext';
|
||||
import { Row } from './Row/Row';
|
||||
import { Summary } from './Summary';
|
||||
import { useGetStats } from './hooks/useGetStats';
|
||||
import { useProjectContext } from '../../contexts/ProjectContext';
|
||||
import { Warn } from './Warn';
|
||||
@@ -65,31 +64,31 @@ export function DialogBody() {
|
||||
|
||||
const { allReleases, allTags } = stats.value;
|
||||
const { mappedReleases } = getMappedReleases({ allReleases, project });
|
||||
const { releasesWithTags } = getReleasesWithTags({
|
||||
const { releaseStats } = getReleaseStats({
|
||||
mappedReleases,
|
||||
allTags,
|
||||
project,
|
||||
});
|
||||
const summary = getSummary({ releasesWithTags });
|
||||
|
||||
const shouldWarn =
|
||||
releasesWithTags.unmappableTags.length > 0 ||
|
||||
releasesWithTags.unmatchedTags.length > 0 ||
|
||||
releasesWithTags.unmatched.length > 0;
|
||||
releaseStats.unmappableTags.length > 0 ||
|
||||
releaseStats.unmatchedTags.length > 0 ||
|
||||
releaseStats.unmatchedReleases.length > 0;
|
||||
|
||||
if (shouldWarn) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("⚠️ Here's a summary of unmapped/unmatched tags/releases", {
|
||||
unmappableTags: releasesWithTags.unmappableTags,
|
||||
unmatchedTags: releasesWithTags.unmatchedTags,
|
||||
unmatchedReleases: releasesWithTags.unmatched,
|
||||
unmatchedReleases: releaseStats.unmatchedReleases,
|
||||
unmatchedTags: releaseStats.unmatchedTags,
|
||||
unmappableTags: releaseStats.unmappableTags,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Summary summary={summary} />
|
||||
<ReleaseStatsContext.Provider value={{ releaseStats }}>
|
||||
<Info />
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer>
|
||||
<Table className={classes.table} size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
@@ -102,26 +101,22 @@ export function DialogBody() {
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{Object.entries(releasesWithTags.releases).map(
|
||||
([baseVersion, releaseWithTags], index) => {
|
||||
{Object.entries(releaseStats.releases).map(
|
||||
([baseVersion, releaseStat], index) => {
|
||||
return (
|
||||
<Row
|
||||
key={`row-${index}`}
|
||||
baseVersion={baseVersion}
|
||||
releaseWithTags={releaseWithTags}
|
||||
releaseStat={releaseStat}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Box marginTop={2}>
|
||||
{shouldWarn && (
|
||||
<Warn releasesWithTags={releasesWithTags} project={project} />
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
<Box marginTop={2}>{shouldWarn && <Warn />}</Box>
|
||||
</TableContainer>
|
||||
</ReleaseStatsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { getDecimalNumber } from '../../helpers/getDecimalNumber';
|
||||
import { useGetReleaseTimes } from '../hooks/useGetReleaseTimes';
|
||||
|
||||
export function AverageReleaseTime({
|
||||
averageReleaseTime,
|
||||
}: {
|
||||
averageReleaseTime: ReturnType<
|
||||
typeof useGetReleaseTimes
|
||||
>['averageReleaseTime'];
|
||||
}) {
|
||||
if (averageReleaseTime.length === 0) {
|
||||
return <>-</>;
|
||||
}
|
||||
|
||||
const average = averageReleaseTime.reduce(
|
||||
(acc, { daysWithHours }) => {
|
||||
acc.daysWithHours += daysWithHours / averageReleaseTime.length;
|
||||
return acc;
|
||||
},
|
||||
{ daysWithHours: 0 },
|
||||
);
|
||||
|
||||
const days = Math.floor(average.daysWithHours);
|
||||
const hours = getDecimalNumber((average.daysWithHours - days) * 24, 1);
|
||||
|
||||
return (
|
||||
<>
|
||||
{days} days {hours} hours
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Tooltip as MaterialTooltip,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { BarChart, Bar, XAxis, YAxis, Legend, Tooltip } from 'recharts';
|
||||
|
||||
import { AverageReleaseTime } from './AverageReleaseTime';
|
||||
import { LinearProgressWithLabel } from '../../../../components/ResponseStepDialog/LinearProgressWithLabel';
|
||||
import { LongestReleaseTime } from './LongestReleaseTime';
|
||||
import { useGetReleaseTimes } from '../hooks/useGetReleaseTimes';
|
||||
import { useReleaseStatsContext } from '../../contexts/ReleaseStatsContext';
|
||||
|
||||
export function InDepth() {
|
||||
const { releaseStats } = useReleaseStatsContext();
|
||||
const {
|
||||
averageReleaseTime,
|
||||
progress,
|
||||
releaseCommitPairs,
|
||||
run,
|
||||
} = useGetReleaseTimes();
|
||||
|
||||
const skipped =
|
||||
Object.keys(releaseStats.releases).length - releaseCommitPairs.length;
|
||||
|
||||
return (
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h4">In-depth</Typography>
|
||||
</Box>
|
||||
|
||||
<Box style={{ display: 'flex' }}>
|
||||
<Box margin={1} style={{ display: 'flex', flex: 1 }}>
|
||||
<Box>
|
||||
<Typography variant="h6">Release time</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
<strong>Release time</strong> is derived by comparing{' '}
|
||||
<i>createdAt</i> of the commits belonging to the first and last
|
||||
tag of each release. Releases without patches will have tags
|
||||
pointing towards the same commit and will thus be omitted. This
|
||||
project will omit {skipped} out of the total{' '}
|
||||
{Object.keys(releaseStats.releases).length} releases.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
margin={1}
|
||||
style={{ display: 'flex', flex: 1, flexDirection: 'column' }}
|
||||
>
|
||||
<Box>
|
||||
<Typography variant="h6">In numbers</Typography>
|
||||
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
<strong>Average release time</strong>:{' '}
|
||||
<AverageReleaseTime averageReleaseTime={averageReleaseTime} />
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
<strong>Longest release</strong>:{' '}
|
||||
<LongestReleaseTime averageReleaseTime={averageReleaseTime} />
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
{progress === 0 && (
|
||||
<MaterialTooltip
|
||||
title={`This action will send ~${
|
||||
releaseCommitPairs.length * 2
|
||||
} requests`}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => run()}
|
||||
size="small"
|
||||
>
|
||||
Crunch the numbers
|
||||
</Button>
|
||||
</MaterialTooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box marginTop={4}>
|
||||
<BarChart
|
||||
width={700}
|
||||
height={70 + averageReleaseTime.length * 22}
|
||||
data={
|
||||
averageReleaseTime.length > 0
|
||||
? averageReleaseTime
|
||||
: [{ version: 'x.y.z', days: 0 }]
|
||||
}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
layout="vertical"
|
||||
>
|
||||
<XAxis type="number" />
|
||||
<YAxis dataKey="version" type="category" />
|
||||
<Tooltip labelStyle={{ color: '#000', fontWeight: 'bold' }} />
|
||||
<Legend />
|
||||
<Bar dataKey="days" fill="#82ca9d" />
|
||||
</BarChart>
|
||||
|
||||
{progress > 0 && progress < 100 && (
|
||||
<Box marginTop={1}>
|
||||
<LinearProgressWithLabel progress={progress} responseSteps={[]} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { getDecimalNumber } from '../../helpers/getDecimalNumber';
|
||||
import { useGetReleaseTimes } from '../hooks/useGetReleaseTimes';
|
||||
|
||||
export function LongestReleaseTime({
|
||||
averageReleaseTime,
|
||||
}: {
|
||||
averageReleaseTime: ReturnType<
|
||||
typeof useGetReleaseTimes
|
||||
>['averageReleaseTime'];
|
||||
}) {
|
||||
if (averageReleaseTime.length === 0) {
|
||||
return <>-</>;
|
||||
}
|
||||
|
||||
const longestRelease = [...averageReleaseTime].sort(
|
||||
(a, b) => b.daysWithHours - a.daysWithHours,
|
||||
)[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
{longestRelease.version} ({longestRelease.days} days{' '}
|
||||
{getDecimalNumber(longestRelease.hours, 1)} hours )
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Paper } from '@material-ui/core';
|
||||
|
||||
import { InDepth } from './InDepth/InDepth';
|
||||
import { Summary } from './Summary';
|
||||
|
||||
export function Info() {
|
||||
return (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
style={{
|
||||
padding: 20,
|
||||
marginLeft: 25,
|
||||
marginRight: 25,
|
||||
marginBottom: 25,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Summary />
|
||||
|
||||
<InDepth />
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
makeStyles,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { getDecimalNumber } from '../helpers/getDecimalNumber';
|
||||
import { getSummary } from '../helpers/getSummary';
|
||||
import { useReleaseStatsContext } from '../contexts/ReleaseStatsContext';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
table: {
|
||||
minWidth: 650,
|
||||
},
|
||||
});
|
||||
|
||||
export function Summary() {
|
||||
const { releaseStats } = useReleaseStatsContext();
|
||||
const { summary } = getSummary({ releaseStats });
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Box margin={1}>
|
||||
<Typography variant="h4">Summary</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
<strong>Total releases</strong>: {summary.totalReleases}
|
||||
</Typography>
|
||||
|
||||
<TableContainer>
|
||||
<Table size="small" className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
<TableCell>Patches</TableCell>
|
||||
<TableCell>Patches per release</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Release Candidate
|
||||
</TableCell>
|
||||
<TableCell>{summary.totalCandidatePatches}</TableCell>
|
||||
<TableCell>
|
||||
{getDecimalNumber(
|
||||
summary.totalCandidatePatches / summary.totalReleases,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Release Version
|
||||
</TableCell>
|
||||
<TableCell>{summary.totalVersionPatches}</TableCell>
|
||||
<TableCell>
|
||||
{getDecimalNumber(
|
||||
summary.totalVersionPatches / summary.totalReleases,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Total
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{summary.totalCandidatePatches + summary.totalVersionPatches}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{getDecimalNumber(
|
||||
(summary.totalCandidatePatches +
|
||||
summary.totalVersionPatches) /
|
||||
summary.totalReleases,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getReleaseCommitPairs } from './getReleaseCommitPairs';
|
||||
|
||||
describe('getReleaseCommitPairs', () => {
|
||||
it('should work', () => {
|
||||
const nonPublishedRelease = {
|
||||
baseVersion: '1.0',
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
candidates: [
|
||||
{
|
||||
tagName: 'rc-1.0.0',
|
||||
sha: 'sha-1.0.0',
|
||||
},
|
||||
{
|
||||
tagName: 'rc-1.0.1',
|
||||
sha: 'sha-1.0.1',
|
||||
},
|
||||
],
|
||||
versions: [],
|
||||
};
|
||||
|
||||
const releaseWithoutPatches = {
|
||||
baseVersion: '2.0',
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
candidates: [
|
||||
{
|
||||
tagName: 'rc-2.0.0',
|
||||
sha: 'sha-2.0.0',
|
||||
},
|
||||
],
|
||||
versions: [
|
||||
{
|
||||
tagName: 'version-2.0.0',
|
||||
sha: 'sha-2.0.0',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const releaseWithPatches = {
|
||||
baseVersion: '3.0',
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
candidates: [
|
||||
{
|
||||
tagName: 'rc-3.0.1',
|
||||
sha: 'sha-3.0.1',
|
||||
},
|
||||
{
|
||||
tagName: 'rc-3.0.0',
|
||||
sha: 'sha-3.0.0',
|
||||
},
|
||||
],
|
||||
versions: [
|
||||
{
|
||||
tagName: 'version-3.0.1',
|
||||
sha: 'sha-3.0.1',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getReleaseCommitPairs({
|
||||
releaseStats: {
|
||||
releases: {
|
||||
nonPublishedRelease, // Should be omitted
|
||||
releaseWithoutPatches, // Should be omitted
|
||||
releaseWithPatches,
|
||||
},
|
||||
unmatchedReleases: [],
|
||||
unmappableTags: [],
|
||||
unmatchedTags: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"releaseCommitPairs": Array [
|
||||
Object {
|
||||
"baseVersion": "3.0",
|
||||
"endCommit": Object {
|
||||
"sha": "sha-3.0.1",
|
||||
"tagName": "version-3.0.1",
|
||||
},
|
||||
"startCommit": Object {
|
||||
"sha": "sha-3.0.0",
|
||||
"tagName": "rc-3.0.0",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReleaseCommitPairs } from '../hooks/useGetReleaseTimes';
|
||||
import { ReleaseStats } from '../../contexts/ReleaseStatsContext';
|
||||
|
||||
export function getReleaseCommitPairs({
|
||||
releaseStats,
|
||||
}: {
|
||||
releaseStats: ReleaseStats;
|
||||
}) {
|
||||
const releaseCommitPairs = Object.values(releaseStats.releases).reduce(
|
||||
(acc: ReleaseCommitPairs, release) => {
|
||||
const startTag = [...release.candidates].reverse()[0];
|
||||
const endTag = release.versions[0];
|
||||
|
||||
// Missing Release Candidate for unknown reason
|
||||
if (!startTag?.sha) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Missing Release Version (likely prerelease)
|
||||
if (!endTag?.sha) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
// First RC tag is pointing towards the same commit as the most
|
||||
// recent Version tag, meaning there haven't been any patches
|
||||
// and we thus cannot determine and difference in time
|
||||
if (startTag?.sha === endTag?.sha) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc.concat({
|
||||
baseVersion: release.baseVersion,
|
||||
startCommit: {
|
||||
tagName: startTag.tagName,
|
||||
sha: startTag.sha,
|
||||
},
|
||||
endCommit: {
|
||||
tagName: endTag.tagName,
|
||||
sha: endTag.sha,
|
||||
},
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
releaseCommitPairs,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAsync, useAsyncFn } from 'react-use';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { getReleaseCommitPairs } from '../helpers/getReleaseCommitPairs';
|
||||
import { usePluginApiClientContext } from '../../../../contexts/PluginApiClientContext';
|
||||
import { useProjectContext } from '../../../../contexts/ProjectContext';
|
||||
import { useReleaseStatsContext } from '../../contexts/ReleaseStatsContext';
|
||||
|
||||
export type ReleaseCommitPairs = Array<{
|
||||
baseVersion: string;
|
||||
startCommit: {
|
||||
tagName: string;
|
||||
sha: string;
|
||||
};
|
||||
endCommit: {
|
||||
tagName: string;
|
||||
sha: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
type ReleaseTime = {
|
||||
version: string;
|
||||
daysWithHours: number;
|
||||
days: number;
|
||||
hours: number;
|
||||
startCommitCreatedAt?: string;
|
||||
endCommitCreatedAt?: string;
|
||||
};
|
||||
|
||||
export function useGetReleaseTimes() {
|
||||
const { pluginApiClient } = usePluginApiClientContext();
|
||||
const { project } = useProjectContext();
|
||||
const { releaseStats } = useReleaseStatsContext();
|
||||
const [averageReleaseTime, setAverageReleaseTime] = useState<ReleaseTime[]>(
|
||||
[],
|
||||
);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const { releaseCommitPairs } = getReleaseCommitPairs({ releaseStats });
|
||||
|
||||
const [fnRes, run] = useAsyncFn(() => {
|
||||
setProgress(0);
|
||||
return getAndSetReleaseTime(0);
|
||||
});
|
||||
|
||||
useAsync(async () => {
|
||||
if (averageReleaseTime.length === 0) return;
|
||||
if (releaseCommitPairs.length === averageReleaseTime.length) return;
|
||||
|
||||
await getAndSetReleaseTime(averageReleaseTime.length);
|
||||
}, [fnRes.value, averageReleaseTime]);
|
||||
|
||||
useEffect(() => {
|
||||
const unboundedProgress = Math.round(
|
||||
(averageReleaseTime.length / releaseCommitPairs.length) * 100,
|
||||
);
|
||||
const boundedProgress = unboundedProgress > 100 ? 100 : unboundedProgress;
|
||||
|
||||
setProgress(boundedProgress);
|
||||
}, [averageReleaseTime.length, releaseCommitPairs.length]);
|
||||
|
||||
async function getAndSetReleaseTime(index: number) {
|
||||
const { baseVersion, startCommit, endCommit } = releaseCommitPairs[index];
|
||||
|
||||
const [
|
||||
{ createdAt: startCommitCreatedAt },
|
||||
{ createdAt: endCommitCreatedAt },
|
||||
] = await Promise.all([
|
||||
pluginApiClient.stats.getCommit({
|
||||
owner: project.owner,
|
||||
repo: project.repo,
|
||||
ref: startCommit.sha,
|
||||
}),
|
||||
pluginApiClient.stats.getCommit({
|
||||
owner: project.owner,
|
||||
repo: project.repo,
|
||||
ref: endCommit.sha,
|
||||
}),
|
||||
]);
|
||||
|
||||
const releaseTime: ReleaseTime = {
|
||||
version: baseVersion,
|
||||
daysWithHours: 0,
|
||||
days: 0,
|
||||
hours: 0,
|
||||
startCommitCreatedAt,
|
||||
endCommitCreatedAt,
|
||||
};
|
||||
|
||||
if (startCommitCreatedAt && endCommitCreatedAt) {
|
||||
const { days: luxDays = 0, hours: luxHours = 0 } = DateTime.fromISO(
|
||||
endCommitCreatedAt,
|
||||
)
|
||||
.diff(DateTime.fromISO(startCommitCreatedAt), ['days', 'hours'])
|
||||
.toObject();
|
||||
|
||||
releaseTime.daysWithHours = luxDays + luxHours / 24;
|
||||
releaseTime.days = luxDays;
|
||||
releaseTime.hours = luxHours;
|
||||
}
|
||||
|
||||
setAverageReleaseTime([...averageReleaseTime, releaseTime]);
|
||||
}
|
||||
|
||||
return {
|
||||
releaseCommitPairs,
|
||||
averageReleaseTime,
|
||||
progress,
|
||||
run,
|
||||
};
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
|
||||
|
||||
import { getReleasesWithTags } from '../helpers/getReleasesWithTags';
|
||||
import { ReleaseStats } from '../contexts/ReleaseStatsContext';
|
||||
import { RowCollapsed } from './RowCollapsed/RowCollapsed';
|
||||
|
||||
const useRowStyles = makeStyles({
|
||||
@@ -40,17 +40,15 @@ const useRowStyles = makeStyles({
|
||||
|
||||
interface RowProps {
|
||||
baseVersion: string;
|
||||
releaseWithTags: ReturnType<
|
||||
typeof getReleasesWithTags
|
||||
>['releasesWithTags']['releases']['0'];
|
||||
releaseStat: ReleaseStats['releases']['0'];
|
||||
}
|
||||
|
||||
export function Row({ baseVersion, releaseWithTags }: RowProps) {
|
||||
export function Row({ baseVersion, releaseStat }: RowProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const classes = useRowStyles();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<TableRow className={classes.root}>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
@@ -63,34 +61,32 @@ export function Row({ baseVersion, releaseWithTags }: RowProps) {
|
||||
</TableCell>
|
||||
|
||||
<TableCell component="th" scope="row">
|
||||
<Link href={releaseWithTags.htmlUrl} target="_blank">
|
||||
<Link href={releaseStat.htmlUrl} target="_blank">
|
||||
{baseVersion}
|
||||
{releaseWithTags.versions.length === 0 ? ' (prerelease)' : ''}
|
||||
{releaseStat.versions.length === 0 ? ' (prerelease)' : ''}
|
||||
</Link>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{releaseWithTags.createdAt
|
||||
? DateTime.fromISO(releaseWithTags.createdAt)
|
||||
{releaseStat.createdAt
|
||||
? DateTime.fromISO(releaseStat.createdAt)
|
||||
.setLocale('sv-SE')
|
||||
.toFormat('yyyy-MM-dd')
|
||||
: '-'}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{releaseWithTags.candidates.length}</TableCell>
|
||||
<TableCell>{releaseStat.candidates.length}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{Math.max(0, releaseWithTags.versions.length - 1)}
|
||||
</TableCell>
|
||||
<TableCell>{Math.max(0, releaseStat.versions.length - 1)}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<RowCollapsed releaseWithTags={releaseWithTags} />
|
||||
<RowCollapsed releaseStat={releaseStat} />
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+7
-9
@@ -17,14 +17,12 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
|
||||
import { getReleasesWithTags } from '../../helpers/getReleasesWithTags';
|
||||
import { ReleaseStats } from '../../contexts/ReleaseStatsContext';
|
||||
|
||||
export function ReleaseTagList({
|
||||
releaseWithTags,
|
||||
releaseStat,
|
||||
}: {
|
||||
releaseWithTags: ReturnType<
|
||||
typeof getReleasesWithTags
|
||||
>['releasesWithTags']['releases']['0'];
|
||||
releaseStat: ReleaseStats['releases']['0'];
|
||||
}) {
|
||||
return (
|
||||
<Box
|
||||
@@ -36,9 +34,9 @@ export function ReleaseTagList({
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{releaseWithTags.versions.length > 0 && (
|
||||
{releaseStat.versions.length > 0 && (
|
||||
<Box style={{ position: 'relative' }}>
|
||||
{releaseWithTags.versions.map(version => (
|
||||
{releaseStat.versions.map(version => (
|
||||
<Typography variant="body1" key={version.tagName}>
|
||||
{version.tagName}
|
||||
</Typography>
|
||||
@@ -46,7 +44,7 @@ export function ReleaseTagList({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{releaseWithTags.versions.length > 0 && (
|
||||
{releaseStat.versions.length > 0 && (
|
||||
<Box
|
||||
margin={1}
|
||||
style={{
|
||||
@@ -60,7 +58,7 @@ export function ReleaseTagList({
|
||||
)}
|
||||
|
||||
<Box style={{ position: 'relative' }}>
|
||||
{releaseWithTags.candidates.map(candidate => (
|
||||
{releaseStat.candidates.map(candidate => (
|
||||
<Typography variant="body1" key={candidate.tagName}>
|
||||
{candidate.tagName}
|
||||
</Typography>
|
||||
|
||||
+15
-24
@@ -15,27 +15,23 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { CenteredCircularProgress } from '../../../../components/CenteredCircularProgress';
|
||||
import { getReleasesWithTags } from '../../helpers/getReleasesWithTags';
|
||||
import { useGetCommit } from '../../hooks/useGetCommit';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
|
||||
import { CenteredCircularProgress } from '../../../../components/CenteredCircularProgress';
|
||||
import { ReleaseStats } from '../../contexts/ReleaseStatsContext';
|
||||
import { useGetCommit } from '../../hooks/useGetCommit';
|
||||
|
||||
interface ReleaseTimeProps {
|
||||
releaseWithTags: ReturnType<
|
||||
typeof getReleasesWithTags
|
||||
>['releasesWithTags']['releases']['0'];
|
||||
releaseStat: ReleaseStats['releases']['0'];
|
||||
}
|
||||
|
||||
export function ReleaseTime({ releaseWithTags }: ReleaseTimeProps) {
|
||||
const reversedCandidates = [...releaseWithTags.candidates].reverse();
|
||||
|
||||
const firstCandidateSha = reversedCandidates[0]?.sha;
|
||||
export function ReleaseTime({ releaseStat }: ReleaseTimeProps) {
|
||||
const firstCandidateSha = [...releaseStat.candidates].reverse()[0]?.sha;
|
||||
const { commit: releaseCut } = useGetCommit({ ref: firstCandidateSha });
|
||||
|
||||
const mostRecentVersionSha = releaseWithTags.versions[0]?.sha;
|
||||
const mostRecentVersionSha = releaseStat.versions[0]?.sha;
|
||||
const { commit: releaseComplete } = useGetCommit({
|
||||
ref: mostRecentVersionSha,
|
||||
});
|
||||
@@ -57,15 +53,6 @@ export function ReleaseTime({ releaseWithTags }: ReleaseTimeProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (releaseComplete.error) {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Failed to fetch the final Release Version Commit (
|
||||
{releaseComplete.error.message})
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
const diff =
|
||||
releaseCut.value?.createdAt && releaseComplete.value?.createdAt
|
||||
? DateTime.fromISO(releaseComplete.value.createdAt)
|
||||
@@ -83,7 +70,7 @@ export function ReleaseTime({ releaseWithTags }: ReleaseTimeProps) {
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
Release completed{' '}
|
||||
{releaseStat.versions.length === 0 ? '-' : 'Release completed '}
|
||||
{releaseComplete.value?.createdAt &&
|
||||
DateTime.fromISO(releaseComplete.value.createdAt)
|
||||
.setLocale('sv-SE')
|
||||
@@ -99,7 +86,11 @@ export function ReleaseTime({ releaseWithTags }: ReleaseTimeProps) {
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" color="secondary">
|
||||
Release time: {diff.days} days
|
||||
{diff.days === -1 ? (
|
||||
<>Ongoing: {diff.days} days</>
|
||||
) : (
|
||||
<>Completed in: {diff.days} days</>
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -16,17 +16,15 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@material-ui/core';
|
||||
|
||||
import { getReleasesWithTags } from '../../helpers/getReleasesWithTags';
|
||||
import { ReleaseTime } from './ReleaseTime';
|
||||
import { ReleaseStats } from '../../contexts/ReleaseStatsContext';
|
||||
import { ReleaseTagList } from './ReleaseTagList';
|
||||
import { ReleaseTime } from './ReleaseTime';
|
||||
|
||||
interface RowCollapsedProps {
|
||||
releaseWithTags: ReturnType<
|
||||
typeof getReleasesWithTags
|
||||
>['releasesWithTags']['releases']['0'];
|
||||
releaseStat: ReleaseStats['releases']['0'];
|
||||
}
|
||||
|
||||
export function RowCollapsed({ releaseWithTags }: RowCollapsedProps) {
|
||||
export function RowCollapsed({ releaseStat }: RowCollapsedProps) {
|
||||
return (
|
||||
<Box
|
||||
margin={1}
|
||||
@@ -37,9 +35,9 @@ export function RowCollapsed({ releaseWithTags }: RowCollapsedProps) {
|
||||
paddingRight: '10%',
|
||||
}}
|
||||
>
|
||||
<ReleaseTagList releaseWithTags={releaseWithTags} />
|
||||
<ReleaseTagList releaseStat={releaseStat} />
|
||||
|
||||
<ReleaseTime releaseWithTags={releaseWithTags} />
|
||||
<ReleaseTime releaseStat={releaseStat} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Paper, Typography } from '@material-ui/core';
|
||||
|
||||
import { getDecimalNumber } from './helpers/getDecimalNumber';
|
||||
import { getSummary } from './helpers/getSummary';
|
||||
|
||||
interface SummaryProps {
|
||||
summary: ReturnType<typeof getSummary>;
|
||||
}
|
||||
|
||||
export function Summary({ summary }: SummaryProps) {
|
||||
return (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
style={{
|
||||
padding: 20,
|
||||
marginLeft: 25,
|
||||
marginRight: 25,
|
||||
marginBottom: 25,
|
||||
}}
|
||||
>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h4">Summary</Typography>
|
||||
<Typography variant="body2">
|
||||
Total releases: {summary.totalReleases}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6">Release Candidate</Typography>
|
||||
<Typography variant="body2">
|
||||
Release Candidate patches: {summary.totalCandidatePatches}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
Release Candidate patches per release:{' '}
|
||||
{getDecimalNumber(
|
||||
summary.totalCandidatePatches / summary.totalReleases,
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6">Release Version</Typography>
|
||||
<Typography variant="body2">
|
||||
Release Version patches: {summary.totalVersionPatches}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
Release Version patches per release:{' '}
|
||||
{getDecimalNumber(
|
||||
summary.totalVersionPatches / summary.totalReleases,
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6">Total</Typography>
|
||||
<Typography variant="body2">
|
||||
Patches: {summary.totalCandidatePatches + summary.totalVersionPatches}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
Patches per release:{' '}
|
||||
{getDecimalNumber(
|
||||
(summary.totalCandidatePatches + summary.totalVersionPatches) /
|
||||
summary.totalReleases,
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -17,39 +17,37 @@
|
||||
import React from 'react';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
|
||||
import { getReleasesWithTags } from './helpers/getReleasesWithTags';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { useProjectContext } from '../../contexts/ProjectContext';
|
||||
import { useReleaseStatsContext } from './contexts/ReleaseStatsContext';
|
||||
|
||||
interface WarnProps {
|
||||
releasesWithTags: ReturnType<typeof getReleasesWithTags>['releasesWithTags'];
|
||||
project: Project;
|
||||
}
|
||||
export const Warn = () => {
|
||||
const { releaseStats } = useReleaseStatsContext();
|
||||
const { project } = useProjectContext();
|
||||
|
||||
export const Warn = ({ releasesWithTags, project }: WarnProps) => {
|
||||
return (
|
||||
<Alert severity="warning" style={{ marginBottom: 10 }}>
|
||||
{releasesWithTags.unmappableTags.length > 0 && (
|
||||
{releaseStats.unmappableTags.length > 0 && (
|
||||
<div>
|
||||
Failed to map{' '}
|
||||
<strong>{releasesWithTags.unmappableTags.length}</strong> tags to
|
||||
releases
|
||||
Failed to map <strong>{releaseStats.unmappableTags.length}</strong>{' '}
|
||||
tags to releases
|
||||
</div>
|
||||
)}
|
||||
|
||||
{releasesWithTags.unmatchedTags.length > 0 && (
|
||||
{releaseStats.unmatchedTags.length > 0 && (
|
||||
<div>
|
||||
Failed to match <strong>{releaseStats.unmatchedTags.length}</strong>{' '}
|
||||
tags to {project.versioningStrategy}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{releaseStats.unmatchedReleases.length > 0 && (
|
||||
<div>
|
||||
Failed to match{' '}
|
||||
<strong>{releasesWithTags.unmatchedTags.length}</strong> tags to{' '}
|
||||
<strong>{releaseStats.unmatchedReleases.length}</strong> releases to{' '}
|
||||
{project.versioningStrategy}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{releasesWithTags.unmatched.length > 0 && (
|
||||
<div>
|
||||
Failed to match <strong>{releasesWithTags.unmatched.length}</strong>{' '}
|
||||
releases to {project.versioningStrategy}
|
||||
</div>
|
||||
)}
|
||||
<div>See full output in the console</div>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import { GitHubReleaseManagerError } from '../../../errors/GitHubReleaseManagerError';
|
||||
|
||||
export interface ReleaseStats {
|
||||
unmappableTags: string[];
|
||||
unmatchedTags: string[];
|
||||
unmatchedReleases: string[];
|
||||
releases: {
|
||||
[baseVersion: string]: {
|
||||
baseVersion: string;
|
||||
createdAt: string | null;
|
||||
htmlUrl: string;
|
||||
|
||||
/**
|
||||
* Ordered from new to old
|
||||
*/
|
||||
candidates: {
|
||||
tagName: string;
|
||||
sha: string;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* Ordered from new to old
|
||||
*/
|
||||
versions: {
|
||||
tagName: string;
|
||||
sha: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const ReleaseStatsContext = createContext<
|
||||
{ releaseStats: ReleaseStats } | undefined
|
||||
>(undefined);
|
||||
|
||||
export const useReleaseStatsContext = () => {
|
||||
const { releaseStats } = useContext(ReleaseStatsContext) ?? {};
|
||||
|
||||
if (!releaseStats) {
|
||||
throw new GitHubReleaseManagerError('releaseStats not found');
|
||||
}
|
||||
|
||||
return {
|
||||
releaseStats,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getDecimalNumber } from './getDecimalNumber';
|
||||
|
||||
describe('getDecimalNumber', () => {
|
||||
it('should handle NaN', () => {
|
||||
const result = getDecimalNumber(NaN);
|
||||
|
||||
expect(result).toEqual(0);
|
||||
});
|
||||
|
||||
it('should only handle decimals', () => {
|
||||
const result = getDecimalNumber(1);
|
||||
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
|
||||
it('should get decimal number with default decimals = 2', () => {
|
||||
const result = getDecimalNumber(1 / 3);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`0.33`);
|
||||
});
|
||||
|
||||
it('should get decimal number for decimals = 1', () => {
|
||||
const result = getDecimalNumber(1 / 3, 1);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`0.3`);
|
||||
});
|
||||
});
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function getDecimalNumber(n: number) {
|
||||
export function getDecimalNumber(n: number, decimals = 2) {
|
||||
if (isNaN(n)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n.toString().includes('.')) {
|
||||
return n.toFixed(2);
|
||||
return parseFloat(n.toFixed(decimals));
|
||||
}
|
||||
|
||||
return n;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getMappedReleases } from './getMappedReleases';
|
||||
import { mockSemverProject } from '../../../test-helpers/test-helpers';
|
||||
|
||||
describe('getMappedReleases', () => {
|
||||
it('should get mapped releases', () => {
|
||||
const createRelease = (tagName: string) => ({
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
id: 1,
|
||||
name: 'name',
|
||||
tagName,
|
||||
});
|
||||
|
||||
const result = getMappedReleases({
|
||||
project: mockSemverProject,
|
||||
allReleases: [createRelease('rc-1.0.0'), createRelease('rc-1.1.0')],
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"mappedReleases": Object {
|
||||
"releases": Object {
|
||||
"1.0": Object {
|
||||
"baseVersion": "1.0",
|
||||
"candidates": Array [
|
||||
Object {
|
||||
"sha": "",
|
||||
"tagName": "rc-1.0.0",
|
||||
},
|
||||
],
|
||||
"createdAt": "2021-01-01T10:11:12Z",
|
||||
"htmlUrl": "html_url",
|
||||
"versions": Array [],
|
||||
},
|
||||
"1.1": Object {
|
||||
"baseVersion": "1.1",
|
||||
"candidates": Array [
|
||||
Object {
|
||||
"sha": "",
|
||||
"tagName": "rc-1.1.0",
|
||||
},
|
||||
],
|
||||
"createdAt": "2021-01-01T10:11:12Z",
|
||||
"htmlUrl": "html_url",
|
||||
"versions": Array [],
|
||||
},
|
||||
},
|
||||
"unmappableTags": Array [],
|
||||
"unmatchedReleases": Array [],
|
||||
"unmatchedTags": Array [],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
+19
-32
@@ -17,6 +17,7 @@
|
||||
import { calverRegexp } from '../../../helpers/tagParts/getCalverTagParts';
|
||||
import { GetAllReleasesResult } from '../../../api/PluginApiClient';
|
||||
import { Project } from '../../../contexts/ProjectContext';
|
||||
import { ReleaseStats } from '../contexts/ReleaseStatsContext';
|
||||
import { semverRegexp } from '../../../helpers/tagParts/getSemverTagParts';
|
||||
|
||||
export function getMappedReleases({
|
||||
@@ -28,61 +29,47 @@ export function getMappedReleases({
|
||||
}) {
|
||||
return {
|
||||
mappedReleases: allReleases.reduce(
|
||||
(
|
||||
acc: {
|
||||
unmatched: string[];
|
||||
releases: {
|
||||
[baseVersion: string]: {
|
||||
createdAt: string | null;
|
||||
candidates: {
|
||||
tagName: string;
|
||||
sha: string;
|
||||
}[];
|
||||
versions: {
|
||||
tagName: string;
|
||||
sha: string;
|
||||
}[];
|
||||
htmlUrl: string;
|
||||
};
|
||||
};
|
||||
},
|
||||
release,
|
||||
) => {
|
||||
(acc: ReleaseStats, release) => {
|
||||
const match =
|
||||
project.versioningStrategy === 'semver'
|
||||
? release.tagName.match(semverRegexp)
|
||||
: release.tagName.match(calverRegexp);
|
||||
|
||||
if (!match) {
|
||||
acc.unmatched.push(release.tagName);
|
||||
acc.unmatchedReleases.push(release.tagName);
|
||||
return acc;
|
||||
}
|
||||
|
||||
const prefix = match[1];
|
||||
const prefix = match[1] as 'rc' | 'version';
|
||||
const baseVersion =
|
||||
project.versioningStrategy === 'semver'
|
||||
? `${match[2]}.${match[3]}`
|
||||
: match[2];
|
||||
|
||||
if (!acc.releases[baseVersion]) {
|
||||
acc.releases[baseVersion] = {
|
||||
createdAt: release.createdAt,
|
||||
candidates:
|
||||
prefix === 'rc' ? [{ tagName: release.tagName, sha: '' }] : [],
|
||||
versions:
|
||||
prefix === 'version'
|
||||
? [{ tagName: release.tagName, sha: '' }]
|
||||
: [],
|
||||
htmlUrl: release.htmlUrl,
|
||||
const releaseEntry = {
|
||||
tagName: release.tagName,
|
||||
sha: '',
|
||||
};
|
||||
|
||||
acc.releases[baseVersion] = {
|
||||
baseVersion,
|
||||
createdAt: release.createdAt,
|
||||
htmlUrl: release.htmlUrl,
|
||||
candidates: prefix === 'rc' ? [releaseEntry] : [],
|
||||
versions: prefix === 'version' ? [releaseEntry] : [],
|
||||
};
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
unmatched: [],
|
||||
releases: {},
|
||||
unmappableTags: [],
|
||||
unmatchedReleases: [],
|
||||
unmatchedTags: [],
|
||||
},
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getReleaseStats } from './getReleaseStats';
|
||||
import { mockSemverProject } from '../../../test-helpers/test-helpers';
|
||||
|
||||
describe('getReleaseStats', () => {
|
||||
it('should get releases with tags', () => {
|
||||
const result = getReleaseStats({
|
||||
project: mockSemverProject,
|
||||
mappedReleases: {
|
||||
releases: {
|
||||
'1.0': {
|
||||
baseVersion: '1.0',
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
candidates: [
|
||||
{
|
||||
tagName: 'rc-1.0.0',
|
||||
sha: '',
|
||||
},
|
||||
],
|
||||
versions: [],
|
||||
},
|
||||
'1.1': {
|
||||
baseVersion: '1.1',
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
candidates: [
|
||||
{
|
||||
tagName: 'rc-1.1.0',
|
||||
sha: '',
|
||||
},
|
||||
],
|
||||
versions: [],
|
||||
},
|
||||
},
|
||||
unmappableTags: [],
|
||||
unmatchedReleases: [],
|
||||
unmatchedTags: [],
|
||||
},
|
||||
allTags: [
|
||||
{ sha: 'sha', tagName: 'rc-1.0.0' },
|
||||
{ sha: 'sha', tagName: 'rc-1.0.1' },
|
||||
{ sha: 'sha', tagName: 'rc-1.0.2' },
|
||||
{ sha: 'sha', tagName: 'version-1.0.2' },
|
||||
{ sha: 'sha', tagName: 'rc-1.1.1' },
|
||||
|
||||
{ sha: 'unmatchable', tagName: 'rc-1/2/3' },
|
||||
{ sha: 'unmappable', tagName: 'rc-123.123.123' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"releaseStats": Object {
|
||||
"releases": Object {
|
||||
"1.0": Object {
|
||||
"baseVersion": "1.0",
|
||||
"candidates": Array [
|
||||
Object {
|
||||
"sha": "sha",
|
||||
"tagName": "rc-1.0.0",
|
||||
},
|
||||
Object {
|
||||
"sha": "sha",
|
||||
"tagName": "rc-1.0.1",
|
||||
},
|
||||
Object {
|
||||
"sha": "sha",
|
||||
"tagName": "rc-1.0.2",
|
||||
},
|
||||
],
|
||||
"createdAt": "2021-01-01T10:11:12Z",
|
||||
"htmlUrl": "html_url",
|
||||
"versions": Array [
|
||||
Object {
|
||||
"sha": "sha",
|
||||
"tagName": "version-1.0.2",
|
||||
},
|
||||
],
|
||||
},
|
||||
"1.1": Object {
|
||||
"baseVersion": "1.1",
|
||||
"candidates": Array [
|
||||
Object {
|
||||
"sha": "",
|
||||
"tagName": "rc-1.1.0",
|
||||
},
|
||||
Object {
|
||||
"sha": "sha",
|
||||
"tagName": "rc-1.1.1",
|
||||
},
|
||||
],
|
||||
"createdAt": "2021-01-01T10:11:12Z",
|
||||
"htmlUrl": "html_url",
|
||||
"versions": Array [],
|
||||
},
|
||||
},
|
||||
"unmappableTags": Array [
|
||||
"rc-123.123.123",
|
||||
],
|
||||
"unmatchedReleases": Array [],
|
||||
"unmatchedTags": Array [
|
||||
"rc-1/2/3",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { calverRegexp } from '../../../helpers/tagParts/getCalverTagParts';
|
||||
import { GetAllTagsResult } from '../../../api/PluginApiClient';
|
||||
import { Project } from '../../../contexts/ProjectContext';
|
||||
import { ReleaseStats } from '../contexts/ReleaseStatsContext';
|
||||
import { semverRegexp } from '../../../helpers/tagParts/getSemverTagParts';
|
||||
|
||||
export function getReleaseStats({
|
||||
allTags,
|
||||
project,
|
||||
mappedReleases,
|
||||
}: {
|
||||
allTags: GetAllTagsResult;
|
||||
project: Project;
|
||||
mappedReleases: ReleaseStats;
|
||||
}) {
|
||||
const releaseStats = allTags.reduce(
|
||||
(acc: ReleaseStats, tag) => {
|
||||
const match =
|
||||
project.versioningStrategy === 'semver'
|
||||
? tag.tagName.match(semverRegexp)
|
||||
: tag.tagName.match(calverRegexp);
|
||||
|
||||
if (!match) {
|
||||
acc.unmatchedTags.push(tag.tagName);
|
||||
return acc;
|
||||
}
|
||||
|
||||
const prefix = match[1] as 'rc' | 'version';
|
||||
const baseVersion =
|
||||
project.versioningStrategy === 'semver'
|
||||
? `${match[2]}.${match[3]}` // major.minor
|
||||
: match[2]; // yyyy.MM.dd
|
||||
|
||||
const release = acc.releases[baseVersion];
|
||||
|
||||
if (!release) {
|
||||
acc.unmappableTags.push(tag.tagName);
|
||||
return acc;
|
||||
}
|
||||
|
||||
const dest = release[prefix === 'rc' ? 'candidates' : 'versions'];
|
||||
const releaseToEnrich = dest.find(
|
||||
({ tagName }) => tagName === tag.tagName,
|
||||
);
|
||||
|
||||
if (releaseToEnrich) {
|
||||
releaseToEnrich.sha = tag.sha;
|
||||
} else {
|
||||
dest.push({
|
||||
tagName: tag.tagName,
|
||||
sha: tag.sha,
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
...mappedReleases,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
releaseStats,
|
||||
};
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { calverRegexp } from '../../../helpers/tagParts/getCalverTagParts';
|
||||
import { GetAllTagsResult } from '../../../api/PluginApiClient';
|
||||
import { getMappedReleases } from './mapReleases';
|
||||
import { Project } from '../../../contexts/ProjectContext';
|
||||
import { semverRegexp } from '../../../helpers/tagParts/getSemverTagParts';
|
||||
|
||||
export function getReleasesWithTags({
|
||||
allTags,
|
||||
project,
|
||||
mappedReleases,
|
||||
}: {
|
||||
allTags: GetAllTagsResult;
|
||||
project: Project;
|
||||
mappedReleases: ReturnType<typeof getMappedReleases>['mappedReleases'];
|
||||
}) {
|
||||
return {
|
||||
releasesWithTags: allTags.reduce(
|
||||
(
|
||||
acc: ReturnType<typeof getMappedReleases>['mappedReleases'] & {
|
||||
unmatchedTags: string[];
|
||||
unmappableTags: string[];
|
||||
},
|
||||
tag,
|
||||
) => {
|
||||
const match =
|
||||
project.versioningStrategy === 'semver'
|
||||
? tag.tagName.match(semverRegexp)
|
||||
: tag.tagName.match(calverRegexp);
|
||||
|
||||
if (!match) {
|
||||
acc.unmatchedTags.push(tag.tagName);
|
||||
return acc;
|
||||
}
|
||||
|
||||
const prefix = match[1];
|
||||
const baseVersion =
|
||||
project.versioningStrategy === 'semver'
|
||||
? `${match[2]}.${match[3]}`
|
||||
: match[2];
|
||||
|
||||
if (!acc.releases[baseVersion]) {
|
||||
acc.unmappableTags.push(tag.tagName);
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (prefix === 'rc') {
|
||||
const existingEntry = acc.releases[baseVersion].candidates.find(
|
||||
({ tagName }) => tagName === tag.tagName,
|
||||
);
|
||||
|
||||
if (existingEntry) {
|
||||
existingEntry.sha = tag.sha;
|
||||
} else {
|
||||
acc.releases[baseVersion].candidates.push({
|
||||
tagName: tag.tagName,
|
||||
sha: tag.sha,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (prefix === 'version') {
|
||||
const existingEntry = acc.releases[baseVersion].versions.find(
|
||||
({ tagName }) => tagName === tag.tagName,
|
||||
);
|
||||
|
||||
if (existingEntry) {
|
||||
existingEntry.sha = tag.sha;
|
||||
} else {
|
||||
acc.releases[baseVersion].versions.push({
|
||||
tagName: tag.tagName,
|
||||
sha: tag.sha,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
...mappedReleases,
|
||||
unmatchedTags: [],
|
||||
unmappableTags: [],
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getSummary } from './getSummary';
|
||||
import { mockReleaseStats } from '../../../test-helpers/stats';
|
||||
|
||||
describe('getSummary', () => {
|
||||
it('should get summary', () => {
|
||||
const result = getSummary({ releaseStats: mockReleaseStats });
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"summary": Object {
|
||||
"totalCandidatePatches": 3,
|
||||
"totalReleases": 2,
|
||||
"totalVersionPatches": 1,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -14,32 +14,35 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getReleasesWithTags } from './getReleasesWithTags';
|
||||
import { ReleaseStats } from '../contexts/ReleaseStatsContext';
|
||||
|
||||
export function getSummary({
|
||||
releasesWithTags,
|
||||
}: {
|
||||
releasesWithTags: ReturnType<typeof getReleasesWithTags>['releasesWithTags'];
|
||||
}) {
|
||||
return Object.entries(releasesWithTags.releases).reduce(
|
||||
(
|
||||
acc: {
|
||||
totalReleases: number;
|
||||
totalCandidatePatches: number;
|
||||
totalVersionPatches: number;
|
||||
export function getSummary({ releaseStats }: { releaseStats: ReleaseStats }) {
|
||||
return {
|
||||
summary: Object.entries(releaseStats.releases).reduce(
|
||||
(
|
||||
acc: {
|
||||
totalReleases: number;
|
||||
totalCandidatePatches: number;
|
||||
totalVersionPatches: number;
|
||||
},
|
||||
[_baseVersion, mappedRelease],
|
||||
) => {
|
||||
const candidatePatches =
|
||||
Object.keys(mappedRelease.candidates).length - 1;
|
||||
const versionPatches = Object.keys(mappedRelease.versions).length - 1;
|
||||
|
||||
acc.totalReleases += 1;
|
||||
acc.totalCandidatePatches +=
|
||||
candidatePatches >= 0 ? candidatePatches : 0;
|
||||
acc.totalVersionPatches += versionPatches >= 0 ? versionPatches : 0;
|
||||
|
||||
return acc;
|
||||
},
|
||||
[_baseVersion, mappedRelease],
|
||||
) => {
|
||||
acc.totalReleases += 1;
|
||||
acc.totalCandidatePatches += mappedRelease.candidates.length - 1;
|
||||
acc.totalVersionPatches += mappedRelease.versions.length - 1;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
totalReleases: 0,
|
||||
totalCandidatePatches: 0,
|
||||
totalVersionPatches: 0,
|
||||
},
|
||||
);
|
||||
{
|
||||
totalReleases: 0,
|
||||
totalCandidatePatches: 0,
|
||||
totalVersionPatches: 0,
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,20 +16,25 @@
|
||||
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { GitHubReleaseManagerError } from '../../../errors/GitHubReleaseManagerError';
|
||||
import { usePluginApiClientContext } from '../../../contexts/PluginApiClientContext';
|
||||
import { useProjectContext } from '../../../contexts/ProjectContext';
|
||||
|
||||
export const useGetCommit = ({ ref }: { ref: string }) => {
|
||||
export const useGetCommit = ({ ref }: { ref?: string }) => {
|
||||
const { pluginApiClient } = usePluginApiClientContext();
|
||||
const { project } = useProjectContext();
|
||||
|
||||
const commit = useAsync(() =>
|
||||
pluginApiClient.stats.getCommit({
|
||||
const commit = useAsync(async () => {
|
||||
if (!ref) {
|
||||
throw new GitHubReleaseManagerError('Missing ref to get commit');
|
||||
}
|
||||
|
||||
return pluginApiClient.stats.getCommit({
|
||||
owner: project.owner,
|
||||
repo: project.repo,
|
||||
ref,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
commit,
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReleaseStats } from '../features/Stats/contexts/ReleaseStatsContext';
|
||||
|
||||
export const mockReleaseStats: ReleaseStats = {
|
||||
releases: {
|
||||
'1.0': {
|
||||
baseVersion: '1.0',
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
candidates: [
|
||||
{
|
||||
tagName: 'rc-1.0.1',
|
||||
sha: 'sha-1.0.1',
|
||||
},
|
||||
{
|
||||
tagName: 'rc-1.0.0',
|
||||
sha: 'sha-1.0.0',
|
||||
},
|
||||
],
|
||||
versions: [],
|
||||
},
|
||||
'1.1': {
|
||||
baseVersion: '1.1',
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
htmlUrl: 'html_url',
|
||||
candidates: [
|
||||
{
|
||||
tagName: 'rc-1.1.2',
|
||||
sha: 'sha-1.1.2',
|
||||
},
|
||||
{
|
||||
tagName: 'rc-1.1.1',
|
||||
sha: 'sha-1.1.1',
|
||||
},
|
||||
{
|
||||
tagName: 'rc-1.1.0',
|
||||
sha: 'sha-1.1.0',
|
||||
},
|
||||
],
|
||||
versions: [
|
||||
{
|
||||
tagName: 'version-1.1.3',
|
||||
sha: 'sha-1.1.3',
|
||||
},
|
||||
{
|
||||
tagName: 'version-1.1.2',
|
||||
sha: 'sha-1.1.2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
unmappableTags: [],
|
||||
unmatchedReleases: [],
|
||||
unmatchedTags: [],
|
||||
};
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as testHelpers from './test-helpers';
|
||||
|
||||
describe('testHelpers', () => {
|
||||
it('should export the correct things', () => {
|
||||
expect(testHelpers).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"mockApiClient": Object {
|
||||
"createRc": Object {
|
||||
"createRef": [MockFunction],
|
||||
"createRelease": [MockFunction],
|
||||
"getComparison": [MockFunction],
|
||||
},
|
||||
"getBranch": [MockFunction],
|
||||
"getHost": [MockFunction],
|
||||
"getLatestCommit": [MockFunction],
|
||||
"getLatestRelease": [MockFunction],
|
||||
"getOwners": [MockFunction],
|
||||
"getRecentCommits": [MockFunction],
|
||||
"getRepoPath": [MockFunction],
|
||||
"getRepositories": [MockFunction],
|
||||
"getRepository": [MockFunction],
|
||||
"getUsername": [MockFunction],
|
||||
"patch": Object {
|
||||
"createCherryPickCommit": [MockFunction],
|
||||
"createReference": [MockFunction],
|
||||
"createTagObject": [MockFunction],
|
||||
"createTempCommit": [MockFunction],
|
||||
"forceBranchHeadToTempCommit": [MockFunction],
|
||||
"merge": [MockFunction],
|
||||
"replaceTempCommit": [MockFunction],
|
||||
"updateRelease": [MockFunction],
|
||||
},
|
||||
"promoteRc": Object {
|
||||
"promoteRelease": [MockFunction],
|
||||
},
|
||||
"stats": Object {
|
||||
"getAllReleases": [MockFunction],
|
||||
"getAllTags": [MockFunction],
|
||||
"getCommit": [MockFunction],
|
||||
},
|
||||
},
|
||||
"mockBumpedTag": "rc-2020.01.01_1337",
|
||||
"mockCalverProject": Object {
|
||||
"isProvidedViaProps": false,
|
||||
"owner": "mock_owner",
|
||||
"repo": "mock_repo",
|
||||
"versioningStrategy": "calver",
|
||||
},
|
||||
"mockDefaultBranch": "mock_defaultBranch",
|
||||
"mockNextGitHubInfoCalver": Object {
|
||||
"rcBranch": "rc/2020.01.01_1",
|
||||
"rcReleaseTag": "rc-2020.01.01_1",
|
||||
"releaseName": "Version 2020.01.01_1",
|
||||
},
|
||||
"mockNextGitHubInfoSemver": Object {
|
||||
"rcBranch": "rc/1.2.3",
|
||||
"rcReleaseTag": "rc-1.2.3",
|
||||
"releaseName": "Version 1.2.3",
|
||||
},
|
||||
"mockReleaseBranch": Object {
|
||||
"commit": Object {
|
||||
"commit": Object {
|
||||
"tree": Object {
|
||||
"sha": "mock_branch_commit_commit_tree_sha",
|
||||
},
|
||||
},
|
||||
"sha": "mock_branch_commit_sha",
|
||||
},
|
||||
"links": Object {
|
||||
"html": "mock_branch_links_html",
|
||||
},
|
||||
"name": "rc/1.2.3",
|
||||
},
|
||||
"mockReleaseCandidateCalver": Object {
|
||||
"htmlUrl": "mock_release_html_url",
|
||||
"id": 1,
|
||||
"prerelease": true,
|
||||
"tagName": "rc-2020.01.01_1",
|
||||
"targetCommitish": "rc/2020.01.01_1",
|
||||
},
|
||||
"mockReleaseCandidateSemver": Object {
|
||||
"htmlUrl": "mock_release_html_url",
|
||||
"id": 1,
|
||||
"prerelease": true,
|
||||
"tagName": "rc-1.2.3",
|
||||
"targetCommitish": "rc/1.2.3",
|
||||
},
|
||||
"mockReleaseVersionCalver": Object {
|
||||
"htmlUrl": "mock_release_html_url",
|
||||
"id": 1,
|
||||
"prerelease": false,
|
||||
"tagName": "version-2020.01.01_1",
|
||||
"targetCommitish": "rc/2020.01.01_1",
|
||||
},
|
||||
"mockReleaseVersionSemver": Object {
|
||||
"htmlUrl": "mock_release_html_url",
|
||||
"id": 1,
|
||||
"prerelease": false,
|
||||
"tagName": "version-1.2.3",
|
||||
"targetCommitish": "rc/1.2.3",
|
||||
},
|
||||
"mockSearchCalver": "?versioningStrategy=calver&owner=mock_owner&repo=mock_repo",
|
||||
"mockSearchSemver": "?versioningStrategy=semver&owner=mock_owner&repo=mock_repo",
|
||||
"mockSelectedPatchCommit": Object {
|
||||
"author": Object {
|
||||
"htmlUrl": "author_html_url",
|
||||
"login": "author_login",
|
||||
},
|
||||
"commit": Object {
|
||||
"message": "commit_message",
|
||||
},
|
||||
"firstParentSha": "mock_first_parent_sha",
|
||||
"htmlUrl": "mock_htmlUrl",
|
||||
"sha": "mock_sha_selected_patch_commit",
|
||||
},
|
||||
"mockSemverProject": Object {
|
||||
"isProvidedViaProps": false,
|
||||
"owner": "mock_owner",
|
||||
"repo": "mock_repo",
|
||||
"versioningStrategy": "semver",
|
||||
},
|
||||
"mockTagParts": Object {
|
||||
"calver": "2020.01.01",
|
||||
"patch": 1,
|
||||
"prefix": "rc",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -280,16 +280,25 @@ export const mockApiClient: IPluginApiClient = {
|
||||
},
|
||||
|
||||
stats: {
|
||||
getAllTags: jest.fn(async () => {
|
||||
throw new Error('Not implemented');
|
||||
}),
|
||||
getAllTags: jest.fn(async () => [
|
||||
{
|
||||
tagName: MOCK_RELEASE_CANDIDATE_TAG_NAME_CALVER,
|
||||
sha: 'mock_sha',
|
||||
},
|
||||
]),
|
||||
|
||||
getAllReleases: jest.fn(async () => {
|
||||
throw new Error('Not implemented');
|
||||
}),
|
||||
getAllReleases: jest.fn(async () => [
|
||||
{
|
||||
id: 1,
|
||||
name: 'mock_release_name',
|
||||
tagName: 'mock_release_tag_name',
|
||||
createdAt: 'mock_release_published_at',
|
||||
htmlUrl: 'mock_release_html_url',
|
||||
},
|
||||
]),
|
||||
|
||||
getCommit: jest.fn(async () => {
|
||||
throw new Error('Not implemented');
|
||||
}),
|
||||
getCommit: jest.fn(async () => ({
|
||||
createdAt: '2021-01-01T10:11:12Z',
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as testIds from './test-ids';
|
||||
|
||||
describe('test-ids', () => {
|
||||
it('should export the correct things', () => {
|
||||
expect(testIds).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"TEST_IDS": Object {
|
||||
"components": Object {
|
||||
"circularProgress": "grm--circular-progress",
|
||||
"differ": Object {
|
||||
"current": "grm--differ-current",
|
||||
"icons": Object {
|
||||
"branch": "grm--differ--icons--branch",
|
||||
"github": "grm--differ--icons--github",
|
||||
"slack": "grm--differ--icons--slack",
|
||||
"tag": "grm--differ--icons--tag",
|
||||
"versioning": "grm--differ--icons--versioning",
|
||||
},
|
||||
"next": "grm--differ-next",
|
||||
},
|
||||
"divider": "grm--divider",
|
||||
"linearProgressWithLabel": "grm--linear-progress-with-label",
|
||||
"noLatestRelease": "grm--no-latest-release",
|
||||
"responseStepListDialogContent": "grm--response-step-list--dialog-content",
|
||||
"responseStepListItem": "grm--response-step-list-item",
|
||||
"responseStepListItemIconDefault": "grm--response-step-list-item--item-icon--default",
|
||||
"responseStepListItemIconFailure": "grm--response-step-list-item--item-icon--failure",
|
||||
"responseStepListItemIconLink": "grm--response-step-list-item--item-icon--link",
|
||||
"responseStepListItemIconSuccess": "grm--response-step-list-item--item-icon--success",
|
||||
},
|
||||
"createRc": Object {
|
||||
"cta": "grm--create-rc--cta",
|
||||
"semverSelect": "grm--create-rc--semver-select",
|
||||
},
|
||||
"form": Object {
|
||||
"owner": Object {
|
||||
"empty": "grm--form--owner--empty",
|
||||
"error": "grm--form--owner--error",
|
||||
"loading": "grm--form--owner--loading",
|
||||
"select": "grm--form--owner--select",
|
||||
},
|
||||
"repo": Object {
|
||||
"empty": "grm--form--repo--empty",
|
||||
"error": "grm--form--repo--error",
|
||||
"loading": "grm--form--repo--loading",
|
||||
"select": "grm--form--repo--select",
|
||||
},
|
||||
"versioningStrategy": Object {
|
||||
"radioGroup": "grm--form--versioning-strategy--radio-group",
|
||||
},
|
||||
},
|
||||
"info": Object {
|
||||
"info": "grm--info",
|
||||
"infoFeaturePlus": "grm--info-feature-plus",
|
||||
},
|
||||
"patch": Object {
|
||||
"body": "grm--patch-body",
|
||||
"error": "grm--patch-body--error",
|
||||
"loading": "grm--patch-body--loading",
|
||||
"notPrerelease": "grm--patch-body--not-prerelease--info",
|
||||
},
|
||||
"promoteRc": Object {
|
||||
"cta": "grm--promote-rc-body--cta",
|
||||
"mockedPromoteRcBody": "grm-mocked-promote-rc-body",
|
||||
"notRcWarning": "grm--promote-rc--not-rc-warning",
|
||||
"promoteRc": "grm--promote-rc",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user