Skip to content

Commit

Permalink
chore: fix audit entries and setup Slack notifications for production…
Browse files Browse the repository at this point in the history
… S3 + Power Automate submissions (#3439)
  • Loading branch information
jessicamcinchak authored Jul 19, 2024
1 parent eb9b358 commit 513a891
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 16 deletions.
3 changes: 1 addition & 2 deletions api.planx.uk/modules/send/email/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { NextFunction, Request, Response } from "express";
import capitalize from "lodash/capitalize";
import { markSessionAsSubmitted } from "../../saveAndReturn/service/utils";
import { sendEmail } from "../../../lib/notify";
import { EmailSubmissionNotifyConfig } from "../../../types";
import { markSessionAsSubmitted } from "../../saveAndReturn/service/utils";
import {
getSessionEmailDetailsById,
getTeamEmailSettings,
Expand Down
22 changes: 10 additions & 12 deletions api.planx.uk/modules/send/s3/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from "axios";
import axios, { AxiosRequestConfig } from "axios";
import type { NextFunction, Request, Response } from "express";
import { gql } from "graphql-request";

import { $api } from "../../../client";
import { Passport } from "../../../types";
import { uploadPrivateFile } from "../../file/service/uploadFile";
Expand Down Expand Up @@ -60,8 +61,8 @@ const sendToS3 = async (req: Request, res: Response, next: NextFunction) => {
`${sessionId}.json`,
);

// Send a notification with the file URL to the Power Automate webook
const webhookRequest = {
// Send a notification with the file URL to the Power Automate webhook
const webhookRequest: AxiosRequestConfig = {
method: "POST",
url: powerAutomateWebhookURL,
adapter: "http",
Expand All @@ -76,11 +77,8 @@ const sendToS3 = async (req: Request, res: Response, next: NextFunction) => {
payload: doValidation ? "Validated ODP Schema" : "Discretionary",
},
};
let webhookResponseStatus: number | undefined;
await axios(webhookRequest)
const webhookResponse = await axios(webhookRequest)
.then(async (res) => {
webhookResponseStatus = res.status;

// Mark session as submitted so that reminder and expiry emails are not triggered
markSessionAsSubmitted(sessionId);

Expand Down Expand Up @@ -114,9 +112,8 @@ const sendToS3 = async (req: Request, res: Response, next: NextFunction) => {
);

return {
application: {
...applicationId.insertS3Application,
},
id: applicationId.insertS3Application?.id,
axiosResponse: res,
};
})
.catch((error) => {
Expand All @@ -125,10 +122,11 @@ const sendToS3 = async (req: Request, res: Response, next: NextFunction) => {
);
});

return res.status(200).send({
res.status(200).send({
message: `Successfully uploaded submission to S3: ${fileUrl}`,
payload: doValidation ? "Validated ODP Schema" : "Discretionary",
webhookResponse: webhookResponseStatus,
webhookResponse: webhookResponse.axiosResponse.status,
auditEntryId: webhookResponse.id,
});
} catch (error) {
return next({
Expand Down
22 changes: 22 additions & 0 deletions api.planx.uk/modules/webhooks/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,33 @@ components:
type: string
required:
- body
S3Submission:
type: object
properties:
body:
type: object
properties:
event:
type: object
properties:
data:
type: object
properties:
new:
type: object
properties:
session_id:
type: string
team_slug:
type: string
required:
- body
SendSlackNotification:
oneOf:
- $ref: "#/components/schemas/BopsSubmission"
- $ref: "#/components/schemas/UniformSubmission"
- $ref: "#/components/schemas/EmailSubmission"
- $ref: "#/components/schemas/S3Submission"
CreatePaymentEvent:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import supertest from "supertest";
import app from "../../../../server";
import SlackNotify from "slack-notify";
import { BOPSBody, EmailBody, UniformBody } from "./types";
import { BOPSBody, EmailBody, S3Body, UniformBody } from "./types";
import { $api } from "../../../../client";
import { CoreDomainClient } from "@opensystemslab/planx-core";

Expand Down Expand Up @@ -340,4 +340,63 @@ describe("Send Slack notifications endpoint", () => {
});
});
});

describe("S3 notifications", () => {
const body: S3Body = {
event: {
data: {
new: {
session_id: "s3-pa-123",
team_slug: "test-team",
},
},
},
};

it("skips the staging environment", async () => {
process.env.APP_ENVIRONMENT = "staging";
await post(ENDPOINT)
.query({ type: "s3-submission" })
.set({ Authorization: process.env.HASURA_PLANX_API_KEY! })
.send(body)
.expect(200)
.then((response) => {
expect(response.body.message).toMatch(/skipping Slack notification/);
});
});

it("posts to Slack on success", async () => {
process.env.APP_ENVIRONMENT = "production";

await post(ENDPOINT)
.query({ type: "s3-submission" })
.set({ Authorization: process.env.HASURA_PLANX_API_KEY! })
.send(body)
.expect(200)
.then((response) => {
expect(SlackNotify).toHaveBeenCalledWith(
process.env.SLACK_WEBHOOK_URL,
);
expect(mockSend).toHaveBeenCalledTimes(1);
expect(response.body.message).toBe("Posted to Slack");
expect(response.body.data).toMatch(/s3-pa-123/);
expect(response.body.data).toMatch(/test-team/);
});
});

it("returns error when Slack fails", async () => {
process.env.APP_ENVIRONMENT = "production";
mockSend.mockRejectedValue("Fail!");

await post(ENDPOINT)
.query({ type: "s3-submission" })
.set({ Authorization: process.env.HASURA_PLANX_API_KEY! })
.send(body)
.expect(500)
.then((response) => {
expect(mockSend).toHaveBeenCalledTimes(1);
expect(response.body.error).toMatch(/Failed to send/);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EmailEventData,
EventData,
EventType,
S3EventData,
UniformEventData,
} from "./types";
import { $api } from "../../../../client";
Expand Down Expand Up @@ -41,13 +42,19 @@ const getMessageForEventType = (data: EventData, type: EventType) => {
const { request, session_id, team_slug } = data as EmailEventData;
return `New email submission "${request.personalisation.serviceName}" *${session_id}* [${team_slug}]`;
}

if (type === "s3-submission") {
const { session_id, team_slug } = data as S3EventData;
return `New S3 + Power Automate submission *${session_id}* [${team_slug}]`;
}
};

const getSessionIdFromEvent = (data: EventData, type: EventType) =>
({
"bops-submission": (data as BOPSEventData).session_id,
"uniform-submission": (data as UniformEventData).payload?.sessionId,
"email-submission": (data as EmailEventData).session_id,
"s3-submission": (data as S3EventData).session_id,
})[type];

const getExemptionStatusesForSession = async (sessionId: string) => {
Expand Down
17 changes: 17 additions & 0 deletions api.planx.uk/modules/webhooks/service/sendNotification/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,25 @@ export const emailSubmissionSchema = z.object({
}),
});

export const s3SubmissionSchema = z.object({
body: z.object({
event: z.object({
data: z.object({
new: z.object({
session_id: z.string(),
team_slug: z.string(),
}),
}),
}),
}),
query: z.object({
type: z.literal("s3-submission"),
}),
});

export const sendSlackNotificationSchema = z.union([
bopsSubmissionSchema,
uniformSubmissionSchema,
emailSubmissionSchema,
s3SubmissionSchema,
]);
10 changes: 9 additions & 1 deletion api.planx.uk/modules/webhooks/service/sendNotification/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ValidatedRequestHandler } from "../../../../shared/middleware/validate"
import {
bopsSubmissionSchema,
emailSubmissionSchema,
s3SubmissionSchema,
sendSlackNotificationSchema,
uniformSubmissionSchema,
} from "./schema";
Expand All @@ -25,7 +26,14 @@ export type UniformEventData = UniformBody["event"]["data"]["new"];
export type EmailBody = z.infer<typeof emailSubmissionSchema>["body"];
export type EmailEventData = EmailBody["event"]["data"]["new"];

export type EventData = BOPSEventData | UniformEventData | EmailEventData;
export type S3Body = z.infer<typeof s3SubmissionSchema>["body"];
export type S3EventData = S3Body["event"]["data"]["new"];

export type EventData =
| BOPSEventData
| UniformEventData
| EmailEventData
| S3EventData;

export type SendSlackNotification = ValidatedRequestHandler<
typeof sendSlackNotificationSchema,
Expand Down
21 changes: 21 additions & 0 deletions hasura.planx.uk/metadata/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,27 @@
filter: {}
check: null
comment: ""
event_triggers:
- name: setup_s3_applications_notifications
definition:
enable_manual: false
insert:
columns: '*'
retry_conf:
interval_sec: 30
num_retries: 1
timeout_sec: 60
webhook: '{{HASURA_PLANX_API_URL}}'
headers:
- name: authorization
value_from_env: HASURA_PLANX_API_KEY
request_transform:
method: POST
query_params:
type: s3-submission
template_engine: Kriti
url: '{{$base_url}}/webhooks/hasura/send-slack-notification'
version: 2
- table:
name: sessions
schema: public
Expand Down

0 comments on commit 513a891

Please sign in to comment.