2021-06-16 11:35:03 +00:00
|
|
|
import React, { useEffect, useState } from "react";
|
2021-03-01 15:06:04 +00:00
|
|
|
import { Link } from "react-router-dom";
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
ButtonVariant,
|
|
|
|
Chip,
|
|
|
|
ChipGroup,
|
|
|
|
InputGroup,
|
|
|
|
PageSection,
|
|
|
|
PageSectionVariants,
|
2021-08-16 12:35:39 +00:00
|
|
|
Stack,
|
|
|
|
StackItem,
|
2021-03-01 15:06:04 +00:00
|
|
|
TextInput,
|
2021-08-16 12:35:39 +00:00
|
|
|
ToolbarItem,
|
2021-03-01 15:06:04 +00:00
|
|
|
} from "@patternfly/react-core";
|
|
|
|
import { SearchIcon } from "@patternfly/react-icons";
|
|
|
|
|
2021-08-26 08:39:35 +00:00
|
|
|
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
2021-03-01 15:06:04 +00:00
|
|
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
|
|
|
import { useAdminClient } from "../context/auth/AdminClient";
|
|
|
|
import { useRealm } from "../context/realm-context/RealmContext";
|
|
|
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
2021-06-21 17:55:51 +00:00
|
|
|
import { GroupPath } from "../components/group/GroupPath";
|
2021-06-16 11:35:03 +00:00
|
|
|
import { useSubGroups } from "./SubGroupsContext";
|
2021-08-16 12:35:39 +00:00
|
|
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
2021-03-01 15:06:04 +00:00
|
|
|
|
|
|
|
type SearchGroup = GroupRepresentation & {
|
|
|
|
link?: string;
|
|
|
|
};
|
|
|
|
|
2021-10-29 16:11:06 +00:00
|
|
|
export default function SearchGroups() {
|
2021-03-01 15:06:04 +00:00
|
|
|
const { t } = useTranslation("groups");
|
|
|
|
const adminClient = useAdminClient();
|
|
|
|
const { realm } = useRealm();
|
|
|
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
const [searchTerms, setSearchTerms] = useState<string[]>([]);
|
|
|
|
|
|
|
|
const [key, setKey] = useState(0);
|
|
|
|
const refresh = () => setKey(new Date().getTime());
|
|
|
|
|
2021-06-16 11:35:03 +00:00
|
|
|
const { setSubGroups } = useSubGroups();
|
2021-08-16 12:35:39 +00:00
|
|
|
useEffect(
|
|
|
|
() => setSubGroups([{ name: t("searchGroups"), id: "search" }]),
|
|
|
|
[]
|
|
|
|
);
|
2021-06-16 11:35:03 +00:00
|
|
|
|
2021-03-01 15:06:04 +00:00
|
|
|
const deleteTerm = (id: string) => {
|
|
|
|
const index = searchTerms.indexOf(id);
|
|
|
|
searchTerms.splice(index, 1);
|
|
|
|
setSearchTerms([...searchTerms]);
|
|
|
|
refresh();
|
|
|
|
};
|
|
|
|
|
|
|
|
const addTerm = () => {
|
2021-08-16 12:35:39 +00:00
|
|
|
if (searchTerm !== "") {
|
|
|
|
setSearchTerms([...searchTerms, searchTerm]);
|
|
|
|
setSearchTerm("");
|
|
|
|
refresh();
|
|
|
|
}
|
2021-03-01 15:06:04 +00:00
|
|
|
};
|
|
|
|
|
2021-08-16 12:35:39 +00:00
|
|
|
const GroupNameCell = (group: SearchGroup) => (
|
2021-08-26 12:15:28 +00:00
|
|
|
<Link
|
|
|
|
key={group.id}
|
|
|
|
to={`/${realm}/groups/search/${group.link}`}
|
|
|
|
onClick={() =>
|
|
|
|
setSubGroups([{ name: t("searchGroups"), id: "search" }, group])
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{group.name}
|
|
|
|
</Link>
|
2021-08-16 12:35:39 +00:00
|
|
|
);
|
2021-03-01 15:06:04 +00:00
|
|
|
|
|
|
|
const flatten = (
|
|
|
|
groups: GroupRepresentation[],
|
|
|
|
id?: string
|
|
|
|
): SearchGroup[] => {
|
|
|
|
let result: SearchGroup[] = [];
|
|
|
|
for (const group of groups) {
|
|
|
|
const link = `${id || ""}${id ? "/" : ""}${group.id}`;
|
|
|
|
result.push({ ...group, link });
|
|
|
|
if (group.subGroups) {
|
|
|
|
result = [...result, ...flatten(group.subGroups, link)];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
const loader = async (first?: number, max?: number) => {
|
|
|
|
const params = {
|
|
|
|
first: first!,
|
|
|
|
max: max!,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result: SearchGroup[] = [];
|
|
|
|
if (searchTerms[0]) {
|
|
|
|
result = await adminClient.groups.find({
|
|
|
|
...params,
|
|
|
|
search: searchTerms[0],
|
|
|
|
});
|
|
|
|
result = flatten(result);
|
|
|
|
for (const searchTerm of searchTerms) {
|
|
|
|
result = result.filter((group) => group.name?.includes(searchTerm));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2021-06-21 17:55:51 +00:00
|
|
|
const Path = (group: GroupRepresentation) => <GroupPath group={group} />;
|
|
|
|
|
2021-03-01 15:06:04 +00:00
|
|
|
return (
|
|
|
|
<>
|
2021-08-16 12:35:39 +00:00
|
|
|
<ViewHeader titleKey="groups:searchGroups" />
|
|
|
|
<PageSection variant={PageSectionVariants.light} className="pf-u-p-0">
|
2021-03-01 15:06:04 +00:00
|
|
|
<KeycloakDataTable
|
|
|
|
key={key}
|
2021-08-16 12:35:39 +00:00
|
|
|
isSearching
|
|
|
|
toolbarItem={
|
|
|
|
<ToolbarItem>
|
|
|
|
<Stack>
|
|
|
|
<StackItem className="pf-u-mb-sm">
|
|
|
|
<InputGroup>
|
|
|
|
<TextInput
|
|
|
|
name="search"
|
|
|
|
data-testid="group-search"
|
|
|
|
type="search"
|
|
|
|
aria-label={t("search")}
|
|
|
|
placeholder={t("searchGroups")}
|
|
|
|
value={searchTerm}
|
|
|
|
onChange={(value) => setSearchTerm(value)}
|
|
|
|
onKeyDown={(event) => {
|
|
|
|
if (event.key === "Enter") {
|
|
|
|
addTerm();
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<Button
|
|
|
|
data-testid="search-button"
|
|
|
|
variant={ButtonVariant.control}
|
|
|
|
aria-label={t("search")}
|
|
|
|
onClick={addTerm}
|
|
|
|
>
|
|
|
|
<SearchIcon />
|
|
|
|
</Button>
|
|
|
|
</InputGroup>
|
|
|
|
</StackItem>
|
|
|
|
<StackItem>
|
|
|
|
<ChipGroup>
|
|
|
|
{searchTerms.map((term) => (
|
|
|
|
<Chip key={term} onClick={() => deleteTerm(term)}>
|
|
|
|
{term}
|
|
|
|
</Chip>
|
|
|
|
))}
|
|
|
|
</ChipGroup>
|
|
|
|
</StackItem>
|
|
|
|
</Stack>
|
|
|
|
</ToolbarItem>
|
|
|
|
}
|
2021-03-01 15:06:04 +00:00
|
|
|
ariaLabelKey="groups:groups"
|
|
|
|
isPaginated
|
|
|
|
loader={loader}
|
|
|
|
columns={[
|
|
|
|
{
|
|
|
|
name: "name",
|
|
|
|
displayKey: "groups:groupName",
|
|
|
|
cellRenderer: GroupNameCell,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "path",
|
|
|
|
displayKey: "groups:path",
|
2021-06-21 17:55:51 +00:00
|
|
|
cellRenderer: Path,
|
2021-03-01 15:06:04 +00:00
|
|
|
},
|
|
|
|
]}
|
|
|
|
emptyState={
|
|
|
|
<ListEmptyState
|
|
|
|
message={t("noSearchResults")}
|
|
|
|
instructions={t("noSearchResultsInstructions")}
|
|
|
|
hasIcon={false}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</PageSection>
|
|
|
|
</>
|
|
|
|
);
|
2021-10-29 16:11:06 +00:00
|
|
|
}
|