Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: UTRP v2 migration #292

Merged
merged 18 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 16 additions & 0 deletions src/pages/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});

Razboy20 marked this conversation as resolved.
Show resolved Hide resolved
// initialize the message listener that will listen for messages from the content script
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
...browserActionHandler,
Expand Down
2 changes: 0 additions & 2 deletions src/pages/background/events/onUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ExtensionStore } from '@shared/storage/ExtensionStore';

import migrateUTRPv1Courses from '../lib/migrateUTRPv1Courses';

/**
* Called when the extension is updated (or when the extension is reloaded in development mode)
Expand All @@ -10,5 +9,4 @@ export default async function onUpdate() {
version: chrome.runtime.getManifest().version,
lastUpdate: Date.now(),
});
migrateUTRPv1Courses();
}
2 changes: 1 addition & 1 deletion src/pages/background/lib/createSchedule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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';
import type { UserSchedule } from 'src/shared/types/UserSchedule';

/**
* Creates a new schedule with the given name
Expand Down
8 changes: 6 additions & 2 deletions src/pages/background/lib/migrateUTRPv1Courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ export async function getUTRPv1Courses(): Promise<string[]> {
* @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/');
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;
return false;
}

const oldCourses = await getUTRPv1Courses();
Expand Down Expand Up @@ -67,6 +69,8 @@ async function migrateUTRPv1Courses() {
} else {
console.warn('No courses successfully found to migrate');
}

return true;
}

export default migrateUTRPv1Courses;
2 changes: 1 addition & 1 deletion src/pages/options/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import DialogProvider from '@views/components/common/DialogProvider/DialogProvider';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import Settings from '@views/components/settings/Settings';
import SentryProvider from '@views/contexts/SentryContext';
import useKC_DABR_WASM from 'kc-dabr-wasm';
import React from 'react';
import SentryProvider from 'src/views/contexts/SentryContext';

/**
* Renders the settings page for the UTRP (UT Registration Plus) extension.
Expand Down
2 changes: 1 addition & 1 deletion src/pages/report/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ReportIssueMain from '@views/components/ReportIssueMain';
import SentryProvider from '@views/contexts/SentryContext';
import React from 'react';
import { createRoot } from 'react-dom/client';
import SentryProvider from 'src/views/contexts/SentryContext';

createRoot(document.getElementById('root')!).render(
<SentryProvider fullInit>
Expand Down
2 changes: 1 addition & 1 deletion src/views/components/common/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function Button({
>
{Icon && <Icon className='h-6 w-6' />}
{!isIconOnly && (
<Text variant='h4' className='translate-y-0.08 inline-flex items-center gap-2'>
<Text variant='h4' className='inline-flex translate-y-0.08 items-center gap-2'>
{children}
</Text>
)}
Expand Down
91 changes: 68 additions & 23 deletions src/views/components/common/MigrationDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import migrateUTRPv1Courses, { getUTRPv1Courses } from '@background/lib/migrateUTRPv1Courses';
import Text from '@views/components/common/Text/Text';
import { useSentryScope } from '@views/contexts/SentryContext';
import React, { useEffect, useState } from 'react';
import { useSentryScope } from 'src/views/contexts/SentryContext';

import { Button } from './Button';
import { usePrompt } from './DialogProvider/DialogProvider';
import Spinner from './Spinner';
Expand All @@ -16,15 +17,24 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
const handleMigration = async () => {
if (processState === 1) {
try {
await migrateUTRPv1Courses();
await chrome.storage.session.set({ pendingMigration: true });
const successful = await migrateUTRPv1Courses();
if (successful) {
await chrome.storage.local.set({ finishedMigration: true });
await chrome.storage.session.remove('pendingMigration');
}
} catch (error) {
console.error(error);
const sentryId = sentryScope.captureException(error);
setError(sentryId);
await chrome.storage.session.remove('pendingMigration');
return;
}
setProcessState(2);
close();
} else {
const { pendingMigration } = await chrome.storage.session.get('pendingMigration');
if (pendingMigration) setProcessState(1);
}
};

Expand All @@ -39,17 +49,29 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
{error.substring(0, 8)})
</Text>
)}
<Button
variant='single'
color='ut-black'
onClick={() => {
close();
if (!error) {
chrome.storage.local.set({ finishedMigration: true });
}
}}
>
Cancel
</Button>
<Button
variant='filled'
color='ut-burntorange'
color='ut-green'
disabled={processState > 0}
onClick={() => {
setProcessState(1);
}}
>
{processState == 1 ? (
{processState === 1 ? (
<>
Migrating... <Spinner className='ml-2.75 w-4! h-4! text-current! inline-block' />
Migrating... <Spinner className='ml-2.75 inline-block h-4! w-4! text-current!' />
</>
) : (
'Migrate courses'
Expand All @@ -59,31 +81,54 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
);
}

export function MigrationDialog(): JSX.Element {
export function useMigrationDialog() {
const showDialog = usePrompt();

return async () => {
if ((await getUTRPv1Courses()).length > 0) {
showDialog(
{
title: 'This extension has updated!',
description:
"You may have already began planning your Spring '25 schedule. Click the button below to transfer your saved schedules into a new schedule. (You may be required to login to the UT Registrar)",

buttons: close => <MigrationButtons close={close} />,
},
{
closeOnClickOutside: false,
}
);
} else {
showDialog({
title: 'Already migrated!',
description:
'There are no courses to migrate. If you have any issues, please submit a feedback report by clicking the flag at the top right of the extension popup.',

buttons: close => (
<Button variant='filled' color='ut-burntorange' onClick={close}>
I Understand
</Button>
),
});
}
};
}

export function MigrationDialog(): JSX.Element {
const showMigrationDialog = useMigrationDialog();

useEffect(() => {
const checkMigration = async () => {
if ((await getUTRPv1Courses()).length > 0) {
showDialog(
{
title: (
<Text variant='h4' className='text-ut-burntorange font-bold'>
This extension has been updated!
</Text>
),
description:
'You may have already began planning your Spring 2025 schedule. Use the button below to migrate your old UTRP v1 courses.',
buttons: close => <MigrationButtons close={close} />,
},
{
closeOnClickOutside: false,
}
);
}
// check if migration was already attempted
if ((await chrome.storage.local.get('finishedMigration')).finishedMigration) return;

if ((await getUTRPv1Courses()).length > 0) showMigrationDialog();
};

checkMigration();
}, []);

// (not actually a useless fragment)
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
Razboy20 marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 5 additions & 3 deletions src/views/components/settings/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import addCourse from '@pages/background/lib/addCourse';
import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule';
import migrateUTRPv1Courses from '@pages/background/lib/migrateUTRPv1Courses';
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
// import { getCourseColors } from '@shared/util/colors';
// import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell';
Expand All @@ -25,6 +24,7 @@ import IconoirGitFork from '~icons/iconoir/git-fork';
// import { ExampleCourse } from 'src/stories/components/ConflictsWithWarning.stories';
import DeleteForeverIcon from '~icons/material-symbols/delete-forever';

import { useMigrationDialog } from '../common/MigrationDialog';
// import RefreshIcon from '~icons/material-symbols/refresh';
import DevMode from './DevMode';
import Preview from './Preview';
Expand Down Expand Up @@ -85,6 +85,8 @@ export default function Settings(): JSX.Element {
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
const [enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);

const showMigrationDialog = useMigrationDialog();

// Toggle GitHub stats when the user presses the 'S' key
const [showGitHubStats, setShowGitHubStats] = useState<boolean>(false);
const [githubStats, setGitHubStats] = useState<Awaited<
Expand Down Expand Up @@ -431,8 +433,8 @@ export default function Settings(): JSX.Element {
<Button variant='filled' color='ut-black' onClick={handleAddCourseByUrl}>
Add course by link
</Button>
<Button variant='filled' color='ut-burntorange' onClick={migrateUTRPv1Courses}>
Migrate UTRP v1 courses
<Button variant='filled' color='ut-burntorange' onClick={showMigrationDialog}>
Show Migration Dialog
</Button>
</section>
</div>
Expand Down
67 changes: 36 additions & 31 deletions src/views/contexts/SentryContext.tsx
Razboy20 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Scope,
} from '@sentry/react';
import type { Client, ClientOptions } from '@sentry/types';
import React, { createContext, useContext } from 'react';
import React, { createContext, useContext, useMemo } from 'react';

/**
* Context for the sentry provider.
Expand All @@ -32,47 +32,52 @@ export default function SentryProvider({
}): JSX.Element {
// prevent accidentally initializing sentry twice
const parent = useSentryScope();
if (parent) {
const [parentScope, parentClient] = parent;

const scope = parentScope.clone();
if (transactionName) scope.setTransactionName(transactionName);
const providerValue = useMemo((): [scope: Scope, client: Client] => {
if (parent) {
const [parentScope, parentClient] = parent;

return <SentryContext.Provider value={[scope, parentClient]}>{children}</SentryContext.Provider>;
}
const scope = parentScope.clone();
if (transactionName) scope.setTransactionName(transactionName);

// filter integrations that use the global variable
const integrations = getDefaultIntegrations({}).filter(defaultIntegration => {
return !['BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers'].includes(defaultIntegration.name);
});
return [scope, parentClient];
}

const options: ClientOptions = {
dsn: 'https://ed1a50d8626ff6be35b98d7b1ec86d9d@o4508033820852224.ingest.us.sentry.io/4508033822490624',
integrations,
transport: makeFetchTransport,
stackParser: defaultStackParser,
// debug: true,
release: import.meta.env.VITE_PACKAGE_VERSION,
};
// filter integrations that use the global variable
const integrations = getDefaultIntegrations({}).filter(
defaultIntegration =>
!['BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers'].includes(defaultIntegration.name)
);

let client: Client;
let scope: Scope;
const options: ClientOptions = {
dsn: 'https://ed1a50d8626ff6be35b98d7b1ec86d9d@o4508033820852224.ingest.us.sentry.io/4508033822490624',
integrations,
transport: makeFetchTransport,
stackParser: defaultStackParser,
// debug: true,
release: import.meta.env.VITE_PACKAGE_VERSION,
};

if (fullInit) {
client = init(options)!;
scope = getCurrentScope();
} else {
client = new BrowserClient(options);
let client: Client;
let scope: Scope;

scope = new Scope();
if (fullInit) {
client = init(options)!;
scope = getCurrentScope();
} else {
client = new BrowserClient(options);

scope.setClient(client);
client.init();
}
scope = new Scope();

scope.setClient(client);
client.init();
}
return [scope, client];
}, []);

return (
<ErrorBoundary>
<SentryContext.Provider value={[scope, client]}>{children}</SentryContext.Provider>
<SentryContext.Provider value={providerValue}>{children}</SentryContext.Provider>
</ErrorBoundary>
);
}
Loading