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>
pull/40126/head
Oliver Eyton-Williams 2020-10-30 20:10:34 +01:00 committed by GitHub
parent 7857dc53f4
commit e4a9b2988c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 145 additions and 124 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,6 @@ import {
challengeMetaSelector
} from '../redux';
import { tap, mapTo } from 'rxjs/operators';
import { helpCategory } from '../../../../utils/challengeTypes';
import { forumLocation } from '../../../../../config/env.json';
function filesToMarkdown(files = {}) {
@ -29,7 +28,9 @@ function createQuestionEpic(action$, state$, { window }) {
tap(() => {
const state = state$.value;
const files = challengeFilesSelector(state);
const { block, title: challengeTitle } = challengeMetaSelector(state);
const { title: challengeTitle, helpCategory } = challengeMetaSelector(
state
);
const {
navigator: { userAgent },
location: { href }
@ -69,13 +70,13 @@ function createQuestionEpic(action$, state$, { window }) {
\`\`\`
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.
\`\`\`\n${endingText}`
);
const category = window.encodeURIComponent(helpCategory[block] || 'Help');
const category = window.encodeURIComponent(helpCategory || 'Help');
const studentCode = window.encodeURIComponent(textMessage);
const altStudentCode = window.encodeURIComponent(altTextMessage);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,89 +17,92 @@ const fileJoi = Joi.object().keys({
history: [Joi.array().items(Joi.string().allow('')), Joi.string().allow('')]
});
const schema = Joi.object().keys({
block: Joi.string(),
blockId: Joi.objectId(),
challengeOrder: Joi.number(),
challengeType: Joi.number()
.min(0)
.max(11)
.required(),
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())
const schema = Joi.object()
.keys({
block: Joi.string(),
blockId: Joi.objectId(),
challengeOrder: Joi.number(),
challengeType: Joi.number()
.min(0)
.max(11)
.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({
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,
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()
indexjsx: fileJoi
}),
// 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()
});
guideUrl: Joi.string().uri({ scheme: 'https' }),
helpCategory: Joi.only(['JavaScript', 'HTML-CSS', 'Python']),
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(),
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 = () => {
return challenge => Joi.validate(challenge, schema);

View File

@ -42,10 +42,7 @@ const {
const MongoIds = require('./utils/mongoIds');
const ChallengeTitles = require('./utils/challengeTitles');
const { challengeSchemaValidator } = require('../schema/challengeSchema');
const {
challengeTypes,
helpCategory
} = require('../../client/utils/challengeTypes');
const { challengeTypes } = require('../../client/utils/challengeTypes');
const { dasherize } = require('../../utils/slugs');
const { toSortedArray } = require('../../utils/sort-files');
@ -249,15 +246,6 @@ async function getChallenges(lang) {
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 }) {
const mongoIds = new MongoIds();
const challengeTitles = new ChallengeTitles();
@ -285,14 +273,10 @@ function populateTestsForLang({ lang, challenges, meta }) {
it('Common checks', function() {
const result = validateChallenge(challenge);
const invalidBlock = validateBlock(challenge);
if (result.error) {
throw new AssertionError(result.error);
}
if (challenge.challengeType !== 7 && invalidBlock) {
throw new Error(invalidBlock);
}
const { id, title, block, dashedName } = challenge;
const dashedBlock = dasherize(block);
const pathAndTitle = `${dashedBlock}/${dashedName}`;