From 10c95472459038a23b6c031acfd8b8cbcd26cb82 Mon Sep 17 00:00:00 2001 From: Erik Engervall Date: Fri, 23 Apr 2021 02:55:10 +0200 Subject: [PATCH] Create getCommit hook When opening row, calculate diff in days from creating RC to being done with the Release Version Signed-off-by: Erik Engervall --- .../src/api/PluginApiClient.test.ts | 1 + .../src/api/PluginApiClient.ts | 82 +++++--- .../src/features/Stats/DialogBody.tsx | 81 +------- .../src/features/Stats/Row.tsx | 186 +++++++++++++----- .../src/features/Stats/Summary.tsx | 89 +++++++++ .../src/features/Stats/Warn.tsx | 4 +- .../Stats/helpers/getDecimalNumber.tsx | 27 +++ .../Stats/{ => helpers}/getMappedReleases.tsx | 24 ++- .../Stats/{ => helpers}/getSummary.tsx | 0 .../features/Stats/{ => helpers}/getTags.tsx | 46 +++-- .../src/features/Stats/hooks/useGetCommit.ts | 37 ++++ .../src/test-helpers/test-helpers.test.ts | 1 + .../src/test-helpers/test-helpers.ts | 4 + 13 files changed, 414 insertions(+), 168 deletions(-) create mode 100644 plugins/github-release-manager/src/features/Stats/Summary.tsx create mode 100644 plugins/github-release-manager/src/features/Stats/helpers/getDecimalNumber.tsx rename plugins/github-release-manager/src/features/Stats/{ => helpers}/getMappedReleases.tsx (71%) rename plugins/github-release-manager/src/features/Stats/{ => helpers}/getSummary.tsx (100%) rename plugins/github-release-manager/src/features/Stats/{ => helpers}/getTags.tsx (58%) create mode 100644 plugins/github-release-manager/src/features/Stats/hooks/useGetCommit.ts diff --git a/plugins/github-release-manager/src/api/PluginApiClient.test.ts b/plugins/github-release-manager/src/api/PluginApiClient.test.ts index 998e8bc4e8..867974b80b 100644 --- a/plugins/github-release-manager/src/api/PluginApiClient.test.ts +++ b/plugins/github-release-manager/src/api/PluginApiClient.test.ts @@ -60,6 +60,7 @@ describe('PluginApiClient', () => { "stats": Object { "getAllReleases": [Function], "getAllTags": [Function], + "getCommit": [Function], }, } `); diff --git a/plugins/github-release-manager/src/api/PluginApiClient.ts b/plugins/github-release-manager/src/api/PluginApiClient.ts index 31996f8882..033e074bcf 100644 --- a/plugins/github-release-manager/src/api/PluginApiClient.ts +++ b/plugins/github-release-manager/src/api/PluginApiClient.ts @@ -576,6 +576,7 @@ ${selectedPatchCommit.commit.message}`, return tags.map(tag => ({ tagName: tag.name, + sha: tag.commit.sha, })); }, @@ -598,6 +599,20 @@ ${selectedPatchCommit.commit.message}`, htmlUrl: release.html_url, })); }, + + getCommit: async ({ owner, repo, ref }: { ref: string } & OwnerRepo) => { + const { octokit } = await this.getOctokit(); + + const { data: commit } = await octokit.repos.getCommit({ + owner, + repo, + ref, + }); + + return { + createdAt: commit.commit.committer?.date, + }; + }, }; } @@ -660,28 +675,6 @@ type GetRecentCommits = ( export type GetRecentCommitsResult = UnboxReturnedPromise; export type GetRecentCommitsResultSingle = UnboxArray; -type GetAllTags = ( - args: OwnerRepo, -) => Promise< - Array<{ - tagName: string; - }> ->; -export type GetAllTagsResult = UnboxReturnedPromise; - -type GetAllReleases = ( - args: OwnerRepo, -) => Promise< - Array<{ - id: number; - name: string | null; - tagName: string; - createdAt: string | null; - htmlUrl: string; - }> ->; -export type GetAllReleasesResult = UnboxReturnedPromise; - type GetLatestRelease = ( args: OwnerRepo, ) => Promise<{ @@ -736,6 +729,9 @@ type GetBranch = ( }>; export type GetBranchResult = UnboxReturnedPromise; +/** + * CreateRc + */ type CreateRef = ( args: { mostRecentSha: string; @@ -771,6 +767,9 @@ type CreateRelease = ( }>; export type CreateReleaseResult = UnboxReturnedPromise; +/** + * Patch + */ type CreateTempCommit = ( args: { tagParts: SemverTagParts | CalverTagParts; @@ -876,6 +875,9 @@ type UpdateRelease = ( }>; export type UpdateReleaseResult = UnboxReturnedPromise; +/** + * PromoteRc + */ type PromoteRelease = ( args: { releaseId: NonNullable['id']; @@ -888,6 +890,41 @@ type PromoteRelease = ( }>; export type PromoteReleaseResult = UnboxReturnedPromise; +/** + * Stats + */ +type GetAllTags = ( + args: OwnerRepo, +) => Promise< + Array<{ + tagName: string; + sha: string; + }> +>; +export type GetAllTagsResult = UnboxReturnedPromise; + +type GetAllReleases = ( + args: OwnerRepo, +) => Promise< + Array<{ + id: number; + name: string | null; + tagName: string; + createdAt: string | null; + htmlUrl: string; + }> +>; +export type GetAllReleasesResult = UnboxReturnedPromise; + +type GetCommit = ( + args: { + ref: string; + } & OwnerRepo, +) => Promise<{ + createdAt: string | undefined; +}>; +export type GetCommitResult = UnboxReturnedPromise; + export interface IPluginApiClient { getHost: GetHost; getRepoPath: GetRepoPath; @@ -920,5 +957,6 @@ export interface IPluginApiClient { stats: { getAllTags: GetAllTags; getAllReleases: GetAllReleases; + getCommit: GetCommit; }; } diff --git a/plugins/github-release-manager/src/features/Stats/DialogBody.tsx b/plugins/github-release-manager/src/features/Stats/DialogBody.tsx index 3c2f884417..d8b406e04b 100644 --- a/plugins/github-release-manager/src/features/Stats/DialogBody.tsx +++ b/plugins/github-release-manager/src/features/Stats/DialogBody.tsx @@ -26,17 +26,17 @@ import { TableContainer, TableHead, TableRow, - Typography, } from '@material-ui/core'; import { CenteredCircularProgress } from '../../components/CenteredCircularProgress'; -import { getMappedReleases } from './getMappedReleases'; -import { getSummary } from './getSummary'; -import { getTags } from './getTags'; +import { getMappedReleases } from './helpers/getMappedReleases'; +import { getSummary } from './helpers/getSummary'; +import { getTags } from './helpers/getTags'; import { Row } from './Row'; import { useGetStats } from './hooks/useGetStats'; import { useProjectContext } from '../../contexts/ProjectContext'; import { Warn } from './Warn'; +import { Summary } from './Summary'; const useStyles = makeStyles({ table: { @@ -81,80 +81,9 @@ export function DialogBody() { }); } - const getDecimalNumber = (n: number) => { - if (isNaN(n)) { - return 0; - } - - if (n.toString().includes('.')) { - return n.toFixed(2); - } - - return n; - }; - return ( <> - - - Summary - - Total releases: {summary.totalReleases} - - - - - Release Candidate - - Release Candidate patches: {summary.totalCandidatePatches} - - - - Release Candidate patches per release:{' '} - {getDecimalNumber( - summary.totalCandidatePatches / summary.totalReleases, - )} - - - - - Release Version - - Release Version patches: {summary.totalVersionPatches} - - - - Release Version patches per release:{' '} - {getDecimalNumber( - summary.totalVersionPatches / summary.totalReleases, - )} - - - - - Total - - Patches:{' '} - {summary.totalCandidatePatches + summary.totalVersionPatches} - - - - Patches per release:{' '} - {getDecimalNumber( - (summary.totalCandidatePatches + summary.totalVersionPatches) / - summary.totalReleases, - )} - - - + diff --git a/plugins/github-release-manager/src/features/Stats/Row.tsx b/plugins/github-release-manager/src/features/Stats/Row.tsx index ea95f8e5c2..53ad5751da 100644 --- a/plugins/github-release-manager/src/features/Stats/Row.tsx +++ b/plugins/github-release-manager/src/features/Stats/Row.tsx @@ -15,7 +15,7 @@ */ import React, { useState } from 'react'; -import { DateTime } from 'luxon'; +import { DateTime, DurationObject } from 'luxon'; import { Box, Collapse, @@ -29,7 +29,9 @@ import { import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; -import { getMappedReleases } from './getMappedReleases'; +import { getMappedReleases } from './helpers/getMappedReleases'; +import { useGetCommit } from './hooks/useGetCommit'; +import { CenteredCircularProgress } from '../../components/CenteredCircularProgress'; const useRowStyles = makeStyles({ root: { @@ -47,9 +49,6 @@ interface RowProps { export function Row({ baseVersion, mappedRelease }: RowProps) { const [open, setOpen] = useState(false); const classes = useRowStyles(); - const versions = mappedRelease.versions.reverse(); - const candidates = mappedRelease.candidates.reverse(); - const isPrerelease = versions.length === 0; return ( @@ -67,7 +66,7 @@ export function Row({ baseVersion, mappedRelease }: RowProps) { {baseVersion} - {isPrerelease ? ' (prerelease)' : ''} + {mappedRelease.versions.length === 0 ? ' (prerelease)' : ''} @@ -79,53 +78,152 @@ export function Row({ baseVersion, mappedRelease }: RowProps) { : '-'} - {candidates.length} + {mappedRelease.candidates.length} - {Math.max(0, versions.length - 1)} + {Math.max(0, mappedRelease.versions.length - 1)} - -
- {!isPrerelease && ( - - {versions.map(version => ( - - {version} - - ))} - - )} - - {!isPrerelease && ( - - {' 🚀 '} - - )} - - - {candidates.map(candidate => ( - - {candidate} - - ))} - -
-
+
); } + +function CollapsedEl({ + mappedRelease, +}: { + mappedRelease: ReturnType['releases']['0']; +}) { + const reversedCandidates = [...mappedRelease.candidates].reverse(); + + const { commit: releaseCut } = useGetCommit({ + ref: reversedCandidates[0]?.sha, + }); + const { commit: releaseComplete } = useGetCommit({ + ref: mappedRelease.versions[0]?.sha, + }); + + console.log('*** releaseCut > ', releaseCut.value); // eslint-disable-line no-console + console.log('*** releaseComplete > ', releaseComplete.value); // eslint-disable-line no-console + + let diff = { days: -1 } as DurationObject; + if (releaseCut.value?.createdAt && releaseComplete.value?.createdAt) { + diff = DateTime.fromISO(releaseComplete.value.createdAt) + .diff(DateTime.fromISO(releaseCut.value.createdAt), ['days', 'hours']) + .toObject(); + } + + return ( + + + {mappedRelease.versions.length > 0 && ( + + {mappedRelease.versions.map(version => ( + + {version.tagName} + + ))} + + )} + + {mappedRelease.versions.length > 0 && ( + + {' 🚀 '} + + )} + + + {mappedRelease.candidates.map(candidate => ( + + {candidate.tagName} + + ))} + + + + + {releaseComplete.loading ? ( + + ) : ( + <> + + Release completed{' '} + {releaseComplete.value?.createdAt && + DateTime.fromISO(releaseComplete.value.createdAt) + .setLocale('sv-SE') + .toFormat('yyyy-MM-dd')} + + + + + Release time: {diff.days} days + + + + + RC created{' '} + {releaseCut.value?.createdAt && + DateTime.fromISO(releaseCut.value.createdAt) + .setLocale('sv-SE') + .toFormat('yyyy-MM-dd')} + + + )} + + + ); +} diff --git a/plugins/github-release-manager/src/features/Stats/Summary.tsx b/plugins/github-release-manager/src/features/Stats/Summary.tsx new file mode 100644 index 0000000000..d5cedaeaae --- /dev/null +++ b/plugins/github-release-manager/src/features/Stats/Summary.tsx @@ -0,0 +1,89 @@ +/* + * 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; +} + +export function Summary({ summary }: SummaryProps) { + return ( + + + Summary + + Total releases: {summary.totalReleases} + + + + + Release Candidate + + Release Candidate patches: {summary.totalCandidatePatches} + + + + Release Candidate patches per release:{' '} + {getDecimalNumber( + summary.totalCandidatePatches / summary.totalReleases, + )} + + + + + Release Version + + Release Version patches: {summary.totalVersionPatches} + + + + Release Version patches per release:{' '} + {getDecimalNumber( + summary.totalVersionPatches / summary.totalReleases, + )} + + + + + Total + + Patches: {summary.totalCandidatePatches + summary.totalVersionPatches} + + + + Patches per release:{' '} + {getDecimalNumber( + (summary.totalCandidatePatches + summary.totalVersionPatches) / + summary.totalReleases, + )} + + + + ); +} diff --git a/plugins/github-release-manager/src/features/Stats/Warn.tsx b/plugins/github-release-manager/src/features/Stats/Warn.tsx index ecae90426d..b3cb813dfb 100644 --- a/plugins/github-release-manager/src/features/Stats/Warn.tsx +++ b/plugins/github-release-manager/src/features/Stats/Warn.tsx @@ -17,8 +17,8 @@ import React from 'react'; import { Alert } from '@material-ui/lab'; -import { getMappedReleases } from './getMappedReleases'; -import { getTags } from './getTags'; +import { getMappedReleases } from './helpers/getMappedReleases'; +import { getTags } from './helpers/getTags'; import { Project } from '../../contexts/ProjectContext'; interface WarnProps { diff --git a/plugins/github-release-manager/src/features/Stats/helpers/getDecimalNumber.tsx b/plugins/github-release-manager/src/features/Stats/helpers/getDecimalNumber.tsx new file mode 100644 index 0000000000..590d95a757 --- /dev/null +++ b/plugins/github-release-manager/src/features/Stats/helpers/getDecimalNumber.tsx @@ -0,0 +1,27 @@ +/* + * 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. + */ + +export function getDecimalNumber(n: number) { + if (isNaN(n)) { + return 0; + } + + if (n.toString().includes('.')) { + return n.toFixed(2); + } + + return n; +} diff --git a/plugins/github-release-manager/src/features/Stats/getMappedReleases.tsx b/plugins/github-release-manager/src/features/Stats/helpers/getMappedReleases.tsx similarity index 71% rename from plugins/github-release-manager/src/features/Stats/getMappedReleases.tsx rename to plugins/github-release-manager/src/features/Stats/helpers/getMappedReleases.tsx index 52a6204589..af573343e5 100644 --- a/plugins/github-release-manager/src/features/Stats/getMappedReleases.tsx +++ b/plugins/github-release-manager/src/features/Stats/helpers/getMappedReleases.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import { calverRegexp } from '../../helpers/tagParts/getCalverTagParts'; -import { GetAllReleasesResult } from '../../api/PluginApiClient'; -import { Project } from '../../contexts/ProjectContext'; -import { semverRegexp } from '../../helpers/tagParts/getSemverTagParts'; +import { calverRegexp } from '../../../helpers/tagParts/getCalverTagParts'; +import { GetAllReleasesResult } from '../../../api/PluginApiClient'; +import { Project } from '../../../contexts/ProjectContext'; +import { semverRegexp } from '../../../helpers/tagParts/getSemverTagParts'; export function getMappedReleases({ allReleases, @@ -33,8 +33,14 @@ export function getMappedReleases({ releases: { [baseVersion: string]: { createdAt: string | null; - candidates: string[]; - versions: string[]; + candidates: { + tagName: string; + sha: string; + }[]; + versions: { + tagName: string; + sha: string; + }[]; htmlUrl: string; }; }; @@ -60,8 +66,10 @@ export function getMappedReleases({ if (!acc.releases[baseVersion]) { acc.releases[baseVersion] = { createdAt: release.createdAt, - candidates: prefix === 'rc' ? [release.tagName] : [], - versions: prefix === 'version' ? [release.tagName] : [], + candidates: + prefix === 'rc' ? [{ tagName: release.tagName, sha: '' }] : [], + versions: + prefix === 'version' ? [{ tagName: release.tagName, sha: '' }] : [], htmlUrl: release.htmlUrl, }; return acc; diff --git a/plugins/github-release-manager/src/features/Stats/getSummary.tsx b/plugins/github-release-manager/src/features/Stats/helpers/getSummary.tsx similarity index 100% rename from plugins/github-release-manager/src/features/Stats/getSummary.tsx rename to plugins/github-release-manager/src/features/Stats/helpers/getSummary.tsx diff --git a/plugins/github-release-manager/src/features/Stats/getTags.tsx b/plugins/github-release-manager/src/features/Stats/helpers/getTags.tsx similarity index 58% rename from plugins/github-release-manager/src/features/Stats/getTags.tsx rename to plugins/github-release-manager/src/features/Stats/helpers/getTags.tsx index 4ad38cec17..82d0a6b587 100644 --- a/plugins/github-release-manager/src/features/Stats/getTags.tsx +++ b/plugins/github-release-manager/src/features/Stats/helpers/getTags.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import { calverRegexp } from '../../helpers/tagParts/getCalverTagParts'; -import { GetAllTagsResult } from '../../api/PluginApiClient'; +import { calverRegexp } from '../../../helpers/tagParts/getCalverTagParts'; +import { GetAllTagsResult } from '../../../api/PluginApiClient'; import { getMappedReleases } from './getMappedReleases'; -import { Project } from '../../contexts/ProjectContext'; -import { semverRegexp } from '../../helpers/tagParts/getSemverTagParts'; +import { Project } from '../../../contexts/ProjectContext'; +import { semverRegexp } from '../../../helpers/tagParts/getSemverTagParts'; export function getTags({ allTags, @@ -52,20 +52,34 @@ export function getTags({ return acc; } - if ( - prefix === 'rc' && - !mappedReleases.releases[baseVersion].candidates.includes(tag.tagName) - ) { - mappedReleases.releases[baseVersion].candidates.push(tag.tagName); - return acc; + if (prefix === 'rc') { + const existingEntry = mappedReleases.releases[ + baseVersion + ].candidates.find(({ tagName }) => tagName === tag.tagName); + + if (existingEntry) { + existingEntry.sha = tag.sha; + } else { + mappedReleases.releases[baseVersion].candidates.push({ + tagName: tag.tagName, + sha: tag.sha, + }); + } } - if ( - prefix === 'version' && - !mappedReleases.releases[baseVersion].versions.includes(tag.tagName) - ) { - mappedReleases.releases[baseVersion].versions.push(tag.tagName); - return acc; + if (prefix === 'version') { + const existingEntry = mappedReleases.releases[ + baseVersion + ].versions.find(({ tagName }) => tagName === tag.tagName); + + if (existingEntry) { + existingEntry.sha = tag.sha; + } else { + mappedReleases.releases[baseVersion].versions.push({ + tagName: tag.tagName, + sha: tag.sha, + }); + } } return acc; diff --git a/plugins/github-release-manager/src/features/Stats/hooks/useGetCommit.ts b/plugins/github-release-manager/src/features/Stats/hooks/useGetCommit.ts new file mode 100644 index 0000000000..d19aa30331 --- /dev/null +++ b/plugins/github-release-manager/src/features/Stats/hooks/useGetCommit.ts @@ -0,0 +1,37 @@ +/* + * 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 { useAsync } from 'react-use'; + +import { usePluginApiClientContext } from '../../../contexts/PluginApiClientContext'; +import { useProjectContext } from '../../../contexts/ProjectContext'; + +export const useGetCommit = ({ ref }: { ref: string }) => { + const { pluginApiClient } = usePluginApiClientContext(); + const { project } = useProjectContext(); + + const commit = useAsync(() => + pluginApiClient.stats.getCommit({ + owner: project.owner, + repo: project.repo, + ref, + }), + ); + + return { + commit, + }; +}; diff --git a/plugins/github-release-manager/src/test-helpers/test-helpers.test.ts b/plugins/github-release-manager/src/test-helpers/test-helpers.test.ts index fbd3b690b4..89553b3e24 100644 --- a/plugins/github-release-manager/src/test-helpers/test-helpers.test.ts +++ b/plugins/github-release-manager/src/test-helpers/test-helpers.test.ts @@ -52,6 +52,7 @@ describe('testHelpers', () => { "stats": Object { "getAllReleases": [MockFunction], "getAllTags": [MockFunction], + "getCommit": [MockFunction], }, }, "mockBumpedTag": "rc-2020.01.01_1337", diff --git a/plugins/github-release-manager/src/test-helpers/test-helpers.ts b/plugins/github-release-manager/src/test-helpers/test-helpers.ts index fc7a5f5c5b..629bdb4031 100644 --- a/plugins/github-release-manager/src/test-helpers/test-helpers.ts +++ b/plugins/github-release-manager/src/test-helpers/test-helpers.ts @@ -287,5 +287,9 @@ export const mockApiClient: IPluginApiClient = { getAllReleases: jest.fn(async () => { throw new Error('Not implemented'); }), + + getCommit: jest.fn(async () => { + throw new Error('Not implemented'); + }), }, };