Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Sma1lboy/codefox into featu…
Browse files Browse the repository at this point in the history
…re-backend-error-handling-strategy
  • Loading branch information
NarwhalChen committed Jan 11, 2025
2 parents 1ff5a5d + f20ec64 commit ebc2daa
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 143 deletions.
49 changes: 0 additions & 49 deletions backend/src/build-system/__tests__/test-database-schemas.spec.ts

This file was deleted.

80 changes: 18 additions & 62 deletions backend/src/build-system/__tests__/test-generate-doc.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
/* eslint-disable no-console */
import { BuilderContext } from 'src/build-system/context';
import { BuildSequence } from '../types';
import * as fs from 'fs';
import * as path from 'path';
import { writeToFile } from './utils';
import { executeBuildSequence } from './utils';

// TODO: adding integration flag
describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
// Generate a unique folder with a timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFolderPath = `./logs/generate-docs-${timestamp}`;
fs.mkdirSync(logFolderPath, { recursive: true });

it('should execute the full sequence and log results to individual files', async () => {
const sequence: BuildSequence = {
id: 'test-sequence',
id: 'test-backend-sequence',
version: '1.0.0',
name: 'Test PRD to UX Sequence',
description: 'Testing PRD to UX sequence execution',
name: 'Spotify-like Music Web',
description: 'Users can play music',
databaseType: 'SQLite',
steps: [
{
id: 'step-1',
Expand All @@ -35,7 +28,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
{
id: 'op:UX:SMD',
name: 'UX Sitemap Document Node',
requires: ['op:PRD'],
},
],
},
Expand All @@ -46,7 +38,6 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
{
id: 'op:UX:SMS',
name: 'UX Sitemap Structure Node',
requires: ['op:UX:SMD'],
},
],
},
Expand All @@ -57,73 +48,38 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
{
id: 'op:UX:DATAMAP:DOC',
name: 'UX Data Map Document node',
requires: ['op:UX:SMD'],
},
],
},
{
id: 'step-5',
name: 'file structure generation',
nodes: [
{
id: 'op:FILE:STRUCT',
name: 'file structure generation',
requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'],
options: {
projectPart: 'frontend',
},
},
],
},
{
id: 'step-6',
name: 'File_Arch Document',
name: 'UX SMD LEVEL 2 Page Details',
nodes: [
{
id: 'op:FILE:ARCH',
name: 'File_Arch',
requires: [
'op:FILE:STRUCT',
//TODO: here use datamap doc rather than datamap struct, we have to change this
'op:UX:DATAMAP:DOC',
],
id: 'op:UX:SMS:LEVEL2',
name: 'UX SMD LEVEL 2 Page Details Node',
},
],
},
],
};

const context = new BuilderContext(sequence, 'test');

// Set input data for context
context.setGlobalContext('projectName', 'spotify like music web');
context.setGlobalContext('description', 'user can play music');
context.setGlobalContext('platform', 'web');

try {
await context.execute();

for (const step of sequence.steps) {
for (const node of step.nodes) {
const resultData = await context.getNodeData(node.id);
console.log(resultData);
if (resultData) {
writeToFile(logFolderPath, node.id, resultData);
}
}
}
const result = await executeBuildSequence(
'test-generate-all-ux-part',
sequence,
);

console.log(
'Sequence completed successfully. Logs stored in:',
logFolderPath,
result.logFolderPath,
);

if (!result.success) {
throw result.error;
}
} catch (error) {
console.error('Error during sequence execution:', error);
fs.writeFileSync(
path.join(logFolderPath, 'error.txt'),
`Error: ${error.message}\n${error.stack}`,
'utf8',
);
throw error;
}
}, 600000);
Expand Down
30 changes: 12 additions & 18 deletions backend/src/build-system/__tests__/test.fullstack-gen.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* eslint-disable no-console */
import { BuilderContext } from 'src/build-system/context';
import { BuildSequence } from '../types';
import * as fs from 'fs';
import * as path from 'path';
import { objectToMarkdown, writeToFile } from './utils';
import { writeToFile } from './utils';
import { BuildMonitor } from '../monitor';
import { BuilderContext } from '../context';

describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas -> Frontend_File_struct -> Frontend_File_arch -> BackendCodeGenerator', () => {
// Generate a unique folder with a timestamp
Expand Down Expand Up @@ -90,6 +89,11 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas ->
projectPart: 'frontend',
},
},
{
id: 'op:UX:SMS:LEVEL2',
name: 'Level 2 UX Sitemap Structure Node details',
requires: ['op:UX:SMS'],
},
],
},
{
Expand Down Expand Up @@ -190,26 +194,16 @@ describe('Sequence: PRD -> UXSD -> UXSS -> UXDD -> DATABASE_REQ -> DBSchemas ->
for (const node of step.nodes) {
const resultData = await context.getNodeData(node.id);
const nodeMetrics = stepMetrics?.nodeMetrics.get(node.id);

if (resultData) {
const content =
typeof resultData === 'object'
? objectToMarkdown(resultData)
: resultData;

writeToFile(logFolderPath, `${node.name}`, content);
writeToFile(logFolderPath, `${node.name}`, resultData);
} else {
console.error(
` Error: Handler ${node.name} failed to produce result data`,
);
writeToFile(
logFolderPath,
`${node.name}-error`,
objectToMarkdown({
error: 'No result data',
metrics: nodeMetrics,
}),
);
writeToFile(logFolderPath, `${node.name}-error`, {
error: 'No result data',
metrics: nodeMetrics,
});
}
}
}
Expand Down
134 changes: 134 additions & 0 deletions backend/src/build-system/__tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Logger } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import { BuildSequence } from '../types';
import { BuilderContext } from '../context';
import { BuildMonitor } from '../monitor';
/**
* Utility function to write content to a file in a clean, formatted manner.
* @param handlerName - The name of the handler.
Expand Down Expand Up @@ -82,3 +85,134 @@ export function objectToMarkdown(obj: any, depth = 1): string {

return markdown;
}

interface TestResult {
success: boolean;
logFolderPath: string;
error?: Error;
metrics?: any;
}

export async function executeBuildSequence(
name: string,
sequence: BuildSequence,
): Promise<TestResult> {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFolderPath = `./logs/${name.toLocaleLowerCase().replaceAll(' ', '-')}-${timestamp}`;
fs.mkdirSync(logFolderPath, { recursive: true });

const context = new BuilderContext(sequence, 'test-env');
const monitor = BuildMonitor.getInstance();

try {
console.time('Total Execution Time');
await context.execute();
console.timeEnd('Total Execution Time');

const monitorReport = monitor.generateTextReport(sequence.id);
fs.writeFileSync(
path.join(logFolderPath, 'execution-metrics.txt'),
monitorReport,
'utf8',
);

const sequenceMetrics = monitor.getSequenceMetrics(sequence.id);
if (sequenceMetrics) {
const metricsJson = {
totalDuration: `${sequenceMetrics.duration}ms`,
successRate: `${sequenceMetrics.successRate.toFixed(2)}%`,
totalSteps: sequenceMetrics.totalSteps,
completedSteps: sequenceMetrics.completedSteps,
failedSteps: sequenceMetrics.failedSteps,
totalNodes: sequenceMetrics.totalNodes,
startTime: new Date(sequenceMetrics.startTime).toISOString(),
endTime: new Date(sequenceMetrics.endTime).toISOString(),
};

fs.writeFileSync(
path.join(logFolderPath, 'metrics.json'),
JSON.stringify(metricsJson, null, 2),
'utf8',
);

console.log('\nSequence Metrics:');
console.table(metricsJson);
}

for (const step of sequence.steps) {
const stepMetrics = sequenceMetrics?.stepMetrics.get(step.id);
for (const node of step.nodes) {
const resultData = await context.getNodeData(node.id);
const nodeMetrics = stepMetrics?.nodeMetrics.get(node.id);

if (resultData) {
const content =
typeof resultData === 'object'
? objectToMarkdown(resultData)
: resultData;
writeToFile(logFolderPath, `${node.name}`, content);
} else {
console.error(
`Error: Handler ${node.name} failed to produce result data`,
);
writeToFile(
logFolderPath,
`${node.name}-error`,
objectToMarkdown({
error: 'No result data',
metrics: nodeMetrics,
}),
);
}
}
}

const summary = {
timestamp: new Date().toISOString(),
sequenceId: sequence.id,
sequenceName: sequence.name,
totalExecutionTime: `${sequenceMetrics?.duration}ms`,
successRate: `${sequenceMetrics?.successRate.toFixed(2)}%`,
nodesExecuted: sequenceMetrics?.totalNodes,
completedNodes: sequenceMetrics?.stepMetrics.size,
logFolder: logFolderPath,
};

fs.writeFileSync(
path.join(logFolderPath, 'execution-summary.json'),
JSON.stringify(summary, null, 2),
'utf8',
);

return {
success: true,
logFolderPath,
metrics: sequenceMetrics,
};
} catch (error) {
const errorReport = {
error: {
message: error.message,
stack: error.stack,
},
metrics: monitor.getSequenceMetrics(sequence.id),
timestamp: new Date().toISOString(),
};

fs.writeFileSync(
path.join(logFolderPath, 'error-with-metrics.json'),
JSON.stringify(errorReport, null, 2),
'utf8',
);

console.error('\nError during sequence execution:');
console.error(error);

return {
success: false,
logFolderPath,
error: error as Error,
metrics: monitor.getSequenceMetrics(sequence.id),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class FileArchGenerateHandler implements BuildHandler<string> {
}

const prompt = generateFileArchPrompt(
JSON.stringify(fileStructure, null, 2),
JSON.stringify(fileStructure.jsonFileStructure, null, 2),
JSON.stringify(datamapDoc, null, 2),
);

Expand Down
Loading

0 comments on commit ebc2daa

Please sign in to comment.