Skip to content

Commit

Permalink
Teanant autoenrolling frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
giacoliva committed Jan 18, 2024
1 parent 2a7ca49 commit 6c29477
Show file tree
Hide file tree
Showing 19 changed files with 731 additions and 167 deletions.
39 changes: 31 additions & 8 deletions frontend/src/components/accountPage/UserList/UserList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DeleteOutlined,
SwapOutlined,
RedoOutlined,
CheckOutlined,
} from '@ant-design/icons';
import Column from 'antd/lib/table/Column';
import { Role } from '../../../generated-types';
Expand Down Expand Up @@ -44,8 +45,21 @@ const UserList: FC<IUserListProps> = props => {
};

const handleChangeCurrentRole = (record: UserAccountPage) => {
const newRole =
record.currentRole === Role.Manager ? Role.User : Role.Manager;
let newRole: Role;
switch (record.currentRole) {
case Role.Manager:
newRole = Role.User;
break;
case Role.User:
newRole = Role.Manager;
break;
case Role.Candidate:
newRole = Role.User;
break;
default:
newRole = Role.User;
break;
}

props.onUpdateUser(record, newRole);
};
Expand Down Expand Up @@ -131,12 +145,21 @@ const UserList: FC<IUserListProps> = props => {
render={(_: any, record: UserAccountPage) =>
props.users.length >= 1 ? (
<div className="flex justify-center">
<Tooltip title="Swap role">
<SwapOutlined
className="mr-2"
onClick={() => handleChangeCurrentRole(record)}
/>
</Tooltip>
{record.currentRole === Role.Candidate ? (
<Tooltip title="Approve request">
<CheckOutlined
className="mr-2"
onClick={() => handleChangeCurrentRole(record)}
/>
</Tooltip>
) : (
<Tooltip title="Swap role">
<SwapOutlined
className="mr-2"
onClick={() => handleChangeCurrentRole(record)}
/>
</Tooltip>
)}

<DeleteOutlined className="text-gray-700" />
</div>
Expand Down
47 changes: 27 additions & 20 deletions frontend/src/components/accountPage/UserListLogic/UserListLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<IUserListLogicProps> = 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<any[]>([]);
// Used to handle stop while uploading users from CSV
Expand All @@ -32,7 +31,7 @@ const UserListLogic: FC<IUserListLogicProps> = props => {
const [users, setUsers] = useState<UserAccountPage[]>([]);
const { data, loading, error, refetch } = useTenantsQuery({
variables: {
labels: `crownlabs.polito.it/${workspaceNamespace}`,
labels: `crownlabs.polito.it/${workspace.namespace}`,
retrieveWorkspaces: true,
},
onError: apolloErrorCatcher,
Expand All @@ -44,7 +43,7 @@ const UserListLogic: FC<IUserListLogicProps> = props => {
}, [abortUploading]);

const getManager = () => {
return `${workspaceName}-${userId || makeRandomDigits(10)}`;
return `${workspace.name}-${userId || makeRandomDigits(10)}`;
};

const refreshUserList = async () => await refetch();
Expand All @@ -59,8 +58,8 @@ const UserListLogic: FC<IUserListLogicProps> = props => {
name: user?.spec?.firstName!,
surname: user?.spec?.lastName!,
email: user?.spec?.email!,
currentRole: user?.spec?.workspaces?.find(roles =>
workspaceName.includes(roles?.name!)
currentRole: user?.spec?.workspaces?.find(
roles => roles?.name === workspace.name
)?.role!,
workspaces:
user?.spec?.workspaces?.map(workspace => ({
Expand All @@ -70,15 +69,15 @@ const UserListLogic: FC<IUserListLogicProps> = props => {
})) || []
);
}
}, [loading, data, workspaceName]);
}, [loading, data, workspace.name]);

const [applyTenantMutation] = useApplyTenantMutation();

const updateUser = async (user: UserAccountPage, newRole: Role) => {
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({
Expand All @@ -90,15 +89,23 @@ const UserListLogic: FC<IUserListLogicProps> = 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);
Expand Down Expand Up @@ -164,8 +171,8 @@ const UserListLogic: FC<IUserListLogicProps> = props => {
users={users}
onAddUser={addUser}
onUpdateUser={updateUser}
workspaceNamespace={workspaceNamespace}
workspaceName={workspaceName}
workspaceNamespace={workspace.namespace}
workspaceName={workspace.name}
uploadedNumber={uploadedNumber}
uploadedUserNumber={uploadedUserNumber}
setAbortUploading={handleAbort}
Expand Down
27 changes: 24 additions & 3 deletions frontend/src/components/workspaces/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { Col } from 'antd';
import { Button, Col } from 'antd';
import { FC, useEffect, useState } from 'react';
import { Workspace } from '../../../utils';
import { SessionValue, StorageKeys } from '../../../utilsStorage';
import { WorkspaceGrid } from '../Grid/WorkspaceGrid';
import { WorkspaceContainer } from '../WorkspaceContainer';
import { WorkspaceWelcome } from '../WorkspaceWelcome';
import WorkspaceAdd from '../WorkspaceAdd/WorkspaceAdd';

const dashboard = new SessionValue(StorageKeys.Dashboard_View, '-1');
export interface IDashboardProps {
tenantNamespace: string;
workspaces: Array<Workspace>;
candidatesButton?: {
show: boolean;
selected: boolean;
select: () => void;
};
}

const Dashboard: FC<IDashboardProps> = ({ ...props }) => {
const [selectedWsId, setSelectedWs] = useState(parseInt(dashboard.get()));
const { tenantNamespace, workspaces } = props;
const { tenantNamespace, workspaces, candidatesButton } = props;

useEffect(() => {
dashboard.set(String(selectedWsId));
Expand All @@ -29,9 +35,22 @@ const Dashboard: FC<IDashboardProps> = ({ ...props }) => {
workspaceItems={workspaces.map((ws, idx) => ({
id: idx,
title: ws.prettyName,
waitingTenants: ws.waitingTenants,
}))}
onClick={setSelectedWs}
/>
{candidatesButton?.show && (
<div className="lg:mt-4 mt-0 text-center">
<Button
type="ghost"
shape="round"
size={'middle'}
onClick={candidatesButton.select}
>
{candidatesButton.selected ? 'Hide' : 'Load'} candidates
</Button>
</div>
)}
</div>
</Col>
<Col
Expand All @@ -40,11 +59,13 @@ const Dashboard: FC<IDashboardProps> = ({ ...props }) => {
xxl={12}
className="lg:pl-4 lg:pr-0 px-4 flex flex-auto"
>
{selectedWsId !== -1 ? (
{selectedWsId >= 0 && selectedWsId < workspaces.length ? (
<WorkspaceContainer
tenantNamespace={tenantNamespace}
workspace={workspaces[selectedWsId]}
/>
) : selectedWsId === -2 ? (
<WorkspaceAdd />
) : (
<WorkspaceWelcome />
)}
Expand Down
116 changes: 112 additions & 4 deletions frontend/src/components/workspaces/DashboardLogic/DashboardLogic.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,131 @@
import { Spin } from 'antd';
import { FC, useContext } from 'react';
import {
FC,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { TenantContext } from '../../../contexts/TenantContext';
import { makeWorkspace } from '../../../utilsLogic';
import Dashboard from '../Dashboard/Dashboard';
import {
Role,
TenantsDocument,
useWorkspacesQuery,
} from '../../../generated-types';
import { Workspace, WorkspaceRole } from '../../../utils';
import { useApolloClient } from '@apollo/client';
import { ErrorContext } from '../../../errorHandling/ErrorContext';
import { LocalValue, StorageKeys } from '../../../utilsStorage';

const dashboard = new LocalValue(StorageKeys.Dashboard_LoadCandidates, 'false');

const DashboardLogic: FC<{}> = () => {
const { apolloErrorCatcher } = useContext(ErrorContext);

const {
data: tenantData,
error: tenantError,
loading: tenantLoading,
} = useContext(TenantContext);

const ws = useMemo(() => {
return (
tenantData?.tenant?.spec?.workspaces
?.filter(w => w?.role !== Role.Candidate)
?.map(makeWorkspace) ?? []
);
}, [tenantData?.tenant?.spec?.workspaces]);

const [viewWs, setViewWs] = useState<Workspace[]>(ws);
const client = useApolloClient();

const { data: workspaceQueryData } = useWorkspacesQuery({
variables: {
labels: 'crownlabs.polito.it/autoenroll=withApproval',
},
onError: apolloErrorCatcher,
});

const [loadCandidates, setLoadCandidates] = useState(
dashboard.get() === 'true'
);

const wsIsManagedWithApproval = useCallback(
(w: Workspace): boolean => {
return (
w?.role === WorkspaceRole.manager &&
workspaceQueryData?.workspaces?.items?.find(
wq => wq?.metadata?.name === w.name
) !== undefined
);
},
[workspaceQueryData?.workspaces?.items]
);

useEffect(() => {
if (loadCandidates) {
const workspaceQueue: Workspace[] = [];
const executeNext = () => {
if (!loadCandidates || workspaceQueue.length === 0) {
return;
}
const w = workspaceQueue.shift();
client
.query({
query: TenantsDocument,
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]);
}
executeNext();
});
};

ws?.filter(
w => w?.role === WorkspaceRole.manager && wsIsManagedWithApproval(w)
).forEach(w => {
workspaceQueue.push(w);
if (workspaceQueue.length === 1) {
executeNext();
}
});
}
}, [
client,
ws,
workspaceQueryData?.workspaces?.items,
loadCandidates,
wsIsManagedWithApproval,
]);

const selectLoadCandidates = () => {
if (loadCandidates) {
ws.forEach(w => (w.waitingTenants = undefined));
}
setViewWs([...ws]);
setLoadCandidates(!loadCandidates);
dashboard.set(String(!loadCandidates));
};

return !tenantLoading && tenantData && !tenantError ? (
<>
<Dashboard
tenantNamespace={tenantData.tenant?.status?.personalNamespace?.name!}
workspaces={
tenantData?.tenant?.spec?.workspaces?.map(makeWorkspace) ?? []
}
workspaces={viewWs}
candidatesButton={{
show: ws.some(w => wsIsManagedWithApproval(w)),
selected: loadCandidates,
select: selectLoadCandidates,
}}
/>
</>
) : (
Expand Down
Loading

0 comments on commit 6c29477

Please sign in to comment.