diff --git a/gradle/changelog/enforce_eslint.yaml b/gradle/changelog/enforce_eslint.yaml new file mode 100644 index 0000000000..5c97e197d0 --- /dev/null +++ b/gradle/changelog/enforce_eslint.yaml @@ -0,0 +1,2 @@ +- type: changed + description: Enforce eslint to ensure accessible html ([#1878](https://github.com/scm-manager/scm-manager/pull/1878)) diff --git a/package.json b/package.json index ae6378a4ab..5d8b6d1a37 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test": "lerna run --scope '@scm-manager/ui-*' test", "e2e-tests": "lerna run --scope '@scm-manager/e2e-tests' ci", "typecheck": "lerna run --scope '@scm-manager/ui-*' typecheck", + "lint": "lerna run --scope '@scm-manager/ui-*' lint", "serve": "ui-scripts serve development", "deploy": "ui-scripts publish", "set-version": "ui-scripts version" diff --git a/scm-ui/build.gradle b/scm-ui/build.gradle index 3424b96efe..3298ad0bb3 100644 --- a/scm-ui/build.gradle +++ b/scm-ui/build.gradle @@ -55,6 +55,26 @@ task typecheck(type: YarnTask) { } } +task lint(type: YarnTask) { + args = ['lint'] + inputs.files(fileTree(project.projectDir) { + include 'ui-*/src/**' + include 'ui-*/**/*.js' + include 'ui-*/**/*.jsx' + include 'ui-*/**/*.ts' + include 'ui-*/**/*.tsx' + }) + .withPathSensitivity(PathSensitivity.RELATIVE) + outputs.file('build/tmp/lint/marker') + dependsOn('yarn_install') + doLast { + File directory = new File(project.buildDir, 'tmp/lint') + directory.mkdirs() + File marker = new File(directory, 'marker') + marker.createNewFile() + } +} + task test(type: YarnTask) { args = ['run', 'test'] inputs.files(fileTree(project.projectDir) { @@ -103,7 +123,7 @@ task updateUiTestTimestamps(type: TouchFiles) { } task check { - dependsOn('typecheck', 'test', 'chromatic', 'checkLicenses') + dependsOn('typecheck', 'test', 'chromatic', 'checkLicenses', 'lint') } yarn_install { diff --git a/scm-ui/ui-api/src/import.ts b/scm-ui/ui-api/src/import.ts index b0cd8f4589..373e203523 100644 --- a/scm-ui/ui-api/src/import.ts +++ b/scm-ui/ui-api/src/import.ts @@ -30,12 +30,12 @@ import { requiredLink } from "./links"; export const useImportLog = (logId: string): ApiResult => { const link = useRequiredIndexLink("importLog").replace("{logId}", logId); - return useQuery(["importLog", logId], () => apiClient.get(link).then((response) => response.text())); + return useQuery(["importLog", logId], () => apiClient.get(link).then(response => response.text())); }; export const useImportRepositoryFromUrl = (repositoryType: RepositoryType) => { const url = requiredLink(repositoryType, "import", "url"); - const { isLoading, error, data, mutate } = useMutation((repo) => + const { isLoading, error, data, mutate } = useMutation(repo => apiClient .post(url, repo, "application/vnd.scmm-repository+json;v=2") .then(fetchResourceFromLocationHeader) @@ -46,14 +46,14 @@ export const useImportRepositoryFromUrl = (repositoryType: RepositoryType) => { isLoading, error, importRepositoryFromUrl: (repository: RepositoryUrlImport) => mutate(repository), - importedRepository: data, + importedRepository: data }; }; const importRepository = (url: string, repository: RepositoryCreation, file: File, password?: string) => { return apiClient - .postBinary(url, (formData) => { - formData.append("bundle", file, file?.name); + .postBinary(url, formData => { + formData.append("bundle", file, file.name); formData.append("repository", JSON.stringify({ ...repository, password })); }) .then(fetchResourceFromLocationHeader) @@ -82,9 +82,9 @@ export const useImportRepositoryFromBundle = (repositoryType: RepositoryType) => repository, file, compressed, - password, + password }), - importedRepository: data, + importedRepository: data }; }; @@ -107,8 +107,8 @@ export const useImportFullRepository = (repositoryType: RepositoryType) => { mutate({ repository, file, - password, + password }), - importedRepository: data, + importedRepository: data }; }; diff --git a/scm-ui/ui-api/src/repositories.test.ts b/scm-ui/ui-api/src/repositories.test.ts index 98ab014a33..95ffc733c7 100644 --- a/scm-ui/ui-api/src/repositories.test.ts +++ b/scm-ui/ui-api/src/repositories.test.ts @@ -37,7 +37,7 @@ import { useRepository, useRepositoryTypes, useUnarchiveRepository, - useUpdateRepository, + useUpdateRepository } from "./repositories"; import { Repository } from "@scm-manager/ui-types"; import { QueryClient } from "react-query"; @@ -50,25 +50,25 @@ describe("Test repository hooks", () => { type: "git", _links: { delete: { - href: "/r/spaceships/heartOfGold", + href: "/r/spaceships/heartOfGold" }, update: { - href: "/r/spaceships/heartOfGold", + href: "/r/spaceships/heartOfGold" }, archive: { - href: "/r/spaceships/heartOfGold/archive", + href: "/r/spaceships/heartOfGold/archive" }, unarchive: { - href: "/r/spaceships/heartOfGold/unarchive", - }, - }, + href: "/r/spaceships/heartOfGold/unarchive" + } + } }; const repositoryCollection = { _embedded: { - repositories: [heartOfGold], + repositories: [heartOfGold] }, - _links: {}, + _links: {} }; afterEach(() => { @@ -78,7 +78,7 @@ describe("Test repository hooks", () => { describe("useRepositories tests", () => { const expectCollection = async (queryClient: QueryClient, request?: UseRepositoriesRequest) => { const { result, waitFor } = renderHook(() => useRepositories(request), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); await waitFor(() => { return !!result.current.data; @@ -91,8 +91,8 @@ describe("Test repository hooks", () => { setIndexLink(queryClient, "repositories", "/repos"); fetchMock.get("/api/v2/repos", repositoryCollection, { query: { - sortBy: "namespaceAndName", - }, + sortBy: "namespaceAndName" + } }); await expectCollection(queryClient); @@ -104,12 +104,12 @@ describe("Test repository hooks", () => { fetchMock.get("/api/v2/repos", repositoryCollection, { query: { sortBy: "namespaceAndName", - page: "42", - }, + page: "42" + } }); await expectCollection(queryClient, { - page: 42, + page: 42 }); }); @@ -118,8 +118,8 @@ describe("Test repository hooks", () => { setIndexLink(queryClient, "repositories", "/repos"); fetchMock.get("/api/v2/spaceships", repositoryCollection, { query: { - sortBy: "namespaceAndName", - }, + sortBy: "namespaceAndName" + } }); await expectCollection(queryClient, { @@ -127,10 +127,10 @@ describe("Test repository hooks", () => { namespace: "spaceships", _links: { repositories: { - href: "/spaceships", - }, - }, - }, + href: "/spaceships" + } + } + } }); }); @@ -140,12 +140,12 @@ describe("Test repository hooks", () => { fetchMock.get("/api/v2/repos", repositoryCollection, { query: { sortBy: "namespaceAndName", - q: "heart", - }, + q: "heart" + } }); await expectCollection(queryClient, { - search: "heart", + search: "heart" }); }); @@ -154,8 +154,8 @@ describe("Test repository hooks", () => { setIndexLink(queryClient, "repositories", "/repos"); fetchMock.get("/api/v2/repos", repositoryCollection, { query: { - sortBy: "namespaceAndName", - }, + sortBy: "namespaceAndName" + } }); await expectCollection(queryClient); @@ -168,7 +168,7 @@ describe("Test repository hooks", () => { const queryClient = createInfiniteCachingClient(); setIndexLink(queryClient, "repositories", "/repos"); const { result } = renderHook(() => useRepositories({ disabled: true }), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); expect(result.current.isLoading).toBe(false); @@ -185,19 +185,18 @@ describe("Test repository hooks", () => { fetchMock.postOnce("/api/v2/r", { status: 201, headers: { - Location: "/r/spaceships/heartOfGold", - }, + Location: "/r/spaceships/heartOfGold" + } }); fetchMock.getOnce("/api/v2/r/spaceships/heartOfGold", heartOfGold); const { result, waitForNextUpdate } = renderHook(() => useCreateRepository(), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); const repository = { - ...heartOfGold, - contextEntries: [], + ...heartOfGold }; await act(() => { @@ -216,19 +215,18 @@ describe("Test repository hooks", () => { fetchMock.postOnce("/api/v2/r?initialize=true", { status: 201, headers: { - Location: "/r/spaceships/heartOfGold", - }, + Location: "/r/spaceships/heartOfGold" + } }); fetchMock.getOnce("/api/v2/r/spaceships/heartOfGold", heartOfGold); const { result, waitForNextUpdate } = renderHook(() => useCreateRepository(), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); const repository = { - ...heartOfGold, - contextEntries: [], + ...heartOfGold }; await act(() => { @@ -245,16 +243,15 @@ describe("Test repository hooks", () => { setIndexLink(queryClient, "repositories", "/r"); fetchMock.postOnce("/api/v2/r", { - status: 201, + status: 201 }); const { result, waitForNextUpdate } = renderHook(() => useCreateRepository(), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); const repository = { - ...heartOfGold, - contextEntries: [], + ...heartOfGold }; await act(() => { @@ -274,7 +271,7 @@ describe("Test repository hooks", () => { fetchMock.get("/api/v2/r/spaceships/heartOfGold", heartOfGold); const { result, waitFor } = renderHook(() => useRepository("spaceships", "heartOfGold"), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); await waitFor(() => { return !!result.current.data; @@ -293,15 +290,15 @@ describe("Test repository hooks", () => { { name: "git", displayName: "Git", - _links: {}, - }, - ], + _links: {} + } + ] }, - _links: {}, + _links: {} }); const { result, waitFor } = renderHook(() => useRepositoryTypes(), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); await waitFor(() => { return !!result.current.data; @@ -322,11 +319,11 @@ describe("Test repository hooks", () => { const deleteRepository = async (options?: UseDeleteRepositoryOptions) => { fetchMock.deleteOnce("/api/v2/r/spaceships/heartOfGold", { - status: 204, + status: 204 }); const { result, waitForNextUpdate } = renderHook(() => useDeleteRepository(options), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); await act(() => { @@ -371,9 +368,9 @@ describe("Test repository hooks", () => { it("should call onSuccess callback", async () => { let repo; await deleteRepository({ - onSuccess: (repository) => { + onSuccess: repository => { repo = repository; - }, + } }); expect(repo).toEqual(heartOfGold); }); @@ -388,11 +385,11 @@ describe("Test repository hooks", () => { const updateRepository = async () => { fetchMock.putOnce("/api/v2/r/spaceships/heartOfGold", { - status: 204, + status: 204 }); const { result, waitForNextUpdate } = renderHook(() => useUpdateRepository(), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); await act(() => { @@ -436,11 +433,11 @@ describe("Test repository hooks", () => { const archiveRepository = async () => { fetchMock.postOnce("/api/v2/r/spaceships/heartOfGold/archive", { - status: 204, + status: 204 }); const { result, waitForNextUpdate } = renderHook(() => useArchiveRepository(), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); await act(() => { @@ -484,11 +481,11 @@ describe("Test repository hooks", () => { const unarchiveRepository = async () => { fetchMock.postOnce("/api/v2/r/spaceships/heartOfGold/unarchive", { - status: 204, + status: 204 }); const { result, waitForNextUpdate } = renderHook(() => useUnarchiveRepository(), { - wrapper: createWrapper(undefined, queryClient), + wrapper: createWrapper(undefined, queryClient) }); await act(() => { diff --git a/scm-ui/ui-components/src/ErrorBoundary.tsx b/scm-ui/ui-components/src/ErrorBoundary.tsx index 2550a3262b..54756fc6fb 100644 --- a/scm-ui/ui-components/src/ErrorBoundary.tsx +++ b/scm-ui/ui-components/src/ErrorBoundary.tsx @@ -113,7 +113,7 @@ const ErrorDisplay: FC = ({ error, errorInfo, fallback: Fallb const fallbackProps = { error, - errorInfo, + errorInfo }; return ; @@ -135,7 +135,7 @@ class ErrorBoundary extends React.Component { componentDidCatch(error: Error, errorInfo: ErrorInfo) { this.setState({ error, - errorInfo, + errorInfo }); } diff --git a/scm-ui/ui-components/src/forms/InputField.tsx b/scm-ui/ui-components/src/forms/InputField.tsx index b2e0d21c00..dfc7e5fda7 100644 --- a/scm-ui/ui-components/src/forms/InputField.tsx +++ b/scm-ui/ui-components/src/forms/InputField.tsx @@ -33,7 +33,7 @@ type BaseProps = { label?: string; name?: string; placeholder?: string; - value?: string; + value?: string | number; type?: string; autofocus?: boolean; onReturnPressed?: () => void; @@ -44,7 +44,7 @@ type BaseProps = { helpText?: string; className?: string; testId?: string; - defaultValue?: string; + defaultValue?: string | number; readOnly?: boolean; }; diff --git a/scm-ui/ui-components/src/navigation/RoutingProps.ts b/scm-ui/ui-components/src/navigation/RoutingProps.ts index 20f9d8d900..3ba7f4efcc 100644 --- a/scm-ui/ui-components/src/navigation/RoutingProps.ts +++ b/scm-ui/ui-components/src/navigation/RoutingProps.ts @@ -22,8 +22,10 @@ * SOFTWARE. */ +import { RouteProps } from "react-router-dom"; + export type RoutingProps = { to: string; activeOnlyWhenExact?: boolean; - activeWhenMatch?: (route: any) => boolean; + activeWhenMatch?: (route: RouteProps) => boolean; }; diff --git a/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx b/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx index 1efe03142b..d623eb9c76 100644 --- a/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx +++ b/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx @@ -53,8 +53,8 @@ const withRoute = (route: string) => { }; storiesOf("Secondary Navigation", module) - .addDecorator((story) => {story()}) - .addDecorator((story) => ( + .addDecorator(story => {story()}) + .addDecorator(story => (
{story()}
@@ -92,7 +92,7 @@ storiesOf("Secondary Navigation", module) route.location.pathname === "/hog"} + activeWhenMatch={route => route.location?.pathname === "/hog"} to="/heart-of-gold" icon="fas fa-star" label="Heart Of Gold" diff --git a/scm-ui/ui-tests/i18n.ts b/scm-ui/ui-tests/i18n.ts index 2588bc6d9b..2032c75cde 100644 --- a/scm-ui/ui-tests/i18n.ts +++ b/scm-ui/ui-tests/i18n.ts @@ -22,7 +22,7 @@ * SOFTWARE. */ -jest.mock("react-i18next", () => ({ +export const jestMock = jest.mock("react-i18next", () => ({ // this mock makes sure any components using the translate HoC receive the t function as a prop withTranslation: () => (Component: any) => { Component.defaultProps = { @@ -32,8 +32,6 @@ jest.mock("react-i18next", () => ({ return Component; }, useTranslation: (ns: string) => { - return [ - (key: string) => key - ]; + return [(key: string) => key]; } })); diff --git a/scm-ui/ui-tests/index.ts b/scm-ui/ui-tests/index.ts new file mode 100644 index 0000000000..08403a19cb --- /dev/null +++ b/scm-ui/ui-tests/index.ts @@ -0,0 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +export * from "./enzyme"; +export * from "./enzyme-router"; +export * from "./i18n"; diff --git a/scm-ui/ui-types/src/General.ts b/scm-ui/ui-types/src/General.ts new file mode 100644 index 0000000000..9b72b56d1d --- /dev/null +++ b/scm-ui/ui-types/src/General.ts @@ -0,0 +1,32 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { Config } from "./Config"; +import { HalRepresentation } from "./hal"; + +export type ConfigChangeHandler = >( + isValid: boolean, + changedValue: Config[Name], + name: Name +) => void; diff --git a/scm-ui/ui-types/src/Repositories.ts b/scm-ui/ui-types/src/Repositories.ts index 4f5fab167c..4f3dd48c87 100644 --- a/scm-ui/ui-types/src/Repositories.ts +++ b/scm-ui/ui-types/src/Repositories.ts @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { PagedCollection, Links, HalRepresentation } from "./hal"; +import { HalRepresentation, Links, PagedCollection } from "./hal"; export type NamespaceAndName = { namespace: string; @@ -53,7 +53,7 @@ export type Repository = HalRepresentation & }; export type RepositoryCreation = RepositoryBase & { - contextEntries: { [key: string]: any }; + contextEntries?: { [key: string]: object | undefined }; }; export type RepositoryUrlImport = RepositoryCreation & { diff --git a/scm-ui/ui-types/src/index.ts b/scm-ui/ui-types/src/index.ts index 8d6d1e13bf..535aaab5a1 100644 --- a/scm-ui/ui-types/src/index.ts +++ b/scm-ui/ui-types/src/index.ts @@ -71,3 +71,4 @@ export * from "./ApiKeys"; export * from "./PublicKeys"; export * from "./GlobalPermissions"; export * from "./Search"; +export * from "./General"; diff --git a/scm-ui/ui-webapp/package.json b/scm-ui/ui-webapp/package.json index 332161424a..1e2b303c28 100644 --- a/scm-ui/ui-webapp/package.json +++ b/scm-ui/ui-webapp/package.json @@ -25,7 +25,8 @@ "systemjs": "0.21.6" }, "scripts": { - "test": "jest" + "test": "jest", + "lint": "eslint src" }, "devDependencies": { "@scm-manager/jest-preset": "^2.13.0", diff --git a/scm-ui/ui-webapp/src/LegacyReduxProvider.tsx b/scm-ui/ui-webapp/src/LegacyReduxProvider.tsx index 229548485c..8c1ffe5ba4 100644 --- a/scm-ui/ui-webapp/src/LegacyReduxProvider.tsx +++ b/scm-ui/ui-webapp/src/LegacyReduxProvider.tsx @@ -65,16 +65,16 @@ const reducer = (state: State = initialState, action: ActionTypes = { type: ACTI ...state, indexResources: { version: action.payload.version, - links: action.payload._links, - }, + links: action.payload._links + } }; } case "scm/me_success": { return { ...state, auth: { - me: action.payload, - }, + me: action.payload + } }; } default: { @@ -90,14 +90,14 @@ const store = createStore(reducer, initialState); export const fetchIndexResourcesSuccess = (index: IndexResources): ActionTypes => { return { type: ACTION_TYPE_INDEX, - payload: index, + payload: index }; }; export const fetchMeSuccess = (me: Me): ActionTypes => { return { type: ACTION_TYPE_ME, - payload: me, + payload: me }; }; diff --git a/scm-ui/ui-webapp/src/ReduxAwareApiProvider.tsx b/scm-ui/ui-webapp/src/ReduxAwareApiProvider.tsx index 671865fc2c..9fc61e2038 100644 --- a/scm-ui/ui-webapp/src/ReduxAwareApiProvider.tsx +++ b/scm-ui/ui-webapp/src/ReduxAwareApiProvider.tsx @@ -40,9 +40,10 @@ const mapDispatchToProps = (dispatch: Dispatch) => { }, onMeFetched: (me: Me) => { dispatch(fetchMeSuccess(me)); - }, + } }; }; +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore no clue how to type it export default connect(undefined, mapDispatchToProps)(ReduxAwareApiProvider); diff --git a/scm-ui/ui-webapp/src/admin/components/form/BaseUrlSettings.tsx b/scm-ui/ui-webapp/src/admin/components/form/BaseUrlSettings.tsx index 7801c8bbf3..72e8866588 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/BaseUrlSettings.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/BaseUrlSettings.tsx @@ -24,11 +24,12 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { Checkbox, InputField, Subtitle } from "@scm-manager/ui-components"; +import { ConfigChangeHandler } from "@scm-manager/ui-types"; type Props = WithTranslation & { baseUrl: string; forceBaseUrl: boolean; - onChange: (p1: boolean, p2: any, p3: string) => void; + onChange: ConfigChangeHandler; hasUpdatePermission: boolean; }; diff --git a/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx b/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx index 3385e2903c..48e7ec5e38 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx @@ -23,7 +23,7 @@ */ import React, { FC, useState, useEffect, FormEvent } from "react"; import { useTranslation } from "react-i18next"; -import { Config, NamespaceStrategies } from "@scm-manager/ui-types"; +import { Config, ConfigChangeHandler, NamespaceStrategies } from "@scm-manager/ui-types"; import { Level, Notification, SubmitButton } from "@scm-manager/ui-components"; import ProxySettings from "./ProxySettings"; import GeneralSettings from "./GeneralSettings"; @@ -45,7 +45,7 @@ const ConfigForm: FC = ({ loading, configReadPermission, configUpdatePermission, - namespaceStrategies, + namespaceStrategies }) => { const [t] = useTranslation("config"); const [innerConfig, setInnerConfig] = useState({ @@ -74,7 +74,7 @@ const ConfigForm: FC = ({ mailDomainName: "", emergencyContacts: [], enabledApiKeys: true, - _links: {}, + _links: {} }); const [showNotification, setShowNotification] = useState(false); const [changed, setChanged] = useState(false); @@ -83,7 +83,7 @@ const ConfigForm: FC = ({ loginAttemptLimit: boolean; }>({ loginAttemptLimitTimeout: false, - loginAttemptLimit: false, + loginAttemptLimit: false }); useEffect(() => { @@ -101,7 +101,7 @@ const ConfigForm: FC = ({ submitForm(innerConfig); }; - const onChange = (isValid: boolean, changedValue: any, name: string) => { + const onChange: ConfigChangeHandler = (isValid: boolean, changedValue: unknown, name: string) => { setInnerConfig({ ...innerConfig, [name]: changedValue }); setError({ ...error, [name]: !isValid }); setChanged(true); @@ -150,21 +150,21 @@ const ConfigForm: FC = ({ enabledApiKeys={innerConfig.enabledApiKeys} emergencyContacts={innerConfig.emergencyContacts} namespaceStrategy={innerConfig.namespaceStrategy} - onChange={(isValid, changedValue, name) => onChange(isValid, changedValue, name)} + onChange={onChange} hasUpdatePermission={configUpdatePermission} />
onChange(isValid, changedValue, name)} + onChange={onChange} hasUpdatePermission={configUpdatePermission} />
onChange(isValid, changedValue, name)} + onChange={onChange} hasUpdatePermission={configUpdatePermission} />
@@ -175,7 +175,7 @@ const ConfigForm: FC = ({ proxyUser={innerConfig.proxyUser ? innerConfig.proxyUser : ""} enableProxy={innerConfig.enableProxy} proxyExcludes={innerConfig.proxyExcludes} - onChange={(isValid, changedValue, name) => onChange(isValid, changedValue, name)} + onChange={onChange} hasUpdatePermission={configUpdatePermission} />
diff --git a/scm-ui/ui-webapp/src/admin/components/form/GeneralSettings.tsx b/scm-ui/ui-webapp/src/admin/components/form/GeneralSettings.tsx index 3562f9856a..63099b9868 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/GeneralSettings.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/GeneralSettings.tsx @@ -24,13 +24,13 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; import { useUserSuggestions } from "@scm-manager/ui-api"; -import { NamespaceStrategies, AnonymousMode, SelectValue } from "@scm-manager/ui-types"; +import { NamespaceStrategies, AnonymousMode, SelectValue, ConfigChangeHandler } from "@scm-manager/ui-types"; import { Checkbox, InputField, MemberNameTagGroup, AutocompleteAddEntryToTableField, - Select, + Select } from "@scm-manager/ui-components"; import NamespaceStrategySelect from "./NamespaceStrategySelect"; @@ -50,7 +50,7 @@ type Props = { emergencyContacts: string[]; namespaceStrategy: string; namespaceStrategies?: NamespaceStrategies; - onChange: (p1: boolean, p2: any, p3: string) => void; + onChange: ConfigChangeHandler; hasUpdatePermission: boolean; }; @@ -68,7 +68,7 @@ const GeneralSettings: FC = ({ namespaceStrategy, namespaceStrategies, onChange, - hasUpdatePermission, + hasUpdatePermission }) => { const { t } = useTranslation("config"); const userSuggestions = useUserSuggestions(); @@ -86,7 +86,7 @@ const GeneralSettings: FC = ({ onChange(true, value, "enabledUserConverter"); }; const handleAnonymousMode = (value: string) => { - onChange(true, value, "anonymousMode"); + onChange(true, value as AnonymousMode, "anonymousMode"); }; const handleNamespaceStrategyChange = (value: string) => { onChange(true, value, "namespaceStrategy"); @@ -181,7 +181,7 @@ const GeneralSettings: FC = ({ options={[ { label: t("general-settings.anonymousMode.full"), value: "FULL" }, { label: t("general-settings.anonymousMode.protocolOnly"), value: "PROTOCOL_ONLY" }, - { label: t("general-settings.anonymousMode.off"), value: "OFF" }, + { label: t("general-settings.anonymousMode.off"), value: "OFF" } ]} helpText={t("help.allowAnonymousAccessHelpText")} testId={"anonymous-mode-select"} diff --git a/scm-ui/ui-webapp/src/admin/components/form/LoginAttempt.tsx b/scm-ui/ui-webapp/src/admin/components/form/LoginAttempt.tsx index 094e87f8bc..7ff526aafb 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/LoginAttempt.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/LoginAttempt.tsx @@ -24,11 +24,12 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { InputField, Subtitle, validation as validator } from "@scm-manager/ui-components"; +import { ConfigChangeHandler } from "@scm-manager/ui-types"; type Props = WithTranslation & { loginAttemptLimit: number; loginAttemptLimitTimeout: number; - onChange: (p1: boolean, p2: any, p3: string) => void; + onChange: ConfigChangeHandler; hasUpdatePermission: boolean; }; @@ -43,7 +44,7 @@ class LoginAttempt extends React.Component { this.state = { loginAttemptLimitError: false, - loginAttemptLimitTimeoutError: false, + loginAttemptLimitTimeoutError: false }; } render() { @@ -84,17 +85,17 @@ class LoginAttempt extends React.Component { handleLoginAttemptLimitChange = (value: string) => { this.setState({ ...this.state, - loginAttemptLimitError: !validator.isNumberValid(value), + loginAttemptLimitError: !validator.isNumberValid(value) }); - this.props.onChange(validator.isNumberValid(value), value, "loginAttemptLimit"); + this.props.onChange(validator.isNumberValid(value), Number(value), "loginAttemptLimit"); }; handleLoginAttemptLimitTimeoutChange = (value: string) => { this.setState({ ...this.state, - loginAttemptLimitTimeoutError: !validator.isNumberValid(value), + loginAttemptLimitTimeoutError: !validator.isNumberValid(value) }); - this.props.onChange(validator.isNumberValid(value), value, "loginAttemptLimitTimeout"); + this.props.onChange(validator.isNumberValid(value), Number(value), "loginAttemptLimitTimeout"); }; } diff --git a/scm-ui/ui-webapp/src/admin/components/form/NamespaceStrategySelect.tsx b/scm-ui/ui-webapp/src/admin/components/form/NamespaceStrategySelect.tsx index 0e20d32de9..e5a17fa47a 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/NamespaceStrategySelect.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/NamespaceStrategySelect.tsx @@ -43,7 +43,7 @@ class NamespaceStrategySelect extends React.Component { available = namespaceStrategies.available; } - return available.map((ns) => { + return available.map(ns => { const key = "namespaceStrategies." + ns; let label = t(key); if (label === key) { @@ -51,7 +51,7 @@ class NamespaceStrategySelect extends React.Component { } return { value: ns, - label: label, + label: label }; }); }; diff --git a/scm-ui/ui-webapp/src/admin/components/form/ProxySettings.tsx b/scm-ui/ui-webapp/src/admin/components/form/ProxySettings.tsx index d435bebb96..1e380c3f02 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/ProxySettings.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/ProxySettings.tsx @@ -25,6 +25,7 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { AddEntryToTableField, Checkbox, InputField, Subtitle } from "@scm-manager/ui-components"; import ProxyExcludesTable from "../table/ProxyExcludesTable"; +import { ConfigChangeHandler } from "@scm-manager/ui-types"; type Props = WithTranslation & { proxyPassword: string; @@ -33,14 +34,22 @@ type Props = WithTranslation & { proxyUser: string; enableProxy: boolean; proxyExcludes: string[]; - onChange: (p1: boolean, p2: any, p3: string) => void; + onChange: ConfigChangeHandler; hasUpdatePermission: boolean; }; class ProxySettings extends React.Component { render() { - const { t, proxyPassword, proxyPort, proxyServer, proxyUser, enableProxy, proxyExcludes, hasUpdatePermission } = - this.props; + const { + t, + proxyPassword, + proxyPort, + proxyServer, + proxyUser, + enableProxy, + proxyExcludes, + hasUpdatePermission + } = this.props; return (
@@ -101,7 +110,7 @@ class ProxySettings extends React.Component {
this.props.onChange(isValid, changedValue, name)} + onChange={this.props.onChange} disabled={!enableProxy || !hasUpdatePermission} /> { this.props.onChange(true, value, "proxyPassword"); }; handleProxyPortChange = (value: string) => { - this.props.onChange(true, value, "proxyPort"); + this.props.onChange(true, Number(value), "proxyPort"); }; handleProxyServerChange = (value: string) => { this.props.onChange(true, value, "proxyServer"); @@ -130,7 +139,7 @@ class ProxySettings extends React.Component { handleProxyUserChange = (value: string) => { this.props.onChange(true, value, "proxyUser"); }; - handleEnableProxyChange = (value: string) => { + handleEnableProxyChange = (value: boolean) => { this.props.onChange(true, value, "enableProxy"); }; diff --git a/scm-ui/ui-webapp/src/admin/components/table/ArrayConfigTable.tsx b/scm-ui/ui-webapp/src/admin/components/table/ArrayConfigTable.tsx index 50f4915e13..13200e8f46 100644 --- a/scm-ui/ui-webapp/src/admin/components/table/ArrayConfigTable.tsx +++ b/scm-ui/ui-webapp/src/admin/components/table/ArrayConfigTable.tsx @@ -42,7 +42,7 @@ class ArrayConfigTable extends React.Component { - {items.map((item) => { + {items.map(item => { return ( @@ -66,7 +66,7 @@ class ArrayConfigTable extends React.Component { } removeEntry = (item: string) => { - const newItems = this.props.items.filter((name) => name !== item); + const newItems = this.props.items.filter(name => name !== item); this.props.onRemove(newItems, item); }; } diff --git a/scm-ui/ui-webapp/src/admin/components/table/ProxyExcludesTable.tsx b/scm-ui/ui-webapp/src/admin/components/table/ProxyExcludesTable.tsx index c439d047b1..5d403aa20c 100644 --- a/scm-ui/ui-webapp/src/admin/components/table/ProxyExcludesTable.tsx +++ b/scm-ui/ui-webapp/src/admin/components/table/ProxyExcludesTable.tsx @@ -24,10 +24,11 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import ArrayConfigTable from "./ArrayConfigTable"; +import { ConfigChangeHandler } from "@scm-manager/ui-types"; type Props = WithTranslation & { proxyExcludes: string[]; - onChange: (p1: boolean, p2: any, p3: string) => void; + onChange: ConfigChangeHandler; disabled: boolean; }; diff --git a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx index ff4b20bc50..e7fccb56fe 100644 --- a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx +++ b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx @@ -23,7 +23,7 @@ */ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import { Redirect, Route, Switch, useRouteMatch } from "react-router-dom"; +import { Redirect, Route, RouteProps, Switch, useRouteMatch } from "react-router-dom"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { CustomQueryFlexWrappedColumns, @@ -34,7 +34,7 @@ import { SecondaryNavigationColumn, StateMenuContextProvider, SubNavigation, - urls, + urls } from "@scm-manager/ui-components"; import AdminDetails from "./AdminDetails"; import PluginsOverview from "../plugins/containers/PluginsOverview"; @@ -51,16 +51,16 @@ const Admin: FC = () => { const availablePluginsLink = links.availablePlugins; const installedPluginsLink = links.installedPlugins; - const matchesRoles = (route: any) => { + const matchesRoles = (route: RouteProps) => { const url = urls.matchedUrlFromMatch(match); const regex = new RegExp(`${url}/role/`); - return route.location.pathname.match(regex); + return !!route.location?.pathname.match(regex); }; const url = urls.matchedUrlFromMatch(match); const extensionProps = { links, - url, + url }; return ( diff --git a/scm-ui/ui-webapp/src/admin/containers/AdminDetails.tsx b/scm-ui/ui-webapp/src/admin/containers/AdminDetails.tsx index f01cf49369..a2c1768c97 100644 --- a/scm-ui/ui-webapp/src/admin/containers/AdminDetails.tsx +++ b/scm-ui/ui-webapp/src/admin/containers/AdminDetails.tsx @@ -60,7 +60,7 @@ const AdminDetails: FC = () => {

{t("admin.info.newRelease.title")}

{t("admin.info.newRelease.description", { - version: updateInfo?.latestVersion, + version: updateInfo?.latestVersion })}

diff --git a/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx b/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx index 1457a085d3..35a05cc9a7 100644 --- a/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx +++ b/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx @@ -34,7 +34,7 @@ const GlobalConfig: FC = () => { const { data: namespaceStrategies, error: namespaceStrategiesLoadingError, - isLoading: isLoadingNamespaceStrategies, + isLoading: isLoadingNamespaceStrategies } = useNamespaceStrategies(); const [t] = useTranslation("config"); const error = configLoadingError || namespaceStrategiesLoadingError || updateError || undefined; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx index b52c232269..f8fd298674 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx @@ -44,7 +44,7 @@ const Section: FC = ({ pendingPlugins, type, label }) => { <> {label}
    - {plugins.map((plugin) => ( + {plugins.map(plugin => (
  • {plugin.name}
  • ))}
diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginActionModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginActionModal.tsx index 2f66273cf0..df7a15c1d7 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginActionModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginActionModal.tsx @@ -72,8 +72,8 @@ class PluginActionModal extends React.Component { {t("plugins.modal.updateQueue")}
    {installedPlugins._embedded.plugins - .filter((plugin) => plugin._links && plugin._links.update) - .map((plugin) => ( + .filter(plugin => plugin._links && plugin._links.update) + .map(plugin => (
  • {plugin.name}
  • ))}
@@ -91,7 +91,7 @@ class PluginActionModal extends React.Component { <> {t("plugins.modal.installQueue")}
    - {pendingPlugins._embedded.new.map((plugin) => ( + {pendingPlugins._embedded.new.map(plugin => (
  • {plugin.name}
  • ))}
@@ -109,7 +109,7 @@ class PluginActionModal extends React.Component { <> {t("plugins.modal.updateQueue")}
    - {pendingPlugins._embedded.update.map((plugin) => ( + {pendingPlugins._embedded.update.map(plugin => (
  • {plugin.name}
  • ))}
@@ -127,7 +127,7 @@ class PluginActionModal extends React.Component { <> {t("plugins.modal.uninstallQueue")}
    - {pendingPlugins._embedded.uninstall.map((plugin) => ( + {pendingPlugins._embedded.uninstall.map(plugin => (
  • {plugin.name}
  • ))}
diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginAvatar.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginAvatar.tsx index 794a98f3e4..13517528c6 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginAvatar.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginAvatar.tsx @@ -47,7 +47,7 @@ export default class PluginAvatar extends React.Component { diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx index 9278c52913..71c8c19f3b 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx @@ -40,8 +40,8 @@ const ActionbarWrapper = styled.div` } `; -const IconWrapperStyle = styled.span.attrs((props) => ({ - className: "level-item mb-0 p-2 is-clickable", +const IconWrapperStyle = styled.span.attrs(props => ({ + className: "level-item mb-0 p-2 is-clickable" }))` border: 1px solid #cdcdcd; // $dark-25 border-radius: 4px; @@ -53,7 +53,7 @@ const IconWrapperStyle = styled.span.attrs((props) => ({ const IconWrapper: FC<{ action: () => void }> = ({ action, children }) => { return ( - e.key === "Enter" && action()} tabIndex={0}> + e.key === "Enter" && action()} tabIndex={0}> {children} ); diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx index e47e979bcb..7021080343 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx @@ -33,7 +33,7 @@ type Props = { }; const PluginGroupEntry: FC = ({ openModal, group }) => { - const entries = group.plugins.map((plugin) => { + const entries = group.plugins.map(plugin => { return ; }); return ; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx index b409fecd05..feca1f71a4 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx @@ -36,7 +36,7 @@ const PluginList: FC = ({ plugins, openModal }) => { const groups = groupByCategory(plugins); return (
- {groups.map((group) => { + {groups.map(group => { return ; })}
diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx index 259dfb1bd5..3eba219ab3 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx @@ -41,10 +41,10 @@ type ParentWithPluginAction = { pluginAction?: PluginAction; }; -const ListParent = styled.div.attrs((props) => ({ - className: "field-label is-inline-flex mr-0 has-text-left", +const ListParent = styled.div.attrs(props => ({ + className: "field-label is-inline-flex mr-0 has-text-left" }))` - min-width: ${(props) => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")}; + min-width: ${props => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")}; `; const ListChild = styled.div` @@ -236,7 +236,7 @@ const PluginModal: FC = ({ onClose, pluginAction, plugin }) => { return ( { return ( {this.createMessageForPluginAction()}{" "} - window.location.reload(true)} className="has-text-info"> + window.location.reload(true)} className="has-text-info"> {t("plugins.modal.reload")} diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/groupByCategory.ts b/scm-ui/ui-webapp/src/admin/plugins/components/groupByCategory.ts index 16871ca62f..c92a7faed2 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/groupByCategory.ts +++ b/scm-ui/ui-webapp/src/admin/plugins/components/groupByCategory.ts @@ -33,7 +33,7 @@ export default function groupByCategory(plugins: Plugin[]): PluginGroup[] { if (!group) { group = { name: groupName, - plugins: [], + plugins: [] }; groups[groupName] = group; } diff --git a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx index e0bf31f868..d13b2c3d80 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx @@ -32,7 +32,7 @@ import { Loading, Notification, Subtitle, - Title, + Title } from "@scm-manager/ui-components"; import PluginsList from "../components/PluginList"; import PluginTopActions from "../components/PluginTopActions"; @@ -48,7 +48,7 @@ export enum PluginAction { INSTALL = "install", UPDATE = "update", UNINSTALL = "uninstall", - CLOUDOGU = "cloudoguInstall", + CLOUDOGU = "cloudoguInstall" } export type PluginModalContent = { @@ -65,12 +65,12 @@ const PluginsOverview: FC = ({ installed }) => { const { data: availablePlugins, isLoading: isLoadingAvailablePlugins, - error: availablePluginsError, + error: availablePluginsError } = useAvailablePlugins({ enabled: !installed }); const { data: installedPlugins, isLoading: isLoadingInstalledPlugins, - error: installedPluginsError, + error: installedPluginsError } = useInstalledPlugins({ enabled: installed }); const { data: pendingPlugins, isLoading: isLoadingPendingPlugins, error: pendingPluginsError } = usePendingPlugins(); const [showPendingModal, setShowPendingModal] = useState(false); @@ -167,7 +167,7 @@ const PluginsOverview: FC = ({ installed }) => { const computeUpdateAllSize = () => { const outdatedPlugins = collection?._embedded.plugins.filter((p: Plugin) => p._links.update).length; return t("plugins.outdatedPlugins", { - count: outdatedPlugins, + count: outdatedPlugins }); }; diff --git a/scm-ui/ui-webapp/src/admin/roles/components/PermissionRoleDetails.tsx b/scm-ui/ui-webapp/src/admin/roles/components/PermissionRoleDetails.tsx index 86c926fa50..a00e5a8e86 100644 --- a/scm-ui/ui-webapp/src/admin/roles/components/PermissionRoleDetails.tsx +++ b/scm-ui/ui-webapp/src/admin/roles/components/PermissionRoleDetails.tsx @@ -58,7 +58,7 @@ class PermissionRoleDetails extends React.Component { name="repositoryRole.role-details.information" renderAll={true} props={{ - role, + role }} /> diff --git a/scm-ui/ui-webapp/src/admin/roles/containers/DeleteRepositoryRole.tsx b/scm-ui/ui-webapp/src/admin/roles/containers/DeleteRepositoryRole.tsx index f675a1abfb..9c7db53587 100644 --- a/scm-ui/ui-webapp/src/admin/roles/containers/DeleteRepositoryRole.tsx +++ b/scm-ui/ui-webapp/src/admin/roles/containers/DeleteRepositoryRole.tsx @@ -67,12 +67,12 @@ const DeleteRepositoryRole: FC = ({ confirmDialog = true, role }: Props) { className: "is-outlined", label: t("repositoryRole.delete.confirmAlert.submit"), - onClick: deleteRoleCallback, + onClick: deleteRoleCallback }, { label: t("repositoryRole.delete.confirmAlert.cancel"), - onClick: () => null, - }, + onClick: () => null + } ]} close={() => setShowConfirmAlert(false)} /> diff --git a/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoleForm.tsx b/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoleForm.tsx index 10f28f0b10..a524f0b52b 100644 --- a/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoleForm.tsx +++ b/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoleForm.tsx @@ -42,8 +42,8 @@ const RepositoryRoleForm: FC = ({ role: initialRole, submitForm }) => { name: "", verbs: [], _links: { - create: { href: createLink }, - }, + create: { href: createLink } + } } ); const availableVerbs = data?.verbs; @@ -58,7 +58,7 @@ const RepositoryRoleForm: FC = ({ role: initialRole, submitForm }) => { const handleVerbChange = (value: boolean, name: string) => setRole({ ...role, - verbs: value ? [...role.verbs, name] : role.verbs.filter((v) => v !== name), + verbs: value ? [...role.verbs, name] : role.verbs.filter(v => v !== name) }); const submit = (event: FormEvent) => { diff --git a/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx b/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx index 62b69754ef..f3c64062c1 100644 --- a/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx +++ b/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx @@ -32,7 +32,7 @@ import { Notification, Subtitle, Title, - urls, + urls } from "@scm-manager/ui-components"; import PermissionRoleTable from "../components/PermissionRoleTable"; import { useRepositoryRoles } from "@scm-manager/ui-api"; diff --git a/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx b/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx index 295764a949..c13eeb64a4 100644 --- a/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx +++ b/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx @@ -50,7 +50,7 @@ const SingleRepositoryRole: FC = () => { const extensionProps = { role, - url, + url }; return ( diff --git a/scm-ui/ui-webapp/src/components/LoginForm.tsx b/scm-ui/ui-webapp/src/components/LoginForm.tsx index d36fc4d567..65808dfdd2 100644 --- a/scm-ui/ui-webapp/src/components/LoginForm.tsx +++ b/scm-ui/ui-webapp/src/components/LoginForm.tsx @@ -60,7 +60,7 @@ class LoginForm extends React.Component { super(props); this.state = { username: "", - password: "", + password: "" }; } @@ -73,13 +73,13 @@ class LoginForm extends React.Component { handleUsernameChange = (value: string) => { this.setState({ - username: value, + username: value }); }; handlePasswordChange = (value: string) => { this.setState({ - password: value, + password: value }); }; diff --git a/scm-ui/ui-webapp/src/components/LoginInfo.tsx b/scm-ui/ui-webapp/src/components/LoginInfo.tsx index eb380d85cd..bfbf900bda 100644 --- a/scm-ui/ui-webapp/src/components/LoginInfo.tsx +++ b/scm-ui/ui-webapp/src/components/LoginInfo.tsx @@ -76,23 +76,23 @@ class LoginInfo extends React.Component { constructor(props: Props) { super(props); this.state = { - loading: !!props.loginInfoLink, + loading: !!props.loginInfoLink }; } fetchLoginInfo = (url: string) => { return fetch(url) - .then((response) => response.json()) - .then((info) => { + .then(response => response.json()) + .then(info => { this.setState({ info, - loading: false, + loading: false }); }); }; - timeout = (ms: number, promise: Promise) => { - return new Promise((resolve, reject) => { + timeout = (ms: number, promise: Promise) => { + return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("timeout during fetch of login info")); }, ms); @@ -107,7 +107,7 @@ class LoginInfo extends React.Component { } this.timeout(1000, this.fetchLoginInfo(loginInfoLink)).catch(() => { this.setState({ - loading: false, + loading: false }); }); } diff --git a/scm-ui/ui-webapp/src/containers/ChangeUserPassword.tsx b/scm-ui/ui-webapp/src/containers/ChangeUserPassword.tsx index d30e0af69d..71464c1a94 100644 --- a/scm-ui/ui-webapp/src/containers/ChangeUserPassword.tsx +++ b/scm-ui/ui-webapp/src/containers/ChangeUserPassword.tsx @@ -29,7 +29,7 @@ import { Notification, PasswordConfirmation, SubmitButton, - Subtitle, + Subtitle } from "@scm-manager/ui-components"; import { useTranslation } from "react-i18next"; import { Me } from "@scm-manager/ui-types"; diff --git a/scm-ui/ui-webapp/src/containers/Index.tsx b/scm-ui/ui-webapp/src/containers/Index.tsx index cada9064a9..e1e2be082b 100644 --- a/scm-ui/ui-webapp/src/containers/Index.tsx +++ b/scm-ui/ui-webapp/src/containers/Index.tsx @@ -39,7 +39,7 @@ const Index: FC = () => { // TODO check componentDidUpdate method for anonymous user stuff - i18next.on("languageChanged", (lng) => { + i18next.on("languageChanged", lng => { document.documentElement.setAttribute("lang", lng); }); diff --git a/scm-ui/ui-webapp/src/containers/InitializationAdminAccountStep.tsx b/scm-ui/ui-webapp/src/containers/InitializationAdminAccountStep.tsx index 4a17483ce8..788a4a08b2 100644 --- a/scm-ui/ui-webapp/src/containers/InitializationAdminAccountStep.tsx +++ b/scm-ui/ui-webapp/src/containers/InitializationAdminAccountStep.tsx @@ -48,21 +48,15 @@ type AdminAccountCreation = { passwordConfirmation: string; }; -const createAdmin = (link: string) => { - return (data: AdminAccountCreation) => { - return apiClient.post(link, data, "application/json").then(() => { - return new Promise((resolve) => resolve()); - }); - }; -}; +const createAdmin = (link: string) => (data: AdminAccountCreation) => apiClient.post(link, data, "application/json"); const useCreateAdmin = (link: string) => { - const { mutate, isLoading, error, isSuccess } = useMutation(createAdmin(link)); + const { mutate, isLoading, error, isSuccess } = useMutation(createAdmin(link)); return { create: mutate, isLoading, error, - isCreated: isSuccess, + isCreated: isSuccess }; }; @@ -74,16 +68,16 @@ const InitializationAdminAccountStep: FC = ({ data }) => { displayName: "SCM Administrator", email: "", password: "", - passwordConfirmation: "", + passwordConfirmation: "" }, - mode: "onChange", + mode: "onChange" }); const { create, isLoading, error, isCreated } = useCreateAdmin((data._links.initialAdminUser as Link).href); useEffect(() => { if (isCreated) { - window.location.reload(false); + window.location.reload(); } }, [isCreated]); diff --git a/scm-ui/ui-webapp/src/containers/LoginButton.tsx b/scm-ui/ui-webapp/src/containers/LoginButton.tsx index 375c99a18b..905a68f8cd 100644 --- a/scm-ui/ui-webapp/src/containers/LoginButton.tsx +++ b/scm-ui/ui-webapp/src/containers/LoginButton.tsx @@ -59,7 +59,7 @@ const LoginButton: FC = ({ burgerMode, links, className }) => { from, to, className: headerButtonContentClassName, - content, + content }; if (links?.login) { diff --git a/scm-ui/ui-webapp/src/containers/LogoutButton.tsx b/scm-ui/ui-webapp/src/containers/LogoutButton.tsx index 1c83921c8e..887dddd7dd 100644 --- a/scm-ui/ui-webapp/src/containers/LogoutButton.tsx +++ b/scm-ui/ui-webapp/src/containers/LogoutButton.tsx @@ -48,7 +48,7 @@ const LogoutButton: FC = ({ burgerMode, links, className }) => { label, className: headerButtonContentClassName, - content, + content }; if (links?.logout) { diff --git a/scm-ui/ui-webapp/src/containers/Main.tsx b/scm-ui/ui-webapp/src/containers/Main.tsx index 1bc40749c6..a7bcfeec89 100644 --- a/scm-ui/ui-webapp/src/containers/Main.tsx +++ b/scm-ui/ui-webapp/src/containers/Main.tsx @@ -56,7 +56,7 @@ type Props = { authenticated?: boolean; }; -const Main: FC = (props) => { +const Main: FC = props => { const { authenticated, me } = props; const redirectUrlFactory = binder.getExtension("main.redirect", props); let url = "/"; diff --git a/scm-ui/ui-webapp/src/containers/Notifications.tsx b/scm-ui/ui-webapp/src/containers/Notifications.tsx index b597c86ccb..b0ba6e19e9 100644 --- a/scm-ui/ui-webapp/src/containers/Notifications.tsx +++ b/scm-ui/ui-webapp/src/containers/Notifications.tsx @@ -30,7 +30,7 @@ import { useClearNotifications, useDismissNotification, useNotifications, - useNotificationSubscription, + useNotificationSubscription } from "@scm-manager/ui-api"; import { Notification, NotificationCollection } from "@scm-manager/ui-types"; import { @@ -43,7 +43,7 @@ import { ToastType, Loading, DateFromNow, - devices, + devices } from "@scm-manager/ui-components"; const DropDownMenu = styled.div` @@ -283,7 +283,7 @@ type NotificationCounterProps = { const NotificationCounter = styled.span` position: absolute; top: -0.5rem; - right: ${(props) => (props.count < 10 ? "0" : "-0.25")}rem; + right: ${props => (props.count < 10 ? "0" : "-0.25")}rem; `; type BellNotificationIconProps = { @@ -351,14 +351,14 @@ const Notifications: FC = ({ className }) => { "dropdown", "is-hoverable", { - "is-active": open, + "is-active": open }, className )} - onClick={(e) => e.stopPropagation()} + onClick={e => e.stopPropagation()} >
- setOpen((o) => !o)} /> + setOpen(o => !o)} />
diff --git a/scm-ui/ui-webapp/src/containers/OmniSearch.tsx b/scm-ui/ui-webapp/src/containers/OmniSearch.tsx index 6c5737c4bf..ce4fc423c8 100644 --- a/scm-ui/ui-webapp/src/containers/OmniSearch.tsx +++ b/scm-ui/ui-webapp/src/containers/OmniSearch.tsx @@ -328,7 +328,7 @@ const OmniSearch: FC = () => { } }; - const { onKeyDown, index } = useKeyBoardNavigation(gotoDetailSearch, clearQuery, data?._embedded.hits); + const { onKeyDown, index } = useKeyBoardNavigation(gotoDetailSearch, clearQuery, data?._embedded?.hits); return (
@@ -372,7 +372,7 @@ const OmniSearch: FC = () => { gotoDetailSearch={gotoDetailSearch} clear={clearQuery} index={index} - hits={data._embedded.hits} + hits={data._embedded?.hits || []} /> ) : null} diff --git a/scm-ui/ui-webapp/src/containers/PluginLoader.tsx b/scm-ui/ui-webapp/src/containers/PluginLoader.tsx index 3f5d8900ae..9a7395f444 100644 --- a/scm-ui/ui-webapp/src/containers/PluginLoader.tsx +++ b/scm-ui/ui-webapp/src/containers/PluginLoader.tsx @@ -53,7 +53,7 @@ class PluginLoader extends React.Component { constructor(props: Props) { super(props); this.state = { - message: "booting", + message: "booting" }; } @@ -61,26 +61,26 @@ class PluginLoader extends React.Component { const { loaded } = this.props; if (!loaded) { this.setState({ - message: "loading plugin information", + message: "loading plugin information" }); this.getPlugins(this.props.link); } } - getPlugins = (link: string): Promise => { - return apiClient + getPlugins = (link: string) => { + apiClient .get(link) - .then((response) => response.text()) + .then(response => response.text()) .then(JSON.parse) - .then((pluginCollection) => pluginCollection._embedded.plugins) + .then(pluginCollection => pluginCollection._embedded.plugins) .then(this.loadPlugins) .then(this.props.callback); }; loadPlugins = (plugins: Plugin[]) => { this.setState({ - message: "loading plugins", + message: "loading plugins" }); const promises = []; @@ -89,21 +89,21 @@ class PluginLoader extends React.Component { promises.push(this.loadPlugin(plugin)); } return promises.reduce((chain, current) => { - return chain.then((chainResults) => { - return current.then((currentResult) => [...chainResults, currentResult]); + return chain.then(chainResults => { + return current.then(currentResult => [...chainResults, currentResult]); }); }, Promise.resolve([])); }; loadPlugin = (plugin: Plugin) => { this.setState({ - message: `loading ${plugin.name}`, + message: `loading ${plugin.name}` }); const promises = []; for (const bundle of plugin.bundles) { promises.push( - loadBundle(bundle).catch((error) => this.setState({ error, errorMessage: `loading ${plugin.name} failed` })) + loadBundle(bundle).catch(error => this.setState({ error, errorMessage: `loading ${plugin.name} failed` })) ); } return Promise.all(promises); diff --git a/scm-ui/ui-webapp/src/containers/ProfileInfo.tsx b/scm-ui/ui-webapp/src/containers/ProfileInfo.tsx index 5a65938604..7f86f39b60 100644 --- a/scm-ui/ui-webapp/src/containers/ProfileInfo.tsx +++ b/scm-ui/ui-webapp/src/containers/ProfileInfo.tsx @@ -77,7 +77,7 @@ class ProfileInfo extends React.Component {
{item}{t("profile.groups")}
    - {me.groups.map((group) => { + {me.groups.map(group => { return
  • {group}
  • ; })}
diff --git a/scm-ui/ui-webapp/src/containers/ScrollToTop.ts b/scm-ui/ui-webapp/src/containers/ScrollToTop.tsx similarity index 76% rename from scm-ui/ui-webapp/src/containers/ScrollToTop.ts rename to scm-ui/ui-webapp/src/containers/ScrollToTop.tsx index 32fe4b61e4..541e22c462 100644 --- a/scm-ui/ui-webapp/src/containers/ScrollToTop.ts +++ b/scm-ui/ui-webapp/src/containers/ScrollToTop.tsx @@ -22,24 +22,23 @@ * SOFTWARE. */ -import React from "react"; -import { withRouter } from "react-router-dom"; +import { FC, ReactElement, useEffect, useRef } from "react"; type Props = { - location: any; - children: any; + location?: Location; + children: ReactElement; }; -class ScrollToTop extends React.Component { - componentDidUpdate(prevProps) { - if (this.props.location.pathname !== prevProps.location.pathname) { +const ScrollToTop: FC = ({ location, children }) => { + const previousLocation = useRef(location); + + useEffect(() => { + if (previousLocation?.current?.pathname !== location?.pathname) { window.scrollTo(0, 0); } - } + }, [location]); - render() { - return this.props.children; - } -} + return children; +}; -export default withRouter(ScrollToTop); +export default ScrollToTop; diff --git a/scm-ui/ui-webapp/src/containers/loadBundle.ts b/scm-ui/ui-webapp/src/containers/loadBundle.ts index 33847e18ef..ea8a6f955c 100644 --- a/scm-ui/ui-webapp/src/containers/loadBundle.ts +++ b/scm-ui/ui-webapp/src/containers/loadBundle.ts @@ -55,12 +55,12 @@ const BundleLoader = { headers: { Cache: "no-cache", // identify the request as ajax request - "X-Requested-With": "XMLHttpRequest", - }, - }).then((response) => { + "X-Requested-With": "XMLHttpRequest" + } + }).then(response => { return response.text(); }); - }, + } }; SystemJS.registry.set(BundleLoader.name, SystemJS.newModule(BundleLoader)); @@ -69,12 +69,13 @@ SystemJS.config({ baseURL: urls.withContextPath("/assets"), meta: { "/*": { + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore typing missing, but seems required esModule: true, authorization: true, - loader: BundleLoader.name, - }, - }, + loader: BundleLoader.name + } + } }); // We have to patch the resolve methods of SystemJS @@ -89,17 +90,18 @@ const resolveModuleUrl = (key: string) => { }; const defaultResolve = SystemJS.resolve; -SystemJS.resolve = function (key, parentName) { +SystemJS.resolve = function(key, parentName) { const module = resolveModuleUrl(key); return defaultResolve.apply(this, [module, parentName]); }; const defaultResolveSync = SystemJS.resolveSync; -SystemJS.resolveSync = function (key, parentName) { +SystemJS.resolveSync = function(key, parentName) { const module = resolveModuleUrl(key); return defaultResolveSync.apply(this, [module, parentName]); }; +//eslint-disable-next-line @typescript-eslint/no-explicit-any const expose = (name: string, cmp: any, defaultCmp?: any) => { let mod = cmp; if (defaultCmp) { @@ -107,7 +109,7 @@ const expose = (name: string, cmp: any, defaultCmp?: any) => { // https://github.com/systemjs/systemjs/issues/1749 mod = { ...cmp, - __useDefault: defaultCmp, + __useDefault: defaultCmp }; } SystemJS.set(name, SystemJS.newModule(mod)); diff --git a/scm-ui/ui-webapp/src/groups/components/GroupForm.tsx b/scm-ui/ui-webapp/src/groups/components/GroupForm.tsx index ce9afbcd42..73ad1cfee4 100644 --- a/scm-ui/ui-webapp/src/groups/components/GroupForm.tsx +++ b/scm-ui/ui-webapp/src/groups/components/GroupForm.tsx @@ -32,7 +32,7 @@ import { MemberNameTagGroup, SubmitButton, Subtitle, - Textarea, + Textarea } from "@scm-manager/ui-components"; import * as validator from "./groupValidation"; @@ -40,7 +40,7 @@ type Props = WithTranslation & { submitForm: (p: Group) => void; loading?: boolean; group?: Group; - loadUserSuggestions: (p: string) => any; + loadUserSuggestions: (p: string) => Promise; }; type State = { @@ -56,14 +56,14 @@ class GroupForm extends React.Component { name: "", description: "", _embedded: { - members: [], + members: [] }, _links: {}, members: [], type: "", - external: false, + external: false }, - nameValidationError: false, + nameValidationError: false }; } @@ -73,8 +73,8 @@ class GroupForm extends React.Component { this.setState({ ...this.state, group: { - ...group, - }, + ...group + } }); } } @@ -183,13 +183,13 @@ class GroupForm extends React.Component { ); } - memberListChanged = (membernames) => { + memberListChanged = membernames => { this.setState({ ...this.state, group: { ...this.state.group, - members: membernames, - }, + members: membernames + } }); }; @@ -202,8 +202,8 @@ class GroupForm extends React.Component { ...this.state, group: { ...this.state.group, - members: [...this.state.group.members, value.value.id], - }, + members: [...this.state.group.members, value.value.id] + } }); }; @@ -216,8 +216,8 @@ class GroupForm extends React.Component { nameValidationError: !validator.isNameValid(name), group: { ...this.state.group, - name, - }, + name + } }); }; @@ -225,8 +225,8 @@ class GroupForm extends React.Component { this.setState({ group: { ...this.state.group, - description, - }, + description + } }); }; @@ -234,8 +234,8 @@ class GroupForm extends React.Component { this.setState({ group: { ...this.state.group, - external, - }, + external + } }); }; } diff --git a/scm-ui/ui-webapp/src/groups/components/navLinks/EditGroupNavLink.test.tsx b/scm-ui/ui-webapp/src/groups/components/navLinks/EditGroupNavLink.test.tsx index 1d770dc6d0..34b6941b90 100644 --- a/scm-ui/ui-webapp/src/groups/components/navLinks/EditGroupNavLink.test.tsx +++ b/scm-ui/ui-webapp/src/groups/components/navLinks/EditGroupNavLink.test.tsx @@ -23,13 +23,12 @@ */ import React from "react"; import { shallow } from "enzyme"; -import "@scm-manager/ui-tests/enzyme"; -import "@scm-manager/ui-tests/i18n"; +import "@scm-manager/ui-tests"; import EditGroupNavLink from "./EditGroupNavLink"; it("should render nothing, if the edit link is missing", () => { const group = { - _links: {}, + _links: {} }; const navLink = shallow(); @@ -40,9 +39,9 @@ it("should render the navLink", () => { const group = { _links: { update: { - href: "/groups", - }, - }, + href: "/groups" + } + } }; const navLink = shallow(); diff --git a/scm-ui/ui-webapp/src/groups/components/table/GroupMember.tsx b/scm-ui/ui-webapp/src/groups/components/table/GroupMember.tsx index f20c252cc3..83659477cb 100644 --- a/scm-ui/ui-webapp/src/groups/components/table/GroupMember.tsx +++ b/scm-ui/ui-webapp/src/groups/components/table/GroupMember.tsx @@ -46,7 +46,7 @@ export default class GroupMember extends React.Component { ); } - showName(to: any, member: Member) { + showName(to: string, member: Member) { if (member._links.self) { return this.renderLink(to, member.name); } else { diff --git a/scm-ui/ui-webapp/src/groups/containers/DeleteGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/DeleteGroup.tsx index bc9dbd0e85..8c55be6710 100644 --- a/scm-ui/ui-webapp/src/groups/containers/DeleteGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/DeleteGroup.tsx @@ -62,12 +62,12 @@ export const DeleteGroup: FC = ({ confirmDialog = true, group }) => { { className: "is-outlined", label: t("deleteGroup.confirmAlert.submit"), - onClick: deleteGroupCallback, + onClick: deleteGroupCallback }, { label: t("deleteGroup.confirmAlert.cancel"), - onClick: () => null, - }, + onClick: () => null + } ]} close={() => setShowConfirmAlert(false)} /> diff --git a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx index 8d88c95812..e42f0e028d 100644 --- a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx @@ -31,7 +31,7 @@ import { OverviewPageActions, Page, PageActions, - urls, + urls } from "@scm-manager/ui-components"; import { GroupTable } from "./../components/table"; import { useGroups } from "@scm-manager/ui-api"; diff --git a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx index 983708e315..0711aef0d6 100644 --- a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx @@ -36,7 +36,7 @@ import { SecondaryNavigationColumn, StateMenuContextProvider, SubNavigation, - urls, + urls } from "@scm-manager/ui-components"; import { Details } from "./../components/table"; import { EditGroupNavLink, SetPermissionsNavLink } from "./../components/navLinks"; @@ -62,7 +62,7 @@ const SingleGroup: FC = () => { const extensionProps = { group, - url, + url }; return ( diff --git a/scm-ui/ui-webapp/src/i18n.ts b/scm-ui/ui-webapp/src/i18n.ts index 1153e0f35b..a72e4f001a 100644 --- a/scm-ui/ui-webapp/src/i18n.ts +++ b/scm-ui/ui-webapp/src/i18n.ts @@ -23,6 +23,7 @@ */ import i18n from "i18next"; +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore import Backend from "i18next-fetch-backend"; import LanguageDetector from "i18next-browser-languagedetector"; @@ -48,19 +49,19 @@ i18n debug: false, interpolation: { - escapeValue: false, // not needed for react!! + escapeValue: false // not needed for react!! }, react: { wait: true, - useSuspense: false, + useSuspense: false }, backend: { loadPath: loadPath, init: { - credentials: "same-origin", - }, + credentials: "same-origin" + } }, // configure LanguageDetector @@ -69,8 +70,8 @@ i18n // we only use browser configuration order: ["navigator"], // we do not cache the detected language - caches: [], - }, + caches: [] + } }); export default i18n; diff --git a/scm-ui/ui-webapp/src/permissions/components/PermissionsWrapper.tsx b/scm-ui/ui-webapp/src/permissions/components/PermissionsWrapper.tsx index 9a21bf9126..100535beb0 100644 --- a/scm-ui/ui-webapp/src/permissions/components/PermissionsWrapper.tsx +++ b/scm-ui/ui-webapp/src/permissions/components/PermissionsWrapper.tsx @@ -55,7 +55,7 @@ export default class PermissionsWrapper extends React.Component { return (
- {permissionArray.slice(0, permissionArray.length / 2 + 1).map((p) => ( + {permissionArray.slice(0, permissionArray.length / 2 + 1).map(p => ( { ))} - {permissionArray.slice(permissionArray.length / 2 + 1, permissionArray.length).map((p) => ( + {permissionArray.slice(permissionArray.length / 2 + 1, permissionArray.length).map(p => ( = ({ group }) => { - const { - data: selectedPermissions, - isLoading: loadingPermissions, - error: permissionsLoadError, - } = useGroupPermissions(group); + const { data: selectedPermissions, isLoading: loadingPermissions, error: permissionsLoadError } = useGroupPermissions( + group + ); const { isLoading: isUpdatingPermissions, isUpdated: permissionsUpdated, setPermissions, - error: permissionsUpdateError, + error: permissionsUpdateError } = useSetGroupPermissions(group, selectedPermissions); return ( = ({ permissionsUpdateError, updatePermissions, permissionsUpdated, - selectedPermissions, + selectedPermissions }) => { const [t] = useTranslation("permissions"); const { data: availablePermissions, error: availablePermissionsLoadError, - isLoading: isLoadingAvailablePermissions, + isLoading: isLoadingAvailablePermissions } = useAvailableGlobalPermissions(); const [permissions, setPermissions] = useState>({}); const [permissionsSubmitted, setPermissionsSubmitted] = useState(false); @@ -61,8 +61,8 @@ const SetPermissions: FC = ({ useEffect(() => { if (selectedPermissions && availablePermissions) { const newPermissions: Record = {}; - availablePermissions.permissions.forEach((p) => (newPermissions[p] = false)); - selectedPermissions.permissions.forEach((p) => (newPermissions[p] = true)); + availablePermissions.permissions.forEach(p => (newPermissions[p] = false)); + selectedPermissions.permissions.forEach(p => (newPermissions[p] = true)); setPermissions(newPermissions); } }, [availablePermissions, selectedPermissions]); @@ -85,7 +85,7 @@ const SetPermissions: FC = ({ const valueChanged = (value: boolean, name: string) => { setPermissions({ ...permissions, - [name]: value, + [name]: value }); setPermissionsChanged(true); }; @@ -96,9 +96,11 @@ const SetPermissions: FC = ({ event.preventDefault(); if (permissions) { const selectedPermissions = Object.entries(permissions) - .filter((e) => e[1]) - .map((e) => e[0]); - updatePermissions!(selectedPermissions); + .filter(e => e[1]) + .map(e => e[0]); + if (updatePermissions) { + updatePermissions(selectedPermissions); + } } }; diff --git a/scm-ui/ui-webapp/src/permissions/components/SetUserPermissions.tsx b/scm-ui/ui-webapp/src/permissions/components/SetUserPermissions.tsx index d8a75fcc9c..98065a53d8 100644 --- a/scm-ui/ui-webapp/src/permissions/components/SetUserPermissions.tsx +++ b/scm-ui/ui-webapp/src/permissions/components/SetUserPermissions.tsx @@ -31,16 +31,14 @@ type Props = { }; const SetUserPermissions: FC = ({ user }) => { - const { - data: selectedPermissions, - isLoading: loadingPermissions, - error: permissionsLoadError, - } = useUserPermissions(user); + const { data: selectedPermissions, isLoading: loadingPermissions, error: permissionsLoadError } = useUserPermissions( + user + ); const { isLoading: isUpdatingPermissions, isUpdated: permissionsUpdated, setPermissions, - error: permissionsUpdateError, + error: permissionsUpdateError } = useSetUserPermissions(user, selectedPermissions); return ( void; - repository: Repository; branches: Branch[]; loading?: boolean; transmittedName?: string; disabled?: boolean; }; -type State = { - source?: string; - name?: string; - nameValidationError: boolean; -}; +const BranchForm: FC = ({ submitForm, branches, disabled, transmittedName, loading }) => { + const [t] = useTranslation("repos"); + const [name, setName] = useState(transmittedName || ""); + const [source, setSource] = useState(""); + const [nameValid, setNameValid] = useState(false); -class BranchForm extends React.Component { - constructor(props: Props) { - super(props); + useEffect(() => { + setNameValid(validator.isBranchValid(name)); + }, [name]); - this.state = { - nameValidationError: false, - name: props.transmittedName, - }; - } + const isValid = () => nameValid && source && name; - isFalsy(value?: string) { - return !value; - } - - isValid = () => { - const { source, name } = this.state; - return !(this.state.nameValidationError || this.isFalsy(source) || this.isFalsy(name)); - }; - - submit = (event: FormEvent) => { + const submit = (event: FormEvent) => { event.preventDefault(); - if (this.isValid()) { - this.props.submitForm({ - name: this.state.name!, - parent: this.state.source!, + if (isValid()) { + submitForm({ + name, + parent: source }); } }; - render() { - const { t, branches, loading, transmittedName, disabled } = this.props; - const { name } = this.state; - orderBranches(branches); - const options = branches.map((branch) => ({ - label: branch.name, - value: branch.name, - })); + orderBranches(branches); + const options = branches.map(branch => ({ + label: branch.name, + value: branch.name + })); - return ( - <> -
-
-
- +
-
-
- - } - /> -
+
+
+
+ + } + />
- - - ); - } +
+ + + ); +}; - handleSourceChange = (source: string) => { - this.setState({ - source, - }); - }; - - handleNameChange = (name: string) => { - this.setState({ - nameValidationError: !validator.isBranchValid(name), - name, - }); - }; -} - -export default withTranslation("repos")(BranchForm); +export default BranchForm; diff --git a/scm-ui/ui-webapp/src/repos/branches/components/BranchView.tsx b/scm-ui/ui-webapp/src/repos/branches/components/BranchView.tsx index 7c89fa2599..d7d13e43e3 100644 --- a/scm-ui/ui-webapp/src/repos/branches/components/BranchView.tsx +++ b/scm-ui/ui-webapp/src/repos/branches/components/BranchView.tsx @@ -45,7 +45,7 @@ class BranchView extends React.Component { renderAll={true} props={{ repository, - branch, + branch }} />
diff --git a/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx b/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx index 3ab3d76539..f09030a306 100644 --- a/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx +++ b/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx @@ -40,9 +40,15 @@ const CreateBranch: FC = ({ repository }) => { const location = useLocation(); const [t] = useTranslation("repos"); - const transmittedName = (url: string) => { - const params = queryString.parse(url); - return params.name; + const transmittedName = (url: string): string | undefined => { + const paramsName = queryString.parse(url).name; + if (paramsName === null) { + return undefined; + } + if (Array.isArray(paramsName)) { + return paramsName[0]; + } + return paramsName; }; if (createdBranch) { @@ -68,8 +74,7 @@ const CreateBranch: FC = ({ repository }) => { diff --git a/scm-ui/ui-webapp/src/repos/branches/util/orderBranches.test.ts b/scm-ui/ui-webapp/src/repos/branches/util/orderBranches.test.ts index 2f956efe96..13024ace8a 100644 --- a/scm-ui/ui-webapp/src/repos/branches/util/orderBranches.test.ts +++ b/scm-ui/ui-webapp/src/repos/branches/util/orderBranches.test.ts @@ -26,36 +26,36 @@ import { orderBranches } from "./orderBranches"; const branch1 = { name: "branch1", - revision: "revision1", + revision: "revision1" }; const branch2 = { name: "branch2", - revision: "revision2", + revision: "revision2" }; const branch3 = { name: "branch3", revision: "revision3", - defaultBranch: true, + defaultBranch: true }; const defaultBranch = { name: "default", revision: "revision4", - defaultBranch: false, + defaultBranch: false }; const developBranch = { name: "develop", revision: "revision5", - defaultBranch: false, + defaultBranch: false }; const mainBranch = { name: "main", revision: "revision6", - defaultBranch: false, + defaultBranch: false }; const masterBranch = { name: "master", revision: "revision7", - defaultBranch: false, + defaultBranch: false }; describe("order branches", () => { diff --git a/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.test.ts b/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.test.ts index 1c38b681be..44ff43fc25 100644 --- a/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.test.ts +++ b/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.test.ts @@ -48,7 +48,7 @@ describe("filepathSearch tests", () => { "SomeResolver", "SomeTokenResolver", "accesstokenresolver", - "ActorExpression", + "ActorExpression" ]; const matches = filepathSearch(paths, "AcToRe"); diff --git a/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.ts b/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.ts index ed9bbff11d..22894d3c15 100644 --- a/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.ts +++ b/scm-ui/ui-webapp/src/repos/codeSection/utils/filepathSearch.ts @@ -33,10 +33,10 @@ declare global { export const filepathSearch = (paths: string[], query: string): string[] => { return paths .map(createMatcher(query)) - .filter((m) => m.matches) + .filter(m => m.matches) .sort((a, b) => b.score - a.score) .slice(0, 50) - .map((m) => m.path); + .map(m => m.path); }; const includes = (value: string, query: string) => { @@ -58,7 +58,7 @@ export const createMatcher = (query: string) => { return { matches: score > 0, score, - path, + path }; }; }; diff --git a/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.test.tsx b/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.test.tsx index 4822cb58ad..0fd665e93d 100644 --- a/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.test.tsx +++ b/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.test.tsx @@ -22,16 +22,16 @@ * SOFTWARE. */ import React from "react"; -import { mount, shallow } from "@scm-manager/ui-tests/enzyme-router"; -import "@scm-manager/ui-tests/enzyme"; -import "@scm-manager/ui-tests/i18n"; - import EditRepoNavLink from "./EditRepoNavLink"; +import { mount, shallow } from "@scm-manager/ui-tests"; describe("GeneralNavLink", () => { it("should render nothing, if the modify link is missing", () => { const repository = { - _links: {}, + namespace: "space", + name: "name", + type: "git", + _links: {} }; const navLink = shallow(); @@ -40,11 +40,14 @@ describe("GeneralNavLink", () => { it("should render the navLink", () => { const repository = { + namespace: "space", + name: "name", + type: "git", _links: { update: { - href: "/repositories", - }, - }, + href: "/repositories" + } + } }; const navLink = mount(); diff --git a/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.tsx b/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.tsx index eb2bbd165c..688418a077 100644 --- a/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.tsx +++ b/scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.tsx @@ -21,29 +21,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React from "react"; -import { WithTranslation, withTranslation } from "react-i18next"; +import React, { FC } from "react"; +import { useTranslation } from "react-i18next"; import { Repository } from "@scm-manager/ui-types"; import { NavLink } from "@scm-manager/ui-components"; -type Props = WithTranslation & { +type Props = { repository: Repository; editUrl: string; }; -class EditRepoNavLink extends React.Component { - isEditable = () => { - return this.props.repository._links.update; - }; - - render() { - const { editUrl, t } = this.props; - - if (!this.isEditable()) { - return null; - } - return ; +const EditRepoNavLink: FC = ({ repository, editUrl }) => { + const [t] = useTranslation("repos"); + if (!repository._links.update) { + return null; } -} + return ; +}; -export default withTranslation("repos")(EditRepoNavLink); +export default EditRepoNavLink; diff --git a/scm-ui/ui-webapp/src/repos/components/ImportFromBundleForm.tsx b/scm-ui/ui-webapp/src/repos/components/ImportFromBundleForm.tsx index cf6fd0133a..292757b890 100644 --- a/scm-ui/ui-webapp/src/repos/components/ImportFromBundleForm.tsx +++ b/scm-ui/ui-webapp/src/repos/components/ImportFromBundleForm.tsx @@ -43,7 +43,7 @@ const ImportFromBundleForm: FC = ({ setCompressed, password, setPassword, - disabled, + disabled }) => { const [t] = useTranslation("repos"); @@ -53,7 +53,7 @@ const ImportFromBundleForm: FC = ({
{ + handleFile={file => { setFile(file); setValid(!!file); }} @@ -74,7 +74,7 @@ const ImportFromBundleForm: FC = ({
setPassword(value)} + onChange={value => setPassword(value)} type="password" label={t("import.bundle.password.title")} helpText={t("import.bundle.password.helpText")} diff --git a/scm-ui/ui-webapp/src/repos/components/ImportFromUrlForm.tsx b/scm-ui/ui-webapp/src/repos/components/ImportFromUrlForm.tsx index f22b5be696..4ab75c82c7 100644 --- a/scm-ui/ui-webapp/src/repos/components/ImportFromUrlForm.tsx +++ b/scm-ui/ui-webapp/src/repos/components/ImportFromUrlForm.tsx @@ -47,7 +47,7 @@ const ImportFromUrlForm: FC = ({ repository, onChange, setValid, disabled const handleImportUrlBlur = (importUrl: string) => { if (!repository.name) { // If the repository name is not fill we set a name suggestion - const match = importUrl.match(/([^\/]+?)(?:.git)?$/); + const match = importUrl.match(/([^/]+?)(?:.git)?$/); if (match && match[1]) { onChange({ ...repository, name: match[1] }); } @@ -71,7 +71,7 @@ const ImportFromUrlForm: FC = ({ repository, onChange, setValid, disabled
onChange({ ...repository, username })} + onChange={username => onChange({ ...repository, username })} value={repository.username} helpText={t("help.usernameHelpText")} disabled={disabled} @@ -80,7 +80,7 @@ const ImportFromUrlForm: FC = ({ repository, onChange, setValid, disabled
onChange({ ...repository, password })} + onChange={password => onChange({ ...repository, password })} value={repository.password} type="password" helpText={t("help.passwordHelpText")} diff --git a/scm-ui/ui-webapp/src/repos/components/ImportFullRepository.tsx b/scm-ui/ui-webapp/src/repos/components/ImportFullRepository.tsx index 5ff33c19ac..925dd347fd 100644 --- a/scm-ui/ui-webapp/src/repos/components/ImportFullRepository.tsx +++ b/scm-ui/ui-webapp/src/repos/components/ImportFullRepository.tsx @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, FormEvent, useEffect, useState } from "react"; +import React, { FC, FormEvent, useCallback, useEffect, useState } from "react"; import { Repository, RepositoryCreation, RepositoryType } from "@scm-manager/ui-types"; import { ErrorNotification, Level, SubmitButton } from "@scm-manager/ui-components"; import { useTranslation } from "react-i18next"; @@ -42,35 +42,46 @@ const ImportFullRepository: FC = ({ setImportPending, setImportedRepository, nameForm: NameForm, - informationForm: InformationForm, + informationForm: InformationForm }) => { const [repo, setRepo] = useState({ name: "", namespace: "", type: repositoryType.name, contact: "", - description: "", - contextEntries: [], + description: "" }); const [password, setPassword] = useState(""); const [valid, setValid] = useState({ namespaceAndName: false, contact: true, file: false }); const [file, setFile] = useState(null); const [t] = useTranslation("repos"); const { importFullRepository, importedRepository, isLoading, error } = useImportFullRepository(repositoryType); + const setContactValid = useCallback((contact: boolean) => setValid(currentValid => ({ ...currentValid, contact })), [ + setValid + ]); + const setNamespaceAndNameValid = useCallback( + (namespaceAndName: boolean) => setValid(currentValid => ({ ...currentValid, namespaceAndName })), + [setValid] + ); + const setFileValid = useCallback((file: boolean) => setValid(currentValid => ({ ...currentValid, file })), [ + setValid + ]); - useEffect(() => setRepo({ ...repo, type: repositoryType.name }), [repositoryType]); - useEffect(() => setImportPending(isLoading), [isLoading]); + useEffect(() => setImportPending(isLoading), [isLoading, setImportPending]); useEffect(() => { if (importedRepository) { setImportedRepository(importedRepository); } - }, [importedRepository]); + }, [importedRepository, setImportedRepository]); - const isValid = () => Object.values(valid).every((v) => v); + const isValid = () => Object.values(valid).every(v => v); const submit = (event: FormEvent) => { event.preventDefault(); - importFullRepository(repo, file!, password); + if (!file) { + throw new Error("File is required for import"); + } + importFullRepository({ ...repo, type: repositoryType.name }, file, password); }; return ( @@ -80,20 +91,20 @@ const ImportFullRepository: FC = ({ setFile={setFile} password={password} setPassword={setPassword} - setValid={(file: boolean) => setValid({ ...valid, file })} + setValid={setFileValid} />
>} - setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })} + setValid={setNamespaceAndNameValid} disabled={isLoading} /> >} disabled={isLoading} - setValid={(contact: boolean) => setValid({ ...valid, contact })} + setValid={setContactValid} /> } diff --git a/scm-ui/ui-webapp/src/repos/components/ImportFullRepositoryForm.tsx b/scm-ui/ui-webapp/src/repos/components/ImportFullRepositoryForm.tsx index e85bf3a4d2..4d02f2096d 100644 --- a/scm-ui/ui-webapp/src/repos/components/ImportFullRepositoryForm.tsx +++ b/scm-ui/ui-webapp/src/repos/components/ImportFullRepositoryForm.tsx @@ -50,7 +50,7 @@ const ImportFullRepositoryForm: FC = ({ setFile, setValid, password, setP
setPassword(value)} + onChange={setPassword} type="password" label={t("import.bundle.password.title")} helpText={t("import.bundle.password.helpText")} diff --git a/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromBundle.tsx b/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromBundle.tsx index c8ecef2ec3..c0da6fd048 100644 --- a/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromBundle.tsx +++ b/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromBundle.tsx @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, FormEvent, useEffect, useState } from "react"; +import React, { FC, FormEvent, useCallback, useEffect, useState } from "react"; import { Repository, RepositoryCreation, RepositoryType } from "@scm-manager/ui-types"; import { ErrorNotification, Level, SubmitButton } from "@scm-manager/ui-components"; import { useTranslation } from "react-i18next"; @@ -42,37 +42,49 @@ const ImportRepositoryFromBundle: FC = ({ setImportPending, setImportedRepository, nameForm: NameForm, - informationForm: InformationForm, + informationForm: InformationForm }) => { const [repo, setRepo] = useState({ name: "", namespace: "", type: repositoryType.name, contact: "", - description: "", - contextEntries: [], + description: "" }); const [password, setPassword] = useState(""); const [valid, setValid] = useState({ namespaceAndName: false, contact: true, file: false }); const [file, setFile] = useState(null); const [compressed, setCompressed] = useState(true); const [t] = useTranslation("repos"); - const { importRepositoryFromBundle, importedRepository, error, isLoading } = - useImportRepositoryFromBundle(repositoryType); + const { importRepositoryFromBundle, importedRepository, error, isLoading } = useImportRepositoryFromBundle( + repositoryType + ); + const setContactValid = useCallback((contact: boolean) => setValid(currentValid => ({ ...currentValid, contact })), [ + setValid + ]); + const setNamespaceAndNameValid = useCallback( + (namespaceAndName: boolean) => setValid(currentValid => ({ ...currentValid, namespaceAndName })), + [setValid] + ); + const setFileValid = useCallback((file: boolean) => setValid(currentValid => ({ ...currentValid, file })), [ + setValid + ]); - useEffect(() => setRepo({ ...repo, type: repositoryType.name }), [repositoryType]); - useEffect(() => setImportPending(isLoading), [isLoading]); + useEffect(() => setImportPending(isLoading), [isLoading, setImportPending]); useEffect(() => { if (importedRepository) { setImportedRepository(importedRepository); } - }, [importedRepository]); + }, [importedRepository, setImportedRepository]); - const isValid = () => Object.values(valid).every((v) => v); + const isValid = () => Object.values(valid).every(v => v); const submit = (event: FormEvent) => { event.preventDefault(); - importRepositoryFromBundle(repo, file!, compressed, password); + if (!file) { + throw new Error("File is required for import"); + } + importRepositoryFromBundle({ ...repo, type: repositoryType.name }, file, compressed, password); }; return ( @@ -80,7 +92,7 @@ const ImportRepositoryFromBundle: FC = ({ setValid({ ...valid, file })} + setValid={setFileValid} compressed={compressed} setCompressed={setCompressed} password={password} @@ -91,14 +103,14 @@ const ImportRepositoryFromBundle: FC = ({ >} - setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })} + setValid={setNamespaceAndNameValid} disabled={isLoading} /> >} disabled={isLoading} - setValid={(contact: boolean) => setValid({ ...valid, contact })} + setValid={setContactValid} /> } diff --git a/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromUrl.tsx b/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromUrl.tsx index fd215d7f17..c603efcccd 100644 --- a/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromUrl.tsx +++ b/scm-ui/ui-webapp/src/repos/components/ImportRepositoryFromUrl.tsx @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, FormEvent, useEffect, useState } from "react"; +import React, { FC, FormEvent, useCallback, useEffect, useState } from "react"; import { Repository, RepositoryCreation, RepositoryType, RepositoryUrlImport } from "@scm-manager/ui-types"; import ImportFromUrlForm from "./ImportFromUrlForm"; import { ErrorNotification, Level, SubmitButton } from "@scm-manager/ui-components"; @@ -42,7 +42,7 @@ const ImportRepositoryFromUrl: FC = ({ setImportPending, setImportedRepository, nameForm: NameForm, - informationForm: InformationForm, + informationForm: InformationForm }) => { const [repo, setRepo] = useState({ name: "", @@ -52,50 +52,54 @@ const ImportRepositoryFromUrl: FC = ({ description: "", importUrl: "", username: "", - password: "", - contextEntries: [], + password: "" }); const [valid, setValid] = useState({ namespaceAndName: false, contact: true, importUrl: false }); const [t] = useTranslation("repos"); const { importRepositoryFromUrl, importedRepository, error, isLoading } = useImportRepositoryFromUrl(repositoryType); + const setContactValid = useCallback((contact: boolean) => setValid(currentValid => ({ ...currentValid, contact })), [ + setValid + ]); + const setNamespaceAndNameValid = useCallback( + (namespaceAndName: boolean) => setValid(currentValid => ({ ...currentValid, namespaceAndName })), + [setValid] + ); + const setImportUrlValid = useCallback( + (importUrl: boolean) => setValid(currentValid => ({ ...currentValid, importUrl })), + [setValid] + ); - useEffect(() => setRepo({ ...repo, type: repositoryType.name }), [repositoryType]); - useEffect(() => setImportPending(isLoading), [isLoading]); + useEffect(() => setImportPending(isLoading), [isLoading, setImportPending]); useEffect(() => { if (importedRepository) { setImportedRepository(importedRepository); } - }, [importedRepository]); + }, [importedRepository, setImportedRepository]); - const isValid = () => Object.values(valid).every((v) => v); + const isValid = () => Object.values(valid).every(v => v); const submit = (event: FormEvent) => { event.preventDefault(); - importRepositoryFromUrl(repo); + importRepositoryFromUrl({ ...repo, type: repositoryType.name }); }; return (
{error ? : null} - setValid({ ...valid, importUrl })} - disabled={isLoading} - /> +
>} - setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })} + setValid={setNamespaceAndNameValid} disabled={isLoading} /> >} disabled={isLoading} - setValid={(contact: boolean) => setValid({ ...valid, contact })} + setValid={setContactValid} /> } diff --git a/scm-ui/ui-webapp/src/repos/components/ImportRepositoryTypeSelect.tsx b/scm-ui/ui-webapp/src/repos/components/ImportRepositoryTypeSelect.tsx index c6b2918a4d..22cdd3f1b3 100644 --- a/scm-ui/ui-webapp/src/repos/components/ImportRepositoryTypeSelect.tsx +++ b/scm-ui/ui-webapp/src/repos/components/ImportRepositoryTypeSelect.tsx @@ -38,11 +38,11 @@ const ImportRepositoryTypeSelect: FC = ({ repositoryTypes, repositoryType const createSelectOptions = () => { const options = repositoryTypes - .filter((repoType) => !!repoType._links.import) - .map((repositoryType) => { + .filter(repoType => !!repoType._links.import) + .map(repositoryType => { return { label: repositoryType.displayName, - value: repositoryType.name, + value: repositoryType.name }; }); options.unshift({ label: "", value: "" }); @@ -50,7 +50,7 @@ const ImportRepositoryTypeSelect: FC = ({ repositoryTypes, repositoryType }; const onChangeType = (type: string) => { - const repositoryType = repositoryTypes.filter((t) => t.name === type)[0]; + const repositoryType = repositoryTypes.filter(t => t.name === type)[0]; setRepositoryType(repositoryType); }; diff --git a/scm-ui/ui-webapp/src/repos/components/NamespaceAndNameFields.tsx b/scm-ui/ui-webapp/src/repos/components/NamespaceAndNameFields.tsx index e4f28722f9..5dbd2a949f 100644 --- a/scm-ui/ui-webapp/src/repos/components/NamespaceAndNameFields.tsx +++ b/scm-ui/ui-webapp/src/repos/components/NamespaceAndNameFields.tsx @@ -68,7 +68,7 @@ const NamespaceAndNameFields: FC = ({ repository, onChange, setValid, dis } else { setValid(false); } - }, [repository.name, repository.namespace]); + }, [repository.name, repository.namespace, namespaceStrategy, setValid]); const handleNamespaceChange = (namespace: string) => { const valid = validator.isNamespaceValid(namespace); @@ -96,7 +96,7 @@ const NamespaceAndNameFields: FC = ({ repository, onChange, setValid, dis errorMessage: t("validation.namespace-invalid"), validationError: namespaceValidationError, disabled: disabled, - informationMessage, + informationMessage }; if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) { diff --git a/scm-ui/ui-webapp/src/repos/components/PermissionsNavLink.test.tsx b/scm-ui/ui-webapp/src/repos/components/PermissionsNavLink.test.tsx index 8a62865433..959f8b4b07 100644 --- a/scm-ui/ui-webapp/src/repos/components/PermissionsNavLink.test.tsx +++ b/scm-ui/ui-webapp/src/repos/components/PermissionsNavLink.test.tsx @@ -22,15 +22,14 @@ * SOFTWARE. */ import React from "react"; -import { mount, shallow } from "@scm-manager/ui-tests/enzyme-router"; -import "@scm-manager/ui-tests/enzyme"; -import "@scm-manager/ui-tests/i18n"; +import { mount, shallow } from "@scm-manager/ui-tests"; +import "@scm-manager/ui-tests"; import PermissionsNavLink from "./PermissionsNavLink"; describe("PermissionsNavLink", () => { it("should render nothing, if the modify link is missing", () => { const repository = { - _links: {}, + _links: {} }; const navLink = shallow(); @@ -41,9 +40,9 @@ describe("PermissionsNavLink", () => { const repository = { _links: { permissions: { - href: "/permissions", - }, - }, + href: "/permissions" + } + } }; const navLink = mount(); diff --git a/scm-ui/ui-webapp/src/repos/components/RepositoryDetails.tsx b/scm-ui/ui-webapp/src/repos/components/RepositoryDetails.tsx index 5b07b2d04c..9aa1fa2ba1 100644 --- a/scm-ui/ui-webapp/src/repos/components/RepositoryDetails.tsx +++ b/scm-ui/ui-webapp/src/repos/components/RepositoryDetails.tsx @@ -42,7 +42,7 @@ class RepositoryDetails extends React.Component { name="repos.repository-details.information" renderAll={true} props={{ - repository, + repository }} />
diff --git a/scm-ui/ui-webapp/src/repos/components/RepositoryInformationForm.tsx b/scm-ui/ui-webapp/src/repos/components/RepositoryInformationForm.tsx index d4b95a9d79..32f40ce766 100644 --- a/scm-ui/ui-webapp/src/repos/components/RepositoryInformationForm.tsx +++ b/scm-ui/ui-webapp/src/repos/components/RepositoryInformationForm.tsx @@ -58,7 +58,7 @@ const RepositoryInformationForm: FC = ({ repository, onChange, disabled, />