@speakableio/core 0.1.19 → 0.1.21

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/index.d.mts CHANGED
@@ -729,10 +729,11 @@ declare const useCreateNotification: () => {
729
729
  };
730
730
 
731
731
  type FsClient = ReturnType<typeof createFsClient>;
732
- declare function createFsClient({ db, platform, httpsCallable, }: {
732
+ declare function createFsClient({ db, platform, httpsCallable, logEvent, }: {
733
733
  db: FirebaseInstance;
734
734
  platform: 'web' | 'native';
735
735
  httpsCallable: (name: string) => CallableFunction;
736
+ logEvent: (name: string, data: any) => void;
736
737
  }): Promise<{
737
738
  assignmentRepo: {
738
739
  getAssignment: (params: {
@@ -969,21 +970,27 @@ interface FsContext {
969
970
  permissions: Permissions;
970
971
  }
971
972
  declare const FsCtx: React.Context<FsContext | null>;
972
- declare function SpeakableProvider({ db, platform, children, queryClient, user, permissions, httpsCallable, }: {
973
+ declare function SpeakableProvider({ db, platform, children, queryClient, user, permissions, httpsCallable, logEvent, }: {
974
+ children: React.ReactNode;
973
975
  user: User;
974
976
  db: FirebaseInstance;
975
977
  platform: 'web' | 'native';
976
978
  queryClient: QueryClient;
977
979
  permissions: Permissions;
978
980
  httpsCallable: (name: string) => CallableFunction;
979
- children: React.ReactNode;
981
+ logEvent: (name: string, data: any) => void;
980
982
  }): react_jsx_runtime.JSX.Element | null;
981
983
  declare function useSpeakableApi(): FsContext;
982
984
 
983
- declare function useActivity({ id, isAssignment, onAssignmentSubmitted, }: {
985
+ declare function useActivity({ id, isAssignment, onAssignmentSubmitted, ltiData, }: {
984
986
  id: string;
985
987
  isAssignment: boolean;
986
988
  onAssignmentSubmitted: (assignmentId: string) => void;
989
+ ltiData?: {
990
+ lineItemId?: string;
991
+ lti_id?: string;
992
+ serviceKey?: string;
993
+ };
987
994
  }): {
988
995
  set: {
989
996
  data: SetWithId | null | undefined;
@@ -1093,7 +1100,14 @@ declare function useActivity({ id, isAssignment, onAssignmentSubmitted, }: {
1093
1100
  message?: undefined;
1094
1101
  }>;
1095
1102
  updateCard: (cardId: string, cardScore: CardScore) => void;
1096
- logGradingStandardEntry: () => void;
1103
+ logGradingStandardEntry: ({ cardId, gradingStandard, type, }: {
1104
+ cardId: string;
1105
+ gradingStandard: {
1106
+ level: string;
1107
+ justification: string;
1108
+ };
1109
+ type: "actfl" | "wida" | "custom";
1110
+ }) => void;
1097
1111
  };
1098
1112
  };
1099
1113
  };
package/dist/index.d.ts CHANGED
@@ -729,10 +729,11 @@ declare const useCreateNotification: () => {
729
729
  };
730
730
 
731
731
  type FsClient = ReturnType<typeof createFsClient>;
732
- declare function createFsClient({ db, platform, httpsCallable, }: {
732
+ declare function createFsClient({ db, platform, httpsCallable, logEvent, }: {
733
733
  db: FirebaseInstance;
734
734
  platform: 'web' | 'native';
735
735
  httpsCallable: (name: string) => CallableFunction;
736
+ logEvent: (name: string, data: any) => void;
736
737
  }): Promise<{
737
738
  assignmentRepo: {
738
739
  getAssignment: (params: {
@@ -969,21 +970,27 @@ interface FsContext {
969
970
  permissions: Permissions;
970
971
  }
971
972
  declare const FsCtx: React.Context<FsContext | null>;
972
- declare function SpeakableProvider({ db, platform, children, queryClient, user, permissions, httpsCallable, }: {
973
+ declare function SpeakableProvider({ db, platform, children, queryClient, user, permissions, httpsCallable, logEvent, }: {
974
+ children: React.ReactNode;
973
975
  user: User;
974
976
  db: FirebaseInstance;
975
977
  platform: 'web' | 'native';
976
978
  queryClient: QueryClient;
977
979
  permissions: Permissions;
978
980
  httpsCallable: (name: string) => CallableFunction;
979
- children: React.ReactNode;
981
+ logEvent: (name: string, data: any) => void;
980
982
  }): react_jsx_runtime.JSX.Element | null;
981
983
  declare function useSpeakableApi(): FsContext;
982
984
 
983
- declare function useActivity({ id, isAssignment, onAssignmentSubmitted, }: {
985
+ declare function useActivity({ id, isAssignment, onAssignmentSubmitted, ltiData, }: {
984
986
  id: string;
985
987
  isAssignment: boolean;
986
988
  onAssignmentSubmitted: (assignmentId: string) => void;
989
+ ltiData?: {
990
+ lineItemId?: string;
991
+ lti_id?: string;
992
+ serviceKey?: string;
993
+ };
987
994
  }): {
988
995
  set: {
989
996
  data: SetWithId | null | undefined;
@@ -1093,7 +1100,14 @@ declare function useActivity({ id, isAssignment, onAssignmentSubmitted, }: {
1093
1100
  message?: undefined;
1094
1101
  }>;
1095
1102
  updateCard: (cardId: string, cardScore: CardScore) => void;
1096
- logGradingStandardEntry: () => void;
1103
+ logGradingStandardEntry: ({ cardId, gradingStandard, type, }: {
1104
+ cardId: string;
1105
+ gradingStandard: {
1106
+ level: string;
1107
+ justification: string;
1108
+ };
1109
+ type: "actfl" | "wida" | "custom";
1110
+ }) => void;
1097
1111
  };
1098
1112
  };
1099
1113
  };
package/dist/index.js CHANGED
@@ -57,6 +57,9 @@ var FirebaseAPI = class _FirebaseAPI {
57
57
  httpsCallable(functionName) {
58
58
  return _optionalChain([this, 'access', _2 => _2.config, 'optionalAccess', _3 => _3.httpsCallable, 'call', _4 => _4(functionName)]);
59
59
  }
60
+ logEvent(name, data) {
61
+ _optionalChain([this, 'access', _5 => _5.config, 'optionalAccess', _6 => _6.logEvent, 'call', _7 => _7(name, data)]);
62
+ }
60
63
  accessQueryConstraints() {
61
64
  const { query, orderBy, limit, startAt, startAfter, endAt, endBefore } = this.helpers;
62
65
  return {
@@ -87,8 +90,6 @@ var FirebaseAPI = class _FirebaseAPI {
87
90
  id: docSnap.id,
88
91
  ...docSnap.data()
89
92
  } : null;
90
- console.log("getDoc", path);
91
- console.log("getDoc db", this.db);
92
93
  return {
93
94
  id: docSnap.id,
94
95
  data
@@ -99,8 +100,6 @@ var FirebaseAPI = class _FirebaseAPI {
99
100
  const collectionRef = collection(this.db, path);
100
101
  const q = queryConstraints.length > 0 ? query(collectionRef, ...queryConstraints) : collectionRef;
101
102
  const querySnapshot = await getDocs(q);
102
- console.log("getDocs", path, queryConstraints);
103
- console.log("getDocs db", this.db);
104
103
  const data = querySnapshot.docs.map((doc) => ({
105
104
  id: doc.id,
106
105
  data: doc.data()
@@ -122,22 +121,16 @@ var FirebaseAPI = class _FirebaseAPI {
122
121
  async setDoc(path, data, options = {}) {
123
122
  const { setDoc, doc } = this.helpers;
124
123
  const docRef = doc(this.db, path);
125
- console.log("setDoc", path, data);
126
- console.log("setDoc db", this.db);
127
124
  await setDoc(docRef, data, options);
128
125
  }
129
126
  async updateDoc(path, data) {
130
127
  const { updateDoc, doc } = this.helpers;
131
- console.log("updateDoc", path, data);
132
- console.log("updateDoc db", this.db);
133
128
  const docRef = doc(this.db, path);
134
129
  await updateDoc(docRef, data);
135
130
  }
136
131
  async deleteDoc(path) {
137
132
  const { deleteDoc, doc } = this.helpers;
138
133
  const docRef = doc(this.db, path);
139
- console.log("deleteDoc", path);
140
- console.log("deleteDoc db", this.db);
141
134
  await deleteDoc(docRef);
142
135
  }
143
136
  async runTransaction(updateFunction) {
@@ -505,7 +498,7 @@ var purify = (word) => {
505
498
  return word.normalize("NFD").replace(/\/([^" "]*)/g, "").replace(/\([^()]*\)/g, "").replace(/([^()]*)/g, "").replace(/[\u0300-\u036f]/g, "").replace(/[-]/g, " ").replace(/[.,/#!¡¿?؟。,.?$%^&*;:{}=\-_`~()’'…\s]/g, "").replace(/\s\s+/g, " ").toLowerCase().trim();
506
499
  };
507
500
  var cleanString = (words) => {
508
- const splitWords = _optionalChain([words, 'optionalAccess', _5 => _5.split, 'call', _6 => _6("+")]);
501
+ const splitWords = _optionalChain([words, 'optionalAccess', _8 => _8.split, 'call', _9 => _9("+")]);
509
502
  if (splitWords && splitWords.length === 1) {
510
503
  const newWord = purify(words);
511
504
  return newWord;
@@ -519,13 +512,14 @@ var cleanString = (words) => {
519
512
  var getWordHash = (word, language) => {
520
513
  const cleanedWord = cleanString(word);
521
514
  const wordHash = _jssha12.default.call(void 0, `${language}-${cleanedWord}`);
515
+ console.log("wordHash core library", wordHash);
522
516
  return wordHash;
523
517
  };
524
518
 
525
519
  // src/domains/cards/services/get-card-verification-status.service.ts
526
520
  var charactarLanguages = ["zh", "ja", "ko"];
527
521
  var getVerificationStatus = async (target_text, language) => {
528
- if (_optionalChain([target_text, 'optionalAccess', _7 => _7.length]) < 3 && !charactarLanguages.includes(language)) {
522
+ if (_optionalChain([target_text, 'optionalAccess', _10 => _10.length]) < 3 && !charactarLanguages.includes(language)) {
529
523
  return "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
530
524
  }
531
525
  const hash = getWordHash(target_text, language);
@@ -758,12 +752,13 @@ var SpeakableFirebaseFunctions = {
758
752
  submitAssignmentV2: api.httpsCallable("submitLTIAssignmentScoreV2"),
759
753
  submitAssessment: api.httpsCallable("submitAssessment"),
760
754
  sendAssessmentScoredEmail: api.httpsCallable("sendAssessmentScoredEmail"),
761
- createNotification: api.httpsCallable("createNotificationV2")
755
+ createNotification: api.httpsCallable("createNotificationV2"),
756
+ updateCourseAnalytics: api.httpsCallable("handleCouresAnalyticsEvent")
762
757
  };
763
758
 
764
759
  // src/domains/notification/services/send-notification.service.ts
765
760
  var _sendNotification = async (sendTo, notification) => {
766
- const results = await _optionalChain([SpeakableFirebaseFunctions, 'access', _8 => _8.createNotification, 'optionalCall', _9 => _9({
761
+ const results = await _optionalChain([SpeakableFirebaseFunctions, 'access', _11 => _11.createNotification, 'optionalCall', _12 => _12({
767
762
  sendTo,
768
763
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
769
764
  notification
@@ -843,7 +838,7 @@ var createNewAssignmentNotification = async ({
843
838
  link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
844
839
  messagePreview: `A new assignment "${assignment.name}" is now available. ${dueDate ? `Due ${dueDate}` : ""}`,
845
840
  title: "New Assignment Available!",
846
- imageUrl: _optionalChain([profile, 'access', _10 => _10.image, 'optionalAccess', _11 => _11.url])
841
+ imageUrl: _optionalChain([profile, 'access', _13 => _13.image, 'optionalAccess', _14 => _14.url])
847
842
  });
848
843
  return results;
849
844
  };
@@ -860,7 +855,7 @@ var createAssessmentSubmissionNotification = async ({
860
855
  title: `Assessment Submitted!`,
861
856
  senderName: studentName,
862
857
  messagePreview: `${studentName} has submitted the assessment "${assignment.name}"`,
863
- imageUrl: _optionalChain([profile, 'access', _12 => _12.image, 'optionalAccess', _13 => _13.url])
858
+ imageUrl: _optionalChain([profile, 'access', _15 => _15.image, 'optionalAccess', _16 => _16.url])
864
859
  });
865
860
  return results;
866
861
  };
@@ -878,13 +873,13 @@ var createAssessmentScoredNotification = async ({
878
873
  link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
879
874
  title,
880
875
  messagePreview,
881
- imageUrl: _optionalChain([profile, 'access', _14 => _14.image, 'optionalAccess', _15 => _15.url]),
876
+ imageUrl: _optionalChain([profile, 'access', _17 => _17.image, 'optionalAccess', _18 => _18.url]),
882
877
  senderName: teacherName
883
878
  });
884
- await _optionalChain([SpeakableFirebaseFunctions, 'access', _16 => _16.sendAssessmentScoredEmail, 'optionalCall', _17 => _17({
879
+ await _optionalChain([SpeakableFirebaseFunctions, 'access', _19 => _19.sendAssessmentScoredEmail, 'optionalCall', _20 => _20({
885
880
  assessmentTitle: assignment.name,
886
881
  link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
887
- senderImage: _optionalChain([profile, 'access', _18 => _18.image, 'optionalAccess', _19 => _19.url]) || "",
882
+ senderImage: _optionalChain([profile, 'access', _21 => _21.image, 'optionalAccess', _22 => _22.url]) || "",
888
883
  studentId: sendTo[0],
889
884
  teacherName: profile.displayName
890
885
  })]);
@@ -902,11 +897,11 @@ var useCreateNotification = () => {
902
897
  const result = await createNotification({
903
898
  type,
904
899
  userId: user.auth.uid,
905
- profile: _nullishCoalesce(_optionalChain([user, 'optionalAccess', _20 => _20.profile]), () => ( {})),
900
+ profile: _nullishCoalesce(_optionalChain([user, 'optionalAccess', _23 => _23.profile]), () => ( {})),
906
901
  data
907
902
  });
908
903
  queryClient.invalidateQueries({
909
- queryKey: notificationQueryKeys.byId(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _21 => _21.auth, 'access', _22 => _22.uid]), () => ( "")))
904
+ queryKey: notificationQueryKeys.byId(_nullishCoalesce(_optionalChain([user, 'optionalAccess', _24 => _24.auth, 'access', _25 => _25.uid]), () => ( "")))
910
905
  });
911
906
  return result;
912
907
  };
@@ -919,14 +914,16 @@ var useCreateNotification = () => {
919
914
  async function createFsClient({
920
915
  db,
921
916
  platform,
922
- httpsCallable
917
+ httpsCallable,
918
+ logEvent
923
919
  }) {
924
920
  const dbAsFirestore = db;
925
921
  const helpers = platform === "web" ? await Promise.resolve().then(() => _interopRequireWildcard(require("firebase/firestore"))) : await Promise.resolve().then(() => _interopRequireWildcard(require("@react-native-firebase/firestore")));
926
922
  api.initialize({
927
923
  db: dbAsFirestore,
928
924
  helpers,
929
- httpsCallable
925
+ httpsCallable,
926
+ logEvent
930
927
  });
931
928
  return {
932
929
  assignmentRepo: createAssignmentRepo(),
@@ -944,14 +941,16 @@ function SpeakableProvider({
944
941
  queryClient,
945
942
  user,
946
943
  permissions,
947
- httpsCallable
944
+ httpsCallable,
945
+ logEvent
948
946
  }) {
949
947
  const [speakableApi, setSpeakableApi] = _react.useState.call(void 0, null);
950
948
  _react.useEffect.call(void 0, () => {
951
949
  createFsClient({
952
950
  db,
953
951
  httpsCallable,
954
- platform
952
+ platform,
953
+ logEvent
955
954
  }).then((repos) => {
956
955
  setSpeakableApi(repos);
957
956
  });
@@ -1174,7 +1173,7 @@ var SpeakablePlanHierarchy = [
1174
1173
  // src/hooks/usePermissions.ts
1175
1174
  var usePermissions = () => {
1176
1175
  const { permissions } = useSpeakableApi();
1177
- const has = (permission) => _optionalChain([permissions, 'access', _23 => _23.permissions, 'optionalAccess', _24 => _24.includes, 'call', _25 => _25(permission)]);
1176
+ const has = (permission) => _optionalChain([permissions, 'access', _26 => _26.permissions, 'optionalAccess', _27 => _27.includes, 'call', _28 => _28(permission)]);
1178
1177
  return {
1179
1178
  plan: permissions.plan,
1180
1179
  permissionsLoaded: permissions.loaded,
@@ -1213,8 +1212,8 @@ var useGoogleClassroom = () => {
1213
1212
  message: "No Google Classroom ID found"
1214
1213
  };
1215
1214
  const { courseWorkId, maxPoints, owners, courseId } = assignment;
1216
- const draftGrade = _optionalChain([scores, 'optionalAccess', _26 => _26.score]) ? _optionalChain([scores, 'optionalAccess', _27 => _27.score]) / 100 * maxPoints : 0;
1217
- const result = await _optionalChain([SpeakableFirebaseFunctions, 'access', _28 => _28.submitAssignmentToGoogleClassroomV2, 'optionalCall', _29 => _29({
1215
+ const draftGrade = _optionalChain([scores, 'optionalAccess', _29 => _29.score]) ? _optionalChain([scores, 'optionalAccess', _30 => _30.score]) / 100 * maxPoints : 0;
1216
+ const result = await _optionalChain([SpeakableFirebaseFunctions, 'access', _31 => _31.submitAssignmentToGoogleClassroomV2, 'optionalCall', _32 => _32({
1218
1217
  teacherId: owners[0],
1219
1218
  courseId,
1220
1219
  courseWorkId,
@@ -1231,6 +1230,61 @@ var useGoogleClassroom = () => {
1231
1230
  };
1232
1231
  };
1233
1232
 
1233
+ // src/lib/firebase/firebase-analytics/grading-standard.ts
1234
+ var logGradingStandardLog = (data) => {
1235
+ if (data.courseId && data.type && data.level) {
1236
+ _optionalChain([SpeakableFirebaseFunctions, 'access', _33 => _33.updateCourseAnalytics, 'optionalCall', _34 => _34({
1237
+ eventType: data.type || "custom",
1238
+ level: data.level,
1239
+ courseId: data.courseId
1240
+ })]);
1241
+ }
1242
+ api.logEvent("logGradingStandard", data);
1243
+ };
1244
+
1245
+ // src/constants/analytics.constants.ts
1246
+ var ANALYTICS_EVENT_TYPES = {
1247
+ VOICE_SUCCESS: "voice_success",
1248
+ VOICE_FAIL: "voice_fail",
1249
+ RESPOND_CARD_SUCCESS: "respond_card_success",
1250
+ RESPOND_CARD_FAIL: "respond_card_fail",
1251
+ RESPOND_WRITE_CARD_SUCCESS: "respond_write_card_success",
1252
+ RESPOND_WRITE_CARD_FAIL: "respond_write_card_fail",
1253
+ RESPOND_FREE_PLAN: "respond_free_plan",
1254
+ RESPOND_WRITE_FREE_PLAN: "respond_write_free_plan",
1255
+ SUBMISSION: "assignment_submitted",
1256
+ ASSIGNMENT_STARTED: "assignment_started",
1257
+ CREATE_ASSIGNMENT: "create_assignment",
1258
+ MC_SUCCESS: "multiple_choice_success",
1259
+ MC_FAIL: "multiple_choice_fail",
1260
+ ACTFL_LEVEL: "actfl_level",
1261
+ WIDA_LEVEL: "wida_level"
1262
+ };
1263
+
1264
+ // src/lib/firebase/firebase-analytics/assignment.ts
1265
+ var logOpenAssignment = (data = {}) => {
1266
+ api.logEvent("open_assignment", data);
1267
+ };
1268
+ var logOpenActivityPreview = (data = {}) => {
1269
+ api.logEvent("open_activity_preview", data);
1270
+ };
1271
+ var logSubmitAssignment = (data = {}) => {
1272
+ _optionalChain([SpeakableFirebaseFunctions, 'access', _35 => _35.updateCourseAnalytics, 'optionalCall', _36 => _36({
1273
+ eventType: ANALYTICS_EVENT_TYPES.SUBMISSION,
1274
+ ...data
1275
+ })]);
1276
+ api.logEvent(ANALYTICS_EVENT_TYPES.SUBMISSION, data);
1277
+ };
1278
+ var logStartAssignment = (data = {}) => {
1279
+ if (data.courseId) {
1280
+ _optionalChain([SpeakableFirebaseFunctions, 'access', _37 => _37.updateCourseAnalytics, 'optionalCall', _38 => _38({
1281
+ eventType: ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED,
1282
+ ...data
1283
+ })]);
1284
+ }
1285
+ api.logEvent(ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED, data);
1286
+ };
1287
+
1234
1288
  // src/domains/assignment/utils/create-default-score.ts
1235
1289
  var defaultScore = (props) => {
1236
1290
  const { serverTimestamp } = api.accessHelpers();
@@ -1269,7 +1323,7 @@ async function _createScore(params) {
1269
1323
  id: params.activityId,
1270
1324
  userId: params.userId
1271
1325
  });
1272
- await _optionalChain([SpeakableFirebaseFunctions, 'access', _30 => _30.updateAssignmentGradebookStatus, 'optionalCall', _31 => _31({
1326
+ await _optionalChain([SpeakableFirebaseFunctions, 'access', _39 => _39.updateAssignmentGradebookStatus, 'optionalCall', _40 => _40({
1273
1327
  assignmentId: params.activityId,
1274
1328
  userId: params.userId,
1275
1329
  status: "IN_PROGRESS",
@@ -1313,6 +1367,9 @@ async function getAssignmentScore({
1313
1367
  }),
1314
1368
  assignmentId: assignment.id
1315
1369
  };
1370
+ logStartAssignment({
1371
+ courseId: assignment.courseId
1372
+ });
1316
1373
  const result = await createScore({
1317
1374
  activityId: assignment.id,
1318
1375
  userId,
@@ -1371,16 +1428,15 @@ var getScore = withErrorHandler(_getScore, "getScore");
1371
1428
 
1372
1429
  // src/domains/assignment/utils/calculateScoreAndProgress.ts
1373
1430
  var calculateScoreAndProgress = (scores, cardsList, weights) => {
1374
- console.log("calculateScoreAndProgress", scores, cardsList, weights);
1375
1431
  const totalSetPoints = cardsList.reduce((acc, cardId) => {
1376
- acc += _optionalChain([weights, 'optionalAccess', _32 => _32[cardId]]) || 1;
1432
+ acc += _optionalChain([weights, 'optionalAccess', _41 => _41[cardId]]) || 1;
1377
1433
  return acc;
1378
1434
  }, 0);
1379
- const totalPointsAwarded = Object.keys(_optionalChain([scores, 'optionalAccess', _33 => _33.cards]) || {}).reduce((acc, cardId) => {
1380
- const cardScores = _optionalChain([scores, 'optionalAccess', _34 => _34.cards, 'optionalAccess', _35 => _35[cardId]]);
1381
- if (_optionalChain([cardScores, 'optionalAccess', _36 => _36.completed]) || _optionalChain([cardScores, 'optionalAccess', _37 => _37.score]) || _optionalChain([cardScores, 'optionalAccess', _38 => _38.score]) === 0) {
1382
- const score2 = _optionalChain([cardScores, 'optionalAccess', _39 => _39.score]) || _optionalChain([cardScores, 'optionalAccess', _40 => _40.score]) === 0 ? Number(_nullishCoalesce(_optionalChain([cardScores, 'optionalAccess', _41 => _41.score]), () => ( 0))) : null;
1383
- const weight = _optionalChain([weights, 'optionalAccess', _42 => _42[cardId]]) || 1;
1435
+ const totalPointsAwarded = Object.keys(_optionalChain([scores, 'optionalAccess', _42 => _42.cards]) || {}).reduce((acc, cardId) => {
1436
+ const cardScores = _optionalChain([scores, 'optionalAccess', _43 => _43.cards, 'optionalAccess', _44 => _44[cardId]]);
1437
+ if (_optionalChain([cardScores, 'optionalAccess', _45 => _45.completed]) || _optionalChain([cardScores, 'optionalAccess', _46 => _46.score]) || _optionalChain([cardScores, 'optionalAccess', _47 => _47.score]) === 0) {
1438
+ const score2 = _optionalChain([cardScores, 'optionalAccess', _48 => _48.score]) || _optionalChain([cardScores, 'optionalAccess', _49 => _49.score]) === 0 ? Number(_nullishCoalesce(_optionalChain([cardScores, 'optionalAccess', _50 => _50.score]), () => ( 0))) : null;
1439
+ const weight = _optionalChain([weights, 'optionalAccess', _51 => _51[cardId]]) || 1;
1384
1440
  const fraction = (_nullishCoalesce(score2, () => ( 0))) / 100;
1385
1441
  if (score2 || score2 === 0) {
1386
1442
  acc += weight * fraction;
@@ -1390,9 +1446,9 @@ var calculateScoreAndProgress = (scores, cardsList, weights) => {
1390
1446
  }
1391
1447
  return acc;
1392
1448
  }, 0);
1393
- const totalCompletedCards = Object.keys(_optionalChain([scores, 'optionalAccess', _43 => _43.cards]) || {}).reduce((acc, cardId) => {
1394
- const cardScores = _optionalChain([scores, 'optionalAccess', _44 => _44.cards, 'optionalAccess', _45 => _45[cardId]]);
1395
- if (_optionalChain([cardScores, 'optionalAccess', _46 => _46.completed]) || _optionalChain([cardScores, 'optionalAccess', _47 => _47.score]) || _optionalChain([cardScores, 'optionalAccess', _48 => _48.score]) === 0) {
1449
+ const totalCompletedCards = Object.keys(_optionalChain([scores, 'optionalAccess', _52 => _52.cards]) || {}).reduce((acc, cardId) => {
1450
+ const cardScores = _optionalChain([scores, 'optionalAccess', _53 => _53.cards, 'optionalAccess', _54 => _54[cardId]]);
1451
+ if (_optionalChain([cardScores, 'optionalAccess', _55 => _55.completed]) || _optionalChain([cardScores, 'optionalAccess', _56 => _56.score]) || _optionalChain([cardScores, 'optionalAccess', _57 => _57.score]) === 0) {
1396
1452
  acc += 1;
1397
1453
  }
1398
1454
  return acc;
@@ -1526,7 +1582,7 @@ async function handleAssessment(assignment, userId, cardIds, setWeights, fieldsU
1526
1582
  }
1527
1583
  const { score: scoreCalculated } = calculateScoreAndProgress_default(response.data, cardIds, setWeights);
1528
1584
  await api.updateDoc(path, { score: scoreCalculated, status: "PENDING_REVIEW" });
1529
- await _optionalChain([SpeakableFirebaseFunctions, 'access', _49 => _49.submitAssessment, 'optionalCall', _50 => _50({
1585
+ await _optionalChain([SpeakableFirebaseFunctions, 'access', _58 => _58.submitAssessment, 'optionalCall', _59 => _59({
1530
1586
  assignmentId: assignment.id,
1531
1587
  assignmentTitle: assignment.name,
1532
1588
  userId,
@@ -1537,7 +1593,7 @@ async function handleAssessment(assignment, userId, cardIds, setWeights, fieldsU
1537
1593
  return { success: true, fieldsUpdated };
1538
1594
  }
1539
1595
  async function handleCourseAssignment(assignment, userId) {
1540
- await _optionalChain([SpeakableFirebaseFunctions, 'optionalAccess', _51 => _51.submitAssignmentV2, 'optionalCall', _52 => _52({
1596
+ await _optionalChain([SpeakableFirebaseFunctions, 'optionalAccess', _60 => _60.submitAssignmentV2, 'optionalCall', _61 => _61({
1541
1597
  assignmentId: assignment.id,
1542
1598
  userId
1543
1599
  })]);
@@ -1602,7 +1658,7 @@ function useUpdateScore() {
1602
1658
  });
1603
1659
  },
1604
1660
  onError: (_, variables, context) => {
1605
- if (_optionalChain([context, 'optionalAccess', _53 => _53.previousData]))
1661
+ if (_optionalChain([context, 'optionalAccess', _62 => _62.previousData]))
1606
1662
  queryClient.setQueryData(scoreQueryKeys.byId(variables.activityId), context.previousData);
1607
1663
  },
1608
1664
  onSettled: (_, err, variables) => {
@@ -1618,25 +1674,18 @@ function useUpdateScore() {
1618
1674
  function useUpdateCardScore({
1619
1675
  isAssignment,
1620
1676
  activityId,
1621
- userId
1677
+ userId,
1678
+ set
1622
1679
  }) {
1623
1680
  const { queryClient } = useSpeakableApi();
1624
- const querySet = useSet({ setId: activityId });
1625
- const dataCurrentSet = querySet.data;
1626
1681
  const queryKey = scoreQueryKeys.byId(activityId);
1627
1682
  const mutation = _reactquery.useMutation.call(void 0, {
1628
1683
  mutationFn: async ({ cardId, cardScore }) => {
1629
1684
  const previousScores = queryClient.getQueryData(queryKey);
1630
- console.log("useUpdateCardScore", {
1631
- previousScores: _nullishCoalesce(previousScores, () => ( {})),
1632
- cardId,
1633
- cardScore,
1634
- activityId
1635
- });
1636
1685
  const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
1637
1686
  previousScores: _nullishCoalesce(previousScores, () => ( {})),
1638
1687
  cardId,
1639
- set: _nullishCoalesce(dataCurrentSet, () => ( void 0)),
1688
+ set,
1640
1689
  cardScore
1641
1690
  });
1642
1691
  await updateCardScore({
@@ -1658,7 +1707,7 @@ function useUpdateCardScore({
1658
1707
  score: previousScore,
1659
1708
  cardId,
1660
1709
  cardScore,
1661
- set: _nullishCoalesce(dataCurrentSet, () => ( void 0))
1710
+ set
1662
1711
  });
1663
1712
  return {
1664
1713
  ...previousScore,
@@ -1685,7 +1734,7 @@ var getScoreUpdated = ({
1685
1734
  previousScores,
1686
1735
  set
1687
1736
  }) => {
1688
- const previousCard = _optionalChain([previousScores, 'access', _54 => _54.cards, 'optionalAccess', _55 => _55[cardId]]);
1737
+ const previousCard = _optionalChain([previousScores, 'access', _63 => _63.cards, 'optionalAccess', _64 => _64[cardId]]);
1689
1738
  const newCardScore = {
1690
1739
  ..._nullishCoalesce(previousCard, () => ( {})),
1691
1740
  ...cardScore
@@ -1699,8 +1748,8 @@ var getScoreUpdated = ({
1699
1748
  };
1700
1749
  const { score, progress } = calculateScoreAndProgress_default(
1701
1750
  newScores,
1702
- _nullishCoalesce(_optionalChain([set, 'optionalAccess', _56 => _56.content]), () => ( [])),
1703
- _nullishCoalesce(_optionalChain([set, 'optionalAccess', _57 => _57.weights]), () => ( {}))
1751
+ _nullishCoalesce(_optionalChain([set, 'optionalAccess', _65 => _65.content]), () => ( [])),
1752
+ _nullishCoalesce(_optionalChain([set, 'optionalAccess', _66 => _66.weights]), () => ( {}))
1704
1753
  );
1705
1754
  return {
1706
1755
  newScoreUpdated: newScores,
@@ -1715,7 +1764,7 @@ var handleOptimisticScore = ({
1715
1764
  cardScore,
1716
1765
  set
1717
1766
  }) => {
1718
- let cards = { ..._nullishCoalesce(_optionalChain([score, 'optionalAccess', _58 => _58.cards]), () => ( {})) };
1767
+ let cards = { ..._nullishCoalesce(_optionalChain([score, 'optionalAccess', _67 => _67.cards]), () => ( {})) };
1719
1768
  cards = {
1720
1769
  ...cards,
1721
1770
  [cardId]: {
@@ -1729,8 +1778,8 @@ var handleOptimisticScore = ({
1729
1778
  ..._nullishCoalesce(score, () => ( {})),
1730
1779
  cards
1731
1780
  },
1732
- _nullishCoalesce(_optionalChain([set, 'optionalAccess', _59 => _59.content]), () => ( [])),
1733
- _nullishCoalesce(_optionalChain([set, 'optionalAccess', _60 => _60.weights]), () => ( {}))
1781
+ _nullishCoalesce(_optionalChain([set, 'optionalAccess', _68 => _68.content]), () => ( [])),
1782
+ _nullishCoalesce(_optionalChain([set, 'optionalAccess', _69 => _69.weights]), () => ( {}))
1734
1783
  );
1735
1784
  return { cards, score: scoreValue, progress };
1736
1785
  };
@@ -1740,7 +1789,7 @@ function useClearScore() {
1740
1789
  mutationFn: clearScore,
1741
1790
  onSettled: (result) => {
1742
1791
  queryClient.invalidateQueries({
1743
- queryKey: scoreQueryKeys.byId(_nullishCoalesce(_optionalChain([result, 'optionalAccess', _61 => _61.activityId]), () => ( "")))
1792
+ queryKey: scoreQueryKeys.byId(_nullishCoalesce(_optionalChain([result, 'optionalAccess', _70 => _70.activityId]), () => ( "")))
1744
1793
  });
1745
1794
  }
1746
1795
  });
@@ -1783,6 +1832,11 @@ function useSubmitAssignmentScore({
1783
1832
  if (assignment.isAssessment) {
1784
1833
  createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
1785
1834
  }
1835
+ if (_optionalChain([assignment, 'optionalAccess', _71 => _71.id])) {
1836
+ logSubmitAssignment({
1837
+ courseId: _optionalChain([assignment, 'optionalAccess', _72 => _72.courseId])
1838
+ });
1839
+ }
1786
1840
  onAssignmentSubmitted(assignment.id);
1787
1841
  queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
1788
1842
  ...scores,
@@ -1843,6 +1897,13 @@ function useSubmitPracticeScore() {
1843
1897
  // src/hooks/useActivity.ts
1844
1898
 
1845
1899
 
1900
+ // src/services/add-grading-standard.ts
1901
+ var addGradingStandardLog = async (gradingStandard, userId) => {
1902
+ logGradingStandardLog(gradingStandard);
1903
+ const path = `users/${userId}/grading_standard_logs`;
1904
+ await api.addDoc(path, gradingStandard);
1905
+ };
1906
+
1846
1907
  // src/hooks/useActivityTracker.ts
1847
1908
 
1848
1909
  function useActivityTracker({ userId }) {
@@ -1874,7 +1935,8 @@ function useActivityTracker({ userId }) {
1874
1935
  function useActivity({
1875
1936
  id,
1876
1937
  isAssignment,
1877
- onAssignmentSubmitted
1938
+ onAssignmentSubmitted,
1939
+ ltiData
1878
1940
  }) {
1879
1941
  const { queryClient, user } = useSpeakableApi();
1880
1942
  const userId = user.auth.uid;
@@ -1884,12 +1946,12 @@ function useActivity({
1884
1946
  enabled: isAssignment
1885
1947
  });
1886
1948
  const activeAssignment = assignmentQuery.data;
1887
- const setId = isAssignment ? _nullishCoalesce(_optionalChain([activeAssignment, 'optionalAccess', _62 => _62.setId]), () => ( "")) : id;
1949
+ const setId = isAssignment ? _nullishCoalesce(_optionalChain([activeAssignment, 'optionalAccess', _73 => _73.setId]), () => ( "")) : id;
1888
1950
  const querySet = useSet({ setId });
1889
1951
  const setData = querySet.data;
1890
- const activityId = isAssignment ? _nullishCoalesce(_optionalChain([activeAssignment, 'optionalAccess', _63 => _63.id]), () => ( "")) : setId;
1952
+ const activityId = isAssignment ? _nullishCoalesce(_optionalChain([activeAssignment, 'optionalAccess', _74 => _74.id]), () => ( "")) : setId;
1891
1953
  const { cardsObject, cardsQueries, cards } = useCards({
1892
- cardIds: _nullishCoalesce(_optionalChain([setData, 'optionalAccess', _64 => _64.content]), () => ( [])),
1954
+ cardIds: _nullishCoalesce(_optionalChain([setData, 'optionalAccess', _75 => _75.content]), () => ( [])),
1893
1955
  enabled: querySet.isSuccess,
1894
1956
  asObject: true
1895
1957
  });
@@ -1897,7 +1959,7 @@ function useActivity({
1897
1959
  isAssignment,
1898
1960
  activityId: id,
1899
1961
  userId,
1900
- courseId: _optionalChain([activeAssignment, 'optionalAccess', _65 => _65.courseId]),
1962
+ courseId: _optionalChain([activeAssignment, 'optionalAccess', _76 => _76.courseId]),
1901
1963
  googleClassroomUserId: user.profile.googleClassroomUserId,
1902
1964
  enabled: isAssignment ? assignmentQuery.isSuccess : querySet.isSuccess
1903
1965
  });
@@ -1905,7 +1967,8 @@ function useActivity({
1905
1967
  const { mutationUpdateCardScore } = useUpdateCardScore({
1906
1968
  activityId,
1907
1969
  isAssignment,
1908
- userId
1970
+ userId,
1971
+ set: _nullishCoalesce(querySet.data, () => ( void 0))
1909
1972
  });
1910
1973
  const { mutationClearScore } = useClearScore();
1911
1974
  const { submitAssignmentScore: submitAssignmentScore2 } = useSubmitAssignmentScore({
@@ -1928,12 +1991,12 @@ function useActivity({
1928
1991
  cardId,
1929
1992
  wasCompleted = true
1930
1993
  }) => {
1931
- const currentCard = _optionalChain([cardsObject, 'optionalAccess', _66 => _66[cardId]]);
1932
- if (_optionalChain([currentCard, 'optionalAccess', _67 => _67.type]) === "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */ || _optionalChain([currentCard, 'optionalAccess', _68 => _68.type]) === "READ_REPEAT" /* READ_REPEAT */) {
1994
+ const currentCard = _optionalChain([cardsObject, 'optionalAccess', _77 => _77[cardId]]);
1995
+ if (_optionalChain([currentCard, 'optionalAccess', _78 => _78.type]) === "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */ || _optionalChain([currentCard, 'optionalAccess', _79 => _79.type]) === "READ_REPEAT" /* READ_REPEAT */) {
1933
1996
  return;
1934
1997
  }
1935
1998
  const queryKeys = scoreQueryKeys.byId(activityId);
1936
- const activeCardScores = _optionalChain([queryClient, 'access', _69 => _69.getQueryData, 'call', _70 => _70(queryKeys), 'optionalAccess', _71 => _71.cards, 'optionalAccess', _72 => _72[cardId]]);
1999
+ const activeCardScores = _optionalChain([queryClient, 'access', _80 => _80.getQueryData, 'call', _81 => _81(queryKeys), 'optionalAccess', _82 => _82.cards, 'optionalAccess', _83 => _83[cardId]]);
1937
2000
  if (activeCardScores === void 0) return;
1938
2001
  mutationClearScore.mutate({
1939
2002
  isAssignment,
@@ -1947,21 +2010,30 @@ function useActivity({
1947
2010
  try {
1948
2011
  let results;
1949
2012
  if (isAssignment) {
1950
- const cardScores = _optionalChain([scoreQuery, 'access', _73 => _73.data, 'optionalAccess', _74 => _74.cards]) || {};
2013
+ const cardScores = _optionalChain([scoreQuery, 'access', _84 => _84.data, 'optionalAccess', _85 => _85.cards]) || {};
1951
2014
  const hasPendingReview = Object.values(cardScores).some(
1952
2015
  (cardScore) => cardScore.status === "pending_review"
1953
2016
  );
1954
2017
  results = await submitAssignmentScore2({
1955
2018
  assignment: assignmentQuery.data,
1956
2019
  userId,
1957
- cardIds: _nullishCoalesce(_optionalChain([setData, 'optionalAccess', _75 => _75.content]), () => ( [])),
2020
+ cardIds: _nullishCoalesce(_optionalChain([setData, 'optionalAccess', _86 => _86.content]), () => ( [])),
1958
2021
  scores: scoreQuery.data,
1959
- setWeights: _nullishCoalesce(_optionalChain([setData, 'optionalAccess', _76 => _76.weights]), () => ( {})),
2022
+ setWeights: _nullishCoalesce(_optionalChain([setData, 'optionalAccess', _87 => _87.weights]), () => ( {})),
1960
2023
  status: hasPendingReview ? "PENDING_REVIEW" : "FINALIZED"
1961
2024
  });
2025
+ if (_optionalChain([assignmentQuery, 'access', _88 => _88.data, 'optionalAccess', _89 => _89.ltiDeeplink])) {
2026
+ submitLTIScore({
2027
+ maxPoints: _optionalChain([assignmentQuery, 'access', _90 => _90.data, 'optionalAccess', _91 => _91.maxPoints]),
2028
+ score: _nullishCoalesce(_optionalChain([scoreQuery, 'access', _92 => _92.data, 'optionalAccess', _93 => _93.score]), () => ( 0)),
2029
+ SERVICE_KEY: _nullishCoalesce(_optionalChain([ltiData, 'optionalAccess', _94 => _94.serviceKey]), () => ( "")),
2030
+ lineItemId: _nullishCoalesce(_optionalChain([ltiData, 'optionalAccess', _95 => _95.lineItemId]), () => ( "")),
2031
+ lti_id: _nullishCoalesce(_optionalChain([ltiData, 'optionalAccess', _96 => _96.lti_id]), () => ( ""))
2032
+ });
2033
+ }
1962
2034
  } else {
1963
2035
  results = await submitPracticeScore2({
1964
- setId: _nullishCoalesce(_optionalChain([querySet, 'access', _77 => _77.data, 'optionalAccess', _78 => _78.id]), () => ( "")),
2036
+ setId: _nullishCoalesce(_optionalChain([querySet, 'access', _97 => _97.data, 'optionalAccess', _98 => _98.id]), () => ( "")),
1965
2037
  userId,
1966
2038
  scores: scoreQuery.data
1967
2039
  });
@@ -1974,6 +2046,41 @@ function useActivity({
1974
2046
  };
1975
2047
  }
1976
2048
  };
2049
+ const logGradingStandardEntry = ({
2050
+ cardId,
2051
+ gradingStandard,
2052
+ type
2053
+ }) => {
2054
+ const card = _optionalChain([cardsObject, 'optionalAccess', _99 => _99[cardId]]);
2055
+ const scoresObject = queryClient.getQueryData(scoreQueryKeys.byId(activityId));
2056
+ const cardScore = _optionalChain([scoresObject, 'optionalAccess', _100 => _100.cards, 'optionalAccess', _101 => _101[cardId]]);
2057
+ const serverTimestamp = api.helpers.serverTimestamp;
2058
+ addGradingStandardLog(
2059
+ {
2060
+ assignmentId: _nullishCoalesce(_optionalChain([activeAssignment, 'optionalAccess', _102 => _102.id]), () => ( "")),
2061
+ courseId: _nullishCoalesce(_optionalChain([activeAssignment, 'optionalAccess', _103 => _103.courseId]), () => ( "")),
2062
+ teacherId: _nullishCoalesce(_optionalChain([activeAssignment, 'optionalAccess', _104 => _104.owners, 'access', _105 => _105[0]]), () => ( "")),
2063
+ setId: _nullishCoalesce(_optionalChain([setData, 'optionalAccess', _106 => _106.id]), () => ( "")),
2064
+ cardId,
2065
+ level: gradingStandard.level,
2066
+ justification: gradingStandard.justification,
2067
+ transcript: _nullishCoalesce(_optionalChain([cardScore, 'optionalAccess', _107 => _107.transcript]), () => ( "")),
2068
+ audioUrl: _nullishCoalesce(_optionalChain([cardScore, 'optionalAccess', _108 => _108.audio]), () => ( "")),
2069
+ prompt: _nullishCoalesce(_optionalChain([card, 'optionalAccess', _109 => _109.prompt]), () => ( "")),
2070
+ responseType: _optionalChain([card, 'optionalAccess', _110 => _110.type]) === "RESPOND_WRITE" /* RESPOND_WRITE */ ? "written" : "spoken",
2071
+ type,
2072
+ dateMade: serverTimestamp()
2073
+ },
2074
+ userId
2075
+ );
2076
+ };
2077
+ _react.useEffect.call(void 0, () => {
2078
+ if (isAssignment) {
2079
+ logOpenAssignment({ assignmentId: id });
2080
+ } else {
2081
+ logOpenActivityPreview({ setId: id });
2082
+ }
2083
+ }, []);
1977
2084
  useInitActivity({
1978
2085
  assignment: _nullishCoalesce(activeAssignment, () => ( void 0)),
1979
2086
  set: _nullishCoalesce(setData, () => ( void 0)),
@@ -2002,8 +2109,7 @@ function useActivity({
2002
2109
  clear: onClearScore,
2003
2110
  submit: onSubmitScore,
2004
2111
  updateCard: handleUpdateCardScore,
2005
- logGradingStandardEntry: () => {
2006
- }
2112
+ logGradingStandardEntry
2007
2113
  }
2008
2114
  }
2009
2115
  };
@@ -2019,34 +2125,64 @@ var useInitActivity = ({
2019
2125
  if (!enabled) return;
2020
2126
  if (!assignment) {
2021
2127
  trackActivity({
2022
- activityName: _nullishCoalesce(_optionalChain([set, 'optionalAccess', _79 => _79.name]), () => ( "")),
2128
+ activityName: _nullishCoalesce(_optionalChain([set, 'optionalAccess', _111 => _111.name]), () => ( "")),
2023
2129
  activityType: "set",
2024
- id: _optionalChain([set, 'optionalAccess', _80 => _80.id]),
2025
- language: _optionalChain([set, 'optionalAccess', _81 => _81.language])
2130
+ id: _optionalChain([set, 'optionalAccess', _112 => _112.id]),
2131
+ language: _optionalChain([set, 'optionalAccess', _113 => _113.language])
2026
2132
  });
2027
2133
  } else if (assignment.name) {
2028
2134
  trackActivity({
2029
2135
  activityName: assignment.name,
2030
2136
  activityType: assignment.isAssessment ? "assessment" : "assignment",
2031
2137
  id: assignment.id,
2032
- language: _optionalChain([set, 'optionalAccess', _82 => _82.language])
2138
+ language: _optionalChain([set, 'optionalAccess', _114 => _114.language])
2033
2139
  });
2034
2140
  }
2035
- if (_optionalChain([set, 'optionalAccess', _83 => _83.public])) {
2036
- _optionalChain([SpeakableFirebaseFunctions, 'optionalAccess', _84 => _84.onSetOpened, 'optionalCall', _85 => _85({
2141
+ if (_optionalChain([set, 'optionalAccess', _115 => _115.public])) {
2142
+ _optionalChain([SpeakableFirebaseFunctions, 'optionalAccess', _116 => _116.onSetOpened, 'optionalCall', _117 => _117({
2037
2143
  setId: set.id,
2038
2144
  language: set.language
2039
2145
  })]);
2040
2146
  }
2041
- _optionalChain([SpeakableFirebaseFunctions, 'optionalAccess', _86 => _86.updateAlgoliaIndex, 'optionalCall', _87 => _87({
2147
+ _optionalChain([SpeakableFirebaseFunctions, 'optionalAccess', _118 => _118.updateAlgoliaIndex, 'optionalCall', _119 => _119({
2042
2148
  updatePlays: true,
2043
- objectID: _optionalChain([set, 'optionalAccess', _88 => _88.id])
2149
+ objectID: _optionalChain([set, 'optionalAccess', _120 => _120.id])
2044
2150
  })]);
2045
2151
  };
2046
2152
  _react.useEffect.call(void 0, () => {
2047
2153
  init();
2048
2154
  }, [set]);
2049
2155
  };
2156
+ var submitLTIScore = async ({
2157
+ maxPoints,
2158
+ score,
2159
+ SERVICE_KEY,
2160
+ lineItemId,
2161
+ lti_id
2162
+ }) => {
2163
+ try {
2164
+ if (!SERVICE_KEY || !lineItemId || !lti_id) {
2165
+ throw new Error("Missing required LTI credentials");
2166
+ }
2167
+ const earnedPoints = score ? score / 100 * maxPoints : 0;
2168
+ const { data } = await _optionalChain([SpeakableFirebaseFunctions, 'optionalAccess', _121 => _121.submitLTIAssignmentScore, 'optionalCall', _122 => _122({
2169
+ SERVICE_KEY,
2170
+ scoreData: {
2171
+ lineItemId,
2172
+ userId: lti_id,
2173
+ maxPoints,
2174
+ earnedPoints
2175
+ }
2176
+ })]);
2177
+ return { success: true, data };
2178
+ } catch (error) {
2179
+ console.error("Failed to submit LTI score:", error);
2180
+ return {
2181
+ success: false,
2182
+ error: error instanceof Error ? error : new Error("Unknown error occurred")
2183
+ };
2184
+ }
2185
+ };
2050
2186
 
2051
2187
 
2052
2188
 
package/dist/index.mjs CHANGED
@@ -57,6 +57,9 @@ var FirebaseAPI = class _FirebaseAPI {
57
57
  httpsCallable(functionName) {
58
58
  return this.config?.httpsCallable(functionName);
59
59
  }
60
+ logEvent(name, data) {
61
+ this.config?.logEvent(name, data);
62
+ }
60
63
  accessQueryConstraints() {
61
64
  const { query, orderBy, limit, startAt, startAfter, endAt, endBefore } = this.helpers;
62
65
  return {
@@ -87,8 +90,6 @@ var FirebaseAPI = class _FirebaseAPI {
87
90
  id: docSnap.id,
88
91
  ...docSnap.data()
89
92
  } : null;
90
- console.log("getDoc", path);
91
- console.log("getDoc db", this.db);
92
93
  return {
93
94
  id: docSnap.id,
94
95
  data
@@ -99,8 +100,6 @@ var FirebaseAPI = class _FirebaseAPI {
99
100
  const collectionRef = collection(this.db, path);
100
101
  const q = queryConstraints.length > 0 ? query(collectionRef, ...queryConstraints) : collectionRef;
101
102
  const querySnapshot = await getDocs(q);
102
- console.log("getDocs", path, queryConstraints);
103
- console.log("getDocs db", this.db);
104
103
  const data = querySnapshot.docs.map((doc) => ({
105
104
  id: doc.id,
106
105
  data: doc.data()
@@ -122,22 +121,16 @@ var FirebaseAPI = class _FirebaseAPI {
122
121
  async setDoc(path, data, options = {}) {
123
122
  const { setDoc, doc } = this.helpers;
124
123
  const docRef = doc(this.db, path);
125
- console.log("setDoc", path, data);
126
- console.log("setDoc db", this.db);
127
124
  await setDoc(docRef, data, options);
128
125
  }
129
126
  async updateDoc(path, data) {
130
127
  const { updateDoc, doc } = this.helpers;
131
- console.log("updateDoc", path, data);
132
- console.log("updateDoc db", this.db);
133
128
  const docRef = doc(this.db, path);
134
129
  await updateDoc(docRef, data);
135
130
  }
136
131
  async deleteDoc(path) {
137
132
  const { deleteDoc, doc } = this.helpers;
138
133
  const docRef = doc(this.db, path);
139
- console.log("deleteDoc", path);
140
- console.log("deleteDoc db", this.db);
141
134
  await deleteDoc(docRef);
142
135
  }
143
136
  async runTransaction(updateFunction) {
@@ -519,6 +512,7 @@ var cleanString = (words) => {
519
512
  var getWordHash = (word, language) => {
520
513
  const cleanedWord = cleanString(word);
521
514
  const wordHash = sha1(`${language}-${cleanedWord}`);
515
+ console.log("wordHash core library", wordHash);
522
516
  return wordHash;
523
517
  };
524
518
 
@@ -758,7 +752,8 @@ var SpeakableFirebaseFunctions = {
758
752
  submitAssignmentV2: api.httpsCallable("submitLTIAssignmentScoreV2"),
759
753
  submitAssessment: api.httpsCallable("submitAssessment"),
760
754
  sendAssessmentScoredEmail: api.httpsCallable("sendAssessmentScoredEmail"),
761
- createNotification: api.httpsCallable("createNotificationV2")
755
+ createNotification: api.httpsCallable("createNotificationV2"),
756
+ updateCourseAnalytics: api.httpsCallable("handleCouresAnalyticsEvent")
762
757
  };
763
758
 
764
759
  // src/domains/notification/services/send-notification.service.ts
@@ -919,14 +914,16 @@ var useCreateNotification = () => {
919
914
  async function createFsClient({
920
915
  db,
921
916
  platform,
922
- httpsCallable
917
+ httpsCallable,
918
+ logEvent
923
919
  }) {
924
920
  const dbAsFirestore = db;
925
921
  const helpers = platform === "web" ? await import("firebase/firestore") : await import("@react-native-firebase/firestore");
926
922
  api.initialize({
927
923
  db: dbAsFirestore,
928
924
  helpers,
929
- httpsCallable
925
+ httpsCallable,
926
+ logEvent
930
927
  });
931
928
  return {
932
929
  assignmentRepo: createAssignmentRepo(),
@@ -944,14 +941,16 @@ function SpeakableProvider({
944
941
  queryClient,
945
942
  user,
946
943
  permissions,
947
- httpsCallable
944
+ httpsCallable,
945
+ logEvent
948
946
  }) {
949
947
  const [speakableApi, setSpeakableApi] = useState(null);
950
948
  useEffect(() => {
951
949
  createFsClient({
952
950
  db,
953
951
  httpsCallable,
954
- platform
952
+ platform,
953
+ logEvent
955
954
  }).then((repos) => {
956
955
  setSpeakableApi(repos);
957
956
  });
@@ -1231,6 +1230,61 @@ var useGoogleClassroom = () => {
1231
1230
  };
1232
1231
  };
1233
1232
 
1233
+ // src/lib/firebase/firebase-analytics/grading-standard.ts
1234
+ var logGradingStandardLog = (data) => {
1235
+ if (data.courseId && data.type && data.level) {
1236
+ SpeakableFirebaseFunctions.updateCourseAnalytics?.({
1237
+ eventType: data.type || "custom",
1238
+ level: data.level,
1239
+ courseId: data.courseId
1240
+ });
1241
+ }
1242
+ api.logEvent("logGradingStandard", data);
1243
+ };
1244
+
1245
+ // src/constants/analytics.constants.ts
1246
+ var ANALYTICS_EVENT_TYPES = {
1247
+ VOICE_SUCCESS: "voice_success",
1248
+ VOICE_FAIL: "voice_fail",
1249
+ RESPOND_CARD_SUCCESS: "respond_card_success",
1250
+ RESPOND_CARD_FAIL: "respond_card_fail",
1251
+ RESPOND_WRITE_CARD_SUCCESS: "respond_write_card_success",
1252
+ RESPOND_WRITE_CARD_FAIL: "respond_write_card_fail",
1253
+ RESPOND_FREE_PLAN: "respond_free_plan",
1254
+ RESPOND_WRITE_FREE_PLAN: "respond_write_free_plan",
1255
+ SUBMISSION: "assignment_submitted",
1256
+ ASSIGNMENT_STARTED: "assignment_started",
1257
+ CREATE_ASSIGNMENT: "create_assignment",
1258
+ MC_SUCCESS: "multiple_choice_success",
1259
+ MC_FAIL: "multiple_choice_fail",
1260
+ ACTFL_LEVEL: "actfl_level",
1261
+ WIDA_LEVEL: "wida_level"
1262
+ };
1263
+
1264
+ // src/lib/firebase/firebase-analytics/assignment.ts
1265
+ var logOpenAssignment = (data = {}) => {
1266
+ api.logEvent("open_assignment", data);
1267
+ };
1268
+ var logOpenActivityPreview = (data = {}) => {
1269
+ api.logEvent("open_activity_preview", data);
1270
+ };
1271
+ var logSubmitAssignment = (data = {}) => {
1272
+ SpeakableFirebaseFunctions.updateCourseAnalytics?.({
1273
+ eventType: ANALYTICS_EVENT_TYPES.SUBMISSION,
1274
+ ...data
1275
+ });
1276
+ api.logEvent(ANALYTICS_EVENT_TYPES.SUBMISSION, data);
1277
+ };
1278
+ var logStartAssignment = (data = {}) => {
1279
+ if (data.courseId) {
1280
+ SpeakableFirebaseFunctions.updateCourseAnalytics?.({
1281
+ eventType: ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED,
1282
+ ...data
1283
+ });
1284
+ }
1285
+ api.logEvent(ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED, data);
1286
+ };
1287
+
1234
1288
  // src/domains/assignment/utils/create-default-score.ts
1235
1289
  var defaultScore = (props) => {
1236
1290
  const { serverTimestamp } = api.accessHelpers();
@@ -1313,6 +1367,9 @@ async function getAssignmentScore({
1313
1367
  }),
1314
1368
  assignmentId: assignment.id
1315
1369
  };
1370
+ logStartAssignment({
1371
+ courseId: assignment.courseId
1372
+ });
1316
1373
  const result = await createScore({
1317
1374
  activityId: assignment.id,
1318
1375
  userId,
@@ -1371,7 +1428,6 @@ var getScore = withErrorHandler(_getScore, "getScore");
1371
1428
 
1372
1429
  // src/domains/assignment/utils/calculateScoreAndProgress.ts
1373
1430
  var calculateScoreAndProgress = (scores, cardsList, weights) => {
1374
- console.log("calculateScoreAndProgress", scores, cardsList, weights);
1375
1431
  const totalSetPoints = cardsList.reduce((acc, cardId) => {
1376
1432
  acc += weights?.[cardId] || 1;
1377
1433
  return acc;
@@ -1618,25 +1674,18 @@ function useUpdateScore() {
1618
1674
  function useUpdateCardScore({
1619
1675
  isAssignment,
1620
1676
  activityId,
1621
- userId
1677
+ userId,
1678
+ set
1622
1679
  }) {
1623
1680
  const { queryClient } = useSpeakableApi();
1624
- const querySet = useSet({ setId: activityId });
1625
- const dataCurrentSet = querySet.data;
1626
1681
  const queryKey = scoreQueryKeys.byId(activityId);
1627
1682
  const mutation = useMutation2({
1628
1683
  mutationFn: async ({ cardId, cardScore }) => {
1629
1684
  const previousScores = queryClient.getQueryData(queryKey);
1630
- console.log("useUpdateCardScore", {
1631
- previousScores: previousScores ?? {},
1632
- cardId,
1633
- cardScore,
1634
- activityId
1635
- });
1636
1685
  const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
1637
1686
  previousScores: previousScores ?? {},
1638
1687
  cardId,
1639
- set: dataCurrentSet ?? void 0,
1688
+ set,
1640
1689
  cardScore
1641
1690
  });
1642
1691
  await updateCardScore({
@@ -1658,7 +1707,7 @@ function useUpdateCardScore({
1658
1707
  score: previousScore,
1659
1708
  cardId,
1660
1709
  cardScore,
1661
- set: dataCurrentSet ?? void 0
1710
+ set
1662
1711
  });
1663
1712
  return {
1664
1713
  ...previousScore,
@@ -1783,6 +1832,11 @@ function useSubmitAssignmentScore({
1783
1832
  if (assignment.isAssessment) {
1784
1833
  createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
1785
1834
  }
1835
+ if (assignment?.id) {
1836
+ logSubmitAssignment({
1837
+ courseId: assignment?.courseId
1838
+ });
1839
+ }
1786
1840
  onAssignmentSubmitted(assignment.id);
1787
1841
  queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
1788
1842
  ...scores,
@@ -1843,6 +1897,13 @@ function useSubmitPracticeScore() {
1843
1897
  // src/hooks/useActivity.ts
1844
1898
  import { useEffect as useEffect2 } from "react";
1845
1899
 
1900
+ // src/services/add-grading-standard.ts
1901
+ var addGradingStandardLog = async (gradingStandard, userId) => {
1902
+ logGradingStandardLog(gradingStandard);
1903
+ const path = `users/${userId}/grading_standard_logs`;
1904
+ await api.addDoc(path, gradingStandard);
1905
+ };
1906
+
1846
1907
  // src/hooks/useActivityTracker.ts
1847
1908
  import { v4 as v42 } from "uuid";
1848
1909
  function useActivityTracker({ userId }) {
@@ -1874,7 +1935,8 @@ function useActivityTracker({ userId }) {
1874
1935
  function useActivity({
1875
1936
  id,
1876
1937
  isAssignment,
1877
- onAssignmentSubmitted
1938
+ onAssignmentSubmitted,
1939
+ ltiData
1878
1940
  }) {
1879
1941
  const { queryClient, user } = useSpeakableApi();
1880
1942
  const userId = user.auth.uid;
@@ -1905,7 +1967,8 @@ function useActivity({
1905
1967
  const { mutationUpdateCardScore } = useUpdateCardScore({
1906
1968
  activityId,
1907
1969
  isAssignment,
1908
- userId
1970
+ userId,
1971
+ set: querySet.data ?? void 0
1909
1972
  });
1910
1973
  const { mutationClearScore } = useClearScore();
1911
1974
  const { submitAssignmentScore: submitAssignmentScore2 } = useSubmitAssignmentScore({
@@ -1959,6 +2022,15 @@ function useActivity({
1959
2022
  setWeights: setData?.weights ?? {},
1960
2023
  status: hasPendingReview ? "PENDING_REVIEW" : "FINALIZED"
1961
2024
  });
2025
+ if (assignmentQuery.data?.ltiDeeplink) {
2026
+ submitLTIScore({
2027
+ maxPoints: assignmentQuery.data?.maxPoints,
2028
+ score: scoreQuery.data?.score ?? 0,
2029
+ SERVICE_KEY: ltiData?.serviceKey ?? "",
2030
+ lineItemId: ltiData?.lineItemId ?? "",
2031
+ lti_id: ltiData?.lti_id ?? ""
2032
+ });
2033
+ }
1962
2034
  } else {
1963
2035
  results = await submitPracticeScore2({
1964
2036
  setId: querySet.data?.id ?? "",
@@ -1974,6 +2046,41 @@ function useActivity({
1974
2046
  };
1975
2047
  }
1976
2048
  };
2049
+ const logGradingStandardEntry = ({
2050
+ cardId,
2051
+ gradingStandard,
2052
+ type
2053
+ }) => {
2054
+ const card = cardsObject?.[cardId];
2055
+ const scoresObject = queryClient.getQueryData(scoreQueryKeys.byId(activityId));
2056
+ const cardScore = scoresObject?.cards?.[cardId];
2057
+ const serverTimestamp = api.helpers.serverTimestamp;
2058
+ addGradingStandardLog(
2059
+ {
2060
+ assignmentId: activeAssignment?.id ?? "",
2061
+ courseId: activeAssignment?.courseId ?? "",
2062
+ teacherId: activeAssignment?.owners[0] ?? "",
2063
+ setId: setData?.id ?? "",
2064
+ cardId,
2065
+ level: gradingStandard.level,
2066
+ justification: gradingStandard.justification,
2067
+ transcript: cardScore?.transcript ?? "",
2068
+ audioUrl: cardScore?.audio ?? "",
2069
+ prompt: card?.prompt ?? "",
2070
+ responseType: card?.type === "RESPOND_WRITE" /* RESPOND_WRITE */ ? "written" : "spoken",
2071
+ type,
2072
+ dateMade: serverTimestamp()
2073
+ },
2074
+ userId
2075
+ );
2076
+ };
2077
+ useEffect2(() => {
2078
+ if (isAssignment) {
2079
+ logOpenAssignment({ assignmentId: id });
2080
+ } else {
2081
+ logOpenActivityPreview({ setId: id });
2082
+ }
2083
+ }, []);
1977
2084
  useInitActivity({
1978
2085
  assignment: activeAssignment ?? void 0,
1979
2086
  set: setData ?? void 0,
@@ -2002,8 +2109,7 @@ function useActivity({
2002
2109
  clear: onClearScore,
2003
2110
  submit: onSubmitScore,
2004
2111
  updateCard: handleUpdateCardScore,
2005
- logGradingStandardEntry: () => {
2006
- }
2112
+ logGradingStandardEntry
2007
2113
  }
2008
2114
  }
2009
2115
  };
@@ -2047,6 +2153,36 @@ var useInitActivity = ({
2047
2153
  init();
2048
2154
  }, [set]);
2049
2155
  };
2156
+ var submitLTIScore = async ({
2157
+ maxPoints,
2158
+ score,
2159
+ SERVICE_KEY,
2160
+ lineItemId,
2161
+ lti_id
2162
+ }) => {
2163
+ try {
2164
+ if (!SERVICE_KEY || !lineItemId || !lti_id) {
2165
+ throw new Error("Missing required LTI credentials");
2166
+ }
2167
+ const earnedPoints = score ? score / 100 * maxPoints : 0;
2168
+ const { data } = await SpeakableFirebaseFunctions?.submitLTIAssignmentScore?.({
2169
+ SERVICE_KEY,
2170
+ scoreData: {
2171
+ lineItemId,
2172
+ userId: lti_id,
2173
+ maxPoints,
2174
+ earnedPoints
2175
+ }
2176
+ });
2177
+ return { success: true, data };
2178
+ } catch (error) {
2179
+ console.error("Failed to submit LTI score:", error);
2180
+ return {
2181
+ success: false,
2182
+ error: error instanceof Error ? error : new Error("Unknown error occurred")
2183
+ };
2184
+ }
2185
+ };
2050
2186
  export {
2051
2187
  ALLOWED_CARD_ACTIVITY_TYPES_FOR_SUMMARY,
2052
2188
  BASE_MULTIPLE_CHOICE_FIELD_VALUES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@speakableio/core",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Speakable-io/speakable-core.git"