refactor: stop spreading challenge over the node (#44499)

* refactor: stop spreading challenge over the node

Instead of creating a Gatsby node with id of challenge.id, we create a
single node field that has all the challenge data.

While this makes the GraphQL queries more verbose, it means we're free
to create multiple nodes with the same challenge.id.

* test: update time-line test for new GQL schema

* test: update mocks with new GQL schema
This commit is contained in:
Oliver Eyton-Williams
2021-12-14 19:11:20 +01:00
committed by GitHub
parent 755f27093c
commit 3b560deab6
18 changed files with 593 additions and 440 deletions

View File

@ -20,13 +20,6 @@ const createByIdentityMap = {
exports.onCreateNode = function onCreateNode({ node, actions, getNode }) { exports.onCreateNode = function onCreateNode({ node, actions, getNode }) {
const { createNodeField } = actions; const { createNodeField } = actions;
if (node.internal.type === 'ChallengeNode') {
const { tests = [], block, dashedName, superBlock } = node;
const slug = `/learn/${superBlock}/${block}/${dashedName}`;
createNodeField({ node, name: 'slug', value: slug });
createNodeField({ node, name: 'blockName', value: blockNameify(block) });
createNodeField({ node, name: 'tests', value: tests });
}
if (node.internal.type === 'MarkdownRemark') { if (node.internal.type === 'MarkdownRemark') {
const slug = createFilePath({ node, getNode }); const slug = createFilePath({ node, getNode });
@ -71,10 +64,17 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
graphql(` graphql(`
{ {
allChallengeNode( allChallengeNode(
sort: { fields: [superOrder, order, challengeOrder] } sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) { ) {
edges { edges {
node { node {
challenge {
block block
challengeType challengeType
fields { fields {
@ -105,6 +105,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
} }
} }
} }
}
allMarkdownRemark { allMarkdownRemark {
edges { edges {
node { node {
@ -137,12 +138,22 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
); );
const blocks = uniq( const blocks = uniq(
result.data.allChallengeNode.edges.map(({ node: { block } }) => block) result.data.allChallengeNode.edges.map(
({
node: {
challenge: { block }
}
}) => block
)
).map(block => blockNameify(block)); ).map(block => blockNameify(block));
const superBlocks = uniq( const superBlocks = uniq(
result.data.allChallengeNode.edges.map( result.data.allChallengeNode.edges.map(
({ node: { superBlock } }) => superBlock ({
node: {
challenge: { superBlock }
}
}) => superBlock
) )
); );
@ -256,6 +267,9 @@ exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions; const { createTypes } = actions;
const typeDefs = ` const typeDefs = `
type ChallengeNode implements Node { type ChallengeNode implements Node {
challenge: Challenge
}
type Challenge {
challengeFiles: [FileContents] challengeFiles: [FileContents]
notes: String notes: String
url: String url: String

View File

@ -1,4 +1,5 @@
const crypto = require('crypto'); const crypto = require('crypto');
const { blockNameify } = require('../../../utils/block-nameify');
function createChallengeNode(challenge, reporter) { function createChallengeNode(challenge, reporter) {
// challengeType 11 is for video challenges (they only have instructions) // challengeType 11 is for video challenges (they only have instructions)
@ -31,6 +32,17 @@ function createChallengeNode(challenge, reporter) {
type: challenge.challengeType === 7 ? 'CertificateNode' : 'ChallengeNode' type: challenge.challengeType === 7 ? 'CertificateNode' : 'ChallengeNode'
}; };
if (internal.type === 'ChallengeNode') {
const { tests = [], block, dashedName, superBlock } = challenge;
const slug = `/learn/${superBlock}/${block}/${dashedName}`;
challenge.fields = {
slug,
blockName: blockNameify(block),
tests
};
}
return JSON.parse( return JSON.parse(
JSON.stringify( JSON.stringify(
Object.assign( Object.assign(
@ -41,7 +53,8 @@ function createChallengeNode(challenge, reporter) {
internal, internal,
sourceInstanceName: 'challenge' sourceInstanceName: 'challenge'
}, },
challenge { challenge },
{ id: crypto.randomUUID() }
) )
) )
); );

View File

@ -1,5 +1,6 @@
const mockChallengeNodes = [ const mockChallengeNodes = [
{ {
challenge: {
fields: { fields: {
slug: '/super-block-one/block-a/challenge-one', slug: '/super-block-one/block-a/challenge-one',
blockName: 'Block A' blockName: 'Block A'
@ -10,8 +11,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-one', superBlock: 'super-block-one',
dashedName: 'challenge-one' dashedName: 'challenge-one'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-one/block-a/challenge-two', slug: '/super-block-one/block-a/challenge-two',
blockName: 'Block A' blockName: 'Block A'
@ -22,8 +25,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-one', superBlock: 'super-block-one',
dashedName: 'challenge-two' dashedName: 'challenge-two'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-one/block-b/challenge-one', slug: '/super-block-one/block-b/challenge-one',
blockName: 'Block B' blockName: 'Block B'
@ -34,8 +39,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-one', superBlock: 'super-block-one',
dashedName: 'challenge-one' dashedName: 'challenge-one'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-one/block-b/challenge-two', slug: '/super-block-one/block-b/challenge-two',
blockName: 'Block B' blockName: 'Block B'
@ -47,8 +54,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-one', superBlock: 'super-block-one',
dashedName: 'challenge-two' dashedName: 'challenge-two'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-one/block-c/challenge-one', slug: '/super-block-one/block-c/challenge-one',
blockName: 'Block C' blockName: 'Block C'
@ -59,8 +68,10 @@ const mockChallengeNodes = [
isPrivate: true, isPrivate: true,
superBlock: 'super-block-one', superBlock: 'super-block-one',
dashedName: 'challenge-one' dashedName: 'challenge-one'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-two/block-a/challenge-one', slug: '/super-block-two/block-a/challenge-one',
blockName: 'Block A' blockName: 'Block A'
@ -71,8 +82,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-two', superBlock: 'super-block-two',
dashedName: 'challenge-one' dashedName: 'challenge-one'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-two/block-a/challenge-two', slug: '/super-block-two/block-a/challenge-two',
blockName: 'Block A' blockName: 'Block A'
@ -83,8 +96,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-two', superBlock: 'super-block-two',
dashedName: 'challenge-two' dashedName: 'challenge-two'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-two/block-b/challenge-one', slug: '/super-block-two/block-b/challenge-one',
blockName: 'Block B' blockName: 'Block B'
@ -95,8 +110,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-two', superBlock: 'super-block-two',
dashedName: 'challenge-one' dashedName: 'challenge-one'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-two/block-b/challenge-two', slug: '/super-block-two/block-b/challenge-two',
blockName: 'Block B' blockName: 'Block B'
@ -107,8 +124,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-two', superBlock: 'super-block-two',
dashedName: 'challenge-two' dashedName: 'challenge-two'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-three/block-a/challenge-one', slug: '/super-block-three/block-a/challenge-one',
blockName: 'Block A' blockName: 'Block A'
@ -119,8 +138,10 @@ const mockChallengeNodes = [
isPrivate: false, isPrivate: false,
superBlock: 'super-block-three', superBlock: 'super-block-three',
dashedName: 'challenge-one' dashedName: 'challenge-one'
}
}, },
{ {
challenge: {
fields: { fields: {
slug: '/super-block-three/block-c/challenge-two', slug: '/super-block-three/block-c/challenge-two',
blockName: 'Block C' blockName: 'Block C'
@ -132,6 +153,7 @@ const mockChallengeNodes = [
superBlock: 'super-block-three', superBlock: 'super-block-three',
dashedName: 'challenge-two' dashedName: 'challenge-two'
} }
}
]; ];
export default mockChallengeNodes; export default mockChallengeNodes;

View File

@ -42,19 +42,19 @@ const linkSpacingStyle = {
function renderLandingMap(nodes: ChallengeNode[]) { function renderLandingMap(nodes: ChallengeNode[]) {
nodes = nodes.filter( nodes = nodes.filter(
node => node.superBlock !== SuperBlocks.CodingInterviewPrep ({ challenge }) => challenge.superBlock !== SuperBlocks.CodingInterviewPrep
); );
return ( return (
<ul data-test-label='certifications'> <ul data-test-label='certifications'>
{nodes.map((node, i) => ( {nodes.map(({ challenge }, i) => (
<li key={i}> <li key={i}>
<Link <Link
className='btn link-btn btn-lg' className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`} to={`/learn/${challenge.superBlock}/`}
> >
<div style={linkSpacingStyle}> <div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')} {generateIconComponent(challenge.superBlock, 'map-icon')}
{i18next.t(`intro:${node.superBlock}.title`)} {i18next.t(`intro:${challenge.superBlock}.title`)}
</div> </div>
<LinkButton /> <LinkButton />
</Link> </Link>
@ -68,18 +68,20 @@ function renderLearnMap(
nodes: ChallengeNode[], nodes: ChallengeNode[],
currentSuperBlock: MapProps['currentSuperBlock'] currentSuperBlock: MapProps['currentSuperBlock']
) { ) {
nodes = nodes.filter(node => node.superBlock !== currentSuperBlock); nodes = nodes.filter(
({ challenge }) => challenge.superBlock !== currentSuperBlock
);
return curriculumLocale === 'english' ? ( return curriculumLocale === 'english' ? (
<ul data-test-label='learn-curriculum-map'> <ul data-test-label='learn-curriculum-map'>
{nodes.map((node, i) => ( {nodes.map(({ challenge }, i) => (
<li key={i}> <li key={i}>
<Link <Link
className='btn link-btn btn-lg' className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`} to={`/learn/${challenge.superBlock}/`}
> >
<div style={linkSpacingStyle}> <div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')} {generateIconComponent(challenge.superBlock, 'map-icon')}
{createSuperBlockTitle(node.superBlock)} {createSuperBlockTitle(challenge.superBlock)}
</div> </div>
</Link> </Link>
</li> </li>
@ -88,16 +90,18 @@ function renderLearnMap(
) : ( ) : (
<ul data-test-label='learn-curriculum-map'> <ul data-test-label='learn-curriculum-map'>
{nodes {nodes
.filter(node => isAuditedCert(curriculumLocale, node.superBlock)) .filter(({ challenge }) =>
.map((node, i) => ( isAuditedCert(curriculumLocale, challenge.superBlock)
)
.map(({ challenge }, i) => (
<li key={i}> <li key={i}>
<Link <Link
className='btn link-btn btn-lg' className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`} to={`/learn/${challenge.superBlock}/`}
> >
<div style={linkSpacingStyle}> <div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')} {generateIconComponent(challenge.superBlock, 'map-icon')}
{createSuperBlockTitle(node.superBlock)} {createSuperBlockTitle(challenge.superBlock)}
</div> </div>
</Link> </Link>
</li> </li>
@ -115,16 +119,19 @@ function renderLearnMap(
<Spacer /> <Spacer />
</div> </div>
{nodes {nodes
.filter(node => !isAuditedCert(curriculumLocale, node.superBlock)) .filter(
.map((node, i) => ( ({ challenge }) =>
!isAuditedCert(curriculumLocale, challenge.superBlock)
)
.map(({ challenge }, i) => (
<li key={i}> <li key={i}>
<Link <Link
className='btn link-btn btn-lg' className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`} to={`/learn/${challenge.superBlock}/`}
> >
<div style={linkSpacingStyle}> <div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')} {generateIconComponent(challenge.superBlock, 'map-icon')}
{createSuperBlockTitle(node.superBlock)} {createSuperBlockTitle(challenge.superBlock)}
</div> </div>
</Link> </Link>
</li> </li>
@ -145,15 +152,17 @@ export function Map({
const data: MapData = useStaticQuery(graphql` const data: MapData = useStaticQuery(graphql`
query SuperBlockNodes { query SuperBlockNodes {
allChallengeNode( allChallengeNode(
sort: { fields: [superOrder] } sort: { fields: [challenge___superOrder] }
filter: { order: { eq: 0 }, challengeOrder: { eq: 0 } } filter: { challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } } }
) { ) {
nodes { nodes {
challenge {
superBlock superBlock
dashedName dashedName
} }
} }
} }
}
`); `);
const nodes = data.allChallengeNode.nodes; const nodes = data.allChallengeNode.nodes;

View File

@ -269,6 +269,7 @@ function useIdToNameMap(): Map<string, string> {
allChallengeNode { allChallengeNode {
edges { edges {
node { node {
challenge {
fields { fields {
slug slug
} }
@ -278,6 +279,7 @@ function useIdToNameMap(): Map<string, string> {
} }
} }
} }
}
`); `);
const idToNameMap = new Map(); const idToNameMap = new Map();
for (const id of getCertIds()) { for (const id of getCertIds()) {
@ -289,6 +291,7 @@ function useIdToNameMap(): Map<string, string> {
edges.forEach( edges.forEach(
({ ({
node: { node: {
challenge: {
// @ts-expect-error Graphql needs typing // @ts-expect-error Graphql needs typing
id, id,
// @ts-expect-error Graphql needs typing // @ts-expect-error Graphql needs typing
@ -296,6 +299,7 @@ function useIdToNameMap(): Map<string, string> {
// @ts-expect-error Graphql needs typing // @ts-expect-error Graphql needs typing
fields: { slug } fields: { slug }
} }
}
}) => { }) => {
idToNameMap.set(id, { challengeTitle: title, challengePath: slug }); idToNameMap.set(id, { challengeTitle: title, challengePath: slug });
} }

View File

@ -12,24 +12,29 @@ beforeEach(() => {
edges: [ edges: [
{ {
node: { node: {
challenge: {
fields: { fields: {
slug: '' slug: ''
}, },
id: '5e46f802ac417301a38fb92b', id: '5e46f802ac417301a38fb92b',
title: 'Page View Time Series Visualizer' title: 'Page View Time Series Visualizer'
} }
}
}, },
{ {
node: { node: {
challenge: {
fields: { fields: {
slug: '' slug: ''
}, },
id: '5e4f5c4b570f7e3a4949899f', id: '5e4f5c4b570f7e3a4949899f',
title: 'Sea Level Predictor' title: 'Sea Level Predictor'
} }
}
}, },
{ {
node: { node: {
challenge: {
fields: { fields: {
slug: '' slug: ''
}, },
@ -37,6 +42,7 @@ beforeEach(() => {
title: 'Medical Data Visualizer' title: 'Medical Data Visualizer'
} }
} }
}
] ]
} }
})); }));

View File

@ -53,9 +53,11 @@ interface LearnPageProps {
user: User; user: User;
data: { data: {
challengeNode: { challengeNode: {
challenge: {
fields: Slug; fields: Slug;
}; };
}; };
};
executeGA: (payload: Record<string, unknown>) => void; executeGA: (payload: Record<string, unknown>) => void;
} }
@ -70,9 +72,11 @@ function LearnPage({
executeGA, executeGA,
data: { data: {
challengeNode: { challengeNode: {
challenge: {
fields: { slug } fields: { slug }
} }
} }
}
}: LearnPageProps) { }: LearnPageProps) {
const { t } = useTranslation(); const { t } = useTranslation();
@ -117,10 +121,12 @@ export default connect(mapStateToProps, mapDispatchToProps)(LearnPage);
export const query = graphql` export const query = graphql`
query FirstChallenge { query FirstChallenge {
challengeNode(order: { eq: 0 }, challengeOrder: { eq: 0 }) { challengeNode(challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } }) {
challenge {
fields { fields {
slug slug
} }
} }
} }
}
`; `;

View File

@ -128,6 +128,7 @@ export interface VideoLocaleIds {
} }
export type ChallengeNode = { export type ChallengeNode = {
challenge: {
block: string; block: string;
challengeOrder: number; challengeOrder: number;
challengeType: number; challengeType: number;
@ -178,6 +179,7 @@ export type ChallengeNode = {
bilibiliIds?: BilibiliIds; bilibiliIds?: BilibiliIds;
videoUrl: string; videoUrl: string;
}; };
};
export type AllChallengeNode = { export type AllChallengeNode = {
edges: [ edges: [

View File

@ -212,7 +212,9 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
componentDidMount() { componentDidMount() {
const { const {
data: { data: {
challengeNode: { title } challengeNode: {
challenge: { title }
}
} }
} = this.props; } = this.props;
this.initializeComponent(title); this.initializeComponent(title);
@ -222,18 +224,22 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
title: prevTitle, title: prevTitle,
fields: { tests: prevTests } fields: { tests: prevTests }
} }
} }
}
} = prevProps; } = prevProps;
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
title: currentTitle, title: currentTitle,
fields: { tests: currTests } fields: { tests: currTests }
} }
} }
}
} = this.props; } = this.props;
if (prevTitle !== currentTitle || prevTests !== currTests) { if (prevTitle !== currentTitle || prevTests !== currTests) {
this.initializeComponent(currentTitle); this.initializeComponent(currentTitle);
@ -250,12 +256,14 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
openModal, openModal,
data: { data: {
challengeNode: { challengeNode: {
challenge: {
challengeFiles, challengeFiles,
fields: { tests }, fields: { tests },
challengeType, challengeType,
removeComments, removeComments,
helpCategory helpCategory
} }
}
}, },
pageContext: { pageContext: {
challengeMeta, challengeMeta,
@ -282,7 +290,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
cancelTests(); cancelTests();
} }
getChallenge = () => this.props.data.challengeNode; getChallenge = () => this.props.data.challengeNode.challenge;
getBlockNameTitle(t: TFunction) { getBlockNameTitle(t: TFunction) {
const { block, superBlock, title } = this.getChallenge(); const { block, superBlock, title } = this.getChallenge();
@ -337,10 +345,12 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
challengeFiles, challengeFiles,
data: { data: {
challengeNode: { challengeNode: {
challenge: {
fields: { tests }, fields: { tests },
usesMultifileEditor usesMultifileEditor
} }
} }
}
} = this.props; } = this.props;
const { description, title } = this.getChallenge(); const { description, title } = this.getChallenge();
// eslint-disable-next-line @typescript-eslint/no-unsafe-return // eslint-disable-next-line @typescript-eslint/no-unsafe-return
@ -489,7 +499,8 @@ export default connect(
export const query = graphql` export const query = graphql`
query ClassicChallenge($slug: String!) { query ClassicChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) { challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
block block
title title
description description
@ -526,4 +537,5 @@ export const query = graphql`
} }
} }
} }
}
`; `;

View File

@ -49,7 +49,9 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
const { const {
updateChallengeMeta, updateChallengeMeta,
data: { data: {
challengeNode: { challengeType, title } challengeNode: {
challenge: { challengeType, title }
}
}, },
pageContext: { challengeMeta } pageContext: { challengeMeta }
} = this.props; } = this.props;
@ -60,10 +62,12 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
title, title,
fields: { blockName }, fields: { blockName },
url url
} }
}
}, },
webhookToken = null webhookToken = null
} = this.props; } = this.props;
@ -94,7 +98,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShowCodeAlly);
// GraphQL // GraphQL
export const query = graphql` export const query = graphql`
query CodeAllyChallenge($slug: String!) { query CodeAllyChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) { challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
title title
challengeType challengeType
url url
@ -103,4 +108,5 @@ export const query = graphql`
} }
} }
} }
}
`; `;

View File

@ -280,9 +280,18 @@ const useCurrentBlockIds = (blockName: string) => {
allChallengeNode: { edges } allChallengeNode: { edges }
}: { allChallengeNode: AllChallengeNode } = useStaticQuery(graphql` }: { allChallengeNode: AllChallengeNode } = useStaticQuery(graphql`
query getCurrentBlockNodes { query getCurrentBlockNodes {
allChallengeNode(sort: { fields: [superOrder, order, challengeOrder] }) { allChallengeNode(
sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) {
edges { edges {
node { node {
challenge {
fields { fields {
blockName blockName
} }
@ -291,12 +300,13 @@ const useCurrentBlockIds = (blockName: string) => {
} }
} }
} }
}
`); `);
const currentBlockIds = edges const currentBlockIds = edges
.filter(edge => edge.node.fields.blockName === blockName) .filter(edge => edge.node.challenge.fields.blockName === blockName)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return // eslint-disable-next-line @typescript-eslint/no-unsafe-return
.map(edge => edge.node.id); .map(edge => edge.node.challenge.id);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return currentBlockIds; return currentBlockIds;
}; };

View File

@ -123,18 +123,22 @@ class BackEnd extends Component<BackEndProps> {
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
title: prevTitle, title: prevTitle,
fields: { tests: prevTests } fields: { tests: prevTests }
} }
} }
}
} = prevProps; } = prevProps;
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
title: currentTitle, title: currentTitle,
fields: { tests: currTests } fields: { tests: currTests }
} }
} }
}
} = this.props; } = this.props;
if (prevTitle !== currentTitle || prevTests !== currTests) { if (prevTitle !== currentTitle || prevTests !== currTests) {
this.initializeComponent(); this.initializeComponent();
@ -149,11 +153,13 @@ class BackEnd extends Component<BackEndProps> {
updateChallengeMeta, updateChallengeMeta,
data: { data: {
challengeNode: { challengeNode: {
challenge: {
fields: { tests }, fields: { tests },
title, title,
challengeType, challengeType,
helpCategory helpCategory
} }
}
}, },
pageContext: { challengeMeta } pageContext: { challengeMeta }
} = this.props; } = this.props;
@ -182,6 +188,7 @@ class BackEnd extends Component<BackEndProps> {
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
fields: { blockName }, fields: { blockName },
challengeType, challengeType,
forumTopicId, forumTopicId,
@ -192,6 +199,7 @@ class BackEnd extends Component<BackEndProps> {
superBlock, superBlock,
block block
} }
}
}, },
isChallengeCompleted, isChallengeCompleted,
output, output,
@ -278,7 +286,8 @@ export default connect(
export const query = graphql` export const query = graphql`
query BackendChallenge($slug: String!) { query BackendChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) { challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
forumTopicId forumTopicId
title title
description description
@ -298,4 +307,5 @@ export const query = graphql`
} }
} }
} }
}
`; `;

View File

@ -76,7 +76,9 @@ class Project extends Component<ProjectProps> {
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title, challengeType, helpCategory } challengeNode: {
challenge: { title, challengeType, helpCategory }
}
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
@ -94,13 +96,17 @@ class Project extends Component<ProjectProps> {
componentDidUpdate(prevProps: ProjectProps): void { componentDidUpdate(prevProps: ProjectProps): void {
const { const {
data: { data: {
challengeNode: { title: prevTitle } challengeNode: {
challenge: { title: prevTitle }
}
} }
} = prevProps; } = prevProps;
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title: currentTitle, challengeType, helpCategory } challengeNode: {
challenge: { title: currentTitle, challengeType, helpCategory }
}
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
@ -130,6 +136,7 @@ class Project extends Component<ProjectProps> {
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
challengeType, challengeType,
fields: { blockName }, fields: { blockName },
forumTopicId, forumTopicId,
@ -140,6 +147,7 @@ class Project extends Component<ProjectProps> {
block, block,
translationPending translationPending
} }
}
}, },
isChallengeCompleted, isChallengeCompleted,
pageContext: { pageContext: {
@ -215,7 +223,8 @@ export default connect(
export const query = graphql` export const query = graphql`
query ProjectChallenge($slug: String!) { query ProjectChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) { challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
forumTopicId forumTopicId
title title
description description
@ -231,4 +240,5 @@ export const query = graphql`
} }
} }
} }
}
`; `;

View File

@ -97,7 +97,9 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title, challengeType, helpCategory } challengeNode: {
challenge: { title, challengeType, helpCategory }
}
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
@ -115,13 +117,17 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
componentDidUpdate(prevProps: ShowVideoProps): void { componentDidUpdate(prevProps: ShowVideoProps): void {
const { const {
data: { data: {
challengeNode: { title: prevTitle } challengeNode: {
challenge: { title: prevTitle }
}
} }
} = prevProps; } = prevProps;
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title: currentTitle, challengeType, helpCategory } challengeNode: {
challenge: { title: currentTitle, challengeType, helpCategory }
}
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
@ -169,6 +175,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
const { const {
data: { data: {
challengeNode: { challengeNode: {
challenge: {
fields: { blockName }, fields: { blockName },
title, title,
description, description,
@ -180,6 +187,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
bilibiliIds, bilibiliIds,
question: { text, answers, solution } question: { text, answers, solution }
} }
}
}, },
openCompletionModal, openCompletionModal,
pageContext: { pageContext: {
@ -313,7 +321,8 @@ export default connect(
export const query = graphql` export const query = graphql`
query VideoChallenge($slug: String!) { query VideoChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) { challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
videoId videoId
videoLocaleIds { videoLocaleIds {
espanol espanol
@ -343,4 +352,5 @@ export const query = graphql`
translationPending translationPending
} }
} }
}
`; `;

View File

@ -101,7 +101,7 @@ export class Block extends Component<BlockProps> {
} = this.props; } = this.props;
let completedCount = 0; let completedCount = 0;
const challengesWithCompleted = challenges.map(challenge => { const challengesWithCompleted = challenges.map(({ challenge }) => {
const { id } = challenge; const { id } = challenge;
const isCompleted = completedChallengeIds.some( const isCompleted = completedChallengeIds.some(
(completedChallengeId: string) => completedChallengeId === id (completedChallengeId: string) => completedChallengeId === id
@ -112,7 +112,7 @@ export class Block extends Component<BlockProps> {
return { ...challenge, isCompleted }; return { ...challenge, isCompleted };
}); });
const isProjectBlock = challenges.some(challenge => { const isProjectBlock = challenges.some(({ challenge }) => {
const isJsProject = const isJsProject =
challenge.order === 10 && challenge.challengeType === 5; challenge.order === 10 && challenge.challengeType === 5;

View File

@ -21,7 +21,7 @@ function renderMenuItems({
edges?: Array<{ node: ChallengeNode }>; edges?: Array<{ node: ChallengeNode }>;
}) { }) {
return edges return edges
.map(({ node }) => node) .map(({ node: { challenge } }) => challenge)
.map(({ title, fields: { slug } }) => ( .map(({ title, fields: { slug } }) => (
<Link key={'intro-' + slug} to={slug}> <Link key={'intro-' + slug} to={slug}>
<ListGroupItem>{title}</ListGroupItem> <ListGroupItem>{title}</ListGroupItem>
@ -42,7 +42,8 @@ function IntroductionPage({
html, html,
frontmatter: { block } frontmatter: { block }
} = markdownRemark; } = markdownRemark;
const firstLesson = allChallengeNode && allChallengeNode.edges[0].node; const firstLesson =
allChallengeNode && allChallengeNode.edges[0].node.challenge;
const firstLessonPath = firstLesson const firstLessonPath = firstLesson
? firstLesson.fields.slug ? firstLesson.fields.slug
: '/strange-place'; : '/strange-place';
@ -97,11 +98,18 @@ export const query = graphql`
html html
} }
allChallengeNode( allChallengeNode(
filter: { block: { eq: $block } } filter: { challenge: { block: { eq: $block } } }
sort: { fields: [superOrder, order, challengeOrder] } sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) { ) {
edges { edges {
node { node {
challenge {
fields { fields {
slug slug
} }
@ -110,4 +118,5 @@ export const query = graphql`
} }
} }
} }
}
`; `;

View File

@ -141,15 +141,15 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
if (isSignedIn) { if (isSignedIn) {
// see if currentChallenge is in this superBlock // see if currentChallenge is in this superBlock
const currentChallengeEdge = edges.find( const currentChallengeEdge = edges.find(
edge => edge.node.id === currentChallengeId edge => edge.node.challenge.id === currentChallengeId
); );
return currentChallengeEdge return currentChallengeEdge
? currentChallengeEdge.node.block ? currentChallengeEdge.node.challenge.block
: edge.node.block; : edge.node.challenge.block;
} }
return edge.node.block; return edge.node.challenge.block;
}; };
const initializeExpandedState = () => { const initializeExpandedState = () => {
@ -173,7 +173,9 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
} = props; } = props;
const nodesForSuperBlock = edges.map(({ node }) => node); const nodesForSuperBlock = edges.map(({ node }) => node);
const blockDashedNames = uniq(nodesForSuperBlock.map(({ block }) => block)); const blockDashedNames = uniq(
nodesForSuperBlock.map(({ challenge: { block } }) => block)
);
const i18nSuperBlock = t(`intro:${superBlock}.title`); const i18nSuperBlock = t(`intro:${superBlock}.title`);
const i18nTitle = const i18nTitle =
superBlock === SuperBlocks.CodingInterviewPrep superBlock === SuperBlocks.CodingInterviewPrep
@ -206,7 +208,7 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
<Block <Block
blockDashedName={blockDashedName} blockDashedName={blockDashedName}
challenges={nodesForSuperBlock.filter( challenges={nodesForSuperBlock.filter(
node => node.block === blockDashedName node => node.challenge.block === blockDashedName
)} )}
superBlock={superBlock} superBlock={superBlock}
/> />
@ -263,11 +265,18 @@ export const query = graphql`
} }
} }
allChallengeNode( allChallengeNode(
sort: { fields: [superOrder, order, challengeOrder] } sort: {
filter: { superBlock: { eq: $superBlock } } fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
filter: { challenge: { superBlock: { eq: $superBlock } } }
) { ) {
edges { edges {
node { node {
challenge {
fields { fields {
slug slug
blockName blockName
@ -283,4 +292,5 @@ export const query = graphql`
} }
} }
} }
}
`; `;

View File

@ -45,12 +45,12 @@ const views = {
function getNextChallengePath(_node, index, nodeArray) { function getNextChallengePath(_node, index, nodeArray) {
const next = nodeArray[index + 1]; const next = nodeArray[index + 1];
return next ? next.node.fields.slug : '/learn'; return next ? next.node.challenge.fields.slug : '/learn';
} }
function getPrevChallengePath(_node, index, nodeArray) { function getPrevChallengePath(_node, index, nodeArray) {
const prev = nodeArray[index - 1]; const prev = nodeArray[index - 1];
return prev ? prev.node.fields.slug : '/learn'; return prev ? prev.node.challenge.fields.slug : '/learn';
} }
function getTemplateComponent(challengeType) { function getTemplateComponent(challengeType) {
@ -58,7 +58,7 @@ function getTemplateComponent(challengeType) {
} }
exports.createChallengePages = function (createPage) { exports.createChallengePages = function (createPage) {
return function ({ node: challenge }, index, allChallengeEdges) { return function ({ node: { challenge } }, index, allChallengeEdges) {
const { const {
superBlock, superBlock,
block, block,
@ -103,8 +103,8 @@ function getProjectPreviewConfig(challenge, allChallengeEdges) {
const { block, challengeOrder, usesMultifileEditor } = challenge; const { block, challengeOrder, usesMultifileEditor } = challenge;
const challengesInBlock = allChallengeEdges const challengesInBlock = allChallengeEdges
.filter(({ node }) => node.block === block) .filter(({ node: { challenge } }) => challenge.block === block)
.map(({ node }) => node); .map(({ node: { challenge } }) => challenge);
const lastChallenge = challengesInBlock[challengesInBlock.length - 1]; const lastChallenge = challengesInBlock[challengesInBlock.length - 1];
const solutionToLastChallenge = sortChallengeFiles( const solutionToLastChallenge = sortChallengeFiles(
lastChallenge.solutions[0] ?? [] lastChallenge.solutions[0] ?? []