Skip to content

Commit

Permalink
chore: add xblock studio view (#178)
Browse files Browse the repository at this point in the history
* chore: add xblock studio view

* chore: linting

* chore: add preview route

* chore: fix url
  • Loading branch information
leangseu-edx authored Jan 24, 2024
1 parent 34935db commit 2d814c0
Show file tree
Hide file tree
Showing 36 changed files with 853 additions and 35 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ CSRF_TOKEN_API_PATH=''
ECOMMERCE_BASE_URL=''
LANGUAGE_PREFERENCE_COOKIE_NAME=''
LMS_BASE_URL=''
STUDIO_BASE_URL=''
LOGIN_URL=''
LOGOUT_URL=''
LOGO_URL=''
Expand Down
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
ECOMMERCE_BASE_URL='http://localhost:18130'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000'
STUDIO_BASE_URL='http://localhost:18010'
LOGIN_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/logout'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
ECOMMERCE_BASE_URL='http://localhost:18130'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000'
STUDIO_BASE_URL='http://localhost:18010'
LOGIN_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/logout'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
Expand Down
5 changes: 5 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SkeletonTheme } from '@edx/paragon';
import AssessmentView from 'views/AssessmentView';
import SubmissionView from 'views/SubmissionView';
import XBlockView from 'views/XBlockView';
import XBlockStudioView from 'views/XBlockStudioView';
import GradeView from 'views/GradeView';

import AppContainer from 'components/AppContainer';
Expand Down Expand Up @@ -67,6 +68,8 @@ const App = () => {
/*
const embeddedRoutes = [
<Route key="embedXblock" path={routes.xblockEmbed} element={<XBlockView />} />,
<Route key="embedXblockStudio" path={routes.xblockStudioEmbed} element={<XBlockStudioView />} />,
<Route key="embedXblockPreview" path={routes.xblockPreviewEmbed} element={<XBlockView />} />,
modalRoute(routes.peerAssessmentEmbed, PeerAssessmentView, 'ORA Peer Assessment'),
modalRoute(routes.selfAssessmentEmbed, SelfAssessmentView, 'ORA Self Assessment'),
modalRoute(routes.studentTrainingEmbed, StudentTrainingView, 'ORA Student Training'),
Expand All @@ -81,6 +84,8 @@ const App = () => {
*/
const baseRoutes = [
appRoute(routes.xblock, XBlockView),
appRoute(routes.xblockStudio, XBlockStudioView),
appRoute(routes.xblockPreview, XBlockView),
modalRoute(routes.peerAssessment, AssessmentView),
modalRoute(routes.selfAssessment, AssessmentView),
modalRoute(routes.studentTraining, AssessmentView),
Expand Down
3 changes: 2 additions & 1 deletion src/components/Instructions/useInstructionsMessage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { stepNames, stepStates } from 'constants/index';

import { useGlobalState } from 'hooks/app';
import { useViewStep } from 'hooks/routing';
import { isXblockStep } from 'utils';

import messages from './messages';

Expand All @@ -15,7 +16,7 @@ const useInstructionsMessage = () => {
effectiveGrade,
stepState,
} = useGlobalState();
const stepName = (viewStep === stepNames.xblock) ? activeStepName : viewStep;
const stepName = isXblockStep(viewStep) ? activeStepName : viewStep;
if (stepState !== stepStates.inProgress || stepName === stepNames.staff) {
return null;
}
Expand Down
6 changes: 4 additions & 2 deletions src/components/ProgressBar/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useViewStep } from 'hooks/routing';
import { useGlobalState, useStepInfo } from 'hooks/app';
import { useOpenModal } from 'hooks/modal';
import { stepRoutes, stepStates, stepNames } from 'constants/index';
import { isXblockStep } from 'utils';

export const useProgressStepData = ({ step, canRevisit = false }) => {
const { xblockId, courseId } = useParams();
Expand All @@ -15,10 +16,11 @@ export const useProgressStepData = ({ step, canRevisit = false }) => {
} = useGlobalState({ step });
const stepInfo = useStepInfo();
const openModal = useOpenModal();
const isXblock = isXblockStep(viewStep);

const href = `/${stepRoutes[step]}/${courseId}/${xblockId}`;
const onClick = () => openModal({ view: step, title: step });
const isActive = viewStep === stepNames.xblock
const isActive = isXblock
? activeStepName === step
: viewStep === step;
let isEnabled = isActive
Expand All @@ -31,7 +33,7 @@ export const useProgressStepData = ({ step, canRevisit = false }) => {
isEnabled = !isWaitingForSubmissions && (isEnabled || isPeerComplete);
}
return {
...(viewStep === stepNames.xblock ? { onClick } : { href }),
...(isXblock ? { onClick } : { href }),
isEnabled,
isActive,
isComplete: stepState === stepStates.done,
Expand Down
5 changes: 4 additions & 1 deletion src/components/ProgressBar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
import { stepNames } from 'constants/index';

import { useViewStep } from 'hooks/routing';
import { isXblockStep } from 'utils';

import ProgressStep from './ProgressStep';

import messages from './messages';
Expand Down Expand Up @@ -42,6 +44,7 @@ export const ProgressBar = ({ className }) => {

const activeStep = useViewStep();
const { activeStepName } = useGlobalState();
const isXblock = isXblockStep(activeStep);

const stepOrders = [
stepNames.submission,
Expand All @@ -66,7 +69,7 @@ export const ProgressBar = ({ className }) => {
/>
);

let activeStepTitle = activeStep === stepNames.xblock ? activeStepName : activeStep;
let activeStepTitle = isXblock ? activeStepName : activeStep;
if (activeStepTitle === stepNames.staff) {
activeStepTitle = stepNames.submission;
}
Expand Down
14 changes: 0 additions & 14 deletions src/components/Prompt/hooks.js

This file was deleted.

26 changes: 19 additions & 7 deletions src/components/Prompt/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import { useActiveStepName, useORAConfigData } from 'hooks/app';
import { useViewStep } from 'hooks/routing';

import messages from './messages';
import usePromptHooks from './hooks';

import './index.scss';

const Prompt = ({ prompt, defaultOpen }) => {
const { open, toggleOpen } = usePromptHooks({ defaultOpen });
const Prompt = ({
prompt, title, open, onToggle,
}) => {
const { formatMessage } = useIntl();
const viewStep = useViewStep();
const activeStepName = useActiveStepName();
const message = messages[viewStep] || messages[activeStepName];
const title = message ? formatMessage(message) : '';
const promptTitle = title || formatMessage(message) || '';
const imgRegex = /img src="\/asset-v1([^"]*)?"/g;
const linkRegex = /a href="\/asset-v1([^"]*)?"/g;
const { baseAssetUrl } = useORAConfigData();
Expand All @@ -33,20 +33,32 @@ const Prompt = ({ prompt, defaultOpen }) => {
const promptWithStaticAssets = promptWithAssets
.replaceAll(staticRegex.img, `img src="${process.env.LMS_BASE_URL}/${baseAssetUrl}$1"`)
.replaceAll(staticRegex.link, `a href="${process.env.LMS_BASE_URL}/${baseAssetUrl}$1"`);

const collapsibleProps = open !== null && onToggle !== null ? {
open,
onToggle,
} : {
defaultOpen: true,
};

return (
<Collapsible title={(<h3 className="py-3">{title}</h3>)} open={open} onToggle={toggleOpen}>
<Collapsible title={(<h3 className="py-3">{promptTitle}</h3>)} {...collapsibleProps}>
<div className="prompt" dangerouslySetInnerHTML={{ __html: promptWithStaticAssets }} />

Check warning on line 46 in src/components/Prompt/index.jsx

View workflow job for this annotation

GitHub Actions / test

Dangerous property 'dangerouslySetInnerHTML' found
</Collapsible>
);
};

Prompt.defaultProps = {
defaultOpen: true,
open: null,
onToggle: null,
title: null,
};

Prompt.propTypes = {
defaultOpen: PropTypes.bool,
prompt: PropTypes.string.isRequired,
open: PropTypes.bool,
onToggle: PropTypes.func,
title: PropTypes.string,
};

export default Prompt;
4 changes: 3 additions & 1 deletion src/components/StatusAlert/hooks/simpleAlerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { useExitAction, useStartStepAction } from 'hooks/actions';

import { stepNames, stepStates } from 'constants/index';

import { isXblockStep } from 'utils';
import useCreateAlert from './useCreateAlert';
import messages from '../messages';

export const useGradedAlerts = ({ step }) => {
const viewStep = useViewStep();
const startAction = useStartStepAction();
const isXblock = isXblockStep(viewStep);
const alert = {
message: messages.alerts.done.status,
heading: messages.headings.done.status,
};
if (startAction && viewStep !== stepNames.xblock) {
if (startAction && !isXblock) {
alert.actions = [startAction.action];
}
return [useCreateAlert({ step })(alert)];
Expand Down
5 changes: 3 additions & 2 deletions src/components/StatusAlert/hooks/useModalAlerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { useViewStep } from 'hooks/routing';
import { useGlobalState, useHasReceivedFinalGrade } from 'hooks/app';
import { useHasSubmitted, useShowTrainingError } from 'hooks/assessment';

import { stepNames, stepStates } from 'constants/index';
import { stepStates } from 'constants/index';

import { isXblockStep } from 'utils';
import { useTrainingErrorAlerts } from './simpleAlerts';

import useRevisitAlerts from './useRevisitAlerts';
Expand All @@ -20,7 +21,7 @@ const useModalAlerts = ({ step }) => {
const successAlerts = useSuccessAlerts({ step });

// Do nothing if in xblock view
if (viewStep === stepNames.xblock) {
if (isXblockStep(viewStep)) {
return [];
}
if (showTrainingError) {
Expand Down
4 changes: 3 additions & 1 deletion src/components/StatusAlert/hooks/useStatusAlertData.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useGlobalState, useHasReceivedFinalGrade } from 'hooks/app';

import { stepNames, stepStates } from 'constants/index';

import { isXblockStep } from 'utils';
import messages from '../messages';
import useCreateAlert from './useCreateAlert';
import {
Expand All @@ -28,13 +29,14 @@ const useStatusAlertData = ({
const gradedAlerts = useGradedAlerts({ step });
const { hasCancelled, cancelledAlerts } = useCancelledAlerts({ step });
const staffAlerts = useStaffAlerts({ step });
const isXblock = isXblockStep(viewStep);

const stepName = step || activeStepName;

if (isDone) {
return gradedAlerts;
}
if (viewStep !== stepNames.xblock) {
if (!isXblock) {
return modalAlerts;
}
if (hasCancelled) {
Expand Down
4 changes: 4 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const closedReasons = StrictDict({

export const stepNames = StrictDict({
xblock: 'xblock',
xblockStudio: 'xblockStudio',
xblockPreview: 'xblockPreview',
submission: 'submission',
peer: 'peer',
self: 'self',
Expand All @@ -55,6 +57,8 @@ export const assessmentSteps = [

export const routeSteps = StrictDict({
xblock: stepNames.xblock,
xblock_studio: stepNames.xblockStudio,
xblock_preview: stepNames.xblockPreview,
submission: stepNames.submission,
peer_assessment: stepNames.peer,
self_assessment: stepNames.self,
Expand Down
4 changes: 4 additions & 0 deletions src/constants/mockData.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { stepNames } from '.';

export const viewKeys = StrictDict({
xblock: 'xblock',
xblockStudio: 'xblock_studio',
xblockPreview: 'xblock_preview',
submission: 'submission',
studentTraining: 'student_training',
self: 'self_assessment',
Expand Down Expand Up @@ -73,6 +75,8 @@ export const stateStepConfigs = {

export const defaultViewProgressKeys = StrictDict({
[viewKeys.xblock]: progressKeys.submissionUnsaved,
[viewKeys.xblockStudio]: progressKeys.submissionUnsaved,
[viewKeys.xblockPreview]: progressKeys.submissionUnsaved,
[viewKeys.submission]: progressKeys.submissionSaved,
[viewKeys.studentTraining]: progressKeys.studentTraining,
[viewKeys.self]: progressKeys.selfAssessment,
Expand Down
5 changes: 4 additions & 1 deletion src/data/services/lms/fakeData/pageData/progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { StrictDict } from '@edx/react-unit-test-utils';

import { stepNames } from 'constants/index';
import { closedStates, progressKeys } from 'constants/mockData';
import { isXblockStep } from 'utils';
import { assessmentSteps } from '../oraConfig';
/* eslint-disable camelcase */

Expand Down Expand Up @@ -133,8 +134,10 @@ export const getProgressState = ({ viewStep, progressKey, stepConfig }) => {
: { submission: subStatuses.unsubmitted, ...nullStepInfo };
}

const isXblock = isXblockStep(viewStep);

// by default, pass null for all steps after submission
const stepIndex = (isGraded || viewStep === stepNames.xblock)
const stepIndex = (isGraded || isXblock)
? stepConfig.length - 1 : stepConfig.indexOf(step);

const out = { submission: subStatuses.submitted };
Expand Down
7 changes: 6 additions & 1 deletion src/data/services/lms/urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import { StrictDict } from '@edx/react-unit-test-utils';
import { getConfig } from '@edx/frontend-platform';

import { stepNames, stepRoutes } from 'constants/index';
import { isXblockStep } from 'utils';

export const useBaseUrl = () => {
const { xblockId, courseId } = useParams();
const pathName = window.location.pathname;
if (pathName.startsWith(`/${stepRoutes[stepNames.xblockStudio]}`) || pathName.startsWith(`/${stepRoutes[stepNames.xblockPreview]}`)) {
return `${getConfig().STUDIO_BASE_URL}/preview/xblock/${xblockId}/handler`;
}
return `${getConfig().LMS_BASE_URL}/courses/${courseId}/xblock/${xblockId}/handler`;
};

Expand All @@ -20,7 +25,7 @@ export const usePageDataUrl = (hasSubmitted) => {
const baseUrl = useBaseUrl();
const url = `${baseUrl}/get_learner_data/`;
return React.useCallback((step) => (
((step === stepNames.xblock) || hasSubmitted) ? url : `${url}${step}`
(hasSubmitted || isXblockStep(step)) ? url : `${url}${step}`
), [hasSubmitted, url]);
};

Expand Down
3 changes: 2 additions & 1 deletion src/hooks/actions/useLoadNextAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { useResetAssessment } from 'hooks/assessment';
import { useViewStep } from 'hooks/routing';
import { MutationStatus, stepNames } from 'constants/index';
import { isXblockStep } from 'utils';

import messages, { loadNextSteps } from './messages';

Expand All @@ -21,7 +22,7 @@ export default () => {
const viewStep = useViewStep();
const activeStep = useActiveStepName();
const stepInfo = useStepInfo();
const step = viewStep === stepNames.xblock ? activeStep : viewStep;
const step = isXblockStep(viewStep) ? activeStep : viewStep;
if (
!(
step === stepNames.studentTraining
Expand Down
4 changes: 4 additions & 0 deletions src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
export default {
xblockEmbed: 'xblock/embedded/:courseId/:xblockId/:progressKey',
xblockStudioEmbed: 'xblock_studio/embedded/:courseId/:xblockId/:progressKey?',
xblockPreviewEmbed: 'xblock_preview/embedded/:courseId/:xblockId/:progressKey?',
peerAssessmentEmbed: 'peer_assessment/embedded/:courseId/:xblockId/:progressKey?',
selfAssessmentEmbed: 'self_assessment/embedded/:courseId/:xblockId/:progressKey?',
studentTrainingEmbed: 'student_training/embedded/:courseId/:xblockId/:progressKey?',
submissionEmbed: 'submission/embedded/:courseId/:xblockId/:progressKey?',
gradedEmbed: 'graded/embedded/:courseId/:xblockId/:progressKey?',
rootEmbed: 'embedded/*',
xblock: 'xblock/:courseId/:xblockId/:progressKey?',
xblockStudio: 'xblock_studio/:courseId/:xblockId/:progressKey?',
xblockPreview: 'xblock_preview/:courseId/:xblockId/:progressKey?',
peerAssessment: 'peer_assessment/:courseId/:xblockId/:progressKey?',
selfAssessment: 'self_assessment/:courseId/:xblockId/:progressKey?',
studentTraining: 'student_training/:courseId/:xblockId/:progressKey?',
Expand Down
5 changes: 4 additions & 1 deletion src/test/constants.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { stepNames } from 'constants/index';
import { progressKeys } from 'constants/mockData';
import { isXblockStep } from 'utils';

const lmsBaseUrl = 'test-base-url';
const studioBaseUrl = 'test-studio-base-url';
export const courseId = 'test-course-id';
export const xblockId = 'test-xblock-id';
export const baseUrl = `${lmsBaseUrl}/courses/${courseId}/xblock/${xblockId}/handler`;
export const config = {
LMS_BASE_URL: lmsBaseUrl,
STUDIO_BASE_URL: studioBaseUrl,
};

const stepProgressKeys = {
Expand Down Expand Up @@ -60,5 +63,5 @@ const checkForProgressKeys = (steps) => (key) => steps.map(loadKeys).flat().incl

export const getProgressKeys = (stepOrder, stepName) => viewProgressKeys[stepName]
.filter(checkForProgressKeys(
stepName === stepNames.xblock ? allSteps(stepOrder) : stepOrders[stepOrder],
isXblockStep(stepName) ? allSteps(stepOrder) : stepOrders[stepOrder],
));
Loading

0 comments on commit 2d814c0

Please sign in to comment.