Skip to content

Commit 89ea483

Browse files
authored
INCS-5747 - Add dockerized support for the SPA (#56)
1 parent 167e750 commit 89ea483

File tree

10 files changed

+75
-12
lines changed

10 files changed

+75
-12
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
# 3. Use the created domain for `APP_URL` (e.g., https://foo-bar-baz.ngrok-free.app)
66
APP_URL=
77

8+
# Environment type
9+
NODE_ENV=
10+
811
###############################################################
912
## The env variables below are required for development only ##
1013
###############################################################

docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ services:
2121
interval: 5s
2222
retries: 100
2323

24+
spa:
25+
container_name: single-page-app
26+
build:
27+
context: .
28+
dockerfile: spa-docker/Dockerfile
29+
volumes:
30+
- ./spa:/app/spa
31+
ports:
32+
- "3010:3010"
33+
command: yarn start
34+
2435
installation:
2536
depends_on:
2637
app:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"clean": "rimraf src/**/*.js test/**/*.js build",
1212
"clean:db": "rimraf db.json",
1313
"lint": "eslint . --ext .ts,.tsx",
14-
"build": "tsc --project .",
14+
"build": "tsc --project . && cd spa && yarn build",
1515
"start": "tsnd --watch=.env,**/*.squirrelly --inspect=0.0.0.0:9229 --respawn --transpile-only -- src/server.ts",
1616
"test:e2e": "run-s test:e2e:install test:e2e:start",
1717
"test:e2e:debug": "PWDEBUG=1 run-s test:e2e:install test:e2e:headed",

spa-docker/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM node:lts-alpine as build
2+
3+
COPY ./spa /app/spa
4+
WORKDIR /app/spa
5+
6+
RUN yarn install
7+
8+
EXPOSE 3010
9+
10+
CMD [ "yarn", "start" ]
11+

spa/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ Reusable page components are included, plus examples.
88

99
# How to use the app
1010

11-
A proxy server has been set up for local instances.
11+
The front end React app should already be up if you ran `docker compose up`.
1212

13-
This lets you work on your Connect app locally while still interacting with Jira.
13+
> **Note:** <br/> For development environment, this React app is proxied into the running node server. <br/>However, for production environment, you'll need to create the build for the react app and make sure the environment variable NODE_ENV is set to "production".
1414
15-
To view the front end on a Jira instance:
15+
# Run React app separately
16+
17+
In case you want to run the React app separately from docker, then follow the steps below:
1618

1719
1. **Run the Node app:** the Node app is the back end of your Connect app and handles requests from Jira. Use the command "yarn start" to start the app.
1820

@@ -21,5 +23,3 @@ To view the front end on a Jira instance:
2123
3. **Run ngrok:** ngrok provides a public URL that forwards incoming requests to your local server. Run the command "ngrok http --domain="yourdomainname.com" 3000" to start ngrok and point it to your local server on port 3000 (make sure to add in your actual domain name).
2224

2325
The Node app, React app, and ngrok all need to be running for the local development environment to run correctly. Run each process in its own terminal window or tab.
24-
25-
Note: These three steps will not be necessary once Docker support is implemented.

spa/src/pagesData/startConnectionData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ export interface startConnectionProps {
2626
export const startConnectionData = {
2727
appName: "Your App",
2828
appLogoPath: "/public/assets/jira-logo.svg", // Replace 'appLogoPath' with the path to your application's logo.
29-
AppMarketplaceUrl: "",
29+
AppMarketplaceUrl: "com.github.integration.production",
3030
integrationRequirements: data,
3131
};

src/routes/router.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { eventsRouter } from "./events";
55
import { webhooksRouter } from "./webhooks";
66
import { apiRouter } from "./api";
77
import { pageRouter } from "./pages";
8+
import { spaRouter } from "./spa";
89

910
export const RootRouter = Router();
1011

@@ -41,4 +42,11 @@ RootRouter.use("/webhooks", webhooksRouter);
4142
/************************************************************************************************************************
4243
* API
4344
************************************************************************************************************************/
44-
RootRouter.use("/api/example", apiRouter);
45+
RootRouter.use("/api/example", apiRouter);
46+
47+
/************************************************************************************************************************
48+
* Single Page App Views
49+
*
50+
* This route is only used for the production builds of SPA.
51+
************************************************************************************************************************/
52+
RootRouter.use("/spa", spaRouter);

src/routes/spa.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Router, static as Static } from "express";
2+
import path from "path";
3+
import fs from "fs/promises";
4+
5+
export const spaRouter = Router();
6+
7+
const rootPath = process.cwd();
8+
9+
//Assets from within the new spa experience in /spa/build/static
10+
spaRouter.use("/static", Static(path.join(rootPath, "spa/build/static")));
11+
12+
let indexHtmlContent = "";
13+
14+
spaRouter.use("/*", async (_, res) => {
15+
if (!indexHtmlContent) {
16+
indexHtmlContent = await fs.readFile(path.join(process.cwd(), "spa/build/index.html"), "utf-8");
17+
}
18+
19+
res.status(200).send(indexHtmlContent);
20+
});

src/spa-proxy.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import { Express } from "express";
22
import httpProxy from "http-proxy";
3+
import { isNodeDev } from "./utils/is-node-dev";
34

45
const SPA_PATH = "/spa";
56

67
const proxy = httpProxy.createProxyServer({
78
target: {
8-
host: "localhost",
9+
host: "single-page-app",
910
port: 3010,
1011
path: SPA_PATH
1112
},
1213
ws: false
1314
});
1415

1516
export const proxyLocalUIForDev = (app: Express) => {
16-
app.use(SPA_PATH, (req, res) => {
17-
return proxy.web(req, res);
18-
});
17+
if (isNodeDev()) {
18+
app.use(SPA_PATH, (req, res) => proxy.web(req, res));
19+
}
1920
};
2021

src/utils/is-node-dev.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export enum EnvironmentEnum {
2+
production = "production",
3+
development = "development",
4+
}
5+
6+
export const getNodeEnv: () => EnvironmentEnum = () => EnvironmentEnum[process.env.NODE_ENV || ""] as EnvironmentEnum | undefined || EnvironmentEnum.development;
7+
export const isNodeEnv = (env: EnvironmentEnum) => getNodeEnv() === env;
8+
export const isNodeProd = () => isNodeEnv(EnvironmentEnum.production);
9+
export const isNodeDev = () => isNodeEnv(EnvironmentEnum.development);

0 commit comments

Comments
 (0)