Skip to content

Commit

Permalink
Merge pull request #1691 from gchq/bugfix/BAI-1530-error-handling-for…
Browse files Browse the repository at this point in the history
…-model-exports

restructured the model mirror page and added a backend check for exis…
  • Loading branch information
ARADDCC002 authored Jan 8, 2025
2 parents b763203 + 7927f02 commit d8f045c
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 58 deletions.
3 changes: 3 additions & 0 deletions backend/src/services/mirroredModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export async function exportModel(
if (!model.settings.mirror.destinationModelId) {
throw BadReq(`The 'Destination Model ID' has not been set on this model.`)
}
if (!model.card || !model.card.schemaId) {
throw BadReq('You must select a schema for your model before you can start the export process.')
}
const mirroredModelId = model.settings.mirror.destinationModelId
const auth = await authorisation.model(user, model, ModelAction.Update)
if (!auth.success) {
Expand Down
3 changes: 2 additions & 1 deletion backend/test/services/mirroredModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ vi.mock('../../src/services/log.js', async () => ({
}))

const modelMocks = vi.hoisted(() => ({
getModelById: vi.fn(() => ({ settings: { mirror: { destinationModelId: '123' } } })),
getModelById: vi.fn(() => ({ settings: { mirror: { destinationModelId: '123' } }, card: { schemaId: 'test' } })),
getModelCardRevisions: vi.fn(() => [{ toJSON: vi.fn(), version: 123 }]),
setLatestImportedModelCard: vi.fn(),
saveImportedModelCard: vi.fn(),
Expand Down Expand Up @@ -237,6 +237,7 @@ describe('services > mirroredModel', () => {
test('exportModel > missing mirrored model ID', async () => {
modelMocks.getModelById.mockReturnValueOnce({
settings: { mirror: { destinationModelId: '' } },
card: { schemaId: 'test' },
})
const response = exportModel({} as UserInterface, 'modelId', true, ['1.2.3'])
await expect(response).rejects.toThrowError(/^The 'Destination Model ID' has not been set on this model./)
Expand Down
44 changes: 21 additions & 23 deletions frontend/src/entry/model/mirroredModels/ExportModelAgreement.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LoadingButton } from '@mui/lab'
import { Box, Button, Card, Checkbox, Container, FormControlLabel, Stack, Typography } from '@mui/material'
import { Box, Button, Checkbox, FormControlLabel, Stack, Typography } from '@mui/material'
import { postModelExportToS3 } from 'actions/model'
import { ChangeEvent, FormEvent, useState } from 'react'
import Restricted from 'src/common/Restricted'
Expand Down Expand Up @@ -46,27 +46,25 @@ export default function ExportModelAgreement({ modelId }: ExportModelAgreementPr
}

return (
<Container maxWidth='md'>
<Card sx={{ mx: 'auto', my: 4, p: 4 }}>
<Typography variant='h6' component='h1' color='primary' align='center'>
Model Export Agreement
</Typography>
<Box component='form' onSubmit={handleSubmit}>
<Stack spacing={2} alignItems='start' justifyContent='start'>
<ModelExportAgreementText />
<FormControlLabel
control={<Checkbox checked={checked} onChange={handleChecked} />}
label='I agree to the terms and conditions of this model export agreement'
/>
<Restricted action='exportMirroredModel' fallback={<Button disabled>Submit</Button>}>
<LoadingButton variant='contained' loading={loading} disabled={!checked} type='submit'>
Submit
</LoadingButton>
</Restricted>
<MessageAlert message={errorMessage} severity='error' />
</Stack>
</Box>
</Card>
</Container>
<>
<Typography variant='h6' component='h1' color='primary'>
Request a Model Export
</Typography>
<Box component='form' onSubmit={handleSubmit}>
<Stack spacing={2} alignItems='start' justifyContent='start'>
<ModelExportAgreementText />
<FormControlLabel
control={<Checkbox checked={checked} onChange={handleChecked} />}
label='I agree to the terms and conditions of this model export agreement'
/>
<Restricted action='exportMirroredModel' fallback={<Button disabled>Submit</Button>}>
<LoadingButton variant='contained' loading={loading} disabled={!checked} type='submit'>
Submit
</LoadingButton>
</Restricted>
<MessageAlert message={errorMessage} severity='error' />
</Stack>
</Box>
</>
)
}
84 changes: 53 additions & 31 deletions frontend/src/entry/model/mirroredModels/ExportSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { LoadingButton } from '@mui/lab'
import { Accordion, AccordionDetails, AccordionSummary, Box, Stack, TextField, Typography } from '@mui/material'
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Card,
Container,
Divider,
Stack,
TextField,
Typography,
} from '@mui/material'
import { patchModel } from 'actions/model'
import { ChangeEvent, FormEvent, useState } from 'react'
import LabelledInput from 'src/common/LabelledInput'
Expand All @@ -19,6 +30,7 @@ export default function ExportSettings({ model }: ExportSettingsProps) {
const [destinationModelId, setDestinationModelId] = useState(model.settings.mirror?.destinationModelId || '')
const [loading, setLoading] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const [isSettingsOpen, setIsSettingsOpen] = useState(destinationModelId === undefined || destinationModelId === '')

// TODO - Add the ability to filter releases needed for export (This functionality is not available on the backend)
// const [selectedReleases, setSelectedReleases] = useState<ReleaseInterface[]>([])
Expand Down Expand Up @@ -55,25 +67,31 @@ export default function ExportSettings({ model }: ExportSettingsProps) {

return (
<>
<ExportModelAgreement modelId={model.id} />
<Accordion sx={{ borderTop: 'none' }} slotProps={{ heading: { component: 'h3' } }}>
<AccordionSummary sx={{ pl: 0 }} expandIcon={<ExpandMoreIcon />}>
<Typography sx={{ width: '100%' }} color='primary' variant='h6' component='div'>
Export Settings
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container maxWidth='md'>
<Card sx={{ mx: 'auto', my: 4, p: 4 }}>
<Box component='form' onSubmit={handleSave}>
<Stack spacing={2} alignItems='flex-start'>
<LabelledInput label={'Destination Model ID'} htmlFor={'destination-model-id'} required>
<TextField
id='destination-model-id'
value={destinationModelId}
onChange={handleDestinationModelId}
size='small'
/>
</LabelledInput>
{/*TODO - Add the ability to filter releases needed for export (This functionality is not available on the backend)
<Stack spacing={3} divider={<Divider flexItem />}>
<Accordion
expanded={isSettingsOpen}
onChange={() => setIsSettingsOpen(isSettingsOpen ? false : true)}
slotProps={{ heading: { component: 'h3' } }}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 0 }}>
<Typography sx={{ width: '100%' }} color='primary' variant='h6' component='div'>
Model Export Settings
</Typography>
</AccordionSummary>
<AccordionDetails>
<Stack spacing={2}>
<LabelledInput label={'Destination Model ID'} htmlFor={'destination-model-id'} required>
<TextField
id='destination-model-id'
value={destinationModelId}
onChange={handleDestinationModelId}
size='small'
/>
</LabelledInput>
{/*TODO - Add the ability to filter releases needed for export (This functionality is not available on the backend)
<ReleaseSelector
model={model}
selectedReleases={selectedReleases}
Expand All @@ -82,20 +100,24 @@ export default function ExportSettings({ model }: ExportSettingsProps) {
requiredRolesText={requiredRolesText}
/>
*/}
<LoadingButton
sx={{ width: 'fit-content' }}
variant='contained'
data-test='createAccessRequestButton'
loading={loading}
type='submit'
>
Save
</LoadingButton>
<MessageAlert message={errorMessage} severity='error' />
<LoadingButton
sx={{ width: 'fit-content' }}
variant='contained'
data-test='createAccessRequestButton'
loading={loading}
type='submit'
>
Save
</LoadingButton>
<MessageAlert message={errorMessage} severity='error' />
</Stack>
</AccordionDetails>
</Accordion>
<ExportModelAgreement modelId={model.id} />
</Stack>
</Box>
</AccordionDetails>
</Accordion>
</Card>
</Container>
</>
)
}
5 changes: 2 additions & 3 deletions frontend/src/entry/model/reviews/ApprovalsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { EmotionJSX } from '@emotion/react/types/jsx-namespace'
import { Done } from '@mui/icons-material'
import { Stack, Tooltip, Typography } from '@mui/material'
import { useGetModelRoles } from 'actions/model'
import { useMemo } from 'react'
import { ReactElement, useMemo } from 'react'
import Loading from 'src/common/Loading'
import MessageAlert from 'src/MessageAlert'
import { ResponseInterface } from 'types/types'
Expand All @@ -22,7 +21,7 @@ export default function ApprovalsDisplay({ modelId, acceptedReviewResponses }: A

const roleApprovals = useMemo(
() =>
dynamicRoles.reduce<EmotionJSX.Element[]>((approvals, dynamicRole) => {
dynamicRoles.reduce<ReactElement[]>((approvals, dynamicRole) => {
const hasRoleApproved = !!acceptedReviewResponses.find(
(acceptedResponse) => acceptedResponse.role === dynamicRole.id,
)
Expand Down

0 comments on commit d8f045c

Please sign in to comment.