Skip to content

Commit

Permalink
Improve report writing
Browse files Browse the repository at this point in the history
  • Loading branch information
akfreas committed Nov 24, 2023
1 parent 3326db4 commit b24b9e2
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 128 deletions.
Empty file added tangential/.prettierrc
Empty file.
1 change: 1 addition & 0 deletions tangential/iam-role.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
Resource:
- !GetAtt JiraAnalysisQueue.Arn
- !GetAtt UpdateProjectAnalysisStatusQueueFifo.Arn
- !GetAtt ReportGenerationQueue.Arn
2 changes: 1 addition & 1 deletion tangential/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@akfreas/tangential-core": "^1.0.18",
"@akfreas/tangential-core": "^1.0",
"@aws-sdk/client-sqs": "^3.438.0",
"aws-lambda": "^1.0.7",
"axios": "^1.6.2",
Expand Down
36 changes: 27 additions & 9 deletions tangential/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ provider:
environment:
jiraAnalysisQueueUrl: ${self:custom.jiraAnalysisQueueUrl.${self:provider.stage}, self:custom.jiraAnalysisQueueUrl.remote}
updateProjectAnalysisStatusQueueUrl: ${self:custom.updateProjectAnalysisStatusQueueUrl.${self:provider.stage}, self:custom.updateProjectAnalysisStatusQueueUrl.remote}
writeReportQueueUrl: ${self:custom.writeReportQueueUrl.${self:provider.stage}, self:custom.writeReportQueueUrl.remote}
textReportGenerationQueueUrl: ${self:custom.textReportGenerationQueueUrl.${self:provider.stage}, self:custom.textReportGenerationQueueUrl.remote}
OPENAI_API_KEY: ${env:OPENAI_API_KEY}
MONGODB_URI: ${env:MONGODB_URI}
MONGODB_DATABASE: tangential-${opt:stage}
Expand Down Expand Up @@ -58,10 +58,10 @@ custom:
remote:
Ref: UpdateProjectAnalysisStatusQueueFifo

writeReportQueueUrl:
offline: ${self:custom.offlineQueueUrl}/WriteReportQueue-${self:provider.stage}
textReportGenerationQueueUrl:
offline: ${self:custom.offlineQueueUrl}/ReportGenerationQueue-${self:provider.stage}
remote:
Ref: WriteReportQueue
Ref: ReportGenerationQueue

serverless-offline-sqs:
endpoint: http://localhost:4566
Expand All @@ -78,13 +78,31 @@ custom:
autoDomain: true

functions:
### HTTP API ###

startWorkspaceAnalysis:
handler: src/functions/startWorkspaceAnalysis.handler
events:
- httpApi:
method: get
path: /workspace/analyze

startTextReportGeneration:
handler: src/functions/startTextReportGeneration.handler
events:
- httpApi:
method: post
path: /generateReport

templates:
handler: src/functions/templates.handler
events:
- httpApi:
method: get
path: /templates

### Queue Handlers ###

analysisQueueHandler:
handler: src/functions/analysisQueueHandler.handler
events:
Expand All @@ -99,11 +117,11 @@ functions:
arn: !GetAtt UpdateProjectAnalysisStatusQueueFifo.Arn
batchSize: 5

writeReportQueueHandler:
handler: src/functions/writeReport.handler
ReportGenerationQueueHandler:
handler: src/functions/generateTextReport.handler
events:
- sqs:
arn: !GetAtt WriteReportQueue.Arn
arn: !GetAtt ReportGenerationQueue.Arn
batchSize: 5

resources:
Expand Down Expand Up @@ -140,10 +158,10 @@ resources:
FifoQueue: true
ContentBasedDeduplication: true

WriteReportQueue:
ReportGenerationQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: WriteReportQueue-${self:provider.stage}
QueueName: ReportGenerationQueue-${self:provider.stage}
RedrivePolicy:
deadLetterTargetArn: !GetAtt WriteReportDeadLetterQueue.Arn
maxReceiveCount: 2
Expand Down
135 changes: 135 additions & 0 deletions tangential/src/functions/generateTextReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { SQSHandler } from "aws-lambda";
import {
ProjectReport,
doLog,
extractFromJiraAuth,
fetchReportByBuildId,
fetchReportTemplateById,
jsonLog,
storeTextReport,
} from "@akfreas/tangential-core";
import { createChatCompletion } from "../utils/openAiWrapper";
import { DateTime } from "luxon";

async function writeReport(
projectReport: ProjectReport,
templateId: string,
owner: string
) {
// Extract basic project details
const projectName = projectReport.title;
const reportDate = projectReport.reportGenerationDate;

// Determine the overall project status
let projectStatus = "Unknown";
if (projectReport.analysis && projectReport.analysis.state) {
projectStatus = projectReport.analysis.state.name;
}

// Prepare the epic details
let epicDetails = "";
if (projectReport.epics) {
projectReport.epics.forEach((epic) => {
const epicName = epic.title;
const assignee = epic.assignee?.displayName || "Unassigned";
const epicStatus =
epic.analysis && epic.analysis.state
? epic.analysis.state.name
: "Unknown";
const details = epic.summary?.longSummary ?? "No additional details.";
const potentialRisks =
epic.summary?.potentialRisks ?? "No potential risks.";
const color = epic.summary?.color ?? "Unknown";
epicDetails += `Epic Name: ${epicName}\n Assignee: ${assignee}\n Status: ${epicStatus}\n Status Summary: ${details} \n Potential Risks: ${potentialRisks}\n Color: ${color}\n\n`;
});
}
const template = await fetchReportTemplateById(templateId);
// Construct the prompt
const system = `Generate a project status update report based on the following data:\n\n
The report should provide an overview of the project's overall status, detail the status and progress of each epic, highlight any delays or issues, and mention any discussions or plans for mitigation.
Sort the epics by status, with the most urgent epics first.
${
template ? `Write this report for the audience: ${template.audience}` : ""
}\n\n
Format the report similarly to the provided example below:
"Program Name: New Application Monitoring
🔴 Overall status of new application monitoring is red. Milestone 1 on October 3rd is likely not going to be held due to predicted delays in the monitoring infra epic. We are currently discussing with Paul from Team Pretzel if we can mitigate this problem by removing one nice-to have feature from Team Pretzel’s backlog.
Details:
🔴 Red: Monitoring infra by team Pretzel. This is the last epic required for Milestone 1 and is planned to start on September 24th. The schedule will likely slip, as team Pretzel is still blocked with work on the Infra cost reduction program due to added features and bugs into the epic there.
The other contributing epics are on track:
🟢 Green: Mobile monitoring by team Maverick. Work has finished on September 12th.
🟢 Green: Service layer by team Stardust. Work is 83% ready and based on the past velocity of the team, all user stories will be finished on September 27th.
🟢 Green: AuthN by team Maverick. Work is 81% ready and based on the past velocity of the team, all user stories will be finished on October 1st."
`;

const user = `
Project Name: ${projectName}\n
Report Date: ${reportDate}\n\n
Project Status: ${projectStatus}\n
Epic Statuses:\n${epicDetails}\n`;
doLog(`Writing report for project ${projectName}...`);
const report = await createChatCompletion({
jsonResponse: false,
messages: [
{
role: "system",
content: system,
},
{
role: "user",
content: user,
},
],
});
doLog(`Report: ${report}`);

await storeTextReport({
basedOnBuildId: projectReport.buildId,
text: report,
generatedOn: DateTime.local().toISO()!,
owner,
templateId,
name: `${projectName} Status Update${
template?.audience ? ` for ${template.audience}` : ""
}`,
projectName,
description: "Project Status Update",
});

return report;
}

export const handler: SQSHandler = async (event) => {
try {
for (const record of event.Records) {
const { auth, buildId, templateId } = JSON.parse(record.body);
if (!auth) {
throw new Error("No auth found");
}

if (!buildId) {
throw new Error("No build ID found");
}
const { atlassianUserId } = extractFromJiraAuth(auth);
jsonLog("Input", { auth, atlassianUserId, buildId });

const report: ProjectReport | null = await fetchReportByBuildId(
atlassianUserId,
buildId
);
// jsonLog("Report", report)
if (!report) {
throw new Error("Report not found");
}
await writeReport(report, templateId, atlassianUserId);
}
} catch (err) {
console.error(err);
throw new Error("Message processing failed");
}
};
37 changes: 37 additions & 0 deletions tangential/src/functions/startTextReportGeneration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { APIGatewayEvent } from "aws-lambda";
import { extractAtlassianHeaders } from "../utils/request";
import { sendTextReportGenerationQueueMessage } from "../utils/sqs";

export async function handler(
event: APIGatewayEvent
): Promise<{ statusCode: number; body: string }> {

const { headers, body } = event;

const auth = extractAtlassianHeaders(headers); // Assuming this function is defined elsewhere
try {
if (!body) {
throw new Error('No body provided');
}
// Parse the body to get buildId and templateId
const { buildId, templateId } = JSON.parse(body);

if (!buildId || !templateId) {
throw new Error('Missing buildId or templateId');
}

// Send message to the queue
await sendTextReportGenerationQueueMessage(buildId, templateId, auth);

return {
statusCode: 200,
body: 'Success'
};
} catch (err) {
console.error(err);
return {
statusCode: 500,
body: 'Error'
};
}
}
29 changes: 29 additions & 0 deletions tangential/src/functions/templates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
extractFromJiraAuth,
fetchAllReportTemplatesByOwnerAndPublic,
} from "@akfreas/tangential-core";
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import { extractAtlassianHeaders } from "../utils/request";

export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
try {
const auth = extractAtlassianHeaders(event.headers);
const { atlassianUserId } = await extractFromJiraAuth(auth);

const templates = await fetchAllReportTemplatesByOwnerAndPublic(
atlassianUserId
);

return {
statusCode: 200,
body: JSON.stringify(templates),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ message: "Internal Server Error" }),
};
}
};
73 changes: 0 additions & 73 deletions tangential/src/functions/writeReport.ts

This file was deleted.

Loading

0 comments on commit b24b9e2

Please sign in to comment.