Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added backend implementation for model templating #1419

Merged
merged 9 commits into from
Aug 28, 2024
28 changes: 23 additions & 5 deletions backend/src/services/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { isValidatorResultError } from '../types/ValidatorResultError.js'
import { toEntity } from '../utils/entity.js'
import { BadReq, Forbidden, NotFound } from '../utils/error.js'
import { convertStringToId } from '../utils/id.js'
import { NotImplemented } from '../utils/result.js'
import { findSchemaById } from './schema.js'

export function checkModelRestriction(model: ModelInterface) {
Expand Down Expand Up @@ -352,9 +351,28 @@ export async function createModelCardFromSchema(
}

export async function createModelCardFromTemplate(
_user: UserInterface,
_modelId: string,
_schemaId: string,
user: UserInterface,
modelId: string,
templateId: string,
): Promise<ModelCardRevisionDoc> {
throw NotImplemented({}, 'This feature is not yet implemented')
if (modelId === templateId) {
throw BadReq('The model and template ID must be different', { modelId, templateId })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a unit test that covers this

}
const model = await getModelById(user, modelId)
if (model.card?.schemaId) {
throw BadReq('This model already has a model card.', { modelId })
}
checkModelRestriction(model)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to checkModelRestriction for the model being used as a template also?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. All it does is check to see if it's "mirrored" which is basically read only. As we're not editing the template in any way, I think it's fine to use it if it's read only.

const template = await getModelById(user, templateId)
// Check to make sure user can access the template. We already check for the model auth later on in _setModelCard
const templateAuth = await authorisation.model(user, template, ModelAction.View)
if (!templateAuth.success) {
throw Forbidden(templateAuth.info, { userDn: user.dn, templateId })
}

if (!template.card?.schemaId) {
throw BadReq('The template model is missing a model card', { modelId, templateId })
}
const revision = await _setModelCard(user, modelId, template.card.schemaId, 1, template.card.metadata)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything to stop a user from calling this with a modelId that references a model that already has model card versions? If this were to happen then this line would set the first version to be the model card of the template and all later versions would be from the pre-existing model card.

return revision
}
61 changes: 61 additions & 0 deletions backend/test/services/model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
canUserActionModelById,
createModel,
createModelCardFromSchema,
createModelCardFromTemplate,
getModelById,
getModelCardRevision,
searchModels,
Expand Down Expand Up @@ -311,4 +312,64 @@ describe('services > model', () => {
)
expect(modelMocks.save).not.toBeCalled()
})

test('crateModelCardFromTemplate > can create a model using a template', async () => {
const testModel = {
name: 'test model',
settings: {
mirror: {},
},
}
const testTemplate = {
name: 'test template',
settings: {
mirror: {},
},
card: {
schemaId: 'test-schema',
version: '1',
createdBy: 'User',
metadata: {
overview: {
questionOne: 'test',
},
},
},
}
modelMocks.findOne.mockResolvedValueOnce(testModel)
modelMocks.findOne.mockResolvedValueOnce(testTemplate)
await createModelCardFromTemplate({} as any, 'testModel', 'testTemplateModel')
expect(modelCardRevisionModel.save).toBeCalled()
expect(modelMocks.updateOne).toBeCalled()
})

test('createModelCardFromTemplate > requesting to use a template without a model card will throw an error', async () => {
const testModel = {
name: 'test model',
settings: {
mirror: {},
},
}
modelMocks.findOne.mockResolvedValue(testModel)
expect(() => createModelCardFromTemplate({} as any, 'testModel', 'testTemplateModel')).rejects.toThrowError(
/^The template model is missing a model card/,
)
})

test('createModelCardFromTemplate > throw bad request when supplying the same template and model id', async () => {
expect(() => createModelCardFromTemplate({} as any, 'testModel', 'testModel')).rejects.toThrowError(
'The model and template ID must be different',
)
})

test('createModelCardFromTemplate > throw forbidden when user does not have access to template', async () => {
vi.mocked(authorisation.model).mockResolvedValue({
info: 'User does not have access to model',
success: false,
id: '',
})
expect(() => createModelCardFromTemplate({} as any, 'testModel', 'testTemplateModel')).rejects.toThrowError(
'User does not have access to model',
)
})
})
2 changes: 1 addition & 1 deletion frontend/src/entry/overview/TemplatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function TemplatePage({ entry }: TemplatePageProps) {
variant='contained'
href={`/${entry.kind}/${entry.id}/template`}
LinkComponent={Link}
disabled
disabled={!!entry.settings.mirror?.sourceModelId}
>
Create
</Button>
Expand Down
Loading