Recreate feat-SUP-829 - #479
Conversation
There was a problem hiding this comment.
Pull request overview
This PR reintroduces the “SUP-829” functionality by integrating a Backoffice feature-flag provider into the /features endpoint, allowing provider availability flags to be fetched (with caching) and merged into the API response.
Changes:
- Added
BackofficeFeatureFlagsServiceto fetch/cache boolean feature flags from Backoffice and merge them into/features. - Wired the new service into LoopBack DI and updated controller/unit tests accordingly.
- Updated Node typings (and a
NodeJS.Timertype) to align with the project’s Node 20+ runtime.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/services/index.ts | Exports the new backoffice feature flags service from the services barrel. |
| src/services/daemon.service.ts | Updates timer typing from NodeJS.Timer to NodeJS.Timeout (Node 20 typings compatibility). |
| src/services/backoffice-feature-flags.service.ts | New service for Backoffice flag retrieval + merge helper for /features. |
| src/dependency-injection-handler.ts | Registers BackofficeFeatureFlagsService in DI as a singleton. |
| src/dependency-injection-bindings.ts | Adds DI binding key for the backoffice feature flags service. |
| src/controllers/features.controller.ts | Fetches provider flags and merges them into the /features response. |
| src/tests/unit/services/backoffice-feature-flags.service.unit.ts | Adds unit coverage for caching, login/session handling, and flag parsing/merging. |
| src/tests/unit/features.controller.unit.ts | Extends controller unit tests to validate merging behavior and fallback behavior. |
| package.json | Bumps package version and updates @types/node to v20.x. |
| package-lock.json | Locks updated package version and @types/node dependency tree. |
| ENV_VARIABLES.md | Documents new BACKOFFICE_* environment variables and behavior. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
src/services/backoffice-feature-flags.service.ts:18
Number(env) || defaultmakes it impossible to intentionally configure0(e.g., disable caching or disable timeout) because0is falsy and will fall back to the default. Prefer an explicit parse that distinguishesundefined/NaNfrom valid numeric values (including 0), e.g. parse the env var, checkNumber.isFinite, and only then apply the default.
private readonly cacheTtlMs = Number(process.env.BACKOFFICE_FLAGS_CACHE_TTL_MS) || 60000;
private readonly timeoutMs = Number(process.env.BACKOFFICE_HTTP_TIMEOUT_MS) || 2000;
src/services/backoffice-feature-flags.service.ts:62
- This always overwrites
init.signal, so callers can’t provide their own abort/cancellation behavior. Consider only settingsignalwheninit.signalis not provided, or composing signals (e.g., abort on either the caller signal or the timeout) so the helper doesn’t silently discard upstream cancellation.
private request(path: string, init: RequestInit = {}): Promise<Response> {
return this.fetchFn(`${this.baseUrl}${path}`, {
...init,
signal: AbortSignal.timeout(this.timeoutMs),
});
}
src/services/backoffice-feature-flags.service.ts:143
- This does an
Array.find()overmergedfor every provider flag (O(flags * features)) and rebuildssupportedBrowsersvia object spread on every reduce step (extra allocations). If the number of flags/features grows, consider indexing existing features by name (e.g., aMap) and using a prebuilt/immutablesupportedBrowserstemplate (or a simple mutation-based fill) to avoid repeated linear scans and allocations.
const merged = [...features];
const now = new Date();
Object.entries(providerFlags).forEach(([key, enabled]) => {
const name = key.toLowerCase();
const value = enabled ? 'enabled' : 'disabled';
const existing = merged.find(feature => feature.name === name);
if (existing) {
if (existing.value !== 'enabled' && existing.value !== 'disabled') {
return;
}
existing.value = value;
existing.lastUpdateDate = now;
} else {
merged.push(
Object.assign(new FeaturesDbDataModel(), {
name,
value,
version: 0,
creationDate: now,
lastUpdateDate: now,
supportedBrowsers: BROWSERS.reduce(
(browsers, browser) => ({ ...browsers, [browser]: true }),
{} as SupportedBrowsers,
),
}),
);
}
});
src/controllers/features.controller.ts:74
- The newly added block’s indentation is inconsistent with the surrounding code (mix of indentation widths). This can cause noisy diffs and may fail linting/formatting checks; please align indentation to the file’s existing convention.
features = await this.featuresDatService.getAll();
responseCode = this.HTTP_SUCCESS_OK;
const providerFlags = await this.backofficeFeatureFlagsService.getProviderFlags();
if (providerFlags) {
features = applyProviderFlags(features, providerFlags);
}
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF Scorecard
Scanned Files
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/services/backoffice-feature-flags.service.ts:47
BACKOFFICE_FLAGS_CACHE_TTL_MS/BACKOFFICE_HTTP_TIMEOUT_MSare parsed withNumber(...) || default, which makes it impossible to configure an explicit0(it will fall back to the default). Use a finite-number check instead so0is honored while invalid values still fall back.
private readonly cacheTtlMs = Number(process.env.BACKOFFICE_FLAGS_CACHE_TTL_MS) || 60000;
private readonly timeoutMs = Number(process.env.BACKOFFICE_HTTP_TIMEOUT_MS) || 2000;
src/controllers/features.controller.ts:74
applyProviderFlags()can introduce non-stringvaluetypes (number/object/array) and apairsproperty into the/featuresresponse, but the endpoint's OpenAPI schema (in the@get('/features', …)decorator above) still describesFeaturesDbDataModelwherevalueis a string andpairsis not defined. This makes the generated spec inaccurate for clients.
const backofficeFlags = await this.backofficeFeatureFlagsService.getProviderFlags();
if (backofficeFlags) {
features = applyProviderFlags(features, backofficeFlags);
}
No description provided.