Skip to content

Commit

Permalink
-Add a logger to report when front-end and api have a version mismatch
Browse files Browse the repository at this point in the history
-Add cache headers to nginx
  • Loading branch information
tgandrews authored and SamGodwin2 committed Feb 25, 2019
1 parent cb3f34f commit 6b69f82
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 35 deletions.
2 changes: 1 addition & 1 deletion concourse.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ set -euf
echo "Building author..."
pushd eq-author
yarn install --frozen-lockfile
yarn build
REACT_APP_EQ_AUTHOR_VERSION=$EQ_AUTHOR_VERSION yarn build
popd
2 changes: 2 additions & 0 deletions eq-author-api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { PORT } = require("./settings");
const status = require("./middleware/status");
const { getLaunchUrl } = require("./middleware/launch");
const createAuthMiddleware = require("./middleware/auth");
const versionMiddleware = require("./middleware/version");
const repositories = require("./repositories");
const modifiers = require("./modifiers");
const schema = require("./schema");
Expand Down Expand Up @@ -60,6 +61,7 @@ db(process.env.DB_SECRET_ID)
pino,
cors(),
authMiddleware,
versionMiddleware,
bodyParser.json(),
graphqlExpress({
schema,
Expand Down
16 changes: 16 additions & 0 deletions eq-author-api/middleware/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = (req, res, next) => {
if (
req.headers.clientversion &&
process.env.EQ_AUTHOR_API_VERSION &&
req.headers.clientversion !== process.env.EQ_AUTHOR_API_VERSION
) {
req.log.warn(
{
clientversion: req.headers.clientversion,
apiversion: process.env.EQ_AUTHOR_API_VERSION,
},
"Application version mismatch"
);
}
next();
};
50 changes: 50 additions & 0 deletions eq-author-api/middleware/version.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const version = require("./version");

describe("version middleware", () => {
let req;
let res;
let next;
let previousEnv;

beforeEach(() => {
res = {
json: jest.fn(),
};
next = jest.fn();
previousEnv = process.env;
process.env = {
...process.env,
EQ_AUTHOR_API_VERSION: "foo",
};
});

it("should output a log on version mismatch", () => {
req = {
headers: { clientversion: "bar" },
log: { warn: jest.fn() },
};

version(req, res, next);

expect(req.log.warn).toHaveBeenCalled();

expect(next).toHaveBeenCalled();
});

it("should do nothing on version match", () => {
req = {
headers: { clientversion: "foo" },
log: { warn: jest.fn() },
};

version(req, res, next);

expect(req.log.warn).not.toHaveBeenCalled();

expect(next).toHaveBeenCalled();
});

afterEach(() => {
process.env = previousEnv;
});
});
34 changes: 17 additions & 17 deletions eq-author/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,19 @@ Spins up the Storybook development server.

### Authentication

| Name | Description | Required |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------- |
| `REACT_APP_FIREBASE_PROJECT_ID` | Firebase is used for basic authentication this environment and the two below are needed for this. | Yes If authentication is enabled |
| | The project ID for your Firebase project. Can be obtained from your Firebase project | |
| `REACT_APP_FIREBASE_API_KEY` | The api key for your Firebase project. Can be obtained from your Firebase project | Yes If authentication is enabled |
| Name | Description | Required |
| ------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------- |
| `REACT_APP_FIREBASE_PROJECT_ID` | Firebase is used for basic authentication this environment and the two below are needed for this. | Yes If authentication is enabled |
| | The project ID for your Firebase project. Can be obtained from your Firebase project | |
| `REACT_APP_FIREBASE_API_KEY` | The api key for your Firebase project. Can be obtained from your Firebase project | Yes If authentication is enabled |

### Functional

| Name | Description | Required |
| ---------------------- | ------------------------------------------------------ | -------- |
| `REACT_APP_API_URL` | Set Author API URL | Yes |
| `REACT_APP_LAUNCH_URL` | Set the launch-a-survey target | No |
| `PUBLIC_URL` | The public URL inferred if not provided | No |
| Name | Description | Required |
| ---------------------- | --------------------------------------- | -------- |
| `REACT_APP_API_URL` | Set Author API URL | Yes |
| `REACT_APP_LAUNCH_URL` | Set the launch-a-survey target | No |
| `PUBLIC_URL` | The public URL inferred if not provided | No |

### Testing

Expand All @@ -144,13 +144,13 @@ Spins up the Storybook development server.

### Build configuration

| Name | Description | Required |
| ------------------- | ------------------------------------------------------------------------------ | -------- |
| `BABEL_ENV` | Sets the environment the code is running in | Yes |
| `NODE_ENV` | Sets the environment the code is running in | Yes |
| `NODE_PATH` | Folder path for the code folder structure | Yes |
| `CI` | Switch that if is set to true will treat warnings as errors | No |
| `EQ_AUTHOR_VERSION` | The current Author version. This is what gets reported on the /status endpoint | No |
| Name | Description | Required |
| ----------------------------- | ------------------------------------------------------------------------------ | -------- |
| `BABEL_ENV` | Sets the environment the code is running in | Yes |
| `NODE_ENV` | Sets the environment the code is running in | Yes |
| `NODE_PATH` | Folder path for the code folder structure | Yes |
| `CI` | Switch that if is set to true will treat warnings as errors | No |
| `REACT_APP_EQ_AUTHOR_VERSION` | The current Author version. This is what gets reported on the /status endpoint | No |

## Authentication

Expand Down
10 changes: 10 additions & 0 deletions eq-author/nginx/sites-enabled/eq-author
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# Expires map
map $sent_http_content_type $expires {
default off;
text/html epoch;
text/css max;
application/javascript max;
~image/ max;
}

server {
listen 3000;
server_name eq-author;
root /etc/nginx/html;
index index.html;
expires $expires;

location / {
try_files $uri /index.html =404;
Expand Down
2 changes: 1 addition & 1 deletion eq-author/scripts/build-status-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const OUTPUT_PATH = "public/status.json";

const status = {
status: "OK",
version: process.env.EQ_AUTHOR_VERSION,
version: process.env.REACT_APP_EQ_AUTHOR_VERSION,
};

fs.writeFileSync(OUTPUT_PATH, JSON.stringify(status, null, " "));
Expand Down
2 changes: 1 addition & 1 deletion eq-author/scripts/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ yarn lint
yarn test --coverage
bash <(curl -s https://codecov.io/bash) -e TRAVIS_NODE_VERSION -c -F author
yarn test:storybook
EQ_AUTHOR_VERSION=$(git rev-parse HEAD) yarn build
REACT_APP_EQ_AUTHOR_VERSION=$(git rev-parse HEAD) yarn build
docker build -t onsdigital/eq-author:$TAG .
9 changes: 6 additions & 3 deletions eq-author/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { setContext } from "apollo-link-context";
import config from "config";
import getIdForObject from "utils/getIdForObject";
import render from "utils/render";
import appendAuthHeader from "utils/appendAuthHeader";
import getHeaders from "middleware/headers";

import App from "App";

Expand Down Expand Up @@ -43,11 +43,14 @@ const history = createHistory();

const httpLink = createHttpLink(config.REACT_APP_API_URL);

const authLink = setContext((_, { headers }) => appendAuthHeader(headers));
const headersLink = setContext((_, { headers }) => ({
headers: getHeaders(headers),
}));

const link = ApolloLink.from([
createErrorLink(getStore),
authLink.concat(httpLink),
headersLink,
httpLink,
]);

const client = createApolloClient(link, cache);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
export default headers => {
if (!window.localStorage) {
return {
headers: {
...headers,
},
...headers,
};
}
const accessToken = localStorage.getItem("accessToken");
Expand All @@ -16,8 +14,6 @@ export default headers => {
}

return {
headers: {
...returnedHeaders,
},
...returnedHeaders,
};
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import appendAuthHeader from "utils/appendAuthHeader";
import appendAuthHeader from "./authHeader";

describe("appendAuthHeader", () => {
let otherHeaders;
Expand All @@ -24,7 +24,7 @@ describe("appendAuthHeader", () => {
});

it("should just return default headers if no localStorage exists", () => {
expect(appendAuthHeader(otherHeaders).headers).toMatchObject({
expect(appendAuthHeader(otherHeaders)).toMatchObject({
ContentType: "text/html",
});
});
Expand All @@ -33,15 +33,13 @@ describe("appendAuthHeader", () => {
it("should append auth header if token exists", () => {
localStorage.setItem("accessToken", "abc.def.ghi");

expect(appendAuthHeader(otherHeaders).headers).toMatchObject({
expect(appendAuthHeader(otherHeaders)).toMatchObject({
ContentType: "text/html",
authorization: "Bearer abc.def.ghi",
});
});

it("should not append auth header if no access token exists", () => {
expect(appendAuthHeader(otherHeaders).headers).not.toHaveProperty(
"authorization"
);
expect(appendAuthHeader(otherHeaders)).not.toHaveProperty("authorization");
});
});
8 changes: 8 additions & 0 deletions eq-author/src/middleware/headers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import appendAuthHeader from "./authHeader";
import appendVersionHeader from "./versionHeader";
import { flow } from "lodash";

export default flow(
appendAuthHeader,
appendVersionHeader
);
9 changes: 9 additions & 0 deletions eq-author/src/middleware/headers/versionHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default headers => {
const { REACT_APP_EQ_AUTHOR_VERSION } = process.env;
return REACT_APP_EQ_AUTHOR_VERSION
? {
...headers,
clientVersion: REACT_APP_EQ_AUTHOR_VERSION,
}
: headers;
};
33 changes: 33 additions & 0 deletions eq-author/src/middleware/headers/versionHeader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import versionHeader from "./versionHeader";

describe("versionHeader", () => {
let env;
beforeEach(() => {
env = Object.assign({}, process.env);
});
afterEach(() => {
process.env = env;
});
it("should append a clientVersion field to an incoming object", () => {
process.env.REACT_APP_EQ_AUTHOR_VERSION = "theBest";

const initialHeaders = {
hello: "goodbye",
};
const newHeaders = versionHeader(initialHeaders);

expect(newHeaders).toMatchObject({
hello: "goodbye",
clientVersion: "theBest",
});
});

it("should not append a clientVersion field to the incoming object if author version not set", () => {
const initialHeaders = {
hello: "goodbye",
};
const newHeaders = versionHeader(initialHeaders);

expect(newHeaders.clientVersion).toBeUndefined();
});
});

0 comments on commit 6b69f82

Please sign in to comment.