@openstax/ts-utils 1.38.0-beta → 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 +9 -4
- package/dist/cjs/services/lrsGateway/attempt-utils.js +74 -89
- package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +2 -9
- package/dist/cjs/services/lrsGateway/xapiUtils.js +21 -34
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/services/lrsGateway/attempt-utils.d.ts +9 -4
- package/dist/esm/services/lrsGateway/attempt-utils.js +71 -86
- package/dist/esm/services/lrsGateway/xapiUtils.d.ts +2 -9
- package/dist/esm/services/lrsGateway/xapiUtils.js +22 -35
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -14,16 +14,21 @@ export type ActivityState = {
|
|
|
14
14
|
mostRecentWithCompleted?: AttemptEntry;
|
|
15
15
|
};
|
|
16
16
|
export declare const matchAttempt: (statement: UXapiStatement) => boolean;
|
|
17
|
-
export declare const matchAttemptCompleted: (attempt: UXapiStatement) => (statement: UXapiStatement) => boolean;
|
|
18
|
-
export declare const matchAttemptScored: (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 | "";
|
|
19
19
|
export declare const resolveAttempts: (statements: UXapiStatement[], options?: {
|
|
20
20
|
activityIRI?: string;
|
|
21
21
|
parentActivityAttempt?: string;
|
|
22
22
|
}) => UXapiStatement[];
|
|
23
23
|
export declare const resolveCompletedForAttempt: (statements: UXapiStatement[], attempt: UXapiStatement, activityIRI?: string) => UXapiStatement | undefined;
|
|
24
24
|
export declare const resolveScoredForAttempt: (statements: UXapiStatement[], attempt: UXapiStatement, activityIRI?: string) => UXapiStatement | undefined;
|
|
25
|
-
export declare const
|
|
26
|
-
export declare const
|
|
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;
|
|
27
32
|
export declare const resolveAttemptInfo: (statements: UXapiStatement[], options?: {
|
|
28
33
|
activityIRI?: string;
|
|
29
34
|
currentAttempt?: string;
|
|
@@ -21,14 +21,14 @@ export const EXT_PENDING_SCORING = 'https://openstax.org/xapi/extensions/pending
|
|
|
21
21
|
export const matchAttempt = (statement) => statement.verb.id === Verb.Attempted;
|
|
22
22
|
export const matchAttemptCompleted = (attempt) => (statement) => {
|
|
23
23
|
var _a, _b;
|
|
24
|
-
return statement.verb.id === Verb.Completed
|
|
24
|
+
return statement.verb.id === Verb.Completed && attempt.id
|
|
25
25
|
&& statement.context !== undefined
|
|
26
26
|
&& ((_a = statement.context.statement) === null || _a === void 0 ? void 0 : _a.id) === attempt.id
|
|
27
27
|
&& statement.context.registration === ((_b = attempt.context) === null || _b === void 0 ? void 0 : _b.registration);
|
|
28
28
|
};
|
|
29
29
|
export const matchAttemptScored = (attempt) => (statement) => {
|
|
30
30
|
var _a, _b;
|
|
31
|
-
return statement.verb.id === Verb.Scored
|
|
31
|
+
return statement.verb.id === Verb.Scored && attempt.id
|
|
32
32
|
&& statement.context !== undefined
|
|
33
33
|
&& ((_a = statement.context.statement) === null || _a === void 0 ? void 0 : _a.id) === attempt.id
|
|
34
34
|
&& statement.context.registration === ((_b = attempt.context) === null || _b === void 0 ? void 0 : _b.registration);
|
|
@@ -42,101 +42,86 @@ export const resolveAttempts = (statements, options) => statements.filter(statem
|
|
|
42
42
|
export const resolveCompletedForAttempt = (statements, attempt, activityIRI) => statements.find(statement => matchAttemptCompleted(attempt)(statement)
|
|
43
43
|
&& (!activityIRI || statement.object.id === activityIRI));
|
|
44
44
|
export const resolveScoredForAttempt = (statements, attempt, activityIRI) => {
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
// scored or completed statements that contain a score in their
|
|
46
|
+
// result are 'scoring statements' for our purposes.
|
|
47
|
+
const scoringStatements = statements.filter(s => {
|
|
48
|
+
var _a;
|
|
49
|
+
return (matchAttemptScored(attempt)(s) || matchAttemptCompleted(attempt)(s))
|
|
50
|
+
&& ((_a = s.result) === null || _a === void 0 ? void 0 : _a.score)
|
|
51
|
+
&& (!activityIRI || s.object.id === activityIRI);
|
|
52
|
+
});
|
|
53
|
+
return mostRecentStatement(scoringStatements);
|
|
54
|
+
};
|
|
55
|
+
const resolveAttemptEntries = (statements, attempts, activityIRI) => attempts.reduce((acc, attempt) => {
|
|
56
|
+
const completed = resolveCompletedForAttempt(statements, attempt, activityIRI);
|
|
57
|
+
const scored = resolveScoredForAttempt(statements, attempt, activityIRI);
|
|
58
|
+
acc.push({ attempt, completed, scored });
|
|
59
|
+
return acc;
|
|
60
|
+
}, []);
|
|
61
|
+
export const getStatementTimeString = (statement) => 'stored' in statement && statement.stored ? statement.stored : statement.timestamp;
|
|
62
|
+
export const getStatementTime = (statement) => parseISO(getStatementTimeString(statement));
|
|
63
|
+
const findByComparator = (comparator, extractor) => (items) => {
|
|
64
|
+
return items.reduce((best, item) => {
|
|
65
|
+
if (!best)
|
|
66
|
+
return item;
|
|
67
|
+
return comparator(extractor(best), extractor(item)) ? best : item;
|
|
68
|
+
}, undefined);
|
|
69
|
+
};
|
|
70
|
+
export const oldestStatement = findByComparator(isBefore, getStatementTime);
|
|
71
|
+
export const mostRecentStatement = findByComparator(isAfter, getStatementTime);
|
|
72
|
+
export const oldestEntry = findByComparator(isBefore, (entry) => getStatementTime(entry.attempt));
|
|
73
|
+
export const mostRecentEntry = findByComparator(isAfter, (entry) => getStatementTime(entry.attempt));
|
|
74
|
+
const scaledScoreForEntry = (entry) => {
|
|
75
|
+
var _a;
|
|
76
|
+
const scoringStatement = entry.scored;
|
|
77
|
+
const score = (_a = scoringStatement === null || scoringStatement === void 0 ? void 0 : scoringStatement.result) === null || _a === void 0 ? void 0 : _a.score;
|
|
78
|
+
if (!score)
|
|
79
|
+
return { score: undefined, entry };
|
|
80
|
+
// Prefer scaled, otherwise normalize raw/max if available
|
|
81
|
+
if (score.scaled !== undefined)
|
|
82
|
+
return { score: score.scaled, entry };
|
|
83
|
+
if (score.raw !== undefined && score.max !== undefined && score.max !== 0) {
|
|
84
|
+
return { score: score.raw / score.max, entry };
|
|
85
|
+
}
|
|
86
|
+
return { score: undefined, entry };
|
|
55
87
|
};
|
|
56
|
-
|
|
57
|
-
|
|
88
|
+
/*
|
|
89
|
+
* the 'best' entry is the one with the highest scaled score.
|
|
90
|
+
* if two entries have no score, the most recent is considered best.
|
|
91
|
+
*/
|
|
92
|
+
export const bestEntry = findByComparator((a, b) => {
|
|
93
|
+
// if neither has a score, use the most recent
|
|
94
|
+
if (a.score === undefined && b.score === undefined) {
|
|
95
|
+
return isAfter(getStatementTime(a.entry.attempt), getStatementTime(b.entry.attempt));
|
|
96
|
+
}
|
|
97
|
+
if (a.score === undefined)
|
|
98
|
+
return false;
|
|
99
|
+
if (b.score === undefined)
|
|
100
|
+
return true;
|
|
101
|
+
return a.score > b.score;
|
|
102
|
+
}, scaledScoreForEntry);
|
|
58
103
|
export const resolveAttemptInfo = (statements, options) => {
|
|
59
104
|
// TODO optimize. i'm 100% that this could all be done in one iteration but i'm not messing around with that for now.
|
|
60
105
|
const attempts = resolveAttempts(statements, options);
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const completedAttempts = completedPairs.length;
|
|
70
|
-
/* the last attempt sorted by timestamp */
|
|
71
|
-
const currentAttempt = (options === null || options === void 0 ? void 0 : options.currentAttempt)
|
|
72
|
-
? attempts.find(attempt => attempt.id === options.currentAttempt)
|
|
73
|
-
: (options === null || options === void 0 ? void 0 : options.currentPreference) === 'oldest'
|
|
74
|
-
? oldestStatement(attempts)
|
|
75
|
-
: mostRecentStatement(attempts);
|
|
76
|
-
const current = currentAttempt
|
|
77
|
-
? {
|
|
78
|
-
attempt: currentAttempt,
|
|
79
|
-
completed: resolveCompletedForAttempt(statements, currentAttempt, options === null || options === void 0 ? void 0 : options.activityIRI),
|
|
80
|
-
scored: resolveScoredForAttempt(statements, currentAttempt, options === null || options === void 0 ? void 0 : options.activityIRI),
|
|
81
|
-
}
|
|
82
|
-
: undefined;
|
|
83
|
-
const currentAttemptStatements = currentAttempt
|
|
106
|
+
const attemptEntries = resolveAttemptEntries(statements, attempts, options === null || options === void 0 ? void 0 : options.activityIRI);
|
|
107
|
+
const completedEntries = attemptEntries.filter((entry) => !!entry.completed);
|
|
108
|
+
const current = (options === null || options === void 0 ? void 0 : options.currentAttempt)
|
|
109
|
+
? attemptEntries.find(({ attempt }) => attempt.id === options.currentAttempt)
|
|
110
|
+
: (options === null || options === void 0 ? void 0 : options.currentPreference) === 'oldest' /* the attempt sorted by timestamp */
|
|
111
|
+
? oldestEntry(attemptEntries)
|
|
112
|
+
: mostRecentEntry(attemptEntries);
|
|
113
|
+
const currentAttemptStatements = current
|
|
84
114
|
? statements.filter(statement => {
|
|
85
115
|
var _a;
|
|
86
116
|
return (!(options === null || options === void 0 ? void 0 : options.activityIRI) || statement.object.id === options.activityIRI) &&
|
|
87
|
-
((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) ===
|
|
117
|
+
((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) === current.attempt.id;
|
|
88
118
|
})
|
|
89
119
|
: [];
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
return pair;
|
|
93
|
-
return isAfter(parseISO(cur.attempt.timestamp), parseISO(pair.attempt.timestamp)) ? cur : pair;
|
|
94
|
-
}, undefined);
|
|
95
|
-
const mostRecentWithCompleted = mostRecentPair
|
|
96
|
-
? {
|
|
97
|
-
attempt: mostRecentPair.attempt,
|
|
98
|
-
completed: resolveCompletedForAttempt(statements, mostRecentPair.attempt, options === null || options === void 0 ? void 0 : options.activityIRI),
|
|
99
|
-
scored: resolveScoredForAttempt(statements, mostRecentPair.attempt, options === null || options === void 0 ? void 0 : options.activityIRI),
|
|
100
|
-
}
|
|
101
|
-
: undefined;
|
|
102
|
-
const scaledForAttempt = (attempt) => {
|
|
103
|
-
var _a;
|
|
104
|
-
// resolveScoredForAttempt may return a Scored, or fallback to a Completed that has a score
|
|
105
|
-
const picked = resolveScoredForAttempt(statements, attempt, options === null || options === void 0 ? void 0 : options.activityIRI);
|
|
106
|
-
const score = (_a = picked === null || picked === void 0 ? void 0 : picked.result) === null || _a === void 0 ? void 0 : _a.score;
|
|
107
|
-
if (!score)
|
|
108
|
-
return undefined;
|
|
109
|
-
// Prefer scaled, otherwise normalize raw/max if available
|
|
110
|
-
if (score.scaled !== undefined)
|
|
111
|
-
return score.scaled;
|
|
112
|
-
if (score.raw !== undefined && score.max !== undefined && score.max !== 0) {
|
|
113
|
-
return score.raw / score.max;
|
|
114
|
-
}
|
|
115
|
-
return undefined;
|
|
116
|
-
};
|
|
117
|
-
let bestAttempt = completedPairs.reduce((best, { attempt }) => {
|
|
118
|
-
const bScaled = best !== undefined ? scaledForAttempt(best) : undefined;
|
|
119
|
-
const aScaled = scaledForAttempt(attempt);
|
|
120
|
-
if (bScaled === undefined)
|
|
121
|
-
return aScaled === undefined ? best : attempt;
|
|
122
|
-
if (aScaled === undefined)
|
|
123
|
-
return best;
|
|
124
|
-
return aScaled > bScaled ? attempt : best;
|
|
125
|
-
}, undefined);
|
|
126
|
-
// Fallback if no attempt had a score, use newest completed as best
|
|
127
|
-
if (!bestAttempt && mostRecentPair) {
|
|
128
|
-
bestAttempt = mostRecentPair.attempt;
|
|
129
|
-
}
|
|
130
|
-
const best = bestAttempt
|
|
131
|
-
? {
|
|
132
|
-
attempt: bestAttempt,
|
|
133
|
-
completed: resolveCompletedForAttempt(statements, bestAttempt, options && options.activityIRI),
|
|
134
|
-
scored: resolveScoredForAttempt(statements, bestAttempt, options && options.activityIRI),
|
|
135
|
-
}
|
|
136
|
-
: undefined;
|
|
120
|
+
const mostRecentWithCompleted = mostRecentEntry(completedEntries);
|
|
121
|
+
const best = bestEntry(completedEntries);
|
|
137
122
|
return {
|
|
138
123
|
attempts: attempts.length,
|
|
139
|
-
completedAttempts,
|
|
124
|
+
completedAttempts: completedEntries.length,
|
|
140
125
|
currentAttemptStatements,
|
|
141
126
|
current,
|
|
142
127
|
best,
|
|
@@ -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 = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import partition from 'lodash/fp/partition';
|
|
2
2
|
import { roundToPrecision } from '../..';
|
|
3
|
-
import { EXT_PENDING_SCORING, resolveAttemptInfo } from './attempt-utils';
|
|
3
|
+
import { EXT_PENDING_SCORING, getStatementTimeString, resolveAttemptInfo } from './attempt-utils';
|
|
4
4
|
export const getRegistrationAttemptInfo = async (lrs, registration, options) => {
|
|
5
5
|
const { currentPreference, ...xapiOptions } = options !== null && options !== void 0 ? options : {};
|
|
6
6
|
const allStatements = await lrs.getAllXapiStatements({ ...xapiOptions, registration });
|
|
@@ -23,60 +23,47 @@ export const getRegistrationAttemptInfo = async (lrs, registration, options) =>
|
|
|
23
23
|
// lti: http://www.imsglobal.org/spec/lti-ags/v2p0#score-publish-service
|
|
24
24
|
// ltijs: https://cvmcosta.me/ltijs/#/grading
|
|
25
25
|
// Note: "min" is currently completely ignored
|
|
26
|
-
export const getScoreGrade = (
|
|
27
|
-
|
|
28
|
-
const {
|
|
26
|
+
export const getScoreGrade = (entry, options) => {
|
|
27
|
+
var _a, _b, _c, _d, _e;
|
|
28
|
+
const { raw, scaled, max } = ((_b = (_a = entry.scored) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.score) || {};
|
|
29
|
+
const { maxScore, userId } = options;
|
|
29
30
|
const scoreMaximum = maxScore !== null && maxScore !== void 0 ? maxScore : 100;
|
|
30
31
|
const scoreGiven = raw && max
|
|
31
32
|
? scoreMaximum / max * raw
|
|
32
33
|
: scaled
|
|
33
34
|
? scaled * scoreMaximum
|
|
34
35
|
: 0;
|
|
35
|
-
const submission = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
36
|
+
const submission = {
|
|
37
|
+
startedAt: entry.attempt ? getStatementTimeString(entry.attempt) : undefined,
|
|
38
|
+
submittedAt: entry.completed ? getStatementTimeString(entry.completed) : undefined,
|
|
39
|
+
};
|
|
40
|
+
const pendingManual = entry.scored === undefined
|
|
41
|
+
&& ((_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[EXT_PENDING_SCORING]) === 'true';
|
|
42
42
|
return {
|
|
43
43
|
userId,
|
|
44
|
-
activityProgress:
|
|
44
|
+
activityProgress: entry.completed ? 'Completed' : 'Started',
|
|
45
45
|
// canvas assumes that anything that isn't 'FullyGraded' requires manual grading and displays a "needs grading" icon.
|
|
46
46
|
// if you warp your mind you can consider the portion of the assignment which is completed to be fully graded.
|
|
47
|
-
gradingProgress: 'FullyGraded',
|
|
47
|
+
gradingProgress: pendingManual ? 'PendingManual' : 'FullyGraded',
|
|
48
48
|
scoreMaximum,
|
|
49
49
|
scoreGiven: roundToPrecision(scoreGiven, -2),
|
|
50
50
|
submission,
|
|
51
51
|
};
|
|
52
52
|
};
|
|
53
53
|
// These methods assign 0's to incomplete activities
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const entry = (gradePreference === 'best' ? state.best : state.current);
|
|
57
|
-
return {
|
|
58
|
-
attempt: entry === null || entry === void 0 ? void 0 : entry.attempt,
|
|
59
|
-
completedOrScored: (_a = entry === null || entry === void 0 ? void 0 : entry.scored) !== null && _a !== void 0 ? _a : entry === null || entry === void 0 ? void 0 : entry.completed,
|
|
60
|
-
completed: entry === null || entry === void 0 ? void 0 : entry.completed,
|
|
61
|
-
};
|
|
54
|
+
const getGradingAttemptEntry = (state, gradePreference) => {
|
|
55
|
+
return (gradePreference === 'best' ? state.best : state.current) || {};
|
|
62
56
|
};
|
|
63
57
|
const getCompletedActivityStateGradeAndProgress = ({ name, scoreMaximum, state, userId, gradePreference = 'current' }) => {
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const grade = getScoreGrade(((_a = completedOrScored === null || completedOrScored === void 0 ? void 0 : completedOrScored.result) === null || _a === void 0 ? void 0 : _a.score) || {}, {
|
|
58
|
+
const entry = getGradingAttemptEntry(state, gradePreference);
|
|
59
|
+
const grade = getScoreGrade(entry, {
|
|
67
60
|
maxScore: scoreMaximum,
|
|
68
|
-
startedAt: attempt === null || attempt === void 0 ? void 0 : attempt.timestamp,
|
|
69
|
-
submittedAt: completed === null || completed === void 0 ? void 0 : completed.timestamp,
|
|
70
61
|
userId,
|
|
71
62
|
});
|
|
72
|
-
// If this Completed is explicitly "pending scoring", surface that in the grade.
|
|
73
|
-
if (((_c = (_b = completedOrScored === null || completedOrScored === void 0 ? void 0 : completedOrScored.result) === null || _b === void 0 ? void 0 : _b.extensions) === null || _c === void 0 ? void 0 : _c[EXT_PENDING_SCORING]) === 'true') {
|
|
74
|
-
grade.gradingProgress = 'PendingManual';
|
|
75
|
-
}
|
|
76
63
|
return {
|
|
77
64
|
grade,
|
|
78
65
|
progress: {
|
|
79
|
-
scaled:
|
|
66
|
+
scaled: entry.completed ? 1 : 0,
|
|
80
67
|
},
|
|
81
68
|
name,
|
|
82
69
|
};
|
|
@@ -89,7 +76,7 @@ export const getCompletedUserInfosGradeAndProgress = (infos, scoreMaximum, grade
|
|
|
89
76
|
gradePreference,
|
|
90
77
|
}));
|
|
91
78
|
export const getCurrentGrade = async (services, registration, options) => {
|
|
92
|
-
var _a;
|
|
79
|
+
var _a, _b;
|
|
93
80
|
const user = await services.ltiAuthProvider.getUser();
|
|
94
81
|
if (!user) {
|
|
95
82
|
return null;
|
|
@@ -107,7 +94,7 @@ export const getCurrentGrade = async (services, registration, options) => {
|
|
|
107
94
|
gradePreference,
|
|
108
95
|
});
|
|
109
96
|
}
|
|
110
|
-
if (userInfo.current
|
|
97
|
+
if (((_b = userInfo.current) === null || _b === void 0 ? void 0 : _b.completed) || !incompleteAttemptCallback) {
|
|
111
98
|
return getCompletedActivityStateGradeAndProgress({
|
|
112
99
|
name,
|
|
113
100
|
scoreMaximum,
|
|
@@ -128,8 +115,8 @@ export const getAssignmentGrades = async (services, assignmentIRI, registration,
|
|
|
128
115
|
}
|
|
129
116
|
// Partition based on whether the chosen preference has a completed attempt
|
|
130
117
|
const [incompleteInfo, completedInfo] = partition((info) => {
|
|
131
|
-
const {
|
|
132
|
-
return
|
|
118
|
+
const { completed } = getGradingAttemptEntry(info.data, gradePreference);
|
|
119
|
+
return completed === undefined;
|
|
133
120
|
})(mappedInfo);
|
|
134
121
|
return getCompletedUserInfosGradeAndProgress(completedInfo, scoreMaximum, gradePreference).concat(await incompleteAttemptsCallback(incompleteInfo));
|
|
135
122
|
};
|