From 68d5c41a4bfef708924672e552d013533250c0d3 Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Fri, 28 Feb 2025 17:49:05 +0100 Subject: [PATCH 01/11] feat: introduce KPI metrics for broken-backlinks --- src/backlinks/handler.js | 20 +++--- src/backlinks/kpi-metrics.js | 72 +++++++++++++++++++ src/backlinks/opportunity-data-mapper.js | 6 +- test/audits/backlinks.test.js | 34 +++++++-- .../auditWithSuggestions.json | 41 +++++++++++ test/fixtures/broken-backlinks/opportunity.js | 6 +- .../broken-backlinks/organic-traffic.js | 29 ++++++++ .../broken-backlinks/rum-traffic.json | 38 ++++++++++ test/fixtures/broken-backlinks/suggestion.js | 45 +++--------- 9 files changed, 239 insertions(+), 52 deletions(-) create mode 100644 src/backlinks/kpi-metrics.js create mode 100644 test/fixtures/broken-backlinks/auditWithSuggestions.json create mode 100644 test/fixtures/broken-backlinks/organic-traffic.js create mode 100644 test/fixtures/broken-backlinks/rum-traffic.json diff --git a/src/backlinks/handler.js b/src/backlinks/handler.js index 66f5c356f..1d9c53eb8 100644 --- a/src/backlinks/handler.js +++ b/src/backlinks/handler.js @@ -20,6 +20,7 @@ import { AuditBuilder } from '../common/audit-builder.js'; import { getScrapedDataForSiteId } from '../support/utils.js'; import { convertToOpportunity } from '../common/opportunity.js'; import { createOpportunityData } from './opportunity-data-mapper.js'; +import calculateKpiDeltasForAudit from './kpi-metrics.js'; const auditType = Audit.AUDIT_TYPES.BROKEN_BACKLINKS; const TIMEOUT = 3000; @@ -235,20 +236,23 @@ export const generateSuggestionData = async (finalUrl, auditData, context, site) }; /** - * Converts audit data to an opportunity and synchronizes suggestions. - * - * @param {string} auditUrl - The URL of the audit. - * @param {Object} auditData - The data from the audit. - * @param {Object} context - The context contains logging and data access utilities. - */ - -export async function opportunityAndSuggestions(auditUrl, auditData, context) { + * Converts audit data to an opportunity and synchronizes suggestions. + * + * @param {string} auditUrl - The URL of the audit. + * @param {Object} auditData - The data from the audit. + * @param {Object} context - The context contains logging and data access utilities. + * @param {Object} site - The site object. + */ + +export async function opportunityAndSuggestions(auditUrl, auditData, context, site) { + const kpiDeltas = await calculateKpiDeltasForAudit(auditData, context, site); const opportunity = await convertToOpportunity( auditUrl, auditData, context, createOpportunityData, auditType, + kpiDeltas, ); const { log } = context; diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js new file mode 100644 index 000000000..82dcc64a3 --- /dev/null +++ b/src/backlinks/kpi-metrics.js @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { getStoredMetrics, isNonEmptyArray, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; + +const calculateKpiMetrics = async (auditData, context, site) => { + const { log } = context; + const siteId = site.getId(); + const rumTrafficData = await getStoredMetrics( + { source: 'rum', metric: 'rum-traffic', siteId }, + context, + ); + + if (!isNonEmptyObject(rumTrafficData)) { + log.info(`No RUM traffic data found for site ${siteId}`); + return null; + } + + const organicTrafficData = await getStoredMetrics( + { source: 'ahrefs', metric: 'organic-traffic', siteId }, + context, + ); + + if (!isNonEmptyArray(organicTrafficData)) { + log.info(`No organic traffic data found for site ${siteId}`); + return null; + } + + const latestOrganicTrafficData = organicTrafficData.sort( + (a, b) => new Date(b.time) - new Date(a.time), + )[0]; + const CPC = latestOrganicTrafficData.cost / latestOrganicTrafficData.value; + + const projectedTrafficLost = auditData.auditResult.brokenBacklinks.reduce((sum, backlink) => { + const { traffic_domain: referringTraffic, urlsSuggested } = backlink; + + let trafficBand; + if (referringTraffic > 25000000) { + trafficBand = 0.03; + } else if (referringTraffic > 10000000) { + trafficBand = 0.02; + } else if (referringTraffic > 1000000) { + trafficBand = 0.01; + } else if (referringTraffic > 500000) { + trafficBand = 0.0075; + } else if (referringTraffic > 10000) { + trafficBand = 0.005; + } else { + trafficBand = 0.001; + } + const proposedTargetTraffic = rumTrafficData[urlsSuggested[0]]?.earned ?? 0; + return sum + (proposedTargetTraffic * trafficBand); + }, 0); + + const projectedTrafficValue = projectedTrafficLost * CPC; + + return { + projectedTrafficLost, + projectedTrafficValue, + }; +}; + +export default calculateKpiMetrics; diff --git a/src/backlinks/opportunity-data-mapper.js b/src/backlinks/opportunity-data-mapper.js index ba56109a7..b625ad8e3 100644 --- a/src/backlinks/opportunity-data-mapper.js +++ b/src/backlinks/opportunity-data-mapper.js @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -export function createOpportunityData() { +export function createOpportunityData(kpiMetrics) { return { runbook: 'https://adobe.sharepoint.com/:w:/r/sites/aemsites-engineering/_layouts/15/doc2.aspx?sourcedoc=%7BAC174971-BA97-44A9-9560-90BE6C7CF789%7D&file=Experience_Success_Studio_Broken_Backlinks_Runbook.docx&action=default&mobileredirect=true', origin: 'AUTOMATION', @@ -26,6 +26,8 @@ export function createOpportunityData() { ], }, tags: ['Traffic acquisition'], - data: null, + data: { + ...kpiMetrics, + }, }; } diff --git a/test/audits/backlinks.test.js b/test/audits/backlinks.test.js index ffb747fcc..f52fe5194 100644 --- a/test/audits/backlinks.test.js +++ b/test/audits/backlinks.test.js @@ -18,6 +18,8 @@ import sinonChai from 'sinon-chai'; import nock from 'nock'; import { FirefallClient } from '@adobe/spacecat-shared-gpt-client'; import auditDataMock from '../fixtures/broken-backlinks/audit.json' with { type: 'json' }; +import auditDataSuggestionsMock from '../fixtures/broken-backlinks/auditWithSuggestions.json' with { type: 'json' }; +import rumTraffic from '../fixtures/broken-backlinks/rum-traffic.json' with { type: 'json' }; import { brokenBacklinksAuditRunner, opportunityAndSuggestions, generateSuggestionData } from '../../src/backlinks/handler.js'; import { MockContextBuilder } from '../shared.js'; import { @@ -39,6 +41,7 @@ import { brokenBacklinksSuggestions, suggestions, } from '../fixtures/broken-backlinks/suggestion.js'; +import { organicTraffic } from '../fixtures/broken-backlinks/organic-traffic.js'; use(sinonChai); use(chaiAsPromised); @@ -66,12 +69,30 @@ describe('Backlinks Tests', function () { AHREFS_API_KEY: 'ahrefs-api', S3_SCRAPER_BUCKET_NAME: 'test-bucket', }, + s3: { + s3Bucket: 'test-bucket', + s3Client: { + send: sandbox.stub(), + }, + }, s3Client: { send: sandbox.stub(), }, }) .build(message); + context.s3.s3Client.send.onCall(0).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), + }, + }); + + context.s3.s3Client.send.onCall(1).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), + }, + }); + nock('https://foo.com') .get('/returns-404') .reply(404); @@ -132,13 +153,18 @@ describe('Backlinks Tests', function () { ahrefsMock(site.getBaseURL(), auditDataMock.auditResult); - await opportunityAndSuggestions(auditUrl, auditDataMock, context); + await opportunityAndSuggestions(auditUrl, auditDataSuggestionsMock, context, site); + + const kpiDeltas = { + projectedTrafficLost: sinon.match.number, + projectedTrafficValue: sinon.match.number, + }; expect(context.dataAccess.Opportunity.create) .to .have .been - .calledOnceWith(opportunityData(auditDataMock.siteId, auditDataMock.id)); + .calledOnceWith(opportunityData(auditDataMock.siteId, auditDataMock.id, kpiDeltas)); expect(brokenBacklinksOpportunity.addSuggestions).to.have.been.calledOnceWith(suggestions); }); @@ -152,7 +178,7 @@ describe('Backlinks Tests', function () { ahrefsMock(site.getBaseURL(), auditDataMock.auditResult); - await opportunityAndSuggestions(auditUrl, auditDataMock, context); + await opportunityAndSuggestions(auditUrl, auditDataSuggestionsMock, context, site); expect(context.dataAccess.Opportunity.create).to.not.have.been.called; expect(brokenBacklinksOpportunity.setAuditId).to.have.been.calledOnceWith(auditDataMock.id); @@ -177,7 +203,7 @@ describe('Backlinks Tests', function () { .reply(200, auditDataMock.auditResult); try { - await opportunityAndSuggestions(auditUrl, auditDataMock, context); + await opportunityAndSuggestions(auditUrl, auditDataSuggestionsMock, context, site); } catch (e) { expect(e.message).to.equal(errorMessage); } diff --git a/test/fixtures/broken-backlinks/auditWithSuggestions.json b/test/fixtures/broken-backlinks/auditWithSuggestions.json new file mode 100644 index 000000000..a45656aed --- /dev/null +++ b/test/fixtures/broken-backlinks/auditWithSuggestions.json @@ -0,0 +1,41 @@ +{ + "siteId": "site-id", + "id": "audit-id", + "auditResult": { + "brokenBacklinks": [ + { + "title": "backlink that redirects to www and throw connection error", + "url_from": "https://from.com/from-2", + "url_to": "https://foo.com/redirects-throws-error", + "urlsSuggested": ["https://foo.com/redirects-throws-error-1", "https://foo.com/redirects-throws-error-2"], + "aiRationale": "The suggested URLs are similar to the original URL and are likely to be the correct destination.", + "traffic_domain": 550000 + }, + { + "title": "backlink that returns 429", + "url_from": "https://from.com/from-3", + "url_to": "https://foo.com/returns-429", + "urlsSuggested": ["https://foo.com/returns-429-suggestion-1", "https://foo.com/returns-429-suggestion-2"], + "aiRationale": "The suggested URLs are similar to the original URL and are likely to be the correct destination.", + "traffic_domain": 11000 + }, + { + "title": "backlink that is not excluded", + "url_from": "https://from.com/from-not-excluded", + "url_to": "https://foo.com/not-excluded", + "urlsSuggested": ["https://foo.com/not-excluded-suggestion-1", "https://foo.com/not-excluded-suggestion-2"], + "aiRationale": "The suggested URLs are similar to the original URL and are likely to be the correct destination.", + "traffic_domain": 5500 + }, + { + "title": "backlink that returns 404", + "url_from": "https://from.com/from-1", + "url_to": "https://foo.com/returns-404", + "urlsSuggested": ["https://foo.com/returns-404-suggestion-1", "https://foo.com/returns-404-suggestion-2"], + "aiRationale": "The suggested URLs are similar to the original URL and are likely to be the correct destination.", + "traffic_domain": 1100000 + } + ] + }, + "fullAuditRef": "https://ahrefs.com/site-explorer/broken-backlinks?select=title%2Curl_from%2Curl_to%2Ctraffic_domain&limit=50&mode=prefix&order_by=domain_rating_source%3Adesc%2Ctraffic_domain%3Adesc&target=https%3A%2F%2Faudit.url&output=json&where=%7B%22and%22%3A%5B%7B%22field%22%3A%22domain_rating_source%22%2C%22is%22%3A%5B%22gte%22%2C29.5%5D%7D%2C%7B%22field%22%3A%22traffic_domain%22%2C%22is%22%3A%5B%22gte%22%2C500%5D%7D%2C%7B%22field%22%3A%22links_external%22%2C%22is%22%3A%5B%22lte%22%2C300%5D%7D%5D%7D" +} diff --git a/test/fixtures/broken-backlinks/opportunity.js b/test/fixtures/broken-backlinks/opportunity.js index e41b5295f..eb4edfd2e 100644 --- a/test/fixtures/broken-backlinks/opportunity.js +++ b/test/fixtures/broken-backlinks/opportunity.js @@ -26,7 +26,7 @@ export const otherOpportunity = { getType: () => 'other', }; -export const opportunityData = (siteId, auditId) => ({ +export const opportunityData = (siteId, auditId, kpiDeltas) => ({ siteId, auditId, runbook: 'https://adobe.sharepoint.com/:w:/r/sites/aemsites-engineering/_layouts/15/doc2.aspx?sourcedoc=%7BAC174971-BA97-44A9-9560-90BE6C7CF789%7D&file=Experience_Success_Studio_Broken_Backlinks_Runbook.docx&action=default&mobileredirect=true', @@ -44,5 +44,7 @@ export const opportunityData = (siteId, auditId) => ({ ], }, tags: ['Traffic acquisition'], - data: null, + data: { + ...kpiDeltas, + }, }); diff --git a/test/fixtures/broken-backlinks/organic-traffic.js b/test/fixtures/broken-backlinks/organic-traffic.js new file mode 100644 index 000000000..0b9128be8 --- /dev/null +++ b/test/fixtures/broken-backlinks/organic-traffic.js @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +export const organicTraffic = (site) => [ + { + siteId: site.getId(), + source: 'ahrefs', + time: '2025-02-28T15:51:00Z', + cost: 14912235, + name: 'organic-traffic', + value: 747684, + }, + { + siteId: site.getId(), + source: 'ahrefs', + time: '2025-01-03T15:51:00Z', + cost: 14912235, + name: 'organic-traffic', + value: 747684, + }, +]; diff --git a/test/fixtures/broken-backlinks/rum-traffic.json b/test/fixtures/broken-backlinks/rum-traffic.json new file mode 100644 index 000000000..167093c03 --- /dev/null +++ b/test/fixtures/broken-backlinks/rum-traffic.json @@ -0,0 +1,38 @@ +{ + "https://foo.com/redirects-throws-error-1": { + "total": 3864740, + "paid": 5640, + "earned": 408120, + "owned": 3450980 + }, + "https://foo.com/redirects-throws-error-2": { + "total": 419400, + "paid": 226500, + "earned": 565477, + "owned": 192900 + }, + "https://foo.com/returns-429-suggestion-1": { + "total": 3864740, + "paid": 5640, + "earned": 408120, + "owned": 3450980 + }, + "https://foo.com/returns-429-suggestion-2": { + "total": 419400, + "paid": 226500, + "earned": 565477, + "owned": 192900 + }, + "https://foo.com/not-excluded-suggestion-1": { + "total": 3864740, + "paid": 5640, + "earned": 408120, + "owned": 3450980 + }, + "https://foo.com/returns-404-suggestion-1": { + "total": 3864740, + "paid": 5640, + "earned": 408120, + "owned": 3450980 + } +} diff --git a/test/fixtures/broken-backlinks/suggestion.js b/test/fixtures/broken-backlinks/suggestion.js index 353743733..2be7c9049 100644 --- a/test/fixtures/broken-backlinks/suggestion.js +++ b/test/fixtures/broken-backlinks/suggestion.js @@ -11,6 +11,7 @@ */ import sinon from 'sinon'; import auditDataMock from './audit.json' with { type: 'json' }; +import auditDataSuggestionsMock from './auditWithSuggestions.json' with { type: 'json' }; import { brokenBacklinksOpportunity } from './opportunity.js'; export const brokenBacklinksSuggestions = { @@ -34,54 +35,26 @@ export const suggestions = [ { opportunityId: 'test-opportunity-id', type: 'REDIRECT_UPDATE', - rank: 2000, - data: { - title: 'backlink that redirects to www and throw connection error', - url_from: 'https://from.com/from-2', - url_to: 'https://foo.com/redirects-throws-error', - urlsSuggested: [], - aiRationale: '', - traffic_domain: 2000, - }, + rank: 550000, + data: auditDataSuggestionsMock.auditResult.brokenBacklinks[0], }, { opportunityId: 'test-opportunity-id', type: 'REDIRECT_UPDATE', - rank: 1000, - data: { - title: 'backlink that returns 429', - url_from: 'https://from.com/from-3', - url_to: 'https://foo.com/returns-429', - urlsSuggested: [], - aiRationale: '', - traffic_domain: 1000, - }, + rank: 11000, + data: auditDataSuggestionsMock.auditResult.brokenBacklinks[1], }, { opportunityId: 'test-opportunity-id', type: 'REDIRECT_UPDATE', - rank: 5000, - data: { - title: 'backlink that is not excluded', - url_from: 'https://from.com/from-not-excluded', - url_to: 'https://foo.com/not-excluded', - urlsSuggested: [], - aiRationale: '', - traffic_domain: 5000, - }, + rank: 5500, + data: auditDataSuggestionsMock.auditResult.brokenBacklinks[2], }, { opportunityId: 'test-opportunity-id', type: 'REDIRECT_UPDATE', - rank: 4000, - data: { - title: 'backlink that returns 404', - url_from: 'https://from.com/from-1', - url_to: 'https://foo.com/returns-404', - urlsSuggested: [], - aiRationale: '', - traffic_domain: 4000, - }, + rank: 1100000, + data: auditDataSuggestionsMock.auditResult.brokenBacklinks[3], }, ]; From 3e2106da8bdf5760b5dc570f0d19cf6c9dad14a6 Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Fri, 28 Feb 2025 17:58:44 +0100 Subject: [PATCH 02/11] fix: PR feedback --- src/backlinks/kpi-metrics.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index 82dcc64a3..22aec37e3 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -30,15 +30,14 @@ const calculateKpiMetrics = async (auditData, context, site) => { context, ); - if (!isNonEmptyArray(organicTrafficData)) { - log.info(`No organic traffic data found for site ${siteId}`); - return null; - } + let CPC = 1; - const latestOrganicTrafficData = organicTrafficData.sort( - (a, b) => new Date(b.time) - new Date(a.time), - )[0]; - const CPC = latestOrganicTrafficData.cost / latestOrganicTrafficData.value; + if (isNonEmptyArray(organicTrafficData)) { + const latestOrganicTrafficData = organicTrafficData.sort( + (a, b) => new Date(b.time) - new Date(a.time), + )[0]; + CPC = latestOrganicTrafficData.cost / latestOrganicTrafficData.value; + } const projectedTrafficLost = auditData.auditResult.brokenBacklinks.reduce((sum, backlink) => { const { traffic_domain: referringTraffic, urlsSuggested } = backlink; From 91f96ea622706c1e423d6c2c25bbb1b54901dde1 Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Fri, 28 Feb 2025 18:11:10 +0100 Subject: [PATCH 03/11] fix: test coverage --- test/audits/backlinks.test.js | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/audits/backlinks.test.js b/test/audits/backlinks.test.js index f52fe5194..81c44f2eb 100644 --- a/test/audits/backlinks.test.js +++ b/test/audits/backlinks.test.js @@ -42,6 +42,7 @@ import { suggestions, } from '../fixtures/broken-backlinks/suggestion.js'; import { organicTraffic } from '../fixtures/broken-backlinks/organic-traffic.js'; +import calculateKpiMetrics from '../../src/backlinks/kpi-metrics.js'; use(sinonChai); use(chaiAsPromised); @@ -472,4 +473,40 @@ describe('Backlinks Tests', function () { expect(context.log.error).to.have.been.calledWith('Batch processing error: Firefall error'); }); }); + + describe('calculateKpiMetrics', () => { + it('should calculate metrics correctly for a single broken backlink', async () => { + const auditData = { + auditResult: { + brokenBacklinks: [ + { traffic_domain: 25000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + { traffic_domain: 10000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + { traffic_domain: 10001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + { traffic_domain: 100, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + ], + }, + }; + + const result = await calculateKpiMetrics(auditData, context, site); + expect(result.projectedTrafficLost).to.equal(22854.719999999998); + expect(result.projectedTrafficValue).to.equal(455827.5360970677); + }); + + it('returns early if there is no RUM traffic data', async () => { + context.s3.s3Client.send.onCall(0).resolves(null); + const auditData = { + auditResult: { + brokenBacklinks: [ + { traffic_domain: 25000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + { traffic_domain: 10000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + { traffic_domain: 10001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + { traffic_domain: 100, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, + ], + }, + }; + + const result = await calculateKpiMetrics(auditData, context, site); + expect(result).to.be.null; + }); + }); }); From e671125bb5ee0302a80d9246a2c0c69207d83aed Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Tue, 4 Mar 2025 10:55:49 +0100 Subject: [PATCH 04/11] fix: test coverage and null pointer exceptions --- src/backlinks/kpi-metrics.js | 6 +- test/audits/backlinks.test.js | 85 ++++++++++++------- .../auditWithSuggestions.json | 2 - .../broken-backlinks/rum-traffic.json | 12 +-- test/fixtures/broken-backlinks/suggestion.js | 6 +- 5 files changed, 67 insertions(+), 44 deletions(-) diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index 22aec37e3..270844e85 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -12,6 +12,8 @@ import { getStoredMetrics, isNonEmptyArray, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; +const CPC_DEFAULT_VALUE = 2.69; + const calculateKpiMetrics = async (auditData, context, site) => { const { log } = context; const siteId = site.getId(); @@ -30,7 +32,7 @@ const calculateKpiMetrics = async (auditData, context, site) => { context, ); - let CPC = 1; + let CPC = CPC_DEFAULT_VALUE; if (isNonEmptyArray(organicTrafficData)) { const latestOrganicTrafficData = organicTrafficData.sort( @@ -56,7 +58,7 @@ const calculateKpiMetrics = async (auditData, context, site) => { } else { trafficBand = 0.001; } - const proposedTargetTraffic = rumTrafficData[urlsSuggested[0]]?.earned ?? 0; + const proposedTargetTraffic = rumTrafficData[urlsSuggested?.[0]]?.earned ?? 0; return sum + (proposedTargetTraffic * trafficBand); }, 0); diff --git a/test/audits/backlinks.test.js b/test/audits/backlinks.test.js index 81c44f2eb..bbf65cc0a 100644 --- a/test/audits/backlinks.test.js +++ b/test/audits/backlinks.test.js @@ -82,18 +82,6 @@ describe('Backlinks Tests', function () { }) .build(message); - context.s3.s3Client.send.onCall(0).resolves({ - Body: { - transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), - }, - }); - - context.s3.s3Client.send.onCall(1).resolves({ - Body: { - transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), - }, - }); - nock('https://foo.com') .get('/returns-404') .reply(404); @@ -146,6 +134,17 @@ describe('Backlinks Tests', function () { }); it('should transform the audit result into an opportunity in the post processor and create a new opportunity', async () => { + context.s3.s3Client.send.onCall(0).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), + }, + }); + + context.s3.s3Client.send.onCall(1).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), + }, + }); context.dataAccess.Site.findById = sinon.stub().withArgs('site1').resolves(site); context.dataAccess.Opportunity.create.resolves(brokenBacklinksOpportunity); brokenBacklinksOpportunity.addSuggestions.resolves(brokenBacklinksSuggestions); @@ -475,35 +474,55 @@ describe('Backlinks Tests', function () { }); describe('calculateKpiMetrics', () => { + const auditData = { + auditResult: { + brokenBacklinks: [ + { traffic_domain: 25000001, urlsSuggested: ['https://foo.com/bar/redirect'] }, + { traffic_domain: 10000001, urlsSuggested: ['https://foo.com/bar/baz/redirect'] }, + { traffic_domain: 10001, urlsSuggested: ['https://foo.com/qux/redirect'] }, + { traffic_domain: 100, urlsSuggested: ['https://foo.com/bar/baz/qux/redirect'] }, + ], + }, + }; + it('should calculate metrics correctly for a single broken backlink', async () => { - const auditData = { - auditResult: { - brokenBacklinks: [ - { traffic_domain: 25000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - { traffic_domain: 10000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - { traffic_domain: 10001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - { traffic_domain: 100, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - ], + context.s3.s3Client.send.onCall(0).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), }, - }; + }); + + context.s3.s3Client.send.onCall(1).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), + }, + }); const result = await calculateKpiMetrics(auditData, context, site); - expect(result.projectedTrafficLost).to.equal(22854.719999999998); - expect(result.projectedTrafficValue).to.equal(455827.5360970677); + expect(result.projectedTrafficLost).to.equal(26788.645); + expect(result.projectedTrafficValue).to.equal(534287.974025892); + }); + + it('skips URL if no RUM data is available for just individual URLs', async () => { + delete rumTraffic['https://foo.com/bar/redirect'].earned; + context.s3.s3Client.send.onCall(0).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), + }, + }); + + context.s3.s3Client.send.onCall(1).resolves({ + Body: { + transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), + }, + }); + + const result = await calculateKpiMetrics(auditData, context, site); + expect(result.projectedTrafficLost).to.equal(14545.045000000002); }); it('returns early if there is no RUM traffic data', async () => { context.s3.s3Client.send.onCall(0).resolves(null); - const auditData = { - auditResult: { - brokenBacklinks: [ - { traffic_domain: 25000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - { traffic_domain: 10000001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - { traffic_domain: 10001, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - { traffic_domain: 100, urlsSuggested: ['https://foo.com/redirects-throws-error-1'] }, - ], - }, - }; const result = await calculateKpiMetrics(auditData, context, site); expect(result).to.be.null; diff --git a/test/fixtures/broken-backlinks/auditWithSuggestions.json b/test/fixtures/broken-backlinks/auditWithSuggestions.json index a45656aed..0bccc2460 100644 --- a/test/fixtures/broken-backlinks/auditWithSuggestions.json +++ b/test/fixtures/broken-backlinks/auditWithSuggestions.json @@ -31,8 +31,6 @@ "title": "backlink that returns 404", "url_from": "https://from.com/from-1", "url_to": "https://foo.com/returns-404", - "urlsSuggested": ["https://foo.com/returns-404-suggestion-1", "https://foo.com/returns-404-suggestion-2"], - "aiRationale": "The suggested URLs are similar to the original URL and are likely to be the correct destination.", "traffic_domain": 1100000 } ] diff --git a/test/fixtures/broken-backlinks/rum-traffic.json b/test/fixtures/broken-backlinks/rum-traffic.json index 167093c03..2efa6de9b 100644 --- a/test/fixtures/broken-backlinks/rum-traffic.json +++ b/test/fixtures/broken-backlinks/rum-traffic.json @@ -1,35 +1,35 @@ { - "https://foo.com/redirects-throws-error-1": { + "https://foo.com/bar/redirect": { "total": 3864740, "paid": 5640, "earned": 408120, "owned": 3450980 }, - "https://foo.com/redirects-throws-error-2": { + "https://foo.com/bar/baz/redirect": { "total": 419400, "paid": 226500, "earned": 565477, "owned": 192900 }, - "https://foo.com/returns-429-suggestion-1": { + "https://foo.com/baz/bar/redirect": { "total": 3864740, "paid": 5640, "earned": 408120, "owned": 3450980 }, - "https://foo.com/returns-429-suggestion-2": { + "https://foo.com/qux/redirect": { "total": 419400, "paid": 226500, "earned": 565477, "owned": 192900 }, - "https://foo.com/not-excluded-suggestion-1": { + "https://foo.com/bar/qux/redirect": { "total": 3864740, "paid": 5640, "earned": 408120, "owned": 3450980 }, - "https://foo.com/returns-404-suggestion-1": { + "https://foo.com/bar/baz/qux/redirect": { "total": 3864740, "paid": 5640, "earned": 408120, diff --git a/test/fixtures/broken-backlinks/suggestion.js b/test/fixtures/broken-backlinks/suggestion.js index 2be7c9049..fb68152a4 100644 --- a/test/fixtures/broken-backlinks/suggestion.js +++ b/test/fixtures/broken-backlinks/suggestion.js @@ -54,7 +54,11 @@ export const suggestions = [ opportunityId: 'test-opportunity-id', type: 'REDIRECT_UPDATE', rank: 1100000, - data: auditDataSuggestionsMock.auditResult.brokenBacklinks[3], + data: { + ...auditDataSuggestionsMock.auditResult.brokenBacklinks[3], + urlsSuggested: [], + aiRationale: '', + }, }, ]; From bdbc049f806c82bc3e163eb629afca16263594ed Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Tue, 4 Mar 2025 17:08:57 +0100 Subject: [PATCH 05/11] fix: rename rum-traffic to all-traffic --- src/backlinks/kpi-metrics.js | 2 +- test/audits/backlinks.test.js | 2 +- .../broken-backlinks/{rum-traffic.json => all-traffic.json} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename test/fixtures/broken-backlinks/{rum-traffic.json => all-traffic.json} (100%) diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index 270844e85..cb03c64dc 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -18,7 +18,7 @@ const calculateKpiMetrics = async (auditData, context, site) => { const { log } = context; const siteId = site.getId(); const rumTrafficData = await getStoredMetrics( - { source: 'rum', metric: 'rum-traffic', siteId }, + { source: 'rum', metric: 'all-traffic', siteId }, context, ); diff --git a/test/audits/backlinks.test.js b/test/audits/backlinks.test.js index bbf65cc0a..377c005f6 100644 --- a/test/audits/backlinks.test.js +++ b/test/audits/backlinks.test.js @@ -19,7 +19,7 @@ import nock from 'nock'; import { FirefallClient } from '@adobe/spacecat-shared-gpt-client'; import auditDataMock from '../fixtures/broken-backlinks/audit.json' with { type: 'json' }; import auditDataSuggestionsMock from '../fixtures/broken-backlinks/auditWithSuggestions.json' with { type: 'json' }; -import rumTraffic from '../fixtures/broken-backlinks/rum-traffic.json' with { type: 'json' }; +import rumTraffic from '../fixtures/broken-backlinks/all-traffic.json' with { type: 'json' }; import { brokenBacklinksAuditRunner, opportunityAndSuggestions, generateSuggestionData } from '../../src/backlinks/handler.js'; import { MockContextBuilder } from '../shared.js'; import { diff --git a/test/fixtures/broken-backlinks/rum-traffic.json b/test/fixtures/broken-backlinks/all-traffic.json similarity index 100% rename from test/fixtures/broken-backlinks/rum-traffic.json rename to test/fixtures/broken-backlinks/all-traffic.json From 416572c5424de05f264db51441e2d2cad3ebc6ca Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Wed, 5 Mar 2025 08:06:54 +0100 Subject: [PATCH 06/11] fix: PR review --- src/backlinks/kpi-metrics.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index cb03c64dc..1ff67f93e 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -13,6 +13,22 @@ import { getStoredMetrics, isNonEmptyArray, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; const CPC_DEFAULT_VALUE = 2.69; +const TRAFFIC_BANDS = [ + { threshold: 25000000, band: 0.03 }, + { threshold: 10000000, band: 0.02 }, + { threshold: 1000000, band: 0.01 }, + { threshold: 500000, band: 0.0075 }, + { threshold: 10000, band: 0.005 }, +]; + +const getTrafficBand = (traffic) => { + for (const { threshold, band } of TRAFFIC_BANDS) { + if (traffic > threshold) { + return band; + } + } + return 0.001; +}; const calculateKpiMetrics = async (auditData, context, site) => { const { log } = context; @@ -41,23 +57,10 @@ const calculateKpiMetrics = async (auditData, context, site) => { CPC = latestOrganicTrafficData.cost / latestOrganicTrafficData.value; } - const projectedTrafficLost = auditData.auditResult.brokenBacklinks.reduce((sum, backlink) => { + const projectedTrafficLost = auditData?.auditResult?.brokenBacklinks?.reduce((sum, backlink) => { const { traffic_domain: referringTraffic, urlsSuggested } = backlink; - let trafficBand; - if (referringTraffic > 25000000) { - trafficBand = 0.03; - } else if (referringTraffic > 10000000) { - trafficBand = 0.02; - } else if (referringTraffic > 1000000) { - trafficBand = 0.01; - } else if (referringTraffic > 500000) { - trafficBand = 0.0075; - } else if (referringTraffic > 10000) { - trafficBand = 0.005; - } else { - trafficBand = 0.001; - } + const trafficBand = getTrafficBand(referringTraffic); const proposedTargetTraffic = rumTrafficData[urlsSuggested?.[0]]?.earned ?? 0; return sum + (proposedTargetTraffic * trafficBand); }, 0); From e93462c23e41713182efc36b5d6d417176b1b653 Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Wed, 5 Mar 2025 09:44:42 +0100 Subject: [PATCH 07/11] fix: accessing stored metrics --- src/backlinks/kpi-metrics.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index 1ff67f93e..878aa44af 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -32,10 +32,16 @@ const getTrafficBand = (traffic) => { const calculateKpiMetrics = async (auditData, context, site) => { const { log } = context; + const storedMetricsConfig = { + ...context, + s3Bucket: context.env?.S3_IMPORTER_BUCKET_NAME, + s3Client: context.s3Client, + }; + const siteId = site.getId(); const rumTrafficData = await getStoredMetrics( { source: 'rum', metric: 'all-traffic', siteId }, - context, + storedMetricsConfig, ); if (!isNonEmptyObject(rumTrafficData)) { @@ -45,7 +51,7 @@ const calculateKpiMetrics = async (auditData, context, site) => { const organicTrafficData = await getStoredMetrics( { source: 'ahrefs', metric: 'organic-traffic', siteId }, - context, + storedMetricsConfig, ); let CPC = CPC_DEFAULT_VALUE; From 1522e19e10cd494fc229d9bd815b00b3efcedd49 Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Wed, 5 Mar 2025 18:00:53 +0100 Subject: [PATCH 08/11] fix: s3 client context --- src/backlinks/kpi-metrics.js | 6 ++++-- test/audits/backlinks.test.js | 21 ++++++++------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index 878aa44af..f6fb28f3b 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -34,8 +34,10 @@ const calculateKpiMetrics = async (auditData, context, site) => { const { log } = context; const storedMetricsConfig = { ...context, - s3Bucket: context.env?.S3_IMPORTER_BUCKET_NAME, - s3Client: context.s3Client, + s3: { + s3Bucket: context.env?.S3_IMPORTER_BUCKET_NAME, + s3Client: context.s3Client, + }, }; const siteId = site.getId(); diff --git a/test/audits/backlinks.test.js b/test/audits/backlinks.test.js index 377c005f6..23079ccb5 100644 --- a/test/audits/backlinks.test.js +++ b/test/audits/backlinks.test.js @@ -69,12 +69,7 @@ describe('Backlinks Tests', function () { AHREFS_API_BASE_URL: 'https://ahrefs.com', AHREFS_API_KEY: 'ahrefs-api', S3_SCRAPER_BUCKET_NAME: 'test-bucket', - }, - s3: { - s3Bucket: 'test-bucket', - s3Client: { - send: sandbox.stub(), - }, + S3_IMPORTER_BUCKET_NAME: 'test-import-bucket', }, s3Client: { send: sandbox.stub(), @@ -134,13 +129,13 @@ describe('Backlinks Tests', function () { }); it('should transform the audit result into an opportunity in the post processor and create a new opportunity', async () => { - context.s3.s3Client.send.onCall(0).resolves({ + context.s3Client.send.onCall(0).resolves({ Body: { transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), }, }); - context.s3.s3Client.send.onCall(1).resolves({ + context.s3Client.send.onCall(1).resolves({ Body: { transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), }, @@ -486,13 +481,13 @@ describe('Backlinks Tests', function () { }; it('should calculate metrics correctly for a single broken backlink', async () => { - context.s3.s3Client.send.onCall(0).resolves({ + context.s3Client.send.onCall(0).resolves({ Body: { transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), }, }); - context.s3.s3Client.send.onCall(1).resolves({ + context.s3Client.send.onCall(1).resolves({ Body: { transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), }, @@ -505,13 +500,13 @@ describe('Backlinks Tests', function () { it('skips URL if no RUM data is available for just individual URLs', async () => { delete rumTraffic['https://foo.com/bar/redirect'].earned; - context.s3.s3Client.send.onCall(0).resolves({ + context.s3Client.send.onCall(0).resolves({ Body: { transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), }, }); - context.s3.s3Client.send.onCall(1).resolves({ + context.s3Client.send.onCall(1).resolves({ Body: { transformToString: sinon.stub().resolves(JSON.stringify(organicTraffic(site))), }, @@ -522,7 +517,7 @@ describe('Backlinks Tests', function () { }); it('returns early if there is no RUM traffic data', async () => { - context.s3.s3Client.send.onCall(0).resolves(null); + context.s3Client.send.onCall(0).resolves(null); const result = await calculateKpiMetrics(auditData, context, site); expect(result).to.be.null; From 826b9136e985b949ffdac1edcf2dfd432c6a168b Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Wed, 5 Mar 2025 18:28:58 +0100 Subject: [PATCH 09/11] fix: rum data structure --- src/backlinks/kpi-metrics.js | 9 ++++---- test/audits/backlinks.test.js | 2 +- .../broken-backlinks/all-traffic.json | 22 ++++++++++++------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index f6fb28f3b..27ab54133 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import { getStoredMetrics, isNonEmptyArray, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; +import { getStoredMetrics, isNonEmptyArray } from '@adobe/spacecat-shared-utils'; const CPC_DEFAULT_VALUE = 2.69; const TRAFFIC_BANDS = [ @@ -46,7 +46,7 @@ const calculateKpiMetrics = async (auditData, context, site) => { storedMetricsConfig, ); - if (!isNonEmptyObject(rumTrafficData)) { + if (!isNonEmptyArray(rumTrafficData)) { log.info(`No RUM traffic data found for site ${siteId}`); return null; } @@ -67,9 +67,10 @@ const calculateKpiMetrics = async (auditData, context, site) => { const projectedTrafficLost = auditData?.auditResult?.brokenBacklinks?.reduce((sum, backlink) => { const { traffic_domain: referringTraffic, urlsSuggested } = backlink; - const trafficBand = getTrafficBand(referringTraffic); - const proposedTargetTraffic = rumTrafficData[urlsSuggested?.[0]]?.earned ?? 0; + const targetUrl = urlsSuggested?.[0]; + const targetTrafficData = rumTrafficData.find((data) => data.url === targetUrl); + const proposedTargetTraffic = targetTrafficData?.earned ?? 0; return sum + (proposedTargetTraffic * trafficBand); }, 0); diff --git a/test/audits/backlinks.test.js b/test/audits/backlinks.test.js index 23079ccb5..bc5e8922b 100644 --- a/test/audits/backlinks.test.js +++ b/test/audits/backlinks.test.js @@ -499,7 +499,7 @@ describe('Backlinks Tests', function () { }); it('skips URL if no RUM data is available for just individual URLs', async () => { - delete rumTraffic['https://foo.com/bar/redirect'].earned; + delete rumTraffic[0].earned; context.s3Client.send.onCall(0).resolves({ Body: { transformToString: sinon.stub().resolves(JSON.stringify(rumTraffic)), diff --git a/test/fixtures/broken-backlinks/all-traffic.json b/test/fixtures/broken-backlinks/all-traffic.json index 2efa6de9b..d13dda1bd 100644 --- a/test/fixtures/broken-backlinks/all-traffic.json +++ b/test/fixtures/broken-backlinks/all-traffic.json @@ -1,38 +1,44 @@ -{ - "https://foo.com/bar/redirect": { +[ + { + "url": "https://foo.com/bar/redirect", "total": 3864740, "paid": 5640, "earned": 408120, "owned": 3450980 }, - "https://foo.com/bar/baz/redirect": { + { + "url": "https://foo.com/bar/baz/redirect", "total": 419400, "paid": 226500, "earned": 565477, "owned": 192900 }, - "https://foo.com/baz/bar/redirect": { + { + "url": "https://foo.com/baz/bar/redirect", "total": 3864740, "paid": 5640, "earned": 408120, "owned": 3450980 }, - "https://foo.com/qux/redirect": { + { + "url": "https://foo.com/qux/redirect", "total": 419400, "paid": 226500, "earned": 565477, "owned": 192900 }, - "https://foo.com/bar/qux/redirect": { + { + "url": "https://foo.com/bar/qux/redirect", "total": 3864740, "paid": 5640, "earned": 408120, "owned": 3450980 }, - "https://foo.com/bar/baz/qux/redirect": { + { + "url": "https://foo.com/bar/baz/qux/redirect", "total": 3864740, "paid": 5640, "earned": 408120, "owned": 3450980 } -} +] From 5fceb96d29c6c8e06ebd2571219bd1ef05f7c999 Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Wed, 5 Mar 2025 18:42:05 +0100 Subject: [PATCH 10/11] fix: kpi deltas --- src/backlinks/handler.js | 5 ++++- src/backlinks/kpi-metrics.js | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/backlinks/handler.js b/src/backlinks/handler.js index 11a728069..3c7090b3a 100644 --- a/src/backlinks/handler.js +++ b/src/backlinks/handler.js @@ -245,7 +245,11 @@ export const generateSuggestionData = async (finalUrl, auditData, context, site) */ export async function opportunityAndSuggestions(auditUrl, auditData, context, site) { + const { log } = context; + const kpiDeltas = await calculateKpiDeltasForAudit(auditData, context, site); + log.info(`KPI deltas for audit ${auditData.id}: ${JSON.stringify(kpiDeltas)}`); + const opportunity = await convertToOpportunity( auditUrl, auditData, @@ -254,7 +258,6 @@ export async function opportunityAndSuggestions(auditUrl, auditData, context, si auditType, kpiDeltas, ); - const { log } = context; const buildKey = (data) => `${data.url_from}|${data.url_to}`; diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index 27ab54133..1d294954e 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -65,11 +65,14 @@ const calculateKpiMetrics = async (auditData, context, site) => { CPC = latestOrganicTrafficData.cost / latestOrganicTrafficData.value; } + log.info(`CPC for site ${siteId} is ${CPC}`); + const projectedTrafficLost = auditData?.auditResult?.brokenBacklinks?.reduce((sum, backlink) => { const { traffic_domain: referringTraffic, urlsSuggested } = backlink; const trafficBand = getTrafficBand(referringTraffic); const targetUrl = urlsSuggested?.[0]; const targetTrafficData = rumTrafficData.find((data) => data.url === targetUrl); + log.info(`Target URL: ${targetUrl}, Traffic: ${targetTrafficData?.earned}`); const proposedTargetTraffic = targetTrafficData?.earned ?? 0; return sum + (proposedTargetTraffic * trafficBand); }, 0); From 7c64311afe10438a61944397a42455aa0d6724cd Mon Sep 17 00:00:00 2001 From: Damian Zehnder Date: Thu, 6 Mar 2025 08:52:27 +0100 Subject: [PATCH 11/11] fix: kpi deltas --- src/backlinks/handler.js | 1 - src/backlinks/kpi-metrics.js | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/backlinks/handler.js b/src/backlinks/handler.js index 3c7090b3a..e6b8cd173 100644 --- a/src/backlinks/handler.js +++ b/src/backlinks/handler.js @@ -248,7 +248,6 @@ export async function opportunityAndSuggestions(auditUrl, auditData, context, si const { log } = context; const kpiDeltas = await calculateKpiDeltasForAudit(auditData, context, site); - log.info(`KPI deltas for audit ${auditData.id}: ${JSON.stringify(kpiDeltas)}`); const opportunity = await convertToOpportunity( auditUrl, diff --git a/src/backlinks/kpi-metrics.js b/src/backlinks/kpi-metrics.js index 1d294954e..27ab54133 100644 --- a/src/backlinks/kpi-metrics.js +++ b/src/backlinks/kpi-metrics.js @@ -65,14 +65,11 @@ const calculateKpiMetrics = async (auditData, context, site) => { CPC = latestOrganicTrafficData.cost / latestOrganicTrafficData.value; } - log.info(`CPC for site ${siteId} is ${CPC}`); - const projectedTrafficLost = auditData?.auditResult?.brokenBacklinks?.reduce((sum, backlink) => { const { traffic_domain: referringTraffic, urlsSuggested } = backlink; const trafficBand = getTrafficBand(referringTraffic); const targetUrl = urlsSuggested?.[0]; const targetTrafficData = rumTrafficData.find((data) => data.url === targetUrl); - log.info(`Target URL: ${targetUrl}, Traffic: ${targetTrafficData?.earned}`); const proposedTargetTraffic = targetTrafficData?.earned ?? 0; return sum + (proposedTargetTraffic * trafficBand); }, 0);