Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions src/app/lib/config/toggles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,30 @@ The toggles response can be viewed here (for test, live). The `Origin` header mu

By default, fetching toggles from iSite is not enabled on the local environment - it will just use the default values from the [localConfig file](https://github.com/bbc/simorgh/blob/latest/src/app/lib/config/toggles/localConfig.js). Note that this file is **not** service aware - it will set the same value for all services.

In cases where the toggles response needs to be tested/validated, the following commands can be run.
In cases where the toggles response needs to be tested/validated, the following commands can be run from `ws-nextjs-app`.

For **Test iSite**:
For toggles from **Test iSite**:

```
FETCH_TOGGLES=true yarn dev
yarn dev:toggles:test
```

For **Live iSite toggles**:
For toggles from **Live iSite**:

```
yarn build:live:debug && yarn start
yarn dev:toggles:live
```

> [!NOTE]
> Hot reloading will not work using this command - if you make a code change you need to rebuild & restart the application server.
> If hot reloading is necessary:
>
> - set `SIMORGH_APP_ENV=live` in local.env (ensure these changes are not committed)
> - run `FETCH_TOGGLES=true yarn dev` to start the application server.
>
> This will also use data from the live BFF FABL module.
Both commands enable remote toggle fetching (`FETCH_TOGGLES=true`) and set the `ctx-service-env` header (via `TOGGLES_SERVICE_ENV`) so you can switch environments without a rebuild. When fetching toggles locally the response cache is bypassed, so every request re-fetches the latest toggles from the endpoint. Hot reloading continues to work with these commands.

> [!NOTE]
> These commands set the environment variables inline, so there is no need to edit `local.env`.

# Simorgh Application Toggles

| Toggle Name | Description | Toggle Value | Example |
| ----------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------- |
| `account` | Enable Account functionality and IDCTA config fetching | Pipe-separated list of services (local env) to enable account for | enabled: true, value: `hindi\|hausa` |
| `account` | Enable Account functionality and IDCTA config fetching | Pipe-separated list of services (local env) to enable account for | enabled: true, value: `hindi\|hausa` |
| `ads` | Display Advertisements on Front Pages | | |
| `articleLiteSiteLink` | Display the link to the lite site on Article pages | | |
| `articlePortraitVideo` | Display portrait video carousel on Article pages | | |
Expand Down
66 changes: 65 additions & 1 deletion src/app/lib/utilities/fetchToggles/index.client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const remoteToggles = {

describe('getToggles', () => {
const originalTogglesBffPath = process.env.TOGGLES_BFF_PATH;
const originalAppEnv = process.env.SIMORGH_APP_ENV;
const originalServiceEnv = process.env.TOGGLES_SERVICE_ENV;

const mockSuccessfulFetchResponse = {
ok: true,
Expand All @@ -26,6 +28,18 @@ describe('getToggles', () => {
jest.resetModules();
jest.restoreAllMocks();
process.env.TOGGLES_BFF_PATH = originalTogglesBffPath;

if (originalAppEnv === undefined) {
delete process.env.SIMORGH_APP_ENV;
} else {
process.env.SIMORGH_APP_ENV = originalAppEnv;
}

if (originalServiceEnv === undefined) {
delete process.env.TOGGLES_SERVICE_ENV;
} else {
process.env.TOGGLES_SERVICE_ENV = originalServiceEnv;
}
});

it('should return defaultToggles if enableFetchingToggles is not enabled', async () => {
Expand Down Expand Up @@ -78,7 +92,31 @@ describe('getToggles', () => {
});
});

it('should only fetch once for repeated calls with the same endpoint and environment', async () => {
it('should support a nested data.toggles response shape', async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200,
json: jest.fn(async () => ({ data: { toggles: remoteToggles } })),
} as unknown as Response);

const { default: getToggles } = await import('./index');
const toggles = await getToggles({
service: 'mundo',
});

expect(toggles).toEqual({
...mockDefaultToggleDefinitions,
...remoteToggles,
});
});
Comment on lines +95 to +111

it('should only fetch once for repeated calls with the same endpoint and environment when not local', async () => {
process.env.SIMORGH_APP_ENV = 'live';
jest.mock('#lib/config/toggles', () => ({
...mockDefaultToggles,
live: mockDefaultToggles.local,
}));

const { default: getToggles } = await import('./index');
(global.fetch as jest.Mock).mockClear();

Expand All @@ -94,6 +132,32 @@ describe('getToggles', () => {
});
});

it('should bypass the cache and fetch on every call when running locally', async () => {
const { default: getToggles } = await import('./index');
(global.fetch as jest.Mock).mockClear();

await getToggles({ service: 'mundo' });
await getToggles({ service: 'mundo' });

expect(global.fetch).toHaveBeenCalledTimes(2);
});

it('should send the ctx-service-env header from TOGGLES_SERVICE_ENV when running locally', async () => {
process.env.TOGGLES_SERVICE_ENV = 'live';

const { default: getToggles } = await import('./index');
(global.fetch as jest.Mock).mockClear();

await getToggles({ service: 'mundo' });

expect(global.fetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: { 'ctx-service-env': 'live' },
}),
);
});

it('should return default toggles when the response is not ok', async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
Expand Down
10 changes: 7 additions & 3 deletions src/app/lib/utilities/fetchToggles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ const getMergedToggles = async ({
const togglesEndpoint = constructTogglesEndpoint({ service, isAmp });

const isLocal = appEnvironment === 'local';
const serviceEnv = isLocal ? 'test' : appEnvironment;
const serviceEnv = isLocal
? process.env.TOGGLES_SERVICE_ENV || 'test'
: appEnvironment;
const cacheKey = `${togglesEndpoint}:${serviceEnv}`;

const cachedResponse = cache.get(cacheKey);
const cachedResponse = isLocal ? undefined : cache.get(cacheKey);

logger.info(TOGGLE_API_REQUEST_RECEIVED, {
service,
Expand Down Expand Up @@ -95,7 +97,9 @@ const getMergedToggles = async ({
const responseBody = await response.json();
const fetchedToggles = responseBody?.data?.toggles;

cache.set(cacheKey, fetchedToggles);
if (!isLocal) {
cache.set(cacheKey, fetchedToggles);
Comment on lines 97 to +101
}

return { ...localToggles, ...fetchedToggles };
} catch (error) {
Expand Down
4 changes: 3 additions & 1 deletion ws-nextjs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"build:live:debug": "rm -rf build && awk '{sub(/LOG_DIR=.+/,\"LOG_DIR='log'\")}1' ../envConfig/live.env > .env && next build --webpack && yarn copyCssFiles && yarn copyPublicFiles",
"build": "yarn build:local",
"dev": "yarn setupDevEnv && next dev -p 7081 --webpack",
"dev:toggles:test": "FETCH_TOGGLES=true TOGGLES_SERVICE_ENV=test yarn dev",
"dev:toggles:live": "FETCH_TOGGLES=true TOGGLES_SERVICE_ENV=live yarn dev",
"dev-https": "yarn setupDevEnv && next dev -p 7081 --experimental-https --webpack",
"start": "NODE_ENV=production HOSTNAME=127.0.0.1 PORT=7081 node build/standalone/ws-nextjs-app/server.js",
"stop": "lsof -t -i:7081 | xargs kill",
Expand Down Expand Up @@ -67,4 +69,4 @@
"sharp": "0.35.3",
"temporal-polyfill": "0.3.0"
}
}
}
Loading