diff --git a/packages/spacecat-shared-data-access/src/models/page-intent/index.d.ts b/packages/spacecat-shared-data-access/src/models/page-intent/index.d.ts index 6cbcfb4b3..515cbbfee 100644 --- a/packages/spacecat-shared-data-access/src/models/page-intent/index.d.ts +++ b/packages/spacecat-shared-data-access/src/models/page-intent/index.d.ts @@ -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 { diff --git a/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.model.js b/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.model.js index fb61ff251..12c62e071 100644 --- a/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.model.js +++ b/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.model.js @@ -28,6 +28,11 @@ class PageIntent extends BaseModel { COMMERCIAL: 'COMMERCIAL', }; + static ANALYSIS_STATUS = { + SUCCESS: 'SUCCESS', + FAILED: 'FAILED', + }; + // add any custom methods or overrides here } diff --git a/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.schema.js b/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.schema.js index 5986db690..fef383520 100644 --- a/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.schema.js +++ b/packages/spacecat-shared-data-access/src/models/page-intent/page-intent.schema.js @@ -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'; @@ -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'] }, diff --git a/packages/spacecat-shared-data-access/test/fixtures/page-intents.fixture.js b/packages/spacecat-shared-data-access/test/fixtures/page-intents.fixture.js index b4d767814..6deeb8754 100644 --- a/packages/spacecat-shared-data-access/test/fixtures/page-intents.fixture.js +++ b/packages/spacecat-shared-data-access/test/fixtures/page-intents.fixture.js @@ -17,6 +17,9 @@ 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', @@ -24,6 +27,17 @@ const pageIntents = [ 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', diff --git a/packages/spacecat-shared-data-access/test/it/page-intent/page-intent.test.js b/packages/spacecat-shared-data-access/test/it/page-intent/page-intent.test.js index 8d58f8ff7..0073431f5 100644 --- a/packages/spacecat-shared-data-access/test/it/page-intent/page-intent.test.js +++ b/packages/spacecat-shared-data-access/test/it/page-intent/page-intent.test.js @@ -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', + }); + }); }); diff --git a/packages/spacecat-shared-html-analyzer/xunit.xml b/packages/spacecat-shared-html-analyzer/xunit.xml new file mode 100644 index 000000000..81d8ae67c --- /dev/null +++ b/packages/spacecat-shared-html-analyzer/xunit.xml @@ -0,0 +1,10 @@ + + + + + + + + + +