diff --git a/backend/src/connectors/v2/authorisation/silly.ts b/backend/src/connectors/v2/authorisation/silly.ts index 30e3b02cc..9509f0d06 100644 --- a/backend/src/connectors/v2/authorisation/silly.ts +++ b/backend/src/connectors/v2/authorisation/silly.ts @@ -84,7 +84,7 @@ export class SillyAuthorisationConnector extends BaseAuthorisationConnector { } if (action !== FileAction.Download) { - log.warn({ userDn: user.dn, file: file._id }, 'Non-collaborator can only download artefacts') + log.warn({ userDn: user.dn, file: file._id }, 'Non-collaborator can only download files') return false } diff --git a/frontend/pages/beta/model/[modelId]/access-request/new.tsx b/frontend/pages/beta/model/[modelId]/access-request/new.tsx index fbe832819..0be6c8153 100644 --- a/frontend/pages/beta/model/[modelId]/access-request/new.tsx +++ b/frontend/pages/beta/model/[modelId]/access-request/new.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' import MultipleErrorWrapper from 'src/errors/MultipleErrorWrapper' import Link from 'src/Link' +import { getErrorMessage } from 'utils/fetcher' import { postAccessRequest } from '../../../../../actions/accessRequest' import { useGetModel } from '../../../../../actions/model' @@ -51,32 +52,41 @@ export default function NewAccessRequest() { async function onSubmit() { setSubmissionErrorText('') setSubmitButtonLoading(true) + + if (!modelId || !schemaId) { + setSubmissionErrorText(`Please wait until the page has finished loading before attempting to submit.`) + setSubmitButtonLoading(false) + return + } + for (const step of splitSchema.steps) { - const isValid = validateForm(step) + // The user has tried to submit, so let's enable schema validation for each page setStepValidate(splitSchema, setSplitSchema, step, true) + } + + for (const step of splitSchema.steps) { + const isValid = validateForm(step) + if (!isValid) { setSubmissionErrorText('Please make sure that all sections have been completed.') setSubmitButtonLoading(false) - } - if (!modelId) { - setSubmissionErrorText('Unknown model ID') - setSubmitButtonLoading(false) - } - if (!schemaId) { - setSubmissionErrorText('Unknown schema ID') - setSubmitButtonLoading(false) - } - if (modelId && schemaId) { - const data = getStepsData(splitSchema, true) - const res = await postAccessRequest(modelId, schemaId, data) - if (res.status && res.status < 400) { - setSubmissionErrorText('') - router.push(`/beta/model/${modelId}?tab=access`) - } else { - setSubmitButtonLoading(false) - } + return } } + + const data = getStepsData(splitSchema, true) + const res = await postAccessRequest(modelId, schemaId, data) + + if (!res.ok) { + setSubmissionErrorText(await getErrorMessage(res)) + setSubmitButtonLoading(false) + return + } + + setSubmitButtonLoading(false) + + const body = await res.json() + router.push(`/beta/model/${modelId}/access-request/${body.accessRequest.id}`) } const error = MultipleErrorWrapper(`Unable to load access request page`, { diff --git a/frontend/pages/beta/model/[modelId]/release/new.tsx b/frontend/pages/beta/model/[modelId]/release/new.tsx index c58fdd808..1b3752c1f 100644 --- a/frontend/pages/beta/model/[modelId]/release/new.tsx +++ b/frontend/pages/beta/model/[modelId]/release/new.tsx @@ -2,7 +2,7 @@ import { ArrowBack, DesignServices } from '@mui/icons-material' import { LoadingButton } from '@mui/lab' import { Box, Button, Card, Container, Stack, Typography } from '@mui/material' import { useGetModel } from 'actions/model' -import { CreateReleaseParams, postFile, postRelease, useGetReleasesForModelId } from 'actions/release' +import { CreateReleaseParams, postFile, postRelease } from 'actions/release' import { useRouter } from 'next/router' import { FormEvent, useState } from 'react' import Loading from 'src/common/Loading' @@ -19,8 +19,8 @@ export default function NewRelease() { const [semver, setSemver] = useState('') const [releaseNotes, setReleaseNotes] = useState('') const [isMinorRelease, setIsMinorRelease] = useState(false) - const [artefacts, setArtefacts] = useState([]) - const [artefactsMetadata, setArtefactsMetadata] = useState([]) + const [files, setFiles] = useState([]) + const [filesMetadata, setFilesMetadata] = useState([]) const [imageList, setImageList] = useState([]) const [errorMessage, setErrorMessage] = useState('') const [loading, setLoading] = useState(false) @@ -28,62 +28,60 @@ export default function NewRelease() { const { modelId }: { modelId?: string } = router.query const { model, isModelLoading, isModelError } = useGetModel(modelId) - const { mutateReleases } = useGetReleasesForModelId(modelId) const handleSubmit = async (event: FormEvent) => { event.preventDefault() - if (model) { - setErrorMessage('') - setLoading(true) - if (!model.card.version) { - setLoading(false) - return setErrorMessage('Please make sure your model has a schema set before drafting a release.') - } - if (isValidSemver(semver)) { - const fileIds: string[] = [] - for (const artefact of artefacts) { - const artefactMetadata = artefactsMetadata.find((metadata) => metadata.fileName === artefact.name) - const postArtefactResponse = await postFile( - artefact, - model.id, - artefact.name, - artefact.type, - artefactMetadata?.metadata, - ) - if (postArtefactResponse.ok) { - const res = await postArtefactResponse.json() - fileIds.push(res.file._id) - } else { - setLoading(false) - return setErrorMessage(await getErrorMessage(postArtefactResponse)) - } - } - - setLoading(false) - - const release: CreateReleaseParams = { - modelId: model.id, - semver, - modelCardVersion: model.card.version, - notes: releaseNotes, - minor: isMinorRelease, - fileIds: fileIds, - images: imageList, - } - - const response = await postRelease(release) - - if (!response.ok) { - const error = await getErrorMessage(response) - setLoading(false) - return setErrorMessage(error) - } - - setLoading(false) - mutateReleases() - router.push(`/beta/model/${modelId}?tab=releases`) + + if (!model) { + return setErrorMessage('Please wait for the model to finish loading before trying to make a release.') + } + + if (!model.card.version) { + return setErrorMessage('Please make sure your model has a schema set before drafting a release.') + } + + if (!isValidSemver(semver)) { + return setErrorMessage('Please set a valid semver value before drafing a release.') + } + + setErrorMessage('') + setLoading(true) + + const fileIds: string[] = [] + for (const file of files) { + const fileMetadata = filesMetadata.find((metadata) => metadata.fileName === file.name) + const postFileResponse = await postFile(file, model.id, file.name, file.type, fileMetadata?.metadata) + + if (!postFileResponse.ok) { + setErrorMessage(await getErrorMessage(postFileResponse)) + return setLoading(false) } + + const res = await postFileResponse.json() + fileIds.push(res.file._id) } + + const release: CreateReleaseParams = { + modelId: model.id, + semver, + modelCardVersion: model.card.version, + notes: releaseNotes, + minor: isMinorRelease, + fileIds: fileIds, + images: imageList, + } + + const response = await postRelease(release) + + if (!response.ok) { + setErrorMessage(await getErrorMessage(response)) + return setLoading(false) + } + + setLoading(false) + + const body = await response.json() + router.push(`/beta/model/${modelId}/release/${body.release.semver}`) } const error = MultipleErrorWrapper(`Unable to load release page`, { @@ -110,7 +108,7 @@ export default function NewRelease() { - A release takes a snapshot of the current state of the model code, artefacts and model card. Access + A release takes a snapshot of the current state of the model code, files and model card. Access requests will be able to select for any release of a model for deployment. @@ -120,15 +118,15 @@ export default function NewRelease() { semver, releaseNotes, isMinorRelease, - artefacts, + files, imageList, }} onSemverChange={(value) => setSemver(value)} onReleaseNotesChange={(value) => setReleaseNotes(value)} onMinorReleaseChange={(value) => setIsMinorRelease(value)} - onArtefactsChange={(value) => setArtefacts(value)} - artefactsMetadata={artefactsMetadata} - onArtefactsMetadataChange={(value) => setArtefactsMetadata(value)} + onFilesChange={(value) => setFiles(value)} + filesMetadata={filesMetadata} + onFilesMetadataChange={(value) => setFilesMetadata(value)} onImageListChange={(value) => setImageList(value)} /> @@ -136,7 +134,7 @@ export default function NewRelease() { variant='contained' loading={loading} type='submit' - disabled={!semver || !artefacts || !releaseNotes || !isValidSemver(semver)} + disabled={!semver || !releaseNotes || !isValidSemver(semver)} sx={{ width: 'fit-content' }} > Create Release diff --git a/frontend/src/common/MultiFileInputFileDisplay.tsx b/frontend/src/common/MultiFileInputFileDisplay.tsx index dea3dde46..1a57273aa 100644 --- a/frontend/src/common/MultiFileInputFileDisplay.tsx +++ b/frontend/src/common/MultiFileInputFileDisplay.tsx @@ -1,4 +1,5 @@ -import { Chip, Grid, TextField, Tooltip } from '@mui/material' +import { Chip, Grid, TextField, Tooltip, Typography } from '@mui/material' +import prettyBytes from 'pretty-bytes' import { ChangeEvent, useCallback, useState } from 'react' import { FileWithMetadata } from 'types/interfaces' @@ -21,17 +22,17 @@ export default function MultiFileInputFileDisplay({ file, handleDelete, onChange return ( - + handleDelete(file)} - sx={{ width: '100%', justifyContent: 'space-between' }} + sx={{ justifyContent: 'space-between' }} /> - + + + {prettyBytes(file.size)} + ) } diff --git a/frontend/src/model/beta/releases/EditableRelease.tsx b/frontend/src/model/beta/releases/EditableRelease.tsx index 373ad839a..5272fdbbd 100644 --- a/frontend/src/model/beta/releases/EditableRelease.tsx +++ b/frontend/src/model/beta/releases/EditableRelease.tsx @@ -20,8 +20,8 @@ export default function EditableRelease({ release }: EditableReleaseProps) { const [semver, setSemver] = useState(release.semver) const [releaseNotes, setReleaseNotes] = useState(release.notes) const [isMinorRelease, setIsMinorRelease] = useState(!!release.minor) - const [artefacts, setArtefacts] = useState([]) // TODO - Default to using the release artefact files (BAI-1026) - const [artefactsMetadata, setArtefactsMetadata] = useState([]) + const [files, setFiles] = useState([]) // TODO - Default to using the release files (BAI-1026) + const [filesMetadata, setFilesMetadata] = useState([]) const [imageList, setImageList] = useState(release.images) const [errorMessage, setErrorMessage] = useState('') const [isLoading, setIsLoading] = useState(false) @@ -35,8 +35,8 @@ export default function EditableRelease({ release }: EditableReleaseProps) { setSemver(release.semver) setReleaseNotes(release.notes) setIsMinorRelease(!!release.minor) - setArtefacts([]) // TODO - Reset the release artefact files (BAI-1026) - setArtefactsMetadata([]) + setFiles([]) // TODO - Reset the release files (BAI-1026) + setFilesMetadata([]) setImageList(release.images) }, [release.images, release.minor, release.notes, release.semver]) @@ -69,13 +69,13 @@ export default function EditableRelease({ release }: EditableReleaseProps) { setIsLoading(true) const fileIds: string[] = [] - for (const artefact of artefacts) { - const postArtefactResponse = await postFile(artefact, model.id, artefact.name, artefact.type) - if (postArtefactResponse.ok) { - const res = await postArtefactResponse.json() + for (const file of files) { + const postFileResponse = await postFile(file, model.id, file.name, file.type) + if (postFileResponse.ok) { + const res = await postFileResponse.json() fileIds.push(res.file._id) } else { - return setErrorMessage(await getErrorMessage(postArtefactResponse)) + return setErrorMessage(await getErrorMessage(postFileResponse)) } } @@ -127,15 +127,15 @@ export default function EditableRelease({ release }: EditableReleaseProps) { semver, releaseNotes, isMinorRelease, - artefacts, + files, imageList, }} - artefactsMetadata={artefactsMetadata} + filesMetadata={filesMetadata} onSemverChange={(value) => setSemver(value)} onReleaseNotesChange={(value) => setReleaseNotes(value)} onMinorReleaseChange={(value) => setIsMinorRelease(value)} - onArtefactsChange={(value) => setArtefacts(value)} - onArtefactsMetadataChange={(value) => setArtefactsMetadata(value)} + onFilesChange={(value) => setFiles(value)} + onFilesMetadataChange={(value) => setFilesMetadata(value)} onImageListChange={(value) => setImageList(value)} /> diff --git a/frontend/src/model/beta/releases/ReleaseDisplay.tsx b/frontend/src/model/beta/releases/ReleaseDisplay.tsx index f7d487d5e..0357da421 100644 --- a/frontend/src/model/beta/releases/ReleaseDisplay.tsx +++ b/frontend/src/model/beta/releases/ReleaseDisplay.tsx @@ -109,7 +109,7 @@ export default function ReleaseDisplay({ {release.files.length > 0 && ( <> - Artefacts + Files {release.files.map((file) => (
diff --git a/frontend/src/model/beta/releases/ReleaseForm.tsx b/frontend/src/model/beta/releases/ReleaseForm.tsx index 0722c632d..21f388589 100644 --- a/frontend/src/model/beta/releases/ReleaseForm.tsx +++ b/frontend/src/model/beta/releases/ReleaseForm.tsx @@ -14,7 +14,7 @@ type ReleaseFormData = { semver: string releaseNotes: string isMinorRelease: boolean - artefacts: File[] + files: File[] imageList: FlattenedModelImage[] } @@ -34,9 +34,9 @@ type ReleaseFormProps = { onSemverChange: (value: string) => void onReleaseNotesChange: (value: string) => void onMinorReleaseChange: (value: boolean) => void - onArtefactsChange: (value: File[]) => void - artefactsMetadata: FileWithMetadata[] - onArtefactsMetadataChange: (value: FileWithMetadata[]) => void + onFilesChange: (value: File[]) => void + filesMetadata: FileWithMetadata[] + onFilesMetadataChange: (value: FileWithMetadata[]) => void onImageListChange: (value: FlattenedModelImage[]) => void } & EditableReleaseFormProps @@ -46,9 +46,9 @@ export default function ReleaseForm({ onSemverChange, onReleaseNotesChange, onMinorReleaseChange, - onArtefactsChange, - artefactsMetadata, - onArtefactsMetadataChange, + onFilesChange, + filesMetadata, + onFilesMetadataChange, onImageListChange, editable = false, isEdit = false, @@ -132,18 +132,18 @@ export default function ReleaseForm({ )} - Artefacts + Files - {isReadOnly && formData.artefacts.length === 0 && } + {isReadOnly && formData.files.length === 0 && } Images