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:
committed by
GitHub
parent
755f27093c
commit
3b560deab6
@ -20,13 +20,6 @@ const createByIdentityMap = {
|
||||
|
||||
exports.onCreateNode = function onCreateNode({ node, actions, getNode }) {
|
||||
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') {
|
||||
const slug = createFilePath({ node, getNode });
|
||||
@ -71,10 +64,17 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
|
||||
graphql(`
|
||||
{
|
||||
allChallengeNode(
|
||||
sort: { fields: [superOrder, order, challengeOrder] }
|
||||
sort: {
|
||||
fields: [
|
||||
challenge___superOrder
|
||||
challenge___order
|
||||
challenge___challengeOrder
|
||||
]
|
||||
}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
block
|
||||
challengeType
|
||||
fields {
|
||||
@ -105,6 +105,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allMarkdownRemark {
|
||||
edges {
|
||||
node {
|
||||
@ -137,12 +138,22 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
|
||||
);
|
||||
|
||||
const blocks = uniq(
|
||||
result.data.allChallengeNode.edges.map(({ node: { block } }) => block)
|
||||
result.data.allChallengeNode.edges.map(
|
||||
({
|
||||
node: {
|
||||
challenge: { block }
|
||||
}
|
||||
}) => block
|
||||
)
|
||||
).map(block => blockNameify(block));
|
||||
|
||||
const superBlocks = uniq(
|
||||
result.data.allChallengeNode.edges.map(
|
||||
({ node: { superBlock } }) => superBlock
|
||||
({
|
||||
node: {
|
||||
challenge: { superBlock }
|
||||
}
|
||||
}) => superBlock
|
||||
)
|
||||
);
|
||||
|
||||
@ -256,6 +267,9 @@ exports.createSchemaCustomization = ({ actions }) => {
|
||||
const { createTypes } = actions;
|
||||
const typeDefs = `
|
||||
type ChallengeNode implements Node {
|
||||
challenge: Challenge
|
||||
}
|
||||
type Challenge {
|
||||
challengeFiles: [FileContents]
|
||||
notes: String
|
||||
url: String
|
||||
|
@ -1,4 +1,5 @@
|
||||
const crypto = require('crypto');
|
||||
const { blockNameify } = require('../../../utils/block-nameify');
|
||||
|
||||
function createChallengeNode(challenge, reporter) {
|
||||
// challengeType 11 is for video challenges (they only have instructions)
|
||||
@ -31,6 +32,17 @@ function createChallengeNode(challenge, reporter) {
|
||||
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(
|
||||
JSON.stringify(
|
||||
Object.assign(
|
||||
@ -41,7 +53,8 @@ function createChallengeNode(challenge, reporter) {
|
||||
internal,
|
||||
sourceInstanceName: 'challenge'
|
||||
},
|
||||
challenge
|
||||
{ challenge },
|
||||
{ id: crypto.randomUUID() }
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
const mockChallengeNodes = [
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-one/block-a/challenge-one',
|
||||
blockName: 'Block A'
|
||||
@ -10,8 +11,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-one',
|
||||
dashedName: 'challenge-one'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-one/block-a/challenge-two',
|
||||
blockName: 'Block A'
|
||||
@ -22,8 +25,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-one',
|
||||
dashedName: 'challenge-two'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-one/block-b/challenge-one',
|
||||
blockName: 'Block B'
|
||||
@ -34,8 +39,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-one',
|
||||
dashedName: 'challenge-one'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-one/block-b/challenge-two',
|
||||
blockName: 'Block B'
|
||||
@ -47,8 +54,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-one',
|
||||
dashedName: 'challenge-two'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-one/block-c/challenge-one',
|
||||
blockName: 'Block C'
|
||||
@ -59,8 +68,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: true,
|
||||
superBlock: 'super-block-one',
|
||||
dashedName: 'challenge-one'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-two/block-a/challenge-one',
|
||||
blockName: 'Block A'
|
||||
@ -71,8 +82,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-two',
|
||||
dashedName: 'challenge-one'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-two/block-a/challenge-two',
|
||||
blockName: 'Block A'
|
||||
@ -83,8 +96,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-two',
|
||||
dashedName: 'challenge-two'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-two/block-b/challenge-one',
|
||||
blockName: 'Block B'
|
||||
@ -95,8 +110,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-two',
|
||||
dashedName: 'challenge-one'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-two/block-b/challenge-two',
|
||||
blockName: 'Block B'
|
||||
@ -107,8 +124,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-two',
|
||||
dashedName: 'challenge-two'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-three/block-a/challenge-one',
|
||||
blockName: 'Block A'
|
||||
@ -119,8 +138,10 @@ const mockChallengeNodes = [
|
||||
isPrivate: false,
|
||||
superBlock: 'super-block-three',
|
||||
dashedName: 'challenge-one'
|
||||
}
|
||||
},
|
||||
{
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: '/super-block-three/block-c/challenge-two',
|
||||
blockName: 'Block C'
|
||||
@ -132,6 +153,7 @@ const mockChallengeNodes = [
|
||||
superBlock: 'super-block-three',
|
||||
dashedName: 'challenge-two'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default mockChallengeNodes;
|
||||
|
@ -42,19 +42,19 @@ const linkSpacingStyle = {
|
||||
|
||||
function renderLandingMap(nodes: ChallengeNode[]) {
|
||||
nodes = nodes.filter(
|
||||
node => node.superBlock !== SuperBlocks.CodingInterviewPrep
|
||||
({ challenge }) => challenge.superBlock !== SuperBlocks.CodingInterviewPrep
|
||||
);
|
||||
return (
|
||||
<ul data-test-label='certifications'>
|
||||
{nodes.map((node, i) => (
|
||||
{nodes.map(({ challenge }, i) => (
|
||||
<li key={i}>
|
||||
<Link
|
||||
className='btn link-btn btn-lg'
|
||||
to={`/learn/${node.superBlock}/`}
|
||||
to={`/learn/${challenge.superBlock}/`}
|
||||
>
|
||||
<div style={linkSpacingStyle}>
|
||||
{generateIconComponent(node.superBlock, 'map-icon')}
|
||||
{i18next.t(`intro:${node.superBlock}.title`)}
|
||||
{generateIconComponent(challenge.superBlock, 'map-icon')}
|
||||
{i18next.t(`intro:${challenge.superBlock}.title`)}
|
||||
</div>
|
||||
<LinkButton />
|
||||
</Link>
|
||||
@ -68,18 +68,20 @@ function renderLearnMap(
|
||||
nodes: ChallengeNode[],
|
||||
currentSuperBlock: MapProps['currentSuperBlock']
|
||||
) {
|
||||
nodes = nodes.filter(node => node.superBlock !== currentSuperBlock);
|
||||
nodes = nodes.filter(
|
||||
({ challenge }) => challenge.superBlock !== currentSuperBlock
|
||||
);
|
||||
return curriculumLocale === 'english' ? (
|
||||
<ul data-test-label='learn-curriculum-map'>
|
||||
{nodes.map((node, i) => (
|
||||
{nodes.map(({ challenge }, i) => (
|
||||
<li key={i}>
|
||||
<Link
|
||||
className='btn link-btn btn-lg'
|
||||
to={`/learn/${node.superBlock}/`}
|
||||
to={`/learn/${challenge.superBlock}/`}
|
||||
>
|
||||
<div style={linkSpacingStyle}>
|
||||
{generateIconComponent(node.superBlock, 'map-icon')}
|
||||
{createSuperBlockTitle(node.superBlock)}
|
||||
{generateIconComponent(challenge.superBlock, 'map-icon')}
|
||||
{createSuperBlockTitle(challenge.superBlock)}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
@ -88,16 +90,18 @@ function renderLearnMap(
|
||||
) : (
|
||||
<ul data-test-label='learn-curriculum-map'>
|
||||
{nodes
|
||||
.filter(node => isAuditedCert(curriculumLocale, node.superBlock))
|
||||
.map((node, i) => (
|
||||
.filter(({ challenge }) =>
|
||||
isAuditedCert(curriculumLocale, challenge.superBlock)
|
||||
)
|
||||
.map(({ challenge }, i) => (
|
||||
<li key={i}>
|
||||
<Link
|
||||
className='btn link-btn btn-lg'
|
||||
to={`/learn/${node.superBlock}/`}
|
||||
to={`/learn/${challenge.superBlock}/`}
|
||||
>
|
||||
<div style={linkSpacingStyle}>
|
||||
{generateIconComponent(node.superBlock, 'map-icon')}
|
||||
{createSuperBlockTitle(node.superBlock)}
|
||||
{generateIconComponent(challenge.superBlock, 'map-icon')}
|
||||
{createSuperBlockTitle(challenge.superBlock)}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
@ -115,16 +119,19 @@ function renderLearnMap(
|
||||
<Spacer />
|
||||
</div>
|
||||
{nodes
|
||||
.filter(node => !isAuditedCert(curriculumLocale, node.superBlock))
|
||||
.map((node, i) => (
|
||||
.filter(
|
||||
({ challenge }) =>
|
||||
!isAuditedCert(curriculumLocale, challenge.superBlock)
|
||||
)
|
||||
.map(({ challenge }, i) => (
|
||||
<li key={i}>
|
||||
<Link
|
||||
className='btn link-btn btn-lg'
|
||||
to={`/learn/${node.superBlock}/`}
|
||||
to={`/learn/${challenge.superBlock}/`}
|
||||
>
|
||||
<div style={linkSpacingStyle}>
|
||||
{generateIconComponent(node.superBlock, 'map-icon')}
|
||||
{createSuperBlockTitle(node.superBlock)}
|
||||
{generateIconComponent(challenge.superBlock, 'map-icon')}
|
||||
{createSuperBlockTitle(challenge.superBlock)}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
@ -145,15 +152,17 @@ export function Map({
|
||||
const data: MapData = useStaticQuery(graphql`
|
||||
query SuperBlockNodes {
|
||||
allChallengeNode(
|
||||
sort: { fields: [superOrder] }
|
||||
filter: { order: { eq: 0 }, challengeOrder: { eq: 0 } }
|
||||
sort: { fields: [challenge___superOrder] }
|
||||
filter: { challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } } }
|
||||
) {
|
||||
nodes {
|
||||
challenge {
|
||||
superBlock
|
||||
dashedName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const nodes = data.allChallengeNode.nodes;
|
||||
|
@ -269,6 +269,7 @@ function useIdToNameMap(): Map<string, string> {
|
||||
allChallengeNode {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
@ -278,6 +279,7 @@ function useIdToNameMap(): Map<string, string> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
const idToNameMap = new Map();
|
||||
for (const id of getCertIds()) {
|
||||
@ -289,6 +291,7 @@ function useIdToNameMap(): Map<string, string> {
|
||||
edges.forEach(
|
||||
({
|
||||
node: {
|
||||
challenge: {
|
||||
// @ts-expect-error Graphql needs typing
|
||||
id,
|
||||
// @ts-expect-error Graphql needs typing
|
||||
@ -296,6 +299,7 @@ function useIdToNameMap(): Map<string, string> {
|
||||
// @ts-expect-error Graphql needs typing
|
||||
fields: { slug }
|
||||
}
|
||||
}
|
||||
}) => {
|
||||
idToNameMap.set(id, { challengeTitle: title, challengePath: slug });
|
||||
}
|
||||
|
@ -12,24 +12,29 @@ beforeEach(() => {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: ''
|
||||
},
|
||||
id: '5e46f802ac417301a38fb92b',
|
||||
title: 'Page View Time Series Visualizer'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
node: {
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: ''
|
||||
},
|
||||
id: '5e4f5c4b570f7e3a4949899f',
|
||||
title: 'Sea Level Predictor'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
node: {
|
||||
challenge: {
|
||||
fields: {
|
||||
slug: ''
|
||||
},
|
||||
@ -37,6 +42,7 @@ beforeEach(() => {
|
||||
title: 'Medical Data Visualizer'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
|
@ -53,9 +53,11 @@ interface LearnPageProps {
|
||||
user: User;
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fields: Slug;
|
||||
};
|
||||
};
|
||||
};
|
||||
executeGA: (payload: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
@ -70,9 +72,11 @@ function LearnPage({
|
||||
executeGA,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fields: { slug }
|
||||
}
|
||||
}
|
||||
}
|
||||
}: LearnPageProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -117,10 +121,12 @@ export default connect(mapStateToProps, mapDispatchToProps)(LearnPage);
|
||||
|
||||
export const query = graphql`
|
||||
query FirstChallenge {
|
||||
challengeNode(order: { eq: 0 }, challengeOrder: { eq: 0 }) {
|
||||
challengeNode(challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } }) {
|
||||
challenge {
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -128,6 +128,7 @@ export interface VideoLocaleIds {
|
||||
}
|
||||
|
||||
export type ChallengeNode = {
|
||||
challenge: {
|
||||
block: string;
|
||||
challengeOrder: number;
|
||||
challengeType: number;
|
||||
@ -177,6 +178,7 @@ export type ChallengeNode = {
|
||||
videoLocaleIds?: VideoLocaleIds;
|
||||
bilibiliIds?: BilibiliIds;
|
||||
videoUrl: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type AllChallengeNode = {
|
||||
|
@ -212,7 +212,9 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
componentDidMount() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: { title }
|
||||
challengeNode: {
|
||||
challenge: { title }
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
this.initializeComponent(title);
|
||||
@ -222,18 +224,22 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
title: prevTitle,
|
||||
fields: { tests: prevTests }
|
||||
}
|
||||
}
|
||||
}
|
||||
} = prevProps;
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
title: currentTitle,
|
||||
fields: { tests: currTests }
|
||||
}
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
if (prevTitle !== currentTitle || prevTests !== currTests) {
|
||||
this.initializeComponent(currentTitle);
|
||||
@ -250,12 +256,14 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
openModal,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
challengeFiles,
|
||||
fields: { tests },
|
||||
challengeType,
|
||||
removeComments,
|
||||
helpCategory
|
||||
}
|
||||
}
|
||||
},
|
||||
pageContext: {
|
||||
challengeMeta,
|
||||
@ -282,7 +290,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
cancelTests();
|
||||
}
|
||||
|
||||
getChallenge = () => this.props.data.challengeNode;
|
||||
getChallenge = () => this.props.data.challengeNode.challenge;
|
||||
|
||||
getBlockNameTitle(t: TFunction) {
|
||||
const { block, superBlock, title } = this.getChallenge();
|
||||
@ -337,10 +345,12 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
challengeFiles,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fields: { tests },
|
||||
usesMultifileEditor
|
||||
}
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
const { description, title } = this.getChallenge();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
@ -489,7 +499,8 @@ export default connect(
|
||||
|
||||
export const query = graphql`
|
||||
query ClassicChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
||||
challenge {
|
||||
block
|
||||
title
|
||||
description
|
||||
@ -526,4 +537,5 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -49,7 +49,9 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
|
||||
const {
|
||||
updateChallengeMeta,
|
||||
data: {
|
||||
challengeNode: { challengeType, title }
|
||||
challengeNode: {
|
||||
challenge: { challengeType, title }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta }
|
||||
} = this.props;
|
||||
@ -60,10 +62,12 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
title,
|
||||
fields: { blockName },
|
||||
url
|
||||
}
|
||||
}
|
||||
},
|
||||
webhookToken = null
|
||||
} = this.props;
|
||||
@ -94,7 +98,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShowCodeAlly);
|
||||
// GraphQL
|
||||
export const query = graphql`
|
||||
query CodeAllyChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
||||
challenge {
|
||||
title
|
||||
challengeType
|
||||
url
|
||||
@ -103,4 +108,5 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -280,9 +280,18 @@ const useCurrentBlockIds = (blockName: string) => {
|
||||
allChallengeNode: { edges }
|
||||
}: { allChallengeNode: AllChallengeNode } = useStaticQuery(graphql`
|
||||
query getCurrentBlockNodes {
|
||||
allChallengeNode(sort: { fields: [superOrder, order, challengeOrder] }) {
|
||||
allChallengeNode(
|
||||
sort: {
|
||||
fields: [
|
||||
challenge___superOrder
|
||||
challenge___order
|
||||
challenge___challengeOrder
|
||||
]
|
||||
}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
fields {
|
||||
blockName
|
||||
}
|
||||
@ -291,12 +300,13 @@ const useCurrentBlockIds = (blockName: string) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
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
|
||||
.map(edge => edge.node.id);
|
||||
.map(edge => edge.node.challenge.id);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return currentBlockIds;
|
||||
};
|
||||
|
@ -123,18 +123,22 @@ class BackEnd extends Component<BackEndProps> {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
title: prevTitle,
|
||||
fields: { tests: prevTests }
|
||||
}
|
||||
}
|
||||
}
|
||||
} = prevProps;
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
title: currentTitle,
|
||||
fields: { tests: currTests }
|
||||
}
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
if (prevTitle !== currentTitle || prevTests !== currTests) {
|
||||
this.initializeComponent();
|
||||
@ -149,11 +153,13 @@ class BackEnd extends Component<BackEndProps> {
|
||||
updateChallengeMeta,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fields: { tests },
|
||||
title,
|
||||
challengeType,
|
||||
helpCategory
|
||||
}
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta }
|
||||
} = this.props;
|
||||
@ -182,6 +188,7 @@ class BackEnd extends Component<BackEndProps> {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fields: { blockName },
|
||||
challengeType,
|
||||
forumTopicId,
|
||||
@ -192,6 +199,7 @@ class BackEnd extends Component<BackEndProps> {
|
||||
superBlock,
|
||||
block
|
||||
}
|
||||
}
|
||||
},
|
||||
isChallengeCompleted,
|
||||
output,
|
||||
@ -278,7 +286,8 @@ export default connect(
|
||||
|
||||
export const query = graphql`
|
||||
query BackendChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
||||
challenge {
|
||||
forumTopicId
|
||||
title
|
||||
description
|
||||
@ -298,4 +307,5 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -76,7 +76,9 @@ class Project extends Component<ProjectProps> {
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: { title, challengeType, helpCategory }
|
||||
challengeNode: {
|
||||
challenge: { title, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
@ -94,13 +96,17 @@ class Project extends Component<ProjectProps> {
|
||||
componentDidUpdate(prevProps: ProjectProps): void {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: { title: prevTitle }
|
||||
challengeNode: {
|
||||
challenge: { title: prevTitle }
|
||||
}
|
||||
}
|
||||
} = prevProps;
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: { title: currentTitle, challengeType, helpCategory }
|
||||
challengeNode: {
|
||||
challenge: { title: currentTitle, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
@ -130,6 +136,7 @@ class Project extends Component<ProjectProps> {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
challengeType,
|
||||
fields: { blockName },
|
||||
forumTopicId,
|
||||
@ -140,6 +147,7 @@ class Project extends Component<ProjectProps> {
|
||||
block,
|
||||
translationPending
|
||||
}
|
||||
}
|
||||
},
|
||||
isChallengeCompleted,
|
||||
pageContext: {
|
||||
@ -215,7 +223,8 @@ export default connect(
|
||||
|
||||
export const query = graphql`
|
||||
query ProjectChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
||||
challenge {
|
||||
forumTopicId
|
||||
title
|
||||
description
|
||||
@ -231,4 +240,5 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -97,7 +97,9 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: { title, challengeType, helpCategory }
|
||||
challengeNode: {
|
||||
challenge: { title, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
@ -115,13 +117,17 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
||||
componentDidUpdate(prevProps: ShowVideoProps): void {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: { title: prevTitle }
|
||||
challengeNode: {
|
||||
challenge: { title: prevTitle }
|
||||
}
|
||||
}
|
||||
} = prevProps;
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: { title: currentTitle, challengeType, helpCategory }
|
||||
challengeNode: {
|
||||
challenge: { title: currentTitle, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
@ -169,6 +175,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fields: { blockName },
|
||||
title,
|
||||
description,
|
||||
@ -180,6 +187,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
||||
bilibiliIds,
|
||||
question: { text, answers, solution }
|
||||
}
|
||||
}
|
||||
},
|
||||
openCompletionModal,
|
||||
pageContext: {
|
||||
@ -313,7 +321,8 @@ export default connect(
|
||||
|
||||
export const query = graphql`
|
||||
query VideoChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
||||
challenge {
|
||||
videoId
|
||||
videoLocaleIds {
|
||||
espanol
|
||||
@ -343,4 +352,5 @@ export const query = graphql`
|
||||
translationPending
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -101,7 +101,7 @@ export class Block extends Component<BlockProps> {
|
||||
} = this.props;
|
||||
|
||||
let completedCount = 0;
|
||||
const challengesWithCompleted = challenges.map(challenge => {
|
||||
const challengesWithCompleted = challenges.map(({ challenge }) => {
|
||||
const { id } = challenge;
|
||||
const isCompleted = completedChallengeIds.some(
|
||||
(completedChallengeId: string) => completedChallengeId === id
|
||||
@ -112,7 +112,7 @@ export class Block extends Component<BlockProps> {
|
||||
return { ...challenge, isCompleted };
|
||||
});
|
||||
|
||||
const isProjectBlock = challenges.some(challenge => {
|
||||
const isProjectBlock = challenges.some(({ challenge }) => {
|
||||
const isJsProject =
|
||||
challenge.order === 10 && challenge.challengeType === 5;
|
||||
|
||||
|
@ -21,7 +21,7 @@ function renderMenuItems({
|
||||
edges?: Array<{ node: ChallengeNode }>;
|
||||
}) {
|
||||
return edges
|
||||
.map(({ node }) => node)
|
||||
.map(({ node: { challenge } }) => challenge)
|
||||
.map(({ title, fields: { slug } }) => (
|
||||
<Link key={'intro-' + slug} to={slug}>
|
||||
<ListGroupItem>{title}</ListGroupItem>
|
||||
@ -42,7 +42,8 @@ function IntroductionPage({
|
||||
html,
|
||||
frontmatter: { block }
|
||||
} = markdownRemark;
|
||||
const firstLesson = allChallengeNode && allChallengeNode.edges[0].node;
|
||||
const firstLesson =
|
||||
allChallengeNode && allChallengeNode.edges[0].node.challenge;
|
||||
const firstLessonPath = firstLesson
|
||||
? firstLesson.fields.slug
|
||||
: '/strange-place';
|
||||
@ -97,11 +98,18 @@ export const query = graphql`
|
||||
html
|
||||
}
|
||||
allChallengeNode(
|
||||
filter: { block: { eq: $block } }
|
||||
sort: { fields: [superOrder, order, challengeOrder] }
|
||||
filter: { challenge: { block: { eq: $block } } }
|
||||
sort: {
|
||||
fields: [
|
||||
challenge___superOrder
|
||||
challenge___order
|
||||
challenge___challengeOrder
|
||||
]
|
||||
}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
@ -110,4 +118,5 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -141,15 +141,15 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
|
||||
if (isSignedIn) {
|
||||
// see if currentChallenge is in this superBlock
|
||||
const currentChallengeEdge = edges.find(
|
||||
edge => edge.node.id === currentChallengeId
|
||||
edge => edge.node.challenge.id === currentChallengeId
|
||||
);
|
||||
|
||||
return currentChallengeEdge
|
||||
? currentChallengeEdge.node.block
|
||||
: edge.node.block;
|
||||
? currentChallengeEdge.node.challenge.block
|
||||
: edge.node.challenge.block;
|
||||
}
|
||||
|
||||
return edge.node.block;
|
||||
return edge.node.challenge.block;
|
||||
};
|
||||
|
||||
const initializeExpandedState = () => {
|
||||
@ -173,7 +173,9 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
|
||||
} = props;
|
||||
|
||||
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 i18nTitle =
|
||||
superBlock === SuperBlocks.CodingInterviewPrep
|
||||
@ -206,7 +208,7 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
|
||||
<Block
|
||||
blockDashedName={blockDashedName}
|
||||
challenges={nodesForSuperBlock.filter(
|
||||
node => node.block === blockDashedName
|
||||
node => node.challenge.block === blockDashedName
|
||||
)}
|
||||
superBlock={superBlock}
|
||||
/>
|
||||
@ -263,11 +265,18 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
allChallengeNode(
|
||||
sort: { fields: [superOrder, order, challengeOrder] }
|
||||
filter: { superBlock: { eq: $superBlock } }
|
||||
sort: {
|
||||
fields: [
|
||||
challenge___superOrder
|
||||
challenge___order
|
||||
challenge___challengeOrder
|
||||
]
|
||||
}
|
||||
filter: { challenge: { superBlock: { eq: $superBlock } } }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
fields {
|
||||
slug
|
||||
blockName
|
||||
@ -283,4 +292,5 @@ export const query = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -45,12 +45,12 @@ const views = {
|
||||
|
||||
function getNextChallengePath(_node, index, nodeArray) {
|
||||
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) {
|
||||
const prev = nodeArray[index - 1];
|
||||
return prev ? prev.node.fields.slug : '/learn';
|
||||
return prev ? prev.node.challenge.fields.slug : '/learn';
|
||||
}
|
||||
|
||||
function getTemplateComponent(challengeType) {
|
||||
@ -58,7 +58,7 @@ function getTemplateComponent(challengeType) {
|
||||
}
|
||||
|
||||
exports.createChallengePages = function (createPage) {
|
||||
return function ({ node: challenge }, index, allChallengeEdges) {
|
||||
return function ({ node: { challenge } }, index, allChallengeEdges) {
|
||||
const {
|
||||
superBlock,
|
||||
block,
|
||||
@ -103,8 +103,8 @@ function getProjectPreviewConfig(challenge, allChallengeEdges) {
|
||||
const { block, challengeOrder, usesMultifileEditor } = challenge;
|
||||
|
||||
const challengesInBlock = allChallengeEdges
|
||||
.filter(({ node }) => node.block === block)
|
||||
.map(({ node }) => node);
|
||||
.filter(({ node: { challenge } }) => challenge.block === block)
|
||||
.map(({ node: { challenge } }) => challenge);
|
||||
const lastChallenge = challengesInBlock[challengesInBlock.length - 1];
|
||||
const solutionToLastChallenge = sortChallengeFiles(
|
||||
lastChallenge.solutions[0] ?? []
|
||||
|
Reference in New Issue
Block a user