diff --git a/package-lock.json b/package-lock.json index 23115dc80..f8ac998be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", - "@redhat-cloud-services/frontend-components-config": "^6.3.1", + "@redhat-cloud-services/frontend-components-config": "^6.3.3", "@redhat-cloud-services/tsc-transform-imports": "^1.0.4", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^13.0.0", @@ -3935,7 +3935,8 @@ }, "node_modules/@openshift/dynamic-plugin-sdk": { "version": "5.0.1", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-5.0.1.tgz", + "integrity": "sha512-+azUBN6FgcDmlcWMzG0bthcRUJC1u12wf9xa2aJGFbC/uTiOXwjrkcQ7LW/PyK5Em7wDhwaUdapaeOgh8I6Kjg==", "dependencies": { "lodash": "^4.17.21", "semver": "^7.3.7", @@ -3975,22 +3976,10 @@ "node": ">=10" } }, - "node_modules/@openshift/dynamic-plugin-sdk/node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@openshift/dynamic-plugin-sdk/node_modules/semver": { - "version": "7.5.4", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -3998,10 +3987,6 @@ "node": ">=10" } }, - "node_modules/@openshift/dynamic-plugin-sdk/node_modules/yallist": { - "version": "4.0.0", - "license": "ISC" - }, "node_modules/@patternfly/quickstarts": { "version": "5.1.0", "license": "MIT", @@ -4621,16 +4606,17 @@ } }, "node_modules/@redhat-cloud-services/frontend-components": { - "version": "4.2.13", - "license": "Apache-2.0", + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.2.22.tgz", + "integrity": "sha512-FEDxxNHF0Jg6thM1IIFxHZSVbsS5Eq4QxOuPvbhlXA7ijvxzkgh5hDwBZyRSYhL2za+sZPqZzYTgwwu1Mb3MNw==", "dependencies": { "@patternfly/react-component-groups": "^5.0.0", "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", "@redhat-cloud-services/types": "^1.0.9", - "@scalprum/core": "^0.7.0", - "@scalprum/react-core": "^0.8.0", + "@scalprum/core": "^0.8.1", + "@scalprum/react-core": "^0.9.1", "classnames": "^2.2.5", - "sanitize-html": "^2.12.1" + "sanitize-html": "^2.13.1" }, "peerDependencies": { "@patternfly/react-core": "^5.0.0", @@ -4646,9 +4632,9 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-config": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.3.1.tgz", - "integrity": "sha512-WLItTdGoIrc0s7QsrjGpcxELQljHSZXrPCTyf9y9fv1QopOtQRoWSIAMolDzABquWQ98HPeMyz4XsJRF7PCX1Q==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.3.3.tgz", + "integrity": "sha512-+vprQN6SYW8gTbv9mdwbFzfZbBziVhdpUpDstDq2rBv42Qb96WEY1titfVypCZC4iGsbRu6uYJfnWYKvpZXekQ==", "dev": true, "dependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", @@ -5298,20 +5284,21 @@ } }, "node_modules/@scalprum/core": { - "version": "0.7.0", - "license": "Apache-2.0", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@scalprum/core/-/core-0.8.1.tgz", + "integrity": "sha512-bRbquESsjUvgu3QHA9aDIK5uTu7XoXjzqoxAmDmytnedzg8LFk/iNKs/bx4lSP2rufxx8iFkdVVvN3sM1W6a4A==", "dependencies": { "@openshift/dynamic-plugin-sdk": "^5.0.1", "tslib": "^2.6.2" } }, "node_modules/@scalprum/react-core": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@scalprum/react-core/-/react-core-0.8.0.tgz", - "integrity": "sha512-ADqL6GdjT0ihVfTtIqu8vvYgwUb3IiUGXj9md1+h6tL8yshbq+FAezwuD9jhX7qcjfgL1Sl3g/faO2/SE9jPvw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@scalprum/react-core/-/react-core-0.9.3.tgz", + "integrity": "sha512-AnBoFLZl+qYB9XnAJBHYZJTHHRFWCW6iYAojGkiqtD4luyiEMmd6SaCv2s8xs/b6l/KKkG2FU272sIYqb/FkAQ==", "dependencies": { "@openshift/dynamic-plugin-sdk": "^5.0.1", - "@scalprum/core": "^0.7.0", + "@scalprum/core": "^0.8.1", "lodash": "^4.17.0" }, "peerDependencies": { @@ -21622,7 +21609,8 @@ }, "node_modules/parse-srcset": { "version": "1.0.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" }, "node_modules/parse5": { "version": "7.1.2", @@ -23273,8 +23261,9 @@ "license": "MIT" }, "node_modules/sanitize-html": { - "version": "2.13.0", - "license": "MIT", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.1.tgz", + "integrity": "sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -23286,7 +23275,8 @@ }, "node_modules/sanitize-html/node_modules/dom-serializer": { "version": "2.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -23298,7 +23288,8 @@ }, "node_modules/sanitize-html/node_modules/domhandler": { "version": "5.0.3", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dependencies": { "domelementtype": "^2.3.0" }, @@ -23311,7 +23302,8 @@ }, "node_modules/sanitize-html/node_modules/domutils": { "version": "3.1.0", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -23323,7 +23315,8 @@ }, "node_modules/sanitize-html/node_modules/escape-string-regexp": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { "node": ">=10" }, @@ -23333,6 +23326,8 @@ }, "node_modules/sanitize-html/node_modules/htmlparser2": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -23340,7 +23335,6 @@ "url": "https://github.com/sponsors/fb55" } ], - "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", diff --git a/package.json b/package.json index 0432e5f77..9ec5f491e 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", - "@redhat-cloud-services/frontend-components-config": "^6.3.1", + "@redhat-cloud-services/frontend-components-config": "^6.3.3", "@redhat-cloud-services/tsc-transform-imports": "^1.0.4", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^13.0.0", diff --git a/src/Messages.js b/src/Messages.js index a3073446c..dec27ac25 100644 --- a/src/Messages.js +++ b/src/Messages.js @@ -2294,6 +2294,26 @@ export default defineMessages({ defaultMessage: 'Select a user group to add {numUsers} {plural} to. These are all the user groups in your account. To manage user groups, go to user groups.', }, + usersEmptyStateTitle: { + id: 'usersEmptyStateTitle', + description: 'Empty state title Users', + defaultMessage: 'No users found', + }, + usersEmptyStateSubtitle: { + id: 'usersEmptyStateSubtitle', + description: 'Empty state subtitle Users', + defaultMessage: 'This filter criteria matches no users.{br}Try changing your filter input.', + }, + userGroupsEmptyStateTitle: { + id: 'userGroupsEmptyStateTitle', + description: 'Empty state title User groups', + defaultMessage: 'No user group found', + }, + userGroupsEmptyStateSubtitle: { + id: 'userGroupsEmptyStateSubtitle', + description: 'Empty state subtitle User groups', + defaultMessage: 'This filter criteria matches no user groups.{br}Try changing your filter input.', + }, assignedRoles: { id: 'assignedRoles', description: 'User details assigned roles label', diff --git a/src/smart-components/access-management/UserGroupsTable.tsx b/src/smart-components/access-management/UserGroupsTable.tsx index ede9ebf5d..a4a8651c8 100644 --- a/src/smart-components/access-management/UserGroupsTable.tsx +++ b/src/smart-components/access-management/UserGroupsTable.tsx @@ -1,21 +1,23 @@ -import React, { useEffect, useCallback, useMemo } from 'react'; +import React, { useEffect, useCallback, useMemo, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { useDataViewSelection, useDataViewPagination } from '@patternfly/react-data-view/dist/dynamic/Hooks'; import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect'; import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; -import { Pagination, Tooltip } from '@patternfly/react-core'; -import { ActionsColumn } from '@patternfly/react-table'; +import { EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon, Pagination, Tooltip } from '@patternfly/react-core'; +import { ActionsColumn, TableVariant } from '@patternfly/react-table'; import { mappedProps } from '../../helpers/shared/helpers'; import { RBACStore } from '../../redux/store'; import { useSearchParams } from 'react-router-dom'; import { fetchGroups } from '../../redux/actions/group-actions'; import { formatDistanceToNow } from 'date-fns'; -import { useIntl } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import messages from '../../Messages'; import { Group } from '../../redux/reducers/group-reducer'; -import { EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view'; +import { DataViewState, EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view'; +import { SearchIcon } from '@patternfly/react-icons'; +import { SkeletonTable } from '@patternfly/react-component-groups'; const COLUMNS: string[] = ['User group name', 'Description', 'Users', 'Service accounts', 'Roles', 'Workspaces', 'Last modified']; @@ -45,6 +47,7 @@ const UserGroupsTable: React.FunctionComponent = ({ focusedGroup, }) => { const dispatch = useDispatch(); + const [activeState, setActiveState] = useState(DataViewState.loading); const intl = useIntl(); const { trigger } = useDataViewEventsContext(); @@ -53,9 +56,10 @@ const UserGroupsTable: React.FunctionComponent = ({ { title: intl.formatMessage(messages['usersAndUserGroupsDeleteUserGroup']), onClick: () => console.log('DELETE USER GROUP') }, ]; - const { groups, totalCount } = useSelector((state: RBACStore) => ({ + const { groups, totalCount, isLoading } = useSelector((state: RBACStore) => ({ groups: state.groupReducer?.groups?.data || [], totalCount: state.groupReducer?.groups?.meta.count || 0, + isLoading: state.groupReducer?.isLoading, })); let pagination; @@ -99,6 +103,14 @@ const UserGroupsTable: React.FunctionComponent = ({ }); }, [fetchData, page, perPage]); + useEffect(() => { + if (isLoading) { + setActiveState(DataViewState.loading); + } else { + totalCount === 0 ? setActiveState(DataViewState.empty) : setActiveState(undefined); + } + }, [totalCount, isLoading]); + useEffect(() => { onChange?.(selected); }, [selected]); @@ -161,8 +173,26 @@ const UserGroupsTable: React.FunctionComponent = ({ /> ); + const empty = ( + + } + /> + + , + }} + /> + + + ); + return ( - + = ({ } pagination={React.cloneElement(paginationComponent, { isCompact: true })} /> - + {isLoading ? ( + + ) : ( + + )} ); diff --git a/src/smart-components/access-management/UsersTable.tsx b/src/smart-components/access-management/UsersTable.tsx index 9bb29e406..f01b288b3 100644 --- a/src/smart-components/access-management/UsersTable.tsx +++ b/src/smart-components/access-management/UsersTable.tsx @@ -7,20 +7,21 @@ import { ResponsiveActions } from '@patternfly/react-component-groups/dist/dynam import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; -import { Pagination, ButtonVariant } from '@patternfly/react-core'; -import { ActionsColumn } from '@patternfly/react-table'; +import { ButtonVariant, Pagination, EmptyState, EmptyStateHeader, EmptyStateIcon, EmptyStateBody } from '@patternfly/react-core'; +import { ActionsColumn, TableVariant } from '@patternfly/react-table'; import { fetchUsers } from '../../redux/actions/user-actions'; import { mappedProps } from '../../helpers/shared/helpers'; import { RBACStore } from '../../redux/store'; import { User } from '../../redux/reducers/user-reducer'; -import { useIntl } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import messages from '../../Messages'; import { Outlet, useSearchParams } from 'react-router-dom'; -import { WarningModal } from '@patternfly/react-component-groups'; -import { EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view'; +import { SkeletonTable, WarningModal } from '@patternfly/react-component-groups'; import paths from '../../utilities/pathnames'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; import useAppNavigate from '../../hooks/useAppNavigate'; +import { DataViewState, EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view'; +import { SearchIcon } from '@patternfly/react-icons'; const COLUMNS: string[] = ['Username', 'Email', 'First name', 'Last name', 'Status', 'Org admin']; @@ -42,6 +43,7 @@ interface UsersTableProps { const UsersTable: React.FunctionComponent = ({ onAddUserClick, focusedUser }) => { const { getBundle, getApp } = useChrome(); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [activeState, setActiveState] = useState(DataViewState.loading); const [currentUser, setCurrentUser] = useState(); const dispatch = useDispatch(); const intl = useIntl(); @@ -53,9 +55,10 @@ const UsersTable: React.FunctionComponent = ({ onAddUserClick, setIsDeleteModalOpen(!isDeleteModalOpen); }; - const { users, totalCount } = useSelector((state: RBACStore) => ({ + const { users, totalCount, isLoading } = useSelector((state: RBACStore) => ({ users: state.userReducer.users.data || [], totalCount: state.userReducer.users.meta.count, + isLoading: state.userReducer.isUserDataLoading, })); const [searchParams, setSearchParams] = useSearchParams(); @@ -82,6 +85,14 @@ const UsersTable: React.FunctionComponent = ({ onAddUserClick, }); }, [fetchData, page, perPage]); + useEffect(() => { + if (isLoading) { + setActiveState(DataViewState.loading); + } else { + totalCount === 0 ? setActiveState(DataViewState.empty) : setActiveState(undefined); + } + }, [totalCount, isLoading]); + const handleBulkSelect = (value: BulkSelectValue) => { if (value === BulkSelectValue.none) { onSelect(false); @@ -148,6 +159,20 @@ const UsersTable: React.FunctionComponent = ({ onAddUserClick, /> ); + const empty = ( + + } /> + + , + }} + /> + + + ); + return ( {isDeleteModalOpen && ( @@ -167,7 +192,7 @@ const UsersTable: React.FunctionComponent = ({ onAddUserClick, {`${currentUser?.username} ${intl.formatMessage(messages.deleteUserModalBody)}`} )} - !row.is_active }}> + !row.is_active }} activeState={activeState}> = ({ onAddUserClick, } /> - + {isLoading ? ( + + ) : ( + + )} diff --git a/src/test/role/__snapshots__/role.test.js.snap b/src/test/role/__snapshots__/role.test.js.snap index 9ac1c3b80..a798825c0 100644 --- a/src/test/role/__snapshots__/role.test.js.snap +++ b/src/test/role/__snapshots__/role.test.js.snap @@ -66,7 +66,7 @@ exports[`role role and group should render correctly with router 1`] = ` class="pf-v5-l-flex pf-m-justify-content-space-between" >

should render correctly in org admin 1`] = ` class="pf-v5-l-flex pf-m-justify-content-space-between" >

should render group list correctly 1`] = ` class="pf-v5-l-flex pf-m-justify-content-space-between" >

should render user 1`] = ` class="pf-v5-l-flex pf-m-justify-content-space-between" >