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

chore: prevent step from clickable when the date for the step is invalid #179

Merged
merged 3 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
35 changes: 0 additions & 35 deletions src/components/AppContainer.jsx

This file was deleted.

53 changes: 53 additions & 0 deletions src/components/AppContainer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';

import { useIntl } from '@edx/frontend-platform/i18n';
import { ErrorPage } from '@edx/frontend-platform/react';
import { Spinner } from '@edx/paragon';

import {
useIsPageDataLoaded,
useIsORAConfigLoaded,
usePageDataError,
useORAConfigDataError,
} from 'hooks/app';

import messages from './messages';

/* The purpose of this component is to wrap views with a header/footer for situations
* where we need to run non-embedded
*/

const AppContainer = ({ children }) => {
const { formatMessage } = useIntl();
const isConfigLoaded = useIsORAConfigLoaded();
const isPageDataLoaded = useIsPageDataLoaded();
const pageDataError = usePageDataError();
const oraConfigDataError = useORAConfigDataError();
const isLoaded = isConfigLoaded && isPageDataLoaded;

if (pageDataError || oraConfigDataError) {
const { response } = pageDataError || oraConfigDataError;
const errorMessage = messages[response?.data?.error?.errorCode] || messages.unknownError;

return <ErrorPage message={formatMessage(errorMessage)} />;
}
return (
<div className="w-100 h-100">
{isLoaded ? (
children
) : (
<Spinner
animation="border"
screenReaderText="loading"
className="app-loading"
/>
)}
</div>
);
};
AppContainer.propTypes = {
children: PropTypes.node.isRequired,
};

export default AppContainer;
21 changes: 21 additions & 0 deletions src/components/AppContainer/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
ERR_INVALID_STATE_FOR_ASSESSMENT: {
defaultMessage: 'This step is not available. Unable to retrieve the assessment.',
description: 'Error message for invalid state for assessment',
id: 'frontend-app-ora.StatusAlerts.ERR_INVALID_STATE_FOR_ASSESSMENT',
},
unknownError: {
defaultMessage: 'An unknown error occurred. Please try again.',
description: 'Error message for unknown error',
id: 'frontend-app-ora.StatusAlerts.unknownError',
},
errorHeader: {
defaultMessage: 'Something went wrong.',
description: 'Error message header',
id: 'frontend-app-ora.StatusAlerts.errorHeader',
},
});

export default messages;
6 changes: 4 additions & 2 deletions src/components/ProgressBar/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const useProgressStepData = ({ step, canRevisit = false }) => {
effectiveGrade,
stepState,
activeStepName,
stepIsUnavailable,
} = useGlobalState({ step });
const stepInfo = useStepInfo();
const openModal = useOpenModal();
Expand All @@ -23,9 +24,10 @@ export const useProgressStepData = ({ step, canRevisit = false }) => {
const isActive = isXblock
? activeStepName === step
: viewStep === step;
let isEnabled = isActive
let isEnabled = !stepIsUnavailable
&& (isActive
|| stepState === stepStates.inProgress
|| (canRevisit && stepState === stepStates.done);
|| (canRevisit && stepState === stepStates.done));

if (step === stepNames.peer) {
const isPeerComplete = stepInfo.peer?.numberOfReceivedAssessments > 0;
Expand Down
20 changes: 20 additions & 0 deletions src/components/StatusAlert/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ const studentTrainingHeadings = defineMessages({
});

const selfAlerts = defineMessages({
[stepStates.notAvailable]: {
id: 'frontend-app-ora.StatusAlert.self.notAvailable',
defaultMessage: 'Self assessment is not available yet. Check back to complete the assignment once this section has opened',
description: 'Self assessment not available status alert',
},
[stepStates.closed]: {
id: 'frontend-app-ora.StatusAlert.self.closed',
defaultMessage: 'The due date for this step has passed. This step is now closed. You can no longer complete a self assessment or continue with this assignment, and you will receive a grade of incomplete',
Expand All @@ -109,6 +114,11 @@ const selfAlerts = defineMessages({
},
});
const selfHeadings = defineMessages({
[stepStates.notAvailable]: {
id: 'frontend-app-ora.StatusAlert.Heading.self.notAvailable',
defaultMessage: 'Self assessment not available',
description: 'Self assessment not available status alert heading',
},
[stepStates.submitted]: {
id: 'frontend-app-ora.StatusAlert.Heading.self.submitted',
defaultMessage: 'Self assessment: Completed',
Expand Down Expand Up @@ -221,6 +231,16 @@ const messages = defineMessages({
defaultMessage: 'Exit',
description: 'Status alert exit button text',
},
stepNotStarted: {
id: 'frontend-app-ora.StatusAlert.step.notStarted',
defaultMessage: '{stepName} step has not started yet. Check back on {startDatetime} to begin.',
description: 'Step not started status alert',
},
stepNotStartedHeading: {
id: 'frontend-app-ora.StatusAlert.Heading.step.notStarted',
defaultMessage: '{stepName} not started',
description: 'Step not started status alert heading',
},
});

export default {
Expand Down
32 changes: 27 additions & 5 deletions src/data/services/lms/hooks/selectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StrictDict } from '@edx/react-unit-test-utils';
import moment from 'moment';

import {
stepNames,
Expand Down Expand Up @@ -29,6 +30,7 @@ export const useStepState = ({ step = null } = {}) => {
const subState = selectors.useSubmissionState();
const trainingStepIsCompleted = selectors.useTrainingStepIsCompleted();
const stepConfig = selectors.useAssessmentStepConfig()?.settings || {};
const now = moment();

if (!stepInfo || !activeStepName || stepIndex === undefined || activeStepIndex === undefined) {
return '';
Expand All @@ -53,20 +55,37 @@ export const useStepState = ({ step = null } = {}) => {
return stepStates.done;
}

if (activeStepName === stepNames.peer && stepInfo?.peer) {
const config = stepConfig[stepNames.peer];
if (stepName === stepNames.peer && stepInfo?.peer) {
const config = stepConfig[stepName];
const { numberOfAssessmentsCompleted, numberOfReceivedAssessments } = stepInfo.peer;
const { minNumberToGrade, minNumberToBeGradedBy } = config;
const { minNumberToGrade, minNumberToBeGradedBy, startDatetime, endDatetime } = config;
const gradingDone = minNumberToGrade <= numberOfAssessmentsCompleted;
const receivingDone = minNumberToBeGradedBy <= numberOfReceivedAssessments;
if (gradingDone && !receivingDone) {
if (now.isBefore(startDatetime)) {
return stepStates.notAvailable;
}
else if (now.isAfter(endDatetime)) {
return stepStates.closed;
}
else if (gradingDone && !receivingDone) {
return stepStates.waitingForPeerGrades;
}
if (stepInfo.peer.isWaitingForSubmissions) {
else if (stepInfo.peer.isWaitingForSubmissions) {
return stepStates.waiting;
}
}

if (stepName === stepNames.self && stepConfig[stepName]) {
const config = stepConfig[stepName];
const { startDatetime, endDatetime } = config;
if (now.isBefore(startDatetime)) {
return stepStates.notAvailable;
}
else if (now.isAfter(endDatetime)) {
return stepStates.closed;
}
}

// For Assessment steps
if (stepIndex < activeStepIndex) { return stepStates.done; }
if (stepIndex > activeStepIndex) { return stepStates.notAvailable; }
Expand Down Expand Up @@ -113,6 +132,8 @@ export const useGlobalState = ({ step = null } = {}) => {
const effectiveGrade = selectors.useEffectiveGrade();
const cancellationInfo = selectors.useCancellationInfo();
const hasReceivedFinalGrade = selectors.useHasReceivedFinalGrade();
const stepIsUnavailable = [stepStates.notAvailable, stepStates.closed].includes(stepState);

return {
activeStepName,
activeStepState,
Expand All @@ -121,6 +142,7 @@ export const useGlobalState = ({ step = null } = {}) => {
hasReceivedFinalGrade,
lastStep,
stepState,
stepIsUnavailable,
};
};

Expand Down
1 change: 1 addition & 0 deletions src/data/services/lms/hooks/selectors/oraConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import moment from 'moment';
import * as data from 'data/services/lms/hooks/data';
import * as types from 'data/services/lms/types';
import { stepNames } from 'constants/index';
Expand Down
21 changes: 18 additions & 3 deletions src/views/XBlockView/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useORAConfigData,
usePrompts,
useRubricConfig,
useGlobalState,
} from 'hooks/app';

import ProgressBar from 'components/ProgressBar';
Expand All @@ -23,10 +24,18 @@ export const XBlockView = () => {
const prompts = usePrompts();
const rubricConfig = useRubricConfig();

const { stepIsUnavailable } = useGlobalState();

useEffect(() => {
if (window.parent.length > 0) {
new ResizeObserver(() => {
window.parent.postMessage({ type: 'plugin.resize', payload: { height: document.body.scrollHeight } }, document.referrer);
window.parent.postMessage(
{
type: 'plugin.resize',
payload: { height: document.body.scrollHeight },
},
document.referrer,
);
}).observe(document.body);
}
}, []);
Expand All @@ -40,8 +49,14 @@ export const XBlockView = () => {
<HotjarSurvey />
<Instructions />
<Actions />
{prompts.map(prompt => <Prompt key={prompt} prompt={prompt} />)}
{rubricConfig.showDuringResponse && <Rubric isCollapsible />}
{!stepIsUnavailable && (
<>
{prompts.map((prompt) => (
<Prompt key={prompt} prompt={prompt} />
))}
{rubricConfig.showDuringResponse && <Rubric isCollapsible />}
</>
)}
</div>
);
};
Expand Down
Loading