added permission search (#1890)

This commit is contained in:
Erik Jan de Wit 2022-01-24 15:17:32 +01:00 committed by GitHub
parent 92c73a7fcc
commit c4724b21c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 186 additions and 106 deletions

View file

@ -30,12 +30,13 @@ import { useAlerts } from "../../components/alert/Alerts";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import useToggle from "../../utils/useToggle"; import useToggle from "../../utils/useToggle";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import { SearchDropdown } from "./SearchDropdown"; import { SearchDropdown, SearchForm } from "./SearchDropdown";
import { MoreLabel } from "./MoreLabel"; import { MoreLabel } from "./MoreLabel";
import { DetailDescription } from "./DetailDescription"; import { DetailDescription } from "./DetailDescription";
import { EmptyPermissionsState } from "./EmptyPermissionsState"; import { EmptyPermissionsState } from "./EmptyPermissionsState";
import { toNewPermission } from "../routes/NewPermission"; import { toNewPermission } from "../routes/NewPermission";
import { toPermissionDetails } from "../routes/PermissionDetails"; import { toPermissionDetails } from "../routes/PermissionDetails";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
import "./permissions.css"; import "./permissions.css";
@ -64,6 +65,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => {
const [disabledCreate, setDisabledCreate] = const [disabledCreate, setDisabledCreate] =
useState<{ resources: boolean; scopes: boolean }>(); useState<{ resources: boolean; scopes: boolean }>();
const [createOpen, toggleCreate] = useToggle(); const [createOpen, toggleCreate] = useToggle();
const [search, setSearch] = useState<SearchForm>({});
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(key + 1); const refresh = () => setKey(key + 1);
@ -90,6 +92,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => {
first, first,
max, max,
id: clientId, id: clientId,
...search,
}); });
return await Promise.all( return await Promise.all(
@ -109,7 +112,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => {
); );
}, },
setPermissions, setPermissions,
[key] [key, search]
); );
useFetch( useFetch(
@ -166,10 +169,12 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => {
return <KeycloakSpinner />; return <KeycloakSpinner />;
} }
const noData = permissions.length === 0;
const searching = Object.keys(search).length !== 0;
return ( return (
<PageSection variant="light" className="pf-u-p-0"> <PageSection variant="light" className="pf-u-p-0">
<DeleteConfirm /> <DeleteConfirm />
{permissions.length > 0 && ( {(!noData || searching) && (
<PaginatingTableToolbar <PaginatingTableToolbar
count={permissions.length} count={permissions.length}
first={first} first={first}
@ -183,7 +188,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => {
toolbarItem={ toolbarItem={
<> <>
<ToolbarItem> <ToolbarItem>
<SearchDropdown types={policyProviders} /> <SearchDropdown types={policyProviders} onSearch={setSearch} />
</ToolbarItem> </ToolbarItem>
<ToolbarItem> <ToolbarItem>
<Dropdown <Dropdown
@ -238,6 +243,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => {
</> </>
} }
> >
{!noData && (
<TableComposable aria-label={t("resources")} variant="compact"> <TableComposable aria-label={t("resources")} variant="compact">
<Thead> <Thead>
<Tr> <Tr>
@ -327,15 +333,23 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => {
</Tbody> </Tbody>
))} ))}
</TableComposable> </TableComposable>
)}
</PaginatingTableToolbar> </PaginatingTableToolbar>
)} )}
{permissions.length === 0 && ( {noData && !searching && (
<EmptyPermissionsState <EmptyPermissionsState
clientId={clientId} clientId={clientId}
isResourceEnabled={disabledCreate?.resources} isResourceEnabled={disabledCreate?.resources}
isScopeEnabled={disabledCreate?.scopes} isScopeEnabled={disabledCreate?.scopes}
/> />
)} )}
{noData && searching && (
<ListEmptyState
isSearchVariant
message={t("common:noSearchResults")}
instructions={t("common:noSearchResultsInstructions")}
/>
)}
</PageSection> </PageSection>
); );
}; };

View file

@ -2,6 +2,8 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { import {
ActionGroup,
Button,
Dropdown, Dropdown,
DropdownToggle, DropdownToggle,
Form, Form,
@ -17,17 +19,50 @@ import useToggle from "../../utils/useToggle";
import "./search-dropdown.css"; import "./search-dropdown.css";
type SearchDropdownProps = { export type SearchForm = {
types?: PolicyProviderRepresentation[]; name?: string;
resource?: string;
scope?: string;
type?: string;
}; };
export const SearchDropdown = ({ types }: SearchDropdownProps) => { type SearchDropdownProps = {
types?: PolicyProviderRepresentation[];
onSearch: (form: SearchForm) => void;
};
export const SearchDropdown = ({ types, onSearch }: SearchDropdownProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const { register, control } = useForm(); const {
register,
control,
formState: { isDirty },
handleSubmit,
} = useForm<SearchForm>({ mode: "onChange" });
const [open, toggle] = useToggle(); const [open, toggle] = useToggle();
const [typeOpen, toggleType] = useToggle(); const [typeOpen, toggleType] = useToggle();
const submit = (form: SearchForm) => {
toggle();
onSearch(form);
};
const typeOptions = (value: string) => [
<SelectOption key="empty" value="">
{t("allTypes")}
</SelectOption>,
...(types || []).map((type) => (
<SelectOption
selected={type.type === value}
key={type.type}
value={type.type}
>
{type.name}
</SelectOption>
)),
];
return ( return (
<Dropdown <Dropdown
data-testid="searchdropdown_dorpdown" data-testid="searchdropdown_dorpdown"
@ -45,6 +80,7 @@ export const SearchDropdown = ({ types }: SearchDropdownProps) => {
<Form <Form
isHorizontal isHorizontal
className="keycloak__client_authentication__searchdropdown_form" className="keycloak__client_authentication__searchdropdown_form"
onSubmit={handleSubmit(submit)}
> >
<FormGroup label={t("common:name")} fieldId="name"> <FormGroup label={t("common:name")} fieldId="name">
<TextInput <TextInput
@ -55,6 +91,24 @@ export const SearchDropdown = ({ types }: SearchDropdownProps) => {
data-testid="searchdropdown_name" data-testid="searchdropdown_name"
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("resource")} fieldId="resource">
<TextInput
ref={register}
type="text"
id="resource"
name="resource"
data-testid="searchdropdown_resource"
/>
</FormGroup>
<FormGroup label={t("scope")} fieldId="scope">
<TextInput
ref={register}
type="text"
id="scope"
name="scope"
data-testid="searchdropdown_scope"
/>
</FormGroup>
<FormGroup label={t("common:type")} fieldId="type"> <FormGroup label={t("common:type")} fieldId="type">
<Controller <Controller
name="type" name="type"
@ -69,24 +123,33 @@ export const SearchDropdown = ({ types }: SearchDropdownProps) => {
onChange(value); onChange(value);
toggleType(); toggleType();
}} }}
selections={value.name} selections={value || t("allTypes")}
variant={SelectVariant.single} variant={SelectVariant.single}
aria-label={t("common:type")} aria-label={t("common:type")}
isOpen={typeOpen} isOpen={typeOpen}
> >
{types?.map((type) => ( {typeOptions(value)}
<SelectOption
selected={type.type === value.type}
key={type.type}
value={type}
>
{type.name}
</SelectOption>
))}
</Select> </Select>
)} )}
/> />
</FormGroup> </FormGroup>
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="search-btn"
isDisabled={!isDirty}
>
{t("common:search")}
</Button>
<Button
variant="link"
data-testid="revert-btn"
onClick={() => onSearch({})}
>
{t("common:clear")}
</Button>
</ActionGroup>
</Form> </Form>
</Dropdown> </Dropdown>
); );

View file

@ -126,6 +126,9 @@ export default {
associatedPermissions: "Associated permission", associatedPermissions: "Associated permission",
allowRemoteResourceManagement: "Remote resource management", allowRemoteResourceManagement: "Remote resource management",
resources: "Resources", resources: "Resources",
resource: "Resource",
allTypes: "All types",
scope: "Scope",
owner: "Owner", owner: "Owner",
uris: "URIs", uris: "URIs",
scopes: "Scopes", scopes: "Scopes",