From 3999046a482f4c9cdb56189849f75a8f4e196a5e Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Sat, 12 Jun 2021 00:51:12 +0800 Subject: [PATCH 001/148] Remove game and achievements toggle in .env --- .env.example | 2 -- src/commons/application/Application.tsx | 4 +--- .../__tests__/__snapshots__/Application.tsx.snap | 2 +- src/commons/navigationBar/NavigationBar.tsx | 2 +- .../subcomponents/NavigationBarMobileSideMenu.tsx | 3 +-- src/commons/sagas/AchievementSaga.ts | 2 +- src/commons/utils/Constants.ts | 4 ---- src/pages/academy/Academy.tsx | 7 ++----- 8 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 70c5109e7d..ae8e28d703 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,6 @@ REACT_APP_BACKEND_URL=http://localhost:4000 REACT_APP_USE_BACKEND=TRUE REACT_APP_PLAYGROUND_ONLY=FALSE -REACT_APP_ENABLE_GAME=TRUE -REACT_APP_ENABLE_ACHIEVEMENTS=TRUE REACT_APP_ENABLE_GITHUB_ASSESSMENTS=TRUE REACT_APP_URL_SHORTENER_SIGNATURE= diff --git a/src/commons/application/Application.tsx b/src/commons/application/Application.tsx index 609be0a0f8..22eedf9344 100644 --- a/src/commons/application/Application.tsx +++ b/src/commons/application/Application.tsx @@ -106,11 +106,9 @@ const Application: React.FC = props => { render={toIncubator} key={1} />, + , loginPath ]; - if (!Constants.playgroundOnly && Constants.enableAchievements) { - fullPaths?.push(); - } const disabled = !['staff', 'admin'].includes(props.role!) && isDisabled; const renderDisabled = () => ( diff --git a/src/commons/application/__tests__/__snapshots__/Application.tsx.snap b/src/commons/application/__tests__/__snapshots__/Application.tsx.snap index f20d3deecc..b110e45b10 100644 --- a/src/commons/application/__tests__/__snapshots__/Application.tsx.snap +++ b/src/commons/application/__tests__/__snapshots__/Application.tsx.snap @@ -13,8 +13,8 @@ exports[`Application renders correctly 1`] = ` - + diff --git a/src/commons/navigationBar/NavigationBar.tsx b/src/commons/navigationBar/NavigationBar.tsx index b8c320b964..3dbd32b0d5 100644 --- a/src/commons/navigationBar/NavigationBar.tsx +++ b/src/commons/navigationBar/NavigationBar.tsx @@ -201,7 +201,7 @@ const NavigationBar: React.FC = props => { )} - {props.role && Constants.enableAchievements && ( + {props.role && ( =
GitHub Assessments
- {props.role && Constants.enableAchievements && ( + {props.role && ( ): any { const role = yield select((state: OverallState) => state.session.role); - if (role && Constants.enableAchievements && !Constants.playgroundOnly) { + if (role && !Constants.playgroundOnly) { loggedEvents.push(action.payload); if (!timeoutSet && role) { diff --git a/src/commons/utils/Constants.ts b/src/commons/utils/Constants.ts index acdef0d422..3871b76aa6 100644 --- a/src/commons/utils/Constants.ts +++ b/src/commons/utils/Constants.ts @@ -22,8 +22,6 @@ const urlShortenerSignature = process.env.REACT_APP_URL_SHORTENER_SIGNATURE; const moduleBackendUrl = process.env.REACT_APP_MODULE_BACKEND_URL || 'modules'; const sharedbBackendUrl = process.env.REACT_APP_SHAREDB_BACKEND_URL || ''; const playgroundOnly = !isTest && isTrue(process.env.REACT_APP_PLAYGROUND_ONLY); -const enableGame = isTest || isTrue(process.env.REACT_APP_ENABLE_GAME); -const enableAchievements = isTest || isTrue(process.env.REACT_APP_ENABLE_ACHIEVEMENTS); const enableGitHubAssessments = isTest || isTrue(process.env.REACT_APP_ENABLE_GITHUB_ASSESSMENTS); const sentryDsn = process.env.REACT_APP_SENTRY_DSN; const googleClientId = process.env.REACT_APP_GOOGLE_CLIENT_ID; @@ -113,8 +111,6 @@ const Constants = { moduleBackendUrl, authProviders, playgroundOnly, - enableGame, - enableAchievements, enableGitHubAssessments, sentryDsn, googleClientId, diff --git a/src/pages/academy/Academy.tsx b/src/pages/academy/Academy.tsx index b47bfc4a0e..aed4148901 100644 --- a/src/pages/academy/Academy.tsx +++ b/src/pages/academy/Academy.tsx @@ -5,7 +5,6 @@ import { Role } from '../../commons/application/ApplicationTypes'; import { isAcademyRe } from '../../commons/application/reducers/SessionsReducer'; import AssessmentContainer from '../../commons/assessment/AssessmentContainer'; import { AssessmentCategories, AssessmentCategory } from '../../commons/assessment/AssessmentTypes'; -import Constants from '../../commons/utils/Constants'; import { HistoryHelper } from '../../commons/utils/HistoryHelper'; import { assessmentCategoryLink } from '../../commons/utils/ParamParseHelper'; import { assessmentRegExp, gradingRegExp } from '../../features/academy/AcademyTypes'; @@ -55,7 +54,7 @@ class Academy extends React.Component { )}/${assessmentRegExp}`} render={this.assessmentRenderFactory(AssessmentCategories.Contest)} /> - {Constants.enableGame && } + { if (clickedFrom != null && isAcademyRe.exec(clickedFrom!) == null && lastAcademy != null) { return () => ; } else { - return Constants.enableGame ? this.redirectToGame : this.redirectToMissions; + return this.redirectToGame; } }; private redirectTo404 = () => ; private redirectToGame = () => ; - - private redirectToMissions = () => ; } export default Academy; From 90a9a621d6f6fb7fa45f57d798391b417403e55a Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Sat, 12 Jun 2021 01:20:54 +0800 Subject: [PATCH 002/148] Added CourseRegistration and CourseConfiguration to session types --- src/commons/application/ApplicationTypes.ts | 18 +++------- .../application/actions/SessionActions.ts | 10 ++++++ .../application/reducers/SessionsReducer.ts | 17 +++++++-- src/commons/application/types/SessionTypes.ts | 36 +++++++++++++------ 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index 0e1b30e057..2e3b87b3b5 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -283,31 +283,21 @@ export const defaultWorkspaceManager: WorkspaceManagerState = { }; export const defaultSession: SessionState = { - accessToken: undefined, + tokens: undefined, + user: undefined, + courseRegistration: undefined, + courseConfiguration: undefined, assessments: new Map(), assessmentOverviews: undefined, githubOctokitObject: { octokit: undefined }, - grade: 0, gradingOverviews: undefined, gradings: new Map(), - group: null, historyHelper: { lastAcademyLocations: [null, null], lastGeneralLocations: [null, null] }, maxGrade: 0, maxXp: 0, - refreshToken: undefined, - role: undefined, - name: undefined, - story: { - story: '', - playStory: false - }, - gameState: { - completed_quests: [], - collectibles: {} - }, xp: 0, notifications: [] }; diff --git a/src/commons/application/actions/SessionActions.ts b/src/commons/application/actions/SessionActions.ts index b5d47662b6..98fc073f55 100644 --- a/src/commons/application/actions/SessionActions.ts +++ b/src/commons/application/actions/SessionActions.ts @@ -10,6 +10,8 @@ import { import { generateOctokitInstance } from '../../utils/GitHubPersistenceHelper'; import { ACKNOWLEDGE_NOTIFICATIONS, + CourseConfiguration, + CourseRegistration, FETCH_ASSESSMENT, FETCH_ASSESSMENT_OVERVIEWS, FETCH_AUTH, @@ -23,6 +25,8 @@ import { REAUTOGRADE_ANSWER, REAUTOGRADE_SUBMISSION, REMOVE_GITHUB_OCTOKIT_OBJECT, + SET_COURSE_CONFIGURATION, + SET_COURSE_REGISTRATION, SET_GITHUB_ASSESSMENT, SET_GITHUB_OCTOKIT_OBJECT, SET_GOOGLE_USER, @@ -80,6 +84,12 @@ export const setTokens = ({ export const setUser = (user: User) => action(SET_USER, user); +export const setCourseConfiguration = (courseConfiguration: CourseConfiguration) => + action(SET_COURSE_CONFIGURATION, courseConfiguration); + +export const setCourseRegistration = (courseRegistration: CourseRegistration) => + action(SET_COURSE_REGISTRATION, courseRegistration); + export const setGoogleUser = (user?: string) => action(SET_GOOGLE_USER, user); export const setGitHubAssessment = (missionRepoData: MissionRepoData) => diff --git a/src/commons/application/reducers/SessionsReducer.ts b/src/commons/application/reducers/SessionsReducer.ts index 5efc886d5e..d2cda7c3a1 100644 --- a/src/commons/application/reducers/SessionsReducer.ts +++ b/src/commons/application/reducers/SessionsReducer.ts @@ -10,6 +10,8 @@ import { LOG_OUT } from '../types/CommonsTypes'; import { REMOVE_GITHUB_OCTOKIT_OBJECT, SessionState, + SET_COURSE_CONFIGURATION, + SET_COURSE_REGISTRATION, SET_GITHUB_ASSESSMENT, SET_GITHUB_OCTOKIT_OBJECT, SET_GOOGLE_USER, @@ -48,13 +50,22 @@ export const SessionsReducer: Reducer = ( case SET_TOKENS: return { ...state, - accessToken: action.payload.accessToken, - refreshToken: action.payload.refreshToken + tokens: action.payload }; case SET_USER: return { ...state, - ...action.payload + user: action.payload + }; + case SET_COURSE_CONFIGURATION: + return { + ...state, + courseConfiguration: action.payload + }; + case SET_COURSE_REGISTRATION: + return { + ...state, + courseRegistration: action.payload }; case UPDATE_HISTORY_HELPERS: const helper = state.historyHelper; diff --git a/src/commons/application/types/SessionTypes.ts b/src/commons/application/types/SessionTypes.ts index 9171b424bc..6e3bc75e69 100644 --- a/src/commons/application/types/SessionTypes.ts +++ b/src/commons/application/types/SessionTypes.ts @@ -19,6 +19,8 @@ export const LOGIN_GITHUB = 'LOGIN_GITHUB'; export const LOGOUT_GITHUB = 'LOGOUT_GITHUB'; export const SET_TOKENS = 'SET_TOKENS'; export const SET_USER = 'SET_USER'; +export const SET_COURSE_CONFIGURATION = 'SET_COURSE_CONFIGURATION'; +export const SET_COURSE_REGISTRATION = 'SET_COURSE_REGISTRATION'; export const SET_GOOGLE_USER = 'SET_GOOGLE_USER'; export const SET_GITHUB_ASSESSMENT = 'SET_GITHUB_ASSESSMENT'; export const SET_GITHUB_OCTOKIT_OBJECT = 'SET_GITHUB_OCTOKIT_OBJECT'; @@ -43,22 +45,17 @@ export const UPLOAD_KEYSTROKE_LOGS = 'UPLOAD_KEYSTROKE_LOGS'; export const UPLOAD_UNSENT_LOGS = 'UPLOAD_UNSENT_LOGS'; export type SessionState = { - readonly accessToken?: string; + readonly tokens?: Tokens; + readonly user?: User; + readonly courseRegistration?: CourseRegistration; + readonly courseConfiguration?: CourseConfiguration; readonly assessmentOverviews?: AssessmentOverview[]; readonly assessments: Map; - readonly grade: number; readonly gradingOverviews?: GradingOverview[]; readonly gradings: Map; - readonly group: string | null; readonly historyHelper: HistoryHelper; readonly maxGrade: number; readonly maxXp: number; - readonly refreshToken?: string; - readonly role?: Role; - readonly story: Story; - readonly gameState: GameState; - readonly name?: string; - readonly userId?: number; readonly xp: number; readonly notifications: Notification[]; readonly googleUser?: string; @@ -76,9 +73,28 @@ export type Tokens = { export type User = { userId: number; name: string; + courses: number[]; +}; + +export type CourseRegistration = { role: Role; + gameState?: GameState; group: string | null; + userId: number; + courseId: number; grade: number; story?: Story; - gameState?: GameState; }; + +export type CourseConfiguration = { + name: string; + moduleCode: string; + viewable: boolean; + enableGame: boolean; + enableAchievements: boolean; + enableSourcecast: boolean; + sourceChapter: number; + sourceVariant: string; + moduleHelpText: string; + assessmentTypes: string[]; +} From 9fa82143f4e60f60018b83fc25e6462f8bb70877 Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Sat, 12 Jun 2021 01:35:24 +0800 Subject: [PATCH 003/148] Added achievements and game toggle from store --- src/commons/application/Application.tsx | 6 +++++- src/commons/application/ApplicationContainer.ts | 3 ++- src/commons/navigationBar/NavigationBar.tsx | 4 +++- .../subcomponents/NavigationBarMobileSideMenu.tsx | 3 ++- src/commons/sagas/AchievementSaga.ts | 3 ++- src/pages/academy/Academy.tsx | 7 +++++-- src/pages/academy/AcademyContainer.ts | 3 ++- 7 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/commons/application/Application.tsx b/src/commons/application/Application.tsx index 22eedf9344..bab4b00760 100644 --- a/src/commons/application/Application.tsx +++ b/src/commons/application/Application.tsx @@ -31,6 +31,7 @@ export type StateProps = { role?: Role; title: string; name?: string; + enableAchievements?: boolean; }; const Application: React.FC = props => { @@ -106,9 +107,11 @@ const Application: React.FC = props => { render={toIncubator} key={1} />, - , loginPath ]; + if (!Constants.playgroundOnly && props.enableAchievements) { + fullPaths?.push(); + } const disabled = !['staff', 'admin'].includes(props.role!) && isDisabled; const renderDisabled = () => ( @@ -124,6 +127,7 @@ const Application: React.FC = props => { role={props.role} name={props.name} title={props.title} + enableAchievements={props.enableAchievements} />
{disabled && ( diff --git a/src/commons/application/ApplicationContainer.ts b/src/commons/application/ApplicationContainer.ts index 084282d56c..46cadec635 100644 --- a/src/commons/application/ApplicationContainer.ts +++ b/src/commons/application/ApplicationContainer.ts @@ -17,7 +17,8 @@ import { OverallState } from './ApplicationTypes'; const mapStateToProps: MapStateToProps = state => ({ title: state.application.title, role: state.session.role, - name: state.session.name + name: state.session.name, + enableAchievements: state.session.courseConfiguration?.enableAchievements }); const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => diff --git a/src/commons/navigationBar/NavigationBar.tsx b/src/commons/navigationBar/NavigationBar.tsx index 3dbd32b0d5..12b204a0f4 100644 --- a/src/commons/navigationBar/NavigationBar.tsx +++ b/src/commons/navigationBar/NavigationBar.tsx @@ -37,6 +37,7 @@ type StateProps = { role?: Role; title: string; name?: string; + enableAchievements?: boolean; }; const NavigationBar: React.FC = props => { @@ -153,6 +154,7 @@ const NavigationBar: React.FC = props => { setMobileSideMenuOpen(false)} handleGitHubLogIn={() => props.handleGitHubLogIn} @@ -201,7 +203,7 @@ const NavigationBar: React.FC = props => { )} - {props.role && ( + {props.role && props.enableAchievements && ( = props => ( @@ -171,7 +172,7 @@ const NavigationBarMobileSideMenu: React.FC =
GitHub Assessments
- {props.role && ( + {props.role && props.enableAchievements && ( ): any { const role = yield select((state: OverallState) => state.session.role); - if (role && !Constants.playgroundOnly) { + const enableAchievements = yield select((state: OverallState) => state.session.courseConfiguration?.enableAchievements) + if (role && enableAchievements && !Constants.playgroundOnly) { loggedEvents.push(action.payload); if (!timeoutSet && role) { diff --git a/src/pages/academy/Academy.tsx b/src/pages/academy/Academy.tsx index aed4148901..576976a668 100644 --- a/src/pages/academy/Academy.tsx +++ b/src/pages/academy/Academy.tsx @@ -23,6 +23,7 @@ export type DispatchProps = { export type StateProps = { historyHelper: HistoryHelper; + enableGame: boolean | undefined; }; export type OwnProps = { @@ -54,7 +55,7 @@ class Academy extends React.Component { )}/${assessmentRegExp}`} render={this.assessmentRenderFactory(AssessmentCategories.Contest)} /> - + {this.props.enableGame && } { if (clickedFrom != null && isAcademyRe.exec(clickedFrom!) == null && lastAcademy != null) { return () => ; } else { - return this.redirectToGame; + return this.props.enableGame ? this.redirectToGame : this.redirectToMissions; } }; private redirectTo404 = () => ; private redirectToGame = () => ; + + private redirectToMissions = () => } export default Academy; diff --git a/src/pages/academy/AcademyContainer.ts b/src/pages/academy/AcademyContainer.ts index 7aee236269..ea09aaab9a 100644 --- a/src/pages/academy/AcademyContainer.ts +++ b/src/pages/academy/AcademyContainer.ts @@ -7,7 +7,8 @@ import { OverallState } from '../../commons/application/ApplicationTypes'; import Academy, { DispatchProps, StateProps } from './Academy'; const mapStateToProps: MapStateToProps = state => ({ - historyHelper: state.session.historyHelper + historyHelper: state.session.historyHelper, + enableGame: state.session.courseConfiguration?.enableGame }); const mapDispatchToProps: MapDispatchToProps = dispatch => From 4dd33a26e91448e8651e47ea6e5ffd5cb3a9bb2f Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Sat, 12 Jun 2021 12:02:07 +0800 Subject: [PATCH 004/148] Updated Application routes for Source Academy @NUS and playground-only configurations --- src/commons/application/Application.tsx | 106 ++++++++++-------- .../application/ApplicationContainer.ts | 3 +- 2 files changed, 64 insertions(+), 45 deletions(-) diff --git a/src/commons/application/Application.tsx b/src/commons/application/Application.tsx index bab4b00760..b2a10d4232 100644 --- a/src/commons/application/Application.tsx +++ b/src/commons/application/Application.tsx @@ -13,7 +13,7 @@ import Login from '../../pages/login/LoginContainer'; import MissionControlContainer from '../../pages/missionControl/MissionControlContainer'; import NotFound from '../../pages/notFound/NotFound'; import Playground from '../../pages/playground/PlaygroundContainer'; -import SourcecastContainer from '../../pages/sourcecast/SourcecastContainer'; +import Sourcecast from '../../pages/sourcecast/SourcecastContainer'; import NavigationBar from '../navigationBar/NavigationBar'; import Constants from '../utils/Constants'; import { parseQuery } from '../utils/QueryHelper'; @@ -32,6 +32,7 @@ export type StateProps = { title: string; name?: string; enableAchievements?: boolean; + enableSourcecast?: boolean; }; const Application: React.FC = props => { @@ -98,20 +99,55 @@ const Application: React.FC = props => { }, [isPWA, isMobile]); const loginPath = ; - const fullPaths = Constants.playgroundOnly - ? null - : [ - , + + const githubAssessmentsPaths = Constants.enableGitHubAssessments + ? [ ( + + )} + key="githubAssessmentsMissions" />, - loginPath - ]; - if (!Constants.playgroundOnly && props.enableAchievements) { - fullPaths?.push(); - } + + ] + : []; + + // Paths for the playground-only deployment + const playgroundOnlyPaths = [ + , + , + , + ...githubAssessmentsPaths + ]; + + // Paths for the Source Academy @NUS deployment + const fullPaths = [ + loginPath, + )} key="authPlayground" />, + ...playgroundOnlyPaths, + , + + ]; + + if (props.enableSourcecast) { + fullPaths.push()} key="sourcecast" />); + } + if (props.enableAchievements) { + fullPaths.push()} key="achievements" />); + } + const disabled = !['staff', 'admin'].includes(props.role!) && isDisabled; const renderDisabled = () => ( @@ -143,35 +179,17 @@ const Application: React.FC = props => { )} - {!disabled && ( + {!disabled && Constants.playgroundOnly && ( + + {playgroundOnlyPaths} + + + + )} + {!disabled && !Constants.playgroundOnly && ( - - - - {Constants.enableGitHubAssessments && ( - ( - - )} - /> - )} - {Constants.enableGitHubAssessments && ( - - )} - {fullPaths} - + )} @@ -193,12 +211,12 @@ const toAcademy = ({ role }: ApplicationProps) => role === undefined ? redirectToLogin : () => ; /** - * A user routes to /achievement, - * 1. If the user is logged in, render the Achievement component + * Routes a user to the specified route, + * 1. If the user is logged in, render the specified component * 2. If the user is not logged in, redirect to /login */ -const toAchievement = ({ role }: ApplicationProps) => - role === undefined ? redirectToLogin : () => ; +const ensureRoleAndRouteTo = ({ role }: ApplicationProps, to: JSX.Element) => + role === undefined ? redirectToLogin : () => to const toLogin = (props: ApplicationProps) => () => { const qstr = parseQuery(props.location.search); diff --git a/src/commons/application/ApplicationContainer.ts b/src/commons/application/ApplicationContainer.ts index 46cadec635..1b59430216 100644 --- a/src/commons/application/ApplicationContainer.ts +++ b/src/commons/application/ApplicationContainer.ts @@ -18,7 +18,8 @@ const mapStateToProps: MapStateToProps = state => title: state.application.title, role: state.session.role, name: state.session.name, - enableAchievements: state.session.courseConfiguration?.enableAchievements + enableAchievements: state.session.courseConfiguration?.enableAchievements, + enableSourcecast: state.session.courseConfiguration?.enableSourcecast }); const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => From 03d9c3685318bf9929b15fb61bc74581c7aab2fb Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Sat, 12 Jun 2021 12:57:52 +0800 Subject: [PATCH 005/148] Updated Academy Assessment routes and changed Assessment category to Assessment type --- src/commons/assessment/Assessment.tsx | 7 +-- src/commons/assessment/AssessmentTypes.ts | 8 ++-- src/pages/academy/Academy.tsx | 55 ++++++++--------------- src/pages/academy/AcademyContainer.ts | 3 +- 4 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/commons/assessment/Assessment.tsx b/src/commons/assessment/Assessment.tsx index 4444c79c78..ce8ba0cf0b 100644 --- a/src/commons/assessment/Assessment.tsx +++ b/src/commons/assessment/Assessment.tsx @@ -56,7 +56,7 @@ export type DispatchProps = { }; export type OwnProps = { - assessmentCategory: AssessmentCategory; + assessmentType: string; }; export type StateProps = { @@ -132,7 +132,7 @@ const Assessment: React.FC = props => { } return ( @@ -168,6 +168,7 @@ const Assessment: React.FC = props => { renderGradingStatus: boolean ) => { const showGrade = overview.gradingStatus === 'graded' || overview.category === 'Path'; + const showGrade = overview.gradingStatus === 'graded' const ratio = isMobileBreakpoint ? 5 : 3; return (
@@ -328,7 +329,7 @@ const Assessment: React.FC = props => { // Define the betcha dialog (in each card's menu) const submissionText = betchaAssessment ? (

- You are about to finalise your submission for the {betchaAssessment.category.toLowerCase()}{' '} + You are about to finalise your submission for the {betchaAssessment.type.toLowerCase()}{' '} "{betchaAssessment.title}".

) : ( diff --git a/src/commons/assessment/AssessmentTypes.ts b/src/commons/assessment/AssessmentTypes.ts index 3393f46ca5..8e76a4e288 100644 --- a/src/commons/assessment/AssessmentTypes.ts +++ b/src/commons/assessment/AssessmentTypes.ts @@ -61,7 +61,7 @@ W* Used to display information regarding an assessment in the UI. * the assessment opens */ export type AssessmentOverview = { - category: AssessmentCategory; + type: string; closeAt: string; coverImage: string; fileName?: string; // For mission control @@ -86,7 +86,7 @@ export type AssessmentOverview = { * Used when an assessment is being actively attempted/graded. */ export type Assessment = { - category: AssessmentCategory; + type: string; globalDeployment?: Library; // For mission control graderDeployment?: Library; // For mission control id: number; @@ -222,7 +222,7 @@ export const normalLibrary = (): Library => { export const overviewTemplate = (): AssessmentOverview => { return { - category: AssessmentCategories.Mission, + type: 'Missions', closeAt: '2100-12-01T00:00+08', coverImage: 'https://fakeimg.pl/300/', grade: 1, @@ -306,7 +306,7 @@ export const mcqTemplate = (): IMCQQuestion => { export const assessmentTemplate = (): Assessment => { return { - category: 'Mission', + type: 'Missions', globalDeployment: normalLibrary(), graderDeployment: emptyLibrary(), id: -1, diff --git a/src/pages/academy/Academy.tsx b/src/pages/academy/Academy.tsx index 576976a668..2823e512f2 100644 --- a/src/pages/academy/Academy.tsx +++ b/src/pages/academy/Academy.tsx @@ -24,6 +24,7 @@ export type DispatchProps = { export type StateProps = { historyHelper: HistoryHelper; enableGame: boolean | undefined; + assessmentTypes: string[] | undefined; }; export type OwnProps = { @@ -43,44 +44,20 @@ class Academy extends React.Component { , , , - + , + ] : null; return (
- + {this.props.assessmentTypes?.map(assessmentType => ( + + ))} {this.props.enableGame && } - - - - - {staffRoutes} @@ -90,8 +67,8 @@ class Academy extends React.Component { } private assessmentRenderFactory = - (cat: AssessmentCategory) => (routerProps: RouteComponentProps) => - ; + (assessmentType: string) => (routerProps: RouteComponentProps) => + ; /** * 1. If user is in /academy.*, redirect to game @@ -104,7 +81,7 @@ class Academy extends React.Component { if (clickedFrom != null && isAcademyRe.exec(clickedFrom!) == null && lastAcademy != null) { return () => ; } else { - return this.props.enableGame ? this.redirectToGame : this.redirectToMissions; + return this.props.enableGame ? this.redirectToGame : this.redirectToAssessments; } }; @@ -112,7 +89,13 @@ class Academy extends React.Component { private redirectToGame = () => ; - private redirectToMissions = () => + private redirectToAssessments = () => { + return this.props.assessmentTypes ? ( + + ) : ( + this.redirectTo404() + ); + }; } export default Academy; diff --git a/src/pages/academy/AcademyContainer.ts b/src/pages/academy/AcademyContainer.ts index ea09aaab9a..3c27b3b64c 100644 --- a/src/pages/academy/AcademyContainer.ts +++ b/src/pages/academy/AcademyContainer.ts @@ -8,7 +8,8 @@ import Academy, { DispatchProps, StateProps } from './Academy'; const mapStateToProps: MapStateToProps = state => ({ historyHelper: state.session.historyHelper, - enableGame: state.session.courseConfiguration?.enableGame + enableGame: state.session.courseConfiguration?.enableGame, + assessmentTypes: state.session.courseConfiguration?.assessmentTypes }); const mapDispatchToProps: MapDispatchToProps = dispatch => From 412dc992a14bb6d1b0cf272dac0628c62469b8f6 Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Sat, 12 Jun 2021 14:04:16 +0800 Subject: [PATCH 006/148] Updated academy navbar routing and included assessment types from backend. Yarn format --- src/commons/application/Application.tsx | 26 +++- .../application/ApplicationContainer.ts | 3 +- src/commons/assessment/Assessment.tsx | 5 +- src/commons/assessment/AssessmentContainer.ts | 7 +- src/commons/navigationBar/NavigationBar.tsx | 4 +- .../subcomponents/AcademyNavigationBar.tsx | 90 ++++---------- .../NavigationBarMobileSideMenu.tsx | 113 ++++-------------- 7 files changed, 80 insertions(+), 168 deletions(-) diff --git a/src/commons/application/Application.tsx b/src/commons/application/Application.tsx index b2a10d4232..9fa8257d34 100644 --- a/src/commons/application/Application.tsx +++ b/src/commons/application/Application.tsx @@ -33,6 +33,7 @@ export type StateProps = { name?: string; enableAchievements?: boolean; enableSourcecast?: boolean; + assessmentTypes?: string[]; }; const Application: React.FC = props => { @@ -131,7 +132,11 @@ const Application: React.FC = props => { // Paths for the Source Academy @NUS deployment const fullPaths = [ loginPath, - )} key="authPlayground" />, + )} + key="authPlayground" + />, ...playgroundOnlyPaths, , = props => { ]; if (props.enableSourcecast) { - fullPaths.push()} key="sourcecast" />); + fullPaths.push( + )} + key="sourcecast" + /> + ); } if (props.enableAchievements) { - fullPaths.push()} key="achievements" />); + fullPaths.push( + )} + key="achievements" + /> + ); } const disabled = !['staff', 'admin'].includes(props.role!) && isDisabled; @@ -164,6 +181,7 @@ const Application: React.FC = props => { name={props.name} title={props.title} enableAchievements={props.enableAchievements} + assessmentTypes={props.assessmentTypes} />
{disabled && ( @@ -216,7 +234,7 @@ const toAcademy = ({ role }: ApplicationProps) => * 2. If the user is not logged in, redirect to /login */ const ensureRoleAndRouteTo = ({ role }: ApplicationProps, to: JSX.Element) => - role === undefined ? redirectToLogin : () => to + role === undefined ? redirectToLogin : () => to; const toLogin = (props: ApplicationProps) => () => { const qstr = parseQuery(props.location.search); diff --git a/src/commons/application/ApplicationContainer.ts b/src/commons/application/ApplicationContainer.ts index 1b59430216..6ef3ea8013 100644 --- a/src/commons/application/ApplicationContainer.ts +++ b/src/commons/application/ApplicationContainer.ts @@ -19,7 +19,8 @@ const mapStateToProps: MapStateToProps = state => role: state.session.role, name: state.session.name, enableAchievements: state.session.courseConfiguration?.enableAchievements, - enableSourcecast: state.session.courseConfiguration?.enableSourcecast + enableSourcecast: state.session.courseConfiguration?.enableSourcecast, + assessmentTypes: state.session.courseConfiguration?.assessmentTypes }); const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => diff --git a/src/commons/assessment/Assessment.tsx b/src/commons/assessment/Assessment.tsx index ce8ba0cf0b..ee8ab81329 100644 --- a/src/commons/assessment/Assessment.tsx +++ b/src/commons/assessment/Assessment.tsx @@ -132,9 +132,7 @@ const Assessment: React.FC = props => { } return (
@@ -92,12 +90,4 @@ const LoginButton = ({ ); }; -const playgroundButton = ( - - - -); - export default Login; From e77d4cf0f8e9acff809a9951ddfac29cb2166242 Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Tue, 15 Jun 2021 14:35:09 +0800 Subject: [PATCH 044/148] Updated localStorage to include course config info --- src/pages/__tests__/localStorage.test.ts | 10 +++++++++- src/pages/localStorage.ts | 9 ++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/pages/__tests__/localStorage.test.ts b/src/pages/__tests__/localStorage.test.ts index 8479cbd0de..b60832d701 100644 --- a/src/pages/__tests__/localStorage.test.ts +++ b/src/pages/__tests__/localStorage.test.ts @@ -8,7 +8,15 @@ const mockShortDefaultState: SavedState = { accessToken: defaultState.session.accessToken, refreshToken: defaultState.session.refreshToken, role: defaultState.session.role, - name: defaultState.session.name + name: defaultState.session.name, + userId: defaultState.session.userId, + courseId: defaultState.session.courseId, + courseShortname: defaultState.session.courseShortname, + enableGame: defaultState.session.enableGame, + enableAchievements: defaultState.session.enableAchievements, + enableSourcecast: defaultState.session.enableSourcecast, + moduleHelpText: defaultState.session.moduleHelpText, + assessmentTypes: defaultState.session.assessmentTypes }, achievements: defaultState.achievement.achievements, playgroundEditorValue: defaultState.workspaces.playground.editorValue, diff --git a/src/pages/localStorage.ts b/src/pages/localStorage.ts index 657ac421e8..5c98958fdf 100644 --- a/src/pages/localStorage.ts +++ b/src/pages/localStorage.ts @@ -42,7 +42,14 @@ export const saveState = (state: OverallState) => { refreshToken: state.session.refreshToken, role: state.session.role, name: state.session.name, - userId: state.session.userId + userId: state.session.userId, + courseId: state.session.courseId, + courseShortname: state.session.courseShortname, + enableGame: state.session.enableGame, + enableAchievements: state.session.enableSourcecast, + enableSourcecast: state.session.enableSourcecast, + moduleHelpText: state.session.moduleHelpText, + assessmentTypes: state.session.assessmentTypes }, achievements: state.achievement.achievements, playgroundEditorValue: state.workspaces.playground.editorValue, From 44907b2955f5a146537947ef81fce038f9366a7d Mon Sep 17 00:00:00 2001 From: Chow En Rong <53928333+chownces@users.noreply.github.com> Date: Tue, 15 Jun 2021 16:05:11 +0800 Subject: [PATCH 045/148] Updated routing, and added hover secondary navbar for playground and sourcecast --- src/commons/application/Application.tsx | 6 +- src/commons/navigationBar/NavigationBar.tsx | 100 ++++++++++++------ .../subcomponents/AcademyNavigationBar.tsx | 1 + .../NavigationBarMobileSideMenu.tsx | 3 +- src/pages/academy/Academy.tsx | 1 + src/styles/_navigationBar.scss | 24 +++++ 6 files changed, 97 insertions(+), 38 deletions(-) diff --git a/src/commons/application/Application.tsx b/src/commons/application/Application.tsx index 71df79f255..6f1d78b6aa 100644 --- a/src/commons/application/Application.tsx +++ b/src/commons/application/Application.tsx @@ -129,8 +129,8 @@ const Application: React.FC = props => { , , , - , - , + , + , ...githubAssessmentsPaths ]; @@ -169,7 +169,7 @@ const Application: React.FC = props => { if (props.enableAchievements) { fullPaths.push( )} key="achievements" /> diff --git a/src/commons/navigationBar/NavigationBar.tsx b/src/commons/navigationBar/NavigationBar.tsx index f03014a368..a29b98623d 100644 --- a/src/commons/navigationBar/NavigationBar.tsx +++ b/src/commons/navigationBar/NavigationBar.tsx @@ -12,18 +12,20 @@ import { Position } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import { Tooltip2 } from '@blueprintjs/popover2'; +import { Popover2 } from '@blueprintjs/popover2'; import classNames from 'classnames'; import * as React from 'react'; import { useMediaQuery } from 'react-responsive'; -import { NavLink, Route, Switch } from 'react-router-dom'; +import { NavLink, Route, Switch, useLocation } from 'react-router-dom'; import SicpNavigationBar from 'src/commons/navigationBar/subcomponents/SicpNavigationBar'; import { Role } from '../application/ApplicationTypes'; import { AssessmentType } from '../assessment/AssessmentTypes'; import Dropdown from '../dropdown/Dropdown'; +import NotificationBadgeContainer from '../notificationBadge/NotificationBadgeContainer'; +import { filterNotificationsByType } from '../notificationBadge/NotificationBadgeHelper'; import Constants from '../utils/Constants'; -import AcademyNavigationBar from './subcomponents/AcademyNavigationBar'; +import AcademyNavigationBar, { icons } from './subcomponents/AcademyNavigationBar'; import GitHubAssessmentsNavigationBar from './subcomponents/GitHubAssessmentsNavigationBar'; import NavigationBarMobileSideMenu from './subcomponents/NavigationBarMobileSideMenu'; @@ -46,8 +48,8 @@ type StateProps = { const NavigationBar: React.FC = props => { const [mobileSideMenuOpen, setMobileSideMenuOpen] = React.useState(false); - const [desktopMenuOpen, setDesktopMenuOpen] = React.useState(true); const isMobileBreakpoint = useMediaQuery({ maxWidth: Constants.mobileBreakpoint }); + const location = useLocation(); FocusStyleManager.onlyShowFocusOnTabs(); @@ -210,22 +212,64 @@ const NavigationBar: React.FC = props => { ); + const desktopNavbarLeftPopoverContent = ( + + + {props.assessmentTypes?.map((assessmentType, idx) => ( + + +
{assessmentType}
+ +
+ ))} +
+
+ ); + + const desktopLogoButton = ( + + + {props.courseShortname && ( + {props.courseShortname} + )} + {!props.courseShortname && ( + Source Academy @ NUS + )} + + ); + + const enableDesktopPopoverIn = ['/playground', '/sourcecast']; + const enableDesktopPopover = enableDesktopPopoverIn.reduce((acc, x) => { + return acc || location.pathname.startsWith(x); + }, false); + // Handles the Source Academy @ NUS left desktop navbar group const desktopNavbarLeft = ( - - - {props.courseShortname && ( - {props.courseShortname} - )} - {!props.courseShortname && ( - Source Academy @ NUS - )} - + {enableDesktopPopover ? ( + + {desktopLogoButton} + + ) : ( + desktopLogoButton + )} {props.role && props.enableSourcecast && ( = props => {
Achievements
@@ -289,20 +333,6 @@ const NavigationBar: React.FC = props => {
Contributors
- {!Constants.playgroundOnly && props.role && !isMobileBreakpoint && ( - <> - - -