refactor(external curricula): simplify get intro logic (#59707)

This commit is contained in:
Huyen Nguyen
2025-04-22 12:39:20 +07:00
committed by GitHub
parent 2a4b45efbd
commit a3941bcd09
2 changed files with 82 additions and 49 deletions
@@ -1,5 +1,5 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs, { readFileSync } from 'fs';
import readdirp from 'readdirp'; import readdirp from 'readdirp';
@@ -8,9 +8,20 @@ import {
superblockSchemaValidator, superblockSchemaValidator,
availableSuperBlocksValidator availableSuperBlocksValidator
} from './external-data-schema'; } from './external-data-schema';
import { orderedSuperBlockInfo } from './build-external-curricula-data'; import {
type Curriculum,
type CurriculumIntros,
type GeneratedCurriculumProps,
orderedSuperBlockInfo
} from './build-external-curricula-data';
const VERSION = 'v1'; const VERSION = 'v1';
const intros = JSON.parse(
readFileSync(
path.resolve(__dirname, '../../../client/i18n/locales/english/intro.json'),
'utf-8'
)
) as CurriculumIntros;
describe('external curriculum data build', () => { describe('external curriculum data build', () => {
const clientStaticPath = path.resolve(__dirname, '../../../client/static'); const clientStaticPath = path.resolve(__dirname, '../../../client/static');
@@ -52,28 +63,69 @@ ${result.error.message}`
} }
}); });
test('the files generated should have the correct schema', async () => { test('the super block files generated should have the correct schema', async () => {
const fileArray = ( const fileArray = (
await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, { await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, {
directoryFilter: ['!challenges'] directoryFilter: ['!challenges'],
fileFilter: entry => {
// The directory contains super block files and other curriculum-related files.
// We're only interested in super block ones.
const superBlocks = Object.values(SuperBlocks);
return superBlocks.includes(entry.basename);
}
}) })
).map(file => file.path); ).map(file => file.path);
fileArray fileArray.forEach(fileInArray => {
.filter(fileInArray => fileInArray !== 'available-superblocks.json') const fileContent = fs.readFileSync(
.forEach(fileInArray => { `${clientStaticPath}/curriculum-data/${VERSION}/${fileInArray}`,
const fileContent = fs.readFileSync( 'utf-8'
`${clientStaticPath}/curriculum-data/${VERSION}/${fileInArray}`, );
'utf-8'
);
const result = validateSuperBlock(JSON.parse(fileContent)); const result = validateSuperBlock(JSON.parse(fileContent));
if (result.error) { if (result.error) {
throw Error(`file: ${fileInArray} throw Error(`file: ${fileInArray}
${result.error.message}`); ${result.error.message}`);
}
});
});
test('super blocks and blocks should have the correct data', async () => {
const superBlockFiles = (
await readdirp.promise(`${clientStaticPath}/curriculum-data/${VERSION}`, {
directoryFilter: ['!challenges'],
fileFilter: entry => {
// The directory contains super block files and other curriculum-related files.
// We're only interested in super block ones.
const superBlocks = Object.values(SuperBlocks);
return superBlocks.includes(entry.basename);
} }
}); })
).map(file => file.path);
superBlockFiles.forEach(file => {
const fileContentJson = fs.readFileSync(
`${clientStaticPath}/curriculum-data/${VERSION}/${file}`,
'utf-8'
);
const fileContent = JSON.parse(
fileContentJson
) as Curriculum<GeneratedCurriculumProps>;
const superBlock = Object.keys(fileContent)[0] as SuperBlocks;
// Randomly pick a block to check its data.
const blocks = Object.keys(fileContent[superBlock].blocks);
const randomBlockIndex = Math.floor(Math.random() * blocks.length);
const randomBlock = blocks[randomBlockIndex];
expect(fileContent[superBlock].intro).toEqual(intros[superBlock].intro);
expect(fileContent[superBlock].blocks[randomBlock].desc).toEqual(
intros[superBlock].blocks[randomBlock].intro
);
});
}); });
test('All public SuperBlocks should be present in the SuperBlock object', () => { test('All public SuperBlocks should be present in the SuperBlock object', () => {
@@ -4,24 +4,26 @@ import { submitTypes } from '../../../shared/config/challenge-types';
import { type ChallengeNode } from '../../../client/src/redux/prop-types'; import { type ChallengeNode } from '../../../client/src/redux/prop-types';
import { SuperBlocks } from '../../../shared/config/curriculum'; import { SuperBlocks } from '../../../shared/config/curriculum';
type Intro = { [keyValue in SuperBlocks]: IntroProps }; export type CurriculumIntros = {
[keyValue in SuperBlocks]: {
title: string;
intro: string[];
blocks: Record<string, { title: string; intro: string[] }>;
};
};
export type Curriculum<T> = { export type Curriculum<T> = {
[keyValue in SuperBlocks]: T extends CurriculumProps [keyValue in SuperBlocks]: T extends CurriculumProps
? CurriculumProps ? CurriculumProps
: GeneratedCurriculumProps; : GeneratedCurriculumProps;
}; };
interface IntroProps extends CurriculumProps {
title: string;
intro: string[];
}
export interface CurriculumProps { export interface CurriculumProps {
intro: string[]; intro: string[];
blocks: Record<string, Block<ChallengeNode['challenge'][]>>; blocks: Record<string, Block<ChallengeNode['challenge'][]>>;
} }
interface GeneratedCurriculumProps { export interface GeneratedCurriculumProps {
intro: string[]; intro: string[];
blocks: Record<string, Block<Record<string, unknown>>>; blocks: Record<string, Block<Record<string, unknown>>>;
} }
@@ -71,6 +73,9 @@ export function buildExtCurriculumData(
__dirname, __dirname,
'../../../client/i18n/locales/english/intro.json' '../../../client/i18n/locales/english/intro.json'
); );
const intros = JSON.parse(
readFileSync(blockIntroPath, 'utf-8')
) as CurriculumIntros;
mkdirSync(dataPath, { recursive: true }); mkdirSync(dataPath, { recursive: true });
@@ -85,7 +90,7 @@ export function buildExtCurriculumData(
writeToFile('available-superblocks', { writeToFile('available-superblocks', {
superblocks: orderedSuperBlockInfo.map(x => ({ superblocks: orderedSuperBlockInfo.map(x => ({
...x, ...x,
title: getSuperBlockTitle(x.dashedName) title: intros[x.dashedName].title
})) }))
}); });
@@ -96,7 +101,7 @@ export function buildExtCurriculumData(
if (blockNames.length === 0) continue; if (blockNames.length === 0) continue;
superBlock[superBlockKey] = <GeneratedCurriculumProps>{}; superBlock[superBlockKey] = <GeneratedCurriculumProps>{};
superBlock[superBlockKey].intro = getSuperBlockDescription(superBlockKey); superBlock[superBlockKey].intro = intros[superBlockKey]['intro'];
superBlock[superBlockKey].blocks = {}; superBlock[superBlockKey].blocks = {};
for (const blockName of blockNames) { for (const blockName of blockNames) {
@@ -105,7 +110,7 @@ export function buildExtCurriculumData(
>{}; >{};
superBlock[superBlockKey]['blocks'][blockName]['desc'] = superBlock[superBlockKey]['blocks'][blockName]['desc'] =
getBlockDescription(superBlockKey, blockName); intros[superBlockKey]['blocks'][blockName]['intro'];
superBlock[superBlockKey]['blocks'][blockName]['challenges'] = superBlock[superBlockKey]['blocks'][blockName]['challenges'] =
curriculum[superBlockKey]['blocks'][blockName]['meta']; curriculum[superBlockKey]['blocks'][blockName]['meta'];
@@ -131,30 +136,6 @@ export function buildExtCurriculumData(
writeFileSync(filePath, JSON.stringify(data, null, 2)); writeFileSync(filePath, JSON.stringify(data, null, 2));
} }
function getBlockDescription(
superBlockKeys: SuperBlocks,
blockKey: string
): string[] {
const intros = JSON.parse(readFileSync(blockIntroPath, 'utf-8')) as Intro;
return intros[superBlockKeys]['blocks'][blockKey]['intro'];
}
function getSuperBlockDescription(superBlockKey: SuperBlocks): string[] {
const superBlockIntro = JSON.parse(
readFileSync(blockIntroPath, 'utf-8')
) as Intro;
return superBlockIntro[superBlockKey]['intro'];
}
function getSuperBlockTitle(superBlock: SuperBlocks): string {
const superBlocks = JSON.parse(
readFileSync(blockIntroPath, 'utf-8')
) as Intro;
return superBlocks[superBlock].title;
}
function getSubmitTypes() { function getSubmitTypes() {
writeFileSync( writeFileSync(
`${dataPath}/submit-types.json`, `${dataPath}/submit-types.json`,