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:
parent
d35582521d
commit
6e8ec947b5
10 changed files with 449 additions and 122 deletions
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
1
package-lock.json
generated
|
@ -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": {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue