Skip to content

Commit

Permalink
implement group UI
Browse files Browse the repository at this point in the history
Signed-off-by: Harsh Modi <[email protected]>
  • Loading branch information
hjmodi committed Jan 12, 2024
1 parent 6a7a579 commit 55d5041
Show file tree
Hide file tree
Showing 6 changed files with 484 additions and 9 deletions.
212 changes: 209 additions & 3 deletions src/main/webui/src/app/components/content/group/GroupEdit.jsx
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 <div>This is not implemented yet!</div>;
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 (
<form onSubmit={e => e.preventDefault()}>
<div className="control-panel">
<EditControlPanel
mode={mode}
store={store}
handleSubmit={handleSubmit}
validate={trigger}
changelog={changelog}
/>
</div>

<div className="content-panel">
<div className="fieldset-caption">Basics</div>
<div className="fieldset">
<div className="detail-field">
<label>Package Type:</label>
{mode === "new" ?
<PackageTypeSelect register={register} formErrors={errors} />
:
<span className="key">{store.packageType}</span>
}
</div>
<div className="detail-field">
<label>Name:</label>
{mode === "new" ?
<span>
<input
type="text"
size="25"
{...register("name", {required: true, maxLength: 50})}
/>{" "}
{errors.name?.type === "required" &&
<span className="alert">Name is required</span>
}
{errors.name?.type === "maxLength" &&
<span className="alert">
Name&apos;s length should be less than 50
</span>
}
</span>
:
<span className="key">{store.name}</span>
}
</div>

<div className="detail-field">
<input
type="checkbox"
defaultChecked={!store.disabled}
{...register("enabled")}
/>{" "}
<label>Enabled?</label>
{store.disabled && store.disableExpiration &&
<span>
<Hint hintKey="Set to automatically re-enable at {TimeUtils.timestampToDateFormat(store.disableExpiration)}" />
</span>
}
</div>
<div className="detail-field">
<input
type="checkbox"
defaultChecked={store.prepend_constituent}
{...register("prepend_constituent")}
/>{" "}
<label>Prepend Constituents?</label>
<span>
<Hint hintKey="If enabled, all new constituents which are added not manually(like promotion) will be at the top of constituents list" />
</span>
</div>

<div className="sub-fields">
<div className="detail-field">
<label>Disable Timeout:</label>
<input
type="number"
defaultValue={store.disable_timeout}
{...register("disable_timeout", {min: -1, max: 999999999})}
/>{" "}
{errors.disable_timeout &&
<span className="alert">Not a valid number</span>
}
<br />
<DisableTimeoutHint />
</div>
</div>
</div>

<div className="fieldset-caption">Description</div>
<div className="fieldset">
<textarea
rows="3"
className="text-description"
{...register("description")}
>
{store.description}
</textarea>
</div>

<div className="fieldset-caption">Constituents</div>
<div className="fieldset">
{
store.constituents && store.constituents.length > 0 &&
<ol className="left-col detail-value detail-edit-list">
{
store.constituents.map(item => <li key={`constituent-${item}`} className="detail-edit-list-item">
<Link to={Utils.detailHref(item)}>{item}</Link>
</li>)
}
</ol>
}
{
store.available && store.available.length > 0 &&
<ol className="right-col detail-value detail-edit-list">
{
store.available.map(item => <li key={`available-${item}`} className="detail-edit-list-item">
<Link to={Utils.detailHref(item)}>{item}</Link>
</li>)
}
</ol>
}
</div>
</div>
{
// <ViewJsonDebugger enableDebug={enableDebug} storeJson={storeJson} rawJson={rawJson}
}
</form>
);
}

GroupEdit.propTypes = {
store: PropTypes.object,
location: PropTypes.object,
match: PropTypes.object,
};

This comment has been minimized.

Copy link
@ligangty

ligangty Jan 12, 2024

Member

The constituents part is not just showing the all item as link, but also has abilities to:

  • Add item from available to current
  • Delete item in current to available
  • promote/demote priority(order) of item in current, or do top/bottom function in current.
98 changes: 98 additions & 0 deletions src/main/webui/src/app/components/content/group/GroupEdit.test.jsx
Original file line number Diff line number Diff line change
@@ -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(<MemoryRouter initialEntries={["/group/new"]}>
<Routes>
<Route path="/group/new" element={<GroupEdit />} />
</Routes>
</MemoryRouter>);

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(<MemoryRouter initialEntries={["/group/maven/edit/local-deployment"]}>
<Routes>
<Route path="/group/:packageType/edit/:name" element={<GroupEdit />} />
</Routes>
</MemoryRouter>);

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();
});
});
});
Loading

0 comments on commit 55d5041

Please sign in to comment.