Skip to content
Merged
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
40 changes: 20 additions & 20 deletions src/tasks/opportunity-status-processor/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,25 @@ async function isRUMAvailable(domain, context) {
}

/**
* Checks if AHREFSImport data is available by checking if top pages exist for the site
* Checks if SEO import data is available by checking if top pages exist for the site
* @param {string} siteId - The site ID to check
* @param {object} dataAccess - The data access object
* @param {object} context - The context object with log
* @returns {Promise<boolean>} True if AHREFS Import data is available, false otherwise
* @returns {Promise<boolean>} True if SEO import data is available, false otherwise
*/
async function isAHREFSImportDataAvailable(siteId, dataAccess, context) {
async function isSEOImportDataAvailable(siteId, dataAccess, context) {
const { log } = context;
const { SiteTopPage } = dataAccess;

try {
const topPages = await SiteTopPage.allBySiteIdAndSourceAndGeo(siteId, 'ahrefs', 'global');
const topPages = await SiteTopPage.allBySiteIdAndSourceAndGeo(siteId, 'seo', 'global');

const hasData = topPages && topPages.length > 0;
log.info(`AHREFS Import data availability for site ${siteId}: ${hasData ? 'Available' : 'Not available'} (${topPages?.length || 0} top pages)`);
log.info(`SEO Import data availability for site ${siteId}: ${hasData ? 'Available' : 'Not available'} (${topPages?.length || 0} top pages)`);

return hasData;
} catch (error) {
log.error(`Error checking AHREFS Import data availability for site ${siteId}: ${error.message}`);
log.error(`Error checking SEO Import data availability for site ${siteId}: ${error.message}`);
return false;
}
}
Expand Down Expand Up @@ -304,8 +304,8 @@ async function analyzeMissingOpportunities(
for (const dep of dependencies) {
if (dep === 'RUM' && !serviceStatus.rum) {
unmetDeps.push('RUM');
} else if (dep === 'AHREFSImport' && !serviceStatus.ahrefsImport) {
unmetDeps.push('AHREFS Import');
} else if (dep === 'SEOImport' && !serviceStatus.seoImport) {
unmetDeps.push('SEO Import');
} else if (dep === 'scraping' && !serviceStatus.scraping) {
unmetDeps.push('Scraping');
}
Expand Down Expand Up @@ -376,7 +376,7 @@ export async function runOpportunityStatusProcessor(message, context) {

// Check data source availability and service preconditions
let rumAvailable = false;
let ahrefsImportAvailable = false;
let seoImportAvailable = false;
let gscConfigured = false;
let scrapingAvailable = false;

Expand Down Expand Up @@ -406,7 +406,7 @@ export async function runOpportunityStatusProcessor(message, context) {
});

const needsRUM = requiredDependencies.has('RUM');
const needsAHREFSImport = requiredDependencies.has('AHREFSImport');
const needsSEOImport = requiredDependencies.has('SEOImport');
const needsScraping = requiredDependencies.has('scraping');
const needsGSC = requiredDependencies.has('GSC');

Expand Down Expand Up @@ -506,14 +506,14 @@ export async function runOpportunityStatusProcessor(message, context) {
}
}

if (needsAHREFSImport) {
ahrefsImportAvailable = await isAHREFSImportDataAvailable(siteId, dataAccess, context);
if (needsSEOImport) {
seoImportAvailable = await isSEOImportDataAvailable(siteId, dataAccess, context);
}

// Determine service status for dependency checking
const serviceStatus = {
rum: rumAvailable,
ahrefsImport: ahrefsImportAvailable,
seoImport: seoImportAvailable,
gsc: gscConfigured,
scraping: scrapingAvailable,
};
Expand Down Expand Up @@ -549,12 +549,12 @@ export async function runOpportunityStatusProcessor(message, context) {

// Data source and service precondition status
const rumStatus = rumAvailable ? ':white_check_mark:' : ':x:';
const ahrefsImportStatus = ahrefsImportAvailable ? ':white_check_mark:' : ':x:';
const seoImportStatus = seoImportAvailable ? ':white_check_mark:' : ':x:';
const gscStatus = gscConfigured ? ':white_check_mark:' : ':x:';
const scrapingStatus = scrapingAvailable ? ':white_check_mark:' : ':x:';

statusMessages.push(`RUM ${rumStatus}`);
statusMessages.push(`AHREFS Import ${ahrefsImportStatus}`);
statusMessages.push(`SEO Import ${seoImportStatus}`);
statusMessages.push(`GSC ${gscStatus}`);
statusMessages.push(`Scraping ${scrapingStatus}`);

Expand Down Expand Up @@ -614,8 +614,8 @@ export async function runOpportunityStatusProcessor(message, context) {
if (needsRUM) {
dataSourceMessages.push(`RUM ${rumAvailable ? ':white_check_mark:' : ':x:'}`);
}
if (needsAHREFSImport) {
dataSourceMessages.push(`AHREFS Import ${ahrefsImportAvailable ? ':white_check_mark:' : ':x:'}`);
if (needsSEOImport) {
dataSourceMessages.push(`SEO Import ${seoImportAvailable ? ':white_check_mark:' : ':x:'}`);
}
if (needsGSC) {
dataSourceMessages.push(`GSC ${gscConfigured ? ':white_check_mark:' : ':x:'}`);
Expand All @@ -635,7 +635,7 @@ export async function runOpportunityStatusProcessor(message, context) {
await say(env, log, slackContext, `*Opportunity Statuses for site ${siteUrl}*`);
const opportunityMessages = statusMessages.filter(
(msg) => !msg.includes('RUM')
&& !msg.includes('AHREFS Import')
&& !msg.includes('SEO Import')
&& !msg.includes('GSC')
&& !msg.includes('Scraping'),
);
Expand Down Expand Up @@ -690,11 +690,11 @@ export async function runOpportunityStatusProcessor(message, context) {
opportunitiesProcessed: opportunities.length,
dataSources: {
rum: rumAvailable,
ahrefsImport: ahrefsImportAvailable,
seoImport: seoImportAvailable,
gsc: gscConfigured,
},
servicePreconditions: {
import: ahrefsImportAvailable, // Import and AHREFS are the same
import: seoImportAvailable,
scraping: scrapingAvailable,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@

/**
* Maps opportunity types to their required dependencies
* Dependencies can be data sources (RUM, AHREFSImport, GSC, scraping)
* Dependencies can be data sources (RUM, SEOImport, GSC, scraping)
*
* Key: Opportunity type
* Value: Array of required dependencies for this opportunity to be generated
*/
export const OPPORTUNITY_DEPENDENCY_MAP = {
cwv: ['RUM'],
'high-organic-low-ctr': ['RUM'],
'broken-internal-links': ['RUM', 'AHREFSImport'],
'meta-tags': ['AHREFSImport', 'scraping'], // meta-tags audit uses scraping
'broken-backlinks': ['AHREFSImport', 'scraping'], // broken-backlinks audit uses scraping
'alt-text': ['AHREFSImport', 'scraping'], // alt-text audit uses scraping
'broken-internal-links': ['RUM', 'SEOImport'],
'meta-tags': ['SEOImport', 'scraping'], // meta-tags audit uses scraping
'broken-backlinks': ['SEOImport', 'scraping'], // broken-backlinks audit uses scraping
'alt-text': ['SEOImport', 'scraping'], // alt-text audit uses scraping
'form-accessibility': ['RUM', 'scraping'], // forms audit uses scraping
'forms-opportunities': ['RUM', 'scraping'], // forms audit uses scraping
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe('Disable Import Audit Processor', () => {
siteUrl: 'https://example.com',
organizationId: 'test-org-id',
taskContext: {
importTypes: ['ahrefs', 'screaming-frog'],
importTypes: ['seo', 'screaming-frog'],
auditTypes: ['cwv', 'broken-links'],
slackContext: 'test-slack-context',
},
Expand All @@ -113,7 +113,7 @@ describe('Disable Import Audit Processor', () => {
taskType: 'disable-import-audit-processor',
siteId: 'test-site-id',
organizationId: 'test-org-id',
importTypes: ['ahrefs', 'screaming-frog'],
importTypes: ['seo', 'screaming-frog'],
auditTypes: ['cwv', 'broken-links'],
scheduledRun: false,
});
Expand All @@ -123,7 +123,7 @@ describe('Disable Import Audit Processor', () => {
expect(mockSite.getConfig).to.have.been.calledOnce;

// Verify import types were disabled
expect(mockSiteConfig.disableImport).to.have.been.calledWith('ahrefs');
expect(mockSiteConfig.disableImport).to.have.been.calledWith('seo');
expect(mockSiteConfig.disableImport).to.have.been.calledWith('screaming-frog');
expect(mockSiteConfig.disableImport).to.have.callCount(2);

Expand All @@ -149,7 +149,7 @@ describe('Disable Import Audit Processor', () => {
context.env,
context.log,
'test-slack-context',
':broom: *For site: https://example.com: Disabled imports*: ahrefs, screaming-frog *and audits*: cwv, broken-links',
':broom: *For site: https://example.com: Disabled imports*: seo, screaming-frog *and audits*: cwv, broken-links',
);
expect(mockSay.secondCall).to.have.been.calledWith(
context.env,
Expand Down Expand Up @@ -240,7 +240,7 @@ describe('Disable Import Audit Processor', () => {
taskType: 'disable-import-audit-processor',
siteId: 'test-site-id',
organizationId: 'test-org-id',
importTypes: ['ahrefs', 'screaming-frog'],
importTypes: ['seo', 'screaming-frog'],
auditTypes: ['cwv', 'broken-links'],
scheduledRun: true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ describe('Opportunity Dependency Map', () => {
it('should map opportunities to dependencies correctly', () => {
expect(OPPORTUNITY_DEPENDENCY_MAP.cwv).to.deep.equal(['RUM']);
expect(OPPORTUNITY_DEPENDENCY_MAP['high-organic-low-ctr']).to.deep.equal(['RUM']);
expect(OPPORTUNITY_DEPENDENCY_MAP['broken-internal-links']).to.deep.equal(['RUM', 'AHREFSImport']);
expect(OPPORTUNITY_DEPENDENCY_MAP['meta-tags']).to.deep.equal(['AHREFSImport', 'scraping']);
expect(OPPORTUNITY_DEPENDENCY_MAP['broken-backlinks']).to.deep.equal(['AHREFSImport', 'scraping']);
expect(OPPORTUNITY_DEPENDENCY_MAP['alt-text']).to.deep.equal(['AHREFSImport', 'scraping']);
expect(OPPORTUNITY_DEPENDENCY_MAP['broken-internal-links']).to.deep.equal(['RUM', 'SEOImport']);
expect(OPPORTUNITY_DEPENDENCY_MAP['meta-tags']).to.deep.equal(['SEOImport', 'scraping']);
expect(OPPORTUNITY_DEPENDENCY_MAP['broken-backlinks']).to.deep.equal(['SEOImport', 'scraping']);
expect(OPPORTUNITY_DEPENDENCY_MAP['alt-text']).to.deep.equal(['SEOImport', 'scraping']);
expect(OPPORTUNITY_DEPENDENCY_MAP['form-accessibility']).to.deep.equal(['RUM', 'scraping']);
expect(OPPORTUNITY_DEPENDENCY_MAP['forms-opportunities']).to.deep.equal(['RUM', 'scraping']);
});
Expand All @@ -53,7 +53,7 @@ describe('Opportunity Dependency Map', () => {
expect(dependencies).to.be.an('array');
expect(dependencies).to.have.lengthOf(2);
expect(dependencies).to.include('RUM');
expect(dependencies).to.include('AHREFSImport');
expect(dependencies).to.include('SEOImport');
});

it('should return empty array for opportunity with no dependencies', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,10 @@ describe('Opportunity Status Processor', () => {
expect(mockSite.getOpportunities.called).to.be.true;
});

it('should check AHREFS Import data availability', async () => {
// Set audit type that requires AHREFSImport
it('should check SEO Import data availability', async () => {
// Set audit type that requires SEOImport
message.taskContext.auditTypes = ['meta-tags'];
// Mock AHREFSImport data available
// Mock SEOImport data available
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves([
{ url: 'https://example.com/page1', traffic: 100 },
{ url: 'https://example.com/page2', traffic: 50 },
Expand All @@ -319,14 +319,14 @@ describe('Opportunity Status Processor', () => {

await runOpportunityStatusProcessor(message, context);

expect(context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo.calledWith('test-site-id', 'ahrefs', 'global')).to.be.true;
expect(context.log.info.calledWith('AHREFS Import data availability for site test-site-id: Available (2 top pages)')).to.be.true;
expect(context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo.calledWith('test-site-id', 'seo', 'global')).to.be.true;
expect(context.log.info.calledWith('SEO Import data availability for site test-site-id: Available (2 top pages)')).to.be.true;
});

it('should handle AHREFSImport data not available', async () => {
// Set audit type that requires AHREFSImport
it('should handle SEOImport data not available', async () => {
// Set audit type that requires SEOImport
message.taskContext.auditTypes = ['meta-tags'];
// Mock AHREFSImport data not available
// Mock SEOImport data not available
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves([]);

const mockOpportunities = [
Expand All @@ -339,13 +339,13 @@ describe('Opportunity Status Processor', () => {

await runOpportunityStatusProcessor(message, context);

expect(context.log.info.calledWith('AHREFS Import data availability for site test-site-id: Not available (0 top pages)')).to.be.true;
expect(context.log.info.calledWith('SEO Import data availability for site test-site-id: Not available (0 top pages)')).to.be.true;
});

it('should handle AHREFSImport check errors', async () => {
// Set audit type that requires AHREFSImport
it('should handle SEOImport check errors', async () => {
// Set audit type that requires SEOImport
message.taskContext.auditTypes = ['meta-tags'];
// Mock AHREFSImport check error
// Mock SEOImport check error
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo.rejects(new Error('Database error'));

const mockOpportunities = [
Expand All @@ -358,7 +358,7 @@ describe('Opportunity Status Processor', () => {

await runOpportunityStatusProcessor(message, context);

expect(context.log.error.calledWith('Error checking AHREFS Import data availability for site test-site-id: Database error')).to.be.true;
expect(context.log.error.calledWith('Error checking SEO Import data availability for site test-site-id: Database error')).to.be.true;
});
});

Expand Down Expand Up @@ -817,12 +817,12 @@ describe('Opportunity Status Processor', () => {
expect(context.log.info.calledWithMatch('Processing opportunities')).to.be.true;
});

it('should detect AHREFSImport failure from runbook', async () => {
it('should detect SEOImport failure from runbook', async () => {
const mockOpportunities = [
{
getType: () => 'seo',
getSuggestions: sinon.stub().resolves([]),
getData: () => ({ runbook: 'AHREFSImport data is required for this analysis' }),
getData: () => ({ runbook: 'SEOImport data is required for this analysis' }),
},
];
mockSite.getOpportunities.resolves(mockOpportunities);
Expand Down Expand Up @@ -1045,35 +1045,35 @@ describe('Opportunity Status Processor', () => {
});
});

describe('Import and AHREFSImport Checks', () => {
it('should handle AHREFSImport check errors gracefully', async () => {
// Set audit type that requires AHREFSImport
describe('Import and SEOImport Checks', () => {
it('should handle SEOImport check errors gracefully', async () => {
// Set audit type that requires SEOImport
message.taskContext.auditTypes = ['meta-tags'];
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo.rejects(new Error('Database error'));

await runOpportunityStatusProcessor(message, context);

expect(context.log.error.calledWithMatch('Error checking AHREFS Import data availability')).to.be.true;
expect(context.log.error.calledWithMatch('Error checking SEO Import data availability')).to.be.true;
});

it('should check AHREFSImport data with specific source and geo parameters', async () => {
// Set audit type that requires AHREFSImport
it('should check SEOImport data with specific source and geo parameters', async () => {
// Set audit type that requires SEOImport
message.taskContext.auditTypes = ['meta-tags'];
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo
.withArgs('test-site-id', 'ahrefs', 'global')
.withArgs('test-site-id', 'seo', 'global')
.resolves([{ url: 'https://example.com/page1' }]);

await runOpportunityStatusProcessor(message, context);

expect(context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo
.calledWith('test-site-id', 'ahrefs', 'global')).to.be.true;
.calledWith('test-site-id', 'seo', 'global')).to.be.true;
});

it('should log AHREFS Import data availability with page count', async () => {
// Set audit type that requires AHREFSImport
it('should log SEO Import data availability with page count', async () => {
// Set audit type that requires SEOImport
message.taskContext.auditTypes = ['meta-tags'];
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo
.withArgs('test-site-id', 'ahrefs', 'global')
.withArgs('test-site-id', 'seo', 'global')
.resolves([
{ url: 'https://example.com/page1' },
{ url: 'https://example.com/page2' },
Expand All @@ -1082,7 +1082,7 @@ describe('Opportunity Status Processor', () => {

await runOpportunityStatusProcessor(message, context);

expect(context.log.info.calledWithMatch('AHREFS Import data availability')).to.be.true;
expect(context.log.info.calledWithMatch('SEO Import data availability')).to.be.true;
expect(context.log.info.calledWithMatch('3 top pages')).to.be.true;
});
});
Expand Down Expand Up @@ -1560,9 +1560,9 @@ describe('Opportunity Status Processor', () => {
};
message.taskContext.auditTypes = ['cwv', 'broken-backlinks'];

// Mock AHREFSImport and Import available
// Mock SEOImport and Import available
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo
.withArgs(message.siteId, 'ahrefs', 'global')
.withArgs(message.siteId, 'seo', 'global')
.resolves([{ url: 'https://example.com/page1' }]);

context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo
Expand All @@ -1578,7 +1578,7 @@ describe('Opportunity Status Processor', () => {
{
getType: () => 'broken-backlinks',
getSuggestions: sinon.stub().resolves([]),
getData: () => ({ runbook: 'AHREFSImport data required' }),
getData: () => ({ runbook: 'SEOImport data required' }),
},
];
mockSite.getOpportunities.resolves(mockOpportunities);
Expand Down Expand Up @@ -1893,13 +1893,13 @@ describe('Opportunity Status Processor', () => {
threadTs: 'test-thread',
};

// Mock import and AHREFSImport as available
// Mock import and SEOImport as available
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo = sinon.stub();
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo
.withArgs(message.siteId)
.resolves([{ url: 'https://example.com/page1' }]);
context.dataAccess.SiteTopPage.allBySiteIdAndSourceAndGeo
.withArgs(message.siteId, 'ahrefs', 'global')
.withArgs(message.siteId, 'seo', 'global')
.resolves([{ url: 'https://example.com/page1', traffic: 1000 }]);

const mockOpportunities = [
Expand All @@ -1912,7 +1912,7 @@ describe('Opportunity Status Processor', () => {

await runOpportunityStatusProcessor(message, context);

// When no siteUrl, RUM/GSC/Scraping are false, but AHREFSImport and Import are true
// When no siteUrl, RUM/GSC/Scraping are false, but SEOImport and Import are true
// This will trigger "Services requiring log analysis" log,
// not "All service preconditions passed"
// The test verifies the function executes without errors
Expand Down
Loading