feat: UTRP v2 migration (#292)

* feat: wip add course by url

* chore: update imports

* feat: add useCourseFromUrl hook

* chore: extract logic into async function

* feat: add checkLoginStatus.ts

* feat: add useCourseMigration hook

* feat: working course migration

* fix: active schedule bug

* feat: refactor logic and add to onUpdate

* feat: update ui style

* feat: add changelog functionality to settings

* chore: update packages

* feat: migration + sentry stuffs

* feat: improve migration flow

* docs: add sentry jsdocs

* chore: fix lint and format

* chore: cleanup + fix race condition

---------

Co-authored-by: Samuel Gunter <sgunter@utexas.edu>
Co-authored-by: Razboy20 <razboy20@gmail.com>
This commit is contained in:
doprz
2024-10-14 21:30:37 -05:00
committed by GitHub
parent e774f316e3
commit d22237d561
23 changed files with 1980 additions and 1865 deletions

View File

@@ -31,6 +31,22 @@ chrome.runtime.onInstalled.addListener(details => {
}
});
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
console.log(changeInfo);
if (changeInfo.url === 'https://utdirect.utexas.edu/apps/registrar/course_schedule/utrp_login/') {
console.log('UTDirect detected');
// close the tab, open popup
function openPopupAction() {
chrome.tabs.onActivated.removeListener(openPopupAction);
chrome.action.openPopup();
}
chrome.tabs.onActivated.addListener(openPopupAction);
await chrome.tabs.remove(tabId);
}
});
// initialize the message listener that will listen for messages from the content script
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
...browserActionHandler,

View File

@@ -1,12 +1,14 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import type { UserSchedule } from '@shared/types/UserSchedule';
import { generateRandomId } from '@shared/util/random';
import type { Serialized } from 'chrome-extension-toolkit';
/**
* Creates a new schedule with the given name
* @param scheduleName the name of the schedule to create
* @returns undefined if successful, otherwise an error message
*/
export default async function createSchedule(scheduleName: string): Promise<string | undefined> {
export default async function createSchedule(scheduleName: string) {
const schedules = await UserScheduleStore.get('schedules');
// get the number of schedules that either have the same name or have the same name with a number appended (e.g. "New Schedule (1)")
// this way we can prevent duplicate schedule names and increment the number if necessary
@@ -22,14 +24,16 @@ export default async function createSchedule(scheduleName: string): Promise<stri
if (count > 0) {
name = `${scheduleName} (${count})`;
}
schedules.push({
const newSchedule: Serialized<UserSchedule> = {
id: generateRandomId(),
name,
courses: [],
hours: 0,
updatedAt: Date.now(),
});
};
schedules.push(newSchedule);
await UserScheduleStore.set('schedules', schedules);
return undefined;
return newSchedule.id;
}

View File

@@ -0,0 +1,78 @@
import { validateLoginStatus } from '@shared/util/checkLoginStatus';
import { getActiveSchedule } from '@views/hooks/useSchedules';
import { courseMigration } from '@views/lib/courseMigration';
import addCourse from './addCourse';
import createSchedule from './createSchedule';
import switchSchedule from './switchSchedule';
/**
* Retrieves the saved courses from the extension's chrome sync storage (old store) and returns an array of course links.
*
* @returns A promise that resolves to an array of course links.
*/
export async function getUTRPv1Courses(): Promise<string[]> {
const { savedCourses } = await chrome.storage.sync.get('savedCourses');
// Check if the savedCourses array is empty
if (!savedCourses || savedCourses.length === 0) {
return [];
}
// Extract the link property from each course object and return it as an array
return savedCourses.map((course: { link: string }) => course.link);
}
/**
* Handles the migration of UTRP v1 courses.
*
* This function checks if the user is logged into the UT course schedule page.
* If the user is not logged in, it logs a message and exits. If the user is
* logged in, it retrieves the UTRP v1 courses, creates a new schedule for the
* migration, switches to the new schedule, and migrates the courses to the
* active schedule.
*
* @returns A promise that resolves when the migration is complete.
*/
async function migrateUTRPv1Courses() {
const loggedInToUT = await validateLoginStatus(
'https://utdirect.utexas.edu/apps/registrar/course_schedule/utrp_login/'
);
if (!loggedInToUT) {
console.warn('Not logged in to UT Registrar.');
return false;
}
const oldCourses = await getUTRPv1Courses();
console.log(oldCourses);
const migratedCourses = await courseMigration(oldCourses);
if (migratedCourses.length > 0) {
console.log(oldCourses, migratedCourses);
const migrateSchedule = await createSchedule('Migrated Schedule');
await switchSchedule(migrateSchedule);
const activeSchedule = getActiveSchedule();
for (const course of migratedCourses) {
// Add the course if it doesn't already exist
if (activeSchedule.courses.every(c => c.uniqueId !== course.uniqueId)) {
// ignore eslint, as we *do* want to spend time on each iteration
// eslint-disable-next-line no-await-in-loop
await addCourse(activeSchedule.id, course);
}
}
// Remove the old courses from storage :>
await chrome.storage.sync.remove('savedCourses');
console.log('Successfully migrated UTRP v1 courses');
} else {
console.warn('No courses successfully found to migrate');
}
return true;
}
export default migrateUTRPv1Courses;