DEV Admin

What it is

A developer console reachable only with the DEV permission. Two screens:

  • Feature flags — inspect and switch flags at runtime, for both this application and EVA, and persist a flag set to global settings.

  • QA — an EVA-hosted QA surface.

Where it lives

Concern Location

Routes

admin/admin.routes.ts — /admin/feature-flags, /admin/qa, with ** redirecting to /admin/feature-flags

Components

admin/base/base.component.ts, admin/feature-flags/feature-flags.component.ts, admin/qa/qa.component.ts

Services

shared/feature-flags/feature-flags-admin.service.ts, feature-flags-eva.service.ts, feature-flags.service.ts

Model

shared/feature-flags/feature-flags.model.ts (FeatureFlagEntity), feature-flags.enum.ts (FEATURE_FLAGS_ENV, FEATURE_FLAGS_ENV_GLOBAL_SETTINGS_KEY)

Access

The /admin route is the only one in the application using an allow clause with only:

{
  path: 'admin',
  canActivate: [RouteAccessResolver, FeatureFlagsGeneralGuard, NgxPermissionsGuard],
  data: { permissions: { only: USER_PERMISSION.DEV, redirectTo: '/' } },
  children: [ /* base → feature-flags | qa | ** → /admin/feature-flags */ ],
}

The sidebar mirrors it with ngxPermissionsOnly="userPermission.DEV". dev_support is a separate permission and does *not grant access here.

The feature-flag console

AdminFeatureFlagsComponent shows one table per environment:

readonly ENV_CONFIG = [
  { env: FEATURE_FLAGS_ENV.IA,  title: 'Internal Admin' },
  { env: FEATURE_FLAGS_ENV.EVA, title: 'Eva' },
];

Each row is a FeatureFlagEntity with three independent booleans:

Column Meaning

build

The flag was baked into the bundle at build time (environment.featureFlags).

globalSettings

The flag is present in the global-settings record for this environment (FEATURE_FLAGS_IA / FEATURE_FLAGS_EVA).

exec

The effective value right now for this browser session, after session overrides.

Seeing all three side by side is the point: it makes "why is this flag on here but not for my colleague" answerable at a glance. The precedence rules are in feature flags.

What the console can do

Operation Effect

Toggle a flag

FeatureFlagsService.changeFlag(flag, value) → recomputes the table and writes the new exec set to sessionStorage. Affects only this browser session.

Clear session overrides

clearAvailableFlagsExecStatus() → removes the sessionStorage keys so the resolved set falls back to build + global settings.

Persist to global settings

storeState(flags) → writes the enabled flag names as a comma-separated string into the FEATURE_FLAGS_IA (or _EVA) global setting, stamping lastChange. This affects every user in the environment.

Refetch global settings

clearGlobalSettingsFetchedFlags() → drops the in-memory cache so the next read hits Actions Tools.

Share a configuration

The component injects ClipboardService and PlatformLocation, and reads settings from the URL (listenSettingsFromUrl(), SETTINGS_KEY = 'config'). A flag configuration can be encoded in the URL and copied to a colleague.

"Persist to global settings" is not a developer-local action. It rewrites the environment-wide flag record and takes effect for other users on their next flag initialisation. On a shared environment, prefer session overrides.

Driving EVA’s flags

The interesting part of this screen is that it manages another application’s flags.

AdminFeatureFlagsComponent embeds an EvaComponent (@ViewChild('appEva')) and registers it with the services (setAppEva(component)). Then:

Internal Admin                                 EVA (iframe)
─────────────────────────────────────────────  ────────────────────────
FeatureFlagsEvaService.initFF(available, build)
  ├─ read FEATURE_FLAGS_EVA from global settings
  ├─ read session overrides (FeatureExecFlagsEva)
  ├─ recalculate exec set
  └─ storeAndSend(flags)
        └─ appEva.sendMessage().sendUpdatedConfig(evaFlags)
                  ── GENERAL__CONFIG_RESPONSE ──►  applies flag set

                  ◄── GENERAL__CONFIG_REQUEST ──   EVA asks for config
                  ◄── GENERAL__CONFIG_UPDATE  ──   EVA reports its own flags
FEATURE_FLAGS__RESET ─────────────────────────►    EVA clears overrides

FeatureFlagsEvaService keeps EVA’s session overrides under separate keys — FeatureExecFlagsEva and FeatureExecFlagsLastChangeEva — so the two applications' overrides never collide.

It also enforces an always-on list:

private readonly ALWAYS_ON_FLAGS = ['🎪'];

Any flag named in ALWAYS_ON_FLAGS is forced to true in storeAndSend and unioned into the fetched global-settings set. The single entry is an emoji — a deliberate sentinel that EVA recognises and that cannot collide with a real flag name.

CommunicationService.featureFlagsFromEva$ is the inbound channel for EVA’s own available-flag list, so the console can render EVA’s rows without hardcoding them.

The QA screen

AdminQaComponent is minimal — a component with a single @ViewChild('appEva') and a template embedding EVA. Like the underwriting tracker, the content is EVA’s; this route exists to host it behind the DEV permission. See underwriting tracker for the same pattern.

Non-production console output

Separately from this screen, FeatureFlagsGeneralGuard prints the resolved flag table to the browser console on the first guarded navigation when environment.production is false:

[Feature Flags] Current Functionality Status:

SUPPLIER_ACCOUNT_HEALTH: Enabled
TASKS_MID_VER_SIMPLE_QUERY: Disabled
…

It is printed once per page load (guarded by a displayed flag). That is often faster than opening /admin/feature-flags.

Other developer affordances

Tool Detail

Redux DevTools

StoreDevtoolsModule.instrument() is enabled unconditionally, including in production builds.

Store debugger

src/app/store-debugger.ts logs every state/action pair, but requires editing EnableStoreDebugging = false in source. Use DevTools instead.

Request ring buffer

FetchedRequestsInterceptor keeps the last 50 requests (serialised) in the errors slice — visible in DevTools.

Error-handling playground

shared/error-handling/tetris/ with game.service.ts — a Tetris game shown on the error page. See error handling.

Version banner

shared/layout/app-stage-info/ shows the environment and build; the sidebar footer shows front-end and back-end versions via VersionService. See versions.

VPN tester

shared/vpn-tester.service.ts, started when CHECK_VPN is on.

Gotchas

Issue Detail

DEV only, not dev_support

Two similar-looking permissions; only DEV opens /admin.

Persisting affects everyone

storeState writes the environment-wide global setting.

Session vs global storage

Session overrides live in sessionStorage (per tab); global settings live in Actions Tools (per environment).

The emoji flag is real

ALWAYS_ON_FLAGS = ['🎪'] is intentional, not a stray character.

initFF has a disabled early-return

if (this.initialized) { // return false; } — the guard is commented out, so EVA flag initialisation always re-runs.

The ** child redirect

/admin/anything lands on /admin/feature-flags, unlike the '' redirect used elsewhere.