From 17d73699655ab071f7aa5ea92ed7b088856cab7f Mon Sep 17 00:00:00 2001 From: Giacomo Olivero Date: Fri, 22 Dec 2023 14:36:55 +0100 Subject: [PATCH] waiting tenants badge --- .../UserListLogic/UserListLogic.tsx | 45 +++++++++------- .../workspaces/Dashboard/Dashboard.tsx | 3 +- .../DashboardLogic/DashboardLogic.tsx | 54 ++++++++++++++++--- .../Grid/WorkspaceGrid/WorkspaceGrid.tsx | 3 +- .../WorkspaceGridItem/WorkspaceGridItem.less | 6 +++ .../WorkspaceGridItem/WorkspaceGridItem.tsx | 7 ++- .../WorkspaceContainer.less | 11 ++++ .../WorkspaceContainer/WorkspaceContainer.tsx | 53 +++++++++--------- frontend/src/utils.ts | 1 + 9 files changed, 129 insertions(+), 54 deletions(-) create mode 100644 frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.less diff --git a/frontend/src/components/accountPage/UserListLogic/UserListLogic.tsx b/frontend/src/components/accountPage/UserListLogic/UserListLogic.tsx index 642631525..fc0674d17 100644 --- a/frontend/src/components/accountPage/UserListLogic/UserListLogic.tsx +++ b/frontend/src/components/accountPage/UserListLogic/UserListLogic.tsx @@ -6,22 +6,21 @@ import { } from '../../../generated-types'; import { getTenantPatchJson } from '../../../graphql-components/utils'; import UserList from '../UserList/UserList'; -import { makeRandomDigits, UserAccountPage } from '../../../utils'; +import { makeRandomDigits, UserAccountPage, Workspace } from '../../../utils'; import { AuthContext } from '../../../contexts/AuthContext'; import { Role } from '../../../generated-types'; import { ErrorContext } from '../../../errorHandling/ErrorContext'; import { ErrorTypes, SupportedError } from '../../../errorHandling/utils'; export interface IUserListLogicProps { - workspaceName: string; - workspaceNamespace: string; + workspace: Workspace; } const UserListLogic: FC = props => { const { apolloErrorCatcher, makeErrorCatcher } = useContext(ErrorContext); const genericErrorCatcher = makeErrorCatcher(ErrorTypes.GenericError); const { userId } = useContext(AuthContext); - const { workspaceName, workspaceNamespace } = props; + const { workspace } = props; const [loadingSpinner, setLoadingSpinner] = useState(false); const [errors, setErrors] = useState([]); // Used to handle stop while uploading users from CSV @@ -32,7 +31,7 @@ const UserListLogic: FC = props => { const [users, setUsers] = useState([]); const { data, loading, error, refetch } = useTenantsQuery({ variables: { - labels: `crownlabs.polito.it/${workspaceNamespace}`, + labels: `crownlabs.polito.it/${workspace.namespace}`, retrieveWorkspaces: true, }, onError: apolloErrorCatcher, @@ -44,7 +43,7 @@ const UserListLogic: FC = props => { }, [abortUploading]); const getManager = () => { - return `${workspaceName}-${userId || makeRandomDigits(10)}`; + return `${workspace.name}-${userId || makeRandomDigits(10)}`; }; const refreshUserList = async () => await refetch(); @@ -60,7 +59,7 @@ const UserListLogic: FC = props => { surname: user?.spec?.lastName!, email: user?.spec?.email!, currentRole: user?.spec?.workspaces?.find( - roles => roles?.name === workspaceName + roles => roles?.name === workspace.name )?.role!, workspaces: user?.spec?.workspaces?.map(workspace => ({ @@ -70,7 +69,7 @@ const UserListLogic: FC = props => { })) || [] ); } - }, [loading, data, workspaceName]); + }, [loading, data, workspace.name]); const [applyTenantMutation] = useApplyTenantMutation(); @@ -78,7 +77,7 @@ const UserListLogic: FC = props => { try { let workspaces = users .find(u => u.userid === user.userid)! - .workspaces?.filter(w => w.name === workspaceName) + .workspaces?.filter(w => w.name === workspace.name) .map(({ name }) => ({ name, role: newRole })); setLoadingSpinner(true); await applyTenantMutation({ @@ -90,15 +89,23 @@ const UserListLogic: FC = props => { onError: apolloErrorCatcher, }); setUsers( - users.map(u => - u.userid === user.userid - ? { - ...u, - currentRole: newRole, - workspaces, + users.map(u => { + if (u.userid === user.userid) { + if (u.currentRole === Role.Candidate && workspace.waitingTenants) { + workspace.waitingTenants--; + if (workspace.waitingTenants === 0) { + workspace.waitingTenants = undefined; } - : u - ) + } + return { + ...u, + currentRole: newRole, + workspaces, + }; + } else { + return u; + } + }) ); } catch (error) { genericErrorCatcher(error as SupportedError); @@ -164,8 +171,8 @@ const UserListLogic: FC = props => { users={users} onAddUser={addUser} onUpdateUser={updateUser} - workspaceNamespace={workspaceNamespace} - workspaceName={workspaceName} + workspaceNamespace={workspace.namespace} + workspaceName={workspace.name} uploadedNumber={uploadedNumber} uploadedUserNumber={uploadedUserNumber} setAbortUploading={handleAbort} diff --git a/frontend/src/components/workspaces/Dashboard/Dashboard.tsx b/frontend/src/components/workspaces/Dashboard/Dashboard.tsx index ae398d35e..69f5f25cc 100644 --- a/frontend/src/components/workspaces/Dashboard/Dashboard.tsx +++ b/frontend/src/components/workspaces/Dashboard/Dashboard.tsx @@ -29,6 +29,7 @@ const Dashboard: FC = ({ ...props }) => { workspaceItems={workspaces.map((ws, idx) => ({ id: idx, title: ws.prettyName, + waitingTenants: ws.waitingTenants, }))} onClick={setSelectedWs} /> @@ -40,7 +41,7 @@ const Dashboard: FC = ({ ...props }) => { xxl={12} className="lg:pl-4 lg:pr-0 px-4 flex flex-auto" > - {selectedWsId > 0 ? ( + {selectedWsId >= 0 ? ( = () => { const { @@ -12,15 +14,55 @@ const DashboardLogic: FC<{}> = () => { loading: tenantLoading, } = useContext(TenantContext); + const [ws, setWs] = useState([]); + const [viewWs, setViewWs] = useState([]); + const client = useApolloClient(); + + useEffect(() => { + let wsList = + tenantData?.tenant?.spec?.workspaces + ?.filter(w => w?.role !== Role.Candidate) + ?.map(makeWorkspace) ?? []; + setWs(wsList); + setViewWs(wsList); + }, [tenantData?.tenant?.spec?.workspaces]); + + useEffect(() => { + ws?.filter(w => w?.role === WorkspaceRole.manager).forEach(w => { + client + .query({ + query: gql` + query tenants($labels: String) { + tenants: itPolitoCrownlabsV1alpha2TenantList( + labelSelector: $labels + ) { + items { + metadata { + name + } + } + } + } + `, + variables: { + labels: `crownlabs.polito.it/workspace-${w?.name}=candidate`, + }, + }) + .then(queryResult => { + let numCandidate = queryResult.data.tenants.items.length; + if (numCandidate > 0) { + ws.find(ws => ws.name === w?.name)!.waitingTenants = numCandidate; + setViewWs([...ws]); + } + }); + }); + }, [ws, client]); + return !tenantLoading && tenantData && !tenantError ? ( <> w?.role !== Role.Candidate) - ?.map(makeWorkspace) ?? [] - } + workspaces={viewWs} /> ) : ( diff --git a/frontend/src/components/workspaces/Grid/WorkspaceGrid/WorkspaceGrid.tsx b/frontend/src/components/workspaces/Grid/WorkspaceGrid/WorkspaceGrid.tsx index ca30197f2..00062b028 100644 --- a/frontend/src/components/workspaces/Grid/WorkspaceGrid/WorkspaceGrid.tsx +++ b/frontend/src/components/workspaces/Grid/WorkspaceGrid/WorkspaceGrid.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { WorkspaceGridItem } from '../WorkspaceGridItem'; export interface IWorkspaceGridProps { - workspaceItems: Array<{ id: number; title: string }>; + workspaceItems: Array<{ id: number; title: string; waitingTenants?: number }>; selectedWs: number; onClick: (id: number) => void; } @@ -17,6 +17,7 @@ const WorkspaceGrid: FC = ({ ...props }) => { id={workspaceItem.id} title={workspaceItem.title} isActive={selectedWs === workspaceItem.id} + badgeValue={workspaceItem.waitingTenants} onClick={onClick} /> diff --git a/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.less b/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.less index d3c0971e1..b3807db1b 100644 --- a/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.less +++ b/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.less @@ -14,3 +14,9 @@ border: solid @primary-color; border-width: 4px; } + +.grid-badge { + position: absolute; + right: 0; + top: 0; +} diff --git a/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.tsx b/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.tsx index 904ffd30b..2668698e1 100644 --- a/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.tsx +++ b/frontend/src/components/workspaces/Grid/WorkspaceGridItem/WorkspaceGridItem.tsx @@ -1,16 +1,18 @@ import { FC } from 'react'; import { Row, Col } from 'antd'; import './WorkspaceGridItem.less'; +import Badge from '../../../common/Badge'; export interface IWorkspaceGridItemProps { id: number; title: string; isActive: boolean; + badgeValue?: number; onClick: (id: number) => void; } const WorkspaceGridItem: FC = ({ ...props }) => { - const { id, title, isActive, onClick } = props; + const { id, title, isActive, badgeValue, onClick } = props; return ( @@ -29,6 +31,9 @@ const WorkspaceGridItem: FC = ({ ...props }) => { > {title[0].toUpperCase() + title[1].toUpperCase()} + {badgeValue && ( + + )} diff --git a/frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.less b/frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.less new file mode 100644 index 000000000..c69bc7224 --- /dev/null +++ b/frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.less @@ -0,0 +1,11 @@ +.tenant-button { + position: relative; +} +.candidate-badge { + position: absolute; + top: -10px; + right: -10px; +} +.tenant-button:active .candidate-badge, .tenant-button:focus .candidate-badge { + position: absolute!important; +} diff --git a/frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.tsx b/frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.tsx index b7f623161..007163a77 100644 --- a/frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.tsx +++ b/frontend/src/components/workspaces/WorkspaceContainer/WorkspaceContainer.tsx @@ -15,6 +15,9 @@ import Box from '../../common/Box'; import ModalCreateTemplate from '../ModalCreateTemplate'; import { Image, Template } from '../ModalCreateTemplate/ModalCreateTemplate'; import { TemplatesTableLogic } from '../Templates/TemplatesTableLogic'; +import Badge from '../../common/Badge'; + +import './WorkspaceContainer.less'; export interface IWorkspaceContainerProps { tenantNamespace: string; @@ -57,15 +60,7 @@ const getImages = (dataImages: ImagesQuery) => { const WorkspaceContainer: FC = ({ ...props }) => { const [showUserListModal, setShowUserListModal] = useState(false); - const { - tenantNamespace, - workspace: { - role, - name: workspaceName, - namespace: workspaceNamespace, - prettyName: workspacePrettyName, - }, - } = props; + const { tenantNamespace, workspace } = props; const { apolloErrorCatcher } = useContext(ErrorContext); const [createTemplateMutation, { loading }] = useCreateTemplateMutation({ @@ -82,9 +77,9 @@ const WorkspaceContainer: FC = ({ ...props }) => { const submitHandler = (t: Template) => createTemplateMutation({ variables: { - workspaceId: workspaceName, - workspaceNamespace: workspaceNamespace, - templateId: `${workspaceName}-`, + workspaceId: workspace.name, + workspaceNamespace: workspace.namespace, + templateId: `${workspace.name}-`, templateName: t.name?.trim()!, descriptionTemplate: t.name?.trim()!, image: t.registry @@ -109,7 +104,7 @@ const WorkspaceContainer: FC = ({ ...props }) => { return ( <> = ({ ...props }) => { center: (

- {workspacePrettyName} + {workspace.prettyName}

), - left: role === 'manager' && ( + left: workspace.role === 'manager' && (
- +
), - right: role === 'manager' && ( + right: workspace.role === 'manager' && (