Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ export interface PageIntent extends BaseModel {
getUrl(): string;
getPageIntent(): string;
getTopic(): string;
getAnalysisStatus(): string | null;
getAnalysisAttempts(): number | null;
getLastAnalysisAt(): string | null;
getAnalysisError(): { code: string; message: string; details?: any } | null;

setSiteId(siteId: string): PageIntent;
setUrl(url: string): PageIntent;
setPageIntent(pageIntent: string): PageIntent;
setTopic(topic: string): PageIntent;
setAnalysisStatus(status: string): PageIntent;
setAnalysisAttempts(attempts: number): PageIntent;
setLastAnalysisAt(timestamp: string): PageIntent;
setAnalysisError(error: { code: string; message: string; details?: any }): PageIntent;
}

export interface PageIntentCollection extends BaseCollection<PageIntent> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ class PageIntent extends BaseModel {
COMMERCIAL: 'COMMERCIAL',
};

static ANALYSIS_STATUS = {
SUCCESS: 'SUCCESS',
FAILED: 'FAILED',
};

// add any custom methods or overrides here
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
* governing permissions and limitations under the License.
*/

import { isValidUrl } from '@adobe/spacecat-shared-utils';
import {
isValidUrl, isObject, isInteger, isIsoDate,
} from '@adobe/spacecat-shared-utils';

import SchemaBuilder from '../base/schema.builder.js';
import PageIntent from './page-intent.model.js';
Expand Down Expand Up @@ -51,6 +53,33 @@ const schema = new SchemaBuilder(PageIntent, PageIntentCollection)
default: PageIntent.DEFAULT_UPDATED_BY,
})

// analysis tracking fields
.addAttribute('analysisStatus', {
type: Object.values(PageIntent.ANALYSIS_STATUS),
required: false,
})
.addAttribute('analysisAttempts', {
type: 'number',
required: false,
default: 0,
validate: (value) => !value || isInteger(value),
})
.addAttribute('lastAnalysisAt', {
type: 'string',
required: false,
validate: (value) => !value || isIsoDate(value),
})
.addAttribute('analysisError', {
type: 'map',
required: false,
properties: {
code: { type: 'string' },
message: { type: 'string' },
details: { type: 'any' },
},
validate: (value) => !value || (isObject(value) && value.code && value.message),
})

// allow fetching the single record by its URL
.addIndex(
{ composite: ['url'] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,27 @@ const pageIntents = [
url: 'https://example0.com/page0',
pageIntent: 'INFORMATIONAL',
topic: 'firefly',
analysisStatus: 'SUCCESS',
analysisAttempts: 1,
lastAnalysisAt: '2025-11-07T10:00:00.000Z',
},
{
pageIntentId: 'e61a9beb-d3ec-4d53-8652-1b6b43127b3e',
siteId: '78fec9c7-2141-4600-b7b1-ea5c78752b91',
url: 'https://example1.com/page1',
pageIntent: 'NAVIGATIONAL',
topic: 'photoshop',
analysisStatus: 'FAILED',
analysisAttempts: 3,
lastAnalysisAt: '2025-11-07T11:30:00.000Z',
analysisError: {
code: 'INVALID_RESPONSE',
message: 'AI model returned invalid page intent format',
details: {
rawResponse: 'UNKNOWN_INTENT',
attemptedAt: '2025-11-07T11:30:00.000Z',
},
},
},
{
pageIntentId: '36fc2fe4-6fd4-45dd-8cf3-2f1aedf778e3',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,76 @@ describe('PageIntent IT', async () => {
expect(pi.getTopic()).to.equal(updates.topic);
expect(pi.getUpdatedBy()).to.equal(updates.updatedBy);
});

it('creates a page intent with successful analysis tracking', async () => {
const data = {
url: 'https://www.example.com/success-page',
siteId: '1c86ba81-f3cc-48d8-8b06-1f9ac958e72d',
pageIntent: 'COMMERCIAL',
topic: 'success-topic',
analysisStatus: 'SUCCESS',
analysisAttempts: 1,
lastAnalysisAt: '2025-11-07T12:00:00.000Z',
};
const pi = await PageIntent.create(data);

checkPageIntent(pi);

expect(pi.getAnalysisStatus()).to.equal('SUCCESS');
expect(pi.getAnalysisAttempts()).to.equal(1);
expect(pi.getLastAnalysisAt()).to.equal('2025-11-07T12:00:00.000Z');
expect(pi.getAnalysisError()).to.be.undefined;
});

it('creates a page intent with failed analysis tracking', async () => {
const data = {
url: 'https://www.example.com/failed-page',
siteId: '1c86ba81-f3cc-48d8-8b06-1f9ac958e72d',
pageIntent: 'INFORMATIONAL',
topic: 'failed-topic',
analysisStatus: 'FAILED',
analysisAttempts: 3,
lastAnalysisAt: '2025-11-07T13:00:00.000Z',
analysisError: {
code: 'TIMEOUT',
message: 'Analysis timed out after 30 seconds',
details: {
attemptedAt: '2025-11-07T13:00:00.000Z',
timeoutMs: 30000,
},
},
};
const pi = await PageIntent.create(data);

checkPageIntent(pi);

expect(pi.getAnalysisStatus()).to.equal('FAILED');
expect(pi.getAnalysisAttempts()).to.equal(3);
expect(pi.getLastAnalysisAt()).to.equal('2025-11-07T13:00:00.000Z');
expect(pi.getAnalysisError()).to.deep.equal(data.analysisError);
});

it('updates analysis tracking fields', async () => {
const sample = sampleData.pageIntents[2];
const pi = await PageIntent.findByUrl(sample.getUrl());

pi.setAnalysisStatus('FAILED');
pi.setAnalysisAttempts(2);
pi.setLastAnalysisAt('2025-11-07T14:00:00.000Z');
pi.setAnalysisError({
code: 'INVALID_FORMAT',
message: 'Response format was invalid',
details: { rawResponse: 'bad data' },
});

await pi.save();

expect(pi.getAnalysisStatus()).to.equal('FAILED');
expect(pi.getAnalysisAttempts()).to.equal(2);
expect(pi.getLastAnalysisAt()).to.equal('2025-11-07T14:00:00.000Z');
expect(pi.getAnalysisError()).to.deep.include({
code: 'INVALID_FORMAT',
message: 'Response format was invalid',
});
});
});
10 changes: 10 additions & 0 deletions packages/spacecat-shared-html-analyzer/xunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<testsuite name="Mocha Tests" tests="8" failures="0" errors="0" skipped="0" timestamp="Fri, 07 Nov 2025 09:29:20 GMT" time="0.096">
<testcase classname="HTML Visibility Analyzer analyzeTextComparison" name="should analyze content differences" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.086"/>
<testcase classname="HTML Visibility Analyzer analyzeTextComparison" name="should handle identical content" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.001"/>
<testcase classname="HTML Visibility Analyzer analyzeTextComparison" name="should handle empty content" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.001"/>
<testcase classname="HTML Visibility Analyzer calculateStats" name="should provide basic comparison statistics" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.001"/>
<testcase classname="HTML Visibility Analyzer calculateBothScenarioStats" name="should provide statistics for both scenarios" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.002"/>
<testcase classname="HTML Visibility Analyzer stripTagsToText" name="should extract text content from HTML" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.001"/>
<testcase classname="HTML Visibility Analyzer stripTagsToText" name="should remove navigation elements when ignoreNavFooter is true" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.001"/>
<testcase classname="HTML Visibility Analyzer stripTagsToText" name="should keep navigation elements when ignoreNavFooter is false" file="/Users/cwisse/Projects/spacecat-shared/packages/spacecat-shared-html-analyzer/test/index.test.js" time="0.001"/>
</testsuite>