Replace react-use-form with URL query params
Introduce RefetchContext instead of passing setRefresh throughout entire project Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
@@ -22,14 +22,27 @@ import {
|
||||
GitHubReleaseManagerPage,
|
||||
} from '../src/plugin';
|
||||
|
||||
function DevWrapper({ children }: { children: React.ReactNode }) {
|
||||
return <div style={{ padding: 30 }}>{children}</div>;
|
||||
}
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(gitHubReleaseManagerPlugin)
|
||||
.addPage({
|
||||
title: 'Page 1',
|
||||
element: <GitHubReleaseManagerPage />,
|
||||
element: (
|
||||
<DevWrapper>
|
||||
<GitHubReleaseManagerPage />
|
||||
</DevWrapper>
|
||||
),
|
||||
})
|
||||
.addPage({
|
||||
title: 'Page 2',
|
||||
element: <GitHubReleaseManagerPage />,
|
||||
element: (
|
||||
<DevWrapper>
|
||||
{' '}
|
||||
<GitHubReleaseManagerPage />
|
||||
</DevWrapper>
|
||||
),
|
||||
})
|
||||
.render();
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"luxon": "^1.26.0",
|
||||
"qs": "^6.10.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^6.6.0",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-use": "^15.3.3",
|
||||
"react": "^16.13.1"
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { useApi, ContentHeader, ErrorBoundary } from '@backstage/core';
|
||||
@@ -42,6 +41,9 @@ import { InfoCardPlus } from './components/InfoCardPlus';
|
||||
import { RepoDetailsForm } from './cards/projectForm/RepoDetailsForm';
|
||||
import { CenteredCircularProgress } from './components/CenteredCircularProgress';
|
||||
import { useVersioningStrategyMatchesRepoTags } from './helpers/useVersioningStrategyMatchesRepoTags';
|
||||
import { useQuery } from './helpers/useQuery';
|
||||
import { getParsedQuery } from './helpers/getNewQueryParams';
|
||||
import { RefetchContext } from './contexts/RefetchContext';
|
||||
|
||||
interface GitHubReleaseManagerProps {
|
||||
components?: {
|
||||
@@ -65,8 +67,14 @@ export function GitHubReleaseManager({
|
||||
const pluginApiClient = useApi(githubReleaseManagerApiRef);
|
||||
const classes = useStyles();
|
||||
const usernameResponse = useAsync(() => pluginApiClient.getUsername());
|
||||
const { control, watch } = useForm();
|
||||
const project: Project = watch('repo-details-form');
|
||||
const query = useQuery();
|
||||
|
||||
const parsedQuery = getParsedQuery({ query });
|
||||
const project: Project = {
|
||||
owner: parsedQuery.owner ?? '',
|
||||
repo: parsedQuery.repo ?? '',
|
||||
versioningStrategy: parsedQuery.versioningStrategy ?? 'semver',
|
||||
};
|
||||
|
||||
if (usernameResponse.error) {
|
||||
return <Alert severity="error">{usernameResponse.error.message}</Alert>;
|
||||
@@ -87,8 +95,8 @@ export function GitHubReleaseManager({
|
||||
|
||||
<InfoCardPlus>
|
||||
<RepoDetailsForm
|
||||
control={control}
|
||||
username={usernameResponse.value.username}
|
||||
project={project}
|
||||
/>
|
||||
</InfoCardPlus>
|
||||
|
||||
@@ -108,10 +116,10 @@ function Cards({
|
||||
project: Project;
|
||||
}) {
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
const [refetch, setRefetch] = useState(0);
|
||||
const [refetchTrigger, setRefetchTrigger] = useState(0);
|
||||
const gitHubBatchInfo = useAsync(
|
||||
getGitHubBatchInfo({ project, pluginApiClient }),
|
||||
[project, refetch],
|
||||
[project, refetchTrigger],
|
||||
);
|
||||
|
||||
const { versioningStrategyMatches } = useVersioningStrategyMatchesRepoTags({
|
||||
@@ -121,7 +129,12 @@ function Cards({
|
||||
});
|
||||
|
||||
if (gitHubBatchInfo.error) {
|
||||
return <Alert severity="error">{gitHubBatchInfo.error.message}</Alert>;
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Error occured while fetching information for "{project.owner}/
|
||||
{project.repo}" ({gitHubBatchInfo.error.message})
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (gitHubBatchInfo.loading) {
|
||||
@@ -154,39 +167,38 @@ function Cards({
|
||||
|
||||
return (
|
||||
<ProjectContext.Provider value={project}>
|
||||
<ErrorBoundary>
|
||||
<Info
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
releaseBranch={gitHubBatchInfo.value.releaseBranch}
|
||||
/>
|
||||
|
||||
{components?.default?.createRc?.omit !== true && (
|
||||
<CreateRc
|
||||
<RefetchContext.Provider value={{ refetchTrigger, setRefetchTrigger }}>
|
||||
<ErrorBoundary>
|
||||
<Info
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
releaseBranch={gitHubBatchInfo.value.releaseBranch}
|
||||
defaultBranch={gitHubBatchInfo.value.repository.defaultBranch}
|
||||
setRefetch={setRefetch}
|
||||
successCb={components?.default?.createRc?.successCb}
|
||||
/>
|
||||
)}
|
||||
|
||||
{components?.default?.promoteRc?.omit !== true && (
|
||||
<PromoteRc
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
setRefetch={setRefetch}
|
||||
successCb={components?.default?.promoteRc?.successCb}
|
||||
/>
|
||||
)}
|
||||
{components?.default?.createRc?.omit !== true && (
|
||||
<CreateRc
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
releaseBranch={gitHubBatchInfo.value.releaseBranch}
|
||||
defaultBranch={gitHubBatchInfo.value.repository.defaultBranch}
|
||||
successCb={components?.default?.createRc?.successCb}
|
||||
/>
|
||||
)}
|
||||
|
||||
{components?.default?.patch?.omit !== true && (
|
||||
<Patch
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
releaseBranch={gitHubBatchInfo.value.releaseBranch}
|
||||
setRefetch={setRefetch}
|
||||
successCb={components?.default?.patch?.successCb}
|
||||
/>
|
||||
)}
|
||||
</ErrorBoundary>
|
||||
{components?.default?.promoteRc?.omit !== true && (
|
||||
<PromoteRc
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
successCb={components?.default?.promoteRc?.successCb}
|
||||
/>
|
||||
)}
|
||||
|
||||
{components?.default?.patch?.omit !== true && (
|
||||
<Patch
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
releaseBranch={gitHubBatchInfo.value.releaseBranch}
|
||||
successCb={components?.default?.patch?.successCb}
|
||||
/>
|
||||
)}
|
||||
</ErrorBoundary>
|
||||
</RefetchContext.Provider>
|
||||
</ProjectContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface IPluginApiClient {
|
||||
|
||||
getRepoPath: (args: PartialProject) => string;
|
||||
|
||||
getOrganizations: () => Promise<{ organizations: string[] }>;
|
||||
getOwners: () => Promise<{ owners: string[] }>;
|
||||
|
||||
getRepositories: (args: {
|
||||
owner: string;
|
||||
@@ -50,8 +50,8 @@ export interface IPluginApiClient {
|
||||
|
||||
getRecentCommits: (
|
||||
args: { releaseBranchName?: string } & PartialProject,
|
||||
) => Promise<{
|
||||
recentCommits: {
|
||||
) => Promise<
|
||||
{
|
||||
htmlUrl: string;
|
||||
sha: string;
|
||||
author: {
|
||||
@@ -62,8 +62,8 @@ export interface IPluginApiClient {
|
||||
message: string;
|
||||
};
|
||||
firstParentSha?: string;
|
||||
}[];
|
||||
}>;
|
||||
}[]
|
||||
>;
|
||||
|
||||
getLatestRelease: (
|
||||
args: PartialProject,
|
||||
@@ -154,7 +154,7 @@ export interface IPluginApiClient {
|
||||
tagParts: SemverTagParts | CalverTagParts;
|
||||
releaseBranchTree: string;
|
||||
selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
>;
|
||||
} & PartialProject,
|
||||
) => Promise<{
|
||||
@@ -191,7 +191,7 @@ export interface IPluginApiClient {
|
||||
args: {
|
||||
bumpedTag: string;
|
||||
selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
>;
|
||||
mergeTree: string;
|
||||
releaseBranchSha: string;
|
||||
@@ -247,7 +247,7 @@ export interface IPluginApiClient {
|
||||
>;
|
||||
tagParts: SemverTagParts | CalverTagParts;
|
||||
selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
>;
|
||||
} & PartialProject,
|
||||
) => Promise<{
|
||||
@@ -273,6 +273,12 @@ export interface IPluginApiClient {
|
||||
};
|
||||
}
|
||||
|
||||
const DISABLE_CACHE = {
|
||||
headers: {
|
||||
'If-None-Match': '',
|
||||
},
|
||||
};
|
||||
|
||||
export class PluginApiClient implements IPluginApiClient {
|
||||
private readonly githubAuthApi: OAuthApi;
|
||||
private readonly baseUrl: string;
|
||||
@@ -329,7 +335,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
return `${owner}/${repo}`;
|
||||
}
|
||||
|
||||
async getOrganizations() {
|
||||
async getOwners() {
|
||||
const { octokit } = await this.getOctokit();
|
||||
const orgListResponse = await octokit.paginate(
|
||||
octokit.orgs.listForAuthenticatedUser,
|
||||
@@ -337,7 +343,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
);
|
||||
|
||||
return {
|
||||
organizations: orgListResponse.map(organization => organization.login),
|
||||
owners: orgListResponse.map(organization => organization.login),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -385,22 +391,21 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
owner,
|
||||
repo,
|
||||
...(releaseBranchName ? { sha: releaseBranchName } : {}),
|
||||
...DISABLE_CACHE,
|
||||
});
|
||||
|
||||
return {
|
||||
recentCommits: recentCommitsResponse.data.map(commit => ({
|
||||
htmlUrl: commit.html_url,
|
||||
sha: commit.sha,
|
||||
author: {
|
||||
htmlUrl: commit.author?.html_url,
|
||||
login: commit.author?.login,
|
||||
},
|
||||
commit: {
|
||||
message: commit.commit.message,
|
||||
},
|
||||
firstParentSha: commit.parents?.[0].sha,
|
||||
})),
|
||||
};
|
||||
return recentCommitsResponse.data.map(commit => ({
|
||||
htmlUrl: commit.html_url,
|
||||
sha: commit.sha,
|
||||
author: {
|
||||
htmlUrl: commit.author?.html_url,
|
||||
login: commit.author?.login,
|
||||
},
|
||||
commit: {
|
||||
message: commit.commit.message,
|
||||
},
|
||||
firstParentSha: commit.parents?.[0]?.sha,
|
||||
}));
|
||||
}
|
||||
|
||||
async getLatestRelease({ owner, repo }: PartialProject) {
|
||||
@@ -409,6 +414,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
owner,
|
||||
repo,
|
||||
per_page: 1,
|
||||
...DISABLE_CACHE,
|
||||
});
|
||||
|
||||
if (latestReleases.length === 0) {
|
||||
@@ -433,10 +439,10 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
|
||||
async getRepository({ owner, repo }: PartialProject) {
|
||||
const { octokit } = await this.getOctokit();
|
||||
|
||||
const { data: repository } = await octokit.repos.get({
|
||||
owner,
|
||||
repo,
|
||||
...DISABLE_CACHE,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -458,6 +464,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
owner,
|
||||
repo,
|
||||
ref: defaultBranch,
|
||||
...DISABLE_CACHE,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -480,6 +487,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
owner,
|
||||
repo,
|
||||
branch: branchName,
|
||||
...DISABLE_CACHE,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -585,7 +593,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
tagParts: SemverTagParts | CalverTagParts;
|
||||
releaseBranchTree: string;
|
||||
selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
>;
|
||||
} & PartialProject) => {
|
||||
const { octokit } = await this.getOctokit();
|
||||
@@ -659,7 +667,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
}: {
|
||||
bumpedTag: string;
|
||||
selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
>;
|
||||
mergeTree: string;
|
||||
releaseBranchSha: string;
|
||||
@@ -773,7 +781,7 @@ export class PluginApiClient implements IPluginApiClient {
|
||||
>;
|
||||
tagParts: SemverTagParts | CalverTagParts;
|
||||
selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
>;
|
||||
} & PartialProject) => {
|
||||
const { octokit } = await this.getOctokit();
|
||||
|
||||
@@ -47,7 +47,6 @@ describe('CreateRc', () => {
|
||||
<CreateRc
|
||||
defaultBranch="mockDefaultBranch"
|
||||
latestRelease={mockRcRelease}
|
||||
setRefetch={jest.fn()}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
/>,
|
||||
);
|
||||
@@ -62,7 +61,6 @@ describe('CreateRc', () => {
|
||||
<CreateRc
|
||||
defaultBranch="mockDefaultBranch"
|
||||
latestRelease={mockReleaseVersion}
|
||||
setRefetch={jest.fn()}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ import { createRc } from './sideEffects/createRc';
|
||||
import { Differ } from '../../components/Differ';
|
||||
import { getRcGitHubInfo } from './getRcGitHubInfo';
|
||||
import { InfoCardPlus } from '../../components/InfoCardPlus';
|
||||
import { ComponentConfigCreateRc, SetRefetch } from '../../types/types';
|
||||
import { ComponentConfigCreateRc } from '../../types/types';
|
||||
import { ResponseStepList } from '../../components/ResponseStepList/ResponseStepList';
|
||||
import { SEMVER_PARTS } from '../../constants/constants';
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
@@ -47,7 +47,6 @@ interface CreateRcProps {
|
||||
IPluginApiClient['getLatestRelease']
|
||||
>['latestRelease'];
|
||||
releaseBranch: ApiMethodRetval<IPluginApiClient['getBranch']> | null;
|
||||
setRefetch: SetRefetch;
|
||||
successCb?: ComponentConfigCreateRc['successCb'];
|
||||
}
|
||||
|
||||
@@ -55,7 +54,6 @@ export const CreateRc = ({
|
||||
defaultBranch,
|
||||
latestRelease,
|
||||
releaseBranch,
|
||||
setRefetch,
|
||||
successCb,
|
||||
}: CreateRcProps) => {
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
@@ -149,7 +147,6 @@ export const CreateRc = ({
|
||||
responseSteps={createGitHubReleaseResponse.value}
|
||||
loading={createGitHubReleaseResponse.loading}
|
||||
title="Create RC result"
|
||||
setRefetch={setRefetch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,11 +32,7 @@ import { Patch } from './Patch';
|
||||
describe('Patch', () => {
|
||||
it('should return early if no latestRelease exists', () => {
|
||||
const { getByTestId } = render(
|
||||
<Patch
|
||||
latestRelease={null}
|
||||
setRefetch={jest.fn()}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
/>,
|
||||
<Patch latestRelease={null} releaseBranch={mockReleaseBranch} />,
|
||||
);
|
||||
|
||||
expect(
|
||||
|
||||
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { Typography } from '@material-ui/core';
|
||||
|
||||
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
|
||||
import { ComponentConfigPatch, SetRefetch } from '../../types/types';
|
||||
import { ComponentConfigPatch } from '../../types/types';
|
||||
import { getBumpedTag } from '../../helpers/getBumpedTag';
|
||||
import { InfoCardPlus } from '../../components/InfoCardPlus';
|
||||
import { NoLatestRelease } from '../../components/NoLatestRelease';
|
||||
@@ -31,14 +31,12 @@ interface PatchProps {
|
||||
IPluginApiClient['getLatestRelease']
|
||||
>['latestRelease'];
|
||||
releaseBranch: ApiMethodRetval<IPluginApiClient['getBranch']> | null;
|
||||
setRefetch: SetRefetch;
|
||||
successCb?: ComponentConfigPatch['successCb'];
|
||||
}
|
||||
|
||||
export const Patch = ({
|
||||
latestRelease,
|
||||
releaseBranch,
|
||||
setRefetch,
|
||||
successCb,
|
||||
}: PatchProps) => {
|
||||
const project = useProjectContext();
|
||||
@@ -64,7 +62,6 @@ export const Patch = ({
|
||||
bumpedTag={bumpedTag}
|
||||
latestRelease={latestRelease}
|
||||
releaseBranch={releaseBranch}
|
||||
setRefetch={setRefetch}
|
||||
successCb={successCb}
|
||||
tagParts={tagParts}
|
||||
/>
|
||||
|
||||
@@ -50,7 +50,6 @@ describe('PatchBody', () => {
|
||||
bumpedTag={mockBumpedTag}
|
||||
latestRelease={mockRcRelease}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
setRefetch={jest.fn()}
|
||||
tagParts={mockTagParts}
|
||||
/>,
|
||||
);
|
||||
@@ -66,7 +65,6 @@ describe('PatchBody', () => {
|
||||
const { getByTestId } = render(
|
||||
<PatchBody
|
||||
latestRelease={mockReleaseVersion}
|
||||
setRefetch={jest.fn()}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
bumpedTag={mockBumpedTag}
|
||||
tagParts={mockTagParts}
|
||||
|
||||
@@ -35,7 +35,7 @@ import OpenInNewIcon from '@material-ui/icons/OpenInNew';
|
||||
|
||||
import { CalverTagParts } from '../../helpers/tagParts/getCalverTagParts';
|
||||
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
|
||||
import { ComponentConfigPatch, SetRefetch } from '../../types/types';
|
||||
import { ComponentConfigPatch } from '../../types/types';
|
||||
import { Differ } from '../../components/Differ';
|
||||
import { patch } from './sideEffects/patch';
|
||||
import { ResponseStepList } from '../../components/ResponseStepList/ResponseStepList';
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
IPluginApiClient,
|
||||
UnboxArray,
|
||||
} from '../../api/PluginApiClient';
|
||||
import { GitHubReleaseManagerError } from '../../errors/GitHubReleaseManagerError';
|
||||
|
||||
interface PatchBodyProps {
|
||||
bumpedTag: string;
|
||||
@@ -56,7 +57,6 @@ interface PatchBodyProps {
|
||||
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
|
||||
>;
|
||||
releaseBranch: ApiMethodRetval<IPluginApiClient['getBranch']>;
|
||||
setRefetch: SetRefetch;
|
||||
successCb?: ComponentConfigPatch['successCb'];
|
||||
tagParts: NonNullable<CalverTagParts | SemverTagParts>;
|
||||
}
|
||||
@@ -65,7 +65,6 @@ export const PatchBody = ({
|
||||
bumpedTag,
|
||||
latestRelease,
|
||||
releaseBranch,
|
||||
setRefetch,
|
||||
successCb,
|
||||
tagParts,
|
||||
}: PatchBodyProps) => {
|
||||
@@ -75,15 +74,19 @@ export const PatchBody = ({
|
||||
|
||||
const githubDataResponse = useAsync(async () => {
|
||||
const [
|
||||
{ recentCommits: recentCommitsOnDefaultBranch },
|
||||
] = await Promise.all([pluginApiClient.getRecentCommits({ ...project })]);
|
||||
|
||||
const {
|
||||
recentCommits: recentCommitsOnReleaseBranch,
|
||||
} = await pluginApiClient.getRecentCommits({
|
||||
...project,
|
||||
releaseBranchName: releaseBranch.name,
|
||||
});
|
||||
recentCommitsOnDefaultBranch,
|
||||
recentCommitsOnReleaseBranch,
|
||||
] = await Promise.all([
|
||||
pluginApiClient.getRecentCommits({
|
||||
owner: project.owner,
|
||||
repo: project.repo,
|
||||
}),
|
||||
pluginApiClient.getRecentCommits({
|
||||
owner: project.owner,
|
||||
repo: project.repo,
|
||||
releaseBranchName: releaseBranch.name,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
recentCommitsOnReleaseBranch,
|
||||
@@ -93,7 +96,7 @@ export const PatchBody = ({
|
||||
|
||||
const [patchReleaseResponse, patchReleaseFn] = useAsyncFn(async (...args) => {
|
||||
const selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
> = args[0];
|
||||
const patchResponseSteps = await patch({
|
||||
project,
|
||||
@@ -111,13 +114,15 @@ export const PatchBody = ({
|
||||
if (githubDataResponse.error) {
|
||||
return (
|
||||
<Alert data-testid={TEST_IDS.patch.error} severity="error">
|
||||
{githubDataResponse.error.message}
|
||||
Unexpected error: {githubDataResponse.error.message}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (patchReleaseResponse.error) {
|
||||
return <Alert severity="error">{patchReleaseResponse.error.message}</Alert>;
|
||||
}
|
||||
|
||||
if (githubDataResponse.loading) {
|
||||
return <CenteredCircularProgress data-testid={TEST_IDS.patch.loading} />;
|
||||
}
|
||||
@@ -156,9 +161,11 @@ export const PatchBody = ({
|
||||
<List>
|
||||
{githubDataResponse.value.recentCommitsOnDefaultBranch.map(
|
||||
(commit, index) => {
|
||||
// FIXME: Performance improvement opportunity: Convert to object lookup
|
||||
const commitExistsOnReleaseBranch = !!githubDataResponse.value?.recentCommitsOnReleaseBranch.find(
|
||||
releaseBranchCommit => releaseBranchCommit.sha === commit.sha,
|
||||
);
|
||||
const hasNoParent = !commit.firstParentSha;
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative' }} key={`commit-${index}`}>
|
||||
@@ -190,7 +197,8 @@ export const PatchBody = ({
|
||||
patchReleaseResponse.loading ||
|
||||
(patchReleaseResponse.value &&
|
||||
patchReleaseResponse.value.length > 0) ||
|
||||
commitExistsOnReleaseBranch
|
||||
commitExistsOnReleaseBranch ||
|
||||
hasNoParent
|
||||
}
|
||||
role={undefined}
|
||||
dense
|
||||
@@ -264,36 +272,28 @@ export const PatchBody = ({
|
||||
responseSteps={patchReleaseResponse.value}
|
||||
loading={patchReleaseResponse.loading}
|
||||
title="Patch result"
|
||||
setRefetch={setRefetch}
|
||||
closeable
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!githubDataResponse.value?.recentCommitsOnDefaultBranch[
|
||||
const selectedPatchCommit =
|
||||
githubDataResponse.value?.recentCommitsOnDefaultBranch[
|
||||
checkedCommitIndex
|
||||
]
|
||||
) {
|
||||
return (
|
||||
<Button disabled variant="contained" color="primary">
|
||||
Patch Release Candidate
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
];
|
||||
return (
|
||||
<Button
|
||||
disabled={checkedCommitIndex === -1}
|
||||
disabled={checkedCommitIndex === -1 || !selectedPatchCommit}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
// FIXME: Optional chaining shouldn't be needed here due to the if-statement above
|
||||
patchReleaseFn(
|
||||
githubDataResponse.value?.recentCommitsOnDefaultBranch[
|
||||
checkedCommitIndex
|
||||
],
|
||||
);
|
||||
if (!selectedPatchCommit) {
|
||||
throw new GitHubReleaseManagerError(
|
||||
'Could not find selected patch commit',
|
||||
);
|
||||
}
|
||||
|
||||
patchReleaseFn(selectedPatchCommit);
|
||||
}}
|
||||
>
|
||||
Patch Release Candidate
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import { ComponentConfigPatch, ResponseStep } from '../../../types/types';
|
||||
import { CalverTagParts } from '../../../helpers/tagParts/getCalverTagParts';
|
||||
import { GitHubReleaseManagerError } from '../../../errors/GitHubReleaseManagerError';
|
||||
import {
|
||||
ApiMethodRetval,
|
||||
IPluginApiClient,
|
||||
@@ -33,13 +32,13 @@ interface Patch {
|
||||
pluginApiClient: IPluginApiClient;
|
||||
project: Project;
|
||||
selectedPatchCommit: UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>
|
||||
>;
|
||||
successCb?: ComponentConfigPatch['successCb'];
|
||||
tagParts: NonNullable<CalverTagParts | SemverTagParts>;
|
||||
}
|
||||
|
||||
// Inspo: https://stackoverflow.com/questions/53859199/how-to-cherry-pick-through-githubs-api
|
||||
// Inspiration: https://stackoverflow.com/questions/53859199/how-to-cherry-pick-through-githubs-api
|
||||
export async function patch({
|
||||
bumpedTag,
|
||||
latestRelease,
|
||||
@@ -51,9 +50,6 @@ export async function patch({
|
||||
}: Patch) {
|
||||
const responseSteps: ResponseStep[] = [];
|
||||
|
||||
if (!selectedPatchCommit || !selectedPatchCommit.sha) {
|
||||
throw new GitHubReleaseManagerError('Invalid commit');
|
||||
}
|
||||
const releaseBranchName = latestRelease.targetCommitish;
|
||||
/**
|
||||
* 1. Here is the branch we want to cherry-pick to:
|
||||
|
||||
@@ -15,71 +15,101 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useAsync } from 'react-use';
|
||||
import { ControllerRenderProps } from 'react-hook-form';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { FormControl, InputLabel, MenuItem, Select } from '@material-ui/core';
|
||||
import {
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
|
||||
import { useFormClasses } from './styles';
|
||||
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { getNewQueryParams } from '../../helpers/getNewQueryParams';
|
||||
import { useQuery } from '../../helpers/useQuery';
|
||||
|
||||
export function Owner({
|
||||
controllerRenderProps,
|
||||
username,
|
||||
project,
|
||||
}: {
|
||||
controllerRenderProps: ControllerRenderProps;
|
||||
username: string;
|
||||
project: Project;
|
||||
}) {
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
const formClasses = useFormClasses();
|
||||
const project: Project = controllerRenderProps.value;
|
||||
const navigate = useNavigate();
|
||||
const query = useQuery();
|
||||
|
||||
const { loading, error, value } = useAsync(() =>
|
||||
pluginApiClient.getOrganizations(),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <CenteredCircularProgress />;
|
||||
}
|
||||
|
||||
if (!value?.organizations) {
|
||||
return <Alert severity="error">Could not fetch organizations</Alert>;
|
||||
}
|
||||
const { loading, error, value } = useAsync(() => pluginApiClient.getOwners());
|
||||
const owners = value?.owners ?? [];
|
||||
const customOwnerFromUrl = !owners
|
||||
.concat(['', username])
|
||||
.includes(project.owner);
|
||||
|
||||
return (
|
||||
<FormControl className={formClasses.formControl}>
|
||||
<InputLabel id="owner-select-label">Organizations</InputLabel>
|
||||
<Select
|
||||
labelId="owner-select-label"
|
||||
id="owner-select"
|
||||
value={project.owner}
|
||||
onChange={event => {
|
||||
controllerRenderProps.onChange({
|
||||
...project,
|
||||
owner: event.target.value,
|
||||
repo: '',
|
||||
} as Project);
|
||||
}}
|
||||
className={formClasses.selectEmpty}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value={username}>
|
||||
<strong>{username}</strong>
|
||||
</MenuItem>
|
||||
{value.organizations.map((orgName, index) => (
|
||||
<MenuItem key={`organization-${index}`} value={orgName}>
|
||||
{orgName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormControl className={formClasses.formControl} required error={!!error}>
|
||||
{loading ? (
|
||||
<CenteredCircularProgress />
|
||||
) : (
|
||||
<>
|
||||
<InputLabel id="owner-select-label">Owners</InputLabel>
|
||||
<Select
|
||||
labelId="owner-select-label"
|
||||
id="owner-select"
|
||||
value={project.owner}
|
||||
defaultValue=""
|
||||
onChange={event => {
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
key: 'owner',
|
||||
value: event.target.value as string,
|
||||
});
|
||||
navigate(`?${queryParams}`, { replace: true });
|
||||
}}
|
||||
className={formClasses.selectEmpty}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem value={username}>
|
||||
<strong>{username}</strong>
|
||||
</MenuItem>
|
||||
|
||||
{!error && customOwnerFromUrl && (
|
||||
<MenuItem value={project.owner}>
|
||||
<strong>From URL: {project.owner}</strong>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{owners.map((orgName, index) => (
|
||||
<MenuItem key={`organization-${index}`} value={orgName}>
|
||||
{orgName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{error && (
|
||||
<FormHelperText>
|
||||
Encountered an error ({error.message})
|
||||
</FormHelperText>
|
||||
)}
|
||||
|
||||
{!error && project.owner.length === 0 && (
|
||||
<>
|
||||
<FormHelperText>Select an owner (org or user)</FormHelperText>
|
||||
<FormHelperText>
|
||||
Custom queries can be made via the query param{' '}
|
||||
<strong>owner</strong>
|
||||
</FormHelperText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,69 +16,92 @@
|
||||
|
||||
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 } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormHelperText,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
|
||||
import { useFormClasses } from './styles';
|
||||
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { getNewQueryParams } from '../../helpers/getNewQueryParams';
|
||||
import { useQuery } from '../../helpers/useQuery';
|
||||
|
||||
export function Repo({
|
||||
controllerRenderProps,
|
||||
}: {
|
||||
controllerRenderProps: ControllerRenderProps;
|
||||
}) {
|
||||
export function Repo({ project }: { project: Project }) {
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
const navigate = useNavigate();
|
||||
const formClasses = useFormClasses();
|
||||
const project: Project = controllerRenderProps.value;
|
||||
const query = useQuery();
|
||||
|
||||
const { loading, error, value } = useAsync(
|
||||
async () => pluginApiClient.getRepositories({ owner: project.owner }),
|
||||
[project.owner],
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <CenteredCircularProgress />;
|
||||
}
|
||||
|
||||
if (!value?.repositories) {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Could not fetch repositories for "{project.owner}"
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
const repositories = value?.repositories ?? [];
|
||||
const customRepoFromUrl = !repositories.concat(['']).includes(project.repo);
|
||||
|
||||
return (
|
||||
<FormControl className={formClasses.formControl}>
|
||||
<InputLabel id="repo-select-label">Repositories</InputLabel>
|
||||
<Select
|
||||
labelId="repo-select-label"
|
||||
id="repo-select"
|
||||
value={project.repo}
|
||||
onChange={event => {
|
||||
controllerRenderProps.onChange({
|
||||
...project,
|
||||
repo: event.target.value,
|
||||
} as Project);
|
||||
}}
|
||||
className={formClasses.selectEmpty}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
{value.repositories.map((repositoryName, index) => (
|
||||
<MenuItem key={`repository-${index}`} value={repositoryName}>
|
||||
{repositoryName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormControl className={formClasses.formControl} required error={!!error}>
|
||||
{loading ? (
|
||||
<CenteredCircularProgress />
|
||||
) : (
|
||||
<>
|
||||
<InputLabel id="repo-select-label">Repositories</InputLabel>
|
||||
<Select
|
||||
labelId="repo-select-label"
|
||||
id="repo-select"
|
||||
value={project.repo}
|
||||
defaultValue=""
|
||||
onChange={event => {
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
key: 'repo',
|
||||
value: event.target.value as string,
|
||||
});
|
||||
navigate(`?${queryParams}`, { replace: true });
|
||||
}}
|
||||
className={formClasses.selectEmpty}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
|
||||
{!error && customRepoFromUrl && (
|
||||
<MenuItem value={project.repo}>
|
||||
<strong>From URL: {project.repo}</strong>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{repositories.map((repositoryName, index) => (
|
||||
<MenuItem key={`repository-${index}`} value={repositoryName}>
|
||||
{repositoryName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{error && (
|
||||
<FormHelperText>
|
||||
Encountered an error ({error.message}")
|
||||
</FormHelperText>
|
||||
)}
|
||||
|
||||
{!error && project.repo.length === 0 && (
|
||||
<>
|
||||
<FormHelperText>Select a repository</FormHelperText>
|
||||
<FormHelperText>
|
||||
Custom queries can be made via the query param{' '}
|
||||
<strong>repo</strong>
|
||||
</FormHelperText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,49 +15,26 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
|
||||
import { VersioningStrategy } from './VersioningStrategy';
|
||||
import { Owner } from './Owner';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { Repo } from './Repo';
|
||||
import { VersioningStrategy } from './VersioningStrategy';
|
||||
|
||||
export function RepoDetailsForm({
|
||||
control,
|
||||
username,
|
||||
project,
|
||||
}: {
|
||||
control: ReturnType<typeof useForm>['control'];
|
||||
username: string;
|
||||
project: Project;
|
||||
}) {
|
||||
return (
|
||||
<Controller
|
||||
render={controllerRenderProps => {
|
||||
const project: Project = controllerRenderProps.value;
|
||||
<>
|
||||
<VersioningStrategy project={project} />
|
||||
|
||||
return (
|
||||
<>
|
||||
<VersioningStrategy controllerRenderProps={controllerRenderProps} />
|
||||
<Owner project={project} username={username} />
|
||||
|
||||
<Owner
|
||||
controllerRenderProps={controllerRenderProps}
|
||||
username={username}
|
||||
/>
|
||||
|
||||
{project.owner.length > 0 && (
|
||||
<Repo controllerRenderProps={controllerRenderProps} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
control={control}
|
||||
name="repo-details-form"
|
||||
defaultValue={
|
||||
{
|
||||
owner: '',
|
||||
repo: '',
|
||||
versioningStrategy: 'semver',
|
||||
} as Project
|
||||
}
|
||||
/>
|
||||
{project.owner.length > 0 && <Repo project={project} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
@@ -21,29 +23,48 @@ import {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { ControllerRenderProps } from 'react-hook-form';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
|
||||
export function VersioningStrategy({
|
||||
controllerRenderProps,
|
||||
}: {
|
||||
controllerRenderProps: ControllerRenderProps;
|
||||
}) {
|
||||
const project: Project = controllerRenderProps.value;
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { useQuery } from '../../helpers/useQuery';
|
||||
import {
|
||||
getNewQueryParams,
|
||||
getParsedQuery,
|
||||
} from '../../helpers/getNewQueryParams';
|
||||
|
||||
export function VersioningStrategy({ project }: { project: Project }) {
|
||||
const navigate = useNavigate();
|
||||
const query = useQuery();
|
||||
|
||||
useEffect(() => {
|
||||
const parsedQuery = getParsedQuery({ query });
|
||||
|
||||
if (!parsedQuery.versioningStrategy) {
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
key: 'versioningStrategy',
|
||||
value: project.versioningStrategy,
|
||||
});
|
||||
|
||||
navigate(`?${queryParams}`, { replace: true });
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormControl component="fieldset" required>
|
||||
<FormLabel component="legend">Calendar strategy</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="calendar-strategy"
|
||||
name="calendar-strategy"
|
||||
value={project.versioningStrategy}
|
||||
defaultValue="semver"
|
||||
onChange={event => {
|
||||
controllerRenderProps.onChange({
|
||||
...project,
|
||||
versioningStrategy: event.target.value,
|
||||
} as Project);
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
key: 'versioningStrategy',
|
||||
value: event.target.value,
|
||||
});
|
||||
|
||||
navigate(`?${queryParams}`, { replace: true });
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
|
||||
@@ -33,9 +33,7 @@ import { PromoteRc } from './PromoteRc';
|
||||
|
||||
describe('PromoteRc', () => {
|
||||
it('return early if no latest release present', () => {
|
||||
const { getByTestId } = render(
|
||||
<PromoteRc latestRelease={null} setRefetch={jest.fn()} />,
|
||||
);
|
||||
const { getByTestId } = render(<PromoteRc latestRelease={null} />);
|
||||
|
||||
expect(
|
||||
getByTestId(TEST_IDS.components.noLatestRelease),
|
||||
@@ -44,16 +42,14 @@ describe('PromoteRc', () => {
|
||||
|
||||
it('should display not-rc warning', () => {
|
||||
const { getByTestId } = render(
|
||||
<PromoteRc latestRelease={mockReleaseVersion} setRefetch={jest.fn()} />,
|
||||
<PromoteRc latestRelease={mockReleaseVersion} />,
|
||||
);
|
||||
|
||||
expect(getByTestId(TEST_IDS.promoteRc.notRcWarning)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display PromoteRcBody', () => {
|
||||
const { getByTestId } = render(
|
||||
<PromoteRc latestRelease={mockRcRelease} setRefetch={jest.fn()} />,
|
||||
);
|
||||
const { getByTestId } = render(<PromoteRc latestRelease={mockRcRelease} />);
|
||||
|
||||
expect(
|
||||
getByTestId(TEST_IDS.promoteRc.mockedPromoteRcBody),
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Typography } from '@material-ui/core';
|
||||
|
||||
import { InfoCardPlus } from '../../components/InfoCardPlus';
|
||||
import { NoLatestRelease } from '../../components/NoLatestRelease';
|
||||
import { ComponentConfigPromoteRc, SetRefetch } from '../../types/types';
|
||||
import { ComponentConfigPromoteRc } from '../../types/types';
|
||||
import { PromoteRcBody } from './PromoteRcBody';
|
||||
import { useStyles } from '../../styles/styles';
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
@@ -30,15 +30,10 @@ interface PromoteRcProps {
|
||||
latestRelease: ApiMethodRetval<
|
||||
IPluginApiClient['getLatestRelease']
|
||||
>['latestRelease'];
|
||||
setRefetch: SetRefetch;
|
||||
successCb?: ComponentConfigPromoteRc['successCb'];
|
||||
}
|
||||
|
||||
export const PromoteRc = ({
|
||||
latestRelease,
|
||||
setRefetch,
|
||||
successCb,
|
||||
}: PromoteRcProps) => {
|
||||
export const PromoteRc = ({ latestRelease, successCb }: PromoteRcProps) => {
|
||||
const classes = useStyles();
|
||||
|
||||
function Body() {
|
||||
@@ -61,13 +56,7 @@ export const PromoteRc = ({
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PromoteRcBody
|
||||
rcRelease={latestRelease}
|
||||
setRefetch={setRefetch}
|
||||
successCb={successCb}
|
||||
/>
|
||||
);
|
||||
return <PromoteRcBody rcRelease={latestRelease} successCb={successCb} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,9 +35,7 @@ import { PromoteRcBody } from './PromoteRcBody';
|
||||
|
||||
describe('PromoteRcBody', () => {
|
||||
it('should display CTA', () => {
|
||||
const { getByTestId } = render(
|
||||
<PromoteRcBody rcRelease={mockRcRelease} setRefetch={jest.fn()} />,
|
||||
);
|
||||
const { getByTestId } = render(<PromoteRcBody rcRelease={mockRcRelease} />);
|
||||
|
||||
expect(getByTestId(TEST_IDS.promoteRc.cta)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Alert } from '@material-ui/lab';
|
||||
import { Button, Typography } from '@material-ui/core';
|
||||
|
||||
import { Differ } from '../../components/Differ';
|
||||
import { ComponentConfigPromoteRc, SetRefetch } from '../../types/types';
|
||||
import { ComponentConfigPromoteRc } from '../../types/types';
|
||||
import { promoteRc } from './sideEffects/promoteRc';
|
||||
import { ResponseStepList } from '../../components/ResponseStepList/ResponseStepList';
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
@@ -33,15 +33,10 @@ interface PromoteRcBodyProps {
|
||||
rcRelease: NonNullable<
|
||||
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
|
||||
>;
|
||||
setRefetch: SetRefetch;
|
||||
successCb?: ComponentConfigPromoteRc['successCb'];
|
||||
}
|
||||
|
||||
export const PromoteRcBody = ({
|
||||
rcRelease,
|
||||
setRefetch,
|
||||
successCb,
|
||||
}: PromoteRcBodyProps) => {
|
||||
export const PromoteRcBody = ({ rcRelease, successCb }: PromoteRcBodyProps) => {
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
const project = useProjectContext();
|
||||
const classes = useStyles();
|
||||
@@ -82,7 +77,6 @@ export const PromoteRcBody = ({
|
||||
<ResponseStepList
|
||||
responseSteps={promoteGitHubRcResponse.value}
|
||||
title="Promote RC result"
|
||||
setRefetch={setRefetch}
|
||||
loading={promoteGitHubRcResponse.loading}
|
||||
/>
|
||||
);
|
||||
|
||||
+8
-12
@@ -17,17 +17,18 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
import { mockRefetch } from '../../test-helpers/test-helpers';
|
||||
import { ResponseStepList } from './ResponseStepList';
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
|
||||
jest.mock('../../contexts/RefetchContext', () => ({
|
||||
useRefetchContext: jest.fn(() => mockRefetch),
|
||||
}));
|
||||
|
||||
describe('ResponseStepList', () => {
|
||||
it('should render loading state when loading', () => {
|
||||
const { getByTestId } = render(
|
||||
<ResponseStepList
|
||||
loading
|
||||
setRefetch={jest.fn()}
|
||||
title="mock_responseStepList_title"
|
||||
/>,
|
||||
<ResponseStepList loading title="mock_responseStepList_title" />,
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -37,11 +38,7 @@ describe('ResponseStepList', () => {
|
||||
|
||||
it('should render loading state when no responseSteps', () => {
|
||||
const { getByTestId } = render(
|
||||
<ResponseStepList
|
||||
loading={false}
|
||||
setRefetch={jest.fn()}
|
||||
title="mock_responseStepList_title"
|
||||
/>,
|
||||
<ResponseStepList loading={false} title="mock_responseStepList_title" />,
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -53,7 +50,6 @@ describe('ResponseStepList', () => {
|
||||
const { getByTestId } = render(
|
||||
<ResponseStepList
|
||||
loading={false}
|
||||
setRefetch={jest.fn()}
|
||||
title="mock_responseStepList_title"
|
||||
responseSteps={[]}
|
||||
/>,
|
||||
|
||||
@@ -16,22 +16,22 @@
|
||||
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import {
|
||||
List,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
List,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { ResponseStep, SetRefetch } from '../../types/types';
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
import { ResponseStepListItem } from './ResponseStepListItem';
|
||||
import { CenteredCircularProgress } from '../CenteredCircularProgress';
|
||||
import { ResponseStep } from '../../types/types';
|
||||
import { ResponseStepListItem } from './ResponseStepListItem';
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
import { useRefetchContext } from '../../contexts/RefetchContext';
|
||||
|
||||
interface ResponseStepListProps {
|
||||
responseSteps?: ResponseStep[];
|
||||
setRefetch: SetRefetch;
|
||||
title: string;
|
||||
animationDelay?: number;
|
||||
loading: boolean;
|
||||
@@ -42,7 +42,6 @@ interface ResponseStepListProps {
|
||||
export const ResponseStepList = ({
|
||||
responseSteps,
|
||||
animationDelay,
|
||||
setRefetch,
|
||||
loading = false,
|
||||
closeable = false,
|
||||
denseList = false,
|
||||
@@ -50,6 +49,7 @@ export const ResponseStepList = ({
|
||||
children,
|
||||
}: PropsWithChildren<ResponseStepListProps>) => {
|
||||
const [open, setOpen] = React.useState(true);
|
||||
const { setRefetchTrigger } = useRefetchContext();
|
||||
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
@@ -102,7 +102,7 @@ export const ResponseStepList = ({
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => setRefetch(Date.now())}
|
||||
onClick={() => setRefetchTrigger(Date.now())}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="large"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import { GitHubReleaseManagerError } from '../errors/GitHubReleaseManagerError';
|
||||
|
||||
export interface Refetch {
|
||||
refetchTrigger: number;
|
||||
setRefetchTrigger: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const RefetchContext = createContext<Refetch | undefined>(undefined);
|
||||
|
||||
export const useRefetchContext = () => {
|
||||
const refetch = useContext(RefetchContext);
|
||||
|
||||
if (!refetch) {
|
||||
throw new GitHubReleaseManagerError('refetch not found');
|
||||
}
|
||||
|
||||
return {
|
||||
setRefetchTrigger: refetch.setRefetchTrigger,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import qs from 'qs';
|
||||
|
||||
import { Project } from '../contexts/ProjectContext';
|
||||
|
||||
export function getParsedQuery({ query }: { query: URLSearchParams }) {
|
||||
const parsedQuery: Partial<Project> = qs.parse(query.toString());
|
||||
|
||||
return parsedQuery;
|
||||
}
|
||||
|
||||
export function getNewQueryParams({
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
}: {
|
||||
query: URLSearchParams;
|
||||
key: keyof Project;
|
||||
value: string;
|
||||
}) {
|
||||
const queryParams = qs.parse(query.toString());
|
||||
queryParams[key] = value;
|
||||
|
||||
return qs.stringify(queryParams);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// import { useQuery } from './useQuery';
|
||||
|
||||
// export function useGetNewQueryParams({}) {
|
||||
// const query = useQuery();
|
||||
|
||||
// return 1;
|
||||
// }
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 { useLocation } from 'react-router';
|
||||
|
||||
export function useQuery(): URLSearchParams {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ describe('testHelpers', () => {
|
||||
"getHost": [MockFunction],
|
||||
"getLatestCommit": [MockFunction],
|
||||
"getLatestRelease": [MockFunction],
|
||||
"getOrganizations": [MockFunction],
|
||||
"getOwners": [MockFunction],
|
||||
"getRecentCommits": [MockFunction],
|
||||
"getRepoPath": [MockFunction],
|
||||
"getRepositories": [MockFunction],
|
||||
@@ -69,6 +69,7 @@ describe('testHelpers', () => {
|
||||
"tagName": "rc-2020.01.01_1",
|
||||
"targetCommitish": "rc/1.2.3",
|
||||
},
|
||||
"mockRefetch": [MockFunction],
|
||||
"mockReleaseBranch": Object {
|
||||
"commit": Object {
|
||||
"commit": Object {
|
||||
|
||||
@@ -105,11 +105,7 @@ export const mockReleaseBranch = createMockBranch();
|
||||
const createMockCommit = ({
|
||||
...rest
|
||||
}: Partial<
|
||||
NonNullable<
|
||||
UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
>
|
||||
>
|
||||
NonNullable<UnboxArray<ApiMethodRetval<IPluginApiClient['getRecentCommits']>>>
|
||||
>) =>
|
||||
({
|
||||
author: {
|
||||
@@ -123,15 +119,15 @@ const createMockCommit = ({
|
||||
firstParentSha: 'mock_first_parent_sha',
|
||||
...rest,
|
||||
} as NonNullable<
|
||||
UnboxArray<
|
||||
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
|
||||
>
|
||||
UnboxArray<ApiMethodRetval<IPluginApiClient['getRecentCommits']>>
|
||||
>);
|
||||
|
||||
export const mockSelectedPatchCommit = createMockCommit({
|
||||
sha: 'mock_sha_selected_patch_commit',
|
||||
});
|
||||
|
||||
export const mockRefetch = jest.fn();
|
||||
|
||||
/**
|
||||
* MOCK API CLIENT
|
||||
*/
|
||||
@@ -140,18 +136,18 @@ export const mockApiClient: IPluginApiClient = {
|
||||
|
||||
getRepoPath: jest.fn(() => 'erikengervall/playground'),
|
||||
|
||||
getOrganizations: jest.fn(),
|
||||
getOwners: jest.fn(),
|
||||
|
||||
getRepositories: jest.fn(),
|
||||
|
||||
getUsername: jest.fn(),
|
||||
|
||||
getRecentCommits: jest.fn().mockResolvedValue({
|
||||
recentCommits: [
|
||||
getRecentCommits: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
createMockCommit({ sha: 'mock_sha_recent_commits_1' }),
|
||||
createMockCommit({ sha: 'mock_sha_recent_commits_2' }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
|
||||
getLatestRelease: jest.fn(), // TODO:
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ interface ComponentConfig<Args = void> {
|
||||
omit?: boolean;
|
||||
}
|
||||
|
||||
export interface ComponentConfigCreateRcSuccessCbArgs {
|
||||
interface ComponentConfigCreateRcSuccessCbArgs {
|
||||
gitHubReleaseUrl: string;
|
||||
gitHubReleaseName: string | null;
|
||||
comparisonUrl: string;
|
||||
@@ -28,7 +28,7 @@ export interface ComponentConfigCreateRcSuccessCbArgs {
|
||||
}
|
||||
export type ComponentConfigCreateRc = ComponentConfig<ComponentConfigCreateRcSuccessCbArgs>;
|
||||
|
||||
export interface ComponentConfigPromoteRcSuccessCbArgs {
|
||||
interface ComponentConfigPromoteRcSuccessCbArgs {
|
||||
gitHubReleaseUrl: string;
|
||||
gitHubReleaseName: string | null;
|
||||
previousTagUrl: string;
|
||||
@@ -38,7 +38,7 @@ export interface ComponentConfigPromoteRcSuccessCbArgs {
|
||||
}
|
||||
export type ComponentConfigPromoteRc = ComponentConfig<ComponentConfigPromoteRcSuccessCbArgs>;
|
||||
|
||||
export interface ComponentConfigPatchSuccessCbArgs {
|
||||
interface ComponentConfigPatchSuccessCbArgs {
|
||||
updatedReleaseUrl: string;
|
||||
updatedReleaseName: string | null;
|
||||
previousTag: string;
|
||||
@@ -48,8 +48,6 @@ export interface ComponentConfigPatchSuccessCbArgs {
|
||||
}
|
||||
export type ComponentConfigPatch = ComponentConfig<ComponentConfigPatchSuccessCbArgs>;
|
||||
|
||||
export type SetRefetch = React.Dispatch<React.SetStateAction<number>>;
|
||||
|
||||
export interface ResponseStep {
|
||||
message: string | React.ReactNode;
|
||||
secondaryMessage?: string | React.ReactNode;
|
||||
|
||||
@@ -1679,50 +1679,6 @@
|
||||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@backstage/core@^0.7.5":
|
||||
version "0.7.5"
|
||||
resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/@backstage/core/-/core-0.7.5.tgz#27e0a7982dbab40973eefaa185d89babdd454913"
|
||||
integrity sha1-J+CnmC26tAlz7vqhhdibq91FSRM=
|
||||
dependencies:
|
||||
"@backstage/config" "^0.1.4"
|
||||
"@backstage/core-api" "^0.2.16"
|
||||
"@backstage/errors" "^0.1.1"
|
||||
"@backstage/theme" "^0.2.5"
|
||||
"@material-ui/core" "^4.11.0"
|
||||
"@material-ui/icons" "^4.9.1"
|
||||
"@material-ui/lab" "4.0.0-alpha.45"
|
||||
"@testing-library/react-hooks" "^3.4.2"
|
||||
"@types/dagre" "^0.7.44"
|
||||
"@types/prop-types" "^15.7.3"
|
||||
"@types/react" "^16.9"
|
||||
"@types/react-sparklines" "^1.7.0"
|
||||
"@types/react-text-truncate" "^0.14.0"
|
||||
classnames "^2.2.6"
|
||||
clsx "^1.1.0"
|
||||
d3-selection "^2.0.0"
|
||||
d3-shape "^2.0.0"
|
||||
d3-zoom "^2.0.0"
|
||||
dagre "^0.8.5"
|
||||
immer "^9.0.1"
|
||||
lodash "^4.17.15"
|
||||
material-table "^1.69.1"
|
||||
prop-types "^15.7.2"
|
||||
qs "^6.9.4"
|
||||
rc-progress "^3.0.0"
|
||||
react "^16.12.0"
|
||||
react-dom "^16.12.0"
|
||||
react-helmet "6.1.0"
|
||||
react-hook-form "^6.6.0"
|
||||
react-markdown "^5.0.2"
|
||||
react-router "6.0.0-beta.0"
|
||||
react-router-dom "6.0.0-beta.0"
|
||||
react-sparklines "^1.7.0"
|
||||
react-syntax-highlighter "^15.4.3"
|
||||
react-text-truncate "^0.16.0"
|
||||
react-use "^15.3.3"
|
||||
remark-gfm "^1.0.0"
|
||||
zen-observable "^0.8.15"
|
||||
|
||||
"@bcoe/v8-coverage@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
@@ -21631,6 +21587,13 @@ qs@6.7.0:
|
||||
resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
qs@^6.10.1:
|
||||
version "6.10.1"
|
||||
resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
|
||||
integrity sha1-STFIL6jWR6Wqt5nFJx0hM7mB+2o=
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
qs@^6.5.2, qs@^6.6.0, qs@^6.7.0, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6:
|
||||
version "6.9.6"
|
||||
resolved "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee"
|
||||
@@ -23680,6 +23643,15 @@ side-channel@^1.0.2:
|
||||
es-abstract "^1.17.0-next.1"
|
||||
object-inspect "^1.7.0"
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
integrity sha1-785cj9wQTudRslxY1CkAEfpeos8=
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
sigmund@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
|
||||
|
||||
Reference in New Issue
Block a user