diff --git a/plugins/github-release-manager/src/GitHubReleaseManager.tsx b/plugins/github-release-manager/src/GitHubReleaseManager.tsx index e0b8df26cd..3d0ea73a37 100644 --- a/plugins/github-release-manager/src/GitHubReleaseManager.tsx +++ b/plugins/github-release-manager/src/GitHubReleaseManager.tsx @@ -32,18 +32,17 @@ import { import { CenteredCircularProgress } from './components/CenteredCircularProgress'; import { CreateRc } from './cards/createRc/CreateRc'; import { getGitHubBatchInfo } from './sideEffects/getGitHubBatchInfo'; -import { getParsedQuery } from './helpers/getNewQueryParams'; import { githubReleaseManagerApiRef } from './api/serviceApiRef'; import { Info } from './cards/info/Info'; import { InfoCardPlus } from './components/InfoCardPlus'; -import { isProjectValid } from './cards/projectForm/isProjectValid'; +import { isProjectValid } from './helpers/isProjectValid'; import { Patch } from './cards/patchRc/Patch'; import { ProjectContext, Project } from './contexts/ProjectContext'; import { PromoteRc } from './cards/promoteRc/PromoteRc'; import { RefetchContext } from './contexts/RefetchContext'; import { RepoDetailsForm } from './cards/projectForm/RepoDetailsForm'; -import { useQuery } from './helpers/useQuery'; import { useVersioningStrategyMatchesRepoTags } from './helpers/useVersioningStrategyMatchesRepoTags'; +import { useQueryHandler } from './helpers/useQueryHandler'; interface GitHubReleaseManagerProps { components?: { @@ -66,13 +65,15 @@ export function GitHubReleaseManager({ }: GitHubReleaseManagerProps) { const pluginApiClient = useApi(githubReleaseManagerApiRef); const classes = useStyles(); - const query = useQuery(); - const parsedQuery = getParsedQuery({ query }); + + const { getParsedQuery } = useQueryHandler(); + const { parsedQuery } = getParsedQuery(); const project: Project = { owner: parsedQuery.owner ?? '', repo: parsedQuery.repo ?? '', versioningStrategy: parsedQuery.versioningStrategy ?? 'semver', }; + const usernameResponse = useAsync(() => pluginApiClient.getUsername({ owner: project.owner, repo: project.repo }), ); @@ -157,19 +158,23 @@ function Cards({ ); } - if (!versioningStrategyMatches) { - return ( - - Versioning mismatch, expected {project.versioningStrategy} version, got{' '} - {gitHubBatchInfo.value.latestRelease?.tagName} - - ); - } - return ( + {gitHubBatchInfo.value.latestRelease && !versioningStrategyMatches && ( + + Versioning mismatch, expected {project.versioningStrategy}{' '} + version, got "{gitHubBatchInfo.value.latestRelease.tagName}" + + )} + + {!gitHubBatchInfo.value.latestRelease && ( + + This repository has not releases yet + + )} + pluginApiClient.getOwners()); const owners = value?.owners ?? []; @@ -63,8 +62,7 @@ export function Owner({ value={project.owner} defaultValue="" onChange={event => { - const queryParams = getNewQueryParams({ - query, + const { queryParams } = getQueryParamsWithUpdates({ updates: [ { key: 'repo', value: '' }, { key: 'owner', value: event.target.value as string }, diff --git a/plugins/github-release-manager/src/cards/projectForm/Repo.tsx b/plugins/github-release-manager/src/cards/projectForm/Repo.tsx index 2e31649d83..a409918ade 100644 --- a/plugins/github-release-manager/src/cards/projectForm/Repo.tsx +++ b/plugins/github-release-manager/src/cards/projectForm/Repo.tsx @@ -29,14 +29,13 @@ 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'; +import { useQueryHandler } from '../../helpers/useQueryHandler'; export function Repo({ project }: { project: Project }) { const pluginApiClient = usePluginApiClientContext(); const navigate = useNavigate(); const formClasses = useFormClasses(); - const query = useQuery(); + const { getQueryParamsWithUpdates } = useQueryHandler(); const { loading, error, value } = useAsync( async () => pluginApiClient.getRepositories({ owner: project.owner }), @@ -59,8 +58,7 @@ export function Repo({ project }: { project: Project }) { value={project.repo} defaultValue="" onChange={event => { - const queryParams = getNewQueryParams({ - query, + const { queryParams } = getQueryParamsWithUpdates({ updates: [{ key: 'repo', value: event.target.value as string }], }); diff --git a/plugins/github-release-manager/src/cards/projectForm/VersioningStrategy.tsx b/plugins/github-release-manager/src/cards/projectForm/VersioningStrategy.tsx index 8235f51916..5b127b8be2 100644 --- a/plugins/github-release-manager/src/cards/projectForm/VersioningStrategy.tsx +++ b/plugins/github-release-manager/src/cards/projectForm/VersioningStrategy.tsx @@ -25,22 +25,17 @@ import { } from '@material-ui/core'; import { Project } from '../../contexts/ProjectContext'; -import { useQuery } from '../../helpers/useQuery'; -import { - getNewQueryParams, - getParsedQuery, -} from '../../helpers/getNewQueryParams'; +import { useQueryHandler } from '../../helpers/useQueryHandler'; export function VersioningStrategy({ project }: { project: Project }) { const navigate = useNavigate(); - const query = useQuery(); + const { getParsedQuery, getQueryParamsWithUpdates } = useQueryHandler(); useEffect(() => { - const parsedQuery = getParsedQuery({ query }); + const { parsedQuery } = getParsedQuery(); if (!parsedQuery.versioningStrategy) { - const queryParams = getNewQueryParams({ - query, + const { queryParams } = getQueryParamsWithUpdates({ updates: [ { key: 'versioningStrategy', value: project.versioningStrategy }, ], @@ -59,8 +54,7 @@ export function VersioningStrategy({ project }: { project: Project }) { value={project.versioningStrategy} defaultValue="semver" onChange={event => { - const queryParams = getNewQueryParams({ - query, + const queryParams = getQueryParamsWithUpdates({ updates: [{ key: 'versioningStrategy', value: event.target.value }], }); diff --git a/plugins/github-release-manager/src/helpers/getNewQueryParams.tsx b/plugins/github-release-manager/src/helpers/getNewQueryParams.tsx deleted file mode 100644 index ed5655bf54..0000000000 --- a/plugins/github-release-manager/src/helpers/getNewQueryParams.tsx +++ /dev/null @@ -1,44 +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 qs from 'qs'; - -import { Project } from '../contexts/ProjectContext'; - -export function getParsedQuery({ query }: { query: URLSearchParams }) { - const parsedQuery: Partial = qs.parse(query.toString()); - - return parsedQuery; -} - -export function getNewQueryParams({ - query, - updates, -}: { - query: URLSearchParams; - updates: { - key: keyof Project; - value: string; - }[]; -}) { - const queryParams = qs.parse(query.toString()); - - for (const { key, value } of updates) { - queryParams[key] = value; - } - - return qs.stringify(queryParams); -} diff --git a/plugins/github-release-manager/src/helpers/isProjectValid.test.ts b/plugins/github-release-manager/src/helpers/isProjectValid.test.ts new file mode 100644 index 0000000000..44cec0e571 --- /dev/null +++ b/plugins/github-release-manager/src/helpers/isProjectValid.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { mockSemverProject } from '../test-helpers/test-helpers'; +import { isProjectValid } from './isProjectValid'; + +describe('isProjectValid', () => { + it('should return true for valid project', () => { + const result = isProjectValid(mockSemverProject); + + expect(result).toEqual(true); + }); + + it('should return false for invalid project (undefined argument)', () => { + const result = isProjectValid(undefined); + + expect(result).toEqual(false); + }); + + it('should return false for invalid project (empty object argument)', () => { + const result = isProjectValid({}); + + expect(result).toEqual(false); + }); + + it('should return false for invalid project (invalid versioningStrategy argument)', () => { + const result = isProjectValid({ + ...mockSemverProject, + versioningStrategy: 'banana', + }); + + expect(result).toEqual(false); + }); +}); diff --git a/plugins/github-release-manager/src/cards/projectForm/isProjectValid.tsx b/plugins/github-release-manager/src/helpers/isProjectValid.ts similarity index 81% rename from plugins/github-release-manager/src/cards/projectForm/isProjectValid.tsx rename to plugins/github-release-manager/src/helpers/isProjectValid.ts index dd6e9871bb..8a48ac5bbd 100644 --- a/plugins/github-release-manager/src/cards/projectForm/isProjectValid.tsx +++ b/plugins/github-release-manager/src/helpers/isProjectValid.ts @@ -14,12 +14,14 @@ * limitations under the License. */ -import { Project } from '../../contexts/ProjectContext'; +import { Project } from '../contexts/ProjectContext'; export function isProjectValid(project: any): project is Project { return ( project?.owner?.length > 0 && project?.repo?.length > 0 && - project?.versioningStrategy?.length > 0 + (['semver', 'calver'] as Project['versioningStrategy'][]).includes( + project?.versioningStrategy, + ) ); } diff --git a/plugins/github-release-manager/src/helpers/useQuery.tsx b/plugins/github-release-manager/src/helpers/useQuery.tsx deleted file mode 100644 index bf45550b10..0000000000 --- a/plugins/github-release-manager/src/helpers/useQuery.tsx +++ /dev/null @@ -1,21 +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 { useLocation } from 'react-router'; - -export function useQuery(): URLSearchParams { - return new URLSearchParams(useLocation().search); -} diff --git a/plugins/github-release-manager/src/helpers/useQueryHandler.test.tsx b/plugins/github-release-manager/src/helpers/useQueryHandler.test.tsx new file mode 100644 index 0000000000..a5559176e2 --- /dev/null +++ b/plugins/github-release-manager/src/helpers/useQueryHandler.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; + +import { mockSemverProject } from '../test-helpers/test-helpers'; + +jest.mock('react-router', () => ({ + useLocation: jest.fn(() => ({ + search: `?versioningStrategy=${mockSemverProject.versioningStrategy}&owner=${mockSemverProject.owner}&repo=${mockSemverProject.repo}`, + })), +})); + +import { useQueryHandler } from './useQueryHandler'; + +const TEST_ID = 'grm--use-query-handler'; + +const MockComponent = () => { + const { getParsedQuery, getQueryParamsWithUpdates } = useQueryHandler(); + + const { parsedQuery } = getParsedQuery(); + const { queryParams } = getQueryParamsWithUpdates({ + updates: [{ key: 'repo', value: 'updated_mock_repo' }], + }); + + return ( +
+ {JSON.stringify({ parsedQuery, queryParams }, null, 2)} +
+ ); +}; + +describe('useQueryHandler', () => { + it('should get parsedQuery and queryParams', () => { + const { getByTestId } = render(); + + const smt = getByTestId(TEST_ID).innerHTML; + + expect(smt).toMatchInlineSnapshot(` + "{ + \\"parsedQuery\\": { + \\"versioningStrategy\\": \\"semver\\", + \\"owner\\": \\"mock_owner\\", + \\"repo\\": \\"mock_repo\\" + }, + \\"queryParams\\": \\"versioningStrategy=semver&owner=mock_owner&repo=updated_mock_repo\\" + }" + `); + }); +}); diff --git a/plugins/github-release-manager/src/helpers/useQueryHandler.ts b/plugins/github-release-manager/src/helpers/useQueryHandler.ts new file mode 100644 index 0000000000..c2b6e87a0f --- /dev/null +++ b/plugins/github-release-manager/src/helpers/useQueryHandler.ts @@ -0,0 +1,68 @@ +/* + * 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'; +import qs from 'qs'; + +import { Project } from '../contexts/ProjectContext'; + +export function useQueryHandler() { + const location = useLocation(); + + function getParsedQuery() { + const { decodedSearch } = getDecodedSearch(location); + const parsedQuery: Partial = qs.parse(decodedSearch); + + return { + parsedQuery, + }; + } + + function getQueryParamsWithUpdates({ + updates, + }: { + updates: { + key: keyof Project; + value: string; + }[]; + }) { + const { decodedSearch } = getDecodedSearch(location); + const queryParams = qs.parse(decodedSearch); + + for (const { key, value } of updates) { + queryParams[key] = value; + } + + return { + queryParams: qs.stringify(queryParams), + }; + } + + return { + getParsedQuery, + getQueryParamsWithUpdates, + }; +} + +function getDecodedSearch(location: ReturnType) { + return { + decodedSearch: new URLSearchParams(location.search).toString(), + }; +} + +export const testables = { + getDecodedSearch, +}; diff --git a/plugins/github-release-manager/src/test-helpers/test-helpers.ts b/plugins/github-release-manager/src/test-helpers/test-helpers.ts index 4deb9a5376..35d1a32520 100644 --- a/plugins/github-release-manager/src/test-helpers/test-helpers.ts +++ b/plugins/github-release-manager/src/test-helpers/test-helpers.ts @@ -24,15 +24,18 @@ import { IPluginApiClient, } from '../api/PluginApiClient'; +const mockOwner = 'mock_owner'; +const mockRepo = 'mock_repo'; + export const mockSemverProject: Project = { - owner: 'mock_owner', - repo: 'mock_repo', + owner: mockOwner, + repo: mockRepo, versioningStrategy: 'semver', }; export const mockCalverProject: Project = { - owner: 'mock_owner', - repo: 'mock_repo', + owner: mockOwner, + repo: mockRepo, versioningStrategy: 'calver', }; @@ -130,18 +133,18 @@ export const mockSelectedPatchCommit = createMockRecentCommit({ export const mockApiClient: IPluginApiClient = { getHost: jest.fn(() => 'github.com'), - getRepoPath: jest.fn(() => 'erikengervall/playground'), + getRepoPath: jest.fn(() => `${mockOwner}/${mockRepo}`), getOwners: jest.fn(async () => ({ - owners: ['owner1', 'owner2'], + owners: [mockOwner, `${mockOwner}2`], })), getRepositories: jest.fn(async () => ({ - repositories: ['repo1', 'repo2'], + repositories: [mockRepo, `${mockRepo}2`], })), getUsername: jest.fn(async () => ({ - username: 'erikengervall', + username: mockOwner, })), getRecentCommits: jest.fn(async () => [