@openstax/ts-utils 1.37.2 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/services/lrsGateway/attempt-utils.d.ts +23 -9
- package/dist/cjs/services/lrsGateway/attempt-utils.js +137 -70
- package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +2 -9
- package/dist/cjs/services/lrsGateway/xapiUtils.js +24 -36
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/services/lrsGateway/attempt-utils.d.ts +23 -9
- package/dist/esm/services/lrsGateway/attempt-utils.js +128 -67
- package/dist/esm/services/lrsGateway/xapiUtils.d.ts +2 -9
- package/dist/esm/services/lrsGateway/xapiUtils.js +25 -37
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import { LrsGateway, UXapiStatement } from '.';
|
|
2
|
+
export declare const EXT_PENDING_SCORING = "https://openstax.org/xapi/extensions/pending-scoring";
|
|
3
|
+
export type AttemptEntry = {
|
|
4
|
+
attempt: UXapiStatement;
|
|
5
|
+
completed?: UXapiStatement;
|
|
6
|
+
scored?: UXapiStatement;
|
|
7
|
+
};
|
|
2
8
|
export type ActivityState = {
|
|
3
9
|
attempts: number;
|
|
4
|
-
bestCompletedAttempt?: UXapiStatement;
|
|
5
|
-
bestCompletedAttemptCompleted?: UXapiStatement;
|
|
6
10
|
completedAttempts: number;
|
|
7
|
-
currentAttempt?: UXapiStatement;
|
|
8
|
-
currentAttemptCompleted?: UXapiStatement;
|
|
9
11
|
currentAttemptStatements: UXapiStatement[];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
current?: AttemptEntry;
|
|
13
|
+
best?: AttemptEntry;
|
|
14
|
+
mostRecentWithCompleted?: AttemptEntry;
|
|
12
15
|
};
|
|
13
16
|
export declare const matchAttempt: (statement: UXapiStatement) => boolean;
|
|
14
|
-
export declare const matchAttemptCompleted: (attempt: UXapiStatement) => (statement: UXapiStatement) => boolean;
|
|
17
|
+
export declare const matchAttemptCompleted: (attempt: UXapiStatement) => (statement: UXapiStatement) => boolean | "";
|
|
18
|
+
export declare const matchAttemptScored: (attempt: UXapiStatement) => (statement: UXapiStatement) => boolean | "";
|
|
15
19
|
export declare const resolveAttempts: (statements: UXapiStatement[], options?: {
|
|
16
20
|
activityIRI?: string;
|
|
17
21
|
parentActivityAttempt?: string;
|
|
18
22
|
}) => UXapiStatement[];
|
|
19
23
|
export declare const resolveCompletedForAttempt: (statements: UXapiStatement[], attempt: UXapiStatement, activityIRI?: string) => UXapiStatement | undefined;
|
|
20
|
-
export declare const
|
|
21
|
-
export declare const
|
|
24
|
+
export declare const resolveScoredForAttempt: (statements: UXapiStatement[], attempt: UXapiStatement, activityIRI?: string) => UXapiStatement | undefined;
|
|
25
|
+
export declare const getStatementTimeString: (statement: UXapiStatement) => string;
|
|
26
|
+
export declare const getStatementTime: (statement: UXapiStatement) => Date;
|
|
27
|
+
export declare const oldestStatement: (items: UXapiStatement[]) => UXapiStatement | undefined;
|
|
28
|
+
export declare const mostRecentStatement: (items: UXapiStatement[]) => UXapiStatement | undefined;
|
|
29
|
+
export declare const oldestEntry: (items: AttemptEntry[]) => AttemptEntry | undefined;
|
|
30
|
+
export declare const mostRecentEntry: (items: AttemptEntry[]) => AttemptEntry | undefined;
|
|
31
|
+
export declare const bestEntry: (items: AttemptEntry[]) => AttemptEntry | undefined;
|
|
22
32
|
export declare const resolveAttemptInfo: (statements: UXapiStatement[], options?: {
|
|
23
33
|
activityIRI?: string;
|
|
24
34
|
currentAttempt?: string;
|
|
@@ -70,3 +80,7 @@ export declare const createAttemptActivityStatement: (attemptStatement: UXapiSta
|
|
|
70
80
|
export declare const putAttemptActivityStatement: (gateway: LrsGateway, attemptStatement: UXapiStatement, verb: UXapiStatement["verb"], result?: UXapiStatement["result"]) => Promise<import(".").EagerXapiStatement>;
|
|
71
81
|
export declare const createCompletedStatement: (attemptStatement: UXapiStatement, result?: UXapiStatement["result"]) => Pick<UXapiStatement, "object" | "verb" | "context" | "result">;
|
|
72
82
|
export declare const putCompletedStatement: (gateway: LrsGateway, attemptStatement: UXapiStatement, result: UXapiStatement["result"]) => Promise<import(".").EagerXapiStatement>;
|
|
83
|
+
export declare const createCompletedPendingScoringStatement: (attemptStatement: UXapiStatement) => Pick<UXapiStatement, "object" | "verb" | "context" | "result">;
|
|
84
|
+
export declare const putCompletedPendingScoringStatement: (gateway: LrsGateway, attemptStatement: UXapiStatement) => Promise<import(".").EagerXapiStatement>;
|
|
85
|
+
export declare const createScoredStatement: (attemptStatement: UXapiStatement, result: UXapiStatement["result"]) => Pick<UXapiStatement, "object" | "verb" | "context" | "result">;
|
|
86
|
+
export declare const putScoredStatement: (gateway: LrsGateway, attemptStatement: UXapiStatement, result: UXapiStatement["result"]) => Promise<import(".").EagerXapiStatement>;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.putCompletedStatement = exports.createCompletedStatement = exports.putAttemptActivityStatement = exports.createAttemptActivityStatement = exports.putAttemptStatement = exports.createAttemptStatement = exports.createStatement = exports.loadActivityAttemptInfo = exports.loadStatementsForActivityAndFirstChildren = exports.resolveAttemptInfo = exports.mostRecentStatement = exports.oldestStatement = exports.resolveCompletedForAttempt = exports.resolveAttempts = exports.matchAttemptCompleted = exports.matchAttempt = void 0;
|
|
6
|
+
exports.putScoredStatement = exports.createScoredStatement = exports.putCompletedPendingScoringStatement = exports.createCompletedPendingScoringStatement = exports.putCompletedStatement = exports.createCompletedStatement = exports.putAttemptActivityStatement = exports.createAttemptActivityStatement = exports.putAttemptStatement = exports.createAttemptStatement = exports.createStatement = exports.loadActivityAttemptInfo = exports.loadStatementsForActivityAndFirstChildren = exports.resolveAttemptInfo = exports.bestEntry = exports.mostRecentEntry = exports.oldestEntry = exports.mostRecentStatement = exports.oldestStatement = exports.getStatementTime = exports.getStatementTimeString = exports.resolveScoredForAttempt = exports.resolveCompletedForAttempt = exports.resolveAttempts = exports.matchAttemptScored = exports.matchAttemptCompleted = exports.matchAttempt = exports.EXT_PENDING_SCORING = void 0;
|
|
7
7
|
/*
|
|
8
8
|
* the structure of xapi statements for handling multiple attempts of an activity
|
|
9
9
|
* including the option of a parent activity/attempt such that a new attempt
|
|
@@ -21,17 +21,27 @@ var Verb;
|
|
|
21
21
|
(function (Verb) {
|
|
22
22
|
Verb["Attempted"] = "http://adlnet.gov/expapi/verbs/attempted";
|
|
23
23
|
Verb["Completed"] = "http://adlnet.gov/expapi/verbs/completed";
|
|
24
|
+
Verb["Scored"] = "http://adlnet.gov/expapi/verbs/scored";
|
|
24
25
|
})(Verb || (Verb = {}));
|
|
26
|
+
exports.EXT_PENDING_SCORING = 'https://openstax.org/xapi/extensions/pending-scoring';
|
|
25
27
|
const matchAttempt = (statement) => statement.verb.id === Verb.Attempted;
|
|
26
28
|
exports.matchAttempt = matchAttempt;
|
|
27
29
|
const matchAttemptCompleted = (attempt) => (statement) => {
|
|
28
30
|
var _a, _b;
|
|
29
|
-
return statement.verb.id === Verb.Completed
|
|
31
|
+
return statement.verb.id === Verb.Completed && attempt.id
|
|
30
32
|
&& statement.context !== undefined
|
|
31
33
|
&& ((_a = statement.context.statement) === null || _a === void 0 ? void 0 : _a.id) === attempt.id
|
|
32
34
|
&& statement.context.registration === ((_b = attempt.context) === null || _b === void 0 ? void 0 : _b.registration);
|
|
33
35
|
};
|
|
34
36
|
exports.matchAttemptCompleted = matchAttemptCompleted;
|
|
37
|
+
const matchAttemptScored = (attempt) => (statement) => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
return statement.verb.id === Verb.Scored && attempt.id
|
|
40
|
+
&& statement.context !== undefined
|
|
41
|
+
&& ((_a = statement.context.statement) === null || _a === void 0 ? void 0 : _a.id) === attempt.id
|
|
42
|
+
&& statement.context.registration === ((_b = attempt.context) === null || _b === void 0 ? void 0 : _b.registration);
|
|
43
|
+
};
|
|
44
|
+
exports.matchAttemptScored = matchAttemptScored;
|
|
35
45
|
const resolveAttempts = (statements, options) => statements.filter(statement => {
|
|
36
46
|
var _a;
|
|
37
47
|
return (0, exports.matchAttempt)(statement)
|
|
@@ -42,80 +52,94 @@ exports.resolveAttempts = resolveAttempts;
|
|
|
42
52
|
const resolveCompletedForAttempt = (statements, attempt, activityIRI) => statements.find(statement => (0, exports.matchAttemptCompleted)(attempt)(statement)
|
|
43
53
|
&& (!activityIRI || statement.object.id === activityIRI));
|
|
44
54
|
exports.resolveCompletedForAttempt = resolveCompletedForAttempt;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const resolveAttemptInfo = (statements, options) => {
|
|
50
|
-
// TODO optimize. i'm 100% that this could all be done in one iteration but i'm not messing around with that for now.
|
|
51
|
-
const attempts = (0, exports.resolveAttempts)(statements, options);
|
|
52
|
-
/* attempts that have a completed statement */
|
|
53
|
-
const resolveAttemptsWithCompleted = (statements, attempts, activityIRI) => attempts.reduce((acc, attempt) => {
|
|
54
|
-
const completed = (0, exports.resolveCompletedForAttempt)(statements, attempt, activityIRI);
|
|
55
|
-
if (completed)
|
|
56
|
-
acc.push({ attempt, completed });
|
|
57
|
-
return acc;
|
|
58
|
-
}, []);
|
|
59
|
-
const completedPairs = resolveAttemptsWithCompleted(statements, attempts, options === null || options === void 0 ? void 0 : options.activityIRI);
|
|
60
|
-
const completedAttempts = completedPairs.map(p => p.attempt);
|
|
61
|
-
/* the last attempt sorted by timestamp */
|
|
62
|
-
const currentAttempt = (options === null || options === void 0 ? void 0 : options.currentAttempt)
|
|
63
|
-
? attempts.find(attempt => attempt.id === options.currentAttempt)
|
|
64
|
-
: (options === null || options === void 0 ? void 0 : options.currentPreference) === 'oldest'
|
|
65
|
-
? (0, exports.oldestStatement)(attempts)
|
|
66
|
-
: (0, exports.mostRecentStatement)(attempts);
|
|
67
|
-
/* all statements for the current attempt (doesn't include the attempt or completed statements) */
|
|
68
|
-
const currentAttemptStatements = currentAttempt ? statements.filter(statement => {
|
|
69
|
-
var _a;
|
|
70
|
-
return (!(options === null || options === void 0 ? void 0 : options.activityIRI) || statement.object.id === options.activityIRI)
|
|
71
|
-
&& ((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) === currentAttempt.id;
|
|
72
|
-
}) : [];
|
|
73
|
-
const currentAttemptCompleted = currentAttempt && (0, exports.resolveCompletedForAttempt)(statements, currentAttempt, options === null || options === void 0 ? void 0 : options.activityIRI);
|
|
74
|
-
const mostRecentPair = completedPairs.reduce((cur, pair) => {
|
|
75
|
-
if (!cur)
|
|
76
|
-
return pair;
|
|
77
|
-
return (0, isAfter_1.default)((0, parseISO_1.default)(cur.attempt.timestamp), (0, parseISO_1.default)(pair.attempt.timestamp)) ? cur : pair;
|
|
78
|
-
}, undefined);
|
|
79
|
-
/*
|
|
80
|
-
* the structure allows for the possibility of multiple incomplete attempts.
|
|
81
|
-
* the implementation can choose at its discretion to ignore the currentAttempt
|
|
82
|
-
* and instead make a new one, for instance if the implementation desires
|
|
83
|
-
* an attempt timeout feature
|
|
84
|
-
*/
|
|
85
|
-
const hasScaledScore = (p) => {
|
|
55
|
+
const resolveScoredForAttempt = (statements, attempt, activityIRI) => {
|
|
56
|
+
// scored or completed statements that contain a score in their
|
|
57
|
+
// result are 'scoring statements' for our purposes.
|
|
58
|
+
const scoringStatements = statements.filter(s => {
|
|
86
59
|
var _a;
|
|
87
|
-
return
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
60
|
+
return ((0, exports.matchAttemptScored)(attempt)(s) || (0, exports.matchAttemptCompleted)(attempt)(s))
|
|
61
|
+
&& ((_a = s.result) === null || _a === void 0 ? void 0 : _a.score)
|
|
62
|
+
&& (!activityIRI || s.object.id === activityIRI);
|
|
63
|
+
});
|
|
64
|
+
return (0, exports.mostRecentStatement)(scoringStatements);
|
|
65
|
+
};
|
|
66
|
+
exports.resolveScoredForAttempt = resolveScoredForAttempt;
|
|
67
|
+
const resolveAttemptEntries = (statements, attempts, activityIRI) => attempts.reduce((acc, attempt) => {
|
|
68
|
+
const completed = (0, exports.resolveCompletedForAttempt)(statements, attempt, activityIRI);
|
|
69
|
+
const scored = (0, exports.resolveScoredForAttempt)(statements, attempt, activityIRI);
|
|
70
|
+
acc.push({ attempt, completed, scored });
|
|
71
|
+
return acc;
|
|
72
|
+
}, []);
|
|
73
|
+
const getStatementTimeString = (statement) => 'stored' in statement && statement.stored ? statement.stored : statement.timestamp;
|
|
74
|
+
exports.getStatementTimeString = getStatementTimeString;
|
|
75
|
+
const getStatementTime = (statement) => (0, parseISO_1.default)((0, exports.getStatementTimeString)(statement));
|
|
76
|
+
exports.getStatementTime = getStatementTime;
|
|
77
|
+
const findByComparator = (comparator, extractor) => (items) => {
|
|
78
|
+
return items.reduce((best, item) => {
|
|
94
79
|
if (!best)
|
|
95
|
-
return
|
|
96
|
-
return
|
|
80
|
+
return item;
|
|
81
|
+
return comparator(extractor(best), extractor(item)) ? best : item;
|
|
97
82
|
}, undefined);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
83
|
+
};
|
|
84
|
+
exports.oldestStatement = findByComparator(isBefore_1.default, exports.getStatementTime);
|
|
85
|
+
exports.mostRecentStatement = findByComparator(isAfter_1.default, exports.getStatementTime);
|
|
86
|
+
exports.oldestEntry = findByComparator(isBefore_1.default, (entry) => (0, exports.getStatementTime)(entry.attempt));
|
|
87
|
+
exports.mostRecentEntry = findByComparator(isAfter_1.default, (entry) => (0, exports.getStatementTime)(entry.attempt));
|
|
88
|
+
const scaledScoreForEntry = (entry) => {
|
|
89
|
+
var _a;
|
|
90
|
+
const scoringStatement = entry.scored;
|
|
91
|
+
const score = (_a = scoringStatement === null || scoringStatement === void 0 ? void 0 : scoringStatement.result) === null || _a === void 0 ? void 0 : _a.score;
|
|
92
|
+
if (!score)
|
|
93
|
+
return { score: undefined, entry };
|
|
94
|
+
// Prefer scaled, otherwise normalize raw/max if available
|
|
95
|
+
if (score.scaled !== undefined)
|
|
96
|
+
return { score: score.scaled, entry };
|
|
97
|
+
if (score.raw !== undefined && score.max !== undefined && score.max !== 0) {
|
|
98
|
+
return { score: score.raw / score.max, entry };
|
|
99
|
+
}
|
|
100
|
+
return { score: undefined, entry };
|
|
101
|
+
};
|
|
102
|
+
/*
|
|
103
|
+
* the 'best' entry is the one with the highest scaled score.
|
|
104
|
+
* if two entries have no score, the most recent is considered best.
|
|
105
|
+
*/
|
|
106
|
+
exports.bestEntry = findByComparator((a, b) => {
|
|
107
|
+
// if neither has a score, use the most recent
|
|
108
|
+
if (a.score === undefined && b.score === undefined) {
|
|
109
|
+
return (0, isAfter_1.default)((0, exports.getStatementTime)(a.entry.attempt), (0, exports.getStatementTime)(b.entry.attempt));
|
|
110
|
+
}
|
|
111
|
+
if (a.score === undefined)
|
|
112
|
+
return false;
|
|
113
|
+
if (b.score === undefined)
|
|
114
|
+
return true;
|
|
115
|
+
return a.score > b.score;
|
|
116
|
+
}, scaledScoreForEntry);
|
|
117
|
+
const resolveAttemptInfo = (statements, options) => {
|
|
118
|
+
// TODO optimize. i'm 100% that this could all be done in one iteration but i'm not messing around with that for now.
|
|
119
|
+
const attempts = (0, exports.resolveAttempts)(statements, options);
|
|
120
|
+
const attemptEntries = resolveAttemptEntries(statements, attempts, options === null || options === void 0 ? void 0 : options.activityIRI);
|
|
121
|
+
const completedEntries = attemptEntries.filter((entry) => !!entry.completed);
|
|
122
|
+
const current = (options === null || options === void 0 ? void 0 : options.currentAttempt)
|
|
123
|
+
? attemptEntries.find(({ attempt }) => attempt.id === options.currentAttempt)
|
|
124
|
+
: (options === null || options === void 0 ? void 0 : options.currentPreference) === 'oldest' /* the attempt sorted by timestamp */
|
|
125
|
+
? (0, exports.oldestEntry)(attemptEntries)
|
|
126
|
+
: (0, exports.mostRecentEntry)(attemptEntries);
|
|
127
|
+
const currentAttemptStatements = current
|
|
128
|
+
? statements.filter(statement => {
|
|
129
|
+
var _a;
|
|
130
|
+
return (!(options === null || options === void 0 ? void 0 : options.activityIRI) || statement.object.id === options.activityIRI) &&
|
|
131
|
+
((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) === current.attempt.id;
|
|
132
|
+
})
|
|
133
|
+
: [];
|
|
134
|
+
const mostRecentWithCompleted = (0, exports.mostRecentEntry)(completedEntries);
|
|
135
|
+
const best = (0, exports.bestEntry)(completedEntries);
|
|
109
136
|
return {
|
|
110
137
|
attempts: attempts.length,
|
|
111
|
-
|
|
112
|
-
bestCompletedAttemptCompleted: bestPair === null || bestPair === void 0 ? void 0 : bestPair.completed,
|
|
113
|
-
completedAttempts: completedAttempts.length,
|
|
114
|
-
currentAttempt,
|
|
115
|
-
currentAttemptCompleted,
|
|
138
|
+
completedAttempts: completedEntries.length,
|
|
116
139
|
currentAttemptStatements,
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
current,
|
|
141
|
+
best,
|
|
142
|
+
mostRecentWithCompleted,
|
|
119
143
|
};
|
|
120
144
|
};
|
|
121
145
|
exports.resolveAttemptInfo = resolveAttemptInfo;
|
|
@@ -291,3 +315,46 @@ const putCompletedStatement = async (gateway, attemptStatement, result) => {
|
|
|
291
315
|
return (await gateway.putXapiStatements([(0, exports.createCompletedStatement)(attemptStatement, result)]))[0];
|
|
292
316
|
};
|
|
293
317
|
exports.putCompletedStatement = putCompletedStatement;
|
|
318
|
+
// pending score statement for when the open written response has been graded.
|
|
319
|
+
const createCompletedPendingScoringStatement = (attemptStatement) => (0, exports.createCompletedStatement)(attemptStatement, {
|
|
320
|
+
extensions: { [exports.EXT_PENDING_SCORING]: 'true' },
|
|
321
|
+
});
|
|
322
|
+
exports.createCompletedPendingScoringStatement = createCompletedPendingScoringStatement;
|
|
323
|
+
const putCompletedPendingScoringStatement = async (gateway, attemptStatement) => {
|
|
324
|
+
return (await gateway.putXapiStatements([
|
|
325
|
+
(0, exports.createCompletedPendingScoringStatement)(attemptStatement),
|
|
326
|
+
]))[0];
|
|
327
|
+
};
|
|
328
|
+
exports.putCompletedPendingScoringStatement = putCompletedPendingScoringStatement;
|
|
329
|
+
// scored statement for when the open written response has been graded.
|
|
330
|
+
const createScoredStatement = (attemptStatement, result) => {
|
|
331
|
+
var _a, _b;
|
|
332
|
+
return {
|
|
333
|
+
context: {
|
|
334
|
+
...(((_a = attemptStatement.context) === null || _a === void 0 ? void 0 : _a.contextActivities) ? {
|
|
335
|
+
contextActivities: attemptStatement.context.contextActivities,
|
|
336
|
+
} : {}),
|
|
337
|
+
...(((_b = attemptStatement.context) === null || _b === void 0 ? void 0 : _b.registration) ? {
|
|
338
|
+
registration: attemptStatement.context.registration,
|
|
339
|
+
} : {}),
|
|
340
|
+
statement: {
|
|
341
|
+
objectType: 'StatementRef',
|
|
342
|
+
id: attemptStatement.id,
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
object: attemptStatement.object,
|
|
346
|
+
verb: {
|
|
347
|
+
display: { 'en-US': 'Scored' },
|
|
348
|
+
id: Verb.Scored,
|
|
349
|
+
},
|
|
350
|
+
result: {
|
|
351
|
+
// no duration here, we treat Scored as a pure grading event
|
|
352
|
+
...result,
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
exports.createScoredStatement = createScoredStatement;
|
|
357
|
+
const putScoredStatement = async (gateway, attemptStatement, result) => {
|
|
358
|
+
return (await gateway.putXapiStatements([(0, exports.createScoredStatement)(attemptStatement, result)]))[0];
|
|
359
|
+
};
|
|
360
|
+
exports.putScoredStatement = putScoredStatement;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AccountsGateway, MappedUserInfo } from '../accountsGateway';
|
|
2
2
|
import { AuthProvider } from '../authProvider';
|
|
3
|
-
import { ActivityState } from './attempt-utils';
|
|
3
|
+
import { ActivityState, AttemptEntry } from './attempt-utils';
|
|
4
4
|
import { LrsGateway } from '.';
|
|
5
5
|
export interface Grade {
|
|
6
6
|
activityProgress: 'Initialized' | 'Started' | 'inProgress' | 'Submitted' | 'Completed';
|
|
@@ -23,15 +23,8 @@ export declare const getRegistrationAttemptInfo: (lrs: LrsGateway, registration:
|
|
|
23
23
|
}) => Promise<{
|
|
24
24
|
[key: string]: ActivityState;
|
|
25
25
|
}>;
|
|
26
|
-
export declare const getScoreGrade: (
|
|
27
|
-
scaled?: number;
|
|
28
|
-
raw?: number;
|
|
29
|
-
min?: number;
|
|
30
|
-
max?: number;
|
|
31
|
-
}, options: {
|
|
26
|
+
export declare const getScoreGrade: (entry: Partial<AttemptEntry>, options: {
|
|
32
27
|
maxScore?: number;
|
|
33
|
-
startedAt?: string;
|
|
34
|
-
submittedAt?: string;
|
|
35
28
|
userId?: string;
|
|
36
29
|
}) => Grade;
|
|
37
30
|
export type Progress = {
|
|
@@ -30,28 +30,28 @@ exports.getRegistrationAttemptInfo = getRegistrationAttemptInfo;
|
|
|
30
30
|
// lti: http://www.imsglobal.org/spec/lti-ags/v2p0#score-publish-service
|
|
31
31
|
// ltijs: https://cvmcosta.me/ltijs/#/grading
|
|
32
32
|
// Note: "min" is currently completely ignored
|
|
33
|
-
const getScoreGrade = (
|
|
34
|
-
|
|
35
|
-
const {
|
|
33
|
+
const getScoreGrade = (entry, options) => {
|
|
34
|
+
var _a, _b, _c, _d, _e;
|
|
35
|
+
const { raw, scaled, max } = ((_b = (_a = entry.scored) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.score) || {};
|
|
36
|
+
const { maxScore, userId } = options;
|
|
36
37
|
const scoreMaximum = maxScore !== null && maxScore !== void 0 ? maxScore : 100;
|
|
37
38
|
const scoreGiven = raw && max
|
|
38
39
|
? scoreMaximum / max * raw
|
|
39
40
|
: scaled
|
|
40
41
|
? scaled * scoreMaximum
|
|
41
42
|
: 0;
|
|
42
|
-
const submission = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
43
|
+
const submission = {
|
|
44
|
+
startedAt: entry.attempt ? (0, attempt_utils_1.getStatementTimeString)(entry.attempt) : undefined,
|
|
45
|
+
submittedAt: entry.completed ? (0, attempt_utils_1.getStatementTimeString)(entry.completed) : undefined,
|
|
46
|
+
};
|
|
47
|
+
const pendingManual = entry.scored === undefined
|
|
48
|
+
&& ((_e = (_d = (_c = entry.completed) === null || _c === void 0 ? void 0 : _c.result) === null || _d === void 0 ? void 0 : _d.extensions) === null || _e === void 0 ? void 0 : _e[attempt_utils_1.EXT_PENDING_SCORING]) === 'true';
|
|
49
49
|
return {
|
|
50
50
|
userId,
|
|
51
|
-
activityProgress:
|
|
51
|
+
activityProgress: entry.completed ? 'Completed' : 'Started',
|
|
52
52
|
// canvas assumes that anything that isn't 'FullyGraded' requires manual grading and displays a "needs grading" icon.
|
|
53
53
|
// if you warp your mind you can consider the portion of the assignment which is completed to be fully graded.
|
|
54
|
-
gradingProgress: 'FullyGraded',
|
|
54
|
+
gradingProgress: pendingManual ? 'PendingManual' : 'FullyGraded',
|
|
55
55
|
scoreMaximum,
|
|
56
56
|
scoreGiven: (0, __1.roundToPrecision)(scoreGiven, -2),
|
|
57
57
|
submission,
|
|
@@ -59,31 +59,19 @@ const getScoreGrade = (score, options) => {
|
|
|
59
59
|
};
|
|
60
60
|
exports.getScoreGrade = getScoreGrade;
|
|
61
61
|
// These methods assign 0's to incomplete activities
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
attempt: state.bestCompletedAttempt,
|
|
66
|
-
completed: state.bestCompletedAttemptCompleted,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
// return current attempt if gradePreference is current
|
|
70
|
-
return {
|
|
71
|
-
attempt: state.currentAttempt,
|
|
72
|
-
completed: state.currentAttemptCompleted,
|
|
73
|
-
};
|
|
62
|
+
const getGradingAttemptEntry = (state, gradePreference) => {
|
|
63
|
+
return (gradePreference === 'best' ? state.best : state.current) || {};
|
|
74
64
|
};
|
|
75
65
|
const getCompletedActivityStateGradeAndProgress = ({ name, scoreMaximum, state, userId, gradePreference = 'current' }) => {
|
|
76
|
-
|
|
77
|
-
const
|
|
66
|
+
const entry = getGradingAttemptEntry(state, gradePreference);
|
|
67
|
+
const grade = (0, exports.getScoreGrade)(entry, {
|
|
68
|
+
maxScore: scoreMaximum,
|
|
69
|
+
userId,
|
|
70
|
+
});
|
|
78
71
|
return {
|
|
79
|
-
grade
|
|
80
|
-
maxScore: scoreMaximum,
|
|
81
|
-
startedAt: attempt === null || attempt === void 0 ? void 0 : attempt.timestamp,
|
|
82
|
-
submittedAt: completed === null || completed === void 0 ? void 0 : completed.timestamp,
|
|
83
|
-
userId,
|
|
84
|
-
}),
|
|
72
|
+
grade,
|
|
85
73
|
progress: {
|
|
86
|
-
scaled: completed ? 1 : 0,
|
|
74
|
+
scaled: entry.completed ? 1 : 0,
|
|
87
75
|
},
|
|
88
76
|
name,
|
|
89
77
|
};
|
|
@@ -97,7 +85,7 @@ const getCompletedUserInfosGradeAndProgress = (infos, scoreMaximum, gradePrefere
|
|
|
97
85
|
}));
|
|
98
86
|
exports.getCompletedUserInfosGradeAndProgress = getCompletedUserInfosGradeAndProgress;
|
|
99
87
|
const getCurrentGrade = async (services, registration, options) => {
|
|
100
|
-
var _a;
|
|
88
|
+
var _a, _b;
|
|
101
89
|
const user = await services.ltiAuthProvider.getUser();
|
|
102
90
|
if (!user) {
|
|
103
91
|
return null;
|
|
@@ -115,7 +103,7 @@ const getCurrentGrade = async (services, registration, options) => {
|
|
|
115
103
|
gradePreference,
|
|
116
104
|
});
|
|
117
105
|
}
|
|
118
|
-
if (userInfo.
|
|
106
|
+
if (((_b = userInfo.current) === null || _b === void 0 ? void 0 : _b.completed) || !incompleteAttemptCallback) {
|
|
119
107
|
return getCompletedActivityStateGradeAndProgress({
|
|
120
108
|
name,
|
|
121
109
|
scoreMaximum,
|
|
@@ -137,7 +125,7 @@ const getAssignmentGrades = async (services, assignmentIRI, registration, option
|
|
|
137
125
|
}
|
|
138
126
|
// Partition based on whether the chosen preference has a completed attempt
|
|
139
127
|
const [incompleteInfo, completedInfo] = (0, partition_1.default)((info) => {
|
|
140
|
-
const { completed } =
|
|
128
|
+
const { completed } = getGradingAttemptEntry(info.data, gradePreference);
|
|
141
129
|
return completed === undefined;
|
|
142
130
|
})(mappedInfo);
|
|
143
131
|
return (0, exports.getCompletedUserInfosGradeAndProgress)(completedInfo, scoreMaximum, gradePreference).concat(await incompleteAttemptsCallback(incompleteInfo));
|