Admin search (#1108)

* admin events: wip

* admin events: wip

* admin events: wip

* admin events: finalised admin search

* admin events: fixed user event search test

* admin events: added cypress tests

* admin events: added cypress tests

* admin events: added cypress tests for admin events search

* admin events: added auth prefix on the params

* admin events: improved search tests

* admin events: attempted fixing search tests

* admin events: fixed cypress events search tests

* admin events: fixed user events search test

* admin events: fixed user and admin events search test

* admin events: fixed admin events search test

Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com>
This commit is contained in:
agagancarczyk 2021-09-03 11:50:45 +01:00 committed by GitHub
parent d35582521d
commit 6e8ec947b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 449 additions and 122 deletions

View file

@ -1,18 +1,20 @@
import LoginPage from "../support/pages/LoginPage"; import LoginPage from "../support/pages/LoginPage";
import SidebarPage from "../support/pages/admin_console/SidebarPage"; import SidebarPage from "../support/pages/admin_console/SidebarPage";
import EventsPage from "../support/pages/admin_console/manage/events/EventsPage"; import UserEventsTab from "../support/pages/admin_console/manage/events/UserEventsTab";
import AdminEventsTab from "../support/pages/admin_console/manage/events/AdminEventsTab";
import RealmSettingsPage from "../support/pages/admin_console/manage/realm_settings/RealmSettingsPage"; import RealmSettingsPage from "../support/pages/admin_console/manage/realm_settings/RealmSettingsPage";
import Masthead from "../support/pages/admin_console/Masthead"; import Masthead from "../support/pages/admin_console/Masthead";
import { keycloakBefore } from "../support/util/keycloak_before"; import { keycloakBefore } from "../support/util/keycloak_before";
const loginPage = new LoginPage(); const loginPage = new LoginPage();
const sidebarPage = new SidebarPage(); const sidebarPage = new SidebarPage();
const eventsPage = new EventsPage(); const userEventsTab = new UserEventsTab();
const adminEventsTab = new AdminEventsTab();
const realmSettingsPage = new RealmSettingsPage(); const realmSettingsPage = new RealmSettingsPage();
const masthead = new Masthead(); const masthead = new Masthead();
describe("Search events test", function () { describe("Search events tests", function () {
describe("Search events dropdown", function () { describe("Search user events", function () {
beforeEach(function () { beforeEach(function () {
keycloakBefore(); keycloakBefore();
loginPage.logIn(); loginPage.logIn();
@ -20,47 +22,81 @@ describe("Search events test", function () {
}); });
it("Check search dropdown display", () => { it("Check search dropdown display", () => {
eventsPage.shouldDisplay(); userEventsTab.shouldDisplay();
}); });
it("Check search form fields display", () => { it("Check user events search form fields display", () => {
eventsPage.shouldHaveFormFields(); userEventsTab.shouldHaveFormFields();
}); });
it("Check event type dropdown options exist", () => { it("Check event type dropdown options exist", () => {
eventsPage.shouldHaveEventTypeOptions(); userEventsTab.shouldHaveEventTypeOptions();
}); });
it("Check `search events` button disabled by default", () => { it("Check `search events` button disabled by default", () => {
eventsPage.shouldHaveSearchBtnDisabled(); userEventsTab.shouldHaveSearchBtnDisabled();
}); });
it.skip("Check search and removal works", () => { it("Check user events search and removal work", () => {
sidebarPage.goToRealmSettings(); sidebarPage.goToRealmSettings();
cy.getId("rs-realm-events-tab").click(); cy.getId("rs-realm-events-tab").click();
cy.get("#eventsEnabled-switch-on") realmSettingsPage
.should("exist") .toggleSwitch(realmSettingsPage.enableEvents)
.then((exist) => { .save(realmSettingsPage.eventsUserSave);
if (exist) {
sidebarPage.goToEvents();
eventsPage.shouldDoSearchAndRemoveChips();
} else {
realmSettingsPage
.toggleSwitch(realmSettingsPage.enableEvents)
.save(realmSettingsPage.eventsUserSave);
masthead.checkNotificationMessage( masthead.signOut();
"Successfully saved configuration" loginPage.logIn();
);
sidebarPage.goToEvents(); sidebarPage.goToEvents();
eventsPage.shouldDoSearchAndRemoveChips(); userEventsTab.shouldDoSearchAndRemoveChips();
} });
});
it("Check for no events logged", () => {
userEventsTab.shouldDoNoResultsSearch();
}); });
it("Check `search events` button enabled", () => { it("Check `search events` button enabled", () => {
eventsPage.shouldHaveSearchBtnEnabled(); userEventsTab.shouldHaveSearchBtnEnabled();
});
});
describe("Search admin events", function () {
beforeEach(function () {
keycloakBefore();
loginPage.logIn();
sidebarPage.goToEvents();
cy.getId("admin-events-tab").click();
});
it("Check admin events search form fields display", () => {
adminEventsTab.shouldHaveFormFields();
});
it("Check `search admin events` button disabled by default", () => {
adminEventsTab.shouldHaveSearchBtnDisabled();
});
it("Check admin events search and removal work", () => {
sidebarPage.goToRealmSettings();
cy.getId("rs-realm-events-tab").click();
cy.getId("rs-admin-events-tab").click();
realmSettingsPage
.toggleSwitch(realmSettingsPage.enableAdminEvents)
.save(realmSettingsPage.eventsAdminSave);
sidebarPage.goToEvents();
cy.getId("admin-events-tab").click();
adminEventsTab.shouldDoAdminEventsSearchAndRemoveChips();
});
it("Check for no events logged", () => {
adminEventsTab.shouldDoNoResultsSearch();
});
it("Check `search admin events` button enabled", () => {
adminEventsTab.shouldHaveSearchBtnEnabled();
}); });
}); });
}); });

View file

@ -0,0 +1,66 @@
export default class AdminEventsTab {
searchAdminEventDrpDwn = ".pf-c-dropdown__toggle";
searchAdminEventDrpDwnBtn = "adminEventsSearchSelectorToggle";
searchForm = ".pf-c-dropdown__menu";
resourceTypesDrpDwnFld = "resource-types-searchField";
operationTypesDrpDwnFld = "operation-types-searchField";
resourcePathInputFld = "resourcePath-searchField";
userInputFld = "user-searchField";
realmInputFld = "realm-searchField";
ipAddressInputFld = "ipAddress-searchField";
dateFromInputFld = "dateFrom-searchField";
dateToInputFld = "dateTo-searchField";
searchEventsBtn = "search-events-btn";
operationTypesList = ".pf-c-form-control";
operationTypesOption = ".pf-c-select__menu-item";
operationTypesInputFld = ".pf-c-form-control.pf-c-select__toggle-typeahead";
operationTypesBtn = ".pf-c-button.pf-c-select__toggle-button.pf-m-plain";
adminEventsTabTitle = ".pf-c-title";
shouldHaveFormFields() {
cy.getId(this.searchAdminEventDrpDwnBtn).click();
cy.get(this.searchForm).contains("Resource types");
cy.get(this.searchForm).contains("Operation types");
cy.get(this.searchForm).contains("Resource path");
cy.get(this.searchForm).contains("User");
cy.get(this.searchForm).contains("Realm");
cy.get(this.searchForm).contains("IP address");
cy.get(this.searchForm).contains("Date(from)");
cy.get(this.searchForm).contains("Date(to)");
cy.get(this.searchForm).contains("Search admin events");
}
shouldHaveSearchBtnDisabled() {
cy.getId(this.searchAdminEventDrpDwnBtn).click();
cy.getId(this.searchEventsBtn).should("have.attr", "disabled");
}
shouldDoAdminEventsSearchAndRemoveChips() {
cy.getId(this.searchAdminEventDrpDwnBtn).click();
cy.getId(this.resourcePathInputFld).type("events/config");
cy.intercept("/auth/admin/realms/master/admin-events*").as("eventsFetch");
cy.getId(this.searchEventsBtn).click();
cy.wait("@eventsFetch");
cy.get("table").contains("td", "events/config").should("be.visible");
cy.get("[id^=remove_group]").click();
cy.wait("@eventsFetch");
cy.get("table").should("be.visible").contains("td", "UPDATE");
}
shouldHaveSearchBtnEnabled() {
cy.getId(this.searchAdminEventDrpDwnBtn).click();
cy.getId(this.ipAddressInputFld).type("11111");
cy.getId(this.searchEventsBtn).should("not.have.attr", "disabled");
}
shouldDoNoResultsSearch() {
cy.getId(this.searchAdminEventDrpDwnBtn).click();
cy.getId(this.resourcePathInputFld).type("events/test");
cy.getId(this.searchEventsBtn).click();
cy.get(this.adminEventsTabTitle).contains("No events logged");
}
}

View file

@ -1,4 +1,4 @@
export default class EventsPage { export default class UserEventsTab {
searchEventDrpDwn = ".pf-c-dropdown__toggle"; searchEventDrpDwn = ".pf-c-dropdown__toggle";
searchEventDrpDwnBtn = "userEventsSearchSelectorToggle"; searchEventDrpDwnBtn = "userEventsSearchSelectorToggle";
searchForm = ".pf-c-dropdown__menu"; searchForm = ".pf-c-dropdown__menu";
@ -12,7 +12,7 @@ export default class EventsPage {
eventTypeOption = ".pf-c-select__menu-item"; eventTypeOption = ".pf-c-select__menu-item";
eventTypeInputFld = ".pf-c-form-control.pf-c-select__toggle-typeahead"; eventTypeInputFld = ".pf-c-form-control.pf-c-select__toggle-typeahead";
eventTypeBtn = ".pf-c-button.pf-c-select__toggle-button.pf-m-plain"; eventTypeBtn = ".pf-c-button.pf-c-select__toggle-button.pf-m-plain";
eventsPageTitle = ".pf-c-title"; userEventsTabTitle = ".pf-c-title";
shouldDisplay() { shouldDisplay() {
cy.get(this.searchEventDrpDwn).should("exist"); cy.get(this.searchEventDrpDwn).should("exist");
@ -38,12 +38,6 @@ export default class EventsPage {
cy.getId(this.searchEventsBtn).should("have.attr", "disabled"); cy.getId(this.searchEventsBtn).should("have.attr", "disabled");
} }
shouldHaveSearchBtnEnabled() {
cy.getId(this.searchEventDrpDwnBtn).click();
cy.getId(this.userIdInputFld).type("11111");
cy.getId(this.searchEventsBtn).should("not.have.attr", "disabled");
}
shouldDoSearchAndRemoveChips() { shouldDoSearchAndRemoveChips() {
cy.getId(this.searchEventDrpDwnBtn).click(); cy.getId(this.searchEventDrpDwnBtn).click();
cy.get(this.eventTypeInputFld).type("LOGIN"); cy.get(this.eventTypeInputFld).type("LOGIN");
@ -59,20 +53,21 @@ export default class EventsPage {
cy.get("table").should("not.have.text", "LOGIN_ERROR"); cy.get("table").should("not.have.text", "LOGIN_ERROR");
cy.get("table").should("not.have.text", "LOGOUT"); cy.get("table").should("not.have.text", "LOGOUT");
cy.get("[id^=remove_pf]").click(); cy.get("[id^=remove_group]").click();
cy.wait("@eventsFetch");
cy.get("table").should("be.visible").contains("td", "LOGIN");
}
shouldHaveSearchBtnEnabled() {
cy.getId(this.searchEventDrpDwnBtn).click(); cy.getId(this.searchEventDrpDwnBtn).click();
cy.getId(this.userIdInputFld).type("11111"); cy.getId(this.userIdInputFld).type("11111");
cy.getId(this.searchEventsBtn).click(); cy.getId(this.searchEventsBtn).should("not.have.attr", "disabled");
cy.get(this.eventsPageTitle).contains("No events logged");
cy.get("[id^=remove_group]").click();
cy.get("table").contains("LOGIN");
} }
shouldDoNoResultsSearch() { shouldDoNoResultsSearch() {
cy.getId(this.searchEventDrpDwnBtn).click(); cy.getId(this.searchEventDrpDwnBtn).click();
cy.getId(this.userIdInputFld).type("test"); cy.getId(this.userIdInputFld).type("test");
cy.getId(this.searchEventsBtn).click(); cy.getId(this.searchEventsBtn).click();
cy.get(this.eventsPageTitle).contains("No events logged"); cy.get(this.userEventsTabTitle).contains("No events logged");
} }
} }

View file

@ -64,6 +64,8 @@ export default class RealmSettingsPage {
displayName = "display-name-input"; displayName = "display-name-input";
enableEvents = "eventsEnabled"; enableEvents = "eventsEnabled";
eventsUserSave = "save-user"; eventsUserSave = "save-user";
enableAdminEvents = "adminEventsEnabled";
eventsAdminSave = "save-admin";
eventTypeColumn = 'tbody > tr > [data-label="Event saved type"]'; eventTypeColumn = 'tbody > tr > [data-label="Event saved type"]';
filterSelectMenu = ".kc-filter-type-select"; filterSelectMenu = ".kc-filter-type-select";
passiveKeysOption = "passive-keys-option"; passiveKeysOption = "passive-keys-option";

1
package-lock.json generated
View file

@ -5,7 +5,6 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "keycloak-admin-ui",
"version": "0.0.1", "version": "0.0.1",
"license": "Apache", "license": "Apache",
"dependencies": { "dependencies": {

View file

@ -1,6 +1,8 @@
import { import {
ActionGroup, ActionGroup,
Button, Button,
Chip,
ChipGroup,
Dropdown, Dropdown,
DropdownToggle, DropdownToggle,
Flex, Flex,
@ -10,6 +12,7 @@ import {
Modal, Modal,
ModalVariant, ModalVariant,
Select, Select,
SelectOption,
SelectVariant, SelectVariant,
TextInput, TextInput,
Tooltip, Tooltip,
@ -24,13 +27,15 @@ import {
import type AdminEventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/adminEventRepresentation"; import type AdminEventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/adminEventRepresentation";
import moment from "moment"; import moment from "moment";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import { useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { pickBy } from "lodash";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import "./events.css"; import "./events.css";
type DisplayDialogProps = { type DisplayDialogProps = {
@ -39,27 +44,27 @@ type DisplayDialogProps = {
}; };
type AdminEventSearchForm = { type AdminEventSearchForm = {
operationType: string[]; resourceTypes: string[];
resourceType: string[]; operationTypes: string[];
resourcePath: string; resourcePath: string;
dateFrom: string; dateFrom: string;
dateTo: string; dateTo: string;
client: string; authClient: string;
user: string; authUser: string;
realm: string[]; authRealm: string;
ipAddress: string; authIpAddress: string;
}; };
const defaultValues: AdminEventSearchForm = { const defaultValues: AdminEventSearchForm = {
operationType: [], resourceTypes: [],
resourceType: [], operationTypes: [],
resourcePath: "", resourcePath: "",
dateFrom: "", dateFrom: "",
dateTo: "", dateTo: "",
client: "", authClient: "",
user: "", authUser: "",
realm: [], authRealm: "",
ipAddress: "", authIpAddress: "",
}; };
const DisplayDialog: FunctionComponent<DisplayDialogProps> = ({ const DisplayDialog: FunctionComponent<DisplayDialogProps> = ({
@ -105,36 +110,98 @@ export const AdminEvents = () => {
const { t } = useTranslation("events"); const { t } = useTranslation("events");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { realm } = useRealm(); const { realm } = useRealm();
const serverInfo = useServerInfo();
const resourceTypes = serverInfo.enums?.["resourceType"];
const operationTypes = serverInfo.enums?.["operationType"];
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const [searchDropdownOpen, setSearchDropdownOpen] = useState(false); const [searchDropdownOpen, setSearchDropdownOpen] = useState(false);
const [selectOpen, setSelectOpen] = useState(false); const [selectResourceTypesOpen, setSelectResourceTypesOpen] = useState(false);
const refresh = () => setKey(new Date().getTime()); const [selectOperationTypesOpen, setSelectOperationTypesOpen] =
useState(false);
const [activeFilters, setActiveFilters] = useState<
Partial<AdminEventSearchForm>
>({});
const [authEvent, setAuthEvent] = useState<AdminEventRepresentation>(); const [authEvent, setAuthEvent] = useState<AdminEventRepresentation>();
const [representationEvent, setRepresentationEvent] = const [representationEvent, setRepresentationEvent] =
useState<AdminEventRepresentation>(); useState<AdminEventRepresentation>();
const filterLabels: Record<keyof AdminEventSearchForm, string> = {
resourceTypes: t("resourceTypes"),
operationTypes: t("operationTypes"),
resourcePath: t("resourcePath"),
dateFrom: t("dateFrom"),
dateTo: t("dateTo"),
authClient: t("client"),
authUser: t("userId"),
authRealm: t("realm"),
authIpAddress: t("ipAddress"),
};
const { const {
getValues,
register, register,
reset,
formState: { isDirty }, formState: { isDirty },
control,
} = useForm<AdminEventSearchForm>({ } = useForm<AdminEventSearchForm>({
shouldUnregister: false, shouldUnregister: false,
mode: "onChange", mode: "onChange",
defaultValues, defaultValues,
}); });
const loader = async (first?: number, max?: number, search?: string) => { function loader(first?: number, max?: number) {
const params = { return adminClient.realms.findAdminEvents({
first: first!, // The admin client wants 'dateFrom' and 'dateTo' to be Date objects, however it cannot actually handle them so we need to cast to any.
max: max!, ...(activeFilters as any),
realm, realm,
}; first,
if (search) { max,
console.log("how to search?", search); });
} }
return await adminClient.realms.findAdminEvents({ ...params });
}; function submitSearch() {
setSearchDropdownOpen(false);
commitFilters();
}
function removeFilter(key: keyof AdminEventSearchForm) {
const formValues: AdminEventSearchForm = { ...getValues() };
delete formValues[key];
reset({ ...defaultValues, ...formValues });
commitFilters();
}
function removeFilterValue(
key: keyof AdminEventSearchForm,
valueToRemove: string
) {
const formValues = getValues();
const fieldValue = formValues[key];
const newFieldValue = Array.isArray(fieldValue)
? fieldValue.filter((val) => val !== valueToRemove)
: fieldValue;
reset({ ...formValues, [key]: newFieldValue });
commitFilters();
}
function commitFilters() {
const newFilters: Partial<AdminEventSearchForm> = pickBy(
getValues(),
(value) => value !== "" || (Array.isArray(value) && value.length > 0)
);
console.log(newFilters);
setActiveFilters(newFilters);
setKey(key + 1);
}
function refresh() {
commitFilters();
}
const LinkResource = (row: AdminEventRepresentation) => ( const LinkResource = (row: AdminEventRepresentation) => (
<Truncate text={row.resourcePath}> <Truncate text={row.resourcePath}>
@ -179,30 +246,177 @@ export const AdminEvents = () => {
> >
<Form <Form
isHorizontal isHorizontal
className="keycloak__admin_events_search__form" className="keycloak__events_search__form"
data-testid="searchForm" data-testid="searchForm"
> >
<FormGroup <FormGroup
label={t("resourceType")} label={t("resourceTypes")}
fieldId="kc-resourceType" fieldId="kc-resourceTypes"
className="keycloak__events_search__form_multiline_label" className="keycloak__events_search__form_label"
> >
<Select <Controller
variant={SelectVariant.single} name="resourceTypes"
onToggle={(isOpen) => setSelectOpen(isOpen)} control={control}
isOpen={selectOpen} render={({
></Select> onChange,
value,
}: {
onChange: (newValue: string[]) => void;
value: string[];
}) => (
<Select
className="keycloak__events_search__type_select"
name="resourceTypes"
data-testid="resource-types-searchField"
chipGroupProps={{
numChips: 1,
expandedText: "Hide",
collapsedText: "Show ${remaining}",
}}
variant={SelectVariant.typeaheadMulti}
typeAheadAriaLabel="Select"
onToggle={(isOpen) => setSelectResourceTypesOpen(isOpen)}
selections={value}
onSelect={(_, selectedValue) => {
const option = selectedValue.toString();
const changedValue = value.includes(option)
? value.filter((item) => item !== option)
: [...value, option];
onChange(changedValue);
}}
onClear={(resource) => {
resource.stopPropagation();
onChange([]);
}}
isOpen={selectResourceTypesOpen}
aria-labelledby={"resourceTypes"}
chipGroupComponent={
<ChipGroup>
{value.map((chip) => (
<Chip
key={chip}
onClick={(resource) => {
resource.stopPropagation();
onChange(value.filter((val) => val !== chip));
}}
>
{chip}
</Chip>
))}
</ChipGroup>
}
>
{resourceTypes?.map((option) => (
<SelectOption key={option} value={option} />
))}
</Select>
)}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("operationType")} label={t("operationTypes")}
fieldId="kc-operationType" fieldId="kc-operationTypes"
className="keycloak__events_search__form_multiline_label" className="keycloak__events_search__form_label"
> >
<Select <Controller
variant={SelectVariant.single} name="operationTypes"
onToggle={(isOpen) => setSelectOpen(isOpen)} control={control}
isOpen={selectOpen} render={({
></Select> onChange,
value,
}: {
onChange: (newValue: string[]) => void;
value: string[];
}) => (
<Select
className="keycloak__events_search__type_select"
name="operationTypes"
data-testid="operation-types-searchField"
chipGroupProps={{
numChips: 1,
expandedText: "Hide",
collapsedText: "Show ${remaining}",
}}
variant={SelectVariant.typeaheadMulti}
typeAheadAriaLabel="Select"
onToggle={(isOpen) => setSelectOperationTypesOpen(isOpen)}
selections={value}
onSelect={(_, selectedValue) => {
const option = selectedValue.toString();
const changedValue = value.includes(option)
? value.filter((item) => item !== option)
: [...value, option];
onChange(changedValue);
}}
onClear={(operation) => {
operation.stopPropagation();
onChange([]);
}}
isOpen={selectOperationTypesOpen}
aria-labelledby={"operationTypes"}
chipGroupComponent={
<ChipGroup>
{value.map((chip) => (
<Chip
key={chip}
onClick={(operation) => {
operation.stopPropagation();
onChange(value.filter((val) => val !== chip));
}}
>
{chip}
</Chip>
))}
</ChipGroup>
}
>
{operationTypes?.map((option) => (
<SelectOption key={option} value={option} />
))}
</Select>
)}
/>
</FormGroup>
<FormGroup
label={t("resourcePath")}
fieldId="kc-resourcePath"
className="keycloak__events_search__form_label"
>
<TextInput
ref={register()}
type="text"
id="kc-resourcePath"
name="resourcePath"
data-testid="resourcePath-searchField"
/>
</FormGroup>
<FormGroup
label={t("realm")}
fieldId="kc-realm"
className="keycloak__events_search__form_label"
>
<TextInput
ref={register()}
type="text"
id="kc-realm"
name="authRealm"
data-testid="realm-searchField"
/>
</FormGroup>
<FormGroup
label={t("client")}
fieldId="kc-client"
className="keycloak__events_search__form_label"
>
<TextInput
ref={register()}
type="text"
id="kc-client"
name="authClient"
data-testid="client-searchField"
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("user")} label={t("user")}
@ -213,21 +427,10 @@ export const AdminEvents = () => {
ref={register()} ref={register()}
type="text" type="text"
id="kc-user" id="kc-user"
name="user" name="authUser"
data-testid="user-searchField" data-testid="user-searchField"
/> />
</FormGroup> </FormGroup>
<FormGroup
label={t("realm")}
fieldId="kc-realm"
className="keycloak__events_search__form_label"
>
<Select
variant={SelectVariant.single}
onToggle={(isOpen) => setSelectOpen(isOpen)}
isOpen={selectOpen}
></Select>
</FormGroup>
<FormGroup <FormGroup
label={t("ipAddress")} label={t("ipAddress")}
fieldId="kc-ipAddress" fieldId="kc-ipAddress"
@ -237,7 +440,7 @@ export const AdminEvents = () => {
ref={register()} ref={register()}
type="text" type="text"
id="kc-ipAddress" id="kc-ipAddress"
name="ipAddress" name="authIpAddress"
data-testid="ipAddress-searchField" data-testid="ipAddress-searchField"
/> />
</FormGroup> </FormGroup>
@ -273,8 +476,9 @@ export const AdminEvents = () => {
</FormGroup> </FormGroup>
<ActionGroup> <ActionGroup>
<Button <Button
className="keycloak__admin_events_search__form_btn" className="keycloak__user_events_search__form_btn"
variant={"primary"} variant={"primary"}
onClick={submitSearch}
data-testid="search-events-btn" data-testid="search-events-btn"
isDisabled={!isDirty} isDisabled={!isDirty}
> >
@ -291,6 +495,41 @@ export const AdminEvents = () => {
{t("refresh")} {t("refresh")}
</Button> </Button>
</FlexItem> </FlexItem>
<FlexItem>
{Object.entries(activeFilters).length > 0 && (
<div className="keycloak__searchChips pf-u-ml-md">
{Object.entries(activeFilters).map((filter) => {
const [key, value] = filter as [
keyof AdminEventSearchForm,
string | string[]
];
return (
<ChipGroup
className="pf-u-mt-md pf-u-mr-md"
key={key}
categoryName={filterLabels[key]}
isClosable
onClick={() => removeFilter(key)}
>
{typeof value === "string" ? (
<Chip isReadOnly>{value}</Chip>
) : (
value.map((entry) => (
<Chip
key={entry}
onClick={() => removeFilterValue(key, entry)}
>
{entry}
</Chip>
))
)}
</ChipGroup>
);
})}
</div>
)}
</FlexItem>
</Flex> </Flex>
); );
}; };

View file

@ -206,7 +206,7 @@ export const EventsSection = () => {
> >
<Form <Form
isHorizontal isHorizontal
className="keycloak__user_events_search__form" className="keycloak__events_search__form"
data-testid="searchForm" data-testid="searchForm"
> >
<FormGroup <FormGroup
@ -238,7 +238,7 @@ export const EventsSection = () => {
value: EventType[]; value: EventType[];
}) => ( }) => (
<Select <Select
className="keycloak__events_search__event_type_select" className="keycloak__events_search__type_select"
name="eventType" name="eventType"
data-testid="event-type-searchField" data-testid="event-type-searchField"
chipGroupProps={{ chipGroupProps={{
@ -454,12 +454,10 @@ export const EventsSection = () => {
}, },
]} ]}
emptyState={ emptyState={
<div className="pf-u-mt-md"> <ListEmptyState
<ListEmptyState message={t("emptyEvents")}
message={t("emptyEvents")} instructions={t("emptyEventsInstructions")}
instructions={t("emptyEventsInstructions")} />
/>
</div>
} }
/> />
</div> </div>
@ -467,6 +465,7 @@ export const EventsSection = () => {
<Tab <Tab
eventKey="adminEvents" eventKey="adminEvents"
title={<TabTitleText>{t("adminEvents")}</TabTitleText>} title={<TabTitleText>{t("adminEvents")}</TabTitleText>}
data-testid="admin-events-tab"
> >
<AdminEvents /> <AdminEvents />
</Tab> </Tab>

View file

@ -8,16 +8,8 @@
z-index: 1; z-index: 1;
} }
.keycloak__user_events_search__form { .keycloak__events_search__form {
--pf-c-form--m-horizontal__group-control--md--GridColumnWidth: 24rem; --pf-c-form--m-horizontal__group-control--md--GridColumnWidth: 24rem;
}
.keycloak__admin_events_search__form {
--pf-c-form--m-horizontal__group-control--md--GridColumnWidth: 12rem;
}
.keycloak__user_events_search__form,
.keycloak__admin_events_search__form {
margin: 0 var(--pf-global--spacer--lg) var(--pf-global--spacer--lg) var(--pf-global--spacer--lg); margin: 0 var(--pf-global--spacer--lg) var(--pf-global--spacer--lg) var(--pf-global--spacer--lg);
--pf-c-form__group--m-action--MarginTop: 0rem; --pf-c-form__group--m-action--MarginTop: 0rem;
--pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 5rem; --pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 5rem;
@ -25,13 +17,10 @@
.keycloak__events_search__form_label { .keycloak__events_search__form_label {
--pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 5rem; --pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 5rem;
line-height: 0px;
} }
.keycloak__events_search__form_multiline_label { .keycloak__events_search__type_select .pf-c-select__menu {
height: 2.2rem;
}
.keycloak__events_search__event_type_select .pf-c-select__menu {
max-height: 200px; max-height: 200px;
overflow: auto; overflow: auto;
} }

View file

@ -26,7 +26,9 @@ export default {
realm: "Realm", realm: "Realm",
resourcePath: "Resource path", resourcePath: "Resource path",
resourceType: "Resource type", resourceType: "Resource type",
resourceTypes: "Resource types",
operationType: "Operation type", operationType: "Operation type",
operationTypes: "Operation types",
auth: "Auth", auth: "Auth",
attribute: "Attribute", attribute: "Attribute",
value: "Value", value: "Value",

View file

@ -172,7 +172,7 @@ export const EventsTab = () => {
<Tab <Tab
eventKey="admin" eventKey="admin"
title={<TabTitleText>{t("adminEventsSettings")}</TabTitleText>} title={<TabTitleText>{t("adminEventsSettings")}</TabTitleText>}
data-testid="rs-events-tab" data-testid="rs-admin-events-tab"
> >
<PageSection> <PageSection>
<Title headingLevel="h4" size="xl"> <Title headingLevel="h4" size="xl">