Skip to content

Commit

Permalink
ISPN-15305 caches list role detail
Browse files Browse the repository at this point in the history
  • Loading branch information
karesti committed Nov 13, 2023
1 parent 53686c5 commit dd4086a
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 23 deletions.
5 changes: 4 additions & 1 deletion cypress/e2e/1_acess_management.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('Global stats', () => {
cy.get('[aria-label=role-description-input]')
.should('have.value', 'aRole description with update');

cy.get('[aria-label=nav-item-Permissions').click();
cy.get('[aria-label=nav-item-permissions').click();
cy.contains('ALL');
cy.get('[data-cy=addPermissionButton').click();
cy.get('[data-cy=menu-toogle-permissions]').click();
Expand All @@ -58,6 +58,9 @@ describe('Global stats', () => {
cy.get("[aria-label=Remove]").click();
cy.contains('Role aRole has been updated');

cy.get('[aria-label=nav-item-caches').click();
cy.contains('default');

// remove
cy.login(Cypress.env('username'), Cypress.env('password'), '/access-management');
cy.get("[aria-label=aRole-menu]").click();
Expand Down
25 changes: 13 additions & 12 deletions src/app/AccessManagement/RoleDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@ import { useTranslation } from 'react-i18next';
import { DataContainerBreadcrumb } from '@app/Common/DataContainerBreadcrumb';
import { RoleGeneral } from '@app/AccessManagement/RoleDetailContent/RoleGeneral';
import { RolePermissions } from '@app/AccessManagement/RoleDetailContent/RolePermissions';
import { RoleCaches } from '@app/AccessManagement/RoleDetailContent/RoleCaches';
import { DeleteRole } from '@app/AccessManagement/DeleteRole';
import { useNavigate } from 'react-router';
import { useDescribeRole } from '@app/services/rolesHook';
import { useNavigate } from 'react-router';
import { useParams } from 'react-router-dom';

const RoleDetail = () => {
const navigate = useNavigate();
const roleName = useParams()['roleName'] as string;
const { t } = useTranslation();
const { role } = useDescribeRole(roleName);
const [activeTabKey, setActiveTabKey] = useState('0');
const { role, loading } = useDescribeRole(roleName);
const [activeTabKey, setActiveTabKey] = useState<'general' | 'permissions' | 'caches'>('general');
const [showGeneralDescription, setShowGeneralDescription] = useState(true);
const [showPermissions, setShowPermissions] = useState(false);
// const [showCaches, setShowCaches] = useState(false);
const [showCaches, setShowCaches] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [isDeleteRole, setIsDeleteRole] = useState(false);

Expand All @@ -50,9 +51,9 @@ const RoleDetail = () => {
};

useEffect(() => {
setShowGeneralDescription(activeTabKey === '0');
setShowPermissions(activeTabKey === '1');
// setShowCaches(activeTabKey === '2');
setShowGeneralDescription(activeTabKey === 'general');
setShowPermissions(activeTabKey === 'permissions');
setShowCaches(activeTabKey === 'caches');
}, [activeTabKey]);

interface AccessTab {
Expand All @@ -65,9 +66,9 @@ const RoleDetail = () => {
};

const tabs: AccessTab[] = [
{ name: t('access-management.role.tab-general'), key: '0' },
{ name: t('access-management.role.tab-permissions'), key: '1' }
// { name: t('access-management.role.tab-caches'), key: '2' }
{ name: t('access-management.role.tab-general'), key: 'general' },
{ name: t('access-management.role.tab-permissions'), key: 'permissions' },
{ name: t('access-management.role.tab-caches'), key: 'caches' }
];

const buildTabs = () => {
Expand All @@ -76,7 +77,7 @@ const RoleDetail = () => {
<NavList>
{tabs.map((tab) => (
<NavItem
aria-label={'nav-item-' + tab.name}
aria-label={'nav-item-' + tab.key}
key={'nav-item-' + tab.key}
itemId={tab.key}
isActive={activeTabKey === tab.key}
Expand Down Expand Up @@ -147,7 +148,7 @@ const RoleDetail = () => {
<CardBody>
{showGeneralDescription && <RoleGeneral name={roleName} />}
{showPermissions && <RolePermissions name={roleName} />}
{/*{showCaches && <RoleCaches />}*/}
{showCaches && <RoleCaches name={roleName}/>}
</CardBody>
</Card>
</PageSection>
Expand Down
189 changes: 186 additions & 3 deletions src/app/AccessManagement/RoleDetailContent/RoleCaches.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,193 @@
import { useTranslation } from 'react-i18next';
import {
Bullseye,
Button,
ButtonVariant,
EmptyState,
EmptyStateBody,
EmptyStateIcon,
EmptyStateVariant,
Pagination,
SearchInput,
Title,
Toolbar,
ToolbarContent,
ToolbarGroup,
ToolbarItem,
ToolbarItemVariant
} from '@patternfly/react-core';
import React, { useEffect, useState } from 'react';
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import { useCachesForRole } from '@app/services/rolesHook';
import { TableErrorState } from '@app/Common/TableErrorState';
import { TableLoadingState } from '@app/Common/TableLoadingState';
import { KeyIcon, SearchIcon } from '@patternfly/react-icons';
import { Link } from 'react-router-dom';

const RoleCaches = () => {
const RoleCaches = (props: { name: string }) => {
const { t } = useTranslation();
const brandname = t('brandname.brandname');
const { secured, nonSecured, loading, error } = useCachesForRole(props.name);
const [pagination, setPagination] = useState({
page: 1,
perPage: 5
});
const [searchValue, setSearchValue] = useState<string>('');
const [cachesRows, setCachesRows] = useState<string[]>([]);
const [filteredCaches, setFilteredCaches] = useState<string[]>([]);

return 'Caches';
useEffect(() => {
if (loading) {
return;
}

const caches = secured.concat(nonSecured);
if (searchValue.trim() !== '') {
setFilteredCaches(
caches.filter((perm) => perm.toLowerCase().includes(searchValue.toLowerCase())).sort()
);
} else {
setFilteredCaches(caches);
}

setPagination({
...pagination,
page: 1
});
}, [secured, nonSecured, searchValue, loading]);

useEffect(() => {
if (loading) {
return;
}
const initSlice = (pagination.page - 1) * pagination.perPage;
setCachesRows(filteredCaches.slice(initSlice, initSlice + pagination.perPage));
}, [filteredCaches, pagination]);

const columnNames = {
name: t('access-management.role.caches-name')
};

const onSetPage = (_event, pageNumber) => {
setPagination({
...pagination,
page: pageNumber
});
};

const onPerPageSelect = (_event, perPage) => {
setPagination({
page: 1,
perPage: perPage
});
};

const onSearchChange = (value: string) => {
setSearchValue(value);
};

const paginationComponent = (
<Pagination
itemCount={filteredCaches.length}
perPage={pagination.perPage}
page={pagination.page}
onSetPage={onSetPage}
widgetId="pagination-caches"
onPerPageSelect={onPerPageSelect}
isCompact
/>
);

const displayRows = () => {
if (!loading && cachesRows.length == 0) {
return (
<Tr>
<Td>
<Bullseye>
<EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h2" size="lg">
{t('access-management.role.no-caches-found')}
</Title>
<EmptyStateBody>{t('access-management.role.no-filtered-caches-body')}</EmptyStateBody>
</EmptyState>
</Bullseye>
</Td>
</Tr>
);
}

return (
<React.Fragment>
{cachesRows.map((row) => (
<Tr key={row}>
<Td dataLabel={columnNames.name}>
{secured.includes(row) && <KeyIcon/> }
<Link
data-cy={`detailLink-${row}`}
key={row}
to={{ pathname: '/cache/' + encodeURIComponent(row), search: location.search }}
>
<Button
data-cy={`detailButton-${row}`}
key={`detail-button-${row}`}
variant={ButtonVariant.link}
>
{row}
</Button>
</Link>
</Td>
</Tr>
))}
</React.Fragment>
);
};

if (loading) {
return <TableLoadingState message={t('access-management.role.loading-caches', { roleName: props.name })} />;
}

if (error) {
return <TableErrorState error={t('access-management.role.error-caches', { roleName: props.name })} />;
}

return (
<React.Fragment>
<Toolbar id="caches-table-toolbar" className={'caches-table-display'}>
<ToolbarContent>
<ToolbarGroup variant="filter-group">
<ToolbarItem variant={'search-filter'}>
<SearchInput
placeholder={t('access-management.role.caches-search-placeholder')}
value={searchValue}
onChange={(_event, value) => onSearchChange(value)}
onSearch={(_event, value) => onSearchChange(value)}
onClear={() => setSearchValue('')}
/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarItem variant={ToolbarItemVariant.pagination}>{paginationComponent}</ToolbarItem>
</ToolbarContent>
</Toolbar>
<Table className={'caches-table'} aria-label={'caches-table-label'} variant={'compact'}>
<Thead noWrap>
<Tr>
<Th
info={{
popover: <div>{t('access-management.role.caches-name-tooltip')}</div>,
ariaLabel: 'Cache name more information',
popoverProps: {
headerContent: columnNames.name,
}
}}>{columnNames.name}</Th>
</Tr>
</Thead>
<Tbody>{displayRows()}</Tbody>
</Table>
<Toolbar id="caches-table-toolbar" className={'caches-table-toolbar'}>
<ToolbarItem variant={ToolbarItemVariant.pagination}>{paginationComponent}</ToolbarItem>
</Toolbar>
</React.Fragment>
);
};

export { RoleCaches };
15 changes: 8 additions & 7 deletions src/app/AccessManagement/RoleTableDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,18 @@ const RoleTableDisplay = () => {
{rolesRows.map((row) => (
<Tr key={row.name}>
<Td dataLabel={columnNames.name} width={15}>
{row.implicit && (
<Icon size="sm" isInline>
<LockIcon className="role-icon" />
</Icon>
)}
{row.implicit && (<LockIcon className="role-icon" />)}
<Link
data-cy={`detailLink-${row.name}`}
key={row.name}
to={{ pathname: '/access-management/role/' + encodeURIComponent(row.name), search: location.search }}
>
{row.name}
<Button
data-cy={`detailLink-${row.name}`}
key={`detailLink-${row}`}
variant={ButtonVariant.link}
>
{row.name}
</Button>
</Link>
</Td>
<Td dataLabel={columnNames.permissions} width={30}>
Expand Down
8 changes: 8 additions & 0 deletions src/app/assets/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -929,13 +929,21 @@
"tab-caches": "Accessible caches",
"loading": "Loading role {{roleName}}",
"error": "An error occurred while retrieving the role {{roleName}}",
"loading-caches": "Loading caches for role {{roleName}}",
"error-caches": "An error occurred while retrieving the available caches for role {{roleName}}",
"implicit-warning": "The general settings for predefined roles are not editable.",
"permission-name": "Permission name",
"permission-description": "Description",
"add-permission-button": "Add permission",
"permissions-search-placeholder": "Filter by name",
"caches-name": "Cache name",
"caches-name-tooltip": "Cache name prefixed with a key indicates that the cache has authorization enabled and can be accessed by this role.",
"caches-secured": "Secured",
"caches-search-placeholder": "Filter by cache name",
"no-permissions-found": "No permission found",
"no-filtered-permissions-body": "No permission match your search criteria.",
"no-caches-found": "No caches found",
"no-filtered-caches-body": "No caches match your search criteria.",
"permissions-implicit-warning": "The permissions for predefined roles are not editable.",
"update-success": "Role {{name}} has been updated",
"update-error": "Unexpected error updating the role {{name}}",
Expand Down
32 changes: 32 additions & 0 deletions src/app/services/rolesHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,35 @@ export function useFlushCache(call: () => void) {
onFlushCache
};
}

export function useCachesForRole(roleName: string) {
const [secured, setSecured] = useState<string[]>([]);
const [nonSecured, setNonSecured] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');

useEffect(() => {
if (loading) {
ConsoleServices.caches()
.getCachesForRole(roleName)
.then((either) => {
if (either.isRight()) {
const securedCaches = either.value.get('secured') as string[];
const nonSecuredCaches = either.value.get('non-secured') as string[];
setSecured(securedCaches.sort());
setNonSecured(nonSecuredCaches.sort());
} else {
setError(either.value.message);
}
})
.finally(() => setLoading(false));
}
}, [loading]);

return {
secured,
nonSecured,
loading,
error
};
}
12 changes: 12 additions & 0 deletions src/services/cacheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,4 +598,16 @@ export class CacheService {
public async getClusterDistribution(): Promise<Either<ActionResponse, ClusterDistribution[]>> {
return this.fetchCaller.get(this.endpoint + '/cluster?action=distribution', (text) => text);
}

/**
* Retrieve caches for a role
*
*/
public async getCachesForRole(role: string): Promise<Either<ActionResponse, Map<string, string[]>>> {
return this.fetchCaller.get(this.endpoint + '/caches?action=role-accessible&role=' + role,
(data) => new Map()
.set('secured', data['secured'])
.set('non-secured', data['non-secured'])
);
}
}

0 comments on commit dd4086a

Please sign in to comment.