Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: migrate metatags audit to AuditBuilder #535

Merged
merged 34 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1909c24
refactor: change defaultMessageSender function to noop
martinst06 Dec 16, 2024
2b0d015
refactor: refactored audit tests
martinst06 Dec 16, 2024
2572108
refactor: migrated metatags and broken backlinks audit to use the Aud…
martinst06 Dec 16, 2024
1572e21
Merge branch 'main' into metatags-broken-backlinks-migration
martinst06 Dec 16, 2024
2f9460f
Merge branch 'main' into metatags-broken-backlinks-migration
martinst06 Dec 16, 2024
9d3e9a0
refactor: metatags handler and tests
martinst06 Dec 18, 2024
35f41a0
refactor: metatags handler and tests
martinst06 Dec 18, 2024
19726e9
refactor: code coverage for testing purposes
martinst06 Dec 18, 2024
7d96c5f
Merge branch 'main' into metatags-broken-backlinks-migration
martinst06 Dec 18, 2024
2d8aa61
refactor: metatags
martinst06 Dec 19, 2024
0055506
Merge branch 'metatags-broken-backlinks-migration' of https://github.…
martinst06 Dec 19, 2024
6b2135f
refactor: swap saving audit with syncOpportunityAndSuggestions
martinst06 Dec 19, 2024
5c63d32
refactor: disable some audit tests temporarily
martinst06 Dec 19, 2024
4b4c270
refactor: audit saving
martinst06 Dec 20, 2024
5c87dbe
refactor: audit tests pass temporarily
martinst06 Dec 20, 2024
bac495b
refactor: merge with main
martinst06 Jan 6, 2025
e94a64a
refactor: merge with main
martinst06 Jan 6, 2025
f696bd1
refactor: moved oopty handler
martinst06 Jan 6, 2025
7a37e25
refactor: separated opportunity and suggestions for metatags
martinst06 Jan 8, 2025
7c29346
Merge branch 'main' into metatags-broken-backlinks-migration
martinst06 Jan 8, 2025
fca597d
fix: build failing
martinst06 Jan 8, 2025
a4ccfed
refactor: missing parameter
martinst06 Jan 8, 2025
546a413
chore: returning coverage to 100
martinst06 Jan 9, 2025
5537617
chore: remove comments and reenabling tests
martinst06 Jan 9, 2025
46a7276
test: readding and updating opportunities and suggestions tests
martinst06 Jan 9, 2025
2d156a1
test: current tests pass, but coverage not 100%
martinst06 Jan 9, 2025
8218f22
test: improves coverage
martinst06 Jan 10, 2025
5ec3a77
chore: remove unnecessary code
martinst06 Jan 10, 2025
3e7f7cb
test: fixed tests (not yet 100% covered)
martinst06 Jan 10, 2025
09fe22e
test: s3-utils test
martinst06 Jan 10, 2025
0bdc72b
test: oppportunityHandler covered
martinst06 Jan 10, 2025
2467d8c
Merge branch 'main' into metatags-broken-backlinks-migration
solaris007 Jan 13, 2025
56b4218
chore: undid backlinks change
martinst06 Jan 13, 2025
a72fd80
Merge branch 'metatags-broken-backlinks-migration' of https://github.…
martinst06 Jan 13, 2025
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
1 change: 0 additions & 1 deletion src/common/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export async function defaultMessageSender() {}
export async function defaultPersister(auditData, context) {
const { dataAccess } = context;
const { Audit } = dataAccess;

return Audit.create(auditData);
}

Expand Down
136 changes: 47 additions & 89 deletions src/metatags/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@
* governing permissions and limitations under the License.
*/

import {
internalServerError, noContent, notFound, ok,
} from '@adobe/spacecat-shared-http-utils';
import { composeAuditURL } from '@adobe/spacecat-shared-utils';
import { retrieveSiteBySiteId } from '../utils/data-access.js';
import { getObjectFromKey, getObjectKeysUsingPrefix } from '../utils/s3-utils.js';
import SeoChecks from './seo-checks.js';
import syncOpportunityAndSuggestions from './opportunityHandler.js';
import { AuditBuilder } from '../common/audit-builder.js';
import { noopUrlResolver } from '../common/audit.js';
import convertToOpportunity from './opportunityHandler.js';

async function fetchAndProcessPageObject(s3Client, bucketName, key, prefix, log) {
export async function fetchAndProcessPageObject(s3Client, bucketName, key, prefix, log) {
const object = await getObjectFromKey(s3Client, bucketName, key, log);
if (!object?.scrapeResult?.tags || typeof object.scrapeResult.tags !== 'object') {
log.error(`No Scraped tags found in S3 ${key} object`);
Expand All @@ -35,88 +32,49 @@ async function fetchAndProcessPageObject(s3Client, bucketName, key, prefix, log)
};
}

export default async function auditMetaTags(message, context) {
const { type, auditContext = {} } = message;
const siteId = message.siteId || message.url;
const {
dataAccess, log, s3Client,
} = context;
const { Audit, Configuration } = dataAccess;

try {
const site = await retrieveSiteBySiteId(dataAccess, siteId, log);
if (!site) {
return notFound('Site not found');
}
if (!site.getIsLive()) {
log.info(`Site ${siteId} is not live`);
return ok();
}
const configuration = await Configuration.findLatest();
if (!configuration.isHandlerEnabledForSite(type, site)) {
log.info(`Audit type ${type} disabled for site ${siteId}`);
return ok();
}
try {
auditContext.finalUrl = await composeAuditURL(site.getBaseURL());
} catch (e) {
log.error(`Get final URL for siteId ${siteId} failed with error: ${e.message}`, e);
return internalServerError(`Internal server error: ${e.message}`);
export async function auditMetaTagsRunner(baseURL, context, site) {
const { log, s3Client } = context;
// Fetch site's scraped content from S3
const bucketName = context.S3_SCRAPER_BUCKET_NAME;
const prefix = `scrapes/${site.getId()}/`;
const scrapedObjectKeys = await getObjectKeysUsingPrefix(s3Client, bucketName, prefix, log);
const extractedTags = {};
const pageMetadataResults = await Promise.all(scrapedObjectKeys.map(
(key) => fetchAndProcessPageObject(s3Client, bucketName, key, prefix, log),
));
pageMetadataResults.forEach((pageMetadata) => {
if (pageMetadata) {
Object.assign(extractedTags, pageMetadata);
}
// Fetch site's scraped content from S3
const bucketName = context.env.S3_SCRAPER_BUCKET_NAME;
const prefix = `scrapes/${siteId}/`;
const scrapedObjectKeys = await getObjectKeysUsingPrefix(s3Client, bucketName, prefix, log);
const extractedTags = {};
const pageMetadataResults = await Promise.all(scrapedObjectKeys.map(
(key) => fetchAndProcessPageObject(s3Client, bucketName, key, prefix, log),
));
pageMetadataResults.forEach((pageMetadata) => {
if (pageMetadata) {
Object.assign(extractedTags, pageMetadata);
}
});
const extractedTagsCount = Object.entries(extractedTags).length;
if (extractedTagsCount === 0) {
log.error(`Failed to extract tags from scraped content for bucket ${bucketName} and prefix ${prefix}`);
return notFound('Site tags data not available');
}
log.info(`Performing SEO checks for ${extractedTagsCount} tags`);
// Perform SEO checks
const seoChecks = new SeoChecks(log);
for (const [pageUrl, pageTags] of Object.entries(extractedTags)) {
seoChecks.performChecks(pageUrl || '/', pageTags);
}
seoChecks.finalChecks();
const detectedTags = seoChecks.getDetectedTags();
// Prepare Audit result
const auditResult = {
detectedTags,
sourceS3Folder: `${bucketName}/${prefix}`,
fullAuditRef: 'na',
finalUrl: auditContext.finalUrl,
};
const auditData = {
siteId: site.getId(),
isLive: site.getIsLive(),
auditedAt: new Date().toISOString(),
auditType: type,
fullAuditRef: auditResult?.fullAuditRef,
auditResult,
};
// Persist Audit result
const audit = await Audit.create(auditData);
log.info(`Successfully audited ${siteId} for ${type} type audit`);
await syncOpportunityAndSuggestions(
siteId,
audit.getId(),
auditData,
dataAccess,
log,
);
return noContent();
} catch (e) {
log.error(`${type} type audit for ${siteId} failed with error: ${e.message}`, e);
return internalServerError(`Internal server error: ${e.message}`);
});
const extractedTagsCount = Object.entries(extractedTags).length;
if (extractedTagsCount === 0) {
log.error(`Failed to extract tags from scraped content for bucket ${bucketName} and prefix ${prefix}`);
}
log.info(`Performing SEO checks for ${extractedTagsCount} tags`);
// Perform SEO checks
const seoChecks = new SeoChecks(log);
for (const [pageUrl, pageTags] of Object.entries(extractedTags)) {
seoChecks.performChecks(pageUrl || '/', pageTags);
}
seoChecks.finalChecks();
const detectedTags = seoChecks.getDetectedTags();

const auditResult = {
detectedTags,
sourceS3Folder: `${bucketName}/${prefix}`,
fullAuditRef: 'na',
finalUrl: baseURL,
};

return {
auditResult,
fullAuditRef: baseURL,
};
}

export default new AuditBuilder()
.withUrlResolver(noopUrlResolver)
.withRunner(auditMetaTagsRunner)
.withPostProcessors([convertToOpportunity])
.build();
52 changes: 22 additions & 30 deletions src/metatags/opportunityHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import { TITLE, DESCRIPTION, H1 } from './constants.js';

function removeTrailingSlash(url) {
export function removeTrailingSlash(url) {
return url.endsWith('/') ? url.slice(0, -1) : url;
}

Expand Down Expand Up @@ -140,39 +140,31 @@ function getIssueRanking(tagName, issue) {
}

/**
* Updates Meta-tags Opportunity and Suggestions collection with new audit results
* @param siteId site id of site being audited
* @param auditId audit id of the latest performed audit
* @param auditData object containing audit results and some metadata
* @param dataAccess object containing accessor objects
* @param log logger
* @returns {Promise<void>}
* @param auditUrl - The URL of the audit
* @param auditData - The audit data containing the audit result and additional details.
* @param context - The context object containing the data access and logger objects.
*/
export default async function syncOpportunityAndSuggestions(
siteId,
auditId,
auditData,
dataAccess,
log,
) {
export default async function convertToOpportunity(auditUrl, auditData, context) {
const { dataAccess, log } = context;
const { Opportunity } = dataAccess;
log.info(`Syncing opportunity and suggestions for ${siteId}`);

log.info(`Syncing opportunity and suggestions for ${auditData.siteId}`);
let metatagsOppty;

try {
// Get all opportunities by site-id and new status
const opportunities = await Opportunity.allBySiteIdAndStatus(siteId, 'NEW');
// Find existing opportunity for meta-tags
const opportunities = await Opportunity.allBySiteIdAndStatus(auditData.siteId, 'NEW');

metatagsOppty = opportunities.find((oppty) => oppty.getType() === 'meta-tags');
} catch (e) {
log.error(`Fetching opportunities for siteId ${siteId} failed with error: ${e.message}`);
throw new Error(`Failed to fetch opportunities for siteId ${siteId}: ${e.message}`);
log.error(`Fetching opportunities for siteId ${auditData.siteId} failed with error: ${e.message}`);
throw new Error(`Failed to fetch opportunities for siteId ${auditData.siteId}: ${e.message}`);
}

try {
if (!metatagsOppty) {
const opportunityData = {
siteId,
auditId,
siteId: auditData.siteId,
auditId: auditData.id,
runbook: 'https://adobe.sharepoint.com/:w:/r/sites/aemsites-engineering/_layouts/15/doc2.aspx?sourcedoc=%7B27CF48AA-5492-435D-B17C-01E38332A5CA%7D&file=Experience_Success_Studio_Metatags_Runbook.docx&action=default&mobileredirect=true',
type: 'meta-tags',
origin: 'AUTOMATION',
Expand All @@ -192,13 +184,14 @@ export default async function syncOpportunityAndSuggestions(
metatagsOppty = await Opportunity.create(opportunityData);
log.debug('Meta-tags Opportunity created');
} else {
metatagsOppty.setAuditId(auditId);
metatagsOppty.setAuditId(auditData.siteId);
await metatagsOppty.save();
}
} catch (e) {
log.error(`Creating meta-tags opportunity for siteId ${siteId} failed with error: ${e.message}`, e);
throw new Error(`Failed to create meta-tags opportunity for siteId ${siteId}: ${e.message}`);
log.error(`Creating meta-tags opportunity for siteId ${auditData.siteId} failed with error: ${e.message}`, e);
throw new Error(`Failed to create meta-tags opportunity for siteId ${auditData.siteId}: ${e.message}`);
}

const { detectedTags } = auditData.auditResult;
const suggestions = [];
// Generate suggestions data to be inserted in meta-tags opportunity suggestions
Expand All @@ -216,6 +209,7 @@ export default async function syncOpportunityAndSuggestions(
});

const buildKey = (data) => `${data.url}|${data.issue}|${data.tagContent}`;

// Sync the suggestions from new audit with old ones
await syncMetatagsSuggestions({
opportunity: metatagsOppty,
Expand All @@ -225,11 +219,9 @@ export default async function syncOpportunityAndSuggestions(
opportunityId: metatagsOppty.getId(),
type: 'METADATA_UPDATE',
rank: suggestion.rank,
data: {
...suggestion,
},
data: { ...suggestion },
}),
log,
});
log.info(`Successfully synced Opportunity And Suggestions for site: ${siteId} and meta-tags audit type.`);
log.info(`Successfully synced Opportunity And Suggestions for site: ${auditData.siteId} and meta-tags audit type.`);
}
Loading
Loading