diff --git a/packages/lib-classifier/src/store/Classification/Classification.js b/packages/lib-classifier/src/store/Classification/Classification.js index b510646774..ed38161940 100644 --- a/packages/lib-classifier/src/store/Classification/Classification.js +++ b/packages/lib-classifier/src/store/Classification/Classification.js @@ -1,9 +1,10 @@ import cuid from 'cuid' -import { types, getSnapshot, getType } from 'mobx-state-tree' +import { types, getSnapshot, getType, addDisposer } from 'mobx-state-tree' import * as tasks from '@plugins/tasks' import AnnotationsStore from '@store/AnnotationsStore' import Resource from '@store/Resource' import ClassificationMetadata from './ClassificationMetadata' +import { autorun } from 'mobx' const annotationModels = Object.values(tasks).map(task => task.AnnotationModel) @@ -19,6 +20,17 @@ const Classification = types metadata: types.maybe(ClassificationMetadata) }) .views(self => ({ + /** + * Returns false until we start updating task annotations. + */ + get inProgress() { + let inProgress = false + self.annotations.forEach(annotation => { + inProgress ||= annotation._inProgress || annotation._choiceInProgress + }) + return inProgress + }, + toSnapshot () { let snapshot = getSnapshot(self) let annotations = [] @@ -58,5 +70,23 @@ const Classification = types newSnapshot.annotations = Object.values(snapshot.annotations) return newSnapshot }) + .actions(self => { + function _onAnnotationsChange () { + // set started at when inProgress changes from false to true + if (self.inProgress) { + self.setStartedAt() + } + } + + return ({ + afterAttach () { + addDisposer(self, autorun(_onAnnotationsChange)) + }, + + setStartedAt () { + self.metadata.startedAt = new Date().toISOString() + } + }) + }) export default types.compose('ClassificationResource', Resource, AnnotationsStore, Classification) diff --git a/packages/lib-classifier/src/store/Classification/ClassificationMetadata.js b/packages/lib-classifier/src/store/Classification/ClassificationMetadata.js index 4cbaeb6596..84e3a48535 100644 --- a/packages/lib-classifier/src/store/Classification/ClassificationMetadata.js +++ b/packages/lib-classifier/src/store/Classification/ClassificationMetadata.js @@ -36,9 +36,10 @@ const ClassificationMetadata = types.model('ClassificationMetadata', { .actions(self => ({ afterAttach() { function _onLocaleChange() { - self.update({ - userLanguage: getRoot(self)?.locale - }) + const userLanguage = getRoot(self)?.locale + if (userLanguage) { + self.update({ userLanguage }) + } } addDisposer(self, autorun(_onLocaleChange)) }, diff --git a/packages/lib-classifier/src/store/ClassificationStore.spec.js b/packages/lib-classifier/src/store/ClassificationStore.spec.js index 346714d9a8..0cdbc17d3d 100644 --- a/packages/lib-classifier/src/store/ClassificationStore.spec.js +++ b/packages/lib-classifier/src/store/ClassificationStore.spec.js @@ -65,6 +65,7 @@ describe('Model > ClassificationStore', function () { describe('when a subject advances', function () { let classifications let rootStore + beforeEach(function () { rootStore = setupStores({ dataVisAnnotating: {}, @@ -106,6 +107,62 @@ describe('Model > ClassificationStore', function () { }) describe('on complete classification', function () { + describe('submitted classifications', function () { + let classifications + let rootStore + let clock + let submittedClassification + + before(function () { + clock = sinon.useFakeTimers(new Date('2024-11-06T13:00:00Z')) + rootStore = setupStores({ + dataVisAnnotating: {}, + drawing: {}, + feedback: {}, + fieldGuide: {}, + subjectViewer: {}, + tutorials: {}, + workflowSteps: {}, + userProjectPreferences: {} + }) + + classifications = rootStore.classifications + classifications.setOnComplete(snapshot => { + submittedClassification = snapshot + }) + const taskSnapshot = Object.assign({}, singleChoiceTaskSnapshot, { taskKey: singleChoiceAnnotationSnapshot.task }) + taskSnapshot.createAnnotation = () => SingleChoiceAnnotation.create(singleChoiceAnnotationSnapshot) + const classification = classifications.active + const annotation = classification.createAnnotation(taskSnapshot) + clock.tick(1 * 60 * 60 * 1000) // wait for one hour before starting the classification. + annotation.update(0) + clock.tick(30 * 1000) // wait for 30 seconds before finishing the classification. + annotation.update(1) + classifications.completeClassification() + }) + + after(function () { + clock.restore() + }) + + it('should record the started at time', function () { + const startedAt = submittedClassification.metadata.started_at + expect(startedAt).to.equal('2024-11-06T14:00:00.000Z') + }) + + it('should record the finished at time', function () { + const finishedAt = submittedClassification.metadata.finished_at + expect(finishedAt).to.equal('2024-11-06T14:00:30.000Z') + }) + + it('should only record time spent classifying', function () { + const startedAt = submittedClassification.metadata.started_at + const finishedAt = submittedClassification.metadata.finished_at + const timeSpent = (new Date(finishedAt) - new Date(startedAt)) / 1000 + expect(timeSpent).to.equal(30) + }) + }) + describe('with invalid feedback', function () { let classifications let rootStore