Continue flattening API

Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
Erik Engervall
2021-04-29 15:23:29 +02:00
parent cfe5a53da4
commit 8d0e7f0adb
9 changed files with 200 additions and 83 deletions
@@ -33,10 +33,11 @@ describe('GitReleaseApiClient', () => {
GitReleaseApiClient {
"baseUrl": "https://api.github.com",
"createRc": Object {
"createRef": [Function],
"createRelease": [Function],
"getComparison": [Function],
},
"createRef": [Function],
"createTagObject": [Function],
"getBranch": [Function],
"getHost": [Function],
"getLatestCommit": [Function],
@@ -54,7 +55,6 @@ describe('GitReleaseApiClient', () => {
"patch": Object {
"createCherryPickCommit": [Function],
"createReference": [Function],
"createTagObject": [Function],
"createTempCommit": [Function],
"forceBranchHeadToTempCommit": [Function],
"merge": [Function],
@@ -247,21 +247,22 @@ export class GitReleaseApiClient implements GitReleaseApi {
};
};
createRef: GitReleaseApi['createRef'] = async ({ owner, repo, sha, ref }) => {
const { octokit } = await this.getOctokit();
const createRefResponse = await octokit.git.createRef({
owner,
repo,
ref,
sha,
});
return {
ref: createRefResponse.data.ref,
objectSha: createRefResponse.data.object.sha,
};
};
createRc: GitReleaseApi['createRc'] = {
createRef: async ({ owner, repo, mostRecentSha, targetBranch }) => {
const { octokit } = await this.getOctokit();
const createRefResponse = await octokit.git.createRef({
owner,
repo,
ref: `refs/heads/${targetBranch}`,
sha: mostRecentSha,
});
return {
ref: createRefResponse.data.ref,
};
},
getComparison: async ({ owner, repo, base, head }) => {
const { octokit } = await this.getOctokit();
const compareCommitsResponse = await octokit.repos.compareCommits({
@@ -304,6 +305,36 @@ export class GitReleaseApiClient implements GitReleaseApi {
},
};
createTagObject: GitReleaseApi['createTagObject'] = async ({
owner,
repo,
tag,
objectSha,
taggerName,
taggerEmail,
message,
}) => {
const { octokit } = await this.getOctokit();
const { data: createdTagObject } = await octokit.git.createTag({
owner,
repo,
message,
tag,
object: objectSha,
type: 'commit',
tagger: {
date: new Date().toISOString(),
email: taggerEmail,
name: taggerName,
},
});
return {
tagName: createdTagObject.tag,
tagSha: createdTagObject.sha,
};
};
patch: GitReleaseApi['patch'] = {
createTempCommit: async ({
owner,
@@ -411,31 +442,13 @@ ${selectedPatchCommit.sha}`,
};
},
createTagObject: async ({ owner, repo, tag, objectSha }) => {
const { octokit } = await this.getOctokit();
const { data: createdTagObject } = await octokit.git.createTag({
owner,
repo,
message:
'Tag generated by your friendly neighborhood Backstage Release Manager',
tag,
object: objectSha,
type: 'commit',
});
return {
tag: createdTagObject.tag,
sha: createdTagObject.sha,
};
},
createReference: async ({ owner, repo, bumpedTag, createdTagObject }) => {
const { octokit } = await this.getOctokit();
const { data: reference } = await octokit.git.createRef({
owner,
repo,
ref: `refs/tags/${bumpedTag}`,
sha: createdTagObject.sha,
sha: createdTagObject.tagSha,
});
return {
@@ -652,16 +665,17 @@ export interface GitReleaseApi {
};
}>;
createRc: {
createRef: (
args: {
mostRecentSha: string;
targetBranch: string;
} & OwnerRepo,
) => Promise<{
createRef: (
args: {
ref: string;
}>;
sha: string;
} & OwnerRepo,
) => Promise<{
ref: string;
objectSha: string;
}>;
createRc: {
getComparison: (
args: {
base: string;
@@ -685,6 +699,20 @@ export interface GitReleaseApi {
tagName: string;
}>;
};
createTagObject: (
args: {
tag: string;
taggerEmail?: string;
message: string;
objectSha: string;
taggerName: string;
} & OwnerRepo,
) => Promise<{
tagName: string;
tagSha: string;
}>;
patch: {
createTempCommit: (
args: {
@@ -749,16 +777,6 @@ export interface GitReleaseApi {
};
}>;
createTagObject: (
args: {
tag: string;
objectSha: string;
} & OwnerRepo,
) => Promise<{
tag: string;
sha: string;
}>;
createReference: (
args: {
bumpedTag: string;
@@ -854,9 +872,7 @@ export type GetLatestCommitResult = UnboxReturnedPromise<
GitReleaseApi['getLatestCommit']
>;
export type GetBranchResult = UnboxReturnedPromise<GitReleaseApi['getBranch']>;
export type CreateRefResult = UnboxReturnedPromise<
GitReleaseApi['createRc']['createRef']
>;
export type CreateRefResult = UnboxReturnedPromise<GitReleaseApi['createRef']>;
export type GetComparisonResult = UnboxReturnedPromise<
GitReleaseApi['createRc']['getComparison']
>;
@@ -877,7 +893,7 @@ export type ReplaceTempCommitResult = UnboxReturnedPromise<
GitReleaseApi['patch']['replaceTempCommit']
>;
export type CreateTagObjectResult = UnboxReturnedPromise<
GitReleaseApi['patch']['createTagObject']
GitReleaseApi['createTagObject']
>;
export type CreateReferenceResult = UnboxReturnedPromise<
GitReleaseApi['patch']['createReference']
@@ -897,3 +913,6 @@ export type GetCommitResult = UnboxReturnedPromise<
export type GetAllReleasesResult = UnboxReturnedPromise<
GitReleaseApi['stats']['getAllReleases']
>;
export type GetSingleTagResult = UnboxReturnedPromise<
GitReleaseApi['stats']['getSingleTag']
>;
@@ -30,6 +30,7 @@ describe('constants', () => {
"minor": "minor",
"patch": "patch",
},
"TAG_OBJECT_MESSAGE": "Tag generated by your friendly neighborhood Backstage Release Manager",
"VERSIONING_STRATEGIES": Object {
"calver": "calver",
"semver": "semver",
@@ -37,3 +37,6 @@ export const VERSIONING_STRATEGIES: {
semver: 'semver',
calver: 'calver',
} as const;
export const TAG_OBJECT_MESSAGE =
'Tag generated by your friendly neighborhood Backstage Release Manager';
@@ -23,6 +23,7 @@ import {
mockDefaultBranch,
mockNextGitInfoCalver,
mockReleaseVersionCalver,
mockUser,
} from '../../../test-helpers/test-helpers';
import { useCreateReleaseCandidate } from './useCreateReleaseCandidate';
@@ -30,6 +31,9 @@ jest.mock('@backstage/core', () => ({
useApi: () => mockApiClient,
createApiRef: jest.fn(),
}));
jest.mock('../../../contexts/UserContext', () => ({
useUserContext: () => ({ user: mockUser }),
}));
describe('useCreateReleaseCandidate', () => {
beforeEach(jest.clearAllMocks);
@@ -49,7 +53,7 @@ describe('useCreateReleaseCandidate', () => {
});
expect(result.error).toEqual(undefined);
expect(result.current.responseSteps).toHaveLength(4);
expect(result.current.responseSteps).toHaveLength(6);
});
it('should return the expected responseSteps and progress (with successCb)', async () => {
@@ -67,7 +71,7 @@ describe('useCreateReleaseCandidate', () => {
await waitFor(() => result.current.run());
});
expect(result.current.responseSteps).toHaveLength(5);
expect(result.current.responseSteps).toHaveLength(7);
expect(result.current).toMatchInlineSnapshot(`
Object {
"progress": 100,
@@ -78,7 +82,15 @@ describe('useCreateReleaseCandidate', () => {
"secondaryMessage": "with message \\"latestCommit.commit.message\\"",
},
Object {
"message": "Cut Release Branch",
"message": "Created Release Branch",
"secondaryMessage": "with ref \\"mock_createRef_ref\\"",
},
Object {
"message": "Created Tag Object",
"secondaryMessage": "with sha \\"mock_tag_object_sha\\"",
},
Object {
"message": "Cut Tag Reference",
"secondaryMessage": "with ref \\"mock_createRef_ref\\"",
},
Object {
@@ -27,7 +27,9 @@ import { getReleaseCandidateGitInfo } from '../../../helpers/getReleaseCandidate
import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef';
import { GitReleaseManagerError } from '../../../errors/GitReleaseManagerError';
import { Project } from '../../../contexts/ProjectContext';
import { TAG_OBJECT_MESSAGE } from '../../../constants/constants';
import { useResponseSteps } from '../../../hooks/useResponseSteps';
import { useUserContext } from '../../../contexts/UserContext';
interface UseCreateReleaseCandidate {
defaultBranch: GetRepositoryResult['defaultBranch'];
@@ -45,6 +47,7 @@ export function useCreateReleaseCandidate({
successCb,
}: UseCreateReleaseCandidate): CardHook<void> {
const pluginApiClient = useApi(gitReleaseManagerApiRef);
const { user } = useUserContext();
if (releaseCandidateGitInfo.error) {
throw new GitReleaseManagerError(
@@ -87,18 +90,18 @@ export function useCreateReleaseCandidate({
});
/**
* (2) Create a new ref based on the default branch's most recent sha
* (2) Create release branch based on default branch's most recent sha
*/
const createRcRes = useAsync(async () => {
const releaseBranchRes = useAsync(async () => {
abortIfError(latestCommitRes.error);
if (!latestCommitRes.value) return undefined;
const createdRef = await pluginApiClient.createRc
const createdReleaseBranch = await pluginApiClient
.createRef({
owner: project.owner,
repo: project.repo,
mostRecentSha: latestCommitRes.value.latestCommit.sha,
targetBranch: releaseCandidateGitInfo.rcBranch,
sha: latestCommitRes.value.latestCommit.sha,
ref: `refs/heads/${releaseCandidateGitInfo.rcBranch}`,
})
.catch(error => {
if (error?.body?.message === 'Reference already exists') {
@@ -111,17 +114,80 @@ export function useCreateReleaseCandidate({
.catch(asyncCatcher);
addStepToResponseSteps({
message: 'Cut Release Branch',
message: 'Created Release Branch',
secondaryMessage: `with ref "${createdReleaseBranch.ref}"`,
});
return {
...createdReleaseBranch,
};
}, [latestCommitRes.value, latestCommitRes.error]);
/**
* (3) Create tag object for our soon-to-be-created annotated tag
*/
const tagObjectRes = useAsync(async () => {
abortIfError(releaseBranchRes.error);
if (!releaseBranchRes.value) return undefined;
const createdTagObject = await pluginApiClient
.createTagObject({
owner: project.owner,
repo: project.repo,
tag: releaseCandidateGitInfo.rcReleaseTag,
objectSha: releaseBranchRes.value.objectSha,
taggerName: user.username,
taggerEmail: user.email,
message: TAG_OBJECT_MESSAGE,
})
.catch(asyncCatcher);
addStepToResponseSteps({
message: 'Created Tag Object',
secondaryMessage: `with sha "${createdTagObject.tagSha}"`,
});
return {
...createdTagObject,
};
}, [releaseBranchRes.value, releaseBranchRes.error]);
/**
* (4) Create reference for tag object
*/
const createRcRes = useAsync(async () => {
abortIfError(tagObjectRes.error);
if (!tagObjectRes.value) return undefined;
const createdRef = await pluginApiClient
.createRef({
owner: project.owner,
repo: project.repo,
ref: `refs/tags/${releaseCandidateGitInfo.rcReleaseTag}`,
sha: tagObjectRes.value.tagSha,
})
.catch(error => {
if (error?.body?.message === 'Reference already exists') {
throw new GitReleaseManagerError(
`Tag reference "${releaseCandidateGitInfo.rcReleaseTag}" already exists`,
);
}
throw error;
})
.catch(asyncCatcher);
addStepToResponseSteps({
message: 'Cut Tag Reference',
secondaryMessage: `with ref "${createdRef.ref}"`,
});
return {
...createdRef,
};
}, [latestCommitRes.value, latestCommitRes.error]);
}, [tagObjectRes.value, tagObjectRes.error]);
/**
* (3) Compose a body for the release
* (5) Compose a body for the release
*/
const getComparisonRes = useAsync(async () => {
abortIfError(createRcRes.error);
@@ -163,7 +229,7 @@ export function useCreateReleaseCandidate({
}, [createRcRes.value, createRcRes.error]);
/**
* (4) Creates the Git Release itself
* (6) Creates the Git Release itself
*/
const createReleaseRes = useAsync(async () => {
abortIfError(getComparisonRes.error);
@@ -192,7 +258,7 @@ export function useCreateReleaseCandidate({
}, [getComparisonRes.value, getComparisonRes.error]);
/**
* (5) Run successCb if defined
* (7) Run successCb if defined
*/
useAsync(async () => {
if (successCb && !!createReleaseRes.value && !!getComparisonRes.value) {
@@ -217,7 +283,7 @@ export function useCreateReleaseCandidate({
}
}, [createReleaseRes.value]);
const TOTAL_STEPS = 4 + (!!successCb ? 1 : 0);
const TOTAL_STEPS = 6 + (!!successCb ? 1 : 0);
const [progress, setProgress] = useState(0);
useEffect(() => {
setProgress((responseSteps.length / TOTAL_STEPS) * 100);
@@ -24,6 +24,7 @@ import {
mockReleaseVersionCalver,
mockSelectedPatchCommit,
mockTagParts,
mockUser,
} from '../../../test-helpers/test-helpers';
import { usePatch } from './usePatch';
@@ -31,6 +32,9 @@ jest.mock('@backstage/core', () => ({
useApi: () => mockApiClient,
createApiRef: jest.fn(),
}));
jest.mock('../../../contexts/UserContext', () => ({
useUserContext: () => ({ user: mockUser }),
}));
describe('patch', () => {
beforeEach(jest.clearAllMocks);
@@ -28,6 +28,8 @@ import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef';
import { Project } from '../../../contexts/ProjectContext';
import { SemverTagParts } from '../../../helpers/tagParts/getSemverTagParts';
import { useResponseSteps } from '../../../hooks/useResponseSteps';
import { TAG_OBJECT_MESSAGE } from '../../../constants/constants';
import { useUserContext } from '../../../contexts/UserContext';
interface Patch {
bumpedTag: string;
@@ -46,6 +48,7 @@ export function usePatch({
successCb,
}: Patch): CardHook<GetRecentCommitsResultSingle> {
const pluginApiClient = useApi(gitReleaseManagerApiRef);
const { user } = useUserContext();
const {
responseSteps,
addStepToResponseSteps,
@@ -234,18 +237,21 @@ export function usePatch({
abortIfError(updatedRefRes.error);
if (!updatedRefRes.value) return undefined;
const createdTagObject = await pluginApiClient.patch
const createdTagObject = await pluginApiClient
.createTagObject({
owner: project.owner,
repo: project.repo,
tag: bumpedTag,
objectSha: updatedRefRes.value.object.sha,
message: TAG_OBJECT_MESSAGE,
taggerName: user.username,
taggerEmail: user.email,
})
.catch(asyncCatcher);
addStepToResponseSteps({
message: 'Created new tag object',
secondaryMessage: `with name "${createdTagObject.tag}"`,
secondaryMessage: `with name "${createdTagObject.tagName}"`,
});
return {
@@ -272,7 +278,7 @@ export function usePatch({
addStepToResponseSteps({
message: `Created new reference "${reference.ref}"`,
secondaryMessage: `for tag object "${createdTagObjRes.value.tag}"`,
secondaryMessage: `for tag object "${createdTagObjRes.value.tagName}"`,
});
return {
@@ -40,6 +40,11 @@ const MOCK_RELEASE_BRANCH_NAME_SEMVER = `rc/${A_SEMVER_VERSION}`;
const MOCK_RELEASE_CANDIDATE_TAG_NAME_SEMVER = `rc-${A_SEMVER_VERSION}`;
const MOCK_RELEASE_VERSION_TAG_NAME_SEMVER = `version-${A_SEMVER_VERSION}`;
export const mockUser = {
username: mockOwner,
email: mockEmail,
};
export const mockSemverProject: Project = {
owner: mockOwner,
repo: mockRepo,
@@ -214,11 +219,12 @@ export const mockApiClient: GitReleaseApi = {
getBranch: jest.fn(async () => createMockBranch()),
createRc: {
createRef: jest.fn(async () => ({
ref: 'mock_createRef_ref',
})),
createRef: jest.fn(async () => ({
ref: 'mock_createRef_ref',
objectSha: 'mock_createRef_objectSha',
})),
createRc: {
createRelease: jest.fn(async () => ({
name: 'mock_createRelease_name',
htmlUrl: 'mock_createRelease_html_url',
@@ -231,6 +237,11 @@ export const mockApiClient: GitReleaseApi = {
})),
},
createTagObject: jest.fn(async () => ({
tagName: 'mock_tag_object_tag',
tagSha: 'mock_tag_object_sha',
})),
patch: {
createCherryPickCommit: jest.fn(async () => ({
message: 'mock_cherrypick_message',
@@ -241,11 +252,6 @@ export const mockApiClient: GitReleaseApi = {
ref: 'mock_reference_ref',
})),
createTagObject: jest.fn(async () => ({
tag: 'mock_tag_object_tag',
sha: 'mock_tag_object_sha',
})),
createTempCommit: jest.fn(async () => ({
message: 'mock_commit_message',
sha: 'mock_commit_sha',