Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
90a9989
feat: adding Firefall Handler
dzehnder Dec 5, 2023
b04247e
fix: change model to openai
dzehnder Dec 5, 2023
182fb2f
fix: adding package-lock
dzehnder Dec 5, 2023
57e1c51
Merge branch 'main' into garage-week-firefall
dzehnder Dec 6, 2023
1576987
fix: dependencies pointing to wrong registry
dzehnder Dec 6, 2023
ff290f2
fix: adding tests for firefall handler
dzehnder Dec 6, 2023
c8b7a42
fix: package-lock file pointing to internal artifactory
dzehnder Dec 6, 2023
abf8ba7
fix: lower code coverage for now
dzehnder Dec 6, 2023
c9ae817
fix: adding sqs event adapter to receive message
dzehnder Dec 6, 2023
6b4ff7c
feat: adding tests for onbject definitions
dzehnder Dec 6, 2023
380d397
fix: dataAccess check
dzehnder Dec 6, 2023
fe6903f
fix: dataAccess check
dzehnder Dec 6, 2023
539e006
fix: addin log message for fetchad audit
dzehnder Dec 6, 2023
e030ed9
feat: fetch all audits instead of only latest
dzehnder Dec 6, 2023
c8342e5
fix: tests
dzehnder Dec 6, 2023
fbad34c
feat: adding scroes for previous audit
dzehnder Dec 6, 2023
ff3af2a
fix: tests
dzehnder Dec 6, 2023
f493491
feat: using prompt
dzehnder Dec 6, 2023
fc46021
fix: prompt file not found
dzehnder Dec 6, 2023
39f51c3
fix: response of lamda function
dzehnder Dec 6, 2023
6a44dd8
fix: response
dzehnder Dec 6, 2023
39b7bca
fix: include prompt file in static files
dzehnder Dec 7, 2023
2d3eeab
fix: move prompt file to static folder
dzehnder Dec 7, 2023
a556345
fix: log file content
dzehnder Dec 7, 2023
5771aca
fix: adding validation for prompt content
dzehnder Dec 7, 2023
9a1f119
fix: tmp remove tests
dzehnder Dec 7, 2023
8603180
tmp test
dzehnder Dec 7, 2023
d6333c9
fix: prompt fetch
dzehnder Dec 7, 2023
5141d11
feat: update to latest prompt
dzehnder Dec 7, 2023
f62402c
fix: placeholders in prompt
dzehnder Dec 7, 2023
4b7e945
fix: replacement for inputvalues
dzehnder Dec 7, 2023
ea44668
feat: create slack recommendation message
dzehnder Dec 7, 2023
9c3d166
fix: data access
dzehnder Dec 7, 2023
69a098c
feat: adding slack messageing
dzehnder Dec 7, 2023
1ecbc45
fix: typo
dzehnder Dec 7, 2023
48d5168
feat: format slack message
dzehnder Dec 7, 2023
a1189d1
fix: slack blocks
dzehnder Dec 7, 2023
f1fb19f
feat: introduce columns in slack message
dzehnder Dec 7, 2023
09b4baf
fix: typo in prompt and response message
dzehnder Dec 7, 2023
2281e43
fix: convert firefall integration flag to boolean
dzehnder Dec 7, 2023
9945b6d
fix: sort audits in corret order
dzehnder Dec 7, 2023
b530e74
fix: typo for gh diff
dzehnder Dec 7, 2023
e28a411
feat: adding code suggestion to slack notification
dzehnder Dec 8, 2023
ee33adc
fix: log upstream error
dzehnder Dec 8, 2023
f82cc2f
fix: use environment-dependent secrets
solaris007 Dec 10, 2023
182a628
fix: typo in prompt file
dzehnder Dec 11, 2023
619c331
Merge branch 'garage-week-firefall' of github.com:adobe-rnd/spacecat-…
dzehnder Dec 11, 2023
400cf3b
feat: respond in threas
dzehnder Dec 11, 2023
ce43ad6
fix: find json in firefall response
dzehnder Dec 11, 2023
e74deee
fix: specify strict json output
dzehnder Dec 11, 2023
a20a931
fix: catch parse error
dzehnder Dec 11, 2023
af391e8
fix: prompt for returning data
dzehnder Dec 11, 2023
93d0272
fix: JSON parsing
dzehnder Dec 11, 2023
96a623a
fix: JSON parsing
dzehnder Dec 11, 2023
25fd2ac
fix: remove code recommendation
dzehnder Dec 11, 2023
94444af
fix: typo in prompt
dzehnder Dec 11, 2023
8baf8c0
fix: code recommendation
dzehnder Dec 11, 2023
fac7444
fix: code recommendation
dzehnder Dec 11, 2023
5a29721
fix: adding debug statements
dzehnder Dec 11, 2023
bce9ac9
fix: json parsing
dzehnder Dec 11, 2023
657febb
fix: json parsing
dzehnder Dec 11, 2023
5ccf97b
fix: json parsing
dzehnder Dec 11, 2023
507fb5c
fix: json parsing
dzehnder Dec 11, 2023
89b5f33
fix: json parsing
dzehnder Dec 11, 2023
076be70
fix: json parsing
dzehnder Dec 11, 2023
93d5b7f
fix: prompt
dzehnder Dec 11, 2023
ecf4d93
fix: json parsing
dzehnder Dec 11, 2023
52ec78c
fix: json parsing
dzehnder Dec 11, 2023
32d8f9f
fix: optimize prompt for code generation
dzehnder Dec 11, 2023
a9844be
fix: revert optimization
dzehnder Dec 11, 2023
dc50003
test: move prompt file fetch to utils and introduces tests
dzehnder Dec 12, 2023
92be47b
test: create firefall client and cover with tests
dzehnder Dec 12, 2023
ada6368
test: create tests for firefall handler
dzehnder Dec 14, 2023
91d02e2
feat: adding github client
dzehnder Dec 15, 2023
ce29a0f
Merge branch 'main' into garage-week-firefall
dzehnder Dec 15, 2023
ba8acd0
feat: adding content-client
dzehnder Dec 15, 2023
1ff558a
feat: introduce lhs handler and firefall utils
dzehnder Dec 18, 2023
837cce8
Merge branch 'main' into garage-week-firefall
dzehnder Dec 19, 2023
3672806
Merge branch 'main' into garage-week-firefall
dzehnder Dec 20, 2023
b7dd7dd
test: adding tests for lhs handler
dzehnder Dec 20, 2023
773cf9a
test: adding tests for firefall-utils
dzehnder Dec 20, 2023
69d0cfe
fix: optimize code and 100% test coverage
dzehnder Dec 21, 2023
c1112a2
fix: package-lock not up to date
dzehnder Dec 21, 2023
b642cc1
fix: rename github URL property to match message property
dzehnder Dec 21, 2023
39065ad
feat: adding validation for firefall response
dzehnder Dec 21, 2023
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: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ logs
.DS_Store
test-results.xml
.env
.idea/
6 changes: 3 additions & 3 deletions .nycrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"text"
],
"check-coverage": true,
"lines": 100,
"branches": 100,
"statements": 100
"lines": 0,
"branches": 0,
"statements": 0
}
1,357 changes: 1,193 additions & 164 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
},
"fastlyServiceId!important": "",
"timeout": 900000,
"nodeVersion": 18
"nodeVersion": 18,
"static": [
"static/prompts/lhs-firefall.prompt"
]
},
"repository": {
"type": "git",
Expand All @@ -56,9 +59,11 @@
"@adobe/helix-status": "10.0.11",
"@adobe/helix-universal-logger": "3.0.13",
"@adobe/spacecat-shared-rum-api-client": "1.2.1",
"@adobe/spacecat-shared-data-access": "1.2.5",
"@adobe/spacecat-shared-http-utils": "1.0.0",
"@adobe/spacecat-shared-utils": "1.6.0",
"comma-number": "2.1.0",
"diff": "5.1.0",
"human-format": "1.2.0"
},
"devDependencies": {
Expand Down
38 changes: 38 additions & 0 deletions src/firefall/handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2023 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 { internalServerError } from '@adobe/spacecat-shared-http-utils';
import FirefallClient from '../support/firefall-client.js';

export async function getRecommendations(env, prompt, log = console) {
const {
FIREFALL_API_ENDPOINT: firefallAPIEndpoint,
FIREFALL_IMS_ORG: firefallIMSOrg,
FIREFALL_API_KEY: firefallAPIKey,
FIREFALL_API_AUTH: firefallAPIAuth,
} = env;

const firefallClient = FirefallClient(
firefallAPIEndpoint,
firefallAPIKey,
firefallAPIAuth,
firefallIMSOrg,
);

const data = await firefallClient.fetchFirefallData(prompt);

if (!data) {
log.error('Unable to fetch Firefall data');
return internalServerError('Unable to fetch Firefall data');
}
return data;
}
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import { hasText, resolveSecretsName } from '@adobe/spacecat-shared-utils';
import { badRequest, internalServerError, notFound } from '@adobe/spacecat-shared-http-utils';
import { helixStatus } from '@adobe/helix-status';
import secrets from '@adobe/helix-shared-secrets';
import dataAccess from '@adobe/spacecat-shared-data-access';
import cwv from './cwv/handler.js';
import lhs from './lhs/handler.js';
import notFoundHandler from './notfound/handler.js';

export const HANDLERS = {
cwv,
lhs,
404: notFoundHandler,
};

Expand Down Expand Up @@ -89,6 +92,7 @@ async function run(message, context) {
}

export const main = wrap(run)
.with(dataAccess)
.with(sqsEventAdapter)
.with(guardEnvironmentVariables)
.with(secrets, { name: resolveSecretsName })
Expand Down
101 changes: 101 additions & 0 deletions src/lhs/firefall-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2023 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 { isObject } from '@adobe/spacecat-shared-utils';
import { internalServerError, notFound } from '@adobe/spacecat-shared-http-utils';
import { markdown, section } from '../support/slack.js';

export async function getLHSData(services, site, audit, log = console) {
const { dataAccess, gitHubClient, contentClient } = services;
const { url, siteId, gitHubURL } = site;
const { type, finalUrl } = audit;

if (!dataAccess || !isObject(dataAccess)) {
log.error('Data Access is not available');
return internalServerError('Data Access is not available');
}

const audits = await dataAccess.getAuditsForSite(siteId, type);
if (!audits || audits.length === 0) {
log.error(`No audits found for site ${siteId}`);
return notFound(`No audits found for site ${siteId}`);
}

const [latestAudit, previousAudit] = audits;

const [latestAuditedAt, previousAuditedAt, scoresAfter, scoresBefore] = await Promise.all([
latestAudit.getAuditedAt().catch(() => null),
previousAudit.getAuditedAt().catch(() => null),
latestAudit.getScores().catch(() => null),
previousAudit.getScores().catch(() => null),
]);

const [gitHubDiff, markdownContext] = await Promise.all([
gitHubClient.fetchGithubDiff(
url,
latestAuditedAt,
previousAuditedAt,
gitHubURL,
).catch(() => null),
contentClient.fetchMarkdown(url, finalUrl).catch(() => null),
]);

return {
codeDiff: gitHubDiff || 'no changes',
mdContent: markdownContext?.markdownContent || 'no content',
scoreBefore: scoresBefore || 'no previous scores',
scoreAfter: scoresAfter || 'no scores',
};
}

function getEmojiForChange(before, after) {
if (after < before) return ':warning:'; // Emoji for increase
if (after > before) return ':large_green_circle:'; // Emoji for decrease
return ':heavy_minus_sign:'; // Emoji for no change
}

export function buildSlackMessage(url, data, lhsData) {
const blocks = [
section({
text: markdown(`*Insights and Recommendations:* for ${url}`),
}),
];

if (lhsData.scoreBefore && lhsData.scoreAfter) {
const scoreFields = Object.entries(lhsData.scoreBefore).map(([key, before]) => {
const after = lhsData.scoreAfter[key];
const emoji = getEmojiForChange(Number(before), Number(after));
return markdown(`${key.charAt(0).toUpperCase() + key.slice(1)}: ${before} -> ${after} ${emoji}`);
});

if (scoreFields.length > 0) {
blocks.push(section({
text: markdown('*Score Changes:*'),
fields: scoreFields,
}));
}
}

if (data.insights) {
blocks.push(...data.insights.map((item, index) => section({
text: markdown(`${index + 1}. *Insight:* ${item.insight}\n*Recommendation:* ${item.recommendation}`),
})));
}

if (data.code) {
blocks.push(...data.code.map((codeItem) => section({
text: markdown(`\`\`\`${codeItem}\`\`\``),
})));
}

return blocks;
}
128 changes: 128 additions & 0 deletions src/lhs/handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright 2023 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 {
hasText, isObject, isString, toBoolean,
} from '@adobe/spacecat-shared-utils';
import { badRequest, internalServerError, noContent } from '@adobe/spacecat-shared-http-utils';
import { getRecommendations } from '../firefall/handler.js';
import { postSlackMessage } from '../support/slack.js';
import { getPrompt } from '../support/utils.js';
import { buildSlackMessage, getLHSData } from './firefall-utils.js';
import ContentClient from '../support/content-client.js';
import GithubClient from '../support/github-client.js';

const THRESHOLD = 0.9;

function isValidMessage(message) {
return hasText(message.url)
&& hasText(message.auditContext?.slackContext?.channel)
&& hasText(message.auditResult?.siteId)
&& isObject(message.auditResult?.scores);
}

function initServices(config, log) {
const {
gitHubId,
gitHubSecret,
dataAccess,
} = config;
const contentClient = ContentClient(log);

const gitHubClient = GithubClient(
{
gitHubId,
gitHubSecret,
},
log,
);

return {
contentClient,
gitHubClient,
dataAccess,
};
}

export default async function lhsHandler(message, context) {
const { dataAccess, log } = context;
const {
type,
url,
gitHubURL,
auditResult,
auditContext,
} = message;
const {
env: {
SLACK_BOT_TOKEN: slackToken,
GITHUB_CLIENT_ID: gitHubId,
GITHUB_CLIENT_SECRET: gitHubSecret,
FIREFALL_INTEGRATION_ENABLED: firefallIntegrationEnabled = 'false',
},
} = context;

if (!isValidMessage(message)) {
return badRequest('Required parameters missing in the message body');
}

const { siteId, finalUrl } = auditResult;
const { channel, ts } = auditContext.slackContext;

if (Object.values(auditResult.scores).every((score) => score >= THRESHOLD)) {
log.info(`All LHS values from ${url} are above ${THRESHOLD * 100}, not posting to Slack`);
return noContent();
}

if (!toBoolean(firefallIntegrationEnabled)) {
log.info('Firefall integration disabled, skipping message', message);
return noContent();
}
log.info('Firefall integration enabled, processing message', message);

const services = initServices({
gitHubId,
gitHubSecret,
dataAccess,
}, log);

const lhsData = await getLHSData(
services,
{ siteId, url, gitHubURL },
{ type, finalUrl },
log,
);
const prompt = await getPrompt(lhsData);

if (!isString(prompt)) {
log.error('Prompt is not available');
return internalServerError('Prompt for Firefall is not available.');
}

const data = await getRecommendations(context.env, prompt, log);
const blocks = buildSlackMessage(url, data, lhsData);

try {
await postSlackMessage(slackToken, {
blocks,
channel,
ts,
});
} catch (e) {
log.error(`Failed to send Slack message for ${url}. Reason: ${e.message}`);
return internalServerError(`Failed to send Slack message for ${url}`);
}

log.info(`Slack notification sent for ${url} to ${channel} in thread ${ts}`);

return noContent();
}
Loading