From ee94e6ffa6993a13b8513fd59a60702fe2cce20b Mon Sep 17 00:00:00 2001 From: araddcc002 Date: Wed, 12 Jun 2024 13:28:20 +0000 Subject: [PATCH 1/6] added optional advanced section on entry creation for adding users to access list --- backend/src/routes/v2/model/postModel.ts | 9 + backend/src/services/model.ts | 25 ++- frontend/src/entry/CreateEntry.tsx | 205 ++++++++++++------ frontend/src/entry/settings/EntityItem.tsx | 25 +-- frontend/src/entry/settings/EntryAccess.tsx | 77 ++----- .../src/entry/settings/EntryAccessPage.tsx | 73 +++++++ frontend/src/entry/settings/Settings.tsx | 4 +- frontend/types/types.ts | 1 + 8 files changed, 265 insertions(+), 154 deletions(-) create mode 100644 frontend/src/entry/settings/EntryAccessPage.tsx diff --git a/backend/src/routes/v2/model/postModel.ts b/backend/src/routes/v2/model/postModel.ts index 42d8c34f9..d020f0683 100644 --- a/backend/src/routes/v2/model/postModel.ts +++ b/backend/src/routes/v2/model/postModel.ts @@ -16,6 +16,15 @@ export const postModelSchema = z.object({ teamId: z.string(), description: z.string(), visibility: z.nativeEnum(EntryVisibility).optional().default(EntryVisibility.Public), + collaborators: z + .array( + z.object({ + entity: z.string().openapi({ example: 'user:user' }), + roles: z.array(z.string()).openapi({ example: ['owner', 'contributor'] }), + }), + ) + .optional() + .default([]), settings: z .object({ ungovernedAccess: z.boolean().optional().default(false).openapi({ example: true }), diff --git a/backend/src/services/model.ts b/backend/src/services/model.ts index 82be72451..66ba4ec0b 100644 --- a/backend/src/services/model.ts +++ b/backend/src/services/model.ts @@ -21,21 +21,32 @@ export function checkModelRestriction(model: ModelInterface) { } } -export type CreateModelParams = Pick +export type CreateModelParams = Pick< + ModelInterface, + 'name' | 'teamId' | 'description' | 'visibility' | 'settings' | 'collaborators' +> export async function createModel(user: UserInterface, modelParams: CreateModelParams) { const modelId = convertStringToId(modelParams.name) // TODO - Find team by teamId to check it's valid. Throw error if not found. + // Add the creator of the entry if they are not already in the collaborator list + const collaborators = modelParams.collaborators.find( + (collaborator) => (collaborator.entity === toEntity('user', user.dn)) !== undefined, + ) + ? [...modelParams.collaborators] + : [ + ...modelParams.collaborators, + { + entity: toEntity('user', user.dn), + roles: ['owner'], + }, + ] + const model = new Model({ ...modelParams, id: modelId, - collaborators: [ - { - entity: toEntity('user', user.dn), - roles: ['owner'], - }, - ], + collaborators, }) const auth = await authorisation.model(user, model, ModelAction.Create) diff --git a/frontend/src/entry/CreateEntry.tsx b/frontend/src/entry/CreateEntry.tsx index 48f921237..d74310253 100644 --- a/frontend/src/entry/CreateEntry.tsx +++ b/frontend/src/entry/CreateEntry.tsx @@ -1,6 +1,10 @@ import { ArrowBack, FileUpload, Lock, LockOpen } from '@mui/icons-material' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import LoadingButton from '@mui/lab/LoadingButton' import { + Accordion, + AccordionDetails, + AccordionSummary, Box, Button, Card, @@ -13,13 +17,25 @@ import { Typography, } from '@mui/material' import { postModel } from 'actions/model' +import { useGetCurrentUser } from 'actions/user' import { useRouter } from 'next/router' import { FormEvent, useMemo, useState } from 'react' +import Loading from 'src/common/Loading' import EntryDescriptionInput from 'src/entry/EntryDescriptionInput' import EntryNameInput from 'src/entry/EntryNameInput' +import EntryAccess from 'src/entry/settings/EntryAccess' import MessageAlert from 'src/MessageAlert' import TeamSelect from 'src/TeamSelect' -import { EntryForm, EntryKind, EntryKindKeys, EntryKindLabel, EntryVisibility, TeamInterface } from 'types/types' +import { + CollaboratorEntry, + EntityKind, + EntryForm, + EntryKind, + EntryKindKeys, + EntryKindLabel, + EntryVisibility, + TeamInterface, +} from 'types/types' import { getErrorMessage } from 'utils/fetcher' import { toTitleCase } from 'utils/stringUtils' @@ -30,10 +46,16 @@ type CreateEntryProps = { export default function CreateEntry({ kind, onBackClick }: CreateEntryProps) { const router = useRouter() + + const { currentUser, isCurrentUserLoading, isCurrentUserError } = useGetCurrentUser() + const [team, setTeam] = useState() const [name, setName] = useState('') const [description, setDescription] = useState('') const [visibility, setVisibility] = useState(EntryVisibility.Public) + const [collaborators, setCollaborators] = useState( + currentUser ? [{ entity: `${EntityKind.USER}:${currentUser?.dn}`, roles: ['owner'] }] : [], + ) const [errorMessage, setErrorMessage] = useState('') const [loading, setLoading] = useState(false) @@ -50,6 +72,7 @@ export default function CreateEntry({ kind, onBackClick }: CreateEntryProps) { kind, description, visibility, + collaborators, } const response = await postModel(formData) @@ -90,77 +113,119 @@ export default function CreateEntry({ kind, onBackClick }: CreateEntryProps) { ) } + if (isCurrentUserError) { + return + } + return ( - - - - - - {`Create ${toTitleCase(kind)}`} - - - {kind === EntryKind.MODEL && ( - A model repository contains all files, history and information related to a model. - )} - - - - } spacing={2}> - <> - - Overview - - - setTeam(value)} /> - setName(value)} /> - - setDescription(value)} /> - - - <> - - Access control + <> + {isCurrentUserLoading && } + + + + + + {`Create ${toTitleCase(kind)}`} - setVisibility(e.target.value as EntryForm['visibility'])} - > - } - label={publicLabel()} - data-test='publicButtonSelector' - /> - } - label={privateLabel()} - data-test='privateButtonSelector' - /> - - - - - - - - {`Create ${EntryKindLabel[kind]}`} - - - - - + + {kind === EntryKind.MODEL && ( + + A model repository contains all files, history and information related to a model. + + )} + - - + + } spacing={2}> + <> + + Overview + + + setTeam(value)} /> + setName(value)} /> + + setDescription(value)} /> + + + <> + + Access control + + setVisibility(e.target.value as EntryForm['visibility'])} + > + } + label={publicLabel()} + data-test='publicButtonSelector' + /> + } + label={privateLabel()} + data-test='privateButtonSelector' + /> + + + + } + aria-controls='panel1-content' + id='panel1-header' + > + + Advanced (optional) + + + + + Manage access list + + + Please note that only entry roles can be added at this stage, and review roles should be added once a + schema has been selected. + + + setCollaborators(val)} + errorMessage={errorMessage} + entryKind={EntryKind.MODEL} + entryRoles={[ + { id: 'owner', name: 'Owner' }, + { id: 'contributor', name: 'Contributor' }, + { id: 'consumer', name: 'Consumer' }, + ]} + /> + + + + + + + + {`Create ${EntryKindLabel[kind]}`} + + + + + + + + + ) } diff --git a/frontend/src/entry/settings/EntityItem.tsx b/frontend/src/entry/settings/EntityItem.tsx index de21222c6..52629308a 100644 --- a/frontend/src/entry/settings/EntityItem.tsx +++ b/frontend/src/entry/settings/EntityItem.tsx @@ -12,29 +12,21 @@ import { Tooltip, Typography, } from '@mui/material' -import { useGetModelRoles } from 'actions/model' import _ from 'lodash-es' import { SyntheticEvent, useMemo } from 'react' -import Loading from 'src/common/Loading' import UserDisplay from 'src/common/UserDisplay' -import MessageAlert from 'src/MessageAlert' -import { CollaboratorEntry, EntityKind, EntryInterface } from 'types/types' +import { CollaboratorEntry, EntityKind, Role } from 'types/types' import { toSentenceCase } from 'utils/stringUtils' type EntityItemProps = { entity: CollaboratorEntry accessList: CollaboratorEntry[] onAccessListChange: (value: CollaboratorEntry[]) => void - entry: EntryInterface + entryKind: string + entryRoles: Role[] } -export default function EntityItem({ entity, accessList, onAccessListChange, entry }: EntityItemProps) { - const { - modelRoles: entryRoles, - isModelRolesLoading: isEntryRolesLoading, - isModelRolesError: isEntryRolesError, - } = useGetModelRoles(entry.id) - +export default function EntityItem({ entity, accessList, onAccessListChange, entryKind, entryRoles }: EntityItemProps) { const entryRoleOptions = useMemo(() => entryRoles.map((role) => role.id), [entryRoles]) function onRoleChange(_event: SyntheticEvent, newValues: string[]) { @@ -55,10 +47,6 @@ export default function EntityItem({ entity, accessList, onAccessListChange, ent return role } - if (isEntryRolesError) { - return - } - return ( @@ -68,8 +56,7 @@ export default function EntityItem({ entity, accessList, onAccessListChange, ent - {isEntryRolesLoading && } - {!isEntryRolesLoading && entryRoles.length > 0 && ( + {entryRoles.length > 0 && ( diff --git a/frontend/src/entry/settings/EntryAccess.tsx b/frontend/src/entry/settings/EntryAccess.tsx index ec2d4306b..7b9879940 100644 --- a/frontend/src/entry/settings/EntryAccess.tsx +++ b/frontend/src/entry/settings/EntryAccess.tsx @@ -1,40 +1,26 @@ -import { LoadingButton } from '@mui/lab' -import { - Autocomplete, - Stack, - Table, - TableBody, - TableCell, - TableHead, - TableRow, - TextField, - Typography, -} from '@mui/material' -import { patchModel, useGetModel } from 'actions/model' +import { Autocomplete, Stack, Table, TableBody, TableCell, TableHead, TableRow, TextField } from '@mui/material' import { useListUsers } from 'actions/user' import { debounce } from 'lodash-es' import { SyntheticEvent, useCallback, useEffect, useMemo, useState } from 'react' import EntityItem from 'src/entry/settings/EntityItem' -import useNotification from 'src/hooks/useNotification' import MessageAlert from 'src/MessageAlert' -import { CollaboratorEntry, EntityObject, EntryInterface } from 'types/types' -import { getErrorMessage } from 'utils/fetcher' -import { toSentenceCase, toTitleCase } from 'utils/stringUtils' +import { CollaboratorEntry, EntityObject, EntryKindKeys, Role } from 'types/types' +import { toSentenceCase } from 'utils/stringUtils' type EntryAccessProps = { - entry: EntryInterface + value: CollaboratorEntry[] + onUpdate: (list: CollaboratorEntry[]) => void + entryKind: EntryKindKeys + errorMessage: string + entryRoles: Role[] } -export default function EntryAccess({ entry }: EntryAccessProps) { +export default function EntryAccess({ value, onUpdate, entryKind, errorMessage = '', entryRoles }: EntryAccessProps) { const [open, setOpen] = useState(false) - const [accessList, setAccessList] = useState(entry.collaborators) + const [accessList, setAccessList] = useState(value) const [userListQuery, setUserListQuery] = useState('') - const [loading, setLoading] = useState(false) - const [errorMessage, setErrorMessage] = useState('') const { users, isUsersLoading, isUsersError } = useListUsers(userListQuery) - const { isModelError: isEntryError, mutateModel: mutateEntry } = useGetModel(entry.id, entry.kind) - const sendNotification = useNotification() const accessListEntities = useMemo( () => @@ -44,17 +30,22 @@ export default function EntryAccess({ entry }: EntryAccessProps) { entity={entity} accessList={accessList} onAccessListChange={setAccessList} - entry={entry} + entryRoles={entryRoles} + entryKind={entryKind} /> )), - [accessList, entry], + [accessList, entryKind, entryRoles], ) useEffect(() => { - if (entry) { - setAccessList(entry.collaborators) + if (value) { + setAccessList(value) } - }, [entry]) + }, [value]) + + useEffect(() => { + onUpdate(accessList) + }, [accessList, onUpdate]) const onUserChange = useCallback( (_event: SyntheticEvent, newValue: EntityObject | null) => { @@ -76,22 +67,6 @@ export default function EntryAccess({ entry }: EntryAccessProps) { handleInputChange(event, value) }, 500) - async function updateAccessList() { - setLoading(true) - const res = await patchModel(entry.id, { collaborators: accessList }) - if (!res.ok) { - setErrorMessage(await getErrorMessage(res)) - } else { - sendNotification({ - variant: 'success', - msg: `${toTitleCase(entry.kind)} access list updated`, - anchorOrigin: { horizontal: 'center', vertical: 'bottom' }, - }) - mutateEntry() - } - setLoading(false) - } - const noOptionsText = useMemo(() => { if (userListQuery.length < 3) return 'Please enter at least three characters' if (isUsersError?.status === 413) return 'Too many results, please refine your search' @@ -102,15 +77,8 @@ export default function EntryAccess({ entry }: EntryAccessProps) { return } - if (isEntryError) { - return - } - return ( - - {`Manage ${toSentenceCase(entry.kind)} access`} - setOpen(true)} @@ -133,7 +101,7 @@ export default function EntryAccess({ entry }: EntryAccessProps) { )} /> @@ -147,9 +115,6 @@ export default function EntryAccess({ entry }: EntryAccessProps) { {accessListEntities} - - Save - ) diff --git a/frontend/src/entry/settings/EntryAccessPage.tsx b/frontend/src/entry/settings/EntryAccessPage.tsx new file mode 100644 index 000000000..ef00968ca --- /dev/null +++ b/frontend/src/entry/settings/EntryAccessPage.tsx @@ -0,0 +1,73 @@ +import { LoadingButton } from '@mui/lab' +import { Stack, Typography } from '@mui/material' +import { patchModel, useGetModel, useGetModelRoles } from 'actions/model' +import { useState } from 'react' +import Loading from 'src/common/Loading' +import EntryAccess from 'src/entry/settings/EntryAccess' +import useNotification from 'src/hooks/useNotification' +import MessageAlert from 'src/MessageAlert' +import { CollaboratorEntry, EntryInterface } from 'types/types' +import { getErrorMessage } from 'utils/fetcher' +import { toSentenceCase, toTitleCase } from 'utils/stringUtils' + +type EntryAccessPageProps = { + entry: EntryInterface +} + +export default function EntryAccessPage({ entry }: EntryAccessPageProps) { + const [loading, setLoading] = useState(false) + const [errorMessage, setErrorMessage] = useState('') + const [accessList, setAccessList] = useState(entry.collaborators) + + const { isModelError: isEntryError, mutateModel: mutateEntry } = useGetModel(entry.id, entry.kind) + const { + modelRoles: entryRoles, + isModelRolesLoading: isEntryRolesLoading, + isModelRolesError: isEntryRolesError, + } = useGetModelRoles(entry.id) + + const sendNotification = useNotification() + + async function updateAccessList() { + setLoading(true) + const res = await patchModel(entry.id, { collaborators: accessList }) + if (!res.ok) { + setErrorMessage(await getErrorMessage(res)) + } else { + sendNotification({ + variant: 'success', + msg: `${toTitleCase(entry.kind)} access list updated`, + anchorOrigin: { horizontal: 'center', vertical: 'bottom' }, + }) + mutateEntry() + } + setLoading(false) + } + + if (isEntryError) { + return + } + + if (isEntryRolesError) { + return + } + + return ( + + {isEntryRolesLoading && } + + {`Manage ${toSentenceCase(entry.kind)} access`} + + setAccessList(val)} + errorMessage={errorMessage} + entryKind={entry.kind} + entryRoles={entryRoles} + /> + + Save + + + ) +} diff --git a/frontend/src/entry/settings/Settings.tsx b/frontend/src/entry/settings/Settings.tsx index 1fb6df75f..2ab076106 100644 --- a/frontend/src/entry/settings/Settings.tsx +++ b/frontend/src/entry/settings/Settings.tsx @@ -4,7 +4,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import SimpleListItemButton from 'src/common/SimpleListItemButton' import AccessRequestSettings from 'src/entry/model/settings/AccessRequestSettings' -import EntryAccess from 'src/entry/settings/EntryAccess' +import EntryAccessPage from 'src/entry/settings/EntryAccessPage' import EntryDetails from 'src/entry/settings/EntryDetails' import { EntryInterface, EntryKind, EntryKindKeys } from 'types/types' import { toTitleCase } from 'utils/stringUtils' @@ -104,7 +104,7 @@ export default function Settings({ entry }: SettingsProps) { {selectedCategory === SettingsCategory.DETAILS && } - {selectedCategory === SettingsCategory.PERMISSIONS && } + {selectedCategory === SettingsCategory.PERMISSIONS && } {selectedCategory === SettingsCategory.ACCESS_REQUESTS && } {selectedCategory === SettingsCategory.DANGER && ( diff --git a/frontend/types/types.ts b/frontend/types/types.ts index 1a27a4972..ebb8ffc4a 100644 --- a/frontend/types/types.ts +++ b/frontend/types/types.ts @@ -383,6 +383,7 @@ export interface EntryForm { teamId: string description: string visibility: EntryVisibilityKeys + collaborators?: CollaboratorEntry[] } export type UpdateEntryForm = Omit From 386e4d2718c4e3963b8b35cc2384f4f67b1892de Mon Sep 17 00:00:00 2001 From: araddcc002 Date: Wed, 12 Jun 2024 16:13:11 +0000 Subject: [PATCH 2/6] updated unit tests and updated backend logic for role checking on post model --- backend/src/services/model.ts | 33 +++++++----- .../__snapshots__/inferencing.spec.ts.snap | 51 ------------------- .../patchReleaseComment.spec.ts.snap | 14 ----- frontend/src/entry/CreateEntry.tsx | 1 - frontend/src/entry/settings/EntryAccess.tsx | 4 +- .../src/entry/settings/EntryAccessPage.tsx | 2 +- 6 files changed, 21 insertions(+), 84 deletions(-) delete mode 100644 backend/test/clients/__snapshots__/inferencing.spec.ts.snap diff --git a/backend/src/services/model.ts b/backend/src/services/model.ts index 66ba4ec0b..15b87f680 100644 --- a/backend/src/services/model.ts +++ b/backend/src/services/model.ts @@ -4,7 +4,7 @@ import _ from 'lodash' import authentication from '../connectors/authentication/index.js' import { ModelAction, ModelActionKeys } from '../connectors/authorisation/actions.js' import authorisation from '../connectors/authorisation/index.js' -import ModelModel, { EntryKindKeys } from '../models/Model.js' +import ModelModel, { CollaboratorEntry, EntryKindKeys } from '../models/Model.js' import Model, { ModelInterface } from '../models/Model.js' import ModelCardRevisionModel, { ModelCardRevisionDoc } from '../models/ModelCardRevision.js' import { UserInterface } from '../models/User.js' @@ -23,25 +23,30 @@ export function checkModelRestriction(model: ModelInterface) { export type CreateModelParams = Pick< ModelInterface, - 'name' | 'teamId' | 'description' | 'visibility' | 'settings' | 'collaborators' + 'name' | 'teamId' | 'description' | 'visibility' | 'settings' | 'kind' | 'collaborators' > export async function createModel(user: UserInterface, modelParams: CreateModelParams) { const modelId = convertStringToId(modelParams.name) // TODO - Find team by teamId to check it's valid. Throw error if not found. - // Add the creator of the entry if they are not already in the collaborator list - const collaborators = modelParams.collaborators.find( - (collaborator) => (collaborator.entity === toEntity('user', user.dn)) !== undefined, - ) - ? [...modelParams.collaborators] - : [ - ...modelParams.collaborators, - { - entity: toEntity('user', user.dn), - roles: ['owner'], - }, - ] + let collaborators: CollaboratorEntry[] = [] + if (modelParams.collaborators.length > 0) { + const collaboratorListContainsOwner = + modelParams.collaborators.filter((collaborator) => collaborator.roles.some((role) => role === 'owner')).length > 0 + if (collaboratorListContainsOwner) { + collaborators = modelParams.collaborators + } else { + throw BadReq('At least one collaborator must be given the owner role.') + } + } else { + collaborators = [ + { + entity: toEntity('user', user.dn), + roles: ['owner'], + }, + ] + } const model = new Model({ ...modelParams, diff --git a/backend/test/clients/__snapshots__/inferencing.spec.ts.snap b/backend/test/clients/__snapshots__/inferencing.spec.ts.snap deleted file mode 100644 index a82a1e901..000000000 --- a/backend/test/clients/__snapshots__/inferencing.spec.ts.snap +++ /dev/null @@ -1,51 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`clients > inferencing > createInferencing > success 1`] = ` -[ - [ - "http://example.com/api/deploy", - { - "body": "{}", - "headers": Headers { - Symbol(headers list): HeadersList { - "cookies": null, - Symbol(headers map): Map { - "content-type" => { - "name": "content-type", - "value": "application/json", - }, - }, - Symbol(headers map sorted): null, - }, - Symbol(guard): "none", - }, - "method": "POST", - }, - ], -] -`; - -exports[`clients > inferencing > updateInferencing > success 1`] = ` -[ - [ - "http://example.com/api/update", - { - "body": "{}", - "headers": Headers { - Symbol(headers list): HeadersList { - "cookies": null, - Symbol(headers map): Map { - "content-type" => { - "name": "content-type", - "value": "application/json", - }, - }, - Symbol(headers map sorted): null, - }, - Symbol(guard): "none", - }, - "method": "PATCH", - }, - ], -] -`; diff --git a/backend/test/routes/model/release/__snapshots__/patchReleaseComment.spec.ts.snap b/backend/test/routes/model/release/__snapshots__/patchReleaseComment.spec.ts.snap index 5b2b886d8..a4a6eec79 100644 --- a/backend/test/routes/model/release/__snapshots__/patchReleaseComment.spec.ts.snap +++ b/backend/test/routes/model/release/__snapshots__/patchReleaseComment.spec.ts.snap @@ -13,17 +13,3 @@ exports[`routes > release > patchReleaseComment > audit > expected call 1`] = ` "message": "test", } `; - -exports[`routes > release > postReleaseComment > 200 > ok 1`] = ` -{ - "release": { - "message": "test", - }, -} -`; - -exports[`routes > release > postReleaseComment > audit > expected call 1`] = ` -{ - "message": "test", -} -`; diff --git a/frontend/src/entry/CreateEntry.tsx b/frontend/src/entry/CreateEntry.tsx index d74310253..180f8d200 100644 --- a/frontend/src/entry/CreateEntry.tsx +++ b/frontend/src/entry/CreateEntry.tsx @@ -196,7 +196,6 @@ export default function CreateEntry({ kind, onBackClick }: CreateEntryProps) { setCollaborators(val)} - errorMessage={errorMessage} entryKind={EntryKind.MODEL} entryRoles={[ { id: 'owner', name: 'Owner' }, diff --git a/frontend/src/entry/settings/EntryAccess.tsx b/frontend/src/entry/settings/EntryAccess.tsx index 7b9879940..88aa91e35 100644 --- a/frontend/src/entry/settings/EntryAccess.tsx +++ b/frontend/src/entry/settings/EntryAccess.tsx @@ -11,11 +11,10 @@ type EntryAccessProps = { value: CollaboratorEntry[] onUpdate: (list: CollaboratorEntry[]) => void entryKind: EntryKindKeys - errorMessage: string entryRoles: Role[] } -export default function EntryAccess({ value, onUpdate, entryKind, errorMessage = '', entryRoles }: EntryAccessProps) { +export default function EntryAccess({ value, onUpdate, entryKind, entryRoles }: EntryAccessProps) { const [open, setOpen] = useState(false) const [accessList, setAccessList] = useState(value) const [userListQuery, setUserListQuery] = useState('') @@ -115,7 +114,6 @@ export default function EntryAccess({ value, onUpdate, entryKind, errorMessage = {accessListEntities} - ) } diff --git a/frontend/src/entry/settings/EntryAccessPage.tsx b/frontend/src/entry/settings/EntryAccessPage.tsx index ef00968ca..6ef8ec71c 100644 --- a/frontend/src/entry/settings/EntryAccessPage.tsx +++ b/frontend/src/entry/settings/EntryAccessPage.tsx @@ -61,13 +61,13 @@ export default function EntryAccessPage({ entry }: EntryAccessPageProps) { setAccessList(val)} - errorMessage={errorMessage} entryKind={entry.kind} entryRoles={entryRoles} /> Save + ) } From 7b66d50db6b2de2eb7255b49868507f8c7ea5e24 Mon Sep 17 00:00:00 2001 From: araddcc002 Date: Wed, 12 Jun 2024 16:16:07 +0000 Subject: [PATCH 3/6] updated logic further --- backend/src/services/model.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/services/model.ts b/backend/src/services/model.ts index 15b87f680..c518be00c 100644 --- a/backend/src/services/model.ts +++ b/backend/src/services/model.ts @@ -32,8 +32,9 @@ export async function createModel(user: UserInterface, modelParams: CreateModelP let collaborators: CollaboratorEntry[] = [] if (modelParams.collaborators.length > 0) { - const collaboratorListContainsOwner = - modelParams.collaborators.filter((collaborator) => collaborator.roles.some((role) => role === 'owner')).length > 0 + const collaboratorListContainsOwner = modelParams.collaborators.some((collaborator) => + collaborator.roles.some((role) => role === 'owner'), + ) if (collaboratorListContainsOwner) { collaborators = modelParams.collaborators } else { From 4a580e807774537fe6f796b6cc0232966e8688bc Mon Sep 17 00:00:00 2001 From: araddcc002 Date: Wed, 12 Jun 2024 16:28:58 +0000 Subject: [PATCH 4/6] updated styling and merged main and fixed issues from merge --- frontend/src/entry/settings/EntryAccess.tsx | 9 +-------- frontend/src/entry/settings/EntryAccessPage.tsx | 11 ++++++++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/frontend/src/entry/settings/EntryAccess.tsx b/frontend/src/entry/settings/EntryAccess.tsx index 4f36a5146..88aa91e35 100644 --- a/frontend/src/entry/settings/EntryAccess.tsx +++ b/frontend/src/entry/settings/EntryAccess.tsx @@ -2,8 +2,6 @@ import { Autocomplete, Stack, Table, TableBody, TableCell, TableHead, TableRow, import { useListUsers } from 'actions/user' import { debounce } from 'lodash-es' import { SyntheticEvent, useCallback, useEffect, useMemo, useState } from 'react' -import HelpDialog from 'src/common/HelpDialog' -import EntryRolesInfo from 'src/entry/model/settings/EntryRolesInfo' import EntityItem from 'src/entry/settings/EntityItem' import MessageAlert from 'src/MessageAlert' import { CollaboratorEntry, EntityObject, EntryKindKeys, Role } from 'types/types' @@ -110,12 +108,7 @@ export default function EntryAccess({ value, onUpdate, entryKind, entryRoles }: Entity - - - Roles - } /> - - + Roles Actions diff --git a/frontend/src/entry/settings/EntryAccessPage.tsx b/frontend/src/entry/settings/EntryAccessPage.tsx index 6ef8ec71c..4ede92364 100644 --- a/frontend/src/entry/settings/EntryAccessPage.tsx +++ b/frontend/src/entry/settings/EntryAccessPage.tsx @@ -2,7 +2,9 @@ import { LoadingButton } from '@mui/lab' import { Stack, Typography } from '@mui/material' import { patchModel, useGetModel, useGetModelRoles } from 'actions/model' import { useState } from 'react' +import HelpDialog from 'src/common/HelpDialog' import Loading from 'src/common/Loading' +import EntryRolesInfo from 'src/entry/model/settings/EntryRolesInfo' import EntryAccess from 'src/entry/settings/EntryAccess' import useNotification from 'src/hooks/useNotification' import MessageAlert from 'src/MessageAlert' @@ -55,9 +57,12 @@ export default function EntryAccessPage({ entry }: EntryAccessPageProps) { return ( {isEntryRolesLoading && } - - {`Manage ${toSentenceCase(entry.kind)} access`} - + + + {`Manage ${toSentenceCase(entry.kind)} access`} + + } /> + setAccessList(val)} From c58dac47212168e120d6a586b47661000e4b7276 Mon Sep 17 00:00:00 2001 From: araddcc002 Date: Mon, 17 Jun 2024 13:01:36 +0000 Subject: [PATCH 5/6] updated model service tests --- backend/src/services/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/model.ts b/backend/src/services/model.ts index c518be00c..1102ff85c 100644 --- a/backend/src/services/model.ts +++ b/backend/src/services/model.ts @@ -31,7 +31,7 @@ export async function createModel(user: UserInterface, modelParams: CreateModelP // TODO - Find team by teamId to check it's valid. Throw error if not found. let collaborators: CollaboratorEntry[] = [] - if (modelParams.collaborators.length > 0) { + if (modelParams.collaborators && modelParams.collaborators.length > 0) { const collaboratorListContainsOwner = modelParams.collaborators.some((collaborator) => collaborator.roles.some((role) => role === 'owner'), ) From 73d6f2aa4f5ca149f242de2f01b6a68beac54e9a Mon Sep 17 00:00:00 2001 From: araddcc002 Date: Mon, 17 Jun 2024 15:31:22 +0000 Subject: [PATCH 6/6] updated the inferencing client headers to allow tests to pass --- backend/src/clients/inferencing.ts | 4 +-- .../__snapshots__/inferencing.spec.ts.snap | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 backend/test/clients/__snapshots__/inferencing.spec.ts.snap diff --git a/backend/src/clients/inferencing.ts b/backend/src/clients/inferencing.ts index 4292af9e1..e6127e425 100644 --- a/backend/src/clients/inferencing.ts +++ b/backend/src/clients/inferencing.ts @@ -15,7 +15,7 @@ export async function createInferenceService(inferenceServiceParams: InferenceSe try { res = await fetch(`${config.ui.inference.connection.host}/api/deploy`, { method: 'POST', - headers: new Headers({ 'content-type': 'application/json' }), + headers: { 'content-type': 'application/json' }, body: JSON.stringify(inferenceServiceParams), }) } catch (err) { @@ -34,7 +34,7 @@ export async function updateInferenceService(inferenceServiceParams: InferenceSe try { res = await fetch(`${config.ui.inference.connection.host}/api/update`, { method: 'PATCH', - headers: new Headers({ 'content-type': 'application/json' }), + headers: { 'content-type': 'application/json' }, body: JSON.stringify(inferenceServiceParams), }) } catch (err) { diff --git a/backend/test/clients/__snapshots__/inferencing.spec.ts.snap b/backend/test/clients/__snapshots__/inferencing.spec.ts.snap new file mode 100644 index 000000000..268c7fee2 --- /dev/null +++ b/backend/test/clients/__snapshots__/inferencing.spec.ts.snap @@ -0,0 +1,31 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`clients > inferencing > createInferencing > success 1`] = ` +[ + [ + "http://example.com/api/deploy", + { + "body": "{}", + "headers": { + "content-type": "application/json", + }, + "method": "POST", + }, + ], +] +`; + +exports[`clients > inferencing > updateInferencing > success 1`] = ` +[ + [ + "http://example.com/api/update", + { + "body": "{}", + "headers": { + "content-type": "application/json", + }, + "method": "PATCH", + }, + ], +] +`;