Merge pull request #142 from christiemolloy/createGroup
Add Create Group Functionality
This commit is contained in:
commit
bcd864bfc9
4 changed files with 223 additions and 90 deletions
99
src/groups/GroupsCreateModal.tsx
Normal file
99
src/groups/GroupsCreateModal.tsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
import React, { useContext } from "react";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
Form,
|
||||
FormGroup,
|
||||
Modal,
|
||||
ModalVariant,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
type GroupsCreateModalProps = {
|
||||
handleModalToggle: () => void;
|
||||
isCreateModalOpen: boolean;
|
||||
setIsCreateModalOpen: (isCreateModalOpen: boolean) => void;
|
||||
createGroupName: string;
|
||||
setCreateGroupName: (createGroupName: string) => void;
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
export const GroupsCreateModal = ({
|
||||
handleModalToggle,
|
||||
isCreateModalOpen,
|
||||
setIsCreateModalOpen,
|
||||
createGroupName,
|
||||
setCreateGroupName,
|
||||
refresh,
|
||||
}: GroupsCreateModalProps) => {
|
||||
const { t } = useTranslation("groups");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const { addAlert } = useAlerts();
|
||||
const form = useForm();
|
||||
const { register, errors } = form;
|
||||
|
||||
const valueChange = (createGroupName: string) => {
|
||||
setCreateGroupName(createGroupName);
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
if (await form.trigger()) {
|
||||
try {
|
||||
await httpClient.doPost(`/admin/realms/${realm}/groups`, {
|
||||
name: createGroupName,
|
||||
});
|
||||
setIsCreateModalOpen(false);
|
||||
setCreateGroupName("");
|
||||
refresh();
|
||||
addAlert(t("groupCreated"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
`${t("couldNotCreateGroup")} ': '${error}'`,
|
||||
AlertVariant.danger
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
variant={ModalVariant.small}
|
||||
title={t("createAGroup")}
|
||||
isOpen={isCreateModalOpen}
|
||||
onClose={handleModalToggle}
|
||||
actions={[
|
||||
<Button key="confirm" variant="primary" onClick={() => submitForm()}>
|
||||
{t("create")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form isHorizontal>
|
||||
<FormGroup
|
||||
name="create-modal-group"
|
||||
label={t("name")}
|
||||
fieldId="group-id"
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={errors.name ? "error" : "default"}
|
||||
isRequired
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: true })}
|
||||
type="text"
|
||||
id="create-group-name"
|
||||
name="name"
|
||||
value={createGroupName}
|
||||
onChange={valueChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -5,14 +5,17 @@ import {
|
|||
TableBody,
|
||||
TableVariant,
|
||||
} from "@patternfly/react-table";
|
||||
import { Button } from "@patternfly/react-core";
|
||||
import { Button, AlertVariant } from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GroupRepresentation } from "./models/groups";
|
||||
import { UsersIcon } from "@patternfly/react-icons";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
|
||||
type GroupsListProps = {
|
||||
export type GroupsListProps = {
|
||||
list?: GroupRepresentation[];
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
type FormattedData = {
|
||||
|
@ -20,11 +23,13 @@ type FormattedData = {
|
|||
selected: boolean;
|
||||
};
|
||||
|
||||
export const GroupsList = ({ list }: GroupsListProps) => {
|
||||
export const GroupsList = ({ list, refresh }: GroupsListProps) => {
|
||||
const { t } = useTranslation("groups");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const columnGroupName: keyof GroupRepresentation = "name";
|
||||
const columnGroupNumber: keyof GroupRepresentation = "membersLength";
|
||||
const { realm } = useContext(RealmContext);
|
||||
const { addAlert } = useAlerts();
|
||||
const [formattedData, setFormattedData] = useState<FormattedData[]>([]);
|
||||
|
||||
const formatData = (data: GroupRepresentation[]) =>
|
||||
|
@ -67,15 +72,6 @@ export const GroupsList = ({ list }: GroupsListProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Delete individual rows using the action in the table
|
||||
function onDelete(rowIndex: number) {
|
||||
const localFilteredData = [...list!];
|
||||
httpClient.doDelete(
|
||||
`/admin/realms/master/groups/${localFilteredData[rowIndex].id}`
|
||||
);
|
||||
// TO DO update the state
|
||||
}
|
||||
|
||||
const tableHeader = [{ title: t("groupName") }, { title: t("members") }];
|
||||
const actions = [
|
||||
{
|
||||
|
@ -84,7 +80,20 @@ export const GroupsList = ({ list }: GroupsListProps) => {
|
|||
},
|
||||
{
|
||||
title: t("common:Delete"),
|
||||
onClick: () => onDelete,
|
||||
onClick: async (
|
||||
_: React.MouseEvent<Element, MouseEvent>,
|
||||
rowId: number
|
||||
) => {
|
||||
try {
|
||||
await httpClient.doDelete(
|
||||
`/admin/realms/${realm}/groups/${list![rowId].id}`
|
||||
);
|
||||
refresh();
|
||||
addAlert(t("Group deleted"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(`${t("clientDeleteError")} ${error}`, AlertVariant.danger);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -2,24 +2,23 @@ import React, { useContext, useEffect, useState } from "react";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { GroupsList } from "./GroupsList";
|
||||
import { GroupsCreateModal } from "./GroupsCreateModal";
|
||||
import { GroupRepresentation } from "./models/groups";
|
||||
import {
|
||||
ServerGroupsArrayRepresentation,
|
||||
ServerGroupMembersRepresentation,
|
||||
} from "./models/server-info";
|
||||
import { TableToolbar } from "../components/table-toolbar/TableToolbar";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
KebabToggle,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Spinner,
|
||||
Title,
|
||||
TitleSizes,
|
||||
ToolbarItem,
|
||||
} from "@patternfly/react-core";
|
||||
import "./GroupsSection.css";
|
||||
|
@ -30,39 +29,43 @@ export const GroupsSection = () => {
|
|||
const [rawData, setRawData] = useState<{ [key: string]: any }[]>();
|
||||
const [filteredData, setFilteredData] = useState<object[]>();
|
||||
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
||||
const [createGroupName, setCreateGroupName] = useState("");
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const columnID: keyof GroupRepresentation = "id";
|
||||
const membersLength: keyof GroupRepresentation = "membersLength";
|
||||
const columnGroupName: keyof GroupRepresentation = "name";
|
||||
|
||||
const loader = async () => {
|
||||
const groups = await httpClient.doGet<ServerGroupsArrayRepresentation[]>(
|
||||
"/admin/realms/master/groups"
|
||||
);
|
||||
const groupsData = groups.data!;
|
||||
|
||||
const getMembers = async (id: number) => {
|
||||
const response = await httpClient.doGet<
|
||||
ServerGroupMembersRepresentation[]
|
||||
>(`/admin/realms/master/groups/${id}/members`);
|
||||
const responseData = response.data!;
|
||||
return responseData.length;
|
||||
};
|
||||
|
||||
const memberPromises = groupsData.map((group: { [key: string]: any }) =>
|
||||
getMembers(group[columnID])
|
||||
);
|
||||
const memberData = await Promise.all(memberPromises);
|
||||
const updatedObject = groupsData.map(
|
||||
(group: { [key: string]: any }, i: number) => {
|
||||
const object = Object.assign({}, group);
|
||||
object[membersLength] = memberData[i];
|
||||
return object;
|
||||
}
|
||||
);
|
||||
|
||||
setRawData(updatedObject);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const groups = await httpClient.doGet<ServerGroupsArrayRepresentation[]>(
|
||||
"/admin/realms/master/groups"
|
||||
);
|
||||
const groupsData = groups.data!;
|
||||
|
||||
const getMembers = async (id: number) => {
|
||||
const response = await httpClient.doGet<
|
||||
ServerGroupMembersRepresentation[]
|
||||
>(`/admin/realms/master/groups/${id}/members`);
|
||||
const responseData = response.data!;
|
||||
return responseData.length;
|
||||
};
|
||||
|
||||
const memberPromises = groupsData.map((group: { [key: string]: any }) =>
|
||||
getMembers(group[columnID])
|
||||
);
|
||||
const memberData = await Promise.all(memberPromises);
|
||||
const updatedObject = groupsData.map(
|
||||
(group: { [key: string]: any }, i: number) => {
|
||||
const object = Object.assign({}, group);
|
||||
object[membersLength] = memberData[i];
|
||||
return object;
|
||||
}
|
||||
);
|
||||
|
||||
setRawData(updatedObject);
|
||||
})();
|
||||
loader();
|
||||
}, []);
|
||||
|
||||
// Filter groups
|
||||
|
@ -83,15 +86,13 @@ export const GroupsSection = () => {
|
|||
setIsKebabOpen(!isKebabOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<Title headingLevel="h3" size={TitleSizes["2xl"]}>
|
||||
{t("groups")}
|
||||
</Title>
|
||||
</PageSection>
|
||||
<Divider />
|
||||
const handleModalToggle = () => {
|
||||
setIsCreateModalOpen(!isCreateModalOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader titleKey="groups:groups" subKey="groups:groupsDescription" />
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
{!rawData && (
|
||||
<div className="pf-u-text-align-center">
|
||||
|
@ -99,43 +100,61 @@ export const GroupsSection = () => {
|
|||
</div>
|
||||
)}
|
||||
{rawData && rawData.length > 0 ? (
|
||||
<TableToolbar
|
||||
inputGroupName="groupsToolbarTextInput"
|
||||
inputGroupPlaceholder={t("searchGroups")}
|
||||
inputGroupOnChange={filterGroups}
|
||||
toolbarItem={
|
||||
<>
|
||||
<ToolbarItem>
|
||||
<Button variant="primary">{t("createGroup")}</Button>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Dropdown
|
||||
onSelect={onKebabSelect}
|
||||
toggle={<KebabToggle onToggle={onKebabToggle} />}
|
||||
isOpen={isKebabOpen}
|
||||
isPlain
|
||||
dropdownItems={[
|
||||
<DropdownItem key="action" component="button">
|
||||
{t("delete")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{rawData && (
|
||||
<GroupsList list={filteredData ? filteredData : rawData} />
|
||||
)}
|
||||
{filteredData && filteredData.length === 0 && (
|
||||
<ListEmptyState
|
||||
hasIcon={true}
|
||||
isSearchVariant={true}
|
||||
message={t("noSearchResults")}
|
||||
instructions={t("noSearchResultsInstructions")}
|
||||
/>
|
||||
)}
|
||||
</TableToolbar>
|
||||
<>
|
||||
<TableToolbar
|
||||
inputGroupName="groupsToolbarTextInput"
|
||||
inputGroupPlaceholder={t("searchGroups")}
|
||||
inputGroupOnChange={filterGroups}
|
||||
toolbarItem={
|
||||
<>
|
||||
<ToolbarItem>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => handleModalToggle()}
|
||||
>
|
||||
{t("createGroup")}
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Dropdown
|
||||
onSelect={onKebabSelect}
|
||||
toggle={<KebabToggle onToggle={onKebabToggle} />}
|
||||
isOpen={isKebabOpen}
|
||||
isPlain
|
||||
dropdownItems={[
|
||||
<DropdownItem key="action" component="button">
|
||||
{t("delete")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{rawData && (
|
||||
<GroupsList
|
||||
list={filteredData ? filteredData : rawData}
|
||||
refresh={loader}
|
||||
/>
|
||||
)}
|
||||
{filteredData && filteredData.length === 0 && (
|
||||
<ListEmptyState
|
||||
hasIcon={true}
|
||||
isSearchVariant={true}
|
||||
message={t("noSearchResults")}
|
||||
instructions={t("noSearchResultsInstructions")}
|
||||
/>
|
||||
)}
|
||||
</TableToolbar>
|
||||
<GroupsCreateModal
|
||||
isCreateModalOpen={isCreateModalOpen}
|
||||
handleModalToggle={handleModalToggle}
|
||||
setIsCreateModalOpen={setIsCreateModalOpen}
|
||||
createGroupName={createGroupName}
|
||||
setCreateGroupName={setCreateGroupName}
|
||||
refresh={loader}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ListEmptyState
|
||||
hasIcon={true}
|
||||
|
@ -145,6 +164,6 @@ export const GroupsSection = () => {
|
|||
/>
|
||||
)}
|
||||
</PageSection>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
"moveTo": "Move to",
|
||||
"delete": "Delete",
|
||||
"tableOfGroups": "Table of groups",
|
||||
"name": "Name",
|
||||
"groupsDescription": "Description goes here",
|
||||
"groupCreated": "Group created",
|
||||
"couldNotCreateGroup": "Could not create group",
|
||||
"createAGroup": "Create a group",
|
||||
"create": "Create",
|
||||
"noSearchResults": "No search results",
|
||||
"noSearchResultsInstructions" : "Click on the search bar above to search for groups",
|
||||
"noGroupsInThisRealm" : "No groups in this Realm",
|
||||
|
|
Loading…
Reference in a new issue