Skip to content

Commit f9f035d

Browse files
authored
Merge branch 'master' into sessions-overhaul
2 parents 2f9502f + dd24b4a commit f9f035d

File tree

21 files changed

+899
-467
lines changed

21 files changed

+899
-467
lines changed

jsdom-env.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import JSDOMEnvironment from 'jest-environment-jsdom';
1+
import { TestEnvironment as JSDOMEnvironment } from 'jest-environment-jsdom';
22

33
// https://stackoverflow.com/a/78051351
44
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
@@ -10,3 +10,7 @@ export default class FixJSDOMEnvironment extends JSDOMEnvironment {
1010
// And any other missing globals
1111
}
1212
}
13+
14+
// https://github.com/prisma/prisma/discussions/14504#discussioncomment-3406588
15+
// https://jest-archive-august-2023.netlify.app/docs/28.x/upgrading-to-jest28#custom-environment
16+
export const TestEnvironment = FixJSDOMEnvironment;

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"dependencies": {
3232
"@blueprintjs/core": "^5.10.1",
3333
"@blueprintjs/datetime2": "^2.3.3",
34-
"@blueprintjs/icons": "^5.9.0",
34+
"@blueprintjs/icons": "^6.0.0",
3535
"@blueprintjs/select": "^5.1.3",
3636
"@convergencelabs/ace-collab-ext": "^0.6.0",
3737
"@mantine/hooks": "^7.11.2",
@@ -71,7 +71,7 @@
7171
"mdast-util-from-markdown": "^2.0.0",
7272
"mdast-util-to-hast": "^13.0.0",
7373
"normalize.css": "^8.0.1",
74-
"phaser": "~3.87.0",
74+
"phaser": "~3.90.0",
7575
"query-string": "^9.0.0",
7676
"re-resizable": "^6.9.9",
7777
"react": "^18.3.1",

renovate.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"packageRules": [
99
{
1010
"rangeStrategy": "update-lockfile",
11-
"matchPackageNames": ["*"]
11+
"matchPackageNames": ["*"],
12+
"minimumReleaseAge": "5 days"
1213
}
1314
],
1415
"enabledManagers": ["npm"]

src/commons/application/Application.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const Application: React.FC = () => {
8888
};
8989
}
9090

91-
const message = Messages.ExtensionPing();
91+
const message = Messages.ExtensionPing(window.origin);
9292
sendToWebview(message);
9393

9494
window.addEventListener('message', event => {
@@ -116,17 +116,36 @@ const Application: React.FC = () => {
116116
}
117117
break;
118118
case MessageTypeNames.Text:
119-
const code = message.code;
119+
const { workspaceLocation, code } = message;
120120
console.log(`FRONTEND: TextMessage: ${code}`);
121-
// TODO: Don't change ace editor directly
122-
// const elements = document.getElementsByClassName('react-ace');
123-
// if (elements.length === 0) {
124-
// return;
125-
// }
126-
// // @ts-expect-error: ace is not available at compile time
127-
// const editor = ace.edit(elements[0]);
128-
// editor.setValue(code);
129-
dispatch(WorkspaceActions.updateEditorValue('assessment', 0, code));
121+
dispatch(WorkspaceActions.updateEditorValue(workspaceLocation, 0, code));
122+
break;
123+
case MessageTypeNames.EvalEditor:
124+
dispatch(WorkspaceActions.evalEditor(message.workspaceLocation));
125+
break;
126+
case MessageTypeNames.Navigate:
127+
window.location.pathname = message.route;
128+
// TODO: Figure out why this doesn't work, this is faster in theory
129+
// navigate(message.route);
130+
break;
131+
case MessageTypeNames.McqQuestion:
132+
dispatch(WorkspaceActions.showMcqPane(message.workspaceLocation, message.options));
133+
break;
134+
case MessageTypeNames.McqAnswer:
135+
console.log(`FRONTEND: MCQAnswerMessage: ${message}`);
136+
dispatch(SessionActions.submitAnswer(message.questionId, message.choice));
137+
break;
138+
case MessageTypeNames.AssessmentAnswer:
139+
dispatch(SessionActions.submitAnswer(message.questionId, message.answer));
140+
break;
141+
case MessageTypeNames.SetEditorBreakpoints:
142+
dispatch(
143+
WorkspaceActions.setEditorBreakpoint(
144+
message.workspaceLocation,
145+
0,
146+
message.newBreakpoints
147+
)
148+
);
130149
break;
131150
}
132151
});

src/commons/assessment/Assessment.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ import {
2020
import { IconNames } from '@blueprintjs/icons';
2121
import classNames from 'classnames';
2222
import { sortBy } from 'lodash';
23-
import React, { useMemo, useState } from 'react';
23+
import React, { useEffect, useMemo, useState } from 'react';
2424
import { useDispatch } from 'react-redux';
2525
import { Navigate, NavLink, useLoaderData, useParams } from 'react-router';
2626
import { numberRegExp } from 'src/features/academy/AcademyTypes';
27+
import Messages, { sendToWebview } from 'src/features/vscode/messages';
2728
import classes from 'src/styles/Academy.module.scss';
2829

2930
import defaultCoverImage from '../../assets/default_cover_image.jpg';
@@ -60,6 +61,23 @@ const Assessment: React.FC = () => {
6061
const { courseId, role, assessmentOverviews: assessmentOverviewsUnfiltered } = useSession();
6162
const dispatch = useDispatch();
6263

64+
useEffect(() => {
65+
if (assessmentOverviewsUnfiltered && courseId) {
66+
sendToWebview(
67+
Messages.NotifyAssessmentsOverview(
68+
assessmentOverviewsUnfiltered.map(oa => ({
69+
type: oa.type,
70+
closeAt: oa.closeAt,
71+
id: oa.id,
72+
isPublished: oa.isPublished,
73+
title: oa.title
74+
})),
75+
courseId
76+
)
77+
);
78+
}
79+
}, [assessmentOverviewsUnfiltered, courseId]);
80+
6381
const toggleClosedAssessments = () => setShowClosedAssessments(!showClosedAssessments);
6482
const toggleOpenAssessments = () => setShowOpenedAssessments(!showOpenedAssessments);
6583
const toggleUpcomingAssessments = () => setShowUpcomingAssessments(!showUpcomingAssessments);

src/commons/assessmentWorkspace/AssessmentWorkspace.tsx

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
9696
const [sessionId, setSessionId] = useState('');
9797
const [isSubmitted, setIsSubmitted] = useState(false);
9898
const { isMobileBreakpoint } = useResponsive();
99+
const isVscode = useTypedSelector(state => state.vscode.isVscode);
99100

100101
const assessment = useTypedSelector(state => state.session.assessments[props.assessmentId]);
101102
const assessmentOverviews = useTypedSelector(state => state.session.assessmentOverviews);
@@ -244,10 +245,10 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
244245
useEffect(() => {
245246
if (!selectedTab) return;
246247

247-
if (!isMobileBreakpoint && mobileOnlyTabIds.includes(selectedTab)) {
248+
if ((!isMobileBreakpoint || isVscode) && mobileOnlyTabIds.includes(selectedTab)) {
248249
setSelectedTab(SideContentType.questionOverview);
249250
}
250-
}, [isMobileBreakpoint, props, selectedTab, setSelectedTab]);
251+
}, [isMobileBreakpoint, isVscode, props, selectedTab, setSelectedTab]);
251252

252253
/* ==================
253254
onChange handlers
@@ -391,7 +392,40 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
391392
);
392393
handleClearContext(question.library, true);
393394
handleUpdateHasUnsavedChanges(false);
394-
sendToWebview(Messages.NewEditor(`assessment${assessment.id}`, props.questionId, ''));
395+
396+
const chapter = question.library.chapter;
397+
const questionType = question.type;
398+
399+
switch (questionType) {
400+
case QuestionTypes.mcq:
401+
const mcqQuestionData = question;
402+
sendToWebview(
403+
Messages.McqQuestion(
404+
workspaceLocation,
405+
`assessment${assessment.id}`,
406+
mcqQuestionData.id,
407+
chapter,
408+
mcqQuestionData.choices.map(choice => choice.content)
409+
)
410+
);
411+
break;
412+
case QuestionTypes.programming || QuestionTypes.voting:
413+
const prepend = question.prepend;
414+
const code = question.answer ?? question.solutionTemplate;
415+
const breakpoints = editorTabs[0]?.breakpoints ?? [];
416+
sendToWebview(
417+
Messages.NewEditor(
418+
workspaceLocation,
419+
`assessment${assessment.id}`,
420+
props.questionId,
421+
chapter,
422+
prepend,
423+
code,
424+
breakpoints
425+
)
426+
);
427+
break;
428+
}
395429
if (options.editorValue) {
396430
// TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode.
397431
handleEditorValueChange(0, options.editorValue);
@@ -416,6 +450,20 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
416450
const handleContestEntryClick = (_submissionId: number, answer: string) => {
417451
// TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode.
418452
handleEditorValueChange(0, answer);
453+
// Hacky way to view the editor, might cause issues
454+
const breakpoints = editorTabs[0]?.breakpoints ?? [];
455+
sendToWebview(
456+
Messages.NewEditor(
457+
workspaceLocation,
458+
`submission${_submissionId}`,
459+
questionId,
460+
question.library.chapter,
461+
'',
462+
answer,
463+
breakpoints
464+
)
465+
);
466+
//
419467
};
420468

421469
const tabs: SideContentTab[] = [
@@ -736,9 +784,10 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
736784
);
737785

738786
return {
739-
editorButtons: !isMobileBreakpoint
740-
? [runButton, saveButton, resetButton, chapterSelect]
741-
: [saveButton, resetButton],
787+
editorButtons:
788+
!isMobileBreakpoint || isVscode
789+
? [runButton, saveButton, resetButton, chapterSelect]
790+
: [saveButton, resetButton],
742791
flowButtons: [previousButton, questionView, nextButton]
743792
};
744793
};
@@ -885,6 +934,14 @@ It is safe to close this window.`}
885934
(assessment!.questions[questionId] as IProgrammingQuestion).solutionTemplate
886935
);
887936
handleUpdateHasUnsavedChanges(true);
937+
if (isVscode) {
938+
sendToWebview(
939+
Messages.ResetEditor(
940+
workspaceLocation,
941+
(assessment!.questions[questionId] as IProgrammingQuestion).solutionTemplate
942+
)
943+
);
944+
}
888945
}}
889946
options={{ minimal: false, intent: Intent.DANGER }}
890947
/>
@@ -968,7 +1025,7 @@ It is safe to close this window.`}
9681025
{submissionOverlay}
9691026
{overlay}
9701027
{resetTemplateOverlay}
971-
{!isMobileBreakpoint ? (
1028+
{isVscode || !isMobileBreakpoint ? (
9721029
<Workspace {...workspaceProps} />
9731030
) : (
9741031
<MobileWorkspace {...mobileWorkspaceProps} />

src/commons/sagas/BackendSaga.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export function* routerNavigate(path: string) {
121121
const newBackendSagaOne = combineSagaHandlers({
122122
[SessionActions.fetchAuth.type]: function* (action): any {
123123
const { code, providerId: payloadProviderId } = action.payload;
124+
const isVscode: boolean = yield select((state: OverallState) => state.vscode.isVscode);
124125

125126
const providerId = payloadProviderId || (getDefaultProvider() || [null])[0];
126127
if (!providerId) {
@@ -132,7 +133,7 @@ const newBackendSagaOne = combineSagaHandlers({
132133
}
133134

134135
const clientId = getClientId(providerId);
135-
const redirectUrl = computeFrontendRedirectUri(providerId);
136+
const redirectUrl = computeFrontendRedirectUri(providerId, isVscode);
136137

137138
const tokens: Tokens | null = yield call(postAuth, code, providerId, clientId, redirectUrl);
138139
if (!tokens) {

src/commons/sagas/LoginSaga.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { setUser } from '@sentry/browser';
2-
import { call } from 'redux-saga/effects';
2+
import { call, select } from 'redux-saga/effects';
3+
import Messages, { sendToWebview } from 'src/features/vscode/messages';
34

45
import CommonsActions from '../application/actions/CommonsActions';
56
import SessionActions from '../application/actions/SessionActions';
@@ -9,12 +10,17 @@ import { showWarningMessage } from '../utils/notifications/NotificationsHelper';
910

1011
const LoginSaga = combineSagaHandlers({
1112
[SessionActions.login.type]: function* ({ payload: providerId }) {
12-
const epUrl = computeEndpointUrl(providerId);
13+
const isVscode = yield select(state => state.vscode.isVscode);
14+
const epUrl = computeEndpointUrl(providerId, isVscode);
1315
if (!epUrl) {
1416
yield call(showWarningMessage, 'Could not log in; invalid provider name provided.');
1517
return;
1618
}
17-
window.location.href = epUrl;
19+
if (!isVscode) {
20+
window.location.href = epUrl;
21+
} else {
22+
sendToWebview(Messages.LoginWithBrowser(epUrl));
23+
}
1824
},
1925
[SessionActions.setUser.type]: function* (action) {
2026
yield call(setUser, { id: action.payload.userId.toString() });

src/commons/sagas/__tests__/BackendSaga.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,12 @@ const mockRouter = createMemoryRouter([
240240
}
241241
]);
242242

243+
const mockVscodeSlice = {
244+
vscode: {
245+
isVscode: false
246+
}
247+
};
248+
243249
const mockStates = {
244250
router: mockRouter,
245251
session: {
@@ -255,7 +261,8 @@ const mockStates = {
255261
},
256262
workspaces: {
257263
assessment: { currentAssessment: mockAssessment.id }
258-
}
264+
},
265+
...mockVscodeSlice
259266
};
260267

261268
const okResp = { ok: true };
@@ -362,7 +369,7 @@ describe('Test FETCH_AUTH action', () => {
362369
}
363370
]
364371
])
365-
.withState({ session: mockTokens }) // need to mock tokens for updateLatestViewedCourse call
372+
.withState({ session: mockTokens, ...mockVscodeSlice }) // need to mock tokens for updateLatestViewedCourse call
366373
.call(postAuth, code, providerId, clientId, redirectUrl)
367374
.put(SessionActions.setTokens(mockTokens))
368375
.call(getUser, mockTokens)
@@ -377,7 +384,7 @@ describe('Test FETCH_AUTH action', () => {
377384

378385
test('when user is null', () => {
379386
return expectSaga(BackendSaga)
380-
.withState({ session: mockTokens }) // need to mock tokens for the selectTokens() call
387+
.withState({ session: mockTokens, ...mockVscodeSlice }) // need to mock tokens for the selectTokens() call
381388
.provide([
382389
[call(postAuth, code, providerId, clientId, redirectUrl), mockTokens],
383390
[

src/commons/utils/AuthHelper.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export enum AuthProviderType {
66
SAML_SSO = 'SAML'
77
}
88

9-
export function computeEndpointUrl(providerId: string): string | undefined {
9+
export function computeEndpointUrl(providerId: string, forVscode?: boolean): string | undefined {
1010
const ep = Constants.authProviders.get(providerId);
1111
if (!ep) {
1212
return undefined;
@@ -15,10 +15,10 @@ export function computeEndpointUrl(providerId: string): string | undefined {
1515
const epUrl = new URL(ep.endpoint);
1616
switch (ep.type) {
1717
case AuthProviderType.OAUTH2:
18-
epUrl.searchParams.set('redirect_uri', computeFrontendRedirectUri(providerId)!);
18+
epUrl.searchParams.set('redirect_uri', computeFrontendRedirectUri(providerId, forVscode)!);
1919
break;
2020
case AuthProviderType.CAS:
21-
epUrl.searchParams.set('service', computeFrontendRedirectUri(providerId)!);
21+
epUrl.searchParams.set('service', computeFrontendRedirectUri(providerId, forVscode)!);
2222
break;
2323
case AuthProviderType.SAML_SSO:
2424
epUrl.searchParams.set('target_url', computeSamlRedirectUri(providerId)!);
@@ -31,13 +31,17 @@ export function computeEndpointUrl(providerId: string): string | undefined {
3131
}
3232
}
3333

34-
export function computeFrontendRedirectUri(providerId: string): string | undefined {
34+
export function computeFrontendRedirectUri(
35+
providerId: string,
36+
forVscode?: boolean
37+
): string | undefined {
3538
const ep = Constants.authProviders.get(providerId);
3639
if (!ep) {
3740
return undefined;
3841
}
3942
const port = window.location.port === '' ? '' : `:${window.location.port}`;
40-
const callback = `${window.location.protocol}//${window.location.hostname}${port}/login/callback${
43+
const path = !forVscode ? '/login/callback' : '/login/vscode_callback';
44+
const callback = `${window.location.protocol}//${window.location.hostname}${port}${path}${
4145
ep.isDefault ? '' : '?provider=' + encodeURIComponent(providerId)
4246
}`;
4347
return callback;

0 commit comments

Comments
 (0)