Skip to content

Commit

Permalink
🌱 Add enablement configuration for mocking data (#1495)
Browse files Browse the repository at this point in the history
Enhance mock handling in the app:
  - By default, no mocking is enabled
  - Mock data can only be enabled in development mode
  - `MOCK`, a new environment variable, is used to configure mocks
  - Wait to do the initial render until the mocks are in place. This
    prevents the app from pulling real data before the mocks are properly
    initialized.

`MOCK` config string options:
  - 4 config string options can be used together separated by '.': `off`,
    `full`, `pass`, and `stub`
  - `off`: Forces mocking to be disabled
  - `full`, `+full`, `-full`: Enable the full mock handlers
  - `pass`, `+pass`, `-pass`, `passthrough`: Enable the "/hub/*" catch-all
     passthrough handler
  - `stub`, `stub=*`, `stub=archetypes,applications`: Control what
    "stub-new-work" handlers are enabled
    - `stub` or `stub=*` enables all available stub handlers
    - `stub=archetypes,applications` enables the 2 stub handlers
      `archetypes` and `applications`, if they exist

When running in development mode, the mock configuration will be logged
to console.

Signed-off-by: Scott J Dickerson <[email protected]>
  • Loading branch information
sjd78 authored Oct 31, 2023
1 parent d3eb379 commit 166b891
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 34 deletions.
2 changes: 2 additions & 0 deletions client/src/app/env.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { decodeEnv, buildKonveyorEnv } from "@konveyor-ui/common";

export const ENV = buildKonveyorEnv(decodeEnv(window._env));

export default ENV;
36 changes: 22 additions & 14 deletions client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

import ENV from "@app/env";
import App from "@app/App";
import reportWebVitals from "@app/reportWebVitals";
import { KeycloakProvider } from "@app/components/KeycloakProvider";
Expand All @@ -15,24 +16,31 @@ dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(customParseFormat);

if (process.env.NODE_ENV === "development") {
const queryClient = new QueryClient();

const renderApp = () => {
ReactDOM.render(
<KeycloakProvider>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</KeycloakProvider>,
document.getElementById("root")
);
};

if (ENV.NODE_ENV === "development") {
import("./mocks/browser").then((browserMocks) => {
browserMocks.worker.start();
if (browserMocks.config.enabled) {
browserMocks.worker.start();
}
renderApp();
});
} else {
renderApp();
}

const queryClient = new QueryClient();

ReactDOM.render(
<KeycloakProvider>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</KeycloakProvider>,
document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
Expand Down
41 changes: 27 additions & 14 deletions client/src/mocks/browser.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { type RestHandler, setupWorker, rest } from "msw";

import StubsForNewWork from "./stub-new-work";
import config from "./config";
import stubNewWork from "./stub-new-work";

const otherHandlers: RestHandler[] = [...StubsForNewWork];
/**
* Handler to catch unhandled traffic to `/hub/*`, log it, and pass it through to the
* server to handle. This is useful to see traffic, in the console logs, that is not
* being mocked elsewhere.
*/
const passthroughHandler: RestHandler = rest.all("/hub/*", (req) => {
console.log(
"%cmsw passthrough%c \u{1fa83} %s",
"font-weight: bold",
"font-weight: normal",
req.url
);
return req.passthrough();
});

export const worker = setupWorker(
...otherHandlers,
const handlers = [
// TODO: Add handlers for a FULL hub mock data set
...stubNewWork,
config.passthrough && passthroughHandler,
].filter(Boolean);

rest.all("/hub/*", (req) => {
console.log(
"%cmsw passthrough%c \u{1fa83} %s",
"font-weight: bold",
"font-weight: normal",
req.url
);
return req.passthrough();
})
);
/**
* A setup MSW browser service worker using the handlers configured in the MOCK env var.
*/
export const worker = setupWorker(...handlers);

export { config } from "./config";
114 changes: 114 additions & 0 deletions client/src/mocks/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { parseMock } from "./config";

describe.each([
// off
{
str: "",
expected: { enabled: false, passthrough: false, full: false, stub: false },
},
{
str: "off",
expected: { enabled: false, passthrough: false, full: false, stub: false },
},
{
str: " off ",
expected: { enabled: false, passthrough: false, full: false, stub: false },
},

// just "full"
{
str: "full",
expected: { enabled: true, passthrough: false, full: true, stub: false },
},
{
str: "-full",
expected: { enabled: false, passthrough: false, full: false, stub: false },
},
{
str: "+full",
expected: { enabled: true, passthrough: false, full: true, stub: false },
},

// just "passthrough"
{
str: "pass",
expected: { enabled: true, passthrough: true, full: false, stub: false },
},
{
str: "-pass",
expected: { enabled: false, passthrough: false, full: false, stub: false },
},
{
str: "+passthrough",
expected: { enabled: true, passthrough: true, full: false, stub: false },
},

// just "stub" in various forms
{
str: "stub",
expected: { enabled: true, passthrough: false, full: false, stub: "*" },
},
{
str: "stub=*",
expected: { enabled: true, passthrough: false, full: false, stub: "*" },
},
{
str: "stub=A,b",
expected: {
enabled: true,
passthrough: false,
full: false,
stub: ["a", "b"],
},
},
{
str: "stub=Alpha, Bravo, Charlie",
expected: {
enabled: true,
passthrough: false,
full: false,
stub: ["alpha", "bravo", "charlie"],
},
},

// Combine forms
{
str: "+full.+pass",
expected: { enabled: true, passthrough: true, full: true, stub: false },
},
{
str: "-pass.-full",
expected: { enabled: false, passthrough: false, full: false, stub: false },
},
{
str: "stub=whiskey,tango,foxtrot.pass",
expected: {
enabled: true,
passthrough: true,
full: false,
stub: ["whiskey", "tango", "foxtrot"],
},
},
{
str: "-pass.stub=wink,nudge",
expected: {
enabled: true,
passthrough: false,
full: false,
stub: ["wink", "nudge"],
},
},
{
str: "+pass.stub=wink,nudge.off",
expected: {
enabled: false,
passthrough: true,
full: false,
stub: ["wink", "nudge"],
},
},
])("MOCK='$str'", ({ str, expected }) => {
test("config string parses as expected", () => {
expect(parseMock(str)).toStrictEqual(expected);
});
});
78 changes: 78 additions & 0 deletions client/src/mocks/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ENV } from "@app/env";

// Samples of what mock string to parse:
// MOCK=
// MOCK=off
// MOCK=+pass
// MOCK=full
// MOCK=-full
// MOCK=stub
// MOCK=stub=*
// MOCK=stub=*.pass
// MOCK=stub=1,2,3.pass
// MOCK=stub=1,2,3.+pass
// MOCK=stub=1,2,3.-pass

/**
* Parse the provided MOCK configuration string and return a configuration object.
*/
export function parseMock(str?: string): {
enabled: boolean;
passthrough: boolean;
stub: boolean | "*" | string[];
full: boolean;
} {
const regexOff = /^(off)?$/;
const regexPassthrough = /^([+-]?)pass(through)?$/;
const regexFull = /^([+-]?)full$/;
const regexStub = /^stub(=\*|=([a-z0-9\-_]+(\s*,\s*[a-z0-9\-_]+)*))?$/;

let off = !str;
let passthrough = false;
let full = false;
let stub: boolean | "*" | string[] = false;

str
?.toLowerCase()
.split(".")
.map((p) => p.trim())
.forEach((part) => {
if (part.match(regexOff)) {
off = true;
}

const matchPassthrough = part.match(regexPassthrough);
if (matchPassthrough) {
passthrough =
matchPassthrough[1].length === 0 || matchPassthrough[1] === "+";
}

const matchFull = part.match(regexFull);
if (matchFull) {
full = matchFull[1].length === 0 || matchFull[1] === "+";
}

const matchStub = part.match(regexStub);
if (matchStub) {
if (!matchStub[1] || matchStub[1] === "" || matchStub[1] === "=*") {
stub = "*";
} else {
stub = matchStub[2].split(",").map((s) => s.trim());
}
}
});

return {
passthrough,
stub,
full,
enabled: !off && (passthrough || full || !!stub),
};
}

export const config = Object.freeze(parseMock(ENV.MOCK));
if (ENV.NODE_ENV === "development") {
console.info("MOCK configuration: ", config);
}

export default config;
28 changes: 22 additions & 6 deletions client/src/mocks/stub-new-work/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { type RestHandler } from "msw";
import { config } from "../config";

export default [
// ...questionnaires,
// ...assessments,
// ...applications,
// ...archetypes,
] as RestHandler[];
import applications from "./applications";
import archetypes from "./archetypes";
import assessments from "./assessments";
import questionnaires from "./questionnaires";

const enableMe = (me: string) =>
config.stub === "*" ||
(Array.isArray(config.stub) ? (config.stub as string[]).includes(me) : false);

/**
* Return the stub-new-work handlers that are enabled by config.
*/
const enabledStubs: RestHandler[] = [
...(enableMe("applications") ? applications : []),
...(enableMe("archetypes") ? archetypes : []),
...(enableMe("assessments") ? assessments : []),
...(enableMe("questionnaires") ? questionnaires : []),
...(enableMe("applications") ? applications : []),
].filter(Boolean);

export default enabledStubs;
5 changes: 5 additions & 0 deletions common/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export type KonveyorEnvType = {
NODE_ENV: "development" | "production" | "test";
VERSION: string;

/** Controls how mock data is injected on the client */
MOCK: string;

/** Enable RBAC authentication/authorization */
AUTH_REQUIRED: "true" | "false";

Expand Down Expand Up @@ -57,6 +60,7 @@ export const SERVER_ENV_KEYS = [
export const buildKonveyorEnv = ({
NODE_ENV = "development",
VERSION = "99.0.0",
MOCK = "off",

AUTH_REQUIRED = "false",
KEYCLOAK_REALM = "tackle",
Expand All @@ -68,6 +72,7 @@ export const buildKonveyorEnv = ({
}: Partial<KonveyorEnvType> = {}): KonveyorEnvType => ({
NODE_ENV,
VERSION,
MOCK,

AUTH_REQUIRED,
KEYCLOAK_REALM,
Expand Down

0 comments on commit 166b891

Please sign in to comment.