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
parent
7857dc53f4
commit
e4a9b2988c
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
id: 5e46f979ac417301a38fb932
|
||||
title: Port Scanner
|
||||
challengeType: 10
|
||||
helpCategory: Python
|
||||
---
|
||||
|
||||
## Description
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
id: 5e46f983ac417301a38fb933
|
||||
title: SHA-1 Password Cracker
|
||||
challengeType: 10
|
||||
helpCategory: Python
|
||||
---
|
||||
|
||||
## Description
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}`;
|
||||
|
|
Loading…
Reference in New Issue