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:
Erik Jan de Wit 2024-06-04 11:01:01 +02:00 committed by GitHub
parent e5123ea9de
commit d1756564a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 163 additions and 83 deletions

View file

@ -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(() => {

View file

@ -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";

View file

@ -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);

View file

@ -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";

View file

@ -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();

View file

@ -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;
}

View file

@ -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";

View file

@ -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";

View file

@ -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);

View file

@ -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)],
},
]}

View file

@ -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>,
];
}}

View file

@ -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)],
},
]}

View file

@ -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,
},
]}