From ce615554d9bebab1381ec24fbb84156160158567 Mon Sep 17 00:00:00 2001 From: harishsurf Date: Fri, 2 Dec 2022 18:29:17 +0530 Subject: [PATCH] Default Permissions tab (PROJQUAY-4570) Signed-off-by: harishsurf --- src/atoms/OrganizationListState.ts | 29 +++ src/atoms/UserState.ts | 16 -- src/resources/PermissionsResource.ts | 11 + .../Organization/Organization.tsx | 64 +++--- .../CreatePermissionModal.tsx | 23 ++ .../DefaultPermissions/DefaultPermissions.tsx | 201 ++++++++++++++++++ .../Tabs/UsageLogs/UsageLogsTab.tsx | 3 - .../OrganizationsList/OrganizationToolBar.tsx | 2 +- .../OrganizationsList/OrganizationsList.tsx | 5 +- 9 files changed, 301 insertions(+), 53 deletions(-) create mode 100644 src/resources/PermissionsResource.ts create mode 100644 src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/CreatePermissionModal.tsx create mode 100644 src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissions.tsx delete mode 100644 src/routes/OrganizationsList/Organization/Tabs/UsageLogs/UsageLogsTab.tsx diff --git a/src/atoms/OrganizationListState.ts b/src/atoms/OrganizationListState.ts index 8668db84..87537cfd 100644 --- a/src/atoms/OrganizationListState.ts +++ b/src/atoms/OrganizationListState.ts @@ -1,6 +1,35 @@ import {atom} from 'recoil'; +import {SearchState} from 'src/components/toolbar/SearchTypes'; +import {IOrganization} from 'src/resources/OrganizationResource'; +import ColumnNames from 'src/routes/OrganizationsList/ColumnNames'; export const refreshPageState = atom({ key: 'refreshOrgPageState', default: 0, }); + +// Organization List page +export const selectedOrgsState = atom({ + key: 'selectedOrgsState', + default: [], +}); +export const searchOrgsState = atom({ + key: 'searchOrgsState', + default: { + query: '', + field: ColumnNames.name, + }, +}); + +// OrgList -> Default Permissions tab +export const selectedPermissionsState = atom({ + key: 'selectedPermissionsState', + default: [], +}); +export const searchPermissionsState = atom({ + key: 'searchPermissionsState', + default: { + query: '', + field: ColumnNames.name, + }, +}); diff --git a/src/atoms/UserState.ts b/src/atoms/UserState.ts index 325f4cbb..df9fa680 100644 --- a/src/atoms/UserState.ts +++ b/src/atoms/UserState.ts @@ -1,22 +1,6 @@ import {atom} from 'recoil'; -import {IOrganization} from 'src/resources/OrganizationResource'; -import ColumnNames from 'src/routes/OrganizationsList/ColumnNames'; -import {SearchState} from 'src/components/toolbar/SearchTypes'; export const CurrentUsernameState = atom({ key: 'currentUsernameState', default: '', }); - -export const selectedOrgsState = atom({ - key: 'selectedOrgsState', - default: [], -}); - -export const searchOrgsState = atom({ - key: 'searchOrgsState', - default: { - query: '', - field: ColumnNames.name, - }, -}); diff --git a/src/resources/PermissionsResource.ts b/src/resources/PermissionsResource.ts new file mode 100644 index 00000000..7999c653 --- /dev/null +++ b/src/resources/PermissionsResource.ts @@ -0,0 +1,11 @@ +export interface IPermission { + name: string; +} + +export async function fetchDefaultPermissions() { + // const response: AxiosResponse = await axios.get( + // '/api/v1/user/', + // ); + // assertHttpCode(response.status, 200); + // return response.data; +} diff --git a/src/routes/OrganizationsList/Organization/Organization.tsx b/src/routes/OrganizationsList/Organization/Organization.tsx index a43bbfb1..cad00352 100644 --- a/src/routes/OrganizationsList/Organization/Organization.tsx +++ b/src/routes/OrganizationsList/Organization/Organization.tsx @@ -7,11 +7,11 @@ import { TabTitleText, Title, } from '@patternfly/react-core'; -import {useLocation} from 'react-router-dom'; -import {NavigationPath} from 'src/routes/NavigationPath'; -import {useCallback, useState} from 'react'; +import {useLocation, useNavigate} from 'react-router-dom'; +import {useState} from 'react'; import RepositoriesList from 'src/routes/RepositoriesList/RepositoriesList'; import {QuayBreadcrumb} from 'src/components/breadcrumb/Breadcrumb'; +import DefaultPermissions from './Tabs/DefaultPermissions/DefaultPermissions'; export default function Organization() { const location = useLocation(); @@ -19,27 +19,27 @@ export default function Organization() { const [activeTabKey, setActiveTabKey] = useState(0); - const onTabSelect = useCallback( - ( - _event: React.MouseEvent, - tabIndex: string | number, - ) => setActiveTabKey(tabIndex), - [], - ); + const navigate = useNavigate(); + + // const onTabSelect = useCallback( + // ( + // _event: React.MouseEvent, + // tabIndex: string | number, + // ) => setActiveTabKey(tabIndex), + // [], + // ); + + enum OrgDetailTabs { + Repositories = 'repositories', + DefaultPermissions = 'permissions', + } - const repositoriesSubNav = [ - { - href: NavigationPath.organizationDetail, - name: 'Repositories', - component: , - }, - // Commenting till needed. - // { - // href: NavigationPath.orgDetailUsageLogsTab, - // name: 'Usage Logs', - // component: , - // }, - ]; + const onTabSelect = (e, tabIndex: string | number = 0) => { + setActiveTabKey(tabIndex); + navigate( + `${location.pathname}?tab=${Object.values(OrgDetailTabs)[tabIndex]}`, + ); + }; return ( @@ -57,15 +57,15 @@ export default function Organization() { className="no-padding-on-sides" > - {repositoriesSubNav.map((nav, idx) => ( - {nav.name}} - > - {nav.component} - - ))} + Repositories}> + + + Default permissions} + > + + diff --git a/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/CreatePermissionModal.tsx b/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/CreatePermissionModal.tsx new file mode 100644 index 00000000..60cbb8da --- /dev/null +++ b/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/CreatePermissionModal.tsx @@ -0,0 +1,23 @@ +import {Drawer, DrawerContent, DrawerContentBody} from '@patternfly/react-core'; + +export default function CreatePermissionModal( + props: CreatePermissionModalProps, +) { + const panelContent = {}; + return ( + + + content-body + + content-body with padding + + content-body + + + ); +} + +interface CreatePermissionModalProps { + isExpanded?: boolean; + onExpand: () => void; +} diff --git a/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissions.tsx b/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissions.tsx new file mode 100644 index 00000000..1ec53a7f --- /dev/null +++ b/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissions.tsx @@ -0,0 +1,201 @@ +import { + PageSection, + PageSectionVariants, + TextContent, + Text, + TextVariants, + SearchInput, + Toolbar, + ToolbarContent, + Flex, + FlexItem, + Button, +} from '@patternfly/react-core'; +import { + TableComposable, + Tbody, + Td, + Th, + Thead, + Tr, +} from '@patternfly/react-table'; +import {useEffect, useState} from 'react'; +import {useRecoilState, useResetRecoilState} from 'recoil'; +import { + searchPermissionsState, + selectedPermissionsState, +} from 'src/atoms/OrganizationListState'; +import {DropdownCheckbox} from 'src/components/toolbar/DropdownCheckbox'; +import {SearchDropdown} from 'src/components/toolbar/SearchDropdown'; +import { + fetchDefaultPermissions, + IPermission, +} from 'src/resources/PermissionsResource'; +import CreatePermissionModal from './CreatePermissionModal'; + +export default function DefaultPermissions() { + const [permissionsList, setPermissionsList] = useState([]); + + const [search, setSearch] = useRecoilState(searchPermissionsState); + const resetSearch = useResetRecoilState(searchPermissionsState); + + const [isCreatePermModalOpen, setCreatePermModalOpen] = useState(true); + + const filteredPermissionsList = + search.query !== '' + ? permissionsList?.filter((permission) => + permission.name.includes(search.query), + ) + : permissionsList; + + // Select checkbox related states + const [selectedPermissions, setSelectedPermissions] = useRecoilState( + selectedPermissionsState, + ); + const isPermissionSelectable = (permission: IPermission) => + permission.name !== ''; // Arbitrary logic for this example + const selectAllPermissions = (isSelecting = true) => + setSelectedPermissions( + isSelecting ? filteredPermissionsList.map((p) => p.name) : [], + ); + + const setPermissionSelected = (permission: IPermission, isSelecting = true) => + setSelectedPermissions((prevSelected) => { + const otherSelectedPermissions = prevSelected.filter( + (p) => p !== permission.name, + ); + return isSelecting && isPermissionSelectable(permission) + ? [...otherSelectedPermissions, permission.name] + : otherSelectedPermissions; + }); + + const isPermissionSelected = (permission: IPermission) => + selectedPermissions.includes(permission.name); + + const onSelectPermission = ( + permission: IPermission, + rowIndex: number, + isSelecting: boolean, + ) => { + setPermissionSelected(permission, isSelecting); + }; + + async function fetchPermissions() { + // clearing previous states + resetSearch(); + setPermissionsList([]); + setSelectedPermissions([]); + try { + const user = await fetchDefaultPermissions(); + // setUserState(user); + } catch (err) { + console.error(err); + } + } + + useEffect(() => { + fetchPermissions(); + }, []); + + const permissionColumnNames = { + repoCreatedBy: 'Repository Created By', + permAppliedTo: 'Permission Applied To', + permission: 'Permission', + }; + + const createPermissionModal = ( + handleCreatePermOnClick} + /> + ); + + const handleCreatePermOnClick = () => { + setCreatePermModalOpen(!isCreatePermModalOpen); + }; + + return ( + + + + The Default permissions panel defines permissions that should be + granted automatically to a repository when it is created, in addition + to the default of the repository's creator. Permissions are + assigned based on the user who created the repository. + + + Note: Permissions added here do not automatically get added to + existing repositories. + + + + + + + + + + + + + {/* */} + + + + + + + {permissionColumnNames.repoCreatedBy} + {permissionColumnNames.permAppliedTo} + {permissionColumnNames.permission} + + + + {permissionsList?.map((permission, rowIndex) => ( + + + onSelectPermission(permission, rowIndex, isSelecting), + isSelected: isPermissionSelected(permission), + disable: !isPermissionSelectable(permission), + }} + /> + + {permissionColumnNames.repoCreatedBy} + + + {permissionColumnNames.repoCreatedBy} + + + {permissionColumnNames.repoCreatedBy} + + + ))} + + + + ); +} diff --git a/src/routes/OrganizationsList/Organization/Tabs/UsageLogs/UsageLogsTab.tsx b/src/routes/OrganizationsList/Organization/Tabs/UsageLogs/UsageLogsTab.tsx deleted file mode 100644 index a8917641..00000000 --- a/src/routes/OrganizationsList/Organization/Tabs/UsageLogs/UsageLogsTab.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function UsageLogsTab() { - return

UsageLogsTab

; -} diff --git a/src/routes/OrganizationsList/OrganizationToolBar.tsx b/src/routes/OrganizationsList/OrganizationToolBar.tsx index 2cd9cdf1..8f5f2ab2 100644 --- a/src/routes/OrganizationsList/OrganizationToolBar.tsx +++ b/src/routes/OrganizationsList/OrganizationToolBar.tsx @@ -5,7 +5,7 @@ import {SearchInput} from 'src/components/toolbar/SearchInput'; import {ToolbarButton} from 'src/components/toolbar/ToolbarButton'; import {Kebab} from 'src/components/toolbar/Kebab'; import {ToolbarPagination} from 'src/components/toolbar/ToolbarPagination'; -import {searchOrgsState} from 'src/atoms/UserState'; +import {searchOrgsState} from 'src/atoms/OrganizationListState'; import {useRecoilState} from 'recoil'; import * as React from 'react'; import ColumnNames from './ColumnNames'; diff --git a/src/routes/OrganizationsList/OrganizationsList.tsx b/src/routes/OrganizationsList/OrganizationsList.tsx index 787d5ca5..50e12a57 100644 --- a/src/routes/OrganizationsList/OrganizationsList.tsx +++ b/src/routes/OrganizationsList/OrganizationsList.tsx @@ -16,7 +16,10 @@ import { import './css/Organizations.scss'; import {CreateOrganizationModal} from './CreateOrganizationModal'; import {useRecoilState, useRecoilValue, useResetRecoilState} from 'recoil'; -import {searchOrgsState, selectedOrgsState} from 'src/atoms/UserState'; +import { + searchOrgsState, + selectedOrgsState, +} from 'src/atoms/OrganizationListState'; import {useEffect, useState} from 'react'; import { bulkDeleteOrganizations,