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, TableBody,
TableVariant, TableVariant,
} from "@patternfly/react-table"; } from "@patternfly/react-table";
import { Button } from "@patternfly/react-core"; import { Button, AlertVariant } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { GroupRepresentation } from "./models/groups"; import { GroupRepresentation } from "./models/groups";
import { UsersIcon } from "@patternfly/react-icons"; import { UsersIcon } from "@patternfly/react-icons";
import { HttpClientContext } from "../context/http-service/HttpClientContext"; 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[]; list?: GroupRepresentation[];
refresh: () => void;
}; };
type FormattedData = { type FormattedData = {
@ -20,11 +23,13 @@ type FormattedData = {
selected: boolean; selected: boolean;
}; };
export const GroupsList = ({ list }: GroupsListProps) => { export const GroupsList = ({ list, refresh }: GroupsListProps) => {
const { t } = useTranslation("groups"); const { t } = useTranslation("groups");
const httpClient = useContext(HttpClientContext)!; const httpClient = useContext(HttpClientContext)!;
const columnGroupName: keyof GroupRepresentation = "name"; const columnGroupName: keyof GroupRepresentation = "name";
const columnGroupNumber: keyof GroupRepresentation = "membersLength"; const columnGroupNumber: keyof GroupRepresentation = "membersLength";
const { realm } = useContext(RealmContext);
const { addAlert } = useAlerts();
const [formattedData, setFormattedData] = useState<FormattedData[]>([]); const [formattedData, setFormattedData] = useState<FormattedData[]>([]);
const formatData = (data: GroupRepresentation[]) => 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 tableHeader = [{ title: t("groupName") }, { title: t("members") }];
const actions = [ const actions = [
{ {
@ -84,7 +80,20 @@ export const GroupsList = ({ list }: GroupsListProps) => {
}, },
{ {
title: t("common:Delete"), 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 { useTranslation } from "react-i18next";
import { HttpClientContext } from "../context/http-service/HttpClientContext"; import { HttpClientContext } from "../context/http-service/HttpClientContext";
import { GroupsList } from "./GroupsList"; import { GroupsList } from "./GroupsList";
import { GroupsCreateModal } from "./GroupsCreateModal";
import { GroupRepresentation } from "./models/groups"; import { GroupRepresentation } from "./models/groups";
import { import {
ServerGroupsArrayRepresentation, ServerGroupsArrayRepresentation,
ServerGroupMembersRepresentation, ServerGroupMembersRepresentation,
} from "./models/server-info"; } from "./models/server-info";
import { TableToolbar } from "../components/table-toolbar/TableToolbar"; import { TableToolbar } from "../components/table-toolbar/TableToolbar";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { import {
Button, Button,
Divider,
Dropdown, Dropdown,
DropdownItem, DropdownItem,
KebabToggle, KebabToggle,
PageSection, PageSection,
PageSectionVariants, PageSectionVariants,
Spinner, Spinner,
Title,
TitleSizes,
ToolbarItem, ToolbarItem,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import "./GroupsSection.css"; import "./GroupsSection.css";
@ -30,39 +29,43 @@ export const GroupsSection = () => {
const [rawData, setRawData] = useState<{ [key: string]: any }[]>(); const [rawData, setRawData] = useState<{ [key: string]: any }[]>();
const [filteredData, setFilteredData] = useState<object[]>(); const [filteredData, setFilteredData] = useState<object[]>();
const [isKebabOpen, setIsKebabOpen] = useState(false); const [isKebabOpen, setIsKebabOpen] = useState(false);
const [createGroupName, setCreateGroupName] = useState("");
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const columnID: keyof GroupRepresentation = "id"; const columnID: keyof GroupRepresentation = "id";
const membersLength: keyof GroupRepresentation = "membersLength"; const membersLength: keyof GroupRepresentation = "membersLength";
const columnGroupName: keyof GroupRepresentation = "name"; 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(() => { useEffect(() => {
(async () => { loader();
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);
})();
}, []); }, []);
// Filter groups // Filter groups
@ -83,15 +86,13 @@ export const GroupsSection = () => {
setIsKebabOpen(!isKebabOpen); setIsKebabOpen(!isKebabOpen);
}; };
return ( const handleModalToggle = () => {
<React.Fragment> setIsCreateModalOpen(!isCreateModalOpen);
<PageSection variant={PageSectionVariants.light}> };
<Title headingLevel="h3" size={TitleSizes["2xl"]}>
{t("groups")}
</Title>
</PageSection>
<Divider />
return (
<>
<ViewHeader titleKey="groups:groups" subKey="groups:groupsDescription" />
<PageSection variant={PageSectionVariants.light}> <PageSection variant={PageSectionVariants.light}>
{!rawData && ( {!rawData && (
<div className="pf-u-text-align-center"> <div className="pf-u-text-align-center">
@ -99,43 +100,61 @@ export const GroupsSection = () => {
</div> </div>
)} )}
{rawData && rawData.length > 0 ? ( {rawData && rawData.length > 0 ? (
<TableToolbar <>
inputGroupName="groupsToolbarTextInput" <TableToolbar
inputGroupPlaceholder={t("searchGroups")} inputGroupName="groupsToolbarTextInput"
inputGroupOnChange={filterGroups} inputGroupPlaceholder={t("searchGroups")}
toolbarItem={ inputGroupOnChange={filterGroups}
<> toolbarItem={
<ToolbarItem> <>
<Button variant="primary">{t("createGroup")}</Button> <ToolbarItem>
</ToolbarItem> <Button
<ToolbarItem> variant="primary"
<Dropdown onClick={() => handleModalToggle()}
onSelect={onKebabSelect} >
toggle={<KebabToggle onToggle={onKebabToggle} />} {t("createGroup")}
isOpen={isKebabOpen} </Button>
isPlain </ToolbarItem>
dropdownItems={[ <ToolbarItem>
<DropdownItem key="action" component="button"> <Dropdown
{t("delete")} onSelect={onKebabSelect}
</DropdownItem>, toggle={<KebabToggle onToggle={onKebabToggle} />}
]} isOpen={isKebabOpen}
/> isPlain
</ToolbarItem> dropdownItems={[
</> <DropdownItem key="action" component="button">
} {t("delete")}
> </DropdownItem>,
{rawData && ( ]}
<GroupsList list={filteredData ? filteredData : rawData} /> />
)} </ToolbarItem>
{filteredData && filteredData.length === 0 && ( </>
<ListEmptyState }
hasIcon={true} >
isSearchVariant={true} {rawData && (
message={t("noSearchResults")} <GroupsList
instructions={t("noSearchResultsInstructions")} list={filteredData ? filteredData : rawData}
/> refresh={loader}
)} />
</TableToolbar> )}
{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 <ListEmptyState
hasIcon={true} hasIcon={true}
@ -145,6 +164,6 @@ export const GroupsSection = () => {
/> />
)} )}
</PageSection> </PageSection>
</React.Fragment> </>
); );
}; };

View file

@ -10,6 +10,12 @@
"moveTo": "Move to", "moveTo": "Move to",
"delete": "Delete", "delete": "Delete",
"tableOfGroups": "Table of groups", "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", "noSearchResults": "No search results",
"noSearchResultsInstructions" : "Click on the search bar above to search for groups", "noSearchResultsInstructions" : "Click on the search bar above to search for groups",
"noGroupsInThisRealm" : "No groups in this Realm", "noGroupsInThisRealm" : "No groups in this Realm",