Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion aws/lti-middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const consumerKeySecretMap = {
};

const oatsHost = "https://cahlr.github.io/OATutor/#";
const stagingHost = "https://cahlr.github.io/OATutor-Staging/#";
const unlinkedPage = "assignment-not-linked";
const alreadyLinkedPage = "assignment-already-linked";
const jwtAlgorithm = "HS256";
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"expr-eval": "~2.0.2",
"firebase": "~9.9.1",
"http2": "~3.3.7",
"jwt-decode": "^4.0.0",
"list-react-files": "~0.2.0",
"localforage": "~1.10.0",
"mathlive": "^0.98.3",
Expand Down
50 changes: 37 additions & 13 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import NotFound from "@components/NotFound.js";
import {
DO_FOCUS_TRACKING,
PROGRESS_STORAGE_KEY,
CANVAS_PROBLEM_SCORE_KEY,
SITE_VERSION,
ThemeContext,
USER_ID_STORAGE_KEY,
Expand Down Expand Up @@ -127,6 +128,7 @@ class App extends React.Component {
const user = parseJwt(additionalContext.jwt);
additionalContext["user"] = user;
additionalContext["studentName"] = user.full_name;
this.jwt = additionalContext.jwt;
}

// Firebase creation
Expand Down Expand Up @@ -229,24 +231,46 @@ class App extends React.Component {
);
})
);
const { setByKey } = this.browserStorage;
setByKey(PROGRESS_STORAGE_KEY, progressedBktParams, (err) => {
if (err) {
console.debug("save progress error: ", err);
toast.warn("Unable to save mastery progress :(", {
console.log(progressedBktParams)

if (this.jwt) {
const key = CANVAS_PROBLEM_SCORE_KEY(this.jwt);
this.firebase.setMastery(key, progressedBktParams)
.then((_) => {})
.catch((err) => {
toast.warn("Unable to save mastery progress to Firebase", {
toastId: "unable_to_save_progress",
});
} else {
console.debug("saved progress successfully");
}
}).then((_) => {});
});
} else {
const { setByKey } = this.browserStorage;
setByKey(PROGRESS_STORAGE_KEY, progressedBktParams, (err) => {
if (err) {
console.debug("save progress error: ", err);
toast.warn("Unable to save mastery progress :(", {
toastId: "unable_to_save_progress",
});
} else {
console.debug("saved progress successfully");
}
}).then((_) => {});
}
};

loadBktProgress = async () => {
const { getByKey } = this.browserStorage;
const progress = await getByKey(PROGRESS_STORAGE_KEY).catch((_e) => {
console.debug("error with getting previous progress", _e);
});
let progress;
if (this.jwt) {
const key = CANVAS_PROBLEM_SCORE_KEY(this.jwt);
progress = await this.firebase.getMastery(key).catch((_e) => {
console.debug("error with getting previous progress", _e);
});
} else {
const { getByKey } = this.browserStorage;
progress = await getByKey(PROGRESS_STORAGE_KEY).catch((_e) => {
console.debug("error with getting previous progress", _e);
});
}

if (
progress == null ||
typeof progress !== "object" ||
Expand Down
66 changes: 58 additions & 8 deletions src/components/Firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import {
import { initializeApp } from "firebase/app";
import {
arrayUnion,
connectFirestoreEmulator,
doc,
getFirestore,
serverTimestamp,
setDoc,
getDoc,
} from "firebase/firestore";
import daysSinceEpoch from "../util/daysSinceEpoch";
import {
Expand All @@ -24,6 +26,8 @@ import {
} from "../util/getBuildType";

const problemSubmissionsOutput = "problemSubmissions";
const canvasLessonProgress = "canvasLessonProgress";
const canvasProblemMastery = "canvasProblemMastery";
const problemStartLogOutput = "problemStartLogs";
const GPTExperimentOutput = "GPTExperimentOutput";
const feedbackOutput = "feedbacks";
Expand Down Expand Up @@ -108,24 +112,24 @@ class Firebase {
Document: Key - How you will access this data later. Usually username
Data: Value - JSON object of data you want to store
*/
async writeData(_collection, data) {
async writeData(_collection, data, docID = null) {
if (!ENABLE_FIREBASE) return;
const collection = this.getCollectionName(_collection);
const payload = this.addMetaData(data);

if (IS_STAGING_OR_DEVELOPMENT) {
// console.log("payload: ", payload);
console.debug("Writing this payload to firebase: ", payload);
}

docID = docID || this._getReadableID();

await setDoc(
doc(this.db, collection, this._getReadableID()),
doc(this.db, collection, docID),
payload
).catch((err) => {
console.log("a non-critical error occurred.");
console.log("Error is: ", err);
console.debug(err);
)
.catch((err) => {
console.log("Error while writing to Firebase: ", err);
});

}

/**
Expand Down Expand Up @@ -331,6 +335,52 @@ class Firebase {
return this.writeData("mouseMovement", data);
}

async getCompletedProblems(docID) {
const collectionName = this.getCollectionName(canvasLessonProgress);
const docRef = doc(this.db, collectionName, docID);
const docSnapshot = await getDoc(docRef);

if (!docSnapshot.exists) {
throw new Error(`Document with ID ${docID} does not exist.`);
}

const data = docSnapshot.data();
if (!data || !data.problems) {
throw new Error(`Document with ID ${docID} does not contain "problems" field.`);
}

return data.problems;
}

async setCompletedProblems(docID, problems) {
const problemObject = {"problems": problems};
this.writeData(canvasLessonProgress, problemObject, docID);
}

async getMastery(docID) {
const collectionName = this.getCollectionName(canvasProblemMastery);
const docRef = doc(this.db, collectionName, docID);

const docSnapshot = await getDoc(docRef);

if (!docSnapshot.exists) {
throw new Error(`Document with ID ${docID} does not exist.`);
}

const data = docSnapshot.data();
if (!data) {
throw new Error(`Document with ID ${docID} does not contain "mastery" field.`);
}

console.log("got mastery from firebase: ", data.mastery);
return data.mastery;
}

async setMastery(docID, masteryObject) {
masteryObject = {"mastery": masteryObject};
this.writeData(canvasProblemMastery, masteryObject, docID);
}

startedProblem(problemID, courseName, lesson, lessonObjectives) {
if (!DO_LOG_DATA) return;
console.debug(
Expand Down
1 change: 0 additions & 1 deletion src/components/problem-layout/Problem.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,6 @@ class Problem extends React.Component {

clickNextProblem = async () => {
scroll.scrollToTop({ duration: 900, smooth: true });

await this.props.problemComplete(this.context);

this.setState({
Expand Down
13 changes: 13 additions & 0 deletions src/config/config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { jwtDecode } from 'jwt-decode';
import courses from "../content-sources/oatutor/coursePlans.json";
import { calculateSemester } from "../util/calculateSemester.js";

Expand Down Expand Up @@ -75,6 +76,18 @@ const PROGRESS_STORAGE_KEY = `${_SHORT_SITE_NAME}-progress`;
export const LESSON_PROGRESS_STORAGE_KEY = (lessonId) =>
`${PROGRESS_STORAGE_KEY}-${lessonId}`;

export const CANVAS_PROBLEM_SCORE_KEY = (jwtToken) => {
const decodedToken = jwtDecode(jwtToken);
const userId = decodedToken.user_id;
return `${PROGRESS_STORAGE_KEY}-${userId}`;
}
export const CANVAS_LESSON_PROGRESS_KEY = (jwtToken, lessonId) => {
const decodedToken = jwtDecode(jwtToken);
const userId = decodedToken.user_id;
return `${PROGRESS_STORAGE_KEY}-${userId}-${lessonId}`;
};


const CANVAS_WARNING_STORAGE_KEY = `${_SHORT_SITE_NAME}-canvas-warning-dismissed`;

// Firebase Config
Expand Down
68 changes: 47 additions & 21 deletions src/platform-logic/Platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
coursePlans,
findLessonById,
LESSON_PROGRESS_STORAGE_KEY,
CANVAS_LESSON_PROGRESS_KEY,
MIDDLEWARE_URL,
SITE_NAME,
ThemeContext,
Expand Down Expand Up @@ -232,10 +233,15 @@ class Platform extends React.Component {
this.lesson = lesson;

const loadLessonProgress = async () => {
const { getByKey } = this.context.browserStorage;
return await getByKey(
LESSON_PROGRESS_STORAGE_KEY(this.lesson.id)
).catch((err) => {});
if (this.context?.jwt) {
const key = CANVAS_LESSON_PROGRESS_KEY(this.context.jwt, this.lesson.id);
return await this.context.firebase.getCompletedProblems(key).catch((err) => {});
} else {
const { getByKey } = this.context.browserStorage;
return await getByKey(
LESSON_PROGRESS_STORAGE_KEY(this.lesson.id)
).catch((err) => {});
}
};

const [, prevCompletedProbs] = await Promise.all([
Expand Down Expand Up @@ -377,23 +383,43 @@ class Platform extends React.Component {

problemComplete = async (context) => {
this.completedProbs.add(this.state.currProblem.id);
const { setByKey } = this.context.browserStorage;
await setByKey(
LESSON_PROGRESS_STORAGE_KEY(this.lesson.id),
this.completedProbs
).catch((error) => {
this.context.firebase.submitSiteLog(
"site-error",
`componentName: Platform.js`,
{
errorName: error.name || "n/a",
errorCode: error.code || "n/a",
errorMsg: error.message || "n/a",
errorStack: error.stack || "n/a",
},
this.state.currProblem.id
);
});
if (this.context?.jwt) {
const key = CANVAS_LESSON_PROGRESS_KEY(this.context.jwt, this.lesson.id)
await this.context.firebase.setCompletedProblems(
key,
Array.from(this.completedProbs)
).catch((error) => {
this.context.firebase.submitSiteLog(
"site-error",
`componentName: Platform.js`,
{
errorName: error.name || "n/a",
errorCode: error.code || "n/a",
errorMsg: error.message || "n/a",
errorStack: error.stack || "n/a",
},
this.state.currProblem.id
);
});
} else {
const { setByKey } = this.context.browserStorage;
await setByKey(
LESSON_PROGRESS_STORAGE_KEY(this.lesson.id),
this.completedProbs
).catch((error) => {
this.context.firebase.submitSiteLog(
"site-error",
`componentName: Platform.js`,
{
errorName: error.name || "n/a",
errorCode: error.code || "n/a",
errorMsg: error.message || "n/a",
errorStack: error.stack || "n/a",
},
this.state.currProblem.id
);
});
}
this._nextProblem(context);
};

Expand Down