@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.
@@ -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
- mostRecentAttemptWithCompleted?: UXapiStatement;
11
- mostRecentAttemptWithCompletedCompleted?: UXapiStatement;
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 oldestStatement: (statements: UXapiStatement[]) => UXapiStatement | undefined;
21
- export declare const mostRecentStatement: (statements: UXapiStatement[]) => UXapiStatement | undefined;
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 oldestStatement = (statements) => statements.reduce((result, statement) => result && (0, isBefore_1.default)((0, parseISO_1.default)('stored' in result && result.stored ? result.stored : result.timestamp), (0, parseISO_1.default)(statement.timestamp)) ? result : statement, statements[0]);
46
- exports.oldestStatement = oldestStatement;
47
- const mostRecentStatement = (statements) => statements.reduce((result, statement) => result && (0, isAfter_1.default)((0, parseISO_1.default)('stored' in result && result.stored ? result.stored : result.timestamp), (0, parseISO_1.default)(statement.timestamp)) ? result : statement, statements[0]);
48
- exports.mostRecentStatement = mostRecentStatement;
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 p.completed.result !== undefined &&
88
- typeof ((_a = p.completed.result.score) === null || _a === void 0 ? void 0 : _a.scaled) === 'number';
89
- };
90
- // Find best scored pair
91
- const bestScoredPair = completedPairs
92
- .filter(hasScaledScore)
93
- .reduce((best, pair) => {
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 pair;
96
- return pair.completed.result.score.scaled > best.completed.result.score.scaled ? pair : best;
80
+ return item;
81
+ return comparator(extractor(best), extractor(item)) ? best : item;
97
82
  }, undefined);
98
- // if no scored pair get newest completed without a score
99
- const bestDefaultPair = !bestScoredPair && completedPairs.length > 0
100
- ? completedPairs.reduce((best, pair) => {
101
- if (!best)
102
- return pair;
103
- return (0, isAfter_1.default)((0, parseISO_1.default)(pair.attempt.timestamp), (0, parseISO_1.default)(best.attempt.timestamp))
104
- ? pair
105
- : best;
106
- }, undefined)
107
- : undefined;
108
- const bestPair = bestScoredPair !== null && bestScoredPair !== void 0 ? bestScoredPair : bestDefaultPair;
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
- bestCompletedAttempt: bestPair === null || bestPair === void 0 ? void 0 : bestPair.attempt,
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
- mostRecentAttemptWithCompleted: mostRecentPair === null || mostRecentPair === void 0 ? void 0 : mostRecentPair.attempt,
118
- mostRecentAttemptWithCompletedCompleted: mostRecentPair === null || mostRecentPair === void 0 ? void 0 : mostRecentPair.completed,
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: (score: {
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 = (score, options) => {
34
- const { raw, scaled, max } = score;
35
- const { maxScore, startedAt, submittedAt, userId } = options;
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
- if (startedAt) {
44
- submission.startedAt = startedAt;
45
- }
46
- if (submittedAt) {
47
- submission.submittedAt = submittedAt;
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: submittedAt ? 'Completed' : 'Started',
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 pickAttemptAndCompleted = (state, gradePreference) => {
63
- if (gradePreference === 'best' && state.bestCompletedAttempt) {
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
- var _a;
77
- const { attempt, completed } = pickAttemptAndCompleted(state, gradePreference);
66
+ const entry = getGradingAttemptEntry(state, gradePreference);
67
+ const grade = (0, exports.getScoreGrade)(entry, {
68
+ maxScore: scoreMaximum,
69
+ userId,
70
+ });
78
71
  return {
79
- grade: (0, exports.getScoreGrade)(((_a = completed === null || completed === void 0 ? void 0 : completed.result) === null || _a === void 0 ? void 0 : _a.score) || {}, {
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.currentAttemptCompleted || !incompleteAttemptCallback) {
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 } = pickAttemptAndCompleted(info.data, gradePreference);
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));