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

feat: Add payment exemption status to Slack notifications #2251

Merged
merged 7 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import supertest from "supertest";
import app from "../server";
import { createScheduledEvent } from "../hasura/metadata";
import app from "../../../server";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the new API structure leads to a lot of long/messy relative import paths we could look at resolving this with path aliases - https://blog.logrocket.com/using-path-aliases-cleaner-react-typescript-imports/

import { createScheduledEvent } from "../../../hasura/metadata";

const { post } = supertest(app);

jest.mock("../hasura/metadata");
jest.mock("../../../hasura/metadata");
const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction<
typeof createScheduledEvent
>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { addDays } from "date-fns";
import { Request, Response, NextFunction } from "express";

import { createScheduledEvent } from "../hasura/metadata";
import { createScheduledEvent } from "../../../hasura/metadata";
import {
DAYS_UNTIL_EXPIRY,
REMINDER_DAYS_FROM_EXPIRY,
} from "../saveAndReturn/utils";
} from "../../../saveAndReturn/utils";

/**
* Create "reminder" events for a lowcal_session record
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import supertest from "supertest";
import app from "../server";
import { createScheduledEvent } from "../hasura/metadata";
import app from "../../../server";
import { createScheduledEvent } from "../../../hasura/metadata";

const { post } = supertest(app);

jest.mock("../hasura/metadata");
jest.mock("../../../hasura/metadata");
const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction<
typeof createScheduledEvent
>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { addDays } from "date-fns";
import { Request, Response, NextFunction } from "express";

import { createScheduledEvent } from "../hasura/metadata";
import { createScheduledEvent } from "../../../hasura/metadata";
import {
DAYS_UNTIL_EXPIRY,
REMINDER_DAYS_FROM_EXPIRY,
} from "../saveAndReturn/utils";
} from "../../../saveAndReturn/utils";

/**
* Create two "invitation" events for a payments_request record: one for the nominee and one for the agent
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import supertest from "supertest";
import app from "../../server";
import app from "../../../../server";
import * as operations from "./operations";

const mockSend = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Request, Response, NextFunction } from "express";

import { getOperations, operationHandler } from "./operations";
import { OperationResult } from "./types";
import { getFormattedEnvironment } from "../../helpers";
import { getFormattedEnvironment } from "../../../../helpers";

/**
* Called by Hasura cron job `sanitise_application_data` on a nightly basis
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { runSQL } from "../../hasura/schema";
import { queryMock } from "../../tests/graphqlQueryMock";
import { runSQL } from "../../../../hasura/schema";
import { queryMock } from "../../../../tests/graphqlQueryMock";
import {
mockIds,
mockSanitiseBOPSApplicationsMutation,
Expand All @@ -25,11 +25,11 @@ import {
deleteHasuraScheduledEventsForSubmittedSessions,
} from "./operations";

jest.mock("../../hasura/schema");
jest.mock("../../../../hasura/schema");
const mockRunSQL = runSQL as jest.MockedFunction<typeof runSQL>;

const mockFindSession = jest.fn();
jest.mock("../../client", () => {
jest.mock("../../../../client", () => {
return {
$admin: {
session: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { gql } from "graphql-request";
import { subMonths } from "date-fns";

import { Operation, OperationResult } from "./types";
import { adminGraphQLClient } from "../../hasura";
import { runSQL } from "../../hasura/schema";
import { getFilesForSession } from "../../session/files";
import { deleteFilesByURL } from "../../s3/deleteFile";
import { adminGraphQLClient } from "../../../../hasura";
import { runSQL } from "../../../../hasura/schema";
import { getFilesForSession } from "../../../../session/files";
import { deleteFilesByURL } from "../../../../s3/deleteFile";

const RETENTION_PERIOD_MONTHS = 6;
export const getRetentionPeriod = () =>
Expand Down
31 changes: 31 additions & 0 deletions api.planx.uk/modules/webhooks/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ServerError } from "../../errors";
import { sendSlackNotification } from "./sendNotification/service";
import { SendSlackNotification } from "./sendNotification/types";

export const sendSlackNotificationController: SendSlackNotification = async (
req,
res,
next,
) => {
const isProduction = process.env.APP_ENVIRONMENT === "production";
if (!isProduction) {
return res.status(200).send({
message: `Staging application submitted, skipping Slack notification`,
});
}

const eventData = req.body.event.data.new;
const eventType = req.query.type;

try {
const data = await sendSlackNotification(eventData, eventType);
return res.status(200).send({ message: "Posted to Slack", data });
} catch (error) {
return next(
new ServerError({
message: `Failed to send ${eventType} Slack notification`,
cause: error,
}),
);
}
};
135 changes: 135 additions & 0 deletions api.planx.uk/modules/webhooks/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
openapi: 3.1.0
info:
title: Plan✕ API
version: 0.1.0
tags:
- name: webhooks
description: Webhooks for event management
components:
schemas:
Payload:
type: object
properties:
sessionId:
type: string
required:
- sessionId
BopsSubmissionSchema:
type: object
properties:
body:
type: object
properties:
event:
type: object
properties:
data:
type: object
properties:
new:
type: object
properties:
payload:
$ref: "#/components/schemas/Payload"
bops_id:
type: string
destination_url:
type: string
required:
- body
UniformSubmissionSchema:
type: object
properties:
body:
type: object
properties:
event:
type: object
properties:
data:
type: object
properties:
new:
type: object
properties:
payload:
$ref: "#/components/schemas/Payload"
submission_reference:
type: string
response:
type: object
properties:
organisation:
type: string
required:
- body
EmailSubmissionSchema:
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
request:
type: object
properties:
personalisation:
type: object
properties:
serviceName:
type: string
required:
- body
SendSlackNotificationSchema:
oneOf:
- $ref: "#/components/schemas/BopsSubmissionSchema"
- $ref: "#/components/schemas/UniformSubmissionSchema"
- $ref: "#/components/schemas/EmailSubmissionSchema"
responses:
SlackNotificationSuccessMessage:
content:
application/json:
schema:
type: object
properties:
data:
type: string
required: false
description: The generated Slack message
message:
type: string
required: true
paths:
/webhooks/hasura/sendSlackNotification:
post:
tags: ["webhooks"]
summary: Send Slack notification
description: Endpoint to trigger a Slack notification following a submission event
parameters:
- in: query
name: type
type: string
enum: ["bops-submission", "uniform-submission", "email-submission"]
required: true
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/SendSlackNotificationSchema"
responses:
"200":
$ref: "#/components/responses/SlackNotificationSuccessMessage"
"500":
$ref: "#/components/responses/ErrorMessage"
40 changes: 40 additions & 0 deletions api.planx.uk/modules/webhooks/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Router } from "express";
import { useHasuraAuth } from "../auth/middleware";
import { createPaymentSendEvents } from "../../inviteToPay/createPaymentSendEvents";
import { sanitiseApplicationData } from "./_old/sanitiseApplicationData";
import {
createExpiryEvent,
createReminderEvent,
} from "./_old/lowcalSessionEvents";
import {
createPaymentExpiryEvents,
createPaymentInvitationEvents,
createPaymentReminderEvents,
} from "./_old/paymentRequestEvents";
import { validate } from "../../shared/middleware/validate";
import { sendSlackNotificationController } from "./controller";
import { sendSlackNotificationSchema } from "./sendNotification/schema";

const router = Router();

router.use("/hasura", useHasuraAuth);
router.post("/hasura/create-reminder-event", createReminderEvent);
router.post("/hasura/create-expiry-event", createExpiryEvent);
router.post(
"/hasura/create-payment-invitation-events",
createPaymentInvitationEvents,
);
router.post(
"/hasura/create-payment-reminder-events",
createPaymentReminderEvents,
);
router.post("/hasura/create-payment-expiry-events", createPaymentExpiryEvents);
router.post("/hasura/create-payment-send-events", createPaymentSendEvents);
router.post(
"/hasura/send-slack-notification",
validate(sendSlackNotificationSchema),
sendSlackNotificationController,
);
router.post("/hasura/sanitise-application-data", sanitiseApplicationData);

export default router;
Loading
Loading