Continue overhaul of the API's interface - normalize and decouple dependency on GitHub

Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
Erik Engervall
2021-04-15 20:59:41 +02:00
parent a1163bb8cb
commit 6c68b63d32
27 changed files with 455 additions and 521 deletions
@@ -81,11 +81,7 @@ export function GitHubReleaseManager({
}
return (
<PluginApiClientContext.Provider
value={
pluginApiClient as any // TODO: Fix type errors
}
>
<PluginApiClientContext.Provider value={pluginApiClient}>
<div className={classes.root}>
<ContentHeader title="GitHub Release Manager" />
@@ -119,7 +115,7 @@ function Cards({
);
const { versioningStrategyMatches } = useVersioningStrategyMatchesRepoTags({
latestReleaseTagName: gitHubBatchInfo.value?.latestRelease?.tag_name,
latestReleaseTagName: gitHubBatchInfo.value?.latestRelease?.tagName,
project,
repositoryName: gitHubBatchInfo.value?.repository.name,
});
@@ -151,7 +147,7 @@ function Cards({
return (
<Alert severity="error">
Versioning mismatch, expected {project.versioningStrategy} version, got{' '}
{gitHubBatchInfo.value?.latestRelease?.tag_name}
{gitHubBatchInfo.value.latestRelease?.tagName}
</Alert>
);
}
@@ -19,10 +19,8 @@ import { Octokit } from '@octokit/rest';
import { readGitHubIntegrationConfigs } from '@backstage/integration';
import {
GhCompareCommitsResponse,
GhCreateCommitResponse,
GhCreateReferenceResponse,
GhCreateReleaseResponse,
GhCreateTagObjectResponse,
GhGetBranchResponse,
GhGetCommitResponse,
@@ -36,32 +34,72 @@ import { getRcGitHubInfo } from '../cards/createRc/getRcGitHubInfo';
import { SemverTagParts } from '../helpers/tagParts/getSemverTagParts';
import { Project } from '../contexts/ProjectContext';
// export type UnboxPromise<T extends Promise<any>> = T extends Promise<infer U>
// ? U
// : never;
type UnboxPromise<T extends Promise<any>> = T extends Promise<infer U>
? U
: never;
type Todo = any;
export type ApiMethodRetval<
T extends (...args: any) => Promise<any>
> = UnboxPromise<ReturnType<T>>;
type Todo = any; // TODO:
type PartialProject = Omit<Project, 'versioningStrategy'>;
export interface IPluginApiClient {
getHost: () => string;
getRepoPath: (args: PartialProject) => string;
getOrganizations: () => Promise<{ organizations: string[] }>;
getRepositories: (args: {
owner: string;
}) => Promise<{ repositories: string[] }>;
getUsername: () => Promise<{ username: string }>;
getRecentCommits: (
args: { releaseBranchName?: string } & PartialProject,
) => Promise<Todo>;
getReleases: (args: { releaseId: number } & PartialProject) => Promise<Todo>;
getRelease: (args: { releaseId: number } & PartialProject) => Promise<Todo>;
) => Promise<{
recentCommits: {
sha: string;
author: {
htmlUrl?: string;
login?: string;
};
commit: {
message: string;
};
}[];
}>;
getLatestRelease: (
args: PartialProject,
) => Promise<{
latestRelease: {
targetCommitish: string;
tagName: string;
prerelease: boolean;
id: number;
htmlUrl: string;
body?: string | null;
} | null;
}>;
getRepository: (
args: PartialProject,
) => Promise<{
repository: {
pushPermissions: boolean | undefined;
defaultBranch: string;
name: string;
};
}>;
getLatestCommit: (
args: { defaultBranch: string } & PartialProject,
) => Promise<Todo>;
getBranch: (args: { branchName: string } & PartialProject) => Promise<Todo>;
createRc: {
@@ -70,21 +108,27 @@ export interface IPluginApiClient {
mostRecentSha: string;
targetBranch: string;
} & PartialProject,
) => Promise<Todo>;
) => Promise<{ ref: string }>;
getComparison: (
args: {
previousReleaseBranch: string;
nextReleaseBranch: string;
} & PartialProject,
) => Promise<Todo>;
) => Promise<{ htmlUrl: string; aheadBy: number }>;
createRelease: (
args: {
nextGitHubInfo: ReturnType<typeof getRcGitHubInfo>;
releaseBody: string;
} & PartialProject,
) => Promise<Todo>;
) => Promise<{
createReleaseResponse: {
name: string | null;
htmlUrl: string;
tagName: string;
};
}>;
};
patch: {
@@ -142,7 +186,9 @@ export interface IPluginApiClient {
updateRelease: (
args: {
bumpedTag: string;
latestRelease: GhGetReleaseResponse;
latestRelease: NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
tagParts: SemverTagParts | CalverTagParts;
selectedPatchCommit: GhGetCommitResponse;
} & PartialProject,
@@ -157,10 +203,6 @@ export interface IPluginApiClient {
} & PartialProject,
) => Promise<Todo>;
};
getOrganizations: (args: { ownerIsUser: boolean }) => Promise<Todo>;
getUsername: () => Promise<{ username: string }>;
getRepositories: (args: { owner: string; username: string }) => Promise<Todo>;
}
export class PluginApiClient implements IPluginApiClient {
@@ -221,36 +263,46 @@ export class PluginApiClient implements IPluginApiClient {
async getOrganizations() {
const { octokit } = await this.getOctokit();
const { data: orgs } = await octokit.orgs.listForAuthenticatedUser();
const orgListResponse = await octokit.paginate(
octokit.orgs.listForAuthenticatedUser,
{ per_page: 100 },
);
return { orgs };
return {
organizations: orgListResponse.map(organization => organization.login),
};
}
async getRepositories({
owner,
username,
}: {
owner: string;
username: string;
}) {
async getRepositories({ owner }: { owner: string }) {
const { octokit } = await this.getOctokit();
if (owner === username) {
const { data: repos } = await octokit.repos.listForUser({ username });
const repositoryResponse = await octokit
.paginate(octokit.repos.listForOrg, { org: owner, per_page: 100 })
.catch(async error => {
// `owner` is not an org, try listing a user's repositories instead
if (error.status === 404) {
const userRepositoryResponse = await octokit.paginate(
octokit.repos.listForUser,
{ username: owner, per_page: 100 },
);
return userRepositoryResponse;
}
return { repos };
}
throw error;
});
const { data: repos } = await octokit.repos.listForOrg({ org: owner });
return { repos };
return {
repositories: repositoryResponse.map(repository => repository.name),
};
}
async getUsername() {
const { octokit } = await this.getOctokit();
const { data: user } = await octokit.users.getAuthenticated();
const userResponse = await octokit.users.getAuthenticated();
return { username: user.login };
return {
username: userResponse.data.login,
};
}
async getRecentCommits({
@@ -261,43 +313,52 @@ export class PluginApiClient implements IPluginApiClient {
releaseBranchName?: string;
} & PartialProject) {
const { octokit } = await this.getOctokit();
const sha = releaseBranchName ? `?sha=${releaseBranchName}` : '';
const recentCommitsResponse = await octokit.repos.listCommits({
owner,
repo,
...(releaseBranchName ? { sha: releaseBranchName } : {}),
});
const recentCommits: GhGetCommitResponse[] = (
await octokit.request(
`/repos/${this.getRepoPath({ owner, repo })}/commits${sha}`,
)
).data;
return { recentCommits };
return {
recentCommits: recentCommitsResponse.data.map(commit => ({
sha: commit.sha,
author: {
htmlUrl: commit.author?.html_url,
login: commit.author?.login,
},
commit: {
message: commit.commit.message,
},
})),
};
}
async getReleases({ owner, repo }: PartialProject) {
async getLatestRelease({ owner, repo }: PartialProject) {
const { octokit } = await this.getOctokit();
const { data: latestReleases } = await octokit.repos.listReleases({
owner,
repo,
per_page: 1,
});
const releases: GhGetReleaseResponse[] = (
await octokit.request(
`/repos/${this.getRepoPath({ owner, repo })}/releases`,
)
).data;
if (latestReleases.length === 0) {
return {
latestRelease: null,
};
}
return { releases };
}
const latestRelease = latestReleases[0];
async getRelease({
owner,
repo,
releaseId,
}: { releaseId: number } & PartialProject) {
const { octokit } = await this.getOctokit();
const latestRelease: GhGetReleaseResponse = (
await octokit.request(
`/repos/${this.getRepoPath({ owner, repo })}/releases/${releaseId}`,
)
).data;
return { latestRelease };
return {
latestRelease: {
targetCommitish: latestRelease.target_commitish,
tagName: latestRelease.tag_name,
prerelease: latestRelease.prerelease,
id: latestRelease.id,
htmlUrl: latestRelease.html_url,
body: latestRelease.body,
},
};
}
async getRepository({ owner, repo }: PartialProject) {
@@ -363,21 +424,16 @@ export class PluginApiClient implements IPluginApiClient {
targetBranch: string;
} & PartialProject) => {
const { octokit } = await this.getOctokit();
const createRefResponse = await octokit.git.createRef({
owner,
repo,
ref: `refs/heads/${targetBranch}`,
sha: mostRecentSha,
});
const createdRef: GhCreateReferenceResponse = (
await octokit.request(
`/repos/${this.getRepoPath({ owner, repo })}/git/refs`,
{
method: 'POST',
data: {
ref: `refs/heads/${targetBranch}`,
sha: mostRecentSha,
},
},
)
).data;
return { createdRef };
return {
ref: createRefResponse.data.ref,
};
},
getComparison: async ({
@@ -390,17 +446,17 @@ export class PluginApiClient implements IPluginApiClient {
nextReleaseBranch: string;
} & PartialProject) => {
const { octokit } = await this.getOctokit();
const compareCommitsResponse = await octokit.repos.compareCommits({
owner,
repo,
base: previousReleaseBranch,
head: nextReleaseBranch,
});
const comparison: GhCompareCommitsResponse = (
await octokit.request(
`/repos/${this.getRepoPath({
owner,
repo,
})}/compare/${previousReleaseBranch}...${nextReleaseBranch}`,
)
).data;
return { comparison };
return {
htmlUrl: compareCommitsResponse.data.html_url,
aheadBy: compareCommitsResponse.data.ahead_by,
};
},
createRelease: async ({
@@ -413,24 +469,23 @@ export class PluginApiClient implements IPluginApiClient {
releaseBody: string;
} & PartialProject) => {
const { octokit } = await this.getOctokit();
const createReleaseResponse = await octokit.repos.createRelease({
owner,
repo,
tag_name: nextGitHubInfo.rcReleaseTag,
name: nextGitHubInfo.releaseName,
target_commitish: nextGitHubInfo.rcBranch,
body: releaseBody,
prerelease: true,
});
const createReleaseResponse: GhCreateReleaseResponse = (
await octokit.request(
`/repos/${this.getRepoPath({ owner, repo })}/releases`,
{
method: 'POST',
data: {
tag_name: nextGitHubInfo.rcReleaseTag,
name: nextGitHubInfo.releaseName,
target_commitish: nextGitHubInfo.rcBranch,
body: releaseBody,
prerelease: true,
},
},
)
).data;
return { createReleaseResponse };
return {
createReleaseResponse: {
name: createReleaseResponse.data.name,
htmlUrl: createReleaseResponse.data.html_url,
tagName: createReleaseResponse.data.tag_name,
},
};
},
};
@@ -640,7 +695,9 @@ export class PluginApiClient implements IPluginApiClient {
selectedPatchCommit,
}: {
bumpedTag: string;
latestRelease: GhGetReleaseResponse;
latestRelease: NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
tagParts: SemverTagParts | CalverTagParts;
selectedPatchCommit: GhGetCommitResponse;
} & PartialProject) => {
@@ -33,7 +33,6 @@ import { InfoCardPlus } from '../../components/InfoCardPlus';
import {
ComponentConfigCreateRc,
GhGetBranchResponse,
GhGetReleaseResponse,
GhGetRepositoryResponse,
SetRefetch,
} from '../../types/types';
@@ -43,10 +42,13 @@ import { TEST_IDS } from '../../test-helpers/test-ids';
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useStyles } from '../../styles/styles';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
interface CreateRcProps {
defaultBranch: GhGetRepositoryResponse['default_branch'];
latestRelease: GhGetReleaseResponse | null;
latestRelease: ApiMethodRetval<
IPluginApiClient['getLatestRelease']
>['latestRelease'];
releaseBranch: GhGetBranchResponse | null;
setRefetch: SetRefetch;
successCb?: ComponentConfigCreateRc['successCb'];
@@ -97,7 +99,7 @@ export const CreateRc = ({
const tagAlreadyExists =
latestRelease !== null &&
latestRelease.tag_name === nextGitHubInfo.rcReleaseTag;
latestRelease.tagName === nextGitHubInfo.rcReleaseTag;
const conflictingPreRelease =
latestRelease !== null && latestRelease.prerelease;
@@ -132,7 +134,7 @@ export const CreateRc = ({
<Typography>
<Differ
icon="tag"
prev={latestRelease?.tag_name}
prev={latestRelease?.tagName}
next={nextGitHubInfo.rcReleaseTag}
/>
</Typography>
@@ -16,7 +16,7 @@
import { DateTime } from 'luxon';
import { GhGetReleaseResponse } from '../../types/types';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
import {
mockSemverProject,
mockCalverProject,
@@ -34,8 +34,8 @@ describe('getRcGitHubInfo', () => {
describe('calver', () => {
const latestRelease = {
tag_name: 'rc-2020.01.01_0',
} as GhGetReleaseResponse;
tagName: 'rc-2020.01.01_0',
} as ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease'];
it('should return correct GitHub info', () => {
expect(
@@ -57,8 +57,8 @@ describe('getRcGitHubInfo', () => {
describe('semver', () => {
const latestRelease = {
tag_name: 'rc-1.1.1',
} as GhGetReleaseResponse;
tagName: 'rc-1.1.1',
} as ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease'];
it("should return correct GitHub info when there's previous releases", () => {
expect(
@@ -18,9 +18,9 @@ import { DateTime } from 'luxon';
import { getBumpedSemverTagParts } from '../../helpers/getBumpedTag';
import { getSemverTagParts } from '../../helpers/tagParts/getSemverTagParts';
import { GhGetReleaseResponse } from '../../types/types';
import { SEMVER_PARTS } from '../../constants/constants';
import { Project } from '../../contexts/ProjectContext';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
export const getRcGitHubInfo = ({
project,
@@ -29,7 +29,9 @@ export const getRcGitHubInfo = ({
injectedDate = DateTime.now().toFormat('yyyy.MM.dd'),
}: {
project: Project;
latestRelease: GhGetReleaseResponse | null;
latestRelease: ApiMethodRetval<
IPluginApiClient['getLatestRelease']
>['latestRelease'];
semverBumpLevel: keyof typeof SEMVER_PARTS;
injectedDate?: string;
}) => {
@@ -49,7 +51,7 @@ export const getRcGitHubInfo = ({
};
}
const tagParts = getSemverTagParts(latestRelease.tag_name);
const tagParts = getSemverTagParts(latestRelease.tagName);
const { bumpedTagParts } = getBumpedSemverTagParts(tagParts, semverBumpLevel);
const bumpedTag = `${bumpedTagParts.major}.${bumpedTagParts.minor}.${bumpedTagParts.patch}`;
@@ -28,10 +28,10 @@ describe('createRc', () => {
it('should work', async () => {
const result = await createRc({
pluginApiClient: mockApiClient,
defaultBranch: mockDefaultBranch,
latestRelease: mockReleaseVersion,
nextGitHubInfo: mockNextGitHubInfo,
pluginApiClient: mockApiClient,
project: mockCalverProject,
});
@@ -17,20 +17,23 @@
import { getRcGitHubInfo } from '../getRcGitHubInfo';
import {
ComponentConfigCreateRc,
GhCreateReferenceResponse,
GhGetReleaseResponse,
GhGetRepositoryResponse,
ResponseStep,
} from '../../../types/types';
import { PluginApiClient } from '../../../api/PluginApiClient';
import {
ApiMethodRetval,
IPluginApiClient,
} from '../../../api/PluginApiClient';
import { GitHubReleaseManagerError } from '../../../errors/GitHubReleaseManagerError';
import { Project } from '../../../contexts/ProjectContext';
interface CreateRC {
defaultBranch: GhGetRepositoryResponse['default_branch'];
latestRelease: GhGetReleaseResponse | null;
latestRelease: ApiMethodRetval<
IPluginApiClient['getLatestRelease']
>['latestRelease'];
nextGitHubInfo: ReturnType<typeof getRcGitHubInfo>;
pluginApiClient: PluginApiClient;
pluginApiClient: IPluginApiClient;
project: Project;
successCb?: ComponentConfigCreateRc['successCb'];
}
@@ -62,23 +65,20 @@ export async function createRc({
* 2. Create a new ref based on the default branch's most recent sha
*/
const mostRecentSha = latestCommit.sha;
let createdRef: GhCreateReferenceResponse;
try {
createdRef = (
await pluginApiClient.createRc.createRef({
...project,
mostRecentSha,
targetBranch: nextGitHubInfo.rcBranch,
})
).createdRef;
} catch (error) {
if (error.body.message === 'Reference already exists') {
throw new GitHubReleaseManagerError(
`Branch "${nextGitHubInfo.rcBranch}" already exists: .../tree/${nextGitHubInfo.rcBranch}`,
);
}
throw error;
}
const createdRef = await pluginApiClient.createRc
.createRef({
...project,
mostRecentSha,
targetBranch: nextGitHubInfo.rcBranch,
})
.catch(error => {
if (error?.body?.message === 'Reference already exists') {
throw new GitHubReleaseManagerError(
`Branch "${nextGitHubInfo.rcBranch}" already exists: .../tree/${nextGitHubInfo.rcBranch}`,
);
}
throw error;
});
responseSteps.push({
message: 'Cut Release Branch',
secondaryMessage: `with ref "${createdRef.ref}"`,
@@ -88,17 +88,17 @@ export async function createRc({
* 3. Compose a body for the release
*/
const previousReleaseBranch = latestRelease
? latestRelease.target_commitish
? latestRelease.targetCommitish
: defaultBranch;
const nextReleaseBranch = nextGitHubInfo.rcBranch;
const { comparison } = await pluginApiClient.createRc.getComparison({
const comparison = await pluginApiClient.createRc.getComparison({
...project,
previousReleaseBranch,
nextReleaseBranch,
});
const releaseBody = `**Compare** ${comparison.html_url}
const releaseBody = `**Compare** ${comparison.htmlUrl}
**Ahead by** ${comparison.ahead_by} commits
**Ahead by** ${comparison.aheadBy} commits
**Release branch** ${createdRef.ref}
@@ -108,7 +108,7 @@ export async function createRc({
responseSteps.push({
message: 'Fetched commit comparison',
secondaryMessage: `${previousReleaseBranch}...${nextReleaseBranch}`,
link: comparison.html_url,
link: comparison.htmlUrl,
});
/**
@@ -124,15 +124,15 @@ export async function createRc({
responseSteps.push({
message: `Created Release Candidate "${createReleaseResponse.name}"`,
secondaryMessage: `with tag "${nextGitHubInfo.rcReleaseTag}"`,
link: createReleaseResponse.html_url,
link: createReleaseResponse.htmlUrl,
});
await successCb?.({
gitHubReleaseUrl: createReleaseResponse.html_url,
gitHubReleaseUrl: createReleaseResponse.htmlUrl,
gitHubReleaseName: createReleaseResponse.name,
comparisonUrl: comparison.html_url,
previousTag: latestRelease?.tag_name,
createdTag: createReleaseResponse.tag_name,
comparisonUrl: comparison.htmlUrl,
previousTag: latestRelease?.tagName,
createdTag: createReleaseResponse.tagName,
});
return responseSteps;
@@ -18,20 +18,24 @@ import React from 'react';
import { Link, Typography } from '@material-ui/core';
import { Differ } from '../../components/Differ';
import { GhGetBranchResponse, GhGetReleaseResponse } from '../../types/types';
import { GhGetBranchResponse } from '../../types/types';
import { InfoCardPlus } from '../../components/InfoCardPlus';
import { TEST_IDS } from '../../test-helpers/test-ids';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useStyles } from '../../styles/styles';
import flowImage from './flow.png';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
interface InfoCardProps {
releaseBranch: GhGetBranchResponse | null;
latestRelease: GhGetReleaseResponse | null;
latestRelease: ApiMethodRetval<
IPluginApiClient['getLatestRelease']
>['latestRelease'];
}
export const Info = ({ releaseBranch, latestRelease }: InfoCardProps) => {
const project = useProjectContext();
const classes = useStyles();
return (
@@ -98,7 +102,7 @@ export const Info = ({ releaseBranch, latestRelease }: InfoCardProps) => {
</Typography>
<Typography>
Latest release: <Differ icon="tag" next={latestRelease?.tag_name} />
Latest release: <Differ icon="tag" next={latestRelease?.tagName} />
</Typography>
</div>
</InfoCardPlus>
@@ -23,15 +23,17 @@ import { NoLatestRelease } from '../../components/NoLatestRelease';
import {
ComponentConfigPatch,
GhGetBranchResponse,
GhGetReleaseResponse,
SetRefetch,
} from '../../types/types';
import { PatchBody } from './PatchBody';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useStyles } from '../../styles/styles';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
interface PatchProps {
latestRelease: GhGetReleaseResponse | null;
latestRelease: ApiMethodRetval<
IPluginApiClient['getLatestRelease']
>['latestRelease'];
releaseBranch: GhGetBranchResponse | null;
setRefetch: SetRefetch;
successCb?: ComponentConfigPatch['successCb'];
@@ -53,7 +55,7 @@ export const Patch = ({
const { bumpedTag, tagParts } = getBumpedTag({
project,
tag: latestRelease.tag_name,
tag: latestRelease.tagName,
bumpLevel: 'patch',
});
@@ -41,7 +41,7 @@ describe('PatchBody', () => {
beforeEach(jest.clearAllMocks);
it('should render error', async () => {
mockApiClient.getBranch.mockImplementationOnce(() => {
(mockApiClient.getBranch as jest.Mock).mockImplementationOnce(() => {
throw new Error('banana');
});
@@ -38,7 +38,6 @@ import {
ComponentConfigPatch,
GhGetBranchResponse,
GhGetCommitResponse,
GhGetReleaseResponse,
SetRefetch,
} from '../../types/types';
import { CalverTagParts } from '../../helpers/tagParts/getCalverTagParts';
@@ -50,10 +49,13 @@ import { TEST_IDS } from '../../test-helpers/test-ids';
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useStyles } from '../../styles/styles';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
interface PatchBodyProps {
bumpedTag: string;
latestRelease: GhGetReleaseResponse;
latestRelease: NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
releaseBranch: GhGetBranchResponse | null;
setRefetch: SetRefetch;
successCb?: ComponentConfigPatch['successCb'];
@@ -75,17 +77,17 @@ export const PatchBody = ({
const githubDataResponse = useAsync(async () => {
const [
{ branch: releaseBranchResponse },
{ recentCommits },
{ recentCommits: recentCommitsOnDefaultBranch },
] = await Promise.all([
pluginApiClient.getBranch({
...project,
branchName: latestRelease.target_commitish,
branchName: latestRelease.targetCommitish,
}),
pluginApiClient.getRecentCommits({ ...project }),
]);
const {
recentCommits: recentReleaseBranchCommits,
recentCommits: recentCommitsOnReleaseBranch,
} = await pluginApiClient.getRecentCommits({
...project,
releaseBranchName: releaseBranchResponse.name,
@@ -93,8 +95,8 @@ export const PatchBody = ({
return {
releaseBranch: releaseBranchResponse,
recentReleaseBranchCommits,
recentCommits,
recentCommitsOnReleaseBranch,
recentCommitsOnDefaultBranch,
};
});
@@ -146,118 +148,118 @@ export const PatchBody = ({
)}
<Typography className={classes.paragraph}>
<Differ icon="tag" prev={latestRelease.tag_name} next={bumpedTag} />
<Differ icon="tag" prev={latestRelease.tagName} next={bumpedTag} />
</Typography>
</>
);
}
function CommitList() {
if (!githubDataResponse.value?.recentCommits) {
if (!githubDataResponse.value?.recentCommitsOnDefaultBranch) {
return null;
}
return (
<List>
{githubDataResponse.value.recentCommits.map((commit, index) => {
const commitExistsOnReleaseBranch = !!githubDataResponse.value?.recentReleaseBranchCommits.find(
({ sha }) => {
return sha === commit.sha;
},
);
{githubDataResponse.value.recentCommitsOnDefaultBranch.map(
(commit, index) => {
const commitExistsOnReleaseBranch = !!githubDataResponse.value?.recentCommitsOnReleaseBranch.find(
releaseBranchCommit => releaseBranchCommit.sha === commit.sha,
);
return (
<div style={{ position: 'relative' }} key={`commit-${index}`}>
{commitExistsOnReleaseBranch && (
<Paper
elevation={3}
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate3d(-50%,-50%,0)',
zIndex: 10,
color: 'green',
padding: 6,
background: 'rgba(244,244,244,1)',
borderRadius: 8,
}}
>
<FileCopyIcon
fontSize="small"
style={{ verticalAlign: 'middle' }}
/>{' '}
Already exists on <b>{releaseBranch?.name}</b>
</Paper>
)}
<ListItem
disabled={
patchReleaseResponse.loading ||
(patchReleaseResponse.value &&
patchReleaseResponse.value.length > 0) ||
commitExistsOnReleaseBranch
}
role={undefined}
dense
button
onClick={() => {
if (index === checkedCommitIndex) {
setCheckedCommitIndex(-1);
} else {
setCheckedCommitIndex(index);
}
}}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={checkedCommitIndex === index}
tabIndex={-1}
/>
</ListItemIcon>
<ListItemText
id={commit.sha}
primary={commit.commit.message}
secondary={
<>
{commit.sha}{' '}
<Link
color="primary"
href={commit.author.html_url}
target="_blank"
>
@{commit.author.login}
</Link>
</>
}
/>
<ListItemSecondaryAction>
<IconButton
aria-label="commit"
disabled={commitExistsOnReleaseBranch || !releaseBranch}
onClick={() => {
const repoPath = pluginApiClient.getRepoPath({
...project,
});
const host = pluginApiClient.getHost();
const newTab = window.open(
`https://${host}/${repoPath}/compare/${releaseBranch?.name}...${commit.sha}`,
'_blank',
);
newTab?.focus();
return (
<div style={{ position: 'relative' }} key={`commit-${index}`}>
{commitExistsOnReleaseBranch && (
<Paper
elevation={3}
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate3d(-50%,-50%,0)',
zIndex: 10,
color: 'green',
padding: 6,
background: 'rgba(244,244,244,1)',
borderRadius: 8,
}}
>
<OpenInNewIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
</div>
);
})}
<FileCopyIcon
fontSize="small"
style={{ verticalAlign: 'middle' }}
/>{' '}
Already exists on <b>{releaseBranch?.name}</b>
</Paper>
)}
<ListItem
disabled={
patchReleaseResponse.loading ||
(patchReleaseResponse.value &&
patchReleaseResponse.value.length > 0) ||
commitExistsOnReleaseBranch
}
role={undefined}
dense
button
onClick={() => {
if (index === checkedCommitIndex) {
setCheckedCommitIndex(-1);
} else {
setCheckedCommitIndex(index);
}
}}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={checkedCommitIndex === index}
tabIndex={-1}
/>
</ListItemIcon>
<ListItemText
id={commit.sha}
primary={commit.commit.message}
secondary={
<>
{commit.sha}{' '}
<Link
color="primary"
href={commit.author.htmlUrl}
target="_blank"
>
@{commit.author.login}
</Link>
</>
}
/>
<ListItemSecondaryAction>
<IconButton
aria-label="commit"
disabled={commitExistsOnReleaseBranch || !releaseBranch}
onClick={() => {
const repoPath = pluginApiClient.getRepoPath({
...project,
});
const host = pluginApiClient.getHost();
const newTab = window.open(
`https://${host}/${repoPath}/compare/${releaseBranch?.name}...${commit.sha}`,
'_blank',
);
newTab?.focus();
}}
>
<OpenInNewIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
</div>
);
},
)}
</List>
);
}
@@ -275,7 +277,11 @@ export const PatchBody = ({
);
}
if (!githubDataResponse.value?.recentCommits[checkedCommitIndex]) {
if (
!githubDataResponse.value?.recentCommitsOnDefaultBranch[
checkedCommitIndex
]
) {
return (
<Button disabled variant="contained" color="primary">
Patch Release Candidate
@@ -291,7 +297,9 @@ export const PatchBody = ({
onClick={() => {
// FIXME: Optional chaining shouldn't be needed here due to the if-statement above
patchReleaseFn(
githubDataResponse.value?.recentCommits[checkedCommitIndex],
githubDataResponse.value?.recentCommitsOnDefaultBranch[
checkedCommitIndex
],
);
}}
>
@@ -17,19 +17,23 @@
import {
ComponentConfigPatch,
GhGetCommitResponse,
GhGetReleaseResponse,
ResponseStep,
} from '../../../types/types';
import { CalverTagParts } from '../../../helpers/tagParts/getCalverTagParts';
import { GitHubReleaseManagerError } from '../../../errors/GitHubReleaseManagerError';
import { PluginApiClient } from '../../../api/PluginApiClient';
import {
ApiMethodRetval,
IPluginApiClient,
} from '../../../api/PluginApiClient';
import { Project } from '../../../contexts/ProjectContext';
import { SemverTagParts } from '../../../helpers/tagParts/getSemverTagParts';
interface Patch {
bumpedTag: string;
latestRelease: GhGetReleaseResponse;
pluginApiClient: PluginApiClient;
latestRelease: NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
pluginApiClient: IPluginApiClient;
project: Project;
selectedPatchCommit: GhGetCommitResponse;
successCb?: ComponentConfigPatch['successCb'];
@@ -52,7 +56,7 @@ export async function patch({
throw new GitHubReleaseManagerError('Invalid commit');
}
const releaseBranchName = latestRelease.target_commitish;
const releaseBranchName = latestRelease.targetCommitish;
/**
* 1. Here is the branch we want to cherry-pick to:
* > branch = GET /repos/$owner/$repo/branches/$branchName
@@ -199,7 +203,7 @@ export async function patch({
await successCb?.({
updatedReleaseUrl: updatedRelease.html_url,
updatedReleaseName: updatedRelease.name,
previousTag: latestRelease.tag_name,
previousTag: latestRelease.tagName,
patchedTag: updatedRelease.tag_name,
patchCommitUrl: selectedPatchCommit.html_url,
patchCommitMessage: selectedPatchCommit.commit.message,
@@ -48,7 +48,7 @@ export function Owner({
return <CenteredCircularProgress />;
}
if (!value?.orgs) {
if (!value?.organizations) {
return <Alert severity="error">Could not fetch organizations</Alert>;
}
@@ -74,9 +74,9 @@ export function Owner({
<MenuItem value={username}>
<strong>{username}</strong>
</MenuItem>
{value.orgs.map((org, index) => (
<MenuItem key={`organization-${index}`} value={org.login}>
{org.login}
{value.organizations.map((orgName, index) => (
<MenuItem key={`organization-${index}`} value={orgName}>
{orgName}
</MenuItem>
))}
</Select>
@@ -18,7 +18,7 @@ import React from 'react';
import { useAsync } from 'react-use';
import { FormControl, InputLabel, Select, MenuItem } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { ControllerRenderProps, useForm } from 'react-hook-form';
import { ControllerRenderProps } from 'react-hook-form';
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
import { useFormClasses } from './styles';
@@ -26,10 +26,8 @@ import { CenteredCircularProgress } from '../../components/CenteredCircularProgr
import { Project } from '../../contexts/ProjectContext';
export function Repo({
username,
controllerRenderProps,
}: {
username: string;
controllerRenderProps: ControllerRenderProps;
}) {
const pluginApiClient = usePluginApiClientContext();
@@ -37,11 +35,7 @@ export function Repo({
const project: Project = controllerRenderProps.value;
const { loading, error, value } = useAsync(
() =>
pluginApiClient.getRepositories({
owner: project.owner,
username,
}),
async () => pluginApiClient.getRepositories({ owner: project.owner }),
[project.owner],
);
@@ -53,7 +47,7 @@ export function Repo({
return <CenteredCircularProgress />;
}
if (!value?.repos) {
if (!value?.repositories) {
return (
<Alert severity="error">
Could not fetch repositories for "{project.owner}"
@@ -79,9 +73,9 @@ export function Repo({
<MenuItem value="">
<em>None</em>
</MenuItem>
{value.repos.map((repository, index) => (
<MenuItem key={`repository-${index}`} value={repository.name}>
{repository.name}
{value.repositories.map((repositoryName, index) => (
<MenuItem key={`repository-${index}`} value={repositoryName}>
{repositoryName}
</MenuItem>
))}
</Select>
@@ -44,10 +44,7 @@ export function RepoDetailsForm({
/>
{project.owner.length > 0 && (
<Repo
controllerRenderProps={controllerRenderProps}
username={username}
/>
<Repo controllerRenderProps={controllerRenderProps} />
)}
</>
);
@@ -20,17 +20,16 @@ import { Typography } from '@material-ui/core';
import { InfoCardPlus } from '../../components/InfoCardPlus';
import { NoLatestRelease } from '../../components/NoLatestRelease';
import {
ComponentConfigPromoteRc,
GhGetReleaseResponse,
SetRefetch,
} from '../../types/types';
import { ComponentConfigPromoteRc, SetRefetch } from '../../types/types';
import { PromoteRcBody } from './PromoteRcBody';
import { useStyles } from '../../styles/styles';
import { TEST_IDS } from '../../test-helpers/test-ids';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
interface PromoteRcProps {
latestRelease: GhGetReleaseResponse | null;
latestRelease: ApiMethodRetval<
IPluginApiClient['getLatestRelease']
>['latestRelease'];
setRefetch: SetRefetch;
successCb?: ComponentConfigPromoteRc['successCb'];
}
@@ -20,20 +20,19 @@ import { Alert } from '@material-ui/lab';
import { Button, Typography } from '@material-ui/core';
import { Differ } from '../../components/Differ';
import {
ComponentConfigPromoteRc,
GhGetReleaseResponse,
SetRefetch,
} from '../../types/types';
import { ComponentConfigPromoteRc, SetRefetch } from '../../types/types';
import { promoteRc } from './sideEffects/promoteRc';
import { ResponseStepList } from '../../components/ResponseStepList/ResponseStepList';
import { TEST_IDS } from '../../test-helpers/test-ids';
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useStyles } from '../../styles/styles';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
interface PromoteRcBodyProps {
rcRelease: GhGetReleaseResponse;
rcRelease: NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
setRefetch: SetRefetch;
successCb?: ComponentConfigPromoteRc['successCb'];
}
@@ -46,7 +45,7 @@ export const PromoteRcBody = ({
const pluginApiClient = usePluginApiClientContext();
const project = useProjectContext();
const classes = useStyles();
const releaseVersion = rcRelease.tag_name.replace('rc-', 'version-');
const releaseVersion = rcRelease.tagName.replace('rc-', 'version-');
const [promoteGitHubRcResponse, promoseGitHubRcFn] = useAsyncFn(
promoteRc({
pluginApiClient,
@@ -71,7 +70,7 @@ export const PromoteRcBody = ({
</Typography>
<Typography className={classes.paragraph}>
<Differ icon="tag" prev={rcRelease.tag_name} next={releaseVersion} />
<Differ icon="tag" prev={rcRelease.tagName} next={releaseVersion} />
</Typography>
</>
);
@@ -15,8 +15,9 @@
*/
import {
mockRcRelease,
mockApiClient,
mockRcRelease,
mockSemverProject,
} from '../../../test-helpers/test-helpers';
import { promoteRc } from './promoteRc';
@@ -28,6 +29,7 @@ describe('promoteRc', () => {
pluginApiClient: mockApiClient,
rcRelease: mockRcRelease,
releaseVersion: 'version-1.2.3',
project: mockSemverProject,
})();
expect(result).toMatchInlineSnapshot(`
@@ -14,18 +14,19 @@
* limitations under the License.
*/
import { ComponentConfigPromoteRc, ResponseStep } from '../../../types/types';
import {
ComponentConfigPromoteRc,
GhGetReleaseResponse,
ResponseStep,
} from '../../../types/types';
import { PluginApiClient } from '../../../api/PluginApiClient';
ApiMethodRetval,
IPluginApiClient,
} from '../../../api/PluginApiClient';
import { Project } from '../../../contexts/ProjectContext';
interface PromoteRc {
pluginApiClient: PluginApiClient;
pluginApiClient: IPluginApiClient;
project: Project;
rcRelease: GhGetReleaseResponse;
rcRelease: NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
releaseVersion: string;
successCb?: ComponentConfigPromoteRc['successCb'];
}
@@ -47,15 +48,15 @@ export function promoteRc({
});
responseSteps.push({
message: `Promoted "${release.name}"`,
secondaryMessage: `from "${rcRelease.tag_name}" to "${release.tag_name}"`,
secondaryMessage: `from "${rcRelease.tagName}" to "${release.tag_name}"`,
link: release.html_url,
});
await successCb?.({
gitHubReleaseUrl: release.html_url,
gitHubReleaseName: release.name,
previousTagUrl: rcRelease.html_url,
previousTag: rcRelease.tag_name,
previousTagUrl: rcRelease.htmlUrl,
previousTag: rcRelease.tagName,
updatedTagUrl: release.html_url,
updatedTag: release.tag_name,
});
@@ -16,11 +16,11 @@
import { createContext, useContext } from 'react';
import { PluginApiClient } from '../api/PluginApiClient';
import { IPluginApiClient } from '../api/PluginApiClient';
import { GitHubReleaseManagerError } from '../errors/GitHubReleaseManagerError';
export const PluginApiClientContext = createContext<
PluginApiClient | undefined
IPluginApiClient | undefined
>(undefined);
export const usePluginApiClientContext = () => {
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import { Project } from '../contexts/ProjectContext';
import { CalverTagParts } from './tagParts/getCalverTagParts';
import { Project } from '../types/types';
export function isCalverTagParts(
project: Project,
@@ -14,22 +14,21 @@
* limitations under the License.
*/
import { getLatestRelease } from './getLatestRelease';
import { PluginApiClient } from '../api/PluginApiClient';
import { IPluginApiClient } from '../api/PluginApiClient';
import { Project } from '../contexts/ProjectContext';
interface GetGitHubBatchInfo {
project: Project;
pluginApiClient: PluginApiClient;
pluginApiClient: IPluginApiClient;
}
export const getGitHubBatchInfo = ({
project,
pluginApiClient,
}: GetGitHubBatchInfo) => async () => {
const [{ repository }, latestRelease] = await Promise.all([
const [{ repository }, { latestRelease }] = await Promise.all([
pluginApiClient.getRepository({ ...project }),
getLatestRelease({ project, pluginApiClient }),
pluginApiClient.getLatestRelease({ ...project }),
]);
if (latestRelease === null) {
@@ -42,7 +41,7 @@ export const getGitHubBatchInfo = ({
const { branch } = await pluginApiClient.getBranch({
...project,
branchName: latestRelease.target_commitish,
branchName: latestRelease.targetCommitish,
});
return {
@@ -1,43 +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 { mockApiClient } from '../test-helpers/test-helpers';
import { getLatestRelease } from './getLatestRelease';
describe('getLatestRelease', () => {
beforeEach(jest.clearAllMocks);
it('should return the latest release with id=1', async () => {
const result = await getLatestRelease({ pluginApiClient: mockApiClient });
expect(result).toMatchInlineSnapshot(`
Object {
"body": "mock_latest_release",
"html_url": "mock_release_html_url",
"id": 1,
"prerelease": false,
}
`);
});
it('should return early with `null` if no releases found', async () => {
mockApiClient.getReleases.mockImplementationOnce(() => ({ releases: [] }));
const result = await getLatestRelease({ pluginApiClient: mockApiClient });
expect(result).toMatchInlineSnapshot(`null`);
});
});
@@ -1,41 +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 { PluginApiClient } from '../api/PluginApiClient';
import { Project } from '../contexts/ProjectContext';
interface GetLatestRelease {
pluginApiClient: PluginApiClient;
project: Project;
}
export async function getLatestRelease({
pluginApiClient,
project,
}: GetLatestRelease) {
const { releases } = await pluginApiClient.getReleases({ ...project });
if (releases.length === 0) {
return null;
}
const { latestRelease } = await pluginApiClient.getRelease({
...project,
releaseId: releases[0].id,
});
return latestRelease;
}
@@ -27,17 +27,15 @@ describe('testHelpers', () => {
"getComparison": [MockFunction],
},
"getBranch": [MockFunction],
"getHost": [MockFunction],
"getLatestCommit": [MockFunction],
"getOctokit": [Function],
"getProject": [MockFunction],
"getLatestRelease": [MockFunction],
"getOrganizations": [MockFunction],
"getRecentCommits": [MockFunction],
"getRelease": [MockFunction],
"getReleases": [MockFunction],
"getRepoPath": [MockFunction],
"getRepositories": [MockFunction],
"getRepository": [MockFunction],
"githubAuthApi": Object {
"getAccessToken": [MockFunction],
},
"getUsername": [MockFunction],
"patch": Object {
"createCherryPickCommit": [MockFunction],
"createReference": [MockFunction],
@@ -48,29 +46,14 @@ describe('testHelpers', () => {
"replaceTempCommit": [MockFunction],
"updateRelease": [MockFunction],
},
"pluginApiClient": Object {
"baseUrl": "http://mock_base_url.hehe",
"getOctokit": [MockFunction],
},
"project": Object {
"github": Object {
"org": "mock_org",
"repo": "mock_repo",
},
"name": "mock_name",
"versioningStrategy": "semver",
},
"promoteRc": Object {
"promoteRelease": [MockFunction],
},
},
"mockBumpedTag": "rc-2020.01.01_1337",
"mockCalverProject": Object {
"github": Object {
"org": "mock_org",
"repo": "mock_repo",
},
"name": "mock_name",
"owner": "mock_owner",
"repo": "mock_repo",
"versioningStrategy": "calver",
},
"mockDefaultBranch": "mock_defaultBranch",
@@ -80,12 +63,11 @@ describe('testHelpers', () => {
"releaseName": "Version 1.2.3",
},
"mockRcRelease": Object {
"body": "mock_body",
"html_url": "mock_release_html_url",
"htmlUrl": "mock_release_html_url",
"id": 1,
"prerelease": true,
"tag_name": "rc-2020.01.01_1",
"target_commitish": "rc/1.2.3",
"tagName": "rc-2020.01.01_1",
"targetCommitish": "rc/1.2.3",
},
"mockRecentCommits": Array [
Object {
@@ -128,12 +110,11 @@ describe('testHelpers', () => {
"name": "rc/1.2.3",
},
"mockReleaseVersion": Object {
"body": "mock_body",
"html_url": "mock_release_html_url",
"htmlUrl": "mock_release_html_url",
"id": 1,
"prerelease": false,
"tag_name": "version-2020.01.01_1",
"target_commitish": "rc/1.2.3",
"tagName": "version-2020.01.01_1",
"targetCommitish": "rc/1.2.3",
},
"mockSelectedPatchCommit": Object {
"author": Object {
@@ -148,11 +129,8 @@ describe('testHelpers', () => {
"sha": "mock_latestCommit_sha",
},
"mockSemverProject": Object {
"github": Object {
"org": "mock_org",
"repo": "mock_repo",
},
"name": "mock_name",
"owner": "mock_owner",
"repo": "mock_repo",
"versioningStrategy": "semver",
},
"mockTagParts": Object {
@@ -17,10 +17,8 @@
import { CalverTagParts } from '../helpers/tagParts/getCalverTagParts';
import { getRcGitHubInfo } from '../cards/createRc/getRcGitHubInfo';
import {
GhCompareCommitsResponse,
GhCreateCommitResponse,
GhCreateReferenceResponse,
GhCreateReleaseResponse,
GhCreateTagObjectResponse,
GhGetBranchResponse,
GhGetCommitResponse,
@@ -28,24 +26,19 @@ import {
GhMergeResponse,
GhUpdateReferenceResponse,
GhUpdateReleaseResponse,
Project,
} from '../types/types';
import { Project } from '../contexts/ProjectContext';
import { ApiMethodRetval, IPluginApiClient } from '../api/PluginApiClient';
export const mockSemverProject: Project = {
github: {
org: 'mock_org',
repo: 'mock_repo',
},
name: 'mock_name',
owner: 'mock_owner',
repo: 'mock_repo',
versioningStrategy: 'semver',
};
export const mockCalverProject: Project = {
github: {
org: 'mock_org',
repo: 'mock_repo',
},
name: 'mock_name',
owner: 'mock_owner',
repo: 'mock_repo',
versioningStrategy: 'calver',
};
@@ -72,23 +65,28 @@ const createMockRelease = ({
id = 1,
prerelease = false,
...rest
}: Partial<GhGetReleaseResponse> = {}) =>
}: Partial<
NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>
> = {}) =>
({
id: 1,
body: 'mock_body',
html_url: 'mock_release_html_url',
htmlUrl: 'mock_release_html_url',
prerelease,
...rest,
} as GhGetReleaseResponse);
} as NonNullable<
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>);
export const mockRcRelease = createMockRelease({
prerelease: true,
tag_name: 'rc-2020.01.01_1',
target_commitish: 'rc/1.2.3',
tagName: 'rc-2020.01.01_1',
targetCommitish: 'rc/1.2.3',
});
export const mockReleaseVersion = createMockRelease({
prerelease: false,
tag_name: 'version-2020.01.01_1',
target_commitish: 'rc/1.2.3',
tagName: 'version-2020.01.01_1',
targetCommitish: 'rc/1.2.3',
});
/**
@@ -134,63 +132,39 @@ export const mockSelectedPatchCommit = createMockCommit({
/**
* MOCK API CLIENT
*/
export const mockApiClient = {
pluginApiClient: {
getOctokit: jest.fn(),
baseUrl: 'http://mock_base_url.hehe',
},
export const mockApiClient: IPluginApiClient = {
getHost: jest.fn(() => 'github.com'),
getRepoPath: jest.fn(() => 'erikengervall/playground'),
getOrganizations: jest.fn(),
getRepositories: jest.fn(),
getUsername: jest.fn(),
getRecentCommits: jest.fn().mockResolvedValue({
recentCommits: mockRecentCommits,
}),
getReleases: jest.fn().mockResolvedValue({
releases: [
createMockRelease({ id: 1, body: 'mock_releases[0]' }),
createMockRelease({ id: 2, body: 'mock_releases[1]' }),
],
}),
getRelease: jest.fn().mockResolvedValue({
latestRelease: createMockRelease({ id: 1, body: 'mock_latest_release' }),
}),
getBranch: jest.fn().mockResolvedValue({
branch: mockReleaseBranch,
}),
getLatestRelease: jest.fn(), // TODO:
getRepository: jest.fn(),
getLatestCommit: jest.fn().mockResolvedValue({
latestCommit: createMockCommit({ node_id: 'mock_latest_commit' }),
}),
getOctokit: () => ({
octokit: {
request: jest.fn(),
},
getBranch: jest.fn().mockResolvedValue({
branch: mockReleaseBranch,
}),
getProject: jest.fn(),
getRepository: jest.fn(),
githubAuthApi: {
getAccessToken: jest.fn(),
},
createRc: {
createRef: jest.fn().mockResolvedValue({
createdRef: {
ref: 'mock_createRef_ref',
} as GhCreateReferenceResponse,
}),
ref: 'mock_createRef_ref',
} as NonNullable<ApiMethodRetval<IPluginApiClient['createRc']['createRef']>>),
createRelease: jest.fn().mockResolvedValue({
createReleaseResponse: {
name: 'mock_createRelease_name',
html_url: 'mock_createRelease_html_url',
tag_name: 'mock_createRelease_tag_name',
} as GhCreateReleaseResponse,
}),
htmlUrl: 'mock_createRelease_html_url',
tagName: 'mock_createRelease_tag_name',
},
} as NonNullable<ApiMethodRetval<IPluginApiClient['createRc']['createRelease']>>),
getComparison: jest.fn().mockResolvedValue({
comparison: {
html_url: 'mock_compareCommits_html_url',
ahead_by: 1,
} as GhCompareCommitsResponse,
}),
htmlUrl: 'mock_compareCommits_html_url',
aheadBy: 1,
} as NonNullable<ApiMethodRetval<IPluginApiClient['createRc']['getComparison']>>),
},
patch: {
createCherryPickCommit: jest.fn().mockResolvedValue({
@@ -252,5 +226,5 @@ export const mockApiClient = {
} as GhGetReleaseResponse,
}),
},
project: mockSemverProject,
} as any;
// project: mockSemverProject,
};
@@ -21,7 +21,7 @@ interface ComponentConfig<Args = void> {
export interface ComponentConfigCreateRcSuccessCbArgs {
gitHubReleaseUrl: string;
gitHubReleaseName: string;
gitHubReleaseName: string | null;
comparisonUrl: string;
previousTag?: string;
createdTag: string;
@@ -30,7 +30,7 @@ export type ComponentConfigCreateRc = ComponentConfig<ComponentConfigCreateRcSuc
export interface ComponentConfigPromoteRcSuccessCbArgs {
gitHubReleaseUrl: string;
gitHubReleaseName: string;
gitHubReleaseName: string | null;
previousTagUrl: string;
previousTag: string;
updatedTagUrl: string;