From 21ff14c2a1e34be0e165da7b00a5beead02ddee6 Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Tue, 16 Jan 2024 09:36:44 -0500 Subject: [PATCH 1/7] implement group-edit Signed-off-by: Harsh Modi --- .../components/content/group/GroupEdit.jsx | 302 +++++++++++++++++- .../content/group/GroupEdit.test.jsx | 158 +++++++++ 2 files changed, 457 insertions(+), 3 deletions(-) create mode 100644 src/main/webui/src/app/components/content/group/GroupEdit.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..78b3095 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,304 @@ * limitations under the License. */ -import React from 'react'; +import React, {useState, useEffect} from 'react'; +import {useLocation, useParams} 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} from '../common/Hints.jsx'; +import {PackageTypeSelect} from '../common/PackageTypeSelect.jsx'; +// import ViewJsonDebugger from './Debugger.jsx'; +import {Utils} from '#utils/AppUtils.js'; +import {TimeUtils} from '#utils/TimeUtils.js'; +import {IndyRest} from '#utils/RestClient.js'; + +const {statsRes, storeRes, disableRes} = IndyRest; + +const EditConstituents = ({store, currentAvailable}) => { + const [, setItems] = useState({ + 'constituents': store.constituents, + 'currentAvailble': currentAvailable, + }); + + const addConstituent = available => { + let idx = currentAvailable.findIndex(a => a.name === available.name); + store.constituents.push(available.packageType + ':' + available.type + ':' + available.name); + // element.addClass('hidden'); + currentAvailable.splice(idx, 1); + setItems({ + 'constituents': store.constituents, + 'currentAvailble': currentAvailable, + }); + }; + + const removeConstituent = constituent => { + let idx = store.constituents.indexOf(constituent); + + let parts = constituent.split(':'); + currentAvailable.push({packageType: parts[0], type: parts[1], name: parts[2]}); + + store.constituents.splice(idx, 1); + + Utils.sortEndpoints(currentAvailable); + setItems({ + 'constituents': store.constituents, + 'currentAvailble': currentAvailable, + }); + }; + + const moveItem = (from, to) => { + let temp = store.constituents.splice(from, 1)[0]; + store.constituents.splice(to, 0, temp); + }; + + const promote = constituent => { + let idx = store.constituents.indexOf(constituent); + if (idx > 0) { + moveItem(idx, idx - 1); + setItems({ + 'constituents': store.constituents, + 'currentAvailble': currentAvailable, + }); + } + }; + + const demote = constituent => { + let idx = store.constituents.indexOf(constituent); + if (idx < store.constituents.length - 1) { + moveItem(idx, idx + 1); + setItems({ + 'constituents': store.constituents, + 'currentAvailble': currentAvailable, + }); + } + }; + + const top = constituent => { + let idx = store.constituents.indexOf(constituent); + moveItem(idx, 0); + setItems({ + 'constituents': store.constituents, + 'currentAvailble': currentAvailable, + }); + }; + + const bottom = constituent => { + let idx = store.constituents.indexOf(constituent); + moveItem(idx, store.constituents.length - 1); + setItems({ + 'constituents': store.constituents, + 'currentAvailble': currentAvailable, + }); + }; + + return
+
    + (hover for controls) + { + store.constituents && store.constituents.map((item, index) =>
  1. +
    {item}
    + +
    +
  2. ) + } +
+
    + (click to add to constituents) + { + currentAvailable && currentAvailable.map(item =>
  1. +
  2. ) + } +
+
; +}; + +EditConstituents.propTypes = { + store: PropTypes.object, + currentAvailable: PropTypes.array, +}; export default function GroupEdit() { - return
This is not implemented yet!
; + const [state, setState] = useState({ + available: [], + 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"}; + let available = []; + 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 available Store data + const availableRes = await statsRes.getAllEndpoints(); + let availableResult = []; + if (availableRes.success) { + availableResult = availableRes.result.items; + } else { + Utils.logMessage(`Getting available constituents failed! Error reason: ${statsRes.error.message}`); + } + // 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, + available: availableResult, + }); + 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; + available = state.available; + } + + 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 && + Set to automatically re-enable at {TimeUtils.timestampToDateFormat(store.disableExpiration)} + } +
+
+ {' '} + + If enabled, all new constituents which are added not manually(like promotion) will be at the top of constituents list +
+ +
+
+ + {' '} + {errors.disable_timeout && Not a valid number}
+ +
+
+ +
Description
+
+ +
+
Constituents
+ +
+
+ { + // + ); } + +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..272bd2d --- /dev/null +++ b/src/main/webui/src/app/components/content/group/GroupEdit.test.jsx @@ -0,0 +1,158 @@ +/* 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 {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"); + expect(screen.getByText("Prepend Constituents?")).toBeInTheDocument(); + parentDiv = screen.getByText("Prepend Constituents?").closest("div"); + expect(within(parentDiv).getByRole("checkbox")).toHaveAttribute("name", "prepend_constituent"); + expect(screen.getByText("Current:")).toBeInTheDocument(); + expect(screen.getByText("Available:")).toBeInTheDocument(); + }); + }); + + it("Verify GroupEdit for edit mode", async () => { + const mockGroupStore = { + name: "local-deployment", type: "group", packageType: "maven", + key: "maven:group:local-deployment", disabled: false, + "allow_snapshots": true, "allow_releases": true, + description: "local deployment repo", + constituents: ["maven:remote:central", "maven:hosted:local-deployment"] + }; + const mockAvailable = { + "items": [ + { + "packageType": "maven", + "name": "build_org-keycloak-keycloak-parent-4-x_20180515.1724", + "type": "group", + "key": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180515.1724", + "storeKey": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180515.1724", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_org-keycloak-keycloak-parent-4-x_20180515.1724" + }, + { + "packageType": "maven", + "name": "build_org-keycloak-keycloak-parent-4-x_20180531.0218", + "type": "group", + "key": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180531.0218", + "storeKey": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180531.0218", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_org-keycloak-keycloak-parent-4-x_20180531.0218" + }, + { + "packageType": "maven", + "name": "build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216", + "type": "group", + "key": "maven:group:build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216", + "storeKey": "maven:group:build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216" + }, + { + "packageType": "maven", + "name": "build_vertx-infinispan-3-5-1_20180705.1313", + "type": "group", + "key": "maven:group:build_vertx-infinispan-3-5-1_20180705.1313", + "storeKey": "maven:group:build_vertx-infinispan-3-5-1_20180705.1313", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_vertx-infinispan-3-5-1_20180705.1313" + }, + { + "packageType": "maven", + "name": "public", + "type": "group", + "key": "maven:group:public", + "storeKey": "maven:group:public", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/public" + } + ] + }; + 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/stats/all-endpoints`, {status: 200, body: JSON.stringify(mockAvailable)}); + 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")).toHaveAttribute("name", "prepend_constituent"); + expect(screen.getByText("Current:")).toBeInTheDocument(); + expect(screen.getByText("Available:")).toBeInTheDocument(); + + }); + }); +}); From 90c210412831c2d15da40e15a7f3de03adf3bc29 Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Wed, 31 Jan 2024 10:34:07 -0500 Subject: [PATCH 2/7] ensure available constituents are listed for new group type Signed-off-by: Harsh Modi --- .../components/content/group/GroupEdit.jsx | 21 +++++++- .../content/group/GroupEdit.test.jsx | 50 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) 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 78b3095..98e7885 100644 --- a/src/main/webui/src/app/components/content/group/GroupEdit.jsx +++ b/src/main/webui/src/app/components/content/group/GroupEdit.jsx @@ -186,7 +186,7 @@ export default function GroupEdit() { const path = location.pathname; const mode = path.match(/.*\/new$/u) ? 'new' : 'edit'; // Give a default packageType - let store = {"packageType": "maven", "type": "group"}; + let store = {"packageType": "maven", "type": "group", "constituents": []}; let available = []; useEffect(() => { if (mode === 'edit') { @@ -229,13 +229,30 @@ export default function GroupEdit() { fetchStore(); } + + if (mode === 'new') { + (async () => { + // get available Store data + const availableRes = await statsRes.getAllEndpoints(); + let availableResult = []; + if (availableRes.success) { + availableResult = availableRes.result.items; + setState({ + available: availableResult, + }); + } else { + Utils.logMessage(`Getting available constituents failed! Error reason: ${statsRes.error.message}`); + } + })(); + } }, [packageType, name, mode, reset]); if (mode === 'edit') { store = state.store; - available = state.available; } + available = state.available; + const changelog = register("changelog"); return ( e.preventDefault()}> 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 index 272bd2d..9c21f19 100644 --- a/src/main/webui/src/app/components/content/group/GroupEdit.test.jsx +++ b/src/main/webui/src/app/components/content/group/GroupEdit.test.jsx @@ -37,6 +37,56 @@ afterEach(() => { describe('GroupEdit tests', () => { it("Verify GroupEdit for new mode", async () => { + const mockAvailable = { + "items": [ + { + "packageType": "maven", + "name": "build_org-keycloak-keycloak-parent-4-x_20180515.1724", + "type": "group", + "key": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180515.1724", + "storeKey": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180515.1724", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_org-keycloak-keycloak-parent-4-x_20180515.1724" + }, + { + "packageType": "maven", + "name": "build_org-keycloak-keycloak-parent-4-x_20180531.0218", + "type": "group", + "key": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180531.0218", + "storeKey": "maven:group:build_org-keycloak-keycloak-parent-4-x_20180531.0218", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_org-keycloak-keycloak-parent-4-x_20180531.0218" + }, + { + "packageType": "maven", + "name": "build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216", + "type": "group", + "key": "maven:group:build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216", + "storeKey": "maven:group:build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_org-keycloak-keycloak-nodejs-auth-utils-3-xnpm_1-2-stage-4_20180104.1216" + }, + { + "packageType": "maven", + "name": "build_vertx-infinispan-3-5-1_20180705.1313", + "type": "group", + "key": "maven:group:build_vertx-infinispan-3-5-1_20180705.1313", + "storeKey": "maven:group:build_vertx-infinispan-3-5-1_20180705.1313", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/build_vertx-infinispan-3-5-1_20180705.1313" + }, + { + "packageType": "maven", + "name": "public", + "type": "group", + "key": "maven:group:public", + "storeKey": "maven:group:public", + "storeType": "group", + "resource_uri": "http://localhost:4000/api/content/maven/group/public" + } + ] + }; + fetchMock.mock(`/api/stats/all-endpoints`, {status: 200, body: JSON.stringify(mockAvailable)}); render( } /> From c6044b19acbb201a2035148653ebd591f8398eca Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Wed, 31 Jan 2024 12:02:54 -0500 Subject: [PATCH 3/7] fix edit changelog not being updated It was being overwritten by old changelog from data.metadata.changelog Signed-off-by: Harsh Modi --- .../src/app/components/content/common/StoreControlPanels.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx b/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx index 2a1bc82..da47c6b 100644 --- a/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx +++ b/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx @@ -136,11 +136,14 @@ const StoreEditControlPanel = ({mode, store, handleSubmit, validate, changelog}) const postSuccessHandler = st => navigate(`/${st.type}/${st.packageType}/view/${st.name}`); let handleSave = () => save(store, mode, postSuccessHandler); if(handleSubmit && typeof handleSubmit === 'function'){ - // console.log(handleSubmit); handleSave = handleSubmit(data=>{ data.disabled = !data.enabled; data.enabled = undefined; if(data.changelog && data.changelog.trim() !== ''){ + if (data.metadata && data.metadata.changelog) { + // need to overwrite previous changelog here + data.metadata.changelog = data.changelog; + } if(store.metadata){ store.metadata.changelog = data.changelog; }else{ From 632dee328695207ac1381f212768f33fd36e354a Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Wed, 31 Jan 2024 12:15:21 -0500 Subject: [PATCH 4/7] use updated constituents from store for saving Signed-off-by: Harsh Modi --- .../src/app/components/content/common/StoreControlPanels.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx b/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx index da47c6b..76c365b 100644 --- a/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx +++ b/src/main/webui/src/app/components/content/common/StoreControlPanels.jsx @@ -139,6 +139,8 @@ const StoreEditControlPanel = ({mode, store, handleSubmit, validate, changelog}) handleSave = handleSubmit(data=>{ data.disabled = !data.enabled; data.enabled = undefined; + // store.constituents can be updated without data being updated + data.constituents = store.constituents; if(data.changelog && data.changelog.trim() !== ''){ if (data.metadata && data.metadata.changelog) { // need to overwrite previous changelog here From f13d8e62aeac82308e342da2032419905636d83b Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Wed, 31 Jan 2024 22:04:58 -0500 Subject: [PATCH 5/7] only show unique available contituents Signed-off-by: Harsh Modi --- .../components/content/group/GroupEdit.jsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) 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 98e7885..b8f6895 100644 --- a/src/main/webui/src/app/components/content/group/GroupEdit.jsx +++ b/src/main/webui/src/app/components/content/group/GroupEdit.jsx @@ -35,8 +35,8 @@ const EditConstituents = ({store, currentAvailable}) => { }); const addConstituent = available => { - let idx = currentAvailable.findIndex(a => a.name === available.name); - store.constituents.push(available.packageType + ':' + available.type + ':' + available.name); + let idx = currentAvailable.indexOf(available); + store.constituents.push(available); // element.addClass('hidden'); currentAvailable.splice(idx, 1); setItems({ @@ -48,8 +48,7 @@ const EditConstituents = ({store, currentAvailable}) => { const removeConstituent = constituent => { let idx = store.constituents.indexOf(constituent); - let parts = constituent.split(':'); - currentAvailable.push({packageType: parts[0], type: parts[1], name: parts[2]}); + currentAvailable.push(constituent); store.constituents.splice(idx, 1); @@ -148,12 +147,12 @@ const EditConstituents = ({store, currentAvailable}) => {
    (click to add to constituents) { - currentAvailable && currentAvailable.map(item =>
  1. + currentAvailable && currentAvailable.map(item =>
  2. ) @@ -199,9 +198,11 @@ export default function GroupEdit() { storeView.disabled = raw.disabled === undefined ? false : raw.disabled; // get available Store data const availableRes = await statsRes.getAllEndpoints(); - let availableResult = []; + let allAvailable = new Set(); if (availableRes.success) { - availableResult = availableRes.result.items; + let availableResult = availableRes.result.items; + availableResult.forEach(item => allAvailable.add(item.packageType + ':' + item.type + ':' + item.name)); + raw.constituents.forEach(item => allAvailable.delete(item)); } else { Utils.logMessage(`Getting available constituents failed! Error reason: ${statsRes.error.message}`); } @@ -218,7 +219,7 @@ export default function GroupEdit() { setState({ storeView: cloned, store: raw, - available: availableResult, + available: Array.from(allAvailable), }); reset(raw); } else { @@ -234,11 +235,12 @@ export default function GroupEdit() { (async () => { // get available Store data const availableRes = await statsRes.getAllEndpoints(); - let availableResult = []; + let allAvailable = new Set(); if (availableRes.success) { - availableResult = availableRes.result.items; + let availableResult = availableRes.result.items; + availableResult.forEach(item => allAvailable.add(item.packageType + ':' + item.type + ':' + item.name)); setState({ - available: availableResult, + available: Array.from(allAvailable), }); } else { Utils.logMessage(`Getting available constituents failed! Error reason: ${statsRes.error.message}`); From 9f2ac6e33190b115e5bab3c138da2a82061ea1a8 Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Thu, 1 Feb 2024 14:25:33 -0500 Subject: [PATCH 6/7] only set available don't reset entire state Signed-off-by: Harsh Modi --- .../components/content/group/GroupEdit.jsx | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) 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 b8f6895..18f8579 100644 --- a/src/main/webui/src/app/components/content/group/GroupEdit.jsx +++ b/src/main/webui/src/app/components/content/group/GroupEdit.jsx @@ -167,11 +167,8 @@ EditConstituents.propTypes = { }; export default function GroupEdit() { - const [state, setState] = useState({ - available: [], - store: {}, - storeView: {} - }); + const [store, setStore] = useState({"packageType": "maven", "type": "group", "constituents": []}); + const [available, setAvailable] = useState([]); const location = useLocation(); const {packageType, name} = useParams(); const { @@ -184,9 +181,6 @@ export default function GroupEdit() { const path = location.pathname; const mode = path.match(/.*\/new$/u) ? 'new' : 'edit'; - // Give a default packageType - let store = {"packageType": "maven", "type": "group", "constituents": []}; - let available = []; useEffect(() => { if (mode === 'edit') { const fetchStore = async () => { @@ -216,11 +210,8 @@ export default function GroupEdit() { Utils.logMessage(`disable timeout getting failed! Error reason: ${timeoutRes.error.message}`); } // Change state and re-rendering - setState({ - storeView: cloned, - store: raw, - available: Array.from(allAvailable), - }); + setStore(raw); + setAvailable(Array.from(allAvailable)); reset(raw); } else { // TODO: find another way to do error handling @@ -239,9 +230,7 @@ export default function GroupEdit() { if (availableRes.success) { let availableResult = availableRes.result.items; availableResult.forEach(item => allAvailable.add(item.packageType + ':' + item.type + ':' + item.name)); - setState({ - available: Array.from(allAvailable), - }); + setAvailable(Array.from(allAvailable)); } else { Utils.logMessage(`Getting available constituents failed! Error reason: ${statsRes.error.message}`); } @@ -249,12 +238,6 @@ export default function GroupEdit() { } }, [packageType, name, mode, reset]); - if (mode === 'edit') { - store = state.store; - } - - available = state.available; - const changelog = register("changelog"); return ( e.preventDefault()}> From 24c735c31949cc29fdb821231d0e7d0b6461cc49 Mon Sep 17 00:00:00 2001 From: Harsh Modi Date: Mon, 5 Feb 2024 18:24:02 -0500 Subject: [PATCH 7/7] make sure available are valid Remove the package itself from available list Remove duplicates if there are any Remove all other packages of not the same type Signed-off-by: Harsh Modi --- .../content/common/PackageTypeSelect.jsx | 20 +++- .../components/content/group/GroupEdit.jsx | 91 ++++++++----------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/main/webui/src/app/components/content/common/PackageTypeSelect.jsx b/src/main/webui/src/app/components/content/common/PackageTypeSelect.jsx index 21f023c..d572cff 100644 --- a/src/main/webui/src/app/components/content/common/PackageTypeSelect.jsx +++ b/src/main/webui/src/app/components/content/common/PackageTypeSelect.jsx @@ -21,7 +21,7 @@ import {Utils} from '#utils/AppUtils'; const {statsRes} = IndyRest; -export const PackageTypeSelect = ({register, formErrors}) =>{ +export const PackageTypeSelect = ({register, formErrors, updatePackageType}) =>{ const [state, setState] = useState({ pkgTypes: [] }); @@ -43,9 +43,22 @@ export const PackageTypeSelect = ({register, formErrors}) =>{ if(register){ registered = register("packageType", {required: true}); } + + const {onChange, onBlur, name, ref} = registered; + + const change = e => { + setSelected(e.target.value); + if (typeof updatePackageType === "function") { + updatePackageType(e.target.value); + } + if (typeof onChange === "function") { + onChange(e); + } + }; + return