feat: update Ask for help (#40114)

* feat: get helpCategory from frontmatter

* DEBUG: sets all the projects to JavaScript

This is just so the tests pass, it'll need to go.

* fix: updated helpCategoryMap categories

* fix: added Python to helpCategory frontmatter key

Co-authored-by: Randell Dawson <rdawson@onepathtech.com>
This commit is contained in:
Oliver Eyton-Williams
2020-10-30 20:10:34 +01:00
committed by GitHub
parent 7857dc53f4
commit e4a9b2988c
12 changed files with 145 additions and 124 deletions

View File

@ -35,6 +35,7 @@ export const ChallengeNode = PropTypes.shape({
forumTopicId: PropTypes.number, forumTopicId: PropTypes.number,
guideUrl: PropTypes.string, guideUrl: PropTypes.string,
head: PropTypes.arrayOf(PropTypes.string), head: PropTypes.arrayOf(PropTypes.string),
helpCategory: PropTypes.string,
instructions: PropTypes.string, instructions: PropTypes.string,
isComingSoon: PropTypes.bool, isComingSoon: PropTypes.bool,
isLocked: PropTypes.bool, isLocked: PropTypes.bool,

View File

@ -153,7 +153,8 @@ class ShowClassic extends Component {
challengeNode: { challengeNode: {
files, files,
fields: { tests }, fields: { tests },
challengeType challengeType,
helpCategory
} }
}, },
pageContext: { challengeMeta } pageContext: { challengeMeta }
@ -161,7 +162,12 @@ class ShowClassic extends Component {
initConsole(''); initConsole('');
createFiles(files); createFiles(files);
initTests(tests); initTests(tests);
updateChallengeMeta({ ...challengeMeta, title, challengeType }); updateChallengeMeta({
...challengeMeta,
title,
challengeType,
helpCategory
});
challengeMounted(challengeMeta.id); challengeMounted(challengeMeta.id);
} }
@ -341,6 +347,7 @@ export const query = graphql`
description description
instructions instructions
challengeType challengeType
helpCategory
videoUrl videoUrl
forumTopicId forumTopicId
fields { fields {

View File

@ -123,14 +123,20 @@ export class BackEnd extends Component {
challengeNode: { challengeNode: {
fields: { tests }, fields: { tests },
title, title,
challengeType challengeType,
helpCategory
} }
}, },
pageContext: { challengeMeta } pageContext: { challengeMeta }
} = this.props; } = this.props;
initConsole(); initConsole();
initTests(tests); initTests(tests);
updateChallengeMeta({ ...challengeMeta, title, challengeType }); updateChallengeMeta({
...challengeMeta,
title,
challengeType,
helpCategory
});
challengeMounted(challengeMeta.id); challengeMounted(challengeMeta.id);
} }
@ -224,6 +230,7 @@ export const query = graphql`
description description
instructions instructions
challengeType challengeType
helpCategory
fields { fields {
blockName blockName
slug slug

View File

@ -55,12 +55,17 @@ export class Project extends Component {
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title, challengeType } challengeNode: { title, challengeType, helpCategory }
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
} = this.props; } = this.props;
updateChallengeMeta({ ...challengeMeta, title, challengeType }); updateChallengeMeta({
...challengeMeta,
title,
challengeType,
helpCategory
});
challengeMounted(challengeMeta.id); challengeMounted(challengeMeta.id);
this._container.focus(); this._container.focus();
} }
@ -74,7 +79,7 @@ export class Project extends Component {
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title: currentTitle, challengeType } challengeNode: { title: currentTitle, challengeType, helpCategory }
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
@ -83,7 +88,8 @@ export class Project extends Component {
updateChallengeMeta({ updateChallengeMeta({
...challengeMeta, ...challengeMeta,
title: currentTitle, title: currentTitle,
challengeType challengeType,
helpCategory
}); });
challengeMounted(challengeMeta.id); challengeMounted(challengeMeta.id);
} }
@ -161,6 +167,7 @@ export const query = graphql`
title title
description description
challengeType challengeType
helpCategory
fields { fields {
blockName blockName
slug slug

View File

@ -7,7 +7,6 @@ import {
challengeMetaSelector challengeMetaSelector
} from '../redux'; } from '../redux';
import { tap, mapTo } from 'rxjs/operators'; import { tap, mapTo } from 'rxjs/operators';
import { helpCategory } from '../../../../utils/challengeTypes';
import { forumLocation } from '../../../../../config/env.json'; import { forumLocation } from '../../../../../config/env.json';
function filesToMarkdown(files = {}) { function filesToMarkdown(files = {}) {
@ -29,7 +28,9 @@ function createQuestionEpic(action$, state$, { window }) {
tap(() => { tap(() => {
const state = state$.value; const state = state$.value;
const files = challengeFilesSelector(state); const files = challengeFilesSelector(state);
const { block, title: challengeTitle } = challengeMetaSelector(state); const { title: challengeTitle, helpCategory } = challengeMetaSelector(
state
);
const { const {
navigator: { userAgent }, navigator: { userAgent },
location: { href } location: { href }
@ -69,13 +70,13 @@ function createQuestionEpic(action$, state$, { window }) {
\`\`\` \`\`\`
Replace these two sentences with your copied code. Replace these two sentences with your copied code.
Please leave the \`\`\` line above and the \`\`\` line below, Please leave the \`\`\` line above and the \`\`\` line below,
because they allow your code to properly format in the post. because they allow your code to properly format in the post.
\`\`\`\n${endingText}` \`\`\`\n${endingText}`
); );
const category = window.encodeURIComponent(helpCategory[block] || 'Help'); const category = window.encodeURIComponent(helpCategory || 'Help');
const studentCode = window.encodeURIComponent(textMessage); const studentCode = window.encodeURIComponent(textMessage);
const altStudentCode = window.encodeURIComponent(altTextMessage); const altStudentCode = window.encodeURIComponent(altTextMessage);

View File

@ -85,12 +85,17 @@ export class Project extends Component {
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title, challengeType } challengeNode: { title, challengeType, helpCategory }
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
} = this.props; } = this.props;
updateChallengeMeta({ ...challengeMeta, title, challengeType }); updateChallengeMeta({
...challengeMeta,
title,
challengeType,
helpCategory
});
challengeMounted(challengeMeta.id); challengeMounted(challengeMeta.id);
this._container.focus(); this._container.focus();
} }
@ -104,7 +109,7 @@ export class Project extends Component {
const { const {
challengeMounted, challengeMounted,
data: { data: {
challengeNode: { title: currentTitle, challengeType } challengeNode: { title: currentTitle, challengeType, helpCategory }
}, },
pageContext: { challengeMeta }, pageContext: { challengeMeta },
updateChallengeMeta updateChallengeMeta
@ -113,7 +118,8 @@ export class Project extends Component {
updateChallengeMeta({ updateChallengeMeta({
...challengeMeta, ...challengeMeta,
title: currentTitle, title: currentTitle,
challengeType challengeType,
helpCategory
}); });
challengeMounted(challengeMeta.id); challengeMounted(challengeMeta.id);
} }
@ -305,6 +311,7 @@ export const query = graphql`
title title
description description
challengeType challengeType
helpCategory
fields { fields {
blockName blockName
slug slug

View File

@ -76,7 +76,7 @@ exports.submitTypes = {
}; };
// determine which help forum questions should be posted to // determine which help forum questions should be posted to
exports.helpCategory = { exports.helpCategoryMap = {
'basic-html-and-html5': 'HTML-CSS', 'basic-html-and-html5': 'HTML-CSS',
'basic-css': 'HTML-CSS', 'basic-css': 'HTML-CSS',
'applied-visual-design': 'HTML-CSS', 'applied-visual-design': 'HTML-CSS',
@ -84,7 +84,7 @@ exports.helpCategory = {
'responsive-web-design-principles': 'HTML-CSS', 'responsive-web-design-principles': 'HTML-CSS',
'css-flexbox': 'HTML-CSS', 'css-flexbox': 'HTML-CSS',
'css-grid': 'HTML-CSS', 'css-grid': 'HTML-CSS',
'responsive-web-design-projects': 'Certification Projects', 'responsive-web-design-projects': 'HTML-CSS',
'basic-javascript': 'JavaScript', 'basic-javascript': 'JavaScript',
es6: 'JavaScript', es6: 'JavaScript',
'regular-expressions': 'JavaScript', 'regular-expressions': 'JavaScript',
@ -94,38 +94,37 @@ exports.helpCategory = {
'object-oriented-programming': 'JavaScript', 'object-oriented-programming': 'JavaScript',
'functional-programming': 'JavaScript', 'functional-programming': 'JavaScript',
'intermediate-algorithm-scripting': 'JavaScript', 'intermediate-algorithm-scripting': 'JavaScript',
'javascript-algorithms-and-data-structures-projects': 'javascript-algorithms-and-data-structures-projects': 'JavaScript',
'Certification Projects',
bootstrap: 'HTML-CSS', bootstrap: 'HTML-CSS',
jquery: 'JavaScript', jquery: 'JavaScript',
sass: 'HTML-CSS', sass: 'HTML-CSS',
react: 'JavaScript', react: 'JavaScript',
redux: 'JavaScript', redux: 'JavaScript',
'react-and-redux': 'JavaScript', 'react-and-redux': 'JavaScript',
'front-end-libraries-projects': 'Certification Projects', 'front-end-libraries-projects': 'JavaScript',
'data-visualization-with-d3': 'JavaScript', 'data-visualization-with-d3': 'JavaScript',
'json-apis-and-ajax': 'JavaScript', 'json-apis-and-ajax': 'JavaScript',
'data-visualization-projects': 'Certification Projects', 'data-visualization-projects': 'JavaScript',
'managing-packages-with-npm': 'JavaScript', 'managing-packages-with-npm': 'JavaScript',
'basic-node-and-express': 'JavaScript', 'basic-node-and-express': 'JavaScript',
'mongodb-and-mongoose': 'JavaScript', 'mongodb-and-mongoose': 'JavaScript',
'apis-and-microservices-projects': 'Certification Projects', 'apis-and-microservices-projects': 'JavaScript',
'information-security-with-helmetjs': 'JavaScript', 'information-security-with-helmetjs': 'JavaScript',
'quality-assurance-and-testing-with-chai': 'JavaScript', 'quality-assurance-and-testing-with-chai': 'JavaScript',
'advanced-node-and-express': 'JavaScript', 'advanced-node-and-express': 'JavaScript',
'quality-assurance-projects': 'Certification Projects', 'quality-assurance-projects': 'JavaScript',
'information-security-projects': 'Certification Projects', 'information-security-projects': 'JavaScript',
algorithms: 'JavaScript', algorithms: 'JavaScript',
'data-structures': 'JavaScript', 'data-structures': 'JavaScript',
'take-home-projects': 'Certification Projects', 'take-home-projects': 'JavaScript',
'rosetta-code': 'JavaScript', 'rosetta-code': 'JavaScript',
'project-euler': 'JavaScript', 'project-euler': 'JavaScript',
'scientific-computing-with-python': 'Python', 'scientific-computing-with-python': 'Python',
'scientific-computing-with-python-projects': 'Certification Projects', 'scientific-computing-with-python-projects': 'Python',
'data-analysis-with-python': 'Python', 'data-analysis-with-python': 'Python',
'data-analysis-with-python-projects': 'Certification Projects', 'data-analysis-with-python-projects': 'Python',
'machine-learning-with-python': 'Python', 'machine-learning-with-python': 'Python',
'machine-learning-with-python-projects': 'Certification Projects', 'machine-learning-with-python-projects': 'Python',
'python-for-everybody': 'Python', 'python-for-everybody': 'Python',
tensorflow: 'Python', tensorflow: 'Python',
'how-neural-networks-work': 'Python', 'how-neural-networks-work': 'Python',

View File

@ -2,6 +2,7 @@
id: 5e46f979ac417301a38fb932 id: 5e46f979ac417301a38fb932
title: Port Scanner title: Port Scanner
challengeType: 10 challengeType: 10
helpCategory: Python
--- ---
## Description ## Description

View File

@ -2,6 +2,7 @@
id: 5e46f983ac417301a38fb933 id: 5e46f983ac417301a38fb933
title: SHA-1 Password Cracker title: SHA-1 Password Cracker
challengeType: 10 challengeType: 10
helpCategory: Python
--- ---
## Description ## Description

View File

@ -16,6 +16,7 @@ const { dasherize, nameify } = require('../utils/slugs');
const { createPoly } = require('../utils/polyvinyl'); const { createPoly } = require('../utils/polyvinyl');
const { blockNameify } = require('../utils/block-nameify'); const { blockNameify } = require('../utils/block-nameify');
const { supportedLangs } = require('./utils'); const { supportedLangs } = require('./utils');
const { helpCategoryMap } = require('../client/utils/challengeTypes');
const access = util.promisify(fs.access); const access = util.promisify(fs.access);
@ -275,6 +276,8 @@ ${getFullPath('english')}
challenge.required = required.concat(challenge.required || []); challenge.required = required.concat(challenge.required || []);
challenge.template = template; challenge.template = template;
challenge.time = time; challenge.time = time;
challenge.helpCategory =
challenge.helpCategory || helpCategoryMap[dasherize(blockName)];
return prepareChallenge(challenge); return prepareChallenge(challenge);
}; };

View File

@ -17,89 +17,92 @@ const fileJoi = Joi.object().keys({
history: [Joi.array().items(Joi.string().allow('')), Joi.string().allow('')] history: [Joi.array().items(Joi.string().allow('')), Joi.string().allow('')]
}); });
const schema = Joi.object().keys({ const schema = Joi.object()
block: Joi.string(), .keys({
blockId: Joi.objectId(), block: Joi.string(),
challengeOrder: Joi.number(), blockId: Joi.objectId(),
challengeType: Joi.number() challengeOrder: Joi.number(),
.min(0) challengeType: Joi.number()
.max(11) .min(0)
.required(), .max(11)
checksum: Joi.number(),
dashedName: Joi.string(),
description: Joi.when('challengeType', {
is: Joi.only([challengeTypes.step, challengeTypes.video]),
then: Joi.string().allow(''),
otherwise: Joi.string().required()
}),
fileName: Joi.string(),
files: Joi.object().keys({
indexcss: fileJoi,
indexhtml: fileJoi,
indexjs: fileJoi,
indexjsx: fileJoi
}),
guideUrl: Joi.string().uri({ scheme: 'https' }),
videoUrl: Joi.string().allow(''),
forumTopicId: Joi.number(),
helpRoom: Joi.string(),
id: Joi.objectId().required(),
instructions: Joi.string().allow(''),
isComingSoon: Joi.bool(),
isLocked: Joi.bool(),
isPrivate: Joi.bool(),
name: Joi.string(),
order: Joi.number(),
// video challenges only:
videoId: Joi.when('challengeType', {
is: challengeTypes.video,
then: Joi.string().required()
}),
question: Joi.object().keys({
text: Joi.string().required(),
answers: Joi.array()
.items(Joi.string())
.required(), .required(),
solution: Joi.number().required() checksum: Joi.number(),
}), dashedName: Joi.string(),
required: Joi.array().items( description: Joi.when('challengeType', {
Joi.object().keys({ is: Joi.only([challengeTypes.step, challengeTypes.video]),
link: Joi.string(), then: Joi.string().allow(''),
raw: Joi.bool(), otherwise: Joi.string().required()
src: Joi.string(), }),
crossDomain: Joi.bool() fileName: Joi.string(),
}) files: Joi.object().keys({
),
solutions: Joi.array().items(
Joi.object().keys({
indexcss: fileJoi, indexcss: fileJoi,
indexhtml: fileJoi, indexhtml: fileJoi,
indexjs: fileJoi, indexjs: fileJoi,
indexjsx: fileJoi, indexjsx: fileJoi
indexpy: fileJoi
})
),
superBlock: Joi.string(),
superOrder: Joi.number(),
suborder: Joi.number(),
tests: Joi.array().items(
// public challenges
Joi.object().keys({
text: Joi.string().required(),
testString: Joi.string()
.allow('')
.required()
}), }),
// our tests used in certification verification guideUrl: Joi.string().uri({ scheme: 'https' }),
Joi.object().keys({ helpCategory: Joi.only(['JavaScript', 'HTML-CSS', 'Python']),
id: Joi.string().required(), videoUrl: Joi.string().allow(''),
title: Joi.string().required() forumTopicId: Joi.number(),
}) helpRoom: Joi.string(),
), id: Joi.objectId().required(),
template: Joi.string().allow(''), instructions: Joi.string().allow(''),
time: Joi.string().allow(''), isComingSoon: Joi.bool(),
title: Joi.string().required() isLocked: Joi.bool(),
}); isPrivate: Joi.bool(),
name: Joi.string(),
order: Joi.number(),
// video challenges only:
videoId: Joi.when('challengeType', {
is: challengeTypes.video,
then: Joi.string().required()
}),
question: Joi.object().keys({
text: Joi.string().required(),
answers: Joi.array()
.items(Joi.string())
.required(),
solution: Joi.number().required()
}),
required: Joi.array().items(
Joi.object().keys({
link: Joi.string(),
raw: Joi.bool(),
src: Joi.string(),
crossDomain: Joi.bool()
})
),
solutions: Joi.array().items(
Joi.object().keys({
indexcss: fileJoi,
indexhtml: fileJoi,
indexjs: fileJoi,
indexjsx: fileJoi,
indexpy: fileJoi
})
),
superBlock: Joi.string(),
superOrder: Joi.number(),
suborder: Joi.number(),
tests: Joi.array().items(
// public challenges
Joi.object().keys({
text: Joi.string().required(),
testString: Joi.string()
.allow('')
.required()
}),
// our tests used in certification verification
Joi.object().keys({
id: Joi.string().required(),
title: Joi.string().required()
})
),
template: Joi.string().allow(''),
time: Joi.string().allow(''),
title: Joi.string().required()
})
.xor('helpCategory', 'isPrivate');
exports.challengeSchemaValidator = () => { exports.challengeSchemaValidator = () => {
return challenge => Joi.validate(challenge, schema); return challenge => Joi.validate(challenge, schema);

View File

@ -42,10 +42,7 @@ const {
const MongoIds = require('./utils/mongoIds'); const MongoIds = require('./utils/mongoIds');
const ChallengeTitles = require('./utils/challengeTitles'); const ChallengeTitles = require('./utils/challengeTitles');
const { challengeSchemaValidator } = require('../schema/challengeSchema'); const { challengeSchemaValidator } = require('../schema/challengeSchema');
const { const { challengeTypes } = require('../../client/utils/challengeTypes');
challengeTypes,
helpCategory
} = require('../../client/utils/challengeTypes');
const { dasherize } = require('../../utils/slugs'); const { dasherize } = require('../../utils/slugs');
const { toSortedArray } = require('../../utils/sort-files'); const { toSortedArray } = require('../../utils/sort-files');
@ -249,15 +246,6 @@ async function getChallenges(lang) {
return sortChallenges(challenges); return sortChallenges(challenges);
} }
function validateBlock(challenge) {
const dashedBlock = dasherize(challenge.block);
if (!helpCategory.hasOwnProperty(dashedBlock)) {
return `'${dashedBlock}' block not found as a helpCategory in client/utils/challengeTypes.js file for the '${challenge.title}' challenge`;
} else {
return null;
}
}
function populateTestsForLang({ lang, challenges, meta }) { function populateTestsForLang({ lang, challenges, meta }) {
const mongoIds = new MongoIds(); const mongoIds = new MongoIds();
const challengeTitles = new ChallengeTitles(); const challengeTitles = new ChallengeTitles();
@ -285,14 +273,10 @@ function populateTestsForLang({ lang, challenges, meta }) {
it('Common checks', function() { it('Common checks', function() {
const result = validateChallenge(challenge); const result = validateChallenge(challenge);
const invalidBlock = validateBlock(challenge);
if (result.error) { if (result.error) {
throw new AssertionError(result.error); throw new AssertionError(result.error);
} }
if (challenge.challengeType !== 7 && invalidBlock) {
throw new Error(invalidBlock);
}
const { id, title, block, dashedName } = challenge; const { id, title, block, dashedName } = challenge;
const dashedBlock = dasherize(block); const dashedBlock = dasherize(block);
const pathAndTitle = `${dashedBlock}/${dashedName}`; const pathAndTitle = `${dashedBlock}/${dashedName}`;