Merge pull request #142 from christiemolloy/createGroup

Add Create Group Functionality
This commit is contained in:
mfrances17 2020-10-14 17:20:18 -04:00 committed by GitHub
commit bcd864bfc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 223 additions and 90 deletions

View 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>
</>
);
};

View file

@ -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);
}
},
},
];

View file

@ -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>
</>
);
};

View file

@ -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",