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

2328 annotation page #2435

Merged
merged 7 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion cypress/e2e/phase_4/annotation.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ describe('Annotation', () => {
'Submission date',
]);
});
let firstUri;

cy.findAllByRole('cell').then((cells) => {
const firstUri = cells[0].textContent;
firstUri = cells[0].textContent;
const secondUri = cells[9].textContent;

expect(firstUri).to.match(/uid:\//);
Expand Down Expand Up @@ -107,6 +108,24 @@ describe('Annotation', () => {
new Date().toLocaleDateString(),
]);
});

cy.findByText('RoboCop').click();

cy.findByText('Annotation: RoboCop').should('be.visible');

cy.findByLabelText('Field Id').should('have.text', '[bZE+]');
cy.findByLabelText('Field label').should('have.text', 'rating');
cy.findByLabelText('Field Internal Name').should('have.text', '');

cy.findByLabelText('Contributor Comment:').should(
'have.text',
'This is another comment',
);

cy.findByLabelText('Submission date').should(
'have.text',
new Date().toLocaleDateString(),
);
});

it('should hide annotation button when field does not support annotations', () => {
Expand Down
37 changes: 37 additions & 0 deletions src/api/controller/api/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,47 @@ export async function getAnnotations(ctx) {
})),
};
}
/**
* @param {Koa.Context} ctx
*/
export async function getAnnotation(ctx, id) {
const annotation = await ctx.annotation.findOneById(id);

if (!annotation) {
ctx.response.status = 404;

return;
}

const titleField = await ctx.field.findTitle();

const resource = annotation.resourceUri
? await ctx.publishedDataset.findByUri(annotation.resourceUri)
: null;

const field = annotation.fieldId
? await ctx.field.findOneById(annotation.fieldId)
: null;

ctx.response.status = 200;
ctx.response.body = {
...annotation,
field,
resource: resource
? {
title: resource.versions[resource.versions.length - 1][
titleField.name
],
uri: resource.uri,
}
: null,
};
}

const app = new Koa();

app.use(route.get('/', getAnnotations));
app.use(route.get('/:id', getAnnotation));
app.use(koaBodyParser());
app.use(route.post('/', createAnnotation));

Expand Down
176 changes: 175 additions & 1 deletion src/api/controller/api/annotation.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createAnnotation, getAnnotations } from './annotation';
import { createAnnotation, getAnnotation, getAnnotations } from './annotation';
import { MongoClient } from 'mongodb';
import createAnnotationModel from '../../models/annotation';
import createFieldModel from '../../models/field';
Expand Down Expand Up @@ -751,4 +751,178 @@ describe('annotation', () => {
});
});
});

describe('/annotations/:id', () => {
let annotation;
let field;
beforeEach(async () => {
await fieldModel.create({ position: 1, overview: 1 }, 'tItL3');

field = await fieldModel.create(
{
position: 2,
label: 'Annotated field',
},
'GvaF',
);

await publishedDatasetModel.create({
uri: 'uid:/1234',
tItL3: 'resource title',
});

annotation = await annotationModel.create({
resourceUri: 'uid:/1234',
itemPath: [],
fieldId: field._id,
authorName: 'Developer',
authorEmail: '[email protected]',
comment: 'This is a comment',
status: 'in_progress',
internal_comment: null,
createdAt: new Date('03-01-2025'),
updatedAt: new Date('03-01-2025'),
});
});

it('should return target annotation with resource and field', async () => {
const ctx = {
annotation: annotationModel,
field: fieldModel,
publishedDataset: publishedDatasetModel,
response: {},
};

await getAnnotation(ctx, annotation._id);

expect(ctx.response).toStrictEqual({
body: {
_id: annotation._id,
authorEmail: '[email protected]',
authorName: 'Developer',
comment: 'This is a comment',
createdAt: expect.any(Date),
field: {
_id: field._id,
label: 'Annotated field',
name: 'GvaF',
position: 2,
},
fieldId: field._id,
internal_comment: null,
itemPath: [],
resource: {
title: 'resource title',
uri: 'uid:/1234',
},
resourceUri: 'uid:/1234',
status: 'to_review',
updatedAt: expect.any(Date),
},
status: 200,
});
});

it('should return target annotation with field at null when it does not exists', async () => {
annotation = await annotationModel.create({
resourceUri: 'uid:/1234',
itemPath: [],
fieldId: '404',
authorName: 'Developer',
authorEmail: '[email protected]',
comment: 'This is a comment',
status: 'in_progress',
internal_comment: null,
createdAt: new Date('03-01-2025'),
updatedAt: new Date('03-01-2025'),
});
const ctx = {
annotation: annotationModel,
field: fieldModel,
publishedDataset: publishedDatasetModel,
response: {},
};

await getAnnotation(ctx, annotation._id);

expect(ctx.response).toStrictEqual({
body: {
_id: annotation._id,
authorEmail: '[email protected]',
authorName: 'Developer',
comment: 'This is a comment',
createdAt: expect.any(Date),
field: null,
fieldId: '404',
internal_comment: null,
itemPath: [],
resource: {
title: 'resource title',
uri: 'uid:/1234',
},
resourceUri: 'uid:/1234',
status: 'to_review',
updatedAt: expect.any(Date),
},
status: 200,
});
});

it('should return target annotation with resource at null when it does not exists', async () => {
annotation = await annotationModel.create({
resourceUri: 'uid:/404',
itemPath: [],
fieldId: field._id,
authorName: 'Developer',
authorEmail: '[email protected]',
comment: 'This is a comment',
status: 'in_progress',
internal_comment: null,
createdAt: new Date('03-01-2025'),
updatedAt: new Date('03-01-2025'),
});
const ctx = {
annotation: annotationModel,
field: fieldModel,
publishedDataset: publishedDatasetModel,
response: {},
};

await getAnnotation(ctx, annotation._id);

expect(ctx.response).toStrictEqual({
body: {
_id: annotation._id,
authorEmail: '[email protected]',
authorName: 'Developer',
comment: 'This is a comment',
createdAt: expect.any(Date),
field,
fieldId: field._id,
internal_comment: null,
itemPath: [],
resource: null,
resourceUri: 'uid:/404',
status: 'to_review',
updatedAt: expect.any(Date),
},
status: 200,
});
});

it('should return 404 status when target annotation does not exists', async () => {
const ctx = {
annotation: annotationModel,
field: fieldModel,
publishedDataset: publishedDatasetModel,
response: {},
};

await getAnnotation(ctx, '404');

expect(ctx.response).toStrictEqual({
status: 404,
});
});
});
});
11 changes: 9 additions & 2 deletions src/api/models/annotation.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ObjectId } from 'mongodb';
import { getCreatedCollection } from './utils';

export default async (db) => {
const annotationCollection = await getCreatedCollection(db, 'annotation');

async function create(annotationPayload) {
console.log('annotationPayload', annotationPayload);
const now = new Date();
const { insertedId } = await annotationCollection.insertOne({
...annotationPayload,
Expand Down Expand Up @@ -40,5 +40,12 @@ export default async (db) => {
return annotationCollection.countDocuments(query);
}

return { create, findLimitFromSkip, count };
async function findOneById(id) {
if (!ObjectId.isValid(id)) {
return null;
}
return annotationCollection.findOne({ _id: new ObjectId(id) });
}

return { create, findLimitFromSkip, count, findOneById };
};
31 changes: 31 additions & 0 deletions src/api/models/annotation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,35 @@ describe('annotation', () => {
).toBe(1);
});
});

describe('findOneById', () => {
it('should return target annotation', async () => {
const annotation = await annotationModel.create({
comment: 'target annotation',
});
await annotationModel.create({
comment: 'another annotation',
});

expect(
await annotationModel.findOneById(annotation._id),
).toStrictEqual(annotation);
});
it('should return null if target annotation does not exists', async () => {
await annotationModel.create({
comment: 'another annotation',
});

expect(
await annotationModel.findOneById('404404404404404404404404'),
).toBeNull();
});
it('should return null if id is not a valid ObjectId format', async () => {
await annotationModel.create({
comment: 'another annotation',
});

expect(await annotationModel.findOneById('404')).toBeNull();
});
});
});
8 changes: 6 additions & 2 deletions src/api/models/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,12 @@ export default async (db) => {
return searchableFields.map(({ name }) => name);
};

collection.findOneById = (id) =>
collection.findOne({ _id: new ObjectId(id) });
collection.findOneById = (id) => {
if (!ObjectId.isValid(id)) {
return null;
}
return collection.findOne({ _id: new ObjectId(id) });
};

collection.findManyByIds = async (ids) => {
const fields = await collection
Expand Down
4 changes: 4 additions & 0 deletions src/app/custom/translations.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"annotation_created_at" "Submission date" "Date de soumission"
"annotation_field_name" "Field Id" "Id du champ"
"annotation_field_label" "Field label" "Etiquette du champ"
"annotation_field_section" "Field:" "Champ :"
"annotation_comment_section" "Contributor Comment:" "Commentaire du contributeur :"
"annotation_complementary_infos_section" "Complementary Informations:" "Informations complémentaires :"
"annotation_field_internal_scopes" "Field Icons" "Icônes du champ"
"annotation_field_internal_name" "Field Internal Name" "Nom interne du champ"
"annotation_resource_link" "Open the resource" "Ouvrir la resource"
Expand All @@ -1222,3 +1225,4 @@
"annotation.createdAt" "Submission date" "Date de soumission"
"annotation.authorName" "Name" "Nom"
"annotation.authorEmail" "Email address (to be informed)" "Adresse e-mail (pour être informé)"
"annotation_header" "Annotation:" "Annotation :"
Loading
Loading