Skip to content

Commit

Permalink
waiting tenants badge
Browse files Browse the repository at this point in the history
  • Loading branch information
giacoliva committed Jan 12, 2024
1 parent d56c807 commit 17d7369
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 54 deletions.
45 changes: 26 additions & 19 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 @@ -60,7 +59,7 @@ const UserListLogic: FC<IUserListLogicProps> = 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 => ({
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
3 changes: 2 additions & 1 deletion frontend/src/components/workspaces/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const Dashboard: FC<IDashboardProps> = ({ ...props }) => {
workspaceItems={workspaces.map((ws, idx) => ({
id: idx,
title: ws.prettyName,
waitingTenants: ws.waitingTenants,
}))}
onClick={setSelectedWs}
/>
Expand All @@ -40,7 +41,7 @@ const Dashboard: FC<IDashboardProps> = ({ ...props }) => {
xxl={12}
className="lg:pl-4 lg:pr-0 px-4 flex flex-auto"
>
{selectedWsId > 0 ? (
{selectedWsId >= 0 ? (
<WorkspaceContainer
tenantNamespace={tenantNamespace}
workspace={workspaces[selectedWsId]}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Spin } from 'antd';
import { FC, useContext } from 'react';
import { FC, useContext, useEffect, useState } from 'react';
import { TenantContext } from '../../../contexts/TenantContext';
import { makeWorkspace } from '../../../utilsLogic';
import Dashboard from '../Dashboard/Dashboard';
import { Role } from '../../../generated-types';
import { Workspace, WorkspaceRole } from '../../../utils';
import { gql, useApolloClient } from '@apollo/client';

const DashboardLogic: FC<{}> = () => {
const {
Expand All @@ -12,15 +14,55 @@ const DashboardLogic: FC<{}> = () => {
loading: tenantLoading,
} = useContext(TenantContext);

const [ws, setWs] = useState<Workspace[]>([]);
const [viewWs, setViewWs] = useState<Workspace[]>([]);
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 ? (
<>
<Dashboard
tenantNamespace={tenantData.tenant?.status?.personalNamespace?.name!}
workspaces={
tenantData?.tenant?.spec?.workspaces
?.filter(w => w?.role !== Role.Candidate)
?.map(makeWorkspace) ?? []
}
workspaces={viewWs}
/>
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -17,6 +17,7 @@ const WorkspaceGrid: FC<IWorkspaceGridProps> = ({ ...props }) => {
id={workspaceItem.id}
title={workspaceItem.title}
isActive={selectedWs === workspaceItem.id}
badgeValue={workspaceItem.waitingTenants}
onClick={onClick}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@
border: solid @primary-color;
border-width: 4px;
}

.grid-badge {
position: absolute;
right: 0;
top: 0;
}
Original file line number Diff line number Diff line change
@@ -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<IWorkspaceGridItemProps> = ({ ...props }) => {
const { id, title, isActive, onClick } = props;
const { id, title, isActive, badgeValue, onClick } = props;

return (
<Row className="sm:px-0 md:px-4">
Expand All @@ -29,6 +31,9 @@ const WorkspaceGridItem: FC<IWorkspaceGridItemProps> = ({ ...props }) => {
>
{title[0].toUpperCase() + title[1].toUpperCase()}
</label>
{badgeValue && (
<Badge value={badgeValue} size="middle" className="grid-badge" />
)}
</button>
</Col>
<Col span={24} className="flex justify-center pb-0">
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,15 +60,7 @@ const getImages = (dataImages: ImagesQuery) => {
const WorkspaceContainer: FC<IWorkspaceContainerProps> = ({ ...props }) => {
const [showUserListModal, setShowUserListModal] = useState<boolean>(false);

const {
tenantNamespace,
workspace: {
role,
name: workspaceName,
namespace: workspaceNamespace,
prettyName: workspacePrettyName,
},
} = props;
const { tenantNamespace, workspace } = props;

const { apolloErrorCatcher } = useContext(ErrorContext);
const [createTemplateMutation, { loading }] = useCreateTemplateMutation({
Expand All @@ -82,9 +77,9 @@ const WorkspaceContainer: FC<IWorkspaceContainerProps> = ({ ...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
Expand All @@ -109,7 +104,7 @@ const WorkspaceContainer: FC<IWorkspaceContainerProps> = ({ ...props }) => {
return (
<>
<ModalCreateTemplate
workspaceNamespace={workspaceNamespace}
workspaceNamespace={workspace.namespace}
cpuInterval={{ max: 4, min: 1 }}
ramInterval={{ max: 8, min: 1 }}
diskInterval={{ max: 50, min: 10 }}
Expand All @@ -125,24 +120,33 @@ const WorkspaceContainer: FC<IWorkspaceContainerProps> = ({ ...props }) => {
center: (
<div className="h-full flex justify-center items-center px-5">
<p className="md:text-4xl text-2xl text-center mb-0">
<b>{workspacePrettyName}</b>
<b>{workspace.prettyName}</b>
</p>
</div>
),
left: role === 'manager' && (
left: workspace.role === 'manager' && (
<div className="h-full flex justify-center items-center pl-10">
<Tooltip title="Manage users">
<Tooltip title="Manage users" className="tenant-button">
<Button
type="primary"
shape="circle"
size="large"
icon={<UserSwitchOutlined />}
onClick={() => setShowUserListModal(true)}
/>
>
{workspace.waitingTenants && (
<Badge
value={workspace.waitingTenants}
size="small"
color="yellow"
className="candidate-badge"
/>
)}
</Button>
</Tooltip>
</div>
),
right: role === 'manager' && (
right: workspace.role === 'manager' && (
<div className="h-full flex justify-center items-center pr-10">
<Tooltip title="Create template">
<Button
Expand All @@ -162,22 +166,19 @@ const WorkspaceContainer: FC<IWorkspaceContainerProps> = ({ ...props }) => {
>
<TemplatesTableLogic
tenantNamespace={tenantNamespace}
role={role}
workspaceNamespace={workspaceNamespace}
workspaceName={workspaceName}
role={workspace.role}
workspaceNamespace={workspace.namespace}
workspaceName={workspace.name}
/>
<Modal
destroyOnClose={true}
title={`Users in ${workspacePrettyName} `}
title={`Users in ${workspace.prettyName} `}
width="800px"
visible={showUserListModal}
footer={null}
onCancel={() => setShowUserListModal(false)}
>
<UserListLogic
workspaceName={workspaceName}
workspaceNamespace={workspaceNamespace}
/>
<UserListLogic workspace={workspace} />
</Modal>
</Box>
</>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type Workspace = {
prettyName: string;
role: WorkspaceRole;
templates?: Array<Template>;
waitingTenants?: number;
};
export type Resources = {
cpu: number;
Expand Down

0 comments on commit 17d7369

Please sign in to comment.