From a88cb71e35fd7aaca56e810cc7b870d1ed6f8999 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= <DafyddLlyr@gmail.com>
Date: Wed, 1 Feb 2023 10:24:18 +0000
Subject: [PATCH] refactor: Add granularity to NODE_ENV and REACT_APP_ENV
 (#1412)

---
 .github/workflows/pull-request.yml      |  1 +
 .github/workflows/push-main.yml         |  1 +
 .github/workflows/push-production.yml   |  1 +
 api.planx.uk/airbrake.ts                |  7 +++----
 api.planx.uk/helpers.ts                 |  3 +++
 api.planx.uk/s3/utils.ts                |  3 ++-
 api.planx.uk/server.ts                  |  5 +++--
 docker-compose.pizza.yml                |  2 ++
 editor.planx.uk/.env                    |  4 +++-
 editor.planx.uk/.env.test               |  1 +
 editor.planx.uk/package.json            |  1 +
 editor.planx.uk/pnpm-lock.yaml          | 12 ++++++++++--
 editor.planx.uk/src/airbrake.ts         |  3 ++-
 editor.planx.uk/src/lib/featureFlags.ts |  2 +-
 editor.planx.uk/src/setupTests.ts       |  3 +++
 editor.planx.uk/src/utils.ts            |  3 +++
 infrastructure/application/index.ts     |  3 +--
 17 files changed, 41 insertions(+), 14 deletions(-)
 create mode 100644 editor.planx.uk/.env.test

diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 1ea4045056..3f482720ea 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -190,6 +190,7 @@ jobs:
           REACT_APP_SHAREDB_URL: wss://sharedb.${{ env.FULL_DOMAIN }}
           # needed because there's no API to change google's allowed OAuth URLs
           REACT_APP_GOOGLE_OAUTH_OVERRIDE: https://api.editor.planx.dev
+          REACT_APP_ENV: pizza
 
   build_storybook:
     name: Build Storybook
diff --git a/.github/workflows/push-main.yml b/.github/workflows/push-main.yml
index 1396bf5e3e..2b022a8bca 100644
--- a/.github/workflows/push-main.yml
+++ b/.github/workflows/push-main.yml
@@ -60,6 +60,7 @@ jobs:
           REACT_APP_SHAREDB_URL: wss://sharedb.editor.planx.dev
           REACT_APP_AIRBRAKE_PROJECT_ID: ${{ secrets.AIRBRAKE_PROJECT_ID }}
           REACT_APP_AIRBRAKE_PROJECT_KEY: ${{ secrets.AIRBRAKE_PROJECT_KEY }}
+          REACT_APP_ENV: staging
       - name: Upload Build Artifact
         uses: actions/upload-artifact@v2
         with:
diff --git a/.github/workflows/push-production.yml b/.github/workflows/push-production.yml
index b17765a6b0..4ad67758fc 100644
--- a/.github/workflows/push-production.yml
+++ b/.github/workflows/push-production.yml
@@ -60,6 +60,7 @@ jobs:
           REACT_APP_SHAREDB_URL: wss://sharedb.editor.planx.uk
           REACT_APP_AIRBRAKE_PROJECT_ID: ${{ secrets.AIRBRAKE_PROJECT_ID }}
           REACT_APP_AIRBRAKE_PROJECT_KEY: ${{ secrets.AIRBRAKE_PROJECT_KEY }}
+          REACT_APP_ENV: production
       - name: Upload Build Artifact
         uses: actions/upload-artifact@v2
         with:
diff --git a/api.planx.uk/airbrake.ts b/api.planx.uk/airbrake.ts
index be2b97ffdb..d85dd6f2c2 100644
--- a/api.planx.uk/airbrake.ts
+++ b/api.planx.uk/airbrake.ts
@@ -1,15 +1,14 @@
 import { Notifier } from "@airbrake/node";
+import { isLiveEnv } from "./helpers";
 
 const airbrake =
-  process.env.NODE_ENV === "production" &&
+  isLiveEnv() &&
     process.env.AIRBRAKE_PROJECT_ID &&
     process.env.AIRBRAKE_PROJECT_KEY
     ? new Notifier({
       projectId: Number(process.env.AIRBRAKE_PROJECT_ID),
       projectKey: process.env.AIRBRAKE_PROJECT_KEY,
-      environment: process.env.API_URL_EXT!.endsWith("planx.uk")
-        ? "production"
-        : "staging",
+      environment: process.env.NODE_ENV!
     })
     : undefined;
 
diff --git a/api.planx.uk/helpers.ts b/api.planx.uk/helpers.ts
index 6fd54ece51..46bed37f58 100644
--- a/api.planx.uk/helpers.ts
+++ b/api.planx.uk/helpers.ts
@@ -189,6 +189,8 @@ const makeUniqueFlow = (flowData: Flow["data"], replaceValue: string): Flow["dat
   return flowData;
 };
 
+const isLiveEnv = () => (["production", "staging", "pizza"].includes(process.env.NODE_ENV || ""));
+
 export {
   getFlowData,
   getMostRecentPublishedFlow,
@@ -197,4 +199,5 @@ export {
   getChildren,
   makeUniqueFlow,
   insertFlow,
+  isLiveEnv,
 };
diff --git a/api.planx.uk/s3/utils.ts b/api.planx.uk/s3/utils.ts
index 32b48c60f4..af79c3999d 100644
--- a/api.planx.uk/s3/utils.ts
+++ b/api.planx.uk/s3/utils.ts
@@ -1,4 +1,5 @@
 import S3 from "aws-sdk/clients/s3";
+import { isLiveEnv } from "../helpers";
 
 export function s3Factory() {
   return new S3({
@@ -11,7 +12,7 @@ export function s3Factory() {
 }
 
 function useMinio() {
-  if (process.env.NODE_ENV === "production") {
+  if (isLiveEnv()) {
     // Points to AWS
     return {};
   } else {
diff --git a/api.planx.uk/server.ts b/api.planx.uk/server.ts
index b7d6bdded0..16a189f3f1 100644
--- a/api.planx.uk/server.ts
+++ b/api.planx.uk/server.ts
@@ -59,6 +59,7 @@ import { useOrdnanceSurveyProxy } from "./proxy/ordnanceSurvey";
 import { usePayProxy } from "./proxy/pay";
 import { downloadFeedbackCSV } from "./admin/feedback/downloadFeedbackCSV";
 import { sanitiseApplicationData } from "./webhooks/sanitiseApplicationData";
+import { isLiveEnv } from "./helpers";
 
 const router = express.Router();
 
@@ -90,7 +91,7 @@ const handleSuccess = (req: Request, res: Response) => {
     const { returnTo = process.env.EDITOR_URL_EXT } = req.session!;
 
     const domain = (() => {
-      if (process.env.NODE_ENV === "production") {
+      if (isLiveEnv()) {
         if (returnTo?.includes("editor.planx.")) {
           // user is logging in to staging from editor.planx.dev
           // or production from editor.planx.uk
@@ -119,7 +120,7 @@ const handleSuccess = (req: Request, res: Response) => {
         httpOnly: false,
       };
 
-      if (process.env.NODE_ENV === "production") {
+      if (isLiveEnv()) {
         cookie.secure = true;
         cookie.sameSite = "none";
       }
diff --git a/docker-compose.pizza.yml b/docker-compose.pizza.yml
index 49fedbea26..38d1af5db2 100644
--- a/docker-compose.pizza.yml
+++ b/docker-compose.pizza.yml
@@ -26,6 +26,8 @@ services:
       virtual.host: api.${ROOT_DOMAIN}
       virtual.port: ${API_PORT}
       virtual.tls: ${TLS_EMAIL}
+    environment:
+      NODE_ENV: "pizza"
 
   sharedb:
     labels:
diff --git a/editor.planx.uk/.env b/editor.planx.uk/.env
index 8726efe4e3..cc7e87ddd4 100644
--- a/editor.planx.uk/.env
+++ b/editor.planx.uk/.env
@@ -8,4 +8,6 @@ REACT_APP_FEEDBACK_FISH_ID=65f02de00b90d1
 
 REACT_APP_API_URL=http://localhost:7002
 REACT_APP_HASURA_URL=http://localhost:7000/v1/graphql
-REACT_APP_SHAREDB_URL=ws://localhost:7003
\ No newline at end of file
+REACT_APP_SHAREDB_URL=ws://localhost:7003
+
+REACT_APP_ENV=development
\ No newline at end of file
diff --git a/editor.planx.uk/.env.test b/editor.planx.uk/.env.test
new file mode 100644
index 0000000000..babbe27a1e
--- /dev/null
+++ b/editor.planx.uk/.env.test
@@ -0,0 +1 @@
+REACT_APP_ENV=test
\ No newline at end of file
diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json
index 73a4bfc15a..1780f17478 100644
--- a/editor.planx.uk/package.json
+++ b/editor.planx.uk/package.json
@@ -40,6 +40,7 @@
     "classnames": "^2.3.1",
     "core-js": "^3.24.1",
     "date-fns": "^2.29.1",
+    "dotenv": "^16.0.3",
     "fast-xml-parser": "^4.0.11",
     "formik": "^2.2.9",
     "graphql": "^16.5.0",
diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml
index 3752f62141..09a9927dda 100644
--- a/editor.planx.uk/pnpm-lock.yaml
+++ b/editor.planx.uk/pnpm-lock.yaml
@@ -117,6 +117,7 @@ specifiers:
   craco-esbuild: ^0.5.1
   css-loader: ^6.7.1
   date-fns: ^2.29.1
+  dotenv: ^16.0.3
   esbuild: ^0.14.54
   esbuild-jest: ^0.5.0
   eslint: ^8.28.0
@@ -224,6 +225,7 @@ dependencies:
   classnames: 2.3.1
   core-js: 3.24.1
   date-fns: 2.29.1
+  dotenv: 16.0.3
   fast-xml-parser: 4.0.11
   formik: 2.2.9_react@18.2.0
   graphql: 16.5.0
@@ -311,7 +313,7 @@ devDependencies:
   esbuild: 0.14.54
   esbuild-jest: 0.5.0_esbuild@0.14.54
   eslint: 8.29.0
-  eslint-plugin-jest: 27.1.6_ixh6nceclvqov4hcu5gszby2fi
+  eslint-plugin-jest: 27.1.6_kkv2tsnmg5we6ee3ny4vbvytka
   eslint-plugin-jsx-a11y: 6.6.1_eslint@8.29.0
   eslint-plugin-simple-import-sort: 7.0.0_eslint@8.29.0
   eslint-plugin-testing-library: 5.6.0_ixh6nceclvqov4hcu5gszby2fi
@@ -8757,6 +8759,11 @@ packages:
     resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==}
     engines: {node: '>=10'}
 
+  /dotenv/16.0.3:
+    resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
+    engines: {node: '>=12'}
+    dev: false
+
   /dotenv/8.2.0:
     resolution: {integrity: sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==}
     engines: {node: '>=8'}
@@ -9373,7 +9380,7 @@ packages:
       - supports-color
       - typescript
 
-  /eslint-plugin-jest/27.1.6_ixh6nceclvqov4hcu5gszby2fi:
+  /eslint-plugin-jest/27.1.6_kkv2tsnmg5we6ee3ny4vbvytka:
     resolution: {integrity: sha512-XA7RFLSrlQF9IGtAmhddkUkBuICCTuryfOTfCSWcZHiHb69OilIH05oozH2XA6CEOtztnOd0vgXyvxZodkxGjg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -9388,6 +9395,7 @@ packages:
     dependencies:
       '@typescript-eslint/utils': 5.36.1_ixh6nceclvqov4hcu5gszby2fi
       eslint: 8.29.0
+      jest: 27.4.5
     transitivePeerDependencies:
       - supports-color
       - typescript
diff --git a/editor.planx.uk/src/airbrake.ts b/editor.planx.uk/src/airbrake.ts
index b15351b64e..76368337f5 100644
--- a/editor.planx.uk/src/airbrake.ts
+++ b/editor.planx.uk/src/airbrake.ts
@@ -1,4 +1,5 @@
 import { Notifier } from "@airbrake/browser";
+import { isLiveEnv } from "utils";
 
 export const reportError = getErrorLogger().notify;
 
@@ -13,7 +14,7 @@ function log(...args: any[]) {
 // forward all JS errors to airbrake.io
 function getErrorLogger(): ErrorLogger {
   const hasConfig =
-    process.env.NODE_ENV === "production" &&
+    isLiveEnv() &&
     process.env.REACT_APP_AIRBRAKE_PROJECT_ID &&
     process.env.REACT_APP_AIRBRAKE_PROJECT_KEY;
 
diff --git a/editor.planx.uk/src/lib/featureFlags.ts b/editor.planx.uk/src/lib/featureFlags.ts
index 4690c89129..9ecd3a4f22 100644
--- a/editor.planx.uk/src/lib/featureFlags.ts
+++ b/editor.planx.uk/src/lib/featureFlags.ts
@@ -62,7 +62,7 @@ export const hasFeatureFlag = (featureFlag: featureFlag) =>
   has: hasFeatureFlag,
 };
 
-if (process.env.NODE_ENV !== "test") {
+if (process.env.REACT_APP_ENV !== "test") {
   // log current flag status on page load
   console.debug(
     activeFeatureFlags.size > 0
diff --git a/editor.planx.uk/src/setupTests.ts b/editor.planx.uk/src/setupTests.ts
index f27a14dfc3..b35436e5e1 100644
--- a/editor.planx.uk/src/setupTests.ts
+++ b/editor.planx.uk/src/setupTests.ts
@@ -6,6 +6,9 @@ import "@testing-library/jest-dom";
 import "jest-localstorage-mock";
 import "jest-axe/extend-expect";
 
+import dotenv from "dotenv";
 import { mockFade } from "testUtils";
 
+dotenv.config({ path: "./.env.test" });
+
 beforeAll(() => mockFade);
diff --git a/editor.planx.uk/src/utils.ts b/editor.planx.uk/src/utils.ts
index c30bac04a2..6c144dd57d 100644
--- a/editor.planx.uk/src/utils.ts
+++ b/editor.planx.uk/src/utils.ts
@@ -80,3 +80,6 @@ export const fetchCurrentTeam = (): Team | undefined => {
   });
   return data?.teams[0];
 };
+
+export const isLiveEnv = () =>
+  ["production", "staging", "pizza"].includes(process.env.NODE_ENV || "");
diff --git a/infrastructure/application/index.ts b/infrastructure/application/index.ts
index 2f31e58a0d..187a8d07ba 100644
--- a/infrastructure/application/index.ts
+++ b/infrastructure/application/index.ts
@@ -9,7 +9,6 @@ import * as postgres from "@pulumi/postgresql";
 import * as mime from "mime";
 import * as tldjs from "tldjs";
 import * as url from "url";
-import * as random from "@pulumi/random";
 
 import { generateTeamSecrets } from "./utils/generateTeamSecrets";
 import { createHasuraService } from "./services/hasura";
@@ -304,7 +303,7 @@ export = async () => {
         memory: 1024 /*MB*/,
         portMappings: [apiListenerHttps],
         environment: [
-          { name: "NODE_ENV", value: "production" },
+          { name: "NODE_ENV", value: env },
           { name: "EDITOR_URL_EXT", value: `https://${DOMAIN}` },
           { name: "AWS_S3_REGION", value: apiBucket.region },
           { name: "AWS_ACCESS_KEY", value: apiUserAccessKey.id },