Support all annotation types in add_annotations API (#7999)#8007
Support all annotation types in add_annotations API (#7999)#8007philipkukulak wants to merge 1 commit into
Conversation
bd8ee04 to
b8d4b33
Compare
b8d4b33 to
84fe165
Compare
Coverage Report for CI Build 27791495213Coverage increased (+0.02%) to 90.24%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats💛 - Coveralls |
| # matching the values reported by the annotations endpoint), mapped to the fields that must | ||
| # be present for each. Mirrors each subclass's model validations, enforced in add_annotations | ||
| # because insert_all bypasses them. | ||
| REQUIRED_ANNOTATION_FIELDS = { |
There was a problem hiding this comment.
Okay, so I think defining this is good, but really this information belongs in each model. It's a bit awkward to translate from string name to class, but since we're always deriving the expected class from the submission file anyway, I suggest the following changes:
- Modify
SubmissionFile.annotation_typeto beSubmissionFile.annotation_classand return the class itself (e.g.,TextAnnotation) rather than the string ("TextAnnotation"). - You can go from the class to its name with the
.namemethod - Define the symbol lists in each of the
Annotationsubclasses and then access them instead ofREQUIRED_ANNOTATION_FIELDS[expected_type]below - There's a use of
REQUIRED_ANNOTATION_FIELDS.key?below, but I don't think we need it. It's sufficient to check against the expect type and report the "does not match the type" error, without having a separate "Invalid annotation type" error.
| annots = annotations_params | ||
| submission_files = result.submission.submission_files.index_by(&:filename) | ||
|
|
||
| # Validation pass: insert_all bypasses model validations, so guard the input here |
There was a problem hiding this comment.
A separate pass isn't necessary here. This check can happen inside the existing loop below; the early return will ensure nothing is inserted into the database, as that only happens after the loop
| end | ||
| end | ||
|
|
||
| missing = REQUIRED_ANNOTATION_FIELDS[expected_type].select { |field| annot_params[field].blank? } |
There was a problem hiding this comment.
Let's be stricter here.
- If there are any required fields that are missing, report an error (this is what the previous
params.requirecall would have done) - If there are additional annotation fields that are "for the wrong type" (e.g. passing
line_startfor aPdfAnnotation) also report an error and return
Then, extract the annotation attributes (you can use the Ruby Hash#slice method and use that to create the hash that's appended to annotations, rather than passing all attributes.
|
|
||
| def annotations_params | ||
| params.require(annotations: [ | ||
| params.permit(annotations: [ |
There was a problem hiding this comment.
I'd like to keep the require, which was strict with the param requirement. I assume you changed this to allow the inner hashes to not have all of the attributes.
I think you can achieve both the strict :annotations key and the permissive inner structure by using the new expect method
| annotation_type = submission_file.annotation_type | ||
| @annotation = result.annotations.create!( | ||
| type: annotation_type, | ||
| **type_specific_annotation_attributes(annotation_type), |
There was a problem hiding this comment.
Right, so this is the part where I'd like the logic to be the same for both controller methods.
- Get the list of symbols from a model method
- Call
Hash#sliceto get the values fromparams(no need to define a separate method)
Proposed Changes
The API endpoint
POST add_annotationsnow supports all annotation types. Co-authored with Claude. Closes #7999.In achieving this, it was found that there was no validation of annotation type to file type for which we are seeking to annotate. This means a request to apply a
PdfAnnotationto a.pyfile would succeed, which would break rendering in the UI. To resolve this, some pre-existing type derivation logic was lifted out of the UIAnnotationsControllerand is now being used in both the UI and API controllers; the API also uses it as a part of the validation chain.As a side effect of this change,
add_existing_annotation's specs were lacking coverage due to a mix in record creation between annotation types:HtmlAnnotationusedcreate!, and all other annotations usedcreate. This is now consolidated. There was no coverage of the failure path for this record creation, so this was added here for good measure.Another small bug that was fixed is that prior to this change,
POSTing aTextAnnotationto a non-text file could create an annotation that would never be displayed on that file. Users will now see a422response if they try to do this.The API endpoint itself now either accepts an explicit annotation type, or derives it implicitly. In the former case, it expects a match between the annotation type and the file being annotated. In the latter, it simply reconciles it from the file being annotated.
Associated documentation repository pull request (if applicable)
MarkUsProject/Wiki#267
Type of Change
Checklist
Before opening your pull request:
After opening your pull request:
Questions and Comments
Here's a summary of the smoke tests for the changes to the endpoint:
Smoke test —
add_annotationsAPILocal server (
/csc108), authenticated instructor API key. Seeded one file per type on aversion-used submission:
hello.py,nemo.jpg,possimus.pdf,ipsa.ipynb. Each resultconfirmed by reading back through
GET …/groups/:id/annotations. Thetypefield uses thefull STI class names (
TextAnnotation/ImageAnnotation/PdfAnnotation/HtmlAnnotation).typesentTextAnnotationhello.pyTextAnnotationTextAnnotation, line/col persistedImageAnnotationnemo.jpgImageAnnotationImageAnnotation, x/y persistedPdfAnnotationpossimus.pdfPdfAnnotationPdfAnnotation, page 2HtmlAnnotationipsa.ipynbHtmlAnnotationHtmlAnnotation, nodes persistedpossimus.pdfPdfAnnotationPdfAnnotation, page 3TextAnnotationpossimus.pdfAnnotation type 'TextAnnotation' does not match the type of file 'possimus.pdf'bogushello.pyInvalid annotation type: boguspossimus.pdf(nopage)Missing required fields for PdfAnnotation: pageTextAnnotationnope.txtSubmission file not found: nope.txtTextAnnotation,TextAnnotationhello.py,possimus.pdfNet: all 5 valid writes persisted with the correct STI type on the correct file; all 4
bad-input paths failed closed with specific 422 messages and left the DB unchanged; batch
validation is atomic (rejects the whole request before any insert).