Skip to content

Commit

Permalink
chore: Fix Run API test type warnings (#2343)
Browse files Browse the repository at this point in the history
* chore: Fix Run API test type warnings

* chore: Misc 'any' type tidy ups
  • Loading branch information
DafyddLlyr authored Oct 27, 2023
1 parent f064283 commit 6693053
Show file tree
Hide file tree
Showing 15 changed files with 115 additions and 69 deletions.
17 changes: 9 additions & 8 deletions api.planx.uk/editor/publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import supertest from "supertest";
import { queryMock } from "../tests/graphqlQueryMock";
import { authHeader } from "../tests/mockJWT";
import app from "../server";
import { Flow } from "../types";
import { flowWithInviteToPay } from "../tests/mocks/inviteToPayData";
import { FlowGraph } from "@opensystemslab/planx-core/types";

beforeEach(() => {
queryMock.mockQuery({
Expand Down Expand Up @@ -168,7 +168,7 @@ describe("sections validation on diff", () => {
});

it("does not update if there are sections, but there is not a section in the first position", async () => {
const flowWithSections: Flow["data"] = {
const flowWithSections: FlowGraph = {
_root: {
edges: ["questionNode", "sectionNode"],
},
Expand Down Expand Up @@ -205,7 +205,7 @@ describe("sections validation on diff", () => {

describe("invite to pay validation on diff", () => {
it("does not update if invite to pay is enabled, but there is not a Send component", async () => {
const { Send, ...invalidatedFlow } = flowWithInviteToPay;
const { Send: _Send, ...invalidatedFlow } = flowWithInviteToPay;
invalidatedFlow["_root"].edges?.splice(
invalidatedFlow["_root"].edges?.indexOf("Send"),
);
Expand Down Expand Up @@ -266,7 +266,8 @@ describe("invite to pay validation on diff", () => {
});

it("does not update if invite to pay is enabled, but there is not a FindProperty (`_address`) component", async () => {
const { FindProperty, ...invalidatedFlow } = flowWithInviteToPay;
const { FindProperty: _FindProperty, ...invalidatedFlow } =
flowWithInviteToPay;
invalidatedFlow["_root"].edges?.splice(
invalidatedFlow["_root"].edges?.indexOf("FindProperty"),
);
Expand Down Expand Up @@ -326,9 +327,9 @@ describe("invite to pay validation on diff", () => {

it("does not update if invite to pay is enabled, but there is not a Checklist that sets `proposal.projectType`", async () => {
const {
Checklist,
ChecklistOptionOne,
ChecklistOptionTwo,
Checklist: _Checklist,
ChecklistOptionOne: _ChecklistOptionOne,
ChecklistOptionTwo: _ChecklistOptionTwo,
...invalidatedFlow
} = flowWithInviteToPay;
invalidatedFlow["_root"].edges?.splice(
Expand Down Expand Up @@ -358,7 +359,7 @@ describe("invite to pay validation on diff", () => {
});
});

const mockFlowData: Flow["data"] = {
const mockFlowData: FlowGraph = {
_root: {
edges: [
"SectionOne",
Expand Down
91 changes: 57 additions & 34 deletions api.planx.uk/editor/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { adminGraphQLClient as adminClient } from "../hasura";
import { dataMerged, getMostRecentPublishedFlow } from "../helpers";
import { gql } from "graphql-request";
import intersection from "lodash/intersection";
import { ComponentType } from "@opensystemslab/planx-core/types";
import {
ComponentType,
FlowGraph,
Node,
} from "@opensystemslab/planx-core/types";
import { userContext } from "../modules/auth/middleware";
import type { Entry } from "type-fest";

const validateAndDiffFlow = async (
req: Request,
Expand Down Expand Up @@ -139,17 +144,17 @@ type ValidationResponse = {
description?: string;
};

const validateSections = (flow: Record<string, any>): ValidationResponse => {
if (getSectionNodeIds(flow)?.length > 0) {
if (!sectionIsInFirstPosition(flow)) {
const validateSections = (flowGraph: FlowGraph): ValidationResponse => {
if (getSectionNodeIds(flowGraph)?.length > 0) {
if (!sectionIsInFirstPosition(flowGraph)) {
return {
isValid: false,
message: "Cannot publish an invalid flow",
description: "When using Sections, your flow must start with a Section",
};
}

if (!allSectionsOnRoot(flow)) {
if (!allSectionsOnRoot(flowGraph)) {
return {
isValid: false,
message: "Cannot publish an invalid flow",
Expand All @@ -165,57 +170,58 @@ const validateSections = (flow: Record<string, any>): ValidationResponse => {
};
};

const getSectionNodeIds = (flow: Record<string, any>): string[] => {
return Object.entries(flow)
.filter(([_nodeId, nodeData]) => nodeData?.type === ComponentType.Section)
?.map(([nodeId, _nodeData]) => nodeId);
const getSectionNodeIds = (flowGraph: FlowGraph): string[] => {
const sectionNodes = Object.entries(flowGraph).filter((entry) =>
isComponentType(entry, ComponentType.Section),
);
return sectionNodes.map(([nodeId, _nodeData]) => nodeId);
};

const sectionIsInFirstPosition = (flow: Record<string, any>): boolean => {
const firstNodeId = flow["_root"].edges[0];
return flow[firstNodeId].type === ComponentType.Section;
const sectionIsInFirstPosition = (flowGraph: FlowGraph): boolean => {
const firstNodeId = flowGraph["_root"].edges[0];
return flowGraph[firstNodeId].type === ComponentType.Section;
};

const allSectionsOnRoot = (flow: Record<string, any>): boolean => {
const sectionTypeNodeIds = getSectionNodeIds(flow);
const allSectionsOnRoot = (flowData: FlowGraph): boolean => {
const sectionTypeNodeIds = getSectionNodeIds(flowData);
const intersectingNodeIds = intersection(
flow["_root"].edges,
flowData["_root"].edges,
sectionTypeNodeIds,
);
return intersectingNodeIds.length === sectionTypeNodeIds.length;
};

const validateInviteToPay = (flow: Record<string, any>): ValidationResponse => {
const validateInviteToPay = (flowGraph: FlowGraph): ValidationResponse => {
const invalidResponseTemplate = {
isValid: false,
message: "Cannot publish an invalid flow",
};

if (inviteToPayEnabled(flow)) {
if (numberOfComponentType(flow, ComponentType.Pay) > 1) {
if (inviteToPayEnabled(flowGraph)) {
if (numberOfComponentType(flowGraph, ComponentType.Pay) > 1) {
return {
...invalidResponseTemplate,
description:
"When using Invite to Pay, your flow must have exactly ONE Pay",
};
}

if (!hasComponentType(flow, ComponentType.Send)) {
if (!hasComponentType(flowGraph, ComponentType.Send)) {
return {
...invalidResponseTemplate,
description: "When using Invite to Pay, your flow must have a Send",
};
}

if (numberOfComponentType(flow, ComponentType.Send) > 1) {
if (numberOfComponentType(flowGraph, ComponentType.Send) > 1) {
return {
...invalidResponseTemplate,
description:
"When using Invite to Pay, your flow must have exactly ONE Send. It can select many destinations",
};
}

if (!hasComponentType(flow, ComponentType.FindProperty)) {
if (!hasComponentType(flowGraph, ComponentType.FindProperty)) {
return {
...invalidResponseTemplate,
description:
Expand All @@ -224,7 +230,11 @@ const validateInviteToPay = (flow: Record<string, any>): ValidationResponse => {
}

if (
!hasComponentType(flow, ComponentType.Checklist, "proposal.projectType")
!hasComponentType(
flowGraph,
ComponentType.Checklist,
"proposal.projectType",
)
) {
return {
...invalidResponseTemplate,
Expand All @@ -241,27 +251,40 @@ const validateInviteToPay = (flow: Record<string, any>): ValidationResponse => {
};
};

const inviteToPayEnabled = (flow: Record<string, any>): boolean => {
const payNodeStatuses = Object.entries(flow)
.filter(([_nodeId, nodeData]) => nodeData?.type === ComponentType.Pay)
?.map(([_nodeId, nodeData]) => nodeData?.data?.allowInviteToPay);
const inviteToPayEnabled = (flowGraph: FlowGraph): boolean => {
const payNodes = Object.entries(flowGraph).filter(
(entry): entry is [string, Node] =>
isComponentType(entry, ComponentType.Pay),
);
const payNodeStatuses = payNodes.map(
([_nodeId, node]) => node?.data?.allowInviteToPay,
);
return (
payNodeStatuses.length > 0 &&
payNodeStatuses.every((status) => status === true)
);
};

const isComponentType = (
entry: Entry<FlowGraph>,
type: ComponentType,
): entry is [string, Node] => {
const [nodeId, node] = entry;
if (nodeId === "_root") return false;
return Boolean(node?.type === type);
};

const hasComponentType = (
flow: Record<string, any>,
flowGraph: FlowGraph,
type: ComponentType,
fn?: string,
): boolean => {
const nodeIds = Object.entries(flow).filter(
([_nodeId, nodeData]) => nodeData?.type === type,
const nodeIds = Object.entries(flowGraph).filter(
(entry): entry is [string, Node] => isComponentType(entry, type),
);
if (fn) {
nodeIds
?.filter(([_nodeId, nodeData]) => nodeData?.data.fn === fn)
?.filter(([_nodeId, nodeData]) => nodeData?.data?.fn === fn)
?.map(([nodeId, _nodeData]) => nodeId);
} else {
nodeIds?.map(([nodeId, _nodeData]) => nodeId);
Expand All @@ -270,16 +293,16 @@ const hasComponentType = (
};

const numberOfComponentType = (
flow: Record<string, any>,
flowGraph: FlowGraph,
type: ComponentType,
fn?: string,
): number => {
const nodeIds = Object.entries(flow).filter(
([_nodeId, nodeData]) => nodeData?.type === type,
const nodeIds = Object.entries(flowGraph).filter(
(entry): entry is [string, Node] => isComponentType(entry, type),
);
if (fn) {
nodeIds
?.filter(([_nodeId, nodeData]) => nodeData?.data.fn === fn)
?.filter(([_nodeId, nodeData]) => nodeData?.data?.fn === fn)
?.map(([nodeId, _nodeData]) => nodeId);
} else {
nodeIds?.map(([nodeId, _nodeData]) => nodeId);
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/gis/classifiedRoads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type OSFeatures = {
type: "Feature";
geometry: {
type: string;
coordinates: any[];
coordinates: number[];
};
properties: OSHighwayFeature;
}[];
Expand Down
18 changes: 9 additions & 9 deletions api.planx.uk/gis/local_authorities/metadata/braintree.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ https://environment.data.gov.uk/arcgis/rest/services
*/

const braintreeDomain = "https://mapping.braintree.gov.uk/arcgis";
const environmentDomain = "https://environment.data.gov.uk";
const _environmentDomain = "https://environment.data.gov.uk";
const A4_KEY = "PARISH";

const planningConstraints = {
Expand All @@ -20,7 +20,7 @@ const planningConstraints = {
serverIndex: 0,
fields: ["OBJECTID"],
neg: "is not in, or within, a Listed Building",
pos: (data) => ({
pos: (_data) => ({
text: "is, or is within, a Listed Building (Grade 1)",
description: null,
}),
Expand All @@ -33,7 +33,7 @@ const planningConstraints = {
serverIndex: 1,
fields: ["OBJECTID"],
neg: "is not in, or within, a Listed Building",
pos: (data) => ({
pos: (_data) => ({
text: "is, or is within, a Listed Building (Grade 2)",
description: null,
}),
Expand All @@ -46,7 +46,7 @@ const planningConstraints = {
serverIndex: 2,
fields: ["OBJECTID"],
neg: "is not in, or within, a Listed Building",
pos: (data) => ({
pos: (_data) => ({
text: "is, or is within, a Listed Building (Grade 2*)",
description: null,
}),
Expand All @@ -59,7 +59,7 @@ const planningConstraints = {
serverIndex: 3,
fields: ["OBJECTID", "DESIGNATED"],
neg: "is not in a Conservation Area",
pos: (data) => ({
pos: (_data) => ({
text: "is in a Conservation Area",
description: null,
}),
Expand Down Expand Up @@ -98,7 +98,7 @@ const planningConstraints = {
serverIndex: 4,
fields: ["OBJECTID"],
neg: "is not in a TPO (Tree Preservation Order) Zone",
pos: (data) => ({
pos: (_data) => ({
text: "is in a TPO (Tree Preservation Order) Zone",
description: null,
}),
Expand All @@ -111,7 +111,7 @@ const planningConstraints = {
serverIndex: 5,
fields: ["OBJECTID", "AREAS"],
neg: "is not in a TPO (Tree Preservation Order) Zone",
pos: (data) => ({
pos: (_data) => ({
text: "is in a TPO (Tree Preservation Order) Zone",
description: null,
}),
Expand All @@ -124,7 +124,7 @@ const planningConstraints = {
serverIndex: 6,
fields: ["OBJECTID", "SPECIES", "REFERENCE_"],
neg: "is not in a TPO (Tree Preservation Order) Zone",
pos: (data) => ({
pos: (_data) => ({
text: "is in a TPO (Tree Preservation Order) Zone",
description: null,
}),
Expand All @@ -137,7 +137,7 @@ const planningConstraints = {
serverIndex: 7,
fields: ["OBJECTID", "GROUPS", "SPECIES"],
neg: "is not in a TPO (Tree Preservation Order) Zone",
pos: (data) => ({
pos: (_data) => ({
text: "is in a TPO (Tree Preservation Order) Zone",
description: null,
}),
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/hasura/metadata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface ScheduledEventArgs {
};
webhook: string;
schedule_at: Date;
payload: Record<string, any>;
payload: Record<string, unknown>;
comment: string;
}

Expand Down
13 changes: 10 additions & 3 deletions api.planx.uk/hasura/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ interface SchemaAPIQuery {
* POST a request to the Hasura Schema API
* https://hasura.io/docs/latest/api-reference/schema-api/index/
*/
const postToSchemaAPI = async (
const postToSchemaAPI = async <T>(
query: SchemaAPIQuery,
): Promise<AxiosResponse<any>> => {
): Promise<AxiosResponse<T>> => {
try {
return await Axios.post(
process.env.HASURA_SCHEMA_URL!,
Expand All @@ -35,13 +35,20 @@ const postToSchemaAPI = async (
}
};

/**
* https://hasura.io/docs/latest/api-reference/schema-api/run-sql/#response
*/
interface RunSQLResponse {
result?: string[][];
}

/**
* Run custom SQL via Hasura Schema API
* https://hasura.io/docs/latest/api-reference/schema-api/run-sql/
*/
export const runSQL = async (sql: string) => {
try {
const response = await postToSchemaAPI({
const response = await postToSchemaAPI<RunSQLResponse>({
type: "run_sql",
args: {
source: "default",
Expand Down
4 changes: 3 additions & 1 deletion api.planx.uk/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@ describe("dataMerged() function", () => {
});
it("handles multiple external portal nodes", async () => {
const result = await dataMerged("parent-id");
const nodeTypes = Object.values(result).map((node) => node.type);
const nodeTypes = Object.values(result).map((node) =>
"type" in node ? node.type : undefined,
);
const areAllPortalsFlattened = !nodeTypes.includes(
ComponentType.ExternalPortal,
);
Expand Down
Loading

0 comments on commit 6693053

Please sign in to comment.