diff --git a/kolibri/plugins/coach/assets/src/modules/classSummary/__test__/dataHelpers.spec.js b/kolibri/plugins/coach/assets/src/modules/classSummary/__test__/dataHelpers.spec.js new file mode 100644 index 00000000000..80cd4f6e264 --- /dev/null +++ b/kolibri/plugins/coach/assets/src/modules/classSummary/__test__/dataHelpers.spec.js @@ -0,0 +1,109 @@ +import Vuex from 'vuex'; +import summaryModule from '../index'; + +import sampleState from './sampleState2'; + +describe('coach summary data helpers', () => { + summaryModule.state = sampleState; + const store = new Vuex.Store(summaryModule); + describe('getGroupNames', () => { + it('returns the names of groups given a list of group IDs', () => { + expect(store.getters.getGroupNames(['group_id_1', 'group_id_3'])).toEqual([ + 'Group 1', + 'Group 3', + ]); + }); + }); + describe('getGroupNamesForLearner', () => { + it('returns the names of groups given a learner ID', () => { + expect(store.getters.getGroupNamesForLearner('learner_id_5')).toEqual(['Group 2', 'Group 3']); + expect(store.getters.getGroupNamesForLearner('learner_id_4')).toEqual(['Group 4']); + }); + }); + describe('getLearnersForGroups', () => { + it('returns everyone in the class when empty', () => { + expect(store.getters.getLearnersForGroups([])).toEqual( + store.getters.learners.map(learner => learner.id) + ); + }); + it('returns the learner IDs of everyone in the groups with no duplicates', () => { + const output = store.getters.getLearnersForGroups(['group_id_2', 'group_id_3']); + output.sort(); + expect(output).toEqual([ + 'learner_id_1', + 'learner_id_11', + 'learner_id_2', + 'learner_id_5', + 'learner_id_6', + 'learner_id_7', + 'learner_id_8', + 'learner_id_9', + ]); + }); + }); + describe('getContentStatusForLearner', () => { + it('returns a recorded status string given a content ID and learner ID', () => { + expect(store.getters.getContentStatusForLearner('content_Q', 'learner_id_1')).toEqual( + 'completed' + ); + expect(store.getters.getContentStatusForLearner('content_Q', 'learner_id_2')).toEqual( + 'started' + ); + expect(store.getters.getContentStatusForLearner('content_Q', 'learner_id_3')).toEqual( + 'not_started' + ); + expect(store.getters.getContentStatusForLearner('content_Q', 'learner_id_4')).toEqual( + 'help_needed' + ); + }); + it('returns "not_started" when passed unknown information', () => { + expect(store.getters.getContentStatusForLearner('content_Q', 'learner_id_7')).toEqual( + 'not_started' + ); + expect(store.getters.getContentStatusForLearner('XXXX', 'learner_id_5')).toEqual( + 'not_started' + ); + expect(store.getters.getContentStatusForLearner('learner_id_5', 'XXXX')).toEqual( + 'not_started' + ); + expect(store.getters.getContentStatusForLearner('XXXX', 'XXXX')).toEqual('not_started'); + }); + }); + describe('getContentStatusCounts', () => { + it('returns total statuses given a content item and a list of learners', () => { + const output = store.getters.getContentStatusCounts('content_Q', [ + 'learner_id_1', + 'learner_id_2', + 'learner_id_3', + 'learner_id_4', + 'learner_id_5', + 'learner_id_6', + 'learner_id_7', + 'learner_id_8', + 'learner_id_9', + ]); + expect(output).toEqual({ + completed: 2, + started: 2, + notStarted: 4, + helpNeeded: 1, + }); + }); + }); + describe('getContentAvgTimeSpent', () => { + it('returns average time a list of learners has worked on an item, omitting not started', () => { + const output = store.getters.getContentAvgTimeSpent('content_Q', [ + 'learner_id_1', + 'learner_id_2', + 'learner_id_3', + 'learner_id_4', + 'learner_id_5', + 'learner_id_6', + 'learner_id_7', + 'learner_id_8', + 'learner_id_9', + ]); + expect(output).toBeCloseTo(7.3, 5); + }); + }); +}); diff --git a/kolibri/plugins/coach/assets/src/modules/classSummary/__test__/sampleState2.js b/kolibri/plugins/coach/assets/src/modules/classSummary/__test__/sampleState2.js new file mode 100644 index 00000000000..39ee48312d1 --- /dev/null +++ b/kolibri/plugins/coach/assets/src/modules/classSummary/__test__/sampleState2.js @@ -0,0 +1,854 @@ +export default { + id: 'class_id', + name: 'class_name', + coachMap: { + coach_id_1: { + id: 'coach_id_1', + name: 'Coach Name 1', + username: 'CoachUsername1', + }, + coach_id_2: { + id: 'coach_id_2', + name: 'Another Coach', + username: 'AnotherCoach', + }, + }, + learnerMap: { + learner_id_1: { + id: 'learner_id_1', + name: 'A', + username: 'A', + }, + learner_id_2: { + id: 'learner_id_2', + name: 'B', + username: 'B', + }, + learner_id_3: { + id: 'learner_id_3', + name: 'C', + username: 'C', + }, + learner_id_4: { + id: 'learner_id_4', + name: 'D', + username: 'D', + }, + learner_id_5: { + id: 'learner_id_5', + name: 'E', + username: 'E', + }, + learner_id_6: { + id: 'learner_id_6', + name: 'F', + username: 'F', + }, + learner_id_7: { + id: 'learner_id_7', + name: 'G', + username: 'G', + }, + learner_id_8: { + id: 'learner_id_8', + name: 'H', + username: 'H', + }, + learner_id_9: { + id: 'learner_id_9', + name: 'I', + username: 'I', + }, + learner_id_10: { + id: 'learner_id_10', + name: 'J', + username: 'J', + }, + learner_id_11: { + id: 'learner_id_11', + name: 'K', + username: 'K', + }, + }, + groupMap: { + group_id_1: { + id: 'group_id_1', + name: 'Group 1', + member_ids: [], + }, + group_id_2: { + id: 'group_id_2', + name: 'Group 2', + member_ids: ['learner_id_5', 'learner_id_2', 'learner_id_8'], + }, + group_id_3: { + id: 'group_id_3', + name: 'Group 3', + member_ids: [ + 'learner_id_5', + 'learner_id_6', + 'learner_id_9', + 'learner_id_7', + 'learner_id_11', + 'learner_id_1', + ], + }, + group_id_4: { + id: 'group_id_4', + name: 'Group 4', + member_ids: ['learner_id_4', 'learner_id_10', 'learner_id_3'], + }, + }, + examMap: { + exam_id_1: { + id: 'exam_id_1', + title: 'exam - B', + active: true, + question_sources: [ + { exercise_id: 'node_13', question_id: '1' }, + { exercise_id: 'node_13', question_id: '2' }, + { exercise_id: 'node_13', question_id: '3' }, + ], + groups: ['group_id_3'], + }, + exam_id_2: { + id: 'exam_id_2', + title: 'exam - A', + active: true, + question_sources: [ + { exercise_id: 'node_26', question_id: 'A' }, + { exercise_id: 'node_26', question_id: 'B' }, + ], + groups: ['group_id_2'], + }, + exam_id_3: { + id: 'exam_id_3', + title: 'exam - C', + active: true, + question_sources: [ + { exercise_id: 'node_16', question_id: '10' }, + { exercise_id: 'node_14', question_id: '11' }, + { exercise_id: 'node_15', question_id: '12' }, + ], + groups: ['group_id_4'], + }, + exam_id_4: { + id: 'exam_id_4', + title: 'Exam - D', + active: true, + question_sources: [ + { exercise_id: 'node_21', question_id: 'x' }, + { exercise_id: 'node_21', question_id: 'y' }, + { exercise_id: 'node_21', question_id: 'z' }, + ], + groups: [], + }, + }, + examLearnerStatusMap: { + exam_id_1: { + learner_id_1: { + exam_id: 'exam_id_1', + learner_id: 'learner_id_1', + status: 'completed', + last_activity: new Date('2019-01-24T23:14:52.831Z'), + num_correct: 1, + score: 0.3333333333333333, + }, + learner_id_11: { + exam_id: 'exam_id_1', + learner_id: 'learner_id_11', + status: 'completed', + last_activity: new Date('2019-01-24T23:04:26.540Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + }, + exam_id_2: { + learner_id_2: { + exam_id: 'exam_id_2', + learner_id: 'learner_id_2', + status: 'started', + last_activity: new Date('2019-01-24T23:00:08.771Z'), + num_correct: 2, + score: 1, + }, + learner_id_5: { + exam_id: 'exam_id_2', + learner_id: 'learner_id_5', + status: 'completed', + last_activity: new Date('2019-01-24T23:12:37.858Z'), + num_correct: 2, + score: 1, + }, + learner_id_8: { + exam_id: 'exam_id_2', + learner_id: 'learner_id_8', + status: 'completed', + last_activity: new Date('2019-01-24T23:11:49.782Z'), + num_correct: 1, + score: 0.5, + }, + }, + exam_id_4: { + learner_id_4: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_4', + status: 'completed', + last_activity: new Date('2019-01-24T22:34:39.693Z'), + num_correct: 0, + score: 0, + }, + learner_id_3: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_3', + status: 'completed', + last_activity: new Date('2019-01-24T22:31:56.584Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_1: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_1', + status: 'completed', + last_activity: new Date('2019-01-24T22:32:25.970Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_10: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_10', + status: 'completed', + last_activity: new Date('2019-01-24T22:33:44.351Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_2: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_2', + status: 'completed', + last_activity: new Date('2019-01-24T22:45:13.097Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_6: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_6', + status: 'completed', + last_activity: new Date('2019-01-24T22:35:15.068Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_5: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_5', + status: 'completed', + last_activity: new Date('2019-01-24T22:32:07.956Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_8: { + exam_id: 'exam_id_4', + learner_id: 'learner_id_8', + status: 'completed', + last_activity: new Date('2019-01-24T22:34:10.329Z'), + num_correct: 1, + score: 0.3333333333333333, + }, + }, + exam_id_3: { + learner_id_10: { + exam_id: 'exam_id_3', + learner_id: 'learner_id_10', + status: 'completed', + last_activity: new Date('2019-01-24T23:15:40.569Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_3: { + exam_id: 'exam_id_3', + learner_id: 'learner_id_3', + status: 'completed', + last_activity: new Date('2019-01-24T23:12:54.385Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + learner_id_4: { + exam_id: 'exam_id_3', + learner_id: 'learner_id_4', + status: 'completed', + last_activity: new Date('2019-01-24T23:11:46.956Z'), + num_correct: 2, + score: 0.6666666666666666, + }, + }, + }, + contentMap: { + content_A: { + node_id: 'node_1', + content_id: 'content_A', + title: 'aesop_fables', + kind: 'document', + }, + content_B: { + node_id: 'node_2', + content_id: 'content_B', + title: 'arthur-conan-doyle_the-adventures-of-sherlock-holmes', + kind: 'document', + }, + content_C: { + node_id: 'node_3', + content_id: 'content_C', + title: 'arthur-conan-doyle_the-hound-of-the-baskervilles', + kind: 'document', + }, + content_D: { + node_id: 'node_4', + content_id: 'content_D', + title: 'Mata and I', + kind: 'document', + }, + content_E: { + node_id: 'node_5', + content_id: 'content_E', + title: 'Expression Exchange', + kind: 'html5', + }, + content_F: { + node_id: 'node_6', + content_id: 'content_F', + title: 'Make a Ten', + kind: 'html5', + }, + content_G: { + node_id: 'node_7', + content_id: 'content_G', + title: "Ajouter des nombres jusqu'à 5", + kind: 'exercise', + }, + content_H: { + node_id: 'node_8', + content_id: 'content_H', + title: "Soustraire des nombres jusqu'à 5", + kind: 'exercise', + }, + content_I: { + node_id: 'node_9', + content_id: 'content_I', + title: " Les bases de l'addition ", + kind: 'video', + }, + content_J: { + node_id: 'node_10', + content_id: 'content_J', + title: 'Addition de vecteurs dans le plan', + kind: 'video', + }, + content_K: { + node_id: 'node_11', + content_id: 'content_K', + title: 'Look at the Clock and Tell the Time Practice', + kind: 'exercise', + }, + content_L: { + node_id: 'node_12', + content_id: 'content_L', + title: 'Identifying Rectangles, Squares, Circles, and Triangles Practice', + kind: 'exercise', + }, + content_M: { + node_id: 'node_13', + content_id: 'content_M', + title: 'Numbers to 120', + kind: 'exercise', + }, + content_N: { + node_id: 'node_14', + content_id: 'content_N', + title: 'Equal sign', + kind: 'exercise', + }, + content_O: { + node_id: 'node_15', + content_id: 'content_O', + title: 'Word problems with "more" and "fewer" 2', + kind: 'exercise', + }, + content_P: { + node_id: 'node_16', + content_id: 'content_P', + title: 'Add 1s or 10s (no regrouping)', + kind: 'exercise', + }, + content_Q: { + node_id: 'node_17', + content_id: 'content_Q', + title: 'Repeated addition: haircuts', + kind: 'video', + }, + content_R: { + node_id: 'node_18', + content_id: 'content_R', + title: 'Repeated addition', + kind: 'exercise', + }, + content_S: { + node_id: 'node_19', + content_id: 'content_S', + title: 'Adding and subtracting on number line word problems', + kind: 'video', + }, + content_T: { + node_id: 'node_20', + content_id: 'content_T', + title: 'Add and subtract on the number line word problems', + kind: 'exercise', + }, + content_U: { + node_id: 'node_21', + content_id: 'content_U', + title: 'Name angles', + kind: 'exercise', + }, + content_V: { + node_id: 'node_22', + content_id: 'content_V', + title: 'Overview of Telling Time to Quarter and Hour', + kind: 'video', + }, + content_W: { + node_id: 'node_23', + content_id: 'content_W', + title: 'Puzzle', + kind: 'html5', + }, + content_X: { + node_id: 'node_24', + content_id: 'content_X', + title: 'Paneer ( indian cheese )', + kind: 'video', + }, + content_Y: { + node_id: 'node_25', + content_id: 'content_Y', + title: 'Overview of Addition (Sums to 10)', + kind: 'video', + }, + content_Z: { + node_id: 'node_26', + content_id: 'content_Z', + title: 'Adding Sentences (Sums to 10) Practice', + kind: 'exercise', + }, + content_AA: { + node_id: 'node_27', + content_id: 'content_AA', + title: 'Arseny’s family', + kind: 'video', + }, + content_BB: { + node_id: 'node_28', + content_id: 'content_BB', + title: 'the diatonic braid', + kind: 'document', + }, + content_CC: { + node_id: 'node_29', + content_id: 'content_CC', + title: "'n Ernstige Ongeluk", + kind: 'html5', + }, + content_DD: { + node_id: 'node_30', + content_id: 'content_DD', + title: "'n Baie Lang Man", + kind: 'html5', + }, + content_FF: { + node_id: 'node_31', + content_id: 'content_FF', + title: "'n Boontjiesaad", + kind: 'html5', + }, + content_GG: { + node_id: 'node_32', + content_id: 'content_GG', + title: "'n Koei is my Vriend", + kind: 'html5', + }, + }, + contentNodeMap: { + node_1: { + node_id: 'node_1', + content_id: 'content_A', + title: 'aesop_fables', + kind: 'document', + }, + node_2: { + node_id: 'node_2', + content_id: 'content_B', + title: 'arthur-conan-doyle_the-adventures-of-sherlock-holmes', + kind: 'document', + }, + node_3: { + node_id: 'node_3', + content_id: 'content_C', + title: 'arthur-conan-doyle_the-hound-of-the-baskervilles', + kind: 'document', + }, + node_4: { + node_id: 'node_4', + content_id: 'content_D', + title: 'Mata and I', + kind: 'document', + }, + node_5: { + node_id: 'node_5', + content_id: 'content_E', + title: 'Expression Exchange', + kind: 'html5', + }, + node_6: { + node_id: 'node_6', + content_id: 'content_F', + title: 'Make a Ten', + kind: 'html5', + }, + node_7: { + node_id: 'node_7', + content_id: 'content_G', + title: "Ajouter des nombres jusqu'à 5", + kind: 'exercise', + }, + node_8: { + node_id: 'node_8', + content_id: 'content_H', + title: "Soustraire des nombres jusqu'à 5", + kind: 'exercise', + }, + node_9: { + node_id: 'node_9', + content_id: 'content_I', + title: " Les bases de l'addition ", + kind: 'video', + }, + node_10: { + node_id: 'node_10', + content_id: 'content_J', + title: 'Addition de vecteurs dans le plan', + kind: 'video', + }, + node_11: { + node_id: 'node_11', + content_id: 'content_K', + title: 'Look at the Clock and Tell the Time Practice', + kind: 'exercise', + }, + node_12: { + node_id: 'node_12', + content_id: 'content_L', + title: 'Identifying Rectangles, Squares, Circles, and Triangles Practice', + kind: 'exercise', + }, + node_13: { + node_id: 'node_13', + content_id: 'content_M', + title: 'Numbers to 120', + kind: 'exercise', + }, + node_14: { + node_id: 'node_14', + content_id: 'content_N', + title: 'Equal sign', + kind: 'exercise', + }, + node_15: { + node_id: 'node_15', + content_id: 'content_O', + title: 'Word problems with "more" and "fewer" 2', + kind: 'exercise', + }, + node_16: { + node_id: 'node_16', + content_id: 'content_P', + title: 'Add 1s or 10s (no regrouping)', + kind: 'exercise', + }, + node_17: { + node_id: 'node_17', + content_id: 'content_Q', + title: 'Repeated addition: haircuts', + kind: 'video', + }, + node_18: { + node_id: 'node_18', + content_id: 'content_R', + title: 'Repeated addition', + kind: 'exercise', + }, + node_19: { + node_id: 'node_19', + content_id: 'content_S', + title: 'Adding and subtracting on number line word problems', + kind: 'video', + }, + node_20: { + node_id: 'node_20', + content_id: 'content_T', + title: 'Add and subtract on the number line word problems', + kind: 'exercise', + }, + node_21: { + node_id: 'node_21', + content_id: 'content_U', + title: 'Name angles', + kind: 'exercise', + }, + node_22: { + node_id: 'node_22', + content_id: 'content_V', + title: 'Overview of Telling Time to Quarter and Hour', + kind: 'video', + }, + node_23: { + node_id: 'node_23', + content_id: 'content_W', + title: 'Puzzle', + kind: 'html5', + }, + node_24: { + node_id: 'node_24', + content_id: 'content_X', + title: 'Paneer ( indian cheese )', + kind: 'video', + }, + node_25: { + node_id: 'node_25', + content_id: 'content_Y', + title: 'Overview of Addition (Sums to 10)', + kind: 'video', + }, + node_26: { + node_id: 'node_26', + content_id: 'content_Z', + title: 'Adding Sentences (Sums to 10) Practice', + kind: 'exercise', + }, + node_27: { + node_id: 'node_27', + content_id: 'content_AA', + title: 'Arseny’s family', + kind: 'video', + }, + node_28: { + node_id: 'node_28', + content_id: 'content_BB', + title: 'the diatonic braid', + kind: 'document', + }, + node_29: { + node_id: 'node_29', + content_id: 'content_CC', + title: "'n Ernstige Ongeluk", + kind: 'html5', + }, + node_30: { + node_id: 'node_30', + content_id: 'content_DD', + title: "'n Baie Lang Man", + kind: 'html5', + }, + node_31: { + node_id: 'node_31', + content_id: 'content_FF', + title: "'n Boontjiesaad", + kind: 'html5', + }, + node_32: { + node_id: 'node_32', + content_id: 'content_GG', + title: "'n Koei is my Vriend", + kind: 'html5', + }, + }, + contentLearnerStatusMap: { + content_Q: { + learner_id_1: { + learner_id: 'learner_id_1', + content_id: 'content_Q', + status: 'completed', + last_activity: new Date('2019-01-24T22:41:29.288Z'), + time_spent: 10, + }, + learner_id_2: { + learner_id: 'learner_id_2', + content_id: 'content_Q', + status: 'started', + last_activity: new Date('2019-01-24T21:34:36.130Z'), + time_spent: 5, + }, + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_Q', + status: 'not_started', + last_activity: new Date('2019-01-24T20:41:29.288Z'), + time_spent: 0, + }, + learner_id_4: { + learner_id: 'learner_id_4', + content_id: 'content_Q', + status: 'help_needed', + last_activity: new Date('2019-01-24T19:34:36.130Z'), + time_spent: 0, + }, + learner_id_5: { + learner_id: 'learner_id_5', + content_id: 'content_Q', + status: 'completed', + last_activity: new Date('2019-01-24T18:41:29.288Z'), + time_spent: 18.5, + }, + learner_id_6: { + learner_id: 'learner_id_6', + content_id: 'content_Q', + status: 'started', + last_activity: new Date('2019-01-24T17:34:36.130Z'), + time_spent: 3.0, + }, + }, + content_R: { + learner_id_6: { + learner_id: 'learner_id_6', + content_id: 'content_R', + status: 'completed', + last_activity: new Date('2019-01-24T22:43:15.790Z'), + time_spent: 0.34, + }, + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_R', + status: 'completed', + last_activity: new Date('2019-01-24T22:46:29.200Z'), + time_spent: 123.34, + }, + }, + content_F: { + learner_id_6: { + learner_id: 'learner_id_6', + content_id: 'content_F', + status: 'not_started', + last_activity: new Date('2019-01-24T22:43:37.786Z'), + time_spent: 4.34, + }, + learner_id_5: { + learner_id: 'learner_id_5', + content_id: 'content_F', + status: 'not_started', + last_activity: new Date('2019-01-24T23:17:24.102Z'), + time_spent: 12232323233.34, + }, + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_F', + status: 'not_started', + last_activity: new Date('2019-01-24T22:46:41.441Z'), + time_spent: 123.34, + }, + }, + content_K: { + learner_id_6: { + learner_id: 'learner_id_6', + content_id: 'content_K', + status: 'completed', + last_activity: new Date('2019-01-24T22:47:10.710Z'), + time_spent: 2323.34, + }, + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_K', + status: 'completed', + last_activity: new Date('2019-01-24T22:51:14.773Z'), + time_spent: 123.34, + }, + }, + content_S: { + learner_id_5: { + learner_id: 'learner_id_5', + content_id: 'content_S', + status: 'completed', + last_activity: new Date('2019-01-24T23:06:09.415Z'), + time_spent: 123.34, + }, + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_S', + status: 'completed', + last_activity: new Date('2019-01-24T23:06:29.634Z'), + time_spent: 123.34, + }, + }, + content_T: { + learner_id_5: { + learner_id: 'learner_id_5', + content_id: 'content_T', + status: 'started', + last_activity: new Date('2019-01-24T23:12:07.371Z'), + time_spent: 123.34, + }, + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_T', + status: 'completed', + last_activity: new Date('2019-01-24T23:12:29.257Z'), + time_spent: 123.34, + }, + }, + content_V: { + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_V', + status: 'started', + last_activity: new Date('2019-01-24T22:49:46.776Z'), + time_spent: 123.34, + }, + }, + content_L: { + learner_id_3: { + learner_id: 'learner_id_3', + content_id: 'content_L', + status: 'completed', + last_activity: new Date('2019-01-24T22:56:56.586Z'), + time_spent: 123.34, + }, + }, + }, + lessonMap: { + lesson_id_1: { + id: 'lesson_id_1', + title: 'The Program 1', + active: true, + node_ids: ['node_17', 'node_18', 'node_6'], + groups: ['group_id_2'], + }, + lesson_id_2: { + id: 'lesson_id_2', + title: 'The Program 2', + active: true, + node_ids: ['node_22', 'node_11'], + groups: ['group_id_3'], + }, + lesson_id_3: { + id: 'lesson_id_3', + title: 'SCHOOL 1', + active: true, + node_ids: ['node_19', 'node_20'], + groups: [], + }, + lesson_4: { + id: 'lesson_4', + title: 'test', + active: false, + node_ids: ['node_29', 'node_32', 'node_30', 'node_31'], + groups: [], + }, + }, +}; diff --git a/kolibri/plugins/coach/assets/src/modules/classSummary/dataHelpers.js b/kolibri/plugins/coach/assets/src/modules/classSummary/dataHelpers.js index d9ed88dda3d..d64bbd35f60 100644 --- a/kolibri/plugins/coach/assets/src/modules/classSummary/dataHelpers.js +++ b/kolibri/plugins/coach/assets/src/modules/classSummary/dataHelpers.js @@ -1,9 +1,17 @@ import map from 'lodash/map'; import get from 'lodash/get'; +import uniq from 'lodash/uniq'; import meanBy from 'lodash/meanBy'; import flatten from 'lodash/flatten'; import { STATUSES } from './constants'; +const keyMap = { + [STATUSES.completed]: 'completed', + [STATUSES.started]: 'started', + [STATUSES.notStarted]: 'notStarted', + [STATUSES.helpNeeded]: 'helpNeeded', +}; + // Getters that return lookup functions // // Implemented as getters for easy access to the store @@ -26,7 +34,7 @@ export default { if (!groupIds.length) { return map(state.learnerMap, 'id'); } - return flatten(map(groupIds, id => state.groupMap[id].member_ids)); + return uniq(flatten(map(groupIds, id => state.groupMap[id].member_ids))); }; }, getContentStatusForLearner(state) { @@ -41,14 +49,14 @@ export default { getContentStatusCounts(state, getters) { return function(contentId, learnerIds) { const tallies = { - [STATUSES.started]: 0, - [STATUSES.notStarted]: 0, - [STATUSES.completed]: 0, - [STATUSES.helpNeeded]: 0, + started: 0, + notStarted: 0, + completed: 0, + helpNeeded: 0, }; learnerIds.forEach(learnerId => { const status = getters.getContentStatusForLearner(contentId, learnerId); - tallies[status] += 1; + tallies[keyMap[status]] += 1; }); return tallies; }; @@ -61,14 +69,14 @@ export default { getExamStatusCounts(state, getters) { return function(examId, learnerIds) { const tallies = { - [STATUSES.started]: 0, - [STATUSES.notStarted]: 0, - [STATUSES.completed]: 0, - [STATUSES.helpNeeded]: 0, + started: 0, + notStarted: 0, + completed: 0, + helpNeeded: 0, }; learnerIds.forEach(learnerId => { const status = getters.getExamStatusForLearner(examId, learnerId); - tallies[status] += 1; + tallies[keyMap[status]] += 1; }); return tallies; }; @@ -81,14 +89,14 @@ export default { getLessonStatusCounts(state, getters) { return function(lessonId, learnerIds) { const tallies = { - [STATUSES.started]: 0, - [STATUSES.notStarted]: 0, - [STATUSES.completed]: 0, - [STATUSES.helpNeeded]: 0, + started: 0, + notStarted: 0, + completed: 0, + helpNeeded: 0, }; learnerIds.forEach(learnerId => { const status = getters.getLessonStatusForLearner(lessonId, learnerId); - tallies[status] += 1; + tallies[keyMap[status]] += 1; }); return tallies; }; diff --git a/kolibri/plugins/coach/assets/src/views/common.js b/kolibri/plugins/coach/assets/src/views/common.js index 9e3d8054eda..016c5cdd7eb 100644 --- a/kolibri/plugins/coach/assets/src/views/common.js +++ b/kolibri/plugins/coach/assets/src/views/common.js @@ -38,11 +38,12 @@ import HeaderTab from './common/HeaderTabs/HeaderTab'; import LearnerProgressRatio from './common/status/LearnerProgressRatio'; import LearnerProgressCount from './common/status/LearnerProgressCount'; import LearnerProgressLabel from './common/status/LearnerProgressLabel'; +import StatusSummary from './common/status/StatusSummary'; import ItemStatusRatio from './common/status/ItemStatusRatio'; import ItemStatusCount from './common/status/ItemStatusCount'; import ItemStatusLabel from './common/status/ItemStatusLabel'; import Placeholder from './common/Placeholder'; -import { OBJECTS, ADJECTIVES, VERBS, ICONS } from './common/status/constants'; +import { VERBS, ICONS } from './common/status/constants'; export default { name: 'ReportsQuizHeader', @@ -75,6 +76,7 @@ export default { HeaderTab, LearnerProgressRatio, LearnerProgressCount, + StatusSummary, LearnerProgressLabel, ItemStatusRatio, ItemStatusCount, @@ -97,12 +99,6 @@ export default { CollectionKinds() { return CollectionKinds; }, - OBJECTS() { - return OBJECTS; - }, - ADJECTIVES() { - return ADJECTIVES; - }, VERBS() { return VERBS; }, diff --git a/kolibri/plugins/coach/assets/src/views/common/status/StatusSummary.vue b/kolibri/plugins/coach/assets/src/views/common/status/StatusSummary.vue new file mode 100644 index 00000000000..053bc45a44f --- /dev/null +++ b/kolibri/plugins/coach/assets/src/views/common/status/StatusSummary.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/kolibri/plugins/coach/assets/src/views/home/HomePage/DashboardBar.vue b/kolibri/plugins/coach/assets/src/views/home/HomePage/DashboardBar.vue index b7cdeab0633..e19d9dc1446 100644 --- a/kolibri/plugins/coach/assets/src/views/home/HomePage/DashboardBar.vue +++ b/kolibri/plugins/coach/assets/src/views/home/HomePage/DashboardBar.vue @@ -1,11 +1,8 @@ @@ -18,21 +15,37 @@ export default { name: 'DashboardBar', props: { - percentage: { + completed: { + type: Number, + required: true, + }, + started: { + type: Number, + required: true, + }, + total: { type: Number, required: true, - validator(value) { - return value >= 0 && value <= 1; - }, }, }, computed: { ...mapGetters(['$coreStatusProgress', '$coreStatusMastered']), - barStyle() { + percentageCompleted() { + return this.completed / this.total; + }, + barStyleCompleted() { + return { + width: `${Math.floor(100 * this.percentageCompleted)}%`, + backgroundColor: this.$coreStatusMastered, + }; + }, + percentageStarted() { + return (this.started + this.completed) / this.total; + }, + barStyleStarted() { return { - width: `${Math.floor(100 * this.percentage)}%`, - backgroundColor: - this.percentage === 1 ? this.$coreStatusMastered : this.$coreStatusProgress, + width: `${Math.floor(100 * this.percentageStarted)}%`, + backgroundColor: this.$coreStatusProgress, }; }, }, @@ -49,15 +62,16 @@ position: relative; width: 100%; height: 16px; + overflow: hidden; background-color: #dedede; border-radius: $radius; - opacity: 0.65; + opacity: 0.6; } .bar { + position: absolute; height: 100%; margin-right: auto; - border-radius: $radius; transition: all $core-time ease; } diff --git a/kolibri/plugins/coach/assets/src/views/home/HomePage/ItemProgressDisplay.vue b/kolibri/plugins/coach/assets/src/views/home/HomePage/ItemProgressDisplay.vue index 8c63cb93760..46ec924c0d4 100644 --- a/kolibri/plugins/coach/assets/src/views/home/HomePage/ItemProgressDisplay.vue +++ b/kolibri/plugins/coach/assets/src/views/home/HomePage/ItemProgressDisplay.vue @@ -4,11 +4,22 @@

{{ name }}

- + + + @@ -62,8 +64,7 @@ return { key: lesson.id, name: lesson.title, - completed: this.numCompleted(lesson.id, assigned), - total: assigned.length, + statusCounts: this.getLessonStatusCounts(lesson.id, assigned), groups: lesson.groups.map(groupId => this.groupMap[groupId].name), }; }); @@ -85,10 +86,6 @@ }); return learnerIds; }, - // return the number of learners who have completed the lesson - numCompleted(lessonId, assignedLearnerIds) { - return this.getLessonStatusCounts(lessonId, assignedLearnerIds)[this.STATUSES.completed]; - }, // return the last activity among all users for a particular lesson lastActivity(lesson) { let last = null; diff --git a/kolibri/plugins/coach/assets/src/views/home/HomePage/QuizzesBlock.vue b/kolibri/plugins/coach/assets/src/views/home/HomePage/QuizzesBlock.vue index 32e2a739e4e..2f6e837e44b 100644 --- a/kolibri/plugins/coach/assets/src/views/home/HomePage/QuizzesBlock.vue +++ b/kolibri/plugins/coach/assets/src/views/home/HomePage/QuizzesBlock.vue @@ -13,8 +13,10 @@ > @@ -56,8 +58,7 @@ return { key: exam.id, name: exam.title, - completed: this.numCompleted(exam.id, assigned), - total: assigned.length, + statusCounts: this.getExamStatusCounts(exam.id, assigned), groups: exam.groups.map(groupId => this.groupMap[groupId].name), }; }); @@ -79,10 +80,6 @@ }); return learnerIds; }, - // return the number of learners who have completed the exam - numCompleted(examId, assignedLearnerIds) { - return this.getExamStatusCounts(examId, assignedLearnerIds)[this.STATUSES.completed]; - }, // return the last activity among all users for a particular exam lastActivity(exam) { let last = null; diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonLearnerListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonLearnerListPage.vue index e59aea78e20..0b30a2dba00 100644 --- a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonLearnerListPage.vue +++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonLearnerListPage.vue @@ -33,14 +33,7 @@ /> - + TODO - lesson status for learner diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonResourceLearnerListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonResourceLearnerListPage.vue index 4ee35b592c1..515483b892c 100644 --- a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonResourceLearnerListPage.vue +++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonResourceLearnerListPage.vue @@ -33,19 +33,19 @@

@@ -64,34 +64,7 @@ {{ tableRow.name }} - - - - + TODO - new status display