remove use of deprecated table component (#29812)
* remove use of deprecated table component Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * added transformer Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix row click Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * removed useless name label Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix row click again Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * removed more useless name label Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * removed useless options Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * removed useless options Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * removed data-label Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix for action click Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * made indeterminate work again Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
e5123ea9de
commit
d1756564a7
13 changed files with 163 additions and 83 deletions
|
@ -777,7 +777,7 @@ describe("Clients test", () => {
|
|||
commonPage.sidebar().waitForPageLoad();
|
||||
rolesTab.goToAssociatedRolesTab();
|
||||
|
||||
cy.get('td[data-label="Name"]')
|
||||
cy.get("td")
|
||||
.contains("manage-account")
|
||||
.parent()
|
||||
.within(() => {
|
||||
|
|
|
@ -11,7 +11,7 @@ export default class RoleMappingTab {
|
|||
#assignBtn = "assign";
|
||||
#hideInheritedRolesBtn = "#hideInheritedRoles";
|
||||
#assignedRolesTable = "assigned-roles";
|
||||
#namesColumn = 'td[data-label="Name"]:visible';
|
||||
#namesColumn = "td:visible";
|
||||
#roleMappingTab = "role-mapping-tab";
|
||||
#filterTypeDropdown = "filter-type-dropdown";
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ export default class InitialAccessTokenTab extends CommonPage {
|
|||
}
|
||||
|
||||
getFirstId(callback: (id: string) => void) {
|
||||
cy.get('tbody > tr:first-child > [data-label="ID"]')
|
||||
cy.get("tbody > tr:first-child > td:first-child")
|
||||
.invoke("text")
|
||||
.then((text) => {
|
||||
callback(text);
|
||||
|
|
|
@ -13,8 +13,7 @@ export default class GroupDetailPage extends GroupPage {
|
|||
#attributesTab = "attributes";
|
||||
#roleMappingTab = "role-mapping-tab";
|
||||
#permissionsTab = "permissionsTab";
|
||||
#memberNameColumn =
|
||||
'[data-testid="members-table"] > tbody > tr > [data-label="Name"]';
|
||||
#memberNameColumn = '[data-testid="members-table"] > tbody > tr';
|
||||
#addMembers = "addMember";
|
||||
#memberUsernameColumn = 'tbody > tr > [data-label="Username"]';
|
||||
#actionDrpDwnItemRenameGroup = "renameGroupAction";
|
||||
|
|
|
@ -8,7 +8,7 @@ export default class AssociatedRolesPage {
|
|||
#filterTypeDropdownItem = "clients";
|
||||
#usersPage = "users-page";
|
||||
#removeRolesButton = "unAssignRole";
|
||||
#addRoleTable = '[aria-label="Roles"] td[data-label="Name"]';
|
||||
#addRoleTable = '[aria-label="Roles"] td';
|
||||
|
||||
addAssociatedRealmRole(roleName: string) {
|
||||
cy.findByTestId(this.#actionDropdown).last().click();
|
||||
|
|
|
@ -85,7 +85,7 @@ export default class RealmSettingsPage extends CommonPage {
|
|||
eventsUserSave = "save-user";
|
||||
enableAdminEvents = "adminEventsEnabled";
|
||||
eventsAdminSave = "save-admin";
|
||||
eventTypeColumn = 'tbody > tr > [data-label="Event saved type"]';
|
||||
eventTypeColumn = "tbody > tr td";
|
||||
filterSelectMenu = ".kc-filter-type-select";
|
||||
passiveKeysOption = "PASSIVE-option";
|
||||
disabledKeysOption = "DISABLED-option";
|
||||
|
@ -1270,12 +1270,12 @@ export default class RealmSettingsPage extends CommonPage {
|
|||
}
|
||||
|
||||
checkElementNotInList(name: string) {
|
||||
cy.get('tbody [data-label="Name"]').should("not.contain.text", name);
|
||||
cy.get("tbody").should("not.contain.text", name);
|
||||
return this;
|
||||
}
|
||||
|
||||
checkElementInList(name: string) {
|
||||
cy.get('tbody [data-label="Name"]').should("contain.text", name);
|
||||
cy.get("tbody").should("contain.text", name);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export default class UserProfile {
|
|||
#newAttributeAnnotationBtn = "annotations-add-row";
|
||||
#newAttributeAnnotationKey = "annotations.0.key";
|
||||
#newAttributeAnnotationValue = "annotations.0.value";
|
||||
#validatorsList = 'tbody [data-label="name"]';
|
||||
#validatorsList = "tbody";
|
||||
#saveNewAttributeBtn = "attribute-create";
|
||||
#addValidatorBtn = "addValidator";
|
||||
#removeValidatorBtn = "deleteValidator";
|
||||
|
|
|
@ -3,7 +3,7 @@ export default class UserRegistration {
|
|||
#defaultGroupTab = "#pf-tab-20-groups";
|
||||
#addRoleBtn = "assignRole";
|
||||
#addDefaultGroupBtn = "no-default-groups-empty-action";
|
||||
#namesColumn = 'tbody td[data-label="Name"]:visible';
|
||||
#namesColumn = "tbody td:visible";
|
||||
#addBtn = "assign";
|
||||
#filterTypeDropdown = "filter-type-dropdown";
|
||||
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
import { Button, ButtonVariant, ToolbarItem } from "@patternfly/react-core";
|
||||
import type { SVGIconProps } from "@patternfly/react-icons/dist/js/createIcon";
|
||||
import {
|
||||
ActionsColumn,
|
||||
ExpandableRowContent,
|
||||
IAction,
|
||||
IActions,
|
||||
IActionsResolver,
|
||||
IFormatter,
|
||||
IRow,
|
||||
IRowCell,
|
||||
ITransform,
|
||||
TableVariant,
|
||||
} from "@patternfly/react-table";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableProps,
|
||||
} from "@patternfly/react-table/deprecated";
|
||||
TableVariant,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
import { cloneDeep, differenceBy, get } from "lodash-es";
|
||||
import {
|
||||
ComponentClass,
|
||||
|
@ -67,6 +71,18 @@ type DataTableProps<T> = {
|
|||
isRadio?: boolean;
|
||||
};
|
||||
|
||||
type CellRendererProps = {
|
||||
row: IRow;
|
||||
};
|
||||
|
||||
const CellRenderer = ({ row }: CellRendererProps) => {
|
||||
const isRow = (c: ReactNode | IRowCell): c is IRowCell =>
|
||||
!!c && (c as IRowCell).title !== undefined;
|
||||
return row.cells!.map((c, i) => (
|
||||
<Td key={`cell-${i}`}>{(isRow(c) ? c.title : c) as ReactNode}</Td>
|
||||
));
|
||||
};
|
||||
|
||||
function DataTable<T>({
|
||||
columns,
|
||||
rows,
|
||||
|
@ -81,32 +97,129 @@ function DataTable<T>({
|
|||
...props
|
||||
}: DataTableProps<T>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selectedRows, setSelectedRows] = useState<boolean[]>([]);
|
||||
const [expandedRows, setExpandedRows] = useState<boolean[]>([]);
|
||||
|
||||
const updateState = (rowIndex: number, isSelected: boolean) => {
|
||||
const items = [
|
||||
...(rowIndex === -1 ? Array(rows.length).fill(isSelected) : selectedRows),
|
||||
];
|
||||
items[rowIndex] = isSelected;
|
||||
setSelectedRows(items);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (canSelectAll) {
|
||||
const selectAllCheckbox = document.getElementsByName("check-all").item(0);
|
||||
if (selectAllCheckbox) {
|
||||
const checkbox = selectAllCheckbox as HTMLInputElement;
|
||||
const selected = selectedRows.filter((r) => r === true);
|
||||
checkbox.indeterminate =
|
||||
selected.length < rows.length && selected.length > 0;
|
||||
}
|
||||
}
|
||||
}, [selectedRows]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
{...props}
|
||||
variant={isNotCompact ? undefined : TableVariant.compact}
|
||||
onSelect={
|
||||
onSelect
|
||||
? (_, isSelected, rowIndex) => onSelect(isSelected, rowIndex)
|
||||
: undefined
|
||||
}
|
||||
onCollapse={
|
||||
onCollapse
|
||||
? (_, rowIndex, isOpen) => onCollapse(isOpen, rowIndex)
|
||||
: undefined
|
||||
}
|
||||
selectVariant={isRadio ? "radio" : "checkbox"}
|
||||
canSelectAll={canSelectAll}
|
||||
cells={columns.map((column) => {
|
||||
return { ...column, title: t(column.displayKey || column.name) };
|
||||
})}
|
||||
rows={rows as IRow[]}
|
||||
actions={actions}
|
||||
actionResolver={actionResolver}
|
||||
aria-label={t(ariaLabelKey)}
|
||||
>
|
||||
<TableHeader />
|
||||
<TableBody />
|
||||
<Thead>
|
||||
<Tr>
|
||||
{onCollapse && <Th />}
|
||||
{canSelectAll && (
|
||||
<Th
|
||||
select={
|
||||
!isRadio
|
||||
? {
|
||||
onSelect: (_, isSelected, rowIndex) => {
|
||||
onSelect!(isSelected, rowIndex);
|
||||
updateState(-1, isSelected);
|
||||
},
|
||||
isSelected:
|
||||
selectedRows.filter((r) => r === true).length ===
|
||||
rows.length,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{columns.map((column) => (
|
||||
<Th
|
||||
key={column.displayKey}
|
||||
aria-label={t(ariaLabelKey)}
|
||||
className={column.transforms?.[0]().className}
|
||||
>
|
||||
{t(column.displayKey || column.name)}
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
{!onCollapse ? (
|
||||
<Tbody>
|
||||
{(rows as IRow[]).map((row, index) => (
|
||||
<Tr key={index} isExpanded={expandedRows[index]}>
|
||||
{onSelect && (
|
||||
<Td
|
||||
select={{
|
||||
rowIndex: index,
|
||||
onSelect: (_, isSelected, rowIndex) => {
|
||||
onSelect!(isSelected, rowIndex);
|
||||
updateState(rowIndex, isSelected);
|
||||
},
|
||||
isSelected: selectedRows[index],
|
||||
variant: isRadio ? "radio" : "checkbox",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<CellRenderer row={row} />
|
||||
{(actions || actionResolver) && (
|
||||
<Td isActionCell>
|
||||
<ActionsColumn
|
||||
items={actions || actionResolver?.(row, {})!}
|
||||
extraData={{ rowIndex: index }}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
) : (
|
||||
(rows as IRow[]).map((row, index) => (
|
||||
<Tbody key={index}>
|
||||
{index % 2 === 0 ? (
|
||||
<Tr>
|
||||
<Td
|
||||
expand={{
|
||||
isExpanded: !!expandedRows[index],
|
||||
rowIndex: index,
|
||||
expandId: `${index}`,
|
||||
onToggle: (_, rowIndex, isOpen) => {
|
||||
onCollapse(isOpen, rowIndex);
|
||||
const expand = [...expandedRows];
|
||||
expand[index] = isOpen;
|
||||
setExpandedRows(expand);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<CellRenderer row={row} />
|
||||
</Tr>
|
||||
) : (
|
||||
<Tr isExpanded={!!expandedRows[index - 1]}>
|
||||
<Td />
|
||||
<Td colSpan={columns.length}>
|
||||
<ExpandableRowContent>
|
||||
<CellRenderer row={row} />
|
||||
</ExpandableRowContent>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</Tbody>
|
||||
))
|
||||
)}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
@ -229,6 +342,10 @@ export function KeycloakDataTable<T>({
|
|||
|
||||
const renderCell = (columns: (Field<T> | DetailField<T>)[], value: T) => {
|
||||
return columns.map((col) => {
|
||||
if ("cellFormatters" in col) {
|
||||
const v = get(value, col.name);
|
||||
return col.cellFormatters?.reduce((s, f) => f(s), v);
|
||||
}
|
||||
if (col.cellRenderer) {
|
||||
const Component = col.cellRenderer;
|
||||
//@ts-ignore
|
||||
|
@ -302,22 +419,6 @@ export function KeycloakDataTable<T>({
|
|||
[search, first, max],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (canSelectAll) {
|
||||
const checkboxes = document
|
||||
.getElementsByClassName("pf-v5-c-table__check")
|
||||
.item(0);
|
||||
if (checkboxes) {
|
||||
const checkAllCheckbox = checkboxes.children!.item(
|
||||
0,
|
||||
)! as HTMLInputElement;
|
||||
checkAllCheckbox.indeterminate =
|
||||
selected.length > 0 &&
|
||||
selected.length < (filteredData || rows)!.length;
|
||||
}
|
||||
}
|
||||
}, [selected]);
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
setLoading(true);
|
||||
|
|
|
@ -153,7 +153,6 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
|
|||
}
|
||||
/>
|
||||
}
|
||||
canSelectAll
|
||||
columns={[
|
||||
{
|
||||
name: "algorithm",
|
||||
|
@ -182,16 +181,14 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
|
|||
{
|
||||
name: "provider",
|
||||
displayKey: "provider",
|
||||
cellRenderer: ({ provider }: KeyData) => provider || "",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
cellRenderer: ({ provider }: KeyData) => provider || "-",
|
||||
transforms: [cellWidth(10)],
|
||||
},
|
||||
{
|
||||
name: "validTo",
|
||||
displayKey: "validTo",
|
||||
cellRenderer: ({ validTo }: KeyData) =>
|
||||
validTo ? formatDate(new Date(validTo)) : "",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
validTo ? formatDate(new Date(validTo)) : "-",
|
||||
transforms: [cellWidth(10)],
|
||||
},
|
||||
{
|
||||
|
@ -252,7 +249,6 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
|
|||
);
|
||||
} else return "";
|
||||
},
|
||||
cellFormatters: [],
|
||||
transforms: [cellWidth(20)],
|
||||
},
|
||||
]}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { CubesIcon, InfoCircleIcon } from "@patternfly/react-icons";
|
||||
import { IRowData } from "@patternfly/react-table";
|
||||
import { MouseEvent, ReactNode, useMemo, useState } from "react";
|
||||
import { ReactNode, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useMatch, useNavigate } from "react-router-dom";
|
||||
import { useAdminClient } from "../admin-client";
|
||||
|
@ -165,11 +165,7 @@ export default function SessionsTable({
|
|||
},
|
||||
});
|
||||
|
||||
async function onClickRevoke(
|
||||
event: MouseEvent,
|
||||
rowIndex: number,
|
||||
rowData: IRowData,
|
||||
) {
|
||||
async function onClickRevoke(rowData: IRowData) {
|
||||
const session = rowData.data as UserSessionRepresentation;
|
||||
await adminClient.realms.deleteSession({
|
||||
realm,
|
||||
|
@ -180,11 +176,7 @@ export default function SessionsTable({
|
|||
refresh();
|
||||
}
|
||||
|
||||
async function onClickSignOut(
|
||||
event: MouseEvent,
|
||||
rowIndex: number,
|
||||
rowData: IRowData,
|
||||
) {
|
||||
async function onClickSignOut(rowData: IRowData) {
|
||||
const session = rowData.data as UserSessionRepresentation;
|
||||
await adminClient.realms.deleteSession({
|
||||
realm,
|
||||
|
@ -230,14 +222,14 @@ export default function SessionsTable({
|
|||
return [
|
||||
{
|
||||
title: t("revoke"),
|
||||
onClick: onClickRevoke,
|
||||
onClick: () => onClickRevoke(rowData),
|
||||
} as Action<UserSessionRepresentation>,
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
title: t("signOut"),
|
||||
onClick: onClickSignOut,
|
||||
onClick: () => onClickSignOut(rowData),
|
||||
} as Action<UserSessionRepresentation>,
|
||||
];
|
||||
}}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { useHelp } from "@keycloak/keycloak-ui-shared";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -12,7 +13,6 @@ import { cellWidth } from "@patternfly/react-table";
|
|||
import { intersectionBy, sortBy, uniqBy } from "lodash-es";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHelp } from "@keycloak/keycloak-ui-shared";
|
||||
import { useAdminClient } from "../admin-client";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
|
@ -21,7 +21,6 @@ import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
|
|||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { emptyFormatter } from "../util";
|
||||
|
||||
type UserGroupsProps = {
|
||||
user: UserRepresentation;
|
||||
|
@ -239,8 +238,7 @@ export const UserGroups = ({ user }: UserGroupsProps) => {
|
|||
{
|
||||
name: "groupMembership",
|
||||
displayKey: "groupMembership",
|
||||
cellRenderer: (group: GroupRepresentation) => group.name || "",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
cellRenderer: (group: GroupRepresentation) => group.name || "-",
|
||||
transforms: [cellWidth(40)],
|
||||
},
|
||||
{
|
||||
|
@ -269,10 +267,9 @@ export const UserGroups = ({ user }: UserGroupsProps) => {
|
|||
{t("leave")}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
"-"
|
||||
);
|
||||
},
|
||||
cellFormatters: [emptyFormatter()],
|
||||
transforms: [cellWidth(20)],
|
||||
},
|
||||
]}
|
||||
|
|
|
@ -194,7 +194,6 @@ export const UserIdentityProviderLinks = ({
|
|||
{
|
||||
name: "identityProvider",
|
||||
displayKey: "name",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
cellRenderer: idpLinkRenderer,
|
||||
transforms: [cellWidth(20)],
|
||||
},
|
||||
|
@ -213,7 +212,6 @@ export const UserIdentityProviderLinks = ({
|
|||
},
|
||||
{
|
||||
name: "",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
cellRenderer: unlinkRenderer,
|
||||
transforms: [cellWidth(20)],
|
||||
},
|
||||
|
@ -223,7 +221,6 @@ export const UserIdentityProviderLinks = ({
|
|||
columns.splice(1, 0, {
|
||||
name: "type",
|
||||
displayKey: "type",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
cellRenderer: badgeRenderer1,
|
||||
transforms: [cellWidth(10)],
|
||||
});
|
||||
|
@ -286,13 +283,11 @@ export const UserIdentityProviderLinks = ({
|
|||
{
|
||||
name: "type",
|
||||
displayKey: "type",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
cellRenderer: badgeRenderer2,
|
||||
transforms: [cellWidth(60)],
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
cellRenderer: linkRenderer,
|
||||
},
|
||||
]}
|
||||
|
|
Loading…
Reference in a new issue