From 55d504136c482724cb9b862016eadf6f7afa1029 Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Wed, 10 Jan 2024 18:34:20 -0500 Subject: [PATCH] implement group UI Signed-off-by: Harsh Modi --- .../components/content/group/GroupEdit.jsx | 212 +++++++++++++++++- .../content/group/GroupEdit.test.jsx | 98 ++++++++ .../components/content/group/GroupList.jsx | 109 ++++++++- .../content/group/GroupList.test.jsx | 70 ++++++ .../components/content/group/GroupView.jsx | 2 +- .../content/group/GroupView.test.jsx | 2 +- 6 files changed, 484 insertions(+), 9 deletions(-) create mode 100644 src/main/webui/src/app/components/content/group/GroupEdit.test.jsx create mode 100644 src/main/webui/src/app/components/content/group/GroupList.test.jsx diff --git a/src/main/webui/src/app/components/content/group/GroupEdit.jsx b/src/main/webui/src/app/components/content/group/GroupEdit.jsx index b03752c..64a1680 100644 --- a/src/main/webui/src/app/components/content/group/GroupEdit.jsx +++ b/src/main/webui/src/app/components/content/group/GroupEdit.jsx @@ -1,5 +1,5 @@ /** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) + * Copyright (C) 2024 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,214 @@ * limitations under the License. */ -import React from 'react'; +import React, {useState, useEffect} from "react"; +import {useLocation, useParams, Link} from "react-router-dom"; +import {useForm} from "react-hook-form"; +import {PropTypes} from "prop-types"; +import {StoreEditControlPanel as EditControlPanel} from "../common/StoreControlPanels.jsx"; +import {DisableTimeoutHint, Hint} from "../common/Hints.jsx"; +import {PackageTypeSelect} from "../common/PackageTypeSelect.jsx"; +// import ViewJsonDebugger from './Debugger.jsx'; +import {Utils} from "#utils/AppUtils.js"; +import {IndyRest} from "#utils/RestClient.js"; + +const {storeRes, disableRes} = IndyRest; export default function GroupEdit() { - return
This is not implemented yet!
; + const [state, setState] = useState({ + store: {}, + storeView: {}, + }); + const location = useLocation(); + const {packageType, name} = useParams(); + const { + register, + reset, + trigger, + handleSubmit, + formState: {errors}, + } = useForm(); + + const path = location.pathname; + const mode = path.match(/.*\/new$/u) ? "new" : "edit"; + // Give a default packageType + let store = {packageType: "maven", type: "group"}; + useEffect(() => { + if (mode === "edit") { + const fetchStore = async () => { + // get Store data + const res = await storeRes.get(packageType, "group", name); + if (res.success) { + const raw = res.result; + const storeView = Utils.cloneObj(raw); + storeView.disabled = + raw.disabled === undefined ? false : raw.disabled; + // get Store disablement data + const timeoutRes = await disableRes.getStoreTimeout( + packageType, + "group", + name, + ); + const cloned = Utils.cloneObj(storeView); + if (timeoutRes.success) { + const timeout = timeoutRes.result; + cloned.disableExpiration = timeout.expiration; + } else { + Utils.logMessage(`disable timeout getting failed! Error reason: ${timeoutRes.error.message}`,); + } + // Change state and re-rendering + setState({ + storeView: cloned, + store: raw, + }); + reset(raw); + } else { + // TODO: find another way to do error handling + Utils.logMessage(`Failed to get store data. Error reason: ${res.error.status}->${res.error.message}`,); + } + }; + + fetchStore(); + } + }, [packageType, name, mode, reset]); + + if (mode === "edit") { + store = state.store; + } + + const changelog = register("changelog"); + return ( +
e.preventDefault()}> +
+ +
+ +
+
Basics
+
+
+ + {mode === "new" ? + + : + {store.packageType} + } +
+
+ + {mode === "new" ? + + {" "} + {errors.name?.type === "required" && + Name is required + } + {errors.name?.type === "maxLength" && + + Name's length should be less than 50 + + } + + : + {store.name} + } +
+ +
+ {" "} + + {store.disabled && store.disableExpiration && + + + + } +
+
+ {" "} + + + + +
+ +
+
+ + {" "} + {errors.disable_timeout && + Not a valid number + } +
+ +
+
+
+ +
Description
+
+ +
+ +
Constituents
+
+ { + store.constituents && store.constituents.length > 0 && +
    + { + store.constituents.map(item =>
  1. + {item} +
  2. ) + } +
+ } + { + store.available && store.available.length > 0 && +
    + { + store.available.map(item =>
  1. + {item} +
  2. ) + } +
+ } +
+
+ { + // + ); } + +GroupEdit.propTypes = { + store: PropTypes.object, + location: PropTypes.object, + match: PropTypes.object, +}; diff --git a/src/main/webui/src/app/components/content/group/GroupEdit.test.jsx b/src/main/webui/src/app/components/content/group/GroupEdit.test.jsx new file mode 100644 index 0000000..63cf0eb --- /dev/null +++ b/src/main/webui/src/app/components/content/group/GroupEdit.test.jsx @@ -0,0 +1,98 @@ +/* eslint-disable camelcase */ +/** + * Copyright (C) 2024 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from "react"; +import {MemoryRouter, Route, Routes} from 'react-router-dom'; +import {render, screen, cleanup, waitFor, within} from '@testing-library/react'; +import '@testing-library/jest-dom'; +import fetchMock from "fetch-mock"; +import GroupEdit from "./GroupEdit.jsx"; +import {Filters} from "#utils/Filters.js"; +import {STORE_API_BASE_URL} from "../../ComponentConstants.js"; + +beforeEach(()=>{ + fetchMock.restore(); + fetchMock.mock( + "/api/stats/package-type/keys", + {status: 200, body: JSON.stringify(["maven", "npm", "generic-http"])} + ); +}); + +afterEach(() => { + cleanup(); +}); + +describe('GroupEdit tests', () => { + it("Verify GroupEdit for new mode", async ()=>{ + render( + + } /> + + ); + + await waitFor(() => { + // ListControl section testing + expect(screen.getByRole("button", {name: "Save"})).toBeInTheDocument(); + expect(screen.getByRole("button", {name: "Cancel"})).toBeInTheDocument(); + + // Basic section testing + expect(screen.getByRole("option", {name: "maven"})).toBeInTheDocument(); + expect(screen.getByRole("option", {name: "npm"})).toBeInTheDocument(); + expect(screen.getByRole("option", {name: "generic-http"})).toBeInTheDocument(); + expect(screen.getByRole("option", {name: ""}).selected).toBe(true); + expect(screen.getByRole("pkgTypeSel")).toHaveValue(""); + + expect(screen.getByText("Name:")).toBeInTheDocument(); + let parentDiv = screen.getByText("Name:").closest("div"); + expect(within(parentDiv).getByRole("textbox")).toHaveAttribute("name", "name"); + expect(screen.getByText("Enabled?")).toBeInTheDocument(); + parentDiv = screen.getByText("Enabled?").closest("div"); + expect(within(parentDiv).getByRole("checkbox")).toHaveAttribute("name", "enabled"); + }); + }); + + it("Verify GroupEdit for edit mode", async ()=>{ + const mockGroupStore = {name: "local-deployment", type: "group", packageType: "maven", + key: "maven:group:local-deployment", disabled: false, storage: "/var/lib/storage", + "allow_snapshots": true, "allow_releases": true, "authoritative_index": true, + constituents: ["maven:remote:central", "maven:hosted:local-deployment"], + description: "local deployment repo"}; + const mockDisableTimeout = {name: "Disable-Timeout", group: "maven:group:local-deployment#Disable-Timeout", + expiration: "2030-02-22T17:00:00.000Z"}; + fetchMock.mock(`${STORE_API_BASE_URL}/maven/group/local-deployment`, {status: 200, body: JSON.stringify(mockGroupStore)}); + fetchMock.mock("/api/admin/schedule/store/maven/group/local-deployment/disable-timeout", {status: 200, body: JSON.stringify(mockDisableTimeout)}); + render( + + } /> + + ); + + await waitFor(() => { + expect(screen.getByText("Package Type:")).toBeInTheDocument(); + expect(screen.getByText(mockGroupStore.packageType, {selector: "span"})).toBeInTheDocument(); + expect(screen.getByText("Name:")).toBeInTheDocument(); + expect(screen.getByText(mockGroupStore.name, {selector: "span"})).toBeInTheDocument(); + expect(screen.getByText("Enabled?")).toBeInTheDocument(); + let parentDiv = screen.getByText("Enabled?").closest("div"); + expect(within(parentDiv).getByRole("checkbox")).toHaveAttribute("name", "enabled"); + expect(screen.getByText("Prepend Constituents?")).toBeInTheDocument(); + parentDiv = screen.getByText("Prepend Constituents?").closest("div"); + expect(within(parentDiv).getByRole("checkbox")).not.toBeChecked(); + expect(screen.getByText("Constituents")).toBeInTheDocument(); + }); + }); +}); diff --git a/src/main/webui/src/app/components/content/group/GroupList.jsx b/src/main/webui/src/app/components/content/group/GroupList.jsx index f7b207d..98e0fae 100644 --- a/src/main/webui/src/app/components/content/group/GroupList.jsx +++ b/src/main/webui/src/app/components/content/group/GroupList.jsx @@ -1,5 +1,5 @@ /** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) + * Copyright (C) 2024 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,109 @@ * limitations under the License. */ -import React from 'react'; +import React, {useEffect, useState} from "react"; +import {useParams} from "react-router-dom"; +import {ListJsonDebugger} from "../common/Debugger.jsx"; +import ListControl from "../common/ListControl.jsx"; +import {groupOptionLegend as options} from "../../ComponentConstants.js"; +import {StoreListingWidget} from "../common/StoreListingWidget.jsx"; +import {LoadingSpiner} from "../common/LoadingSpiner.jsx"; +import {Utils} from "#utils/AppUtils.js"; +import {IndyRest} from "#utils/RestClient.js"; -export default function GruopList() { - return
This is not implemented yet!
; +const {storeRes, disableRes} = IndyRest; + +const handlers = { + handleDebug: (event, setState) => { + setState({ + enableDebug: event.target.checked, + }); + }, + handleSearch: (event, rawList, setState) => { + setState({ + rawList, + listing: Utils.searchByKeyForNewStores(event.target.value, rawList), + }); + }, + handleSortBy: (event, rawList, setState) => { + setState({ + rawList, + listing: Utils.sortByPropForStores(event.target.value, rawList), + }); + }, +}; + +export default function GroupList() { + const {packageType} = useParams(); + const [state, setState] = useState({ + rawList: [], + listing: [], + disabledMap: {}, + enableDebug: false, + message: "", + }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + const fetchdData = async () => { + const res = await storeRes.getStores(packageType, "group"); + if (res.success) { + const timeoutRes = await disableRes.getAllStoreTimeout(); + let disabledMap = {}; + if (timeoutRes.success) { + const timeoutData = timeoutRes.result; + disabledMap = Utils.setDisableMap(timeoutData); + } else { + Utils.logMessage(`disable timeout get failed in group listing! Error reason: ${timeoutRes.error.message}`,); + } + let data = res.result; + if (typeof data === "string") { + data = JSON.parse(data); + } + setState({ + rawList: data.items, + listing: data.items, + disabledMap, + }); + } else { + setState({ + message: res.error.message, + }); + } + setLoading(false); + }; + fetchdData(); + }, [packageType]); + + if (loading) { + return ; + } + + return ( + + handlers.handleSearch(event, state.rawList, setState) + } + handleDebug={event => handlers.handleDebug(event, setState)} + handleSortBy={event => handlers.handleSortBy(event, state.rawList, setState) + } + /> + {state.listing ? + + : +
No content fetched!
+ } + +
+ ); } diff --git a/src/main/webui/src/app/components/content/group/GroupList.test.jsx b/src/main/webui/src/app/components/content/group/GroupList.test.jsx new file mode 100644 index 0000000..ba47aa9 --- /dev/null +++ b/src/main/webui/src/app/components/content/group/GroupList.test.jsx @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from "react"; +import {MemoryRouter, Route, Routes} from 'react-router-dom'; +import {render, screen, cleanup, waitFor} from '@testing-library/react'; +import '@testing-library/jest-dom'; +import fetchMock from "fetch-mock"; +import GroupList from "./GroupList.jsx"; +import {Utils} from "#utils/AppUtils.js"; +import {STORE_API_BASE_URL} from "../../ComponentConstants.js"; + +beforeEach(()=>{ + fetchMock.restore(); +}); + +afterEach(() => { + cleanup(); +}); + +describe('GroupList tests', () => { + it("Verify GroupList", async ()=>{ + const mockGroupStoreList = JSON.stringify({items: [ + {name: "central", type: "group", packageType: "maven", + key: "maven:group:central", disabled: false, "allow_snapshots": true, + "allow_releases": true, url: "https://repo.maven.apache.org/maven2/", + description: "official maven central"}, + {name: "mrrc", type: "group", packageType: "maven", + key: "maven:group:mrrc", disabled: false, + url: "https://maven.repository.redhat.com/ga/", + constituents: ["maven:remote:central", "maven:hosted:local-deployment"], + description: "Red Hat maven repository"} + ]}); + const mockDisableTimeout = JSON.stringify({items: [ + {name: "Disable-Timeout", group: "maven:group:central#Disable-Timeout", + expiration: "2030-02-22T17:00:00.000Z"}, + {name: "Disable-Timeout", group: "maven:group:mrrc#Disable-Timeout", + expiration: "2030-03-22T17:00:00.000Z"} + ]}); + fetchMock.mock(`${STORE_API_BASE_URL}/maven/group`, {status: 200, body: JSON.stringify(mockGroupStoreList)}); + fetchMock.mock("/api/admin/schedule/store/all/disable-timeout", {status: 200, body: JSON.stringify(mockDisableTimeout)}); + render( + + } /> + + ); + + await waitFor(() => { + // ListControl section testing + expect(screen.getByRole("button", {name: "New..."})).toBeInTheDocument(); + + // StoreListing section testing + expect(screen.getByRole("link", {name: Utils.storeHref("maven:group:central")})).toHaveAttribute("href", Utils.storeHref("maven:group:central")); + expect(screen.getByRole("link", {name: Utils.storeHref("maven:group:mrrc")})).toHaveAttribute("href", Utils.storeHref("maven:group:mrrc")); + }); + }); +}); diff --git a/src/main/webui/src/app/components/content/group/GroupView.jsx b/src/main/webui/src/app/components/content/group/GroupView.jsx index ecb3b0d..9a3317d 100644 --- a/src/main/webui/src/app/components/content/group/GroupView.jsx +++ b/src/main/webui/src/app/components/content/group/GroupView.jsx @@ -1,5 +1,5 @@ /** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) + * Copyright (C) 2024 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/webui/src/app/components/content/group/GroupView.test.jsx b/src/main/webui/src/app/components/content/group/GroupView.test.jsx index 7184de7..8aedfc2 100644 --- a/src/main/webui/src/app/components/content/group/GroupView.test.jsx +++ b/src/main/webui/src/app/components/content/group/GroupView.test.jsx @@ -1,6 +1,6 @@ /* eslint-disable camelcase */ /** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) + * Copyright (C) 2024 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.