refactor: simplify challenge.block usage (#41185)

This commit is contained in:
Oliver Eyton-Williams
2021-02-23 05:22:48 +01:00
committed by GitHub
parent 3dd33d87ed
commit f8699a8d55
14 changed files with 32 additions and 47 deletions

View File

@ -13,7 +13,6 @@ import isNumeric from 'validator/lib/isNumeric';
import isURL from 'validator/lib/isURL'; import isURL from 'validator/lib/isURL';
import { ifNoUserSend } from '../utils/middleware'; import { ifNoUserSend } from '../utils/middleware';
import { dasherize } from '../../../../utils/slugs';
import { fixCompletedChallengeItem } from '../../common/utils'; import { fixCompletedChallengeItem } from '../../common/utils';
import { getChallenges } from '../utils/get-curriculum'; import { getChallenges } from '../utils/get-curriculum';
import { import {
@ -144,7 +143,7 @@ export function buildUserUpdate(
export function buildChallengeUrl(challenge) { export function buildChallengeUrl(challenge) {
const { superBlock, block, dashedName } = challenge; const { superBlock, block, dashedName } = challenge;
return `/learn/${dasherize(superBlock)}/${dasherize(block)}/${dashedName}`; return `/learn/${superBlock}/${block}/${dashedName}`;
} }
// this is only called once during boot, so it can be slow. // this is only called once during boot, so it can be slow.

View File

@ -159,14 +159,6 @@ describe('boot/challenge', () => {
expect(result).toEqual(requestedChallengeUrl); expect(result).toEqual(requestedChallengeUrl);
}); });
it('can handle non-url-compliant challenge names', () => {
const challenge = { ...mockChallenge, superBlock: 'my awesome' };
const expected = '/learn/my-awesome/actual/challenge';
const result = buildChallengeUrl(challenge);
expect(result).toEqual(expected);
});
}); });
describe('challengeUrlResolver', () => { describe('challengeUrlResolver', () => {

View File

@ -3,7 +3,6 @@ const env = require('../config/env');
const { createFilePath } = require('gatsby-source-filesystem'); const { createFilePath } = require('gatsby-source-filesystem');
const uniq = require('lodash/uniq'); const uniq = require('lodash/uniq');
const { dasherize } = require('../utils/slugs');
const { blockNameify } = require('../utils/block-nameify'); const { blockNameify } = require('../utils/block-nameify');
const { const {
createChallengePages, createChallengePages,
@ -20,7 +19,7 @@ exports.onCreateNode = function onCreateNode({ node, actions, getNode }) {
const { createNodeField } = actions; const { createNodeField } = actions;
if (node.internal.type === 'ChallengeNode') { if (node.internal.type === 'ChallengeNode') {
const { tests = [], block, dashedName, superBlock } = node; const { tests = [], block, dashedName, superBlock } = node;
const slug = `/learn/${superBlock}/${dasherize(block)}/${dashedName}`; const slug = `/learn/${superBlock}/${block}/${dashedName}`;
createNodeField({ node, name: 'slug', value: slug }); createNodeField({ node, name: 'slug', value: slug });
createNodeField({ node, name: 'blockName', value: blockNameify(block) }); createNodeField({ node, name: 'blockName', value: blockNameify(block) });
createNodeField({ node, name: 'tests', value: tests }); createNodeField({ node, name: 'tests', value: tests });

View File

@ -24,7 +24,6 @@ import Hotkeys from '../components/Hotkeys';
import { getGuideUrl } from '../utils'; import { getGuideUrl } from '../utils';
import { challengeTypes } from '../../../../utils/challengeTypes'; import { challengeTypes } from '../../../../utils/challengeTypes';
import { ChallengeNode } from '../../../redux/propTypes'; import { ChallengeNode } from '../../../redux/propTypes';
import { dasherize } from '../../../../../utils/slugs';
import { import {
createFiles, createFiles,
challengeFilesSelector, challengeFilesSelector,
@ -206,7 +205,7 @@ class ShowClassic extends Component {
renderInstructionsPanel({ showToolPanel }) { renderInstructionsPanel({ showToolPanel }) {
const { const {
fields: { blockName }, block,
description, description,
instructions, instructions,
superBlock, superBlock,
@ -216,12 +215,11 @@ class ShowClassic extends Component {
const { forumTopicId, title } = this.getChallenge(); const { forumTopicId, title } = this.getChallenge();
return ( return (
<SidePanel <SidePanel
block={blockName} block={block}
className='full-height' className='full-height'
description={description} description={description}
guideUrl={getGuideUrl({ forumTopicId, title })} guideUrl={getGuideUrl({ forumTopicId, title })}
instructions={instructions} instructions={instructions}
section={dasherize(blockName)}
showToolPanel={showToolPanel} showToolPanel={showToolPanel}
superBlock={superBlock} superBlock={superBlock}
title={title} title={title}
@ -357,6 +355,7 @@ 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(fields: { slug: { eq: $slug } }) {
block
title title
description description
instructions instructions
@ -367,8 +366,8 @@ export const query = graphql`
translationPending translationPending
forumTopicId forumTopicId
fields { fields {
slug
blockName blockName
slug
tests { tests {
text text
testString testString

View File

@ -5,14 +5,14 @@ import PrismFormatted from './PrismFormatted';
import './challenge-description.css'; import './challenge-description.css';
const propTypes = { const propTypes = {
block: PropTypes.string,
description: PropTypes.string, description: PropTypes.string,
instructions: PropTypes.string, instructions: PropTypes.string
section: PropTypes.string
}; };
function ChallengeDescription({ description, instructions, section }) { function ChallengeDescription({ description, instructions, block }) {
return ( return (
<div className={`challenge-instructions${section ? ' ' + section : ''}`}> <div className={`challenge-instructions${block ? ' ' + block : ''}`}>
{description && <PrismFormatted text={description} />} {description && <PrismFormatted text={description} />}
{instructions && ( {instructions && (
<Fragment> <Fragment>

View File

@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Link } from '../../../components/helpers/index'; import { Link } from '../../../components/helpers/index';
import { dasherize } from '../../../../../utils/slugs';
import './challenge-title.css'; import './challenge-title.css';
import GreenPass from '../../../assets/icons/GreenPass'; import GreenPass from '../../../assets/icons/GreenPass';
import i18next from 'i18next'; import i18next from 'i18next';
@ -46,9 +45,9 @@ function ChallengeTitle({
<Link <Link
className='breadcrumb-right' className='breadcrumb-right'
state={{ breadcrumbBlockClick: block }} state={{ breadcrumbBlockClick: block }}
to={`/learn/${superBlock}/#${dasherize(block)}`} to={`/learn/${superBlock}/#${block}`}
> >
{i18next.t(`intro:${superBlock}.blocks.${dasherize(block)}.title`)} {i18next.t(`intro:${superBlock}.blocks.${block}.title`)}
</Link> </Link>
</div> </div>
<div className='challenge-title'> <div className='challenge-title'>

View File

@ -6,10 +6,11 @@ import renderer from 'react-test-renderer';
import ChallengeTitle from './Challenge-Title'; import ChallengeTitle from './Challenge-Title';
const baseProps = { const baseProps = {
block: 'fake block', block: 'fake-block',
children: 'title text', children: 'title text',
isCompleted: true, isCompleted: true,
superBlock: 'fake-superblock' superBlock: 'fake-superblock',
translationPending: false
}; };
describe('<ChallengeTitle/>', () => { describe('<ChallengeTitle/>', () => {

View File

@ -27,7 +27,6 @@ const propTypes = {
guideUrl: PropTypes.string, guideUrl: PropTypes.string,
instructions: PropTypes.string, instructions: PropTypes.string,
isChallengeCompleted: PropTypes.bool, isChallengeCompleted: PropTypes.bool,
section: PropTypes.string,
showToolPanel: PropTypes.bool, showToolPanel: PropTypes.bool,
superBlock: PropTypes.string, superBlock: PropTypes.string,
tests: PropTypes.arrayOf(PropTypes.object), tests: PropTypes.arrayOf(PropTypes.object),
@ -41,8 +40,8 @@ export class SidePanel extends Component {
const MathJax = global.MathJax; const MathJax = global.MathJax;
const mathJaxMountPoint = document.querySelector('#mathjax'); const mathJaxMountPoint = document.querySelector('#mathjax');
const mathJaxChallenge = const mathJaxChallenge =
this.props.section === 'rosetta-code' || this.props.block === 'rosetta-code' ||
this.props.section === 'project-euler'; this.props.block === 'project-euler';
if (MathJax) { if (MathJax) {
// Configure MathJax when it's loaded and // Configure MathJax when it's loaded and
// users navigate from another challenge // users navigate from another challenge
@ -73,7 +72,6 @@ export class SidePanel extends Component {
isChallengeCompleted, isChallengeCompleted,
guideUrl, guideUrl,
tests, tests,
section,
showToolPanel, showToolPanel,
superBlock, superBlock,
translationPending, translationPending,
@ -91,9 +89,9 @@ export class SidePanel extends Component {
{title} {title}
</ChallengeTitle> </ChallengeTitle>
<ChallengeDescription <ChallengeDescription
block={block}
description={description} description={description}
instructions={instructions} instructions={instructions}
section={section}
/> />
</div> </div>
{showToolPanel && <ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />} {showToolPanel && <ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />}

View File

@ -12,7 +12,7 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
href="/learn/fake-superblock" href="/learn/fake-superblock"
state={ state={
Object { Object {
"breadcrumbBlockClick": "fake block", "breadcrumbBlockClick": "fake-block",
} }
} }
> >
@ -28,7 +28,7 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
href="/learn/fake-superblock/#fake-block" href="/learn/fake-superblock/#fake-block"
state={ state={
Object { Object {
"breadcrumbBlockClick": "fake block", "breadcrumbBlockClick": "fake-block",
} }
} }
/> />

View File

@ -14,7 +14,6 @@ import Login from '../../components/Header/components/Login';
import Map from '../../components/Map'; import Map from '../../components/Map';
import CertChallenge from './components/CertChallenge'; import CertChallenge from './components/CertChallenge';
import SuperBlockIntro from './components/SuperBlockIntro'; import SuperBlockIntro from './components/SuperBlockIntro';
import { dasherize } from '../../../../utils/slugs';
import Block from './components/Block'; import Block from './components/Block';
import { Spacer } from '../../components/helpers'; import { Spacer } from '../../components/helpers';
import { import {
@ -97,7 +96,7 @@ export class SuperBlockIntroductionPage extends Component {
// if coming from breadcrumb click // if coming from breadcrumb click
if (location.state && location.state.breadcrumbBlockClick) { if (location.state && location.state.breadcrumbBlockClick) {
return dasherize(location.state.breadcrumbBlockClick); return location.state.breadcrumbBlockClick;
} }
// if the URL includes a hash // if the URL includes a hash

View File

@ -68,7 +68,7 @@ exports.createChallengePages = createPage => ({ node }, index, thisArray) => {
context: { context: {
challengeMeta: { challengeMeta: {
superBlock, superBlock,
block: block, block,
template, template,
required, required,
nextChallengePath: getNextChallengePath(node, index, thisArray), nextChallengePath: getNextChallengePath(node, index, thisArray),

View File

@ -288,7 +288,7 @@ ${getFullPath('english')}
template, template,
time time
} = meta; } = meta;
challenge.block = blockName; challenge.block = dasherize(blockName);
challenge.order = order; challenge.order = order;
challenge.superOrder = superOrder; challenge.superOrder = superOrder;
challenge.superBlock = superBlock; challenge.superBlock = superBlock;
@ -298,7 +298,7 @@ ${getFullPath('english')}
challenge.template = template; challenge.template = template;
challenge.time = time; challenge.time = time;
challenge.helpCategory = challenge.helpCategory =
challenge.helpCategory || helpCategoryMap[dasherize(blockName)]; challenge.helpCategory || helpCategoryMap[challenge.block];
challenge.translationPending = challenge.translationPending =
lang !== 'english' && !isAuditedCert(lang, superBlock); lang !== 'english' && !isAuditedCert(lang, superBlock);
@ -344,7 +344,6 @@ function prepareChallenge(challenge) {
if (challenge.solutionFiles) { if (challenge.solutionFiles) {
challenge.solutionFiles = filesToObject(challenge.solutionFiles); challenge.solutionFiles = filesToObject(challenge.solutionFiles);
} }
challenge.block = dasherize(challenge.block);
return challenge; return challenge;
} }

View File

@ -3,6 +3,8 @@ Joi.objectId = require('joi-objectid')(Joi);
const { challengeTypes } = require('../../client/utils/challengeTypes'); const { challengeTypes } = require('../../client/utils/challengeTypes');
const slugRE = new RegExp('^[a-z0-9-]+$');
const fileJoi = Joi.object().keys({ const fileJoi = Joi.object().keys({
key: Joi.string(), key: Joi.string(),
ext: Joi.string(), ext: Joi.string(),
@ -20,7 +22,7 @@ const fileJoi = Joi.object().keys({
const schema = Joi.object() const schema = Joi.object()
.keys({ .keys({
block: Joi.string(), block: Joi.string().regex(slugRE),
blockId: Joi.objectId(), blockId: Joi.objectId(),
challengeOrder: Joi.number(), challengeOrder: Joi.number(),
challengeType: Joi.number() challengeType: Joi.number()
@ -29,7 +31,7 @@ const schema = Joi.object()
.required(), .required(),
checksum: Joi.number(), checksum: Joi.number(),
// TODO: require this only for normal challenges, not certs // TODO: require this only for normal challenges, not certs
dashedName: Joi.string(), dashedName: Joi.string().regex(slugRE),
description: Joi.when('challengeType', { description: Joi.when('challengeType', {
is: Joi.only([challengeTypes.step, challengeTypes.video]), is: Joi.only([challengeTypes.step, challengeTypes.video]),
then: Joi.string().allow(''), then: Joi.string().allow(''),
@ -82,7 +84,7 @@ const schema = Joi.object()
indexpy: fileJoi indexpy: fileJoi
}) })
), ),
superBlock: Joi.string(), superBlock: Joi.string().regex(slugRE),
superOrder: Joi.number(), superOrder: Joi.number(),
suborder: Joi.number(), suborder: Joi.number(),
tests: Joi.array().items( tests: Joi.array().items(

View File

@ -44,7 +44,6 @@ const ChallengeTitles = require('./utils/challengeTitles');
const { challengeSchemaValidator } = require('../schema/challengeSchema'); const { challengeSchemaValidator } = require('../schema/challengeSchema');
const { challengeTypes } = require('../../client/utils/challengeTypes'); const { challengeTypes } = require('../../client/utils/challengeTypes');
const { dasherize } = require('../../utils/slugs');
const { toSortedArray } = require('../../utils/sort-files'); const { toSortedArray } = require('../../utils/sort-files');
const { testedLang } = require('../utils'); const { testedLang } = require('../utils');
@ -197,7 +196,7 @@ async function setup() {
const meta = {}; const meta = {};
for (const challenge of challenges) { for (const challenge of challenges) {
const dashedBlockName = dasherize(challenge.block); const dashedBlockName = challenge.block;
if (!meta[dashedBlockName]) { if (!meta[dashedBlockName]) {
meta[dashedBlockName] = (await getMetaForBlock( meta[dashedBlockName] = (await getMetaForBlock(
dashedBlockName dashedBlockName
@ -256,7 +255,7 @@ function populateTestsForLang({ lang, challenges, meta }) {
describe(`Check challenges (${lang})`, function() { describe(`Check challenges (${lang})`, function() {
this.timeout(5000); this.timeout(5000);
challenges.forEach((challenge, id) => { challenges.forEach((challenge, id) => {
const dashedBlockName = dasherize(challenge.block); const dashedBlockName = challenge.block;
describe(challenge.block || 'No block', function() { describe(challenge.block || 'No block', function() {
describe(challenge.title || 'No title', function() { describe(challenge.title || 'No title', function() {
// Note: the title in meta.json are purely for human readability and // Note: the title in meta.json are purely for human readability and
@ -280,8 +279,7 @@ function populateTestsForLang({ lang, challenges, meta }) {
throw new AssertionError(result.error); throw new AssertionError(result.error);
} }
const { id, title, block, dashedName } = challenge; const { id, title, block, dashedName } = challenge;
const dashedBlock = dasherize(block); const pathAndTitle = `${block}/${dashedName}`;
const pathAndTitle = `${dashedBlock}/${dashedName}`;
mongoIds.check(id, title); mongoIds.check(id, title);
challengeTitles.check(title, pathAndTitle); challengeTitles.check(title, pathAndTitle);
}); });