refactor(client): data sourcing for super-block-intro (#55751)

This commit is contained in:
Oliver Eyton-Williams
2024-09-25 09:34:04 +00:00
committed by GitHub
parent 21fa84348c
commit 28ad751427
3 changed files with 107 additions and 119 deletions
@@ -13,6 +13,7 @@ import {
BilibiliIds
} from '../../../redux/prop-types';
import { isAuditedSuperBlock } from '../../../../../shared/utils/is-audited';
import { BlockTypes } from '../../../../../shared/config/blocks';
import Block from './block';
jest.mock('../../../../../shared/utils/is-audited', () => ({
@@ -20,65 +21,64 @@ jest.mock('../../../../../shared/utils/is-audited', () => ({
}));
const defaultProps = {
blockDashedName: 'test-block',
block: 'test-block',
challenges: [
{
challenge: {
block: 'testblock',
certification: 'mockCertification',
challengeOrder: 1,
challengeType: 0,
dashedName: 'mock-dashed-name',
description: 'mockDescription',
challengeFiles: {} as ChallengeFiles,
fields: {} as Fields,
forumTopicId: 12345,
guideUrl: 'https://mockurl.com',
head: ['mockHead'],
hasEditableBoundaries: false,
helpCategory: 'mockHelpCategory',
id: 'mockId',
instructions: 'mockInstructions',
isComingSoon: false,
internal: {
content: 'mockContent',
contentDigest: 'mockContentDigest',
description: 'mockInternalDescription',
fieldOwners: ['mockOwner'],
ignoreType: null,
mediaType: 'mockMediaType',
owner: 'mockOwner',
type: 'mockType'
},
notes: 'mockNotes',
prerequisites: [] as PrerequisiteChallenge[],
isLocked: false,
isPrivate: false,
order: 1,
questions: [] as Question[],
assignments: ['mockAssignment'],
required: [],
solutions: {
['indexhtml']: {} as FileKeyChallenge,
['scriptjs']: {} as FileKeyChallenge,
['stylescss']: {} as FileKeyChallenge,
['indexjsx']: {} as FileKeyChallenge
},
sourceInstanceName: 'mockSourceInstanceName',
superOrder: 1,
superBlock: SuperBlocks.UpcomingPython,
tail: ['mockTail'],
template: 'mockTemplate',
tests: [] as Test[],
title: 'mockTitle',
translationPending: false,
url: 'https://mockurl.com',
usesMultifileEditor: false,
videoId: 'mockVideoId',
videoLocaleIds: {},
bilibiliIds: {} as BilibiliIds,
videoUrl: 'https://mockvideourl.com'
}
block: 'testblock',
blockType: BlockTypes.lab,
certification: 'mockCertification',
challengeOrder: 1,
challengeType: 0,
dashedName: 'mock-dashed-name',
description: 'mockDescription',
challengeFiles: {} as ChallengeFiles,
fields: {} as Fields,
forumTopicId: 12345,
guideUrl: 'https://mockurl.com',
head: ['mockHead'],
hasEditableBoundaries: false,
helpCategory: 'mockHelpCategory',
id: 'mockId',
instructions: 'mockInstructions',
isComingSoon: false,
internal: {
content: 'mockContent',
contentDigest: 'mockContentDigest',
description: 'mockInternalDescription',
fieldOwners: ['mockOwner'],
ignoreType: null,
mediaType: 'mockMediaType',
owner: 'mockOwner',
type: 'mockType'
},
notes: 'mockNotes',
prerequisites: [] as PrerequisiteChallenge[],
isLocked: false,
isPrivate: false,
order: 1,
questions: [] as Question[],
assignments: ['mockAssignment'],
required: [],
solutions: {
['indexhtml']: {} as FileKeyChallenge,
['scriptjs']: {} as FileKeyChallenge,
['stylescss']: {} as FileKeyChallenge,
['indexjsx']: {} as FileKeyChallenge
},
sourceInstanceName: 'mockSourceInstanceName',
superOrder: 1,
superBlock: SuperBlocks.UpcomingPython,
tail: ['mockTail'],
template: 'mockTemplate',
tests: [] as Test[],
title: 'mockTitle',
translationPending: false,
url: 'https://mockurl.com',
usesMultifileEditor: false,
videoId: 'mockVideoId',
videoLocaleIds: {},
bilibiliIds: {} as BilibiliIds,
videoUrl: 'https://mockvideourl.com'
}
],
completedChallengeIds: ['testchallengeIds'],
@@ -26,11 +26,10 @@ import '../intro.css';
const { curriculumLocale, showUpcomingChanges, showNewCurriculum } = envData;
const mapStateToProps = (
state: unknown,
ownProps: { blockDashedName: string }
) => {
const expandedSelector = makeExpandedBlockSelector(ownProps.blockDashedName);
type Challenge = ChallengeNode['challenge'];
const mapStateToProps = (state: unknown, ownProps: { block: string }) => {
const expandedSelector = makeExpandedBlockSelector(ownProps.block);
return createSelector(
expandedSelector,
@@ -46,8 +45,8 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators({ toggleBlock }, dispatch);
interface BlockProps {
blockDashedName: string;
challenges: ChallengeNode[];
block: string;
challenges: Challenge[];
completedChallengeIds: string[];
isExpanded: boolean;
superBlock: SuperBlocks;
@@ -82,14 +81,14 @@ class Block extends Component<BlockProps> {
}
handleBlockClick(): void {
const { blockDashedName, toggleBlock } = this.props;
const { block, toggleBlock } = this.props;
void playTone('block-toggle');
toggleBlock(blockDashedName);
toggleBlock(block);
}
render(): JSX.Element {
const {
blockDashedName,
block,
completedChallengeIds,
challenges,
isExpanded,
@@ -99,7 +98,7 @@ class Block extends Component<BlockProps> {
let completedCount = 0;
const challengesWithCompleted = challenges.map(({ challenge }) => {
const challengesWithCompleted = challenges.map(challenge => {
const { id } = challenge;
const isCompleted = completedChallengeIds.some(
(completedChallengeId: string) => completedChallengeId === id
@@ -110,11 +109,11 @@ class Block extends Component<BlockProps> {
return { ...challenge, isCompleted };
});
const isProjectBlock = challenges.some(({ challenge }) => {
return isProjectBased(challenge.challengeType, blockDashedName);
const isProjectBlock = challenges.some(challenge => {
return isProjectBased(challenge.challengeType, block);
});
const isGridBlock = challenges.some(({ challenge }) => {
const isGridBlock = challenges.some(challenge => {
return isGridBased(superBlock, challenge.challengeType);
});
@@ -123,12 +122,12 @@ class Block extends Component<BlockProps> {
showUpcomingChanges
});
const blockTitle = t(`intro:${superBlock}.blocks.${blockDashedName}.title`);
const blockTitle = t(`intro:${superBlock}.blocks.${block}.title`);
// the real type of TFunction is the type below, because intro can be an array of strings
// type RealTypeOFTFunction = TFunction & ((key: string) => string[]);
// But changing the type will require refactoring that isn't worth it for a wrong type.
const blockIntroArr = t<string, DefaultTFuncReturn & string[]>(
`intro:${superBlock}.blocks.${blockDashedName}.intro`
`intro:${superBlock}.blocks.${block}.intro`
);
const expandText = t('intro:misc-text.expand');
const collapseText = t('intro:misc-text.collapse');
@@ -151,7 +150,7 @@ class Block extends Component<BlockProps> {
const Block = (
<>
{' '}
<ScrollableAnchor id={blockDashedName}>
<ScrollableAnchor id={block}>
<div className={`block ${isExpanded ? 'open' : ''}`}>
<div className='block-header'>
<h3 className='big-block-title'>{blockTitle}</h3>
@@ -207,7 +206,7 @@ class Block extends Component<BlockProps> {
const ProjectBlock = (
<>
<ScrollableAnchor id={blockDashedName}>
<ScrollableAnchor id={block}>
<div className='block'>
<div className='block-header'>
<h3 className='big-block-title'>{blockTitle}</h3>
@@ -245,12 +244,12 @@ class Block extends Component<BlockProps> {
const GridBlock = (
<>
{' '}
<ScrollableAnchor id={blockDashedName}>
<ScrollableAnchor id={block}>
<div className={`block block-grid ${isExpanded ? 'open' : ''}`}>
<h3 className='block-grid-title'>
<button
aria-expanded={isExpanded ? 'true' : 'false'}
aria-controls={`${blockDashedName}-panel`}
aria-controls={`${block}-panel`}
className='block-header'
onClick={() => {
this.handleBlockClick();
@@ -283,7 +282,7 @@ class Block extends Component<BlockProps> {
</div>
)}
{isExpanded && (
<div id={`${blockDashedName}-panel`}>
<div id={`${block}-panel`}>
<BlockIntros intros={blockIntroArr} />
<Challenges
challengesWithCompleted={challengesWithCompleted}
@@ -299,7 +298,7 @@ class Block extends Component<BlockProps> {
);
const GridProjectBlock = (
<ScrollableAnchor id={blockDashedName}>
<ScrollableAnchor id={block}>
<div className='block block-grid grid-project-block'>
<div className='tags-wrapper'>
<span className='cert-tag' aria-hidden='true'>
@@ -25,7 +25,7 @@ import {
userFetchStateSelector,
signInLoadingSelector
} from '../../redux/selectors';
import type { AllChallengeNode, User } from '../../redux/prop-types';
import type { ChallengeNode, User } from '../../redux/prop-types';
import { CertTitle } from '../../../config/cert-and-project-map';
import Block from './components/block';
import CertChallenge from './components/cert-challenge';
@@ -45,7 +45,7 @@ type FetchState = {
type SuperBlockProp = {
currentChallengeId: string;
data: {
allChallengeNode: AllChallengeNode;
allChallengeNode: { nodes: ChallengeNode[] };
};
expandedState: {
[key: string]: boolean;
@@ -119,7 +119,7 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
const getChosenBlock = (): string => {
const {
data: {
allChallengeNode: { edges }
allChallengeNode: { nodes }
},
isSignedIn,
currentChallengeId,
@@ -144,20 +144,18 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
return dashedBlock;
}
const edge = edges[0];
const firstChallenge = nodes[0]?.challenge;
if (isSignedIn) {
// see if currentChallenge is in this superBlock
const currentChallengeEdge = edges.find(
edge => edge.node.challenge.id === currentChallengeId
);
const currentChallenge = nodes.find(
node => node.challenge.id === currentChallengeId
)?.challenge;
return currentChallengeEdge
? currentChallengeEdge.node.challenge.block
: edge.node.challenge.block;
return currentChallenge ? currentChallenge.block : firstChallenge?.block;
}
return edge.node.challenge.block;
return firstChallenge?.block;
};
const initializeExpandedState = () => {
@@ -169,7 +167,7 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
const {
data: {
allChallengeNode: { edges }
allChallengeNode: { nodes }
},
isSignedIn,
signInLoading,
@@ -177,16 +175,11 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
pageContext: { superBlock, title, certification }
} = props;
const allChallenges = edges.map(({ node }) => node.challenge);
const nodesForSuperBlock = edges
.filter(edge => edge.node.challenge.superBlock === superBlock)
.map(({ node }) => node);
const blockDashedNames = uniq(
nodesForSuperBlock.map(({ challenge: { block } }) => block)
);
const allChallenges = nodes.map(({ challenge }) => challenge);
const challenges = allChallenges.filter(c => c.superBlock === superBlock);
const blocks = uniq(challenges.map(({ block }) => block));
const i18nTitle = getSuperBlockTitleForMap(superBlock);
const defaultCurriculumNames = blockDashedNames;
const superblockWithoutCert = [
SuperBlocks.RespWebDesign,
@@ -230,13 +223,11 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
</h2>
<Spacer size='medium' />
<div className='block-ui'>
{defaultCurriculumNames.map(blockDashedName => (
{blocks.map(block => (
<Block
key={blockDashedName}
blockDashedName={blockDashedName}
challenges={nodesForSuperBlock.filter(
node => node.challenge.block === blockDashedName
)}
key={block}
block={block}
challenges={challenges.filter(c => c.block === block)}
superBlock={superBlock}
/>
))}
@@ -292,21 +283,19 @@ export const query = graphql`
]
}
) {
edges {
node {
challenge {
fields {
slug
blockName
}
id
block
challengeType
title
order
superBlock
dashedName
nodes {
challenge {
fields {
slug
blockName
}
id
block
challengeType
title
order
superBlock
dashedName
}
}
}