Continue flattening API
Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
@@ -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';
|
||||
|
||||
+15
-3
@@ -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 {
|
||||
|
||||
+77
-11
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user