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: check if changes have been made before publishing a new flow #574

Merged
merged 7 commits into from
Aug 13, 2021
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
1 change: 0 additions & 1 deletion api.planx.uk/gis/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const makeEsriUrl = (domain, id, serverIndex = 0, overrideParams = {}) => {
.map((key) => key + "=" + escape(params[key]))
.join("&"),
].join("?");
console.log({ url });

return url;
};
Expand Down
2 changes: 0 additions & 2 deletions api.planx.uk/gis/local_authorities/southwark.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ async function locationSearch(x, y, extras) {
: false,
};

console.log(responses);

responses
.filter(([_key, result]) => result instanceof Error)
.forEach(([key, _result]) => {
Expand Down
23 changes: 1 addition & 22 deletions api.planx.uk/jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,7 @@
const { QueryMock } = require("graphql-query-test-mock");

require("dotenv").config({ path: "./.env.test" });

const queryMock = new QueryMock();
const { queryMock } = require("./tests/graphqlQueryMock");

beforeEach(() => {
queryMock.setup(process.env.HASURA_GRAPHQL_URL);

queryMock.mockQuery({
name: "GetTeams",
data: {
teams: [{ id: 1 }],
},
});

queryMock.mockQuery({
name: "CreateApplication",
data: {
insert_bops_applications_one: { id: 22 },
},
matchOnVariables: false,
variables: {
destination_url:
"https://southwark.bops.services/api/v1/planning_applications",
},
});
});
1 change: 1 addition & 0 deletions api.planx.uk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"http-proxy-middleware": "^1.3.1",
"https": "^1.0.0",
"isomorphic-fetch": "^2.2.1",
"jsondiffpatch": "^0.4.1",
"jsonwebtoken": "^8.5.1",
"mime": "^2.4.6",
"nanoid": "^3.1.12",
Expand Down
99 changes: 70 additions & 29 deletions api.planx.uk/publish.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { GraphQLClient } = require("graphql-request");
const jsondiffpatch = require("jsondiffpatch");

const client = new GraphQLClient(process.env.HASURA_GRAPHQL_URL, {
headers: {
Expand All @@ -21,6 +22,26 @@ const getFlowData = async (id) => {
return data.flows_by_pk.data;
};

const getMostRecentPublishedFlow = async (id) => {
const data = await client.request(
`
query GetMostRecentPublishedFlow($id: uuid!) {
flows_by_pk(id: $id) {
published_flows(limit: 1, order_by: { id: desc }) {
data
}
}
}
`,
{ id }
);

return (
data.flows_by_pk.published_flows[0] &&
data.flows_by_pk.published_flows[0].data
);
};

// XXX: getFlowData & dataMerged are currently repeated in ../editor.planx.uk/src/lib/dataMergedHotfix.ts
// in order to load previews for flows that have not been published yet
const dataMerged = async (id, ob = {}) => {
Expand Down Expand Up @@ -50,38 +71,58 @@ const dataMerged = async (id, ob = {}) => {
const publishFlow = async (req, res) => {
if (!req.user?.sub)
return res.status(401).json({ error: "User ID missing from JWT" });

const flattenedFlow = await dataMerged(req.params.flowId);

const publishedFlow = await client.request(
`
mutation PublishFlow(
$data: jsonb = {},
$flow_id: uuid,
$publisher_id: Int,
) {
insert_published_flows_one(object: {
data: $data,
flow_id: $flow_id,
publisher_id: $publisher_id,
}) {
id
flow_id
publisher_id
created_at
data
try {
const flattenedFlow = await dataMerged(req.params.flowId);
const mostRecent = await getMostRecentPublishedFlow(req.params.flowId);

const delta = jsondiffpatch.diff(mostRecent, flattenedFlow);

if (delta) {
const response = await client.request(
`
mutation PublishFlow(
$data: jsonb = {},
$flow_id: uuid,
$publisher_id: Int,
) {
insert_published_flows_one(object: {
data: $data,
flow_id: $flow_id,
publisher_id: $publisher_id,
}) {
id
flow_id
publisher_id
created_at
data
}
}
`,
{
data: flattenedFlow,
flow_id: req.params.flowId,
publisher_id: parseInt(req.user.sub, 10),
}
}`,
{
data: flattenedFlow,
flow_id: req.params.flowId,
publisher_id: parseInt(req.user.sub, 10),
}
);
);
const publishedFlow =
response.insert_published_flows_one &&
response.insert_published_flows_one.data;

try {
// return published flow record
res.json(publishedFlow.insert_published_flows_one);
const alteredNodes = Object.keys(delta).map((key) => ({
id: key,
...publishedFlow[key],
}));

res.json({
alteredNodes,
});
} else {
res.json({
alteredNodes: null,
message: "No new changes",
});
}
} catch (error) {
console.error(error);
res.status(500).json({ error });
Expand Down
217 changes: 217 additions & 0 deletions api.planx.uk/publish.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
const supertest = require("supertest");

const { queryMock } = require("./tests/graphqlQueryMock");
const { authHeader } = require("./tests/mockJWT");
const app = require("./server");

beforeEach(() => {
queryMock.mockQuery({
name: "GetFlowData",
matchOnVariables: false,
data: {
flows_by_pk: {
data: mockFlowData,
},
},
});

queryMock.mockQuery({
name: "GetMostRecentPublishedFlow",
matchOnVariables: false,
data: {
flows_by_pk: {
published_flows: [
{
data: mockFlowData,
},
],
},
},
});

queryMock.mockQuery({
name: "PublishFlow",
matchOnVariables: false,
data: {
insert_published_flows_one: {
data: mockFlowData,
},
},
});
});

it("does not update if there are no new changes", async () => {
await supertest(app)
.post("/flows/1/publish")
.set(authHeader())
.expect(200)
.then((res) => {
expect(res.body).toEqual({
alteredNodes: null,
message: "No new changes",
});
});
});

it("updates published flow and returns altered nodes if there have been changes", async () => {
const alteredFlow = {
...mockFlowData,
"4CJgXe8Ttl": {
data: {
flagSet: "Planning permission",
overrides: {
NO_APP_REQUIRED: {
heading: "Some Other Heading",
},
},
},
type: 3,
},
};

queryMock.mockQuery({
name: "GetFlowData",
matchOnVariables: false,
data: {
flows_by_pk: {
data: alteredFlow,
},
},
});

queryMock.mockQuery({
name: "PublishFlow",
matchOnVariables: false,
data: {
insert_published_flows_one: {
data: alteredFlow,
},
},
});

await supertest(app)
.post("/flows/1/publish")
.set(authHeader())
.expect(200)
.then((res) => {
expect(res.body).toEqual({
alteredNodes: [
{
id: "4CJgXe8Ttl",
type: 3,
data: {
flagSet: "Planning permission",
overrides: {
NO_APP_REQUIRED: {
heading: "Some Other Heading",
},
},
},
},
],
});
});
});

const mockFlowData = {
_root: {
edges: [
"RYYckLE2cH",
"R99ncwKifm",
"3qssvGXmMO",
"SEp0QeNsTS",
"q8Foul9hRN",
"4CJgXe8Ttl",
"dnVqd6zt4N",
],
},
"3qssvGXmMO": {
type: 9,
},
"4CJgXe8Ttl": {
data: {
flagSet: "Planning permission",
overrides: {
NO_APP_REQUIRED: {
heading: "Congratulations!",
},
},
},
type: 3,
},
"5sWfsvXphd": {
data: {
text: "?",
},
type: 200,
},
BV2VJhOC0I: {
data: {
text: "internal question",
},
type: 100,
edges: ["ScjaYmpbVK", "b7j9tq22dj"],
},
OL9JENldcI: {
data: {
text: "!!",
},
type: 200,
},
R99ncwKifm: {
data: {
text: "portal",
},
type: 300,
edges: ["BV2VJhOC0I"],
},
RYYckLE2cH: {
data: {
text: "Question",
},
type: 100,
edges: ["5sWfsvXphd", "OL9JENldcI"],
},
SEp0QeNsTS: {
data: {
fn: "application.fee.payable",
url: "http://localhost:7002/pay",
color: "#EFEFEF",
title: "Pay for your application",
description:
'<p>The planning fee covers the cost of processing your application. Find out more about how planning fees are calculated <a href="https://www.gov.uk/guidance/fees-for-planning-applications" target="_self">here</a>.</p>',
},
type: 400,
},
ScjaYmpbVK: {
data: {
text: "?",
},
type: 200,
},
b7j9tq22dj: {
data: {
text: "*",
},
type: 200,
},
dnVqd6zt4N: {
data: {
heading: "Application sent",
moreInfo:
"<h2>You will be contacted</h2>\n<ul>\n<li>if there is anything missing from the information you have provided so far</li>\n<li>if any additional information is required</li>\n<li>to arrange a site visit, if required</li>\n<li>to inform you whether a certificate has been granted or not</li>\n</ul>\n",
contactInfo:
'<p>You can contact us at <a href="mailto:planning@lambeth.gov.uk" target="_self"><strong>planning@lambeth.gov.uk</strong></a></p>\n',
description:
"A payment receipt has been emailed to you. You will also receive an email to confirm when your application has been received.",
feedbackCTA: "What did you think of this service? (takes 30 seconds)",
},
type: 725,
},
q8Foul9hRN: {
data: {
url: "http://localhost:7002/bops/southwark",
},
type: 650,
},
};
Loading