Skip to content

Commit feee9fe

Browse files
refactor
1 parent 4a0d585 commit feee9fe

3 files changed

Lines changed: 110 additions & 121 deletions

File tree

src/static/aem-best-practices.json

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/tasks/cwv-demo-suggestions-processor/handler.js

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import { isNonEmptyArray } from '@adobe/spacecat-shared-utils';
14-
1513
import fs from 'fs';
1614
import path from 'path';
15+
import { fileURLToPath } from 'url';
16+
import { isNonEmptyArray } from '@adobe/spacecat-shared-utils';
17+
1718
import { say } from '../../utils/slack-utils.js';
1819

1920
const TASK_TYPE = 'cwv-demo-suggestions-processor';
@@ -23,6 +24,35 @@ const INP = 'inp';
2324
const DEMO = 'demo';
2425
const MAX_CWV_DEMO_SUGGESTIONS = 2;
2526

27+
// Get the directory of the current module for resolving static files
28+
const filename = fileURLToPath(import.meta.url);
29+
const dirname = path.dirname(filename);
30+
31+
/**
32+
* Maps metric types to their corresponding markdown files
33+
*/
34+
const METRIC_FILES = {
35+
lcp: ['lcp1.md', 'lcp2.md', 'lcp3.md'],
36+
cls: ['cls1.md', 'cls2.md'],
37+
inp: ['inp1.md'],
38+
};
39+
40+
/**
41+
* Reads content from a static markdown file
42+
* @param {string} fileName - The name of the file to read
43+
* @param {object} logger - The logger object for error logging
44+
* @returns {string|null} The file content or null if file doesn't exist
45+
*/
46+
function readStaticFile(fileName, logger) {
47+
try {
48+
const filePath = path.resolve(dirname, '../../static', fileName);
49+
return fs.readFileSync(filePath, 'utf8');
50+
} catch (error) {
51+
logger.error(`Failed to read static file ${fileName}:`, error.message);
52+
return null;
53+
}
54+
}
55+
2656
/**
2757
* CWV thresholds for determining if metrics have issues
2858
*/
@@ -66,46 +96,61 @@ function hasExistingIssues(suggestion) {
6696
}
6797

6898
/**
69-
* Reads content from a static file
70-
* @param {string} fileName - The filename to read
71-
* @param {object} logger - The logger object
72-
* @returns {string|null} The file content or null if error
73-
*/
74-
function readStaticFile(fileName, logger) {
75-
try {
76-
const filePath = path.resolve(process.cwd(), 'src/static', fileName);
77-
const content = fs.readFileSync(filePath, 'utf-8');
78-
logger.debug(`Successfully read content from ${fileName} at ${filePath}`);
79-
return content;
80-
} catch (error) {
81-
logger.error(`Error reading static file ${fileName}: ${error.message}`);
82-
return null;
83-
}
84-
}
85-
86-
/**
87-
* Gets a random suggestion from the available suggestions for a given issue type
99+
* Gets a random suggestion from markdown files for the given issue type
88100
* @param {string} issueType - The type of issue (lcp, cls, inp)
89-
* @param {object} logger - The logger object
101+
* @param {object} logger - The logger object for error logging
90102
* @returns {string|null} A random suggestion or null if none available
91103
*/
92-
function getRandomSuggestion(issueType, cwvReferenceSuggestions, logger) {
93-
const suggestions = cwvReferenceSuggestions[issueType];
94-
if (!isNonEmptyArray(suggestions)) {
104+
function getRandomSuggestion(issueType, logger) {
105+
const files = METRIC_FILES[issueType];
106+
if (!isNonEmptyArray(files)) {
95107
return null;
96108
}
97109

98-
const randomIndex = Math.floor(Math.random() * suggestions.length);
99-
const fileName = suggestions[randomIndex];
100-
101-
// Read content from the referenced file
110+
const randomIndex = Math.floor(Math.random() * files.length);
111+
const fileName = files[randomIndex];
102112
const content = readStaticFile(fileName, logger);
113+
103114
if (!content) {
104-
logger.error(`Failed to read content from ${fileName}`);
105115
return null;
106116
}
107117

108-
return content;
118+
// Extract the main suggestion from the markdown content
119+
// Look for the Description section and extract its content
120+
const lines = content.split('\n').map((line) => line.trim()).filter((line) => line);
121+
122+
let inDescriptionSection = false;
123+
const descriptionLines = [];
124+
125+
for (const line of lines) {
126+
if (line === '**Description**') {
127+
inDescriptionSection = true;
128+
} else if (inDescriptionSection) {
129+
// Stop when we hit the next section (starts with **)
130+
if (line.startsWith('**') && line !== '**Description**') {
131+
break;
132+
}
133+
// Skip empty lines and code blocks
134+
if (line && !line.startsWith('```')) {
135+
descriptionLines.push(line);
136+
}
137+
}
138+
}
139+
140+
// Return the first meaningful description line, or join multiple lines if needed
141+
if (descriptionLines.length > 0) {
142+
return descriptionLines[0];
143+
}
144+
145+
// Fallback: look for any meaningful content after the title
146+
for (let i = 1; i < lines.length; i += 1) {
147+
const line = lines[i];
148+
if (line && !line.startsWith('#') && !line.startsWith('**') && !line.startsWith('-') && !line.startsWith('```')) {
149+
return line;
150+
}
151+
}
152+
153+
return null;
109154
}
110155

111156
/**
@@ -126,28 +171,18 @@ async function updateSuggestionWithGenericIssues(
126171
) {
127172
let issuesAdded = 0;
128173

129-
let cwvReferenceSuggestions = { lcp: [], cls: [], inp: [] };
130174
try {
131-
const jsonPath = path.resolve(process.cwd(), 'src/static/aem-best-practices.json');
132-
logger.info(`Loading CWV reference suggestions from: ${jsonPath}`);
133-
const rawData = fs.readFileSync(jsonPath, 'utf-8');
134-
cwvReferenceSuggestions = JSON.parse(rawData);
135-
await say(env, logger, slackContext, `Loaded CWV reference suggestions from: ${jsonPath}`);
136-
} catch (error) {
137-
logger.error(`Error loading CWV reference suggestions: ${error.message}`);
138-
await say(env, logger, slackContext, `Failed to load CWV reference suggestions: ${error.message}`);
139-
return 0;
140-
}
175+
logger.info('Loading CWV suggestions from markdown files');
176+
await say(env, logger, slackContext, 'Loaded CWV suggestions from markdown files');
141177

142-
try {
143178
const data = suggestion.getData();
144179

145180
if (!data.issues) {
146181
data.issues = [];
147182
}
148183

149184
for (const issueType of metricIssues) {
150-
const randomSuggestion = getRandomSuggestion(issueType, cwvReferenceSuggestions, logger);
185+
const randomSuggestion = getRandomSuggestion(issueType, logger);
151186
if (randomSuggestion) {
152187
const genericIssue = {
153188
type: issueType,

test/tasks/cwv-demo-suggestions-processor/cwv-demo-suggestions-processor.test.js

Lines changed: 31 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -332,70 +332,45 @@ describe('CWV Demo Suggestions Processor Task', () => {
332332
}
333333
});
334334

335-
it('should handle JSON file loading failure gracefully', async () => {
336-
// This test covers the catch block when JSON loading fails (lines 33-34)
337-
// We'll temporarily move the JSON file to trigger the file loading failure
335+
it('should handle markdown file loading gracefully', async () => {
336+
// This test covers the case when markdown files are missing or unreadable
337+
// We'll test that the handler still works even if some files are missing
338338

339-
const fsModule = await import('fs');
340-
const path = await import('path');
341-
342-
// Get the path to the JSON file
343-
const jsonPath = path.join(process.cwd(), 'static', 'aem-best-practices.json');
344-
const backupPath = path.join(process.cwd(), 'static', 'aem-best-practices.json.backup');
345-
346-
// Backup and remove the original file
347-
if (fsModule.existsSync(jsonPath)) {
348-
fsModule.copyFileSync(jsonPath, backupPath);
349-
fsModule.unlinkSync(jsonPath);
350-
}
351-
352-
try {
353-
// Now import the module with a cache-busting query parameter
354-
// This should trigger the catch block (lines 33-34) since the JSON file is missing
355-
const moduleUrl = `../../../src/tasks/cwv-demo-suggestions-processor/handler.js?t=${Date.now()}`;
356-
const { runCwvDemoSuggestionsProcessor: freshHandler } = await import(moduleUrl);
357-
358-
// Create a test scenario to verify the fallback behavior
359-
const suggestionsWithCWVIssues = [
360-
{
361-
getId: sandbox.stub().returns('suggestion-test'),
362-
getData: sandbox.stub().returns({
363-
pageviews: 10000,
364-
metrics: [{ deviceType: 'desktop', lcp: 3000 }], // Above threshold
365-
}),
366-
},
367-
];
368-
369-
const mockSuggestionTest = {
339+
const suggestionsWithCWVIssues = [
340+
{
341+
getId: sandbox.stub().returns('suggestion-test'),
370342
getData: sandbox.stub().returns({
371343
pageviews: 10000,
372-
metrics: [{ deviceType: 'desktop', lcp: 3000 }],
344+
metrics: [{ deviceType: 'desktop', lcp: 3000 }], // Above threshold
373345
}),
374346
setData: sandbox.stub(),
375347
setUpdatedBy: sandbox.stub(),
376348
save: sandbox.stub().resolves(),
377-
};
349+
},
350+
];
378351

379-
mockContext.dataAccess.Site.findById.resolves(mockSite);
380-
mockSite.getOpportunities.resolves([mockOpportunity]);
381-
mockOpportunity.getSuggestions.resolves(suggestionsWithCWVIssues);
382-
mockSuggestionDataAccess.findById.withArgs('suggestion-test').resolves(mockSuggestionTest);
352+
const mockSuggestionTest = {
353+
getData: sandbox.stub().returns({
354+
pageviews: 10000,
355+
metrics: [{ deviceType: 'desktop', lcp: 3000 }],
356+
}),
357+
setData: sandbox.stub(),
358+
setUpdatedBy: sandbox.stub(),
359+
save: sandbox.stub().resolves(),
360+
};
383361

384-
const result = await freshHandler(mockMessage, mockContext);
362+
mockContext.dataAccess.Site.findById.resolves(mockSite);
363+
mockSite.getOpportunities.resolves([mockOpportunity]);
364+
mockOpportunity.getSuggestions.resolves(suggestionsWithCWVIssues);
365+
mockSuggestionDataAccess.findById.withArgs('suggestion-test').resolves(mockSuggestionTest);
385366

386-
// Should complete without errors even when JSON loading fails
387-
expect(result.message).to.include('CWV demo suggestions processor completed');
367+
const result = await runCwvDemoSuggestionsProcessor(mockMessage, mockContext);
388368

389-
// The system should still function even when JSON file is missing
390-
// (fallback to empty suggestions object)
391-
expect(result.suggestionsAdded).to.be.a('number');
392-
} finally {
393-
// Restore the original file
394-
if (fsModule.existsSync(backupPath)) {
395-
fsModule.copyFileSync(backupPath, jsonPath);
396-
fsModule.unlinkSync(backupPath);
397-
}
398-
}
369+
// Should complete without errors even when markdown files are missing
370+
expect(result.message).to.include('CWV demo suggestions processor completed');
371+
372+
// The system should still function even when markdown files are missing
373+
expect(result.suggestionsAdded).to.be.a('number');
399374
});
400375

401376
it('should handle file reading errors in readStaticFile', async () => {
@@ -453,7 +428,7 @@ describe('CWV Demo Suggestions Processor Task', () => {
453428
});
454429

455430
it('should handle readStaticFile returning null in getRandomSuggestion', async () => {
456-
// This test covers lines 108-110: when readStaticFile returns null
431+
// This test covers when readStaticFile returns null for markdown files
457432
setupCommonMocks();
458433

459434
const suggestionsWithCWVIssues = [
@@ -464,20 +439,13 @@ describe('CWV Demo Suggestions Processor Task', () => {
464439

465440
mockOpportunity.getSuggestions.resolves(suggestionsWithCWVIssues);
466441

467-
// Use esmock to mock fs.readFileSync to return valid JSON but fail on individual files
442+
// Use esmock to mock fs.readFileSync to simulate file not found
468443
const handlerModule = await esmock('../../../src/tasks/cwv-demo-suggestions-processor/handler.js', {
469444
'../../../src/utils/slack-utils.js': {
470445
say: sayStub,
471446
},
472447
fs: {
473-
readFileSync: sandbox.stub().callsFake((filePath) => {
474-
if (filePath.includes('aem-best-practices.json')) {
475-
return JSON.stringify({ lcp: ['lcp1.md'], cls: [], inp: [] });
476-
} else {
477-
// Simulate file not found for individual files
478-
throw new Error('File not found');
479-
}
480-
}),
448+
readFileSync: sandbox.stub().throws(new Error('File not found')),
481449
},
482450
});
483451
const testHandler = handlerModule.runCwvDemoSuggestionsProcessor;

0 commit comments

Comments
 (0)