Introduce 'useRequiredContext' utility function (#882)

This commit is contained in:
Jon Koops 2021-07-21 11:30:18 +02:00 committed by GitHub
parent d8d230557e
commit 41a0923bad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 393 additions and 258 deletions

94
package-lock.json generated
View file

@ -58,6 +58,7 @@
"postcss-import": "^14.0.2",
"prettier": "^2.3.2",
"snowpack": "^3.8.2",
"ts-jest": "^26.5.6",
"ts-node": "^10.1.0",
"typescript": "4.2.4"
},
@ -4789,6 +4790,18 @@
"url": "https://opencollective.com/browserslist"
}
},
"node_modules/bs-logger": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
"dev": true,
"dependencies": {
"fast-json-stable-stringify": "2.x"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/bser": {
"version": "2.1.1",
"integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
@ -16097,6 +16110,49 @@
"integrity": "sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==",
"dev": true
},
"node_modules/ts-jest": {
"version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
"integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==",
"dev": true,
"dependencies": {
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^26.1.0",
"json5": "2.x",
"lodash": "4.x",
"make-error": "1.x",
"mkdirp": "1.x",
"semver": "7.x",
"yargs-parser": "20.x"
},
"bin": {
"ts-jest": "cli.js"
},
"engines": {
"node": ">= 10"
},
"peerDependencies": {
"jest": ">=26 <27",
"typescript": ">=3.8 <5.0"
}
},
"node_modules/ts-jest/node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-node": {
"version": "10.1.0",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
@ -20470,6 +20526,15 @@
"node-releases": "^1.1.71"
}
},
"bs-logger": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
"dev": true,
"requires": {
"fast-json-stable-stringify": "2.x"
}
},
"bser": {
"version": "2.1.1",
"integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
@ -28974,6 +29039,35 @@
"integrity": "sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==",
"dev": true
},
"ts-jest": {
"version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
"integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==",
"dev": true,
"requires": {
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^26.1.0",
"json5": "2.x",
"lodash": "4.x",
"make-error": "1.x",
"mkdirp": "1.x",
"semver": "7.x",
"yargs-parser": "20.x"
},
"dependencies": {
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"ts-node": {
"version": "10.1.0",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",

View file

@ -74,6 +74,7 @@
"postcss-import": "^14.0.2",
"prettier": "^2.3.2",
"snowpack": "^3.8.2",
"ts-jest": "^26.5.6",
"ts-node": "^10.1.0",
"typescript": "4.2.4"
},

View file

@ -1,5 +1,3 @@
import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import {
Avatar,
Brand,
@ -10,14 +8,16 @@ import {
KebabToggle,
PageHeader,
PageHeaderTools,
PageHeaderToolsItem,
PageHeaderToolsGroup,
PageHeaderToolsItem,
} from "@patternfly/react-core";
import { HelpIcon } from "@patternfly/react-icons";
import { WhoAmIContext } from "./context/whoami/WhoAmI";
import { HelpContext, HelpHeader } from "./components/help-enabler/HelpHeader";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom";
import { HelpHeader, useHelp } from "./components/help-enabler/HelpHeader";
import { useAdminClient } from "./context/auth/AdminClient";
import { useWhoAmI } from "./context/whoami/WhoAmI";
import environment from "./environment";
export const Header = () => {
@ -73,7 +73,7 @@ export const Header = () => {
const HelpDropdownItem = () => {
const { t } = useTranslation();
const { enabled, toggleHelp } = useContext(HelpContext);
const { enabled, toggleHelp } = useHelp();
return (
<DropdownItem icon={<HelpIcon />} onClick={toggleHelp}>
{enabled ? t("helpEnabled") : t("helpDisabled")}
@ -155,7 +155,7 @@ export const Header = () => {
};
const UserDropdown = () => {
const { whoAmI } = useContext(WhoAmIContext);
const { whoAmI } = useWhoAmI();
const [isDropdownOpen, setDropdownOpen] = useState(false);
const onDropdownToggle = () => {

View file

@ -1,36 +1,34 @@
import React, { useContext, useState } from "react";
import { useHistory, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Controller, useForm } from "react-hook-form";
import {
ActionGroup,
Button,
Divider,
FormGroup,
PageSection,
Select,
SelectVariant,
TextInput,
SelectOption,
ActionGroup,
Button,
SelectGroup,
SelectOption,
SelectVariant,
Split,
SplitItem,
Divider,
TextInput,
ValidatedOptions,
} from "@patternfly/react-core";
import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import type ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import React, { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useHistory, useParams } from "react-router-dom";
import { useAlerts } from "../../components/alert/Alerts";
import { RealmContext } from "../../context/realm-context/RealmContext";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
export const RoleMappingForm = () => {
const { realm } = useContext(RealmContext);
const { realm } = useRealm();
const adminClient = useAdminClient();
const history = useHistory();
const { addAlert } = useAlerts();

View file

@ -1,5 +1,3 @@
import React, { useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ClipboardCopy,
EmptyState,
@ -25,19 +23,19 @@ import {
} from "@patternfly/react-core";
import { QuestionCircleIcon } from "@patternfly/react-icons";
import type ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import type ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import type { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { RealmContext } from "../../context/realm-context/RealmContext";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useHelp } from "../../components/help-enabler/HelpHeader";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import "./evaluate.css";
import { HelpContext } from "../../components/help-enabler/HelpHeader";
export type EvaluateScopesProps = {
clientId: string;
@ -114,9 +112,9 @@ const EffectiveRoles = ({
export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
const prefix = "openid";
const { t } = useTranslation("clients");
const { enabled } = useContext(HelpContext);
const { enabled } = useHelp();
const adminClient = useAdminClient();
const { realm } = useContext(RealmContext);
const { realm } = useRealm();
const mapperTypes = useServerInfo().protocolMapperTypes![protocol];
const [selectableScopes, setSelectableScopes] = useState<

View file

@ -1,6 +1,7 @@
import React, { useState, createContext, ReactNode, useContext } from "react";
import { AlertType, AlertPanel } from "./AlertPanel";
import { AlertVariant } from "@patternfly/react-core";
import React, { createContext, ReactNode, useState } from "react";
import useRequiredContext from "../../utils/useRequiredContext";
import { AlertPanel, AlertType } from "./AlertPanel";
type AlertProps = {
addAlert: (
@ -10,11 +11,9 @@ type AlertProps = {
) => void;
};
export const AlertContext = createContext<AlertProps>({
addAlert: () => {},
});
export const AlertContext = createContext<AlertProps | undefined>(undefined);
export const useAlerts = () => useContext(AlertContext);
export const useAlerts = () => useRequiredContext(AlertContext);
export const AlertProvider = ({ children }: { children: ReactNode }) => {
const [alerts, setAlerts] = useState<AlertType[]>([]);

View file

@ -1,4 +1,3 @@
import React, { useState, useContext } from "react";
import {
Alert,
AlertVariant,
@ -13,13 +12,13 @@ import {
TextArea,
} from "@patternfly/react-core";
import FileSaver from "file-saver";
import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog";
import { HelpItem } from "../help-enabler/HelpItem";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { HelpContext } from "../help-enabler/HelpHeader";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog";
import { useHelp } from "../help-enabler/HelpHeader";
import { HelpItem } from "../help-enabler/HelpItem";
type DownloadDialogProps = {
id: string;
@ -36,7 +35,7 @@ export const DownloadDialog = ({
}: DownloadDialogProps) => {
const adminClient = useAdminClient();
const { t } = useTranslation("common");
const { enabled } = useContext(HelpContext);
const { enabled } = useHelp();
const serverInfo = useServerInfo();
const configFormats = serverInfo.clientInstallations![protocol];

View file

@ -1,4 +1,3 @@
import React, { useState, useContext, ReactNode, createContext } from "react";
import {
Divider,
Dropdown,
@ -9,9 +8,10 @@ import {
Switch,
TextContent,
} from "@patternfly/react-core";
import { ExternalLinkAltIcon, HelpIcon } from "@patternfly/react-icons";
import React, { createContext, ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import { HelpIcon, ExternalLinkAltIcon } from "@patternfly/react-icons";
import useRequiredContext from "../../utils/useRequiredContext";
import "./help-header.css";
type HelpProps = {
@ -23,10 +23,11 @@ type HelpContextProps = {
toggleHelp: () => void;
};
export const HelpContext = createContext<HelpContextProps>({
enabled: true,
toggleHelp: () => {},
});
export const HelpContext = createContext<HelpContextProps | undefined>(
undefined
);
export const useHelp = () => useRequiredContext(HelpContext);
export const Help = ({ children }: HelpProps) => {
const [enabled, setHelp] = useState(true);
@ -43,7 +44,7 @@ export const Help = ({ children }: HelpProps) => {
export const HelpHeader = () => {
const [open, setOpen] = useState(false);
const help = useContext(HelpContext);
const help = useHelp();
const { t } = useTranslation();
const dropdownItems = [

View file

@ -1,8 +1,8 @@
import React, { isValidElement, ReactNode, useContext } from "react";
import { Popover } from "@patternfly/react-core";
import { HelpIcon } from "@patternfly/react-icons";
import React, { isValidElement, ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { HelpContext } from "./HelpHeader";
import { useHelp } from "./HelpHeader";
type HelpItemProps = {
helpText: string | ReactNode;
@ -22,7 +22,7 @@ export const HelpItem = ({
unWrap = false,
}: HelpItemProps) => {
const { t } = useTranslation();
const { enabled } = useContext(HelpContext);
const { enabled } = useHelp();
return (
<>
{enabled && (

View file

@ -1,31 +1,30 @@
import React, { useState, useContext, ReactElement, useMemo } from "react";
import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
Dropdown,
DropdownToggle,
DropdownItem,
Button,
Divider,
SplitItem,
Split,
ContextSelector,
ContextSelectorItem,
Divider,
Dropdown,
DropdownItem,
DropdownToggle,
Label,
Split,
SplitItem,
} from "@patternfly/react-core";
import { CheckIcon } from "@patternfly/react-icons";
import React, { ReactElement, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toUpperCase } from "../../util";
import { useRealm } from "../../context/realm-context/RealmContext";
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
import { useWhoAmI } from "../../context/whoami/WhoAmI";
import { toUpperCase } from "../../util";
import { RecentUsed } from "./recent-used";
import "./realm-selector.css";
export const RealmSelector = () => {
const { realm, setRealm, realms } = useRealm();
const { whoAmI } = useContext(WhoAmIContext);
const { whoAmI } = useWhoAmI();
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const history = useHistory();

View file

@ -1,26 +1,26 @@
import React, { ReactElement, ReactNode, useContext, useState } from "react";
import {
Text,
PageSection,
TextContent,
Badge,
Divider,
Dropdown,
DropdownPosition,
DropdownToggle,
Level,
LevelItem,
PageSection,
Switch,
Text,
TextContent,
Toolbar,
ToolbarContent,
ToolbarItem,
Badge,
Dropdown,
DropdownToggle,
DropdownPosition,
} from "@patternfly/react-core";
import { HelpContext } from "../help-enabler/HelpHeader";
import React, { ReactElement, ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import {
FormattedLink,
FormattedLinkProps,
} from "../external-link/FormattedLink";
import { useHelp } from "../help-enabler/HelpHeader";
import { HelpItem } from "../help-enabler/HelpItem";
export type ViewHeaderProps = {
@ -56,7 +56,7 @@ export const ViewHeader = ({
helpTextKey,
}: ViewHeaderProps) => {
const { t } = useTranslation();
const { enabled } = useContext(HelpContext);
const { enabled } = useHelp();
const [isDropdownOpen, setDropdownOpen] = useState(false);
const [isLowerDropdownOpen, setIsLowerDropdownOpen] = useState(false);

View file

@ -1,25 +1,24 @@
import React, { createContext, useContext, useEffect, useState } from "react";
import type { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
import { RealmContext } from "../../context/realm-context/RealmContext";
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
import React, { createContext, useEffect, useState } from "react";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useWhoAmI } from "../../context/whoami/WhoAmI";
import useRequiredContext from "../../utils/useRequiredContext";
type AccessContextProps = {
hasAccess: (...types: AccessType[]) => boolean;
hasSomeAccess: (...types: AccessType[]) => boolean;
};
export const AccessContext = createContext<AccessContextProps>({
hasAccess: () => false,
hasSomeAccess: () => false,
});
export const AccessContext = createContext<AccessContextProps | undefined>(
undefined
);
export const useAccess = () => useContext(AccessContext);
export const useAccess = () => useRequiredContext(AccessContext);
type AccessProviderProps = { children: React.ReactNode };
export const AccessContextProvider = ({ children }: AccessProviderProps) => {
const { whoAmI } = useContext(WhoAmIContext);
const { realm } = useContext(RealmContext);
const { whoAmI } = useWhoAmI();
const { realm } = useRealm();
const [access, setAccess] = useState<readonly AccessType[]>([]);
useEffect(() => {

View file

@ -1,16 +1,14 @@
import { createContext, DependencyList, useContext, useEffect } from "react";
import axios from "axios";
import type KeycloakAdminClient from "keycloak-admin";
import { createContext, DependencyList, useEffect } from "react";
import { useErrorHandler } from "react-error-boundary";
import useRequiredContext from "../../utils/useRequiredContext";
export const AdminClient = createContext<KeycloakAdminClient | undefined>(
undefined
);
export const useAdminClient = () => {
return useContext(AdminClient)!;
};
export const useAdminClient = () => useRequiredContext(AdminClient);
/**
* Util function to only set the state when the component is still mounted.

View file

@ -1,10 +1,10 @@
import React, { useContext, useState } from "react";
import _ from "lodash";
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import _ from "lodash";
import React, { useState } from "react";
import { RecentUsed } from "../../components/realm-selector/recent-used";
import { useAdminClient, useFetch } from "../auth/AdminClient";
import environment from "../../environment";
import useRequiredContext from "../../utils/useRequiredContext";
import { useAdminClient, useFetch } from "../auth/AdminClient";
type RealmContextType = {
realm: string;
@ -13,12 +13,9 @@ type RealmContextType = {
refresh: () => Promise<void>;
};
export const RealmContext = React.createContext<RealmContextType>({
realm: "",
setRealm: () => {},
realms: [],
refresh: () => Promise.resolve(),
});
export const RealmContext = React.createContext<RealmContextType | undefined>(
undefined
);
type RealmContextProviderProps = { children: React.ReactNode };
@ -70,4 +67,4 @@ export const RealmContextProvider = ({
);
};
export const useRealm = () => useContext(RealmContext);
export const useRealm = () => useRequiredContext(RealmContext);

View file

@ -1,15 +1,15 @@
import React, { createContext, ReactNode, useContext } from "react";
import type { ServerInfoRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
import { sortProviders } from "../../util";
import React, { createContext, ReactNode } from "react";
import { DataLoader } from "../../components/data-loader/DataLoader";
import { sortProviders } from "../../util";
import useRequiredContext from "../../utils/useRequiredContext";
import { useAdminClient } from "../auth/AdminClient";
export const ServerInfoContext = createContext<ServerInfoRepresentation>(
{} as ServerInfoRepresentation
);
export const ServerInfoContext = createContext<
ServerInfoRepresentation | undefined
>(undefined);
export const useServerInfo = () => useContext(ServerInfoContext);
export const useServerInfo = () => useRequiredContext(ServerInfoContext);
export const useLoginProviders = () => {
return sortProviders(useServerInfo().providers!["login-protocol"].providers);

View file

@ -1,8 +1,8 @@
import React, { useState } from "react";
import i18n from "../../i18n";
import type WhoAmIRepresentation from "keycloak-admin/lib/defs/whoAmIRepresentation";
import type { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
import React, { useState } from "react";
import i18n from "../../i18n";
import useRequiredContext from "../../utils/useRequiredContext";
import { useAdminClient, useFetch } from "../auth/AdminClient";
export class WhoAmI {
@ -44,10 +44,11 @@ type WhoAmIProps = {
whoAmI: WhoAmI;
};
export const WhoAmIContext = React.createContext<WhoAmIProps>({
refresh: () => {},
whoAmI: new WhoAmI(),
});
export const WhoAmIContext = React.createContext<WhoAmIProps | undefined>(
undefined
);
export const useWhoAmI = () => useRequiredContext(WhoAmIContext);
type WhoAmIProviderProps = { children: React.ReactNode };
export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => {

View file

@ -1,6 +1,3 @@
import React, { ReactNode, useContext, useState } from "react";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
Button,
Modal,
@ -8,11 +5,6 @@ import {
ToolbarItem,
Tooltip,
} from "@patternfly/react-core";
import moment from "moment";
import { useAdminClient } from "../context/auth/AdminClient";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { RealmContext } from "../context/realm-context/RealmContext";
import {
cellWidth,
Table,
@ -21,7 +13,14 @@ import {
TableVariant,
} from "@patternfly/react-table";
import type AdminEventRepresentation from "keycloak-admin/lib/defs/adminEventRepresentation";
import moment from "moment";
import React, { ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
type DisplayDialogProps = {
titleKey: string;
@ -67,7 +66,7 @@ const Truncate = ({
export const AdminEvents = () => {
const { t } = useTranslation("events");
const adminClient = useAdminClient();
const { realm } = useContext(RealmContext);
const { realm } = useRealm();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());

View file

@ -1,7 +1,3 @@
import React, { useContext, useState } from "react";
import { Link } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import moment from "moment";
import {
Button,
DescriptionList,
@ -14,24 +10,26 @@ import {
ToolbarItem,
Tooltip,
} from "@patternfly/react-core";
import { cellWidth, expandable } from "@patternfly/react-table";
import { CheckCircleIcon, WarningTriangleIcon } from "@patternfly/react-icons";
import { cellWidth, expandable } from "@patternfly/react-table";
import type EventRepresentation from "keycloak-admin/lib/defs/eventRepresentation";
import { useAdminClient } from "../context/auth/AdminClient";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { RealmContext } from "../context/realm-context/RealmContext";
import { AdminEvents } from "./AdminEvents";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import moment from "moment";
import React, { useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { AdminEvents } from "./AdminEvents";
import "./events-section.css";
export const EventsSection = () => {
const { t } = useTranslation("events");
const adminClient = useAdminClient();
const { realm } = useContext(RealmContext);
const { realm } = useRealm();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());

View file

@ -1,5 +1,6 @@
import React, { createContext, ReactNode, useContext, useState } from "react";
import type GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
import React, { createContext, ReactNode, useState } from "react";
import useRequiredContext from "../utils/useRequiredContext";
type SubGroupsProps = {
subGroups: GroupRepresentation[];
@ -9,15 +10,7 @@ type SubGroupsProps = {
currentGroup: () => GroupRepresentation;
};
const SubGroupContext = createContext<SubGroupsProps>({
subGroups: [],
setSubGroups: () => {},
clear: () => {},
remove: () => {},
currentGroup: () => {
return {};
},
});
const SubGroupContext = createContext<SubGroupsProps | undefined>(undefined);
export const SubGroups = ({ children }: { children: ReactNode }) => {
const [subGroups, setSubGroups] = useState<GroupRepresentation[]>([]);
@ -37,4 +30,4 @@ export const SubGroups = ({ children }: { children: ReactNode }) => {
);
};
export const useSubGroups = () => useContext(SubGroupContext);
export const useSubGroups = () => useRequiredContext(SubGroupContext);

View file

@ -1,14 +1,14 @@
import React, { useContext } from "react";
import { useHistory, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Button, PageSection, Popover } from "@patternfly/react-core";
import { QuestionCircleIcon } from "@patternfly/react-icons";
import React from "react";
import { useTranslation } from "react-i18next";
import { useHistory, useParams } from "react-router-dom";
import { useHelp } from "../components/help-enabler/HelpHeader";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { upperCaseFormatter, emptyFormatter } from "../util";
import { useAdminClient } from "../context/auth/AdminClient";
import { QuestionCircleIcon } from "@patternfly/react-icons";
import { useRealm } from "../context/realm-context/RealmContext";
import { HelpContext } from "../components/help-enabler/HelpHeader";
import { emptyFormatter, upperCaseFormatter } from "../util";
export const UsersInRoleTab = () => {
const history = useHistory();
@ -30,7 +30,7 @@ export const UsersInRoleTab = () => {
return usersWithRole || [];
};
const { enabled } = useContext(HelpContext);
const { enabled } = useHelp();
return (
<>

View file

@ -1,6 +1,3 @@
import React, { useContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useForm, useWatch } from "react-hook-form";
import {
ActionGroup,
AlertVariant,
@ -11,20 +8,21 @@ import {
Switch,
TextInput,
} from "@patternfly/react-core";
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import React, { useEffect, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { emailRegexPattern } from "../util";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext";
import "./RealmSettingsSection.css";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import { WhoAmIContext } from "../context/whoami/WhoAmI";
import { useWhoAmI } from "../context/whoami/WhoAmI";
import { emailRegexPattern } from "../util";
import { AddUserEmailModal } from "./AddUserEmailModal";
import "./RealmSettingsSection.css";
type RealmSettingsEmailTabProps = {
realm: RealmRepresentation;
@ -39,7 +37,7 @@ export const RealmSettingsEmailTab = ({
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const { addAlert } = useAlerts();
const { whoAmI } = useContext(WhoAmIContext);
const { whoAmI } = useWhoAmI();
const [realm, setRealm] = useState(initialRealm);
const [userEmailModalOpen, setUserEmailModalOpen] = useState(false);

View file

@ -1,7 +1,3 @@
import React, { useEffect, useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Controller, FormProvider, useForm } from "react-hook-form";
import {
AlertVariant,
Breadcrumb,
@ -14,30 +10,33 @@ import {
Tabs,
TabTitleText,
} from "@patternfly/react-core";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import { toUpperCase } from "../util";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import React, { useEffect, useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAlerts } from "../components/alert/Alerts";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { RealmSettingsLoginTab } from "./LoginTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
import { PartialImportDialog } from "./PartialImport";
import { RealmSettingsThemesTab } from "./ThemesTab";
import { RealmSettingsEmailTab } from "./EmailTab";
import { KeysListTab } from "./KeysListTab";
import { EventsTab } from "./event-config/EventsTab";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { KeysProvidersTab } from "./KeysProvidersTab";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
//import { LocalizationTab } from "./LocalizationTab";
import { WhoAmIContext } from "../context/whoami/WhoAmI";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import { useWhoAmI } from "../context/whoami/WhoAmI";
import { toUpperCase } from "../util";
import { RealmSettingsEmailTab } from "./EmailTab";
import { EventsTab } from "./event-config/EventsTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
import { KeysListTab } from "./KeysListTab";
import { KeysProvidersTab } from "./KeysProvidersTab";
import { RealmSettingsLoginTab } from "./LoginTab";
import { PartialImportDialog } from "./PartialImport";
import { SecurityDefences } from "./security-defences/SecurityDefences";
import { RealmSettingsSessionsTab } from "./SessionsTab";
import { RealmSettingsThemesTab } from "./ThemesTab";
type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void;
@ -124,9 +123,7 @@ const RealmSettingsHeader = ({
>
{t("partialImport")}
</DropdownItem>,
<DropdownItem key="export" onClick={() => {}}>
{t("partialExport")}
</DropdownItem>,
<DropdownItem key="export">{t("partialExport")}</DropdownItem>,
<DropdownSeparator key="separator" />,
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
{t("common:delete")}
@ -159,7 +156,7 @@ export const RealmSettingsSection = () => {
const [realmComponents, setRealmComponents] =
useState<ComponentRepresentation[]>();
const [currentUser, setCurrentUser] = useState<UserRepresentation>();
const { whoAmI } = useContext(WhoAmIContext);
const { whoAmI } = useWhoAmI();
const kpComponentTypes =
useServerInfo().componentTypes!["org.keycloak.keys.KeyProvider"];

View file

@ -1,30 +1,29 @@
import React, { useContext } from "react";
import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
PageSection,
FormGroup,
TextInput,
Switch,
ActionGroup,
Button,
AlertVariant,
Button,
FormGroup,
PageSection,
Switch,
TextInput,
} from "@patternfly/react-core";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { useAlerts } from "../../components/alert/Alerts";
import { useForm, Controller } from "react-hook-form";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import { useAdminClient } from "../../context/auth/AdminClient";
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
import React from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useAlerts } from "../../components/alert/Alerts";
import { FormAccess } from "../../components/form-access/FormAccess";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { useAdminClient } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useWhoAmI } from "../../context/whoami/WhoAmI";
export const NewRealmForm = () => {
const { t } = useTranslation("realm");
const history = useHistory();
const { refresh } = useContext(WhoAmIContext);
const { refresh } = useWhoAmI();
const { refresh: realmRefresh } = useRealm();
const adminClient = useAdminClient();
const { addAlert } = useAlerts();

View file

@ -1,5 +1,3 @@
import React, { useContext, useState } from "react";
import { useHistory, useRouteMatch } from "react-router-dom";
import {
AlertVariant,
ButtonVariant,
@ -15,17 +13,17 @@ import {
TextContent,
TextVariants,
} from "@patternfly/react-core";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { KeycloakCard } from "../components/keycloak-card/KeycloakCard";
import { useAlerts } from "../components/alert/Alerts";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { DatabaseIcon } from "@patternfly/react-icons";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { RealmContext } from "../context/realm-context/RealmContext";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useHistory, useRouteMatch } from "react-router-dom";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { KeycloakCard } from "../components/keycloak-card/KeycloakCard";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import "./user-federation.css";
export const UserFederationSection = () => {
@ -33,7 +31,7 @@ export const UserFederationSection = () => {
useState<ComponentRepresentation[]>();
const { addAlert } = useAlerts();
const { t } = useTranslation("user-federation");
const { realm } = useContext(RealmContext);
const { realm } = useRealm();
const adminClient = useAdminClient();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());

View file

@ -1,6 +1,3 @@
import React, { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
AlertVariant,
Button,
@ -8,20 +5,23 @@ import {
Checkbox,
Popover,
} from "@patternfly/react-core";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { QuestionCircleIcon } from "@patternfly/react-icons";
import { cellWidth } from "@patternfly/react-table";
import type GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import _ from "lodash";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { emptyFormatter } from "../util";
import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import type GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
import { cellWidth } from "@patternfly/react-table";
import _ from "lodash";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
import { HelpContext } from "../components/help-enabler/HelpHeader";
import { QuestionCircleIcon } from "@patternfly/react-icons";
import { GroupPath } from "../components/group/GroupPath";
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
import { useHelp } from "../components/help-enabler/HelpHeader";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { emptyFormatter } from "../util";
export type UserFormProps = {
username?: string;
@ -52,7 +52,7 @@ export const UserGroups = () => {
>([]);
const [open, setOpen] = useState(false);
const { enabled } = useContext(HelpContext);
const { enabled } = useHelp();
const adminClient = useAdminClient();
const { id } = useParams<{ id: string }>();
@ -295,7 +295,6 @@ export const UserGroups = () => {
ariaLabelKey="roles:roleList"
searchPlaceholderKey="groups:searchGroup"
canSelectAll
onSelect={() => {}}
toolbarItem={
<>
<Button

View file

@ -1,6 +1,3 @@
import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory, useRouteMatch } from "react-router-dom";
import {
AlertVariant,
Button,
@ -16,17 +13,18 @@ import {
WarningTriangleIcon,
} from "@patternfly/react-icons";
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory, useRouteMatch } from "react-router-dom";
import { useAlerts } from "../components/alert/Alerts";
import { RealmContext } from "../context/realm-context/RealmContext";
import { SearchUser } from "./SearchUser";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { emptyFormatter } from "../util";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { emptyFormatter } from "../util";
import { SearchUser } from "./SearchUser";
import "./user-section.css";
type BruteUser = UserRepresentation & {
@ -37,7 +35,7 @@ export const UsersSection = () => {
const { t } = useTranslation("users");
const adminClient = useAdminClient();
const { addAlert } = useAlerts();
const { realm: realmName } = useContext(RealmContext);
const { realm: realmName } = useRealm();
const history = useHistory();
const { url } = useRouteMatch();
const [listUsers, setListUsers] = useState(false);

View file

@ -0,0 +1,47 @@
import type { Context } from "react";
import { useContext } from "react";
import { mocked } from "ts-jest/utils";
import useRequiredContext from "./useRequiredContext";
jest.mock("react");
const useContextMock = mocked(useContext);
describe("useRequiredContext", () => {
beforeEach(() => {
useContextMock.mockReset();
});
it("resolves the context", () => {
const context = {} as Context<unknown>;
const resolved = "FakeValue";
useContextMock.mockReturnValue(resolved);
expect(useRequiredContext(context)).toEqual(resolved);
});
it("throws if a named context cannot be resolved", () => {
const displayName = "FakeDisplayName";
const context = { displayName } as Context<unknown>;
const expected = `No provider found for the '${displayName}' context, make sure it is included in your component hierarchy.`;
useContextMock.mockReturnValue(undefined);
expect(() => useRequiredContext(context)).toThrow(expected);
useContextMock.mockReturnValue(null);
expect(() => useRequiredContext(context)).toThrow(expected);
});
it("throws if an unnamed context cannot be resolved", () => {
const context = {} as Context<unknown>;
const expected =
"No provider found for an unknown context, make sure it is included in your component hierarchy.";
useContextMock.mockReturnValue(undefined);
expect(() => useRequiredContext(context)).toThrow(expected);
useContextMock.mockReturnValue(null);
expect(() => useRequiredContext(context)).toThrow(expected);
});
});

View file

@ -0,0 +1,25 @@
import { useContext } from "react";
import type { Context } from "react";
/**
* Passes the call to `useContext` and throw an exception if the resolved value is either `null` or `undefined`.
* Can be used for contexts that are required and should always have a non nullable value.
*
* @param context The context to pass to `useContext`
* @returns
*/
export default function useRequiredContext<T>(
context: Context<T>
): NonNullable<T> {
const resolved = useContext(context);
if (resolved !== undefined && resolved !== null) {
return resolved as NonNullable<T>;
}
throw new Error(
`No provider found for ${
context.displayName ? `the '${context.displayName}'` : "an unknown"
} context, make sure it is included in your component hierarchy.`
);
}