@pl4yzonellc/valar-ui 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -22,6 +22,28 @@ After building your library with `ng build valar-ui`, go to the dist folder
|
|
|
22
22
|
|
|
23
23
|
Run `ng test valar-ui` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
24
24
|
|
|
25
|
+
## Feature Flags Tenant Flow
|
|
26
|
+
|
|
27
|
+
`FeatureFlagsModule.forRoot(...)` configures API host/version and root tenant header only.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
FeatureFlagsModule.forRoot({
|
|
31
|
+
apiBaseUrl: 'https://api.example.com',
|
|
32
|
+
apiVersion: 'v1',
|
|
33
|
+
rootTenantId: environment.tenantId,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Runtime tenant selection is provided by the consuming app via component Input/Output:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<valar-feature-flags-manager [(tenantId)]="selectedTenantId"></valar-feature-flags-manager>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `tenantId` input is used for API paths like `/v1/feature-flags/tenant_<tenantId>`.
|
|
44
|
+
- `tenantId` is also included in PUT payload as `tenant_<tenantId>`.
|
|
45
|
+
- `rootTenantId` from `forRoot` is sent as the `x-tenant-id` header.
|
|
46
|
+
|
|
25
47
|
## Further help
|
|
26
48
|
|
|
27
49
|
To get more help on the Angular CLI use `ng help` or go check out
|
|
@@ -26,42 +26,47 @@ class FeatureFlagsApiService {
|
|
|
26
26
|
this.http = http;
|
|
27
27
|
const version = config.apiVersion ?? 'v1';
|
|
28
28
|
this.baseUrl = `${config.apiBaseUrl}/${version}/feature-flags`;
|
|
29
|
-
this.tenantId = config.tenantId;
|
|
30
29
|
this.rootTenantId = config.rootTenantId;
|
|
31
30
|
this.tenantHeaderName = config.tenantHeaderName ?? 'x-tenant-id';
|
|
32
31
|
}
|
|
33
32
|
/** GET /v1/feature-flags/tenant_<tenantId> */
|
|
34
|
-
getByTenantId() {
|
|
35
|
-
return this.http.get(this.tenantScopedBaseUrl(), this.requestOptions())
|
|
33
|
+
getByTenantId(tenantId) {
|
|
34
|
+
return this.http.get(this.tenantScopedBaseUrl(tenantId), this.requestOptions())
|
|
36
35
|
.pipe(map(doc => doc ?? null));
|
|
37
36
|
}
|
|
38
37
|
/** PUT /v1/feature-flags (merge-upsert) */
|
|
39
|
-
upsert(payload) {
|
|
38
|
+
upsert(tenantId, payload) {
|
|
40
39
|
const requestPayload = {
|
|
41
40
|
...payload,
|
|
42
|
-
tenantId:
|
|
41
|
+
tenantId: this.toTenantToken(tenantId),
|
|
43
42
|
};
|
|
44
43
|
return this.http.put(this.baseUrl, requestPayload, this.requestOptions());
|
|
45
44
|
}
|
|
46
45
|
/** DELETE /v1/feature-flags/tenant_<tenantId>/:group/:key */
|
|
47
|
-
deleteFlag(group, key) {
|
|
46
|
+
deleteFlag(tenantId, group, key) {
|
|
48
47
|
const url = [
|
|
49
|
-
this.tenantScopedBaseUrl(),
|
|
48
|
+
this.tenantScopedBaseUrl(tenantId),
|
|
50
49
|
encodeURIComponent(group),
|
|
51
50
|
encodeURIComponent(key),
|
|
52
51
|
].join('/');
|
|
53
52
|
return this.http.delete(url, this.requestOptions());
|
|
54
53
|
}
|
|
55
54
|
/** DELETE /v1/feature-flags/tenant_<tenantId>/:group */
|
|
56
|
-
deleteGroup(group) {
|
|
55
|
+
deleteGroup(tenantId, group) {
|
|
57
56
|
const url = [
|
|
58
|
-
this.tenantScopedBaseUrl(),
|
|
57
|
+
this.tenantScopedBaseUrl(tenantId),
|
|
59
58
|
encodeURIComponent(group),
|
|
60
59
|
].join('/');
|
|
61
60
|
return this.http.delete(url, this.requestOptions());
|
|
62
61
|
}
|
|
63
|
-
tenantScopedBaseUrl() {
|
|
64
|
-
return `${this.baseUrl}
|
|
62
|
+
tenantScopedBaseUrl(tenantId) {
|
|
63
|
+
return `${this.baseUrl}/${encodeURIComponent(this.toTenantToken(tenantId))}`;
|
|
64
|
+
}
|
|
65
|
+
toTenantToken(tenantId) {
|
|
66
|
+
const normalized = tenantId.trim();
|
|
67
|
+
if (normalized.startsWith('tenant_'))
|
|
68
|
+
return normalized;
|
|
69
|
+
return `tenant_${normalized}`;
|
|
65
70
|
}
|
|
66
71
|
requestOptions() {
|
|
67
72
|
return {
|
|
@@ -274,9 +279,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImpor
|
|
|
274
279
|
}] } });
|
|
275
280
|
|
|
276
281
|
class FeatureFlagsManagerComponent {
|
|
277
|
-
constructor(api
|
|
282
|
+
constructor(api) {
|
|
278
283
|
this.api = api;
|
|
279
284
|
this.tenantId = '';
|
|
285
|
+
this.tenantIdChange = new EventEmitter();
|
|
280
286
|
this.groups = [];
|
|
281
287
|
this.loading = false;
|
|
282
288
|
this.error = null;
|
|
@@ -299,9 +305,19 @@ class FeatureFlagsManagerComponent {
|
|
|
299
305
|
this.showSummaryModal = false;
|
|
300
306
|
this.newlyCreatedGroups = new Set();
|
|
301
307
|
this.destroy$ = new Subject();
|
|
302
|
-
this.tenantId = config.tenantId;
|
|
303
308
|
}
|
|
304
|
-
|
|
309
|
+
ngOnChanges(changes) {
|
|
310
|
+
if (!changes['tenantId'])
|
|
311
|
+
return;
|
|
312
|
+
const id = this.tenantId.trim();
|
|
313
|
+
if (!id) {
|
|
314
|
+
this.groups = [];
|
|
315
|
+
this.loaded = false;
|
|
316
|
+
this.pendingChanges.clear();
|
|
317
|
+
this.clearMessages();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.tenantIdChange.emit(id);
|
|
305
321
|
this.loadFlags();
|
|
306
322
|
}
|
|
307
323
|
ngOnDestroy() {
|
|
@@ -330,10 +346,15 @@ class FeatureFlagsManagerComponent {
|
|
|
330
346
|
}
|
|
331
347
|
// ── Load ──────────────────────────────────────────────────────────
|
|
332
348
|
loadFlags() {
|
|
349
|
+
const id = this.tenantId.trim();
|
|
350
|
+
if (!id) {
|
|
351
|
+
this.error = 'Tenant ID is required.';
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
333
354
|
this.clearMessages();
|
|
334
355
|
this.pendingChanges.clear();
|
|
335
356
|
this.loading = true;
|
|
336
|
-
this.api.getByTenantId()
|
|
357
|
+
this.api.getByTenantId(id)
|
|
337
358
|
.pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))
|
|
338
359
|
.subscribe({
|
|
339
360
|
next: (doc) => {
|
|
@@ -399,7 +420,7 @@ class FeatureFlagsManagerComponent {
|
|
|
399
420
|
}
|
|
400
421
|
payload[change.group][change.key] = change.newValue;
|
|
401
422
|
}
|
|
402
|
-
this.api.upsert(payload)
|
|
423
|
+
this.api.upsert(this.tenantId, payload)
|
|
403
424
|
.pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))
|
|
404
425
|
.subscribe({
|
|
405
426
|
next: (doc) => {
|
|
@@ -427,7 +448,7 @@ class FeatureFlagsManagerComponent {
|
|
|
427
448
|
return;
|
|
428
449
|
}
|
|
429
450
|
this.loading = true;
|
|
430
|
-
this.api.upsert({ [name]: {} })
|
|
451
|
+
this.api.upsert(this.tenantId, { [name]: {} })
|
|
431
452
|
.pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))
|
|
432
453
|
.subscribe({
|
|
433
454
|
next: (doc) => {
|
|
@@ -475,7 +496,7 @@ class FeatureFlagsManagerComponent {
|
|
|
475
496
|
this.loading = true;
|
|
476
497
|
// Also clear any pending change for this flag
|
|
477
498
|
this.pendingChanges.delete(this.makeCompositeKey(groupName, flagKey));
|
|
478
|
-
this.api.deleteFlag(groupName, flagKey)
|
|
499
|
+
this.api.deleteFlag(this.tenantId, groupName, flagKey)
|
|
479
500
|
.pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))
|
|
480
501
|
.subscribe({
|
|
481
502
|
next: (doc) => {
|
|
@@ -495,7 +516,7 @@ class FeatureFlagsManagerComponent {
|
|
|
495
516
|
this.pendingChanges.delete(key);
|
|
496
517
|
}
|
|
497
518
|
}
|
|
498
|
-
this.api.deleteGroup(groupName)
|
|
519
|
+
this.api.deleteGroup(this.tenantId, groupName)
|
|
499
520
|
.pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))
|
|
500
521
|
.subscribe({
|
|
501
522
|
next: (doc) => {
|
|
@@ -577,16 +598,17 @@ class FeatureFlagsManagerComponent {
|
|
|
577
598
|
}
|
|
578
599
|
}
|
|
579
600
|
}
|
|
580
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FeatureFlagsManagerComponent, deps: [{ token: FeatureFlagsApiService }
|
|
581
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.5", type: FeatureFlagsManagerComponent, isStandalone: false, selector: "valar-feature-flags-manager", ngImport: i0, template: "<div class=\"container-fluid py-4 pb-5\">\n <!-- Header -->\n <div class=\"d-flex align-items-center mb-4\">\n <h2 class=\"mb-0\">\n <i class=\"bi bi-toggles2 me-2\" aria-hidden=\"true\"></i>Feature Flags\n </h2>\n </div>\n\n <!-- Alerts -->\n <div *ngIf=\"error\" class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle-fill me-2\" aria-hidden=\"true\"></i>{{ error }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"error = null\"></button>\n </div>\n <div *ngIf=\"successMsg\" class=\"alert alert-success alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-check-circle-fill me-2\" aria-hidden=\"true\"></i>{{ successMsg }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"successMsg = null\"></button>\n </div>\n <!-- Tenant Context -->\n <div class=\"card shadow-sm mb-4\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <label class=\"form-label fw-semibold mb-1\">Tenant ID</label>\n <div><code>{{ tenantId }}</code></div>\n </div>\n <button class=\"btn btn-primary\" [disabled]=\"loading\" (click)=\"loadFlags()\">\n <span *ngIf=\"loading\" class=\"spinner-border spinner-border-sm me-1\"\n role=\"status\" aria-hidden=\"true\"></span>\n {{ loading ? 'Loading...' : 'Reload Flags' }}\n </button>\n </div>\n <div class=\"form-text\">\n Tenant
|
|
601
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FeatureFlagsManagerComponent, deps: [{ token: FeatureFlagsApiService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
602
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.5", type: FeatureFlagsManagerComponent, isStandalone: false, selector: "valar-feature-flags-manager", inputs: { tenantId: "tenantId" }, outputs: { tenantIdChange: "tenantIdChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"container-fluid py-4 pb-5\">\n <!-- Header -->\n <div class=\"d-flex align-items-center mb-4\">\n <h2 class=\"mb-0\">\n <i class=\"bi bi-toggles2 me-2\" aria-hidden=\"true\"></i>Feature Flags\n </h2>\n </div>\n\n <!-- Alerts -->\n <div *ngIf=\"error\" class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle-fill me-2\" aria-hidden=\"true\"></i>{{ error }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"error = null\"></button>\n </div>\n <div *ngIf=\"successMsg\" class=\"alert alert-success alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-check-circle-fill me-2\" aria-hidden=\"true\"></i>{{ successMsg }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"successMsg = null\"></button>\n </div>\n <!-- Tenant Context -->\n <div class=\"card shadow-sm mb-4\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <label class=\"form-label fw-semibold mb-1\">Tenant ID</label>\n <div><code>{{ tenantId }}</code></div>\n </div>\n <button class=\"btn btn-primary\" [disabled]=\"loading\" (click)=\"loadFlags()\">\n <span *ngIf=\"loading\" class=\"spinner-border spinner-border-sm me-1\"\n role=\"status\" aria-hidden=\"true\"></span>\n {{ loading ? 'Loading...' : 'Reload Flags' }}\n </button>\n </div>\n <div class=\"form-text\">\n Tenant is provided by parent component input, while <code>x-tenant-id</code> comes from module config.\n </div>\n </div>\n </div>\n\n <!-- Content area (only after load) -->\n <div *ngIf=\"loaded\">\n <!-- Toolbar -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <span class=\"text-muted\">\n {{ groups.length }} group{{ groups.length !== 1 ? 's' : '' }} found\n </span>\n <button class=\"btn btn-success btn-sm\" (click)=\"showAddGroup = true\" [disabled]=\"showAddGroup\">\n <i class=\"bi bi-plus-circle me-1\" aria-hidden=\"true\"></i>Add Group\n </button>\n </div>\n\n <!-- Add Group Form -->\n <div *ngIf=\"showAddGroup\" class=\"card border-success mb-3\">\n <div class=\"card-body\">\n <h6 class=\"card-title text-success\">\n <i class=\"bi bi-folder-plus me-1\" aria-hidden=\"true\"></i>New Group\n </h6>\n <div class=\"input-group input-group-sm\">\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Group name (e.g. uiOptions)\"\n [(ngModel)]=\"newGroupName\"\n (keyup.enter)=\"addGroup()\"\n aria-label=\"New group name\" />\n <button class=\"btn btn-success\" (click)=\"addGroup()\" [disabled]=\"loading || !newGroupName.trim()\">\n {{ loading ? 'Creating...' : 'Create Group' }}\n </button>\n <button class=\"btn btn-outline-secondary\" (click)=\"showAddGroup = false; newGroupName = ''\">\n Cancel\n </button>\n </div>\n <div class=\"form-text\">Letters, numbers, _ and - only. Must start with a letter.</div>\n </div>\n </div>\n\n <!-- Flag Groups -->\n <div *ngFor=\"let group of groups; trackBy: trackGroup\" class=\"mb-3\">\n <valar-flag-group-card\n [groupName]=\"group.name\"\n [flags]=\"group.flags\"\n [isNewGroup]=\"isNewGroup(group)\"\n [dirtyKeys]=\"dirtyKeysForGroup(group.name)\"\n [isAddingFlag]=\"addingFlagToGroup === group.name\"\n [newFlagKey]=\"newFlagKey\"\n [newFlagValue]=\"newFlagValue\"\n [newFlagType]=\"newFlagType\"\n (flagChanged)=\"onFlagChanged(group.name, $event)\"\n (revertFlag)=\"revertFlag(group.name, $event)\"\n (addFlagRequest)=\"addingFlagToGroup = group.name\"\n (cancelAddFlag)=\"cancelAddFlag()\"\n (confirmAddFlag)=\"confirmAddFlag(group.name)\"\n (newFlagKeyChange)=\"newFlagKey = $event\"\n (newFlagValueChange)=\"newFlagValue = $event\"\n (newFlagTypeChange)=\"newFlagType = $event\"\n (deleteFlagRequest)=\"deleteFlag(group.name, $event)\"\n (deleteGroupRequest)=\"deleteGroup(group.name)\">\n </valar-flag-group-card>\n </div>\n\n <!-- Empty state -->\n <div *ngIf=\"groups.length === 0 && !showAddGroup\" class=\"text-center text-muted py-5\">\n <i class=\"bi bi-inbox display-1\" aria-hidden=\"true\"></i>\n <p class=\"mt-3\">No feature flag groups yet. Click <strong>Add Group</strong> to get started.</p>\n </div>\n </div>\n</div>\n\n<!-- Sticky bottom action bar (visible when there are pending changes) -->\n<div *ngIf=\"hasPendingChanges\" class=\"action-bar\">\n <div class=\"container-fluid d-flex justify-content-between align-items-center\">\n <span class=\"text-white\">\n <i class=\"bi bi-pencil-square me-1\" aria-hidden=\"true\"></i>\n <strong>{{ pendingChangeCount }}</strong>\n unsaved change{{ pendingChangeCount !== 1 ? 's' : '' }}\n </span>\n <div>\n <button class=\"btn btn-outline-light btn-sm me-2\" (click)=\"discardAll()\">\n <i class=\"bi bi-x-lg me-1\" aria-hidden=\"true\"></i>Discard All\n </button>\n <button class=\"btn btn-warning btn-sm fw-semibold\" (click)=\"openSummaryModal()\">\n <i class=\"bi bi-send me-1\" aria-hidden=\"true\"></i>Review & Submit\n </button>\n </div>\n </div>\n</div>\n\n<!-- Changes Summary Modal -->\n<valar-changes-summary-modal\n *ngIf=\"showSummaryModal\"\n [changes]=\"pendingChangesList\"\n [tenantId]=\"tenantId\"\n (confirm)=\"submitAll()\"\n (dismiss)=\"closeSummaryModal()\">\n</valar-changes-summary-modal>\n\n", styles: [":host{display:block}.bi{vertical-align:-.125em}.action-bar{position:fixed;bottom:0;left:0;right:0;z-index:1030;background:linear-gradient(135deg,#343a40,#495057);padding:.75rem 1rem;box-shadow:0 -4px 12px #00000040;animation:slideUp .25s ease-out}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FlagGroupCardComponent, selector: "valar-flag-group-card", inputs: ["groupName", "flags", "isNewGroup", "dirtyKeys", "isAddingFlag", "newFlagKey", "newFlagValue", "newFlagType"], outputs: ["flagChanged", "revertFlag", "addFlagRequest", "cancelAddFlag", "confirmAddFlag", "newFlagKeyChange", "newFlagValueChange", "newFlagTypeChange", "deleteFlagRequest", "deleteGroupRequest"] }, { kind: "component", type: ChangesSummaryModalComponent, selector: "valar-changes-summary-modal", inputs: ["changes", "tenantId"], outputs: ["confirm", "dismiss"] }] }); }
|
|
582
603
|
}
|
|
583
604
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FeatureFlagsManagerComponent, decorators: [{
|
|
584
605
|
type: Component,
|
|
585
|
-
args: [{ selector: 'valar-feature-flags-manager', standalone: false, template: "<div class=\"container-fluid py-4 pb-5\">\n <!-- Header -->\n <div class=\"d-flex align-items-center mb-4\">\n <h2 class=\"mb-0\">\n <i class=\"bi bi-toggles2 me-2\" aria-hidden=\"true\"></i>Feature Flags\n </h2>\n </div>\n\n <!-- Alerts -->\n <div *ngIf=\"error\" class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle-fill me-2\" aria-hidden=\"true\"></i>{{ error }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"error = null\"></button>\n </div>\n <div *ngIf=\"successMsg\" class=\"alert alert-success alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-check-circle-fill me-2\" aria-hidden=\"true\"></i>{{ successMsg }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"successMsg = null\"></button>\n </div>\n <!-- Tenant Context -->\n <div class=\"card shadow-sm mb-4\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <label class=\"form-label fw-semibold mb-1\">Tenant ID</label>\n <div><code>{{ tenantId }}</code></div>\n </div>\n <button class=\"btn btn-primary\" [disabled]=\"loading\" (click)=\"loadFlags()\">\n <span *ngIf=\"loading\" class=\"spinner-border spinner-border-sm me-1\"\n role=\"status\" aria-hidden=\"true\"></span>\n {{ loading ? 'Loading...' : 'Reload Flags' }}\n </button>\n </div>\n <div class=\"form-text\">\n Tenant
|
|
586
|
-
}], ctorParameters: () => [{ type: FeatureFlagsApiService },
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
606
|
+
args: [{ selector: 'valar-feature-flags-manager', standalone: false, template: "<div class=\"container-fluid py-4 pb-5\">\n <!-- Header -->\n <div class=\"d-flex align-items-center mb-4\">\n <h2 class=\"mb-0\">\n <i class=\"bi bi-toggles2 me-2\" aria-hidden=\"true\"></i>Feature Flags\n </h2>\n </div>\n\n <!-- Alerts -->\n <div *ngIf=\"error\" class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle-fill me-2\" aria-hidden=\"true\"></i>{{ error }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"error = null\"></button>\n </div>\n <div *ngIf=\"successMsg\" class=\"alert alert-success alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-check-circle-fill me-2\" aria-hidden=\"true\"></i>{{ successMsg }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"successMsg = null\"></button>\n </div>\n <!-- Tenant Context -->\n <div class=\"card shadow-sm mb-4\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <label class=\"form-label fw-semibold mb-1\">Tenant ID</label>\n <div><code>{{ tenantId }}</code></div>\n </div>\n <button class=\"btn btn-primary\" [disabled]=\"loading\" (click)=\"loadFlags()\">\n <span *ngIf=\"loading\" class=\"spinner-border spinner-border-sm me-1\"\n role=\"status\" aria-hidden=\"true\"></span>\n {{ loading ? 'Loading...' : 'Reload Flags' }}\n </button>\n </div>\n <div class=\"form-text\">\n Tenant is provided by parent component input, while <code>x-tenant-id</code> comes from module config.\n </div>\n </div>\n </div>\n\n <!-- Content area (only after load) -->\n <div *ngIf=\"loaded\">\n <!-- Toolbar -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <span class=\"text-muted\">\n {{ groups.length }} group{{ groups.length !== 1 ? 's' : '' }} found\n </span>\n <button class=\"btn btn-success btn-sm\" (click)=\"showAddGroup = true\" [disabled]=\"showAddGroup\">\n <i class=\"bi bi-plus-circle me-1\" aria-hidden=\"true\"></i>Add Group\n </button>\n </div>\n\n <!-- Add Group Form -->\n <div *ngIf=\"showAddGroup\" class=\"card border-success mb-3\">\n <div class=\"card-body\">\n <h6 class=\"card-title text-success\">\n <i class=\"bi bi-folder-plus me-1\" aria-hidden=\"true\"></i>New Group\n </h6>\n <div class=\"input-group input-group-sm\">\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Group name (e.g. uiOptions)\"\n [(ngModel)]=\"newGroupName\"\n (keyup.enter)=\"addGroup()\"\n aria-label=\"New group name\" />\n <button class=\"btn btn-success\" (click)=\"addGroup()\" [disabled]=\"loading || !newGroupName.trim()\">\n {{ loading ? 'Creating...' : 'Create Group' }}\n </button>\n <button class=\"btn btn-outline-secondary\" (click)=\"showAddGroup = false; newGroupName = ''\">\n Cancel\n </button>\n </div>\n <div class=\"form-text\">Letters, numbers, _ and - only. Must start with a letter.</div>\n </div>\n </div>\n\n <!-- Flag Groups -->\n <div *ngFor=\"let group of groups; trackBy: trackGroup\" class=\"mb-3\">\n <valar-flag-group-card\n [groupName]=\"group.name\"\n [flags]=\"group.flags\"\n [isNewGroup]=\"isNewGroup(group)\"\n [dirtyKeys]=\"dirtyKeysForGroup(group.name)\"\n [isAddingFlag]=\"addingFlagToGroup === group.name\"\n [newFlagKey]=\"newFlagKey\"\n [newFlagValue]=\"newFlagValue\"\n [newFlagType]=\"newFlagType\"\n (flagChanged)=\"onFlagChanged(group.name, $event)\"\n (revertFlag)=\"revertFlag(group.name, $event)\"\n (addFlagRequest)=\"addingFlagToGroup = group.name\"\n (cancelAddFlag)=\"cancelAddFlag()\"\n (confirmAddFlag)=\"confirmAddFlag(group.name)\"\n (newFlagKeyChange)=\"newFlagKey = $event\"\n (newFlagValueChange)=\"newFlagValue = $event\"\n (newFlagTypeChange)=\"newFlagType = $event\"\n (deleteFlagRequest)=\"deleteFlag(group.name, $event)\"\n (deleteGroupRequest)=\"deleteGroup(group.name)\">\n </valar-flag-group-card>\n </div>\n\n <!-- Empty state -->\n <div *ngIf=\"groups.length === 0 && !showAddGroup\" class=\"text-center text-muted py-5\">\n <i class=\"bi bi-inbox display-1\" aria-hidden=\"true\"></i>\n <p class=\"mt-3\">No feature flag groups yet. Click <strong>Add Group</strong> to get started.</p>\n </div>\n </div>\n</div>\n\n<!-- Sticky bottom action bar (visible when there are pending changes) -->\n<div *ngIf=\"hasPendingChanges\" class=\"action-bar\">\n <div class=\"container-fluid d-flex justify-content-between align-items-center\">\n <span class=\"text-white\">\n <i class=\"bi bi-pencil-square me-1\" aria-hidden=\"true\"></i>\n <strong>{{ pendingChangeCount }}</strong>\n unsaved change{{ pendingChangeCount !== 1 ? 's' : '' }}\n </span>\n <div>\n <button class=\"btn btn-outline-light btn-sm me-2\" (click)=\"discardAll()\">\n <i class=\"bi bi-x-lg me-1\" aria-hidden=\"true\"></i>Discard All\n </button>\n <button class=\"btn btn-warning btn-sm fw-semibold\" (click)=\"openSummaryModal()\">\n <i class=\"bi bi-send me-1\" aria-hidden=\"true\"></i>Review & Submit\n </button>\n </div>\n </div>\n</div>\n\n<!-- Changes Summary Modal -->\n<valar-changes-summary-modal\n *ngIf=\"showSummaryModal\"\n [changes]=\"pendingChangesList\"\n [tenantId]=\"tenantId\"\n (confirm)=\"submitAll()\"\n (dismiss)=\"closeSummaryModal()\">\n</valar-changes-summary-modal>\n\n", styles: [":host{display:block}.bi{vertical-align:-.125em}.action-bar{position:fixed;bottom:0;left:0;right:0;z-index:1030;background:linear-gradient(135deg,#343a40,#495057);padding:.75rem 1rem;box-shadow:0 -4px 12px #00000040;animation:slideUp .25s ease-out}@keyframes slideUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}\n"] }]
|
|
607
|
+
}], ctorParameters: () => [{ type: FeatureFlagsApiService }], propDecorators: { tenantId: [{
|
|
608
|
+
type: Input
|
|
609
|
+
}], tenantIdChange: [{
|
|
610
|
+
type: Output
|
|
611
|
+
}] } });
|
|
590
612
|
|
|
591
613
|
/**
|
|
592
614
|
* In-memory mock of FeatureFlagsApiService.
|
|
@@ -598,23 +620,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImpor
|
|
|
598
620
|
class MockFeatureFlagsApiService {
|
|
599
621
|
constructor() {
|
|
600
622
|
this.store = new Map();
|
|
601
|
-
this.activeTenantId = 'tenant_acme';
|
|
602
623
|
this.seedData();
|
|
603
624
|
}
|
|
604
625
|
// ────────────────────────── Public API (same shape as real service) ──
|
|
605
|
-
getByTenantId() {
|
|
606
|
-
const doc = this.store.get(this.
|
|
626
|
+
getByTenantId(tenantId) {
|
|
627
|
+
const doc = this.store.get(this.toTenantToken(tenantId)) ?? null;
|
|
607
628
|
return of(this.clone(doc)).pipe(delay(250));
|
|
608
629
|
}
|
|
609
|
-
upsert(payload) {
|
|
610
|
-
const
|
|
611
|
-
let doc = this.store.get(
|
|
630
|
+
upsert(tenantId, payload) {
|
|
631
|
+
const tenantToken = this.toTenantToken(tenantId);
|
|
632
|
+
let doc = this.store.get(tenantToken);
|
|
612
633
|
if (!doc) {
|
|
613
|
-
doc = this.newDoc(
|
|
614
|
-
this.store.set(
|
|
634
|
+
doc = this.newDoc(tenantToken);
|
|
635
|
+
this.store.set(tenantToken, doc);
|
|
615
636
|
}
|
|
616
637
|
// Merge groups via dot-path (mirrors the backend's $set behavior)
|
|
617
|
-
for (const [groupName, groupFlags] of Object.entries(
|
|
638
|
+
for (const [groupName, groupFlags] of Object.entries(payload)) {
|
|
618
639
|
if (RESERVED_KEYS.has(groupName))
|
|
619
640
|
continue;
|
|
620
641
|
const existing = doc[groupName] ?? {};
|
|
@@ -623,11 +644,12 @@ class MockFeatureFlagsApiService {
|
|
|
623
644
|
doc.updatedAt = new Date().toISOString();
|
|
624
645
|
return of(this.clone(doc)).pipe(delay(200));
|
|
625
646
|
}
|
|
626
|
-
deleteFlag(group, key) {
|
|
627
|
-
const
|
|
647
|
+
deleteFlag(tenantId, group, key) {
|
|
648
|
+
const tenantToken = this.toTenantToken(tenantId);
|
|
649
|
+
const doc = this.store.get(tenantToken);
|
|
628
650
|
if (!doc) {
|
|
629
651
|
return throwError(() => ({
|
|
630
|
-
error: { message: `No feature flags found for tenantId='${
|
|
652
|
+
error: { message: `No feature flags found for tenantId='${tenantToken}'` },
|
|
631
653
|
}));
|
|
632
654
|
}
|
|
633
655
|
const g = doc[group];
|
|
@@ -637,11 +659,12 @@ class MockFeatureFlagsApiService {
|
|
|
637
659
|
doc.updatedAt = new Date().toISOString();
|
|
638
660
|
return of(this.clone(doc)).pipe(delay(200));
|
|
639
661
|
}
|
|
640
|
-
deleteGroup(group) {
|
|
641
|
-
const
|
|
662
|
+
deleteGroup(tenantId, group) {
|
|
663
|
+
const tenantToken = this.toTenantToken(tenantId);
|
|
664
|
+
const doc = this.store.get(tenantToken);
|
|
642
665
|
if (!doc) {
|
|
643
666
|
return throwError(() => ({
|
|
644
|
-
error: { message: `No feature flags found for tenantId='${
|
|
667
|
+
error: { message: `No feature flags found for tenantId='${tenantToken}'` },
|
|
645
668
|
}));
|
|
646
669
|
}
|
|
647
670
|
delete doc[group];
|
|
@@ -728,6 +751,12 @@ class MockFeatureFlagsApiService {
|
|
|
728
751
|
const now = new Date().toISOString();
|
|
729
752
|
return { tenantId, createdAt: now, updatedAt: now };
|
|
730
753
|
}
|
|
754
|
+
toTenantToken(tenantId) {
|
|
755
|
+
const normalized = tenantId.trim();
|
|
756
|
+
if (normalized.startsWith('tenant_'))
|
|
757
|
+
return normalized;
|
|
758
|
+
return `tenant_${normalized}`;
|
|
759
|
+
}
|
|
731
760
|
/** Deep-clone to avoid leaking mutable references */
|
|
732
761
|
clone(obj) {
|
|
733
762
|
return JSON.parse(JSON.stringify(obj));
|
|
@@ -748,9 +777,12 @@ class FeatureFlagsModule {
|
|
|
748
777
|
* FeatureFlagsModule.forRoot({
|
|
749
778
|
* apiBaseUrl: 'http://localhost:3000',
|
|
750
779
|
* apiVersion: 'v1',
|
|
751
|
-
* tenantId: 'abc123', // used in /feature-flags/tenant_abc123
|
|
752
780
|
* rootTenantId: 'root001', // sent in x-tenant-id header
|
|
753
781
|
* })
|
|
782
|
+
*
|
|
783
|
+
* <valar-feature-flags-manager
|
|
784
|
+
* [(tenantId)]="selectedTenantId">
|
|
785
|
+
* </valar-feature-flags-manager>
|
|
754
786
|
*/
|
|
755
787
|
static forRoot(config) {
|
|
756
788
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pl4yzonellc-valar-ui.mjs","sources":["../../../projects/valar-ui/src/lib/feature-flags/models/feature-flags.model.ts","../../../projects/valar-ui/src/lib/feature-flags/services/feature-flags-api.service.ts","../../../projects/valar-ui/src/lib/feature-flags/components/flag-group-card/flag-group-card.component.ts","../../../projects/valar-ui/src/lib/feature-flags/components/flag-group-card/flag-group-card.component.html","../../../projects/valar-ui/src/lib/feature-flags/components/changes-summary-modal/changes-summary-modal.component.ts","../../../projects/valar-ui/src/lib/feature-flags/components/changes-summary-modal/changes-summary-modal.component.html","../../../projects/valar-ui/src/lib/feature-flags/components/feature-flags-manager/feature-flags-manager.component.ts","../../../projects/valar-ui/src/lib/feature-flags/components/feature-flags-manager/feature-flags-manager.component.html","../../../projects/valar-ui/src/lib/feature-flags/services/mock-feature-flags-api.service.ts","../../../projects/valar-ui/src/lib/feature-flags/feature-flags.module.ts","../../../projects/valar-ui/src/public-api.ts","../../../projects/valar-ui/src/pl4yzonellc-valar-ui.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\n/**\n * Primitive types allowed as feature flag values.\n * Mirrors the backend's FlagPrimitive type.\n */\nexport type FlagPrimitive = boolean | string | number | null;\n\n/**\n * A group of feature flags: key → primitive value.\n */\nexport type FlagGroup = Record<string, FlagPrimitive>;\n\n/**\n * The full feature flags document for a tenant.\n * Dynamic group names sit at the top level alongside metadata.\n */\nexport interface FeatureFlagsDocument {\n _id?: string;\n tenantId: string;\n createdAt?: string;\n updatedAt?: string;\n [groupName: string]: FlagGroup | string | undefined;\n}\n\n/**\n * Shape of the upsert request body sent to the backend.\n * tenantId + any number of group objects.\n */\nexport interface UpsertFlagsPayload {\n [groupName: string]: FlagGroup | string;\n}\n\n/**\n * Configuration for the FeatureFlags library module.\n * The consuming app provides this via forRoot().\n */\nexport interface FeatureFlagsConfig {\n /** Base URL of the Valar API, e.g. 'http://localhost:3000' */\n apiBaseUrl: string;\n /** API version prefix, defaults to 'v1' */\n apiVersion?: string;\n /** Target tenant id used in API paths (e.g. /feature-flags/tenant_<tenantId>) */\n tenantId: string;\n /** Root tenant id sent in request headers */\n rootTenantId: string;\n /** Tenant header name, defaults to 'x-tenant-id' */\n tenantHeaderName?: string;\n}\n\n/** Injection token for the library config */\nexport const FEATURE_FLAGS_CONFIG = new InjectionToken<FeatureFlagsConfig>(\n 'FEATURE_FLAGS_CONFIG'\n);\n\n/** Reserved keys that are NOT flag groups */\nexport const RESERVED_KEYS = new Set([\n '_id', 'tenantId', 'createdAt', 'updatedAt', '__v', 'id',\n]);\n\n/** A single staged change for the batch-edit workflow */\nexport interface PendingChange {\n group: string;\n key: string;\n oldValue: FlagPrimitive;\n newValue: FlagPrimitive;\n isNew?: boolean; // true if this is a brand-new flag\n}\n","import { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Inject, Injectable } from '@angular/core';\nimport { Observable, map } from 'rxjs';\nimport {\n FEATURE_FLAGS_CONFIG,\n FeatureFlagsConfig,\n FeatureFlagsDocument,\n UpsertFlagsPayload,\n} from '../models';\n\n/**\n * HTTP service that talks to the Valar feature-flags backend.\n * Base URL is injected via FEATURE_FLAGS_CONFIG so the consuming\n * app controls where requests go.\n */\n@Injectable()\nexport class FeatureFlagsApiService {\n private readonly baseUrl: string;\n private readonly tenantId: string;\n private readonly rootTenantId: string;\n private readonly tenantHeaderName: string;\n\n constructor(\n private readonly http: HttpClient,\n @Inject(FEATURE_FLAGS_CONFIG) config: FeatureFlagsConfig,\n ) {\n const version = config.apiVersion ?? 'v1';\n this.baseUrl = `${config.apiBaseUrl}/${version}/feature-flags`;\n this.tenantId = config.tenantId;\n this.rootTenantId = config.rootTenantId;\n this.tenantHeaderName = config.tenantHeaderName ?? 'x-tenant-id';\n }\n\n /** GET /v1/feature-flags/tenant_<tenantId> */\n getByTenantId(): Observable<FeatureFlagsDocument | null> {\n return this.http.get<FeatureFlagsDocument | null>(this.tenantScopedBaseUrl(), this.requestOptions())\n .pipe(map(doc => doc ?? null));\n }\n\n /** PUT /v1/feature-flags (merge-upsert) */\n upsert(payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument> {\n const requestPayload: UpsertFlagsPayload = {\n ...payload,\n tenantId: `tenant_${this.tenantId}`,\n };\n return this.http.put<FeatureFlagsDocument>(this.baseUrl, requestPayload, this.requestOptions());\n }\n\n /** DELETE /v1/feature-flags/tenant_<tenantId>/:group/:key */\n deleteFlag(group: string, key: string): Observable<FeatureFlagsDocument> {\n const url = [\n this.tenantScopedBaseUrl(),\n encodeURIComponent(group),\n encodeURIComponent(key),\n ].join('/');\n return this.http.delete<FeatureFlagsDocument>(url, this.requestOptions());\n }\n\n /** DELETE /v1/feature-flags/tenant_<tenantId>/:group */\n deleteGroup(group: string): Observable<FeatureFlagsDocument> {\n const url = [\n this.tenantScopedBaseUrl(),\n encodeURIComponent(group),\n ].join('/');\n return this.http.delete<FeatureFlagsDocument>(url, this.requestOptions());\n }\n\n private tenantScopedBaseUrl(): string {\n return `${this.baseUrl}/tenant_${encodeURIComponent(this.tenantId)}`;\n }\n\n private requestOptions(): { headers: HttpHeaders } {\n return {\n headers: new HttpHeaders({\n [this.tenantHeaderName]: this.rootTenantId,\n }),\n };\n }\n}\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { FlagPrimitive } from '../../models';\n\nexport interface FlagEntry {\n key: string;\n value: FlagPrimitive;\n}\n\nexport interface FlagChangedEvent {\n key: string;\n oldValue: FlagPrimitive;\n newValue: FlagPrimitive;\n}\n\n@Component({\n selector: 'valar-flag-group-card',\n templateUrl: './flag-group-card.component.html',\n styleUrls: ['./flag-group-card.component.css'],\n standalone: false,\n})\nexport class FlagGroupCardComponent {\n @Input() groupName = '';\n @Input() flags: FlagEntry[] = [];\n @Input() isNewGroup = false;\n @Input() dirtyKeys: Set<string> = new Set();\n @Input() isAddingFlag = false;\n @Input() newFlagKey = '';\n @Input() newFlagValue = '';\n @Input() newFlagType: 'string' | 'boolean' | 'number' = 'string';\n\n @Output() flagChanged = new EventEmitter<FlagChangedEvent>();\n @Output() revertFlag = new EventEmitter<string>();\n @Output() addFlagRequest = new EventEmitter<void>();\n @Output() cancelAddFlag = new EventEmitter<void>();\n @Output() confirmAddFlag = new EventEmitter<void>();\n @Output() newFlagKeyChange = new EventEmitter<string>();\n @Output() newFlagValueChange = new EventEmitter<string>();\n @Output() newFlagTypeChange = new EventEmitter<'string' | 'boolean' | 'number'>();\n @Output() deleteFlagRequest = new EventEmitter<string>();\n @Output() deleteGroupRequest = new EventEmitter<void>();\n\n collapsed = false;\n confirmDeleteGroup = false;\n confirmDeleteKey: string | null = null;\n\n /** Which flag key is currently in inline-edit mode */\n editingKey: string | null = null;\n editValue = '';\n editType: 'string' | 'boolean' | 'number' = 'string';\n\n isDirty(key: string): boolean {\n return this.dirtyKeys.has(key);\n }\n\n /** Enter inline-edit mode for a flag */\n startEdit(flag: FlagEntry): void {\n this.editingKey = flag.key;\n this.editType = this.detectType(flag.value);\n this.editValue = flag.value === null ? '' : String(flag.value);\n }\n\n /** Commit the inline edit — emits change to parent, stays in view mode */\n commitEdit(flag: FlagEntry): void {\n const newValue = this.coerceValue(this.editValue, this.editType);\n this.flagChanged.emit({ key: flag.key, oldValue: flag.value, newValue });\n this.editingKey = null;\n }\n\n /** Cancel inline edit without staging anything */\n cancelEdit(): void {\n this.editingKey = null;\n }\n\n displayValue(value: FlagPrimitive): string {\n if (value === null) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n return String(value);\n }\n\n typeBadgeClass(value: FlagPrimitive): string {\n if (typeof value === 'boolean') return 'bg-info text-dark';\n if (typeof value === 'number') return 'bg-warning text-dark';\n if (value === null) return 'bg-secondary';\n return 'bg-primary';\n }\n\n typeLabel(value: FlagPrimitive): string {\n if (value === null) return 'null';\n return typeof value;\n }\n\n isBooleanFlag(value: FlagPrimitive): boolean {\n return typeof value === 'boolean';\n }\n\n trackFlag(_index: number, flag: FlagEntry): string {\n return flag.key;\n }\n\n onDeleteFlag(key: string): void {\n if (this.confirmDeleteKey === key) {\n this.deleteFlagRequest.emit(key);\n this.confirmDeleteKey = null;\n } else {\n this.confirmDeleteKey = key;\n }\n }\n\n onDeleteGroup(): void {\n if (this.confirmDeleteGroup) {\n this.deleteGroupRequest.emit();\n this.confirmDeleteGroup = false;\n } else {\n this.confirmDeleteGroup = true;\n }\n }\n\n private detectType(value: FlagPrimitive): 'string' | 'boolean' | 'number' {\n if (typeof value === 'boolean') return 'boolean';\n if (typeof value === 'number') return 'number';\n return 'string';\n }\n\n private coerceValue(raw: string, type: 'string' | 'boolean' | 'number'): FlagPrimitive {\n const trimmed = raw.trim();\n if (trimmed === '' || trimmed.toLowerCase() === 'null') return null;\n switch (type) {\n case 'boolean':\n return trimmed.toLowerCase() === 'true';\n case 'number': {\n const n = Number(trimmed);\n return isNaN(n) ? trimmed : n;\n }\n default:\n return trimmed;\n }\n }\n}\n","<div class=\"card shadow-sm\">\n <!-- Card Header -->\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <div class=\"d-flex align-items-center\">\n <button\n class=\"btn btn-sm btn-link text-decoration-none p-0 me-2\"\n (click)=\"collapsed = !collapsed\"\n [attr.aria-expanded]=\"!collapsed\"\n [attr.aria-controls]=\"'group-' + groupName\"\n aria-label=\"Toggle group\">\n <i class=\"bi\" [ngClass]=\"collapsed ? 'bi-chevron-right' : 'bi-chevron-down'\" aria-hidden=\"true\"></i>\n </button>\n <h6 class=\"mb-0 fw-bold\">\n <i class=\"bi bi-folder2 me-1 text-primary\" aria-hidden=\"true\"></i>\n {{ groupName }}\n </h6>\n <span *ngIf=\"isNewGroup\" class=\"badge bg-success ms-2\">NEW</span>\n <span class=\"badge bg-secondary ms-2\">{{ flags.length }}</span>\n <span *ngIf=\"dirtyKeys.size > 0\" class=\"badge bg-warning text-dark ms-1\" title=\"Unsaved changes\">\n {{ dirtyKeys.size }} edited\n </span>\n </div>\n <div class=\"btn-group btn-group-sm\">\n <button\n class=\"btn btn-outline-primary btn-sm\"\n (click)=\"addFlagRequest.emit()\"\n title=\"Add flag to this group\"\n aria-label=\"Add flag\">\n <i class=\"bi bi-plus\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"btn btn-sm\"\n [ngClass]=\"confirmDeleteGroup ? 'btn-danger' : 'btn-outline-danger'\"\n (click)=\"onDeleteGroup()\"\n [title]=\"confirmDeleteGroup ? 'Click again to confirm' : 'Delete entire group'\"\n [attr.aria-label]=\"confirmDeleteGroup ? 'Confirm delete group' : 'Delete group'\">\n <i class=\"bi\" [ngClass]=\"confirmDeleteGroup ? 'bi-exclamation-triangle' : 'bi-trash'\" aria-hidden=\"true\"></i>\n <span *ngIf=\"confirmDeleteGroup\" class=\"ms-1\">Confirm?</span>\n </button>\n </div>\n </div>\n\n <!-- Card Body -->\n <div [id]=\"'group-' + groupName\" *ngIf=\"!collapsed\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover table-sm mb-0\" [attr.aria-label]=\"'Flags in ' + groupName\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\" style=\"width: 28%\">Key</th>\n <th scope=\"col\" style=\"width: 12%\">Type</th>\n <th scope=\"col\" style=\"width: 38%\">Value</th>\n <th scope=\"col\" style=\"width: 22%\" class=\"text-end\">Actions</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let flag of flags; trackBy: trackFlag\"\n [class.row-dirty]=\"isDirty(flag.key)\">\n <!-- KEY -->\n <td class=\"align-middle\">\n <code>{{ flag.key }}</code>\n <i *ngIf=\"isDirty(flag.key)\" class=\"bi bi-pencil-fill text-warning ms-1 small\"\n title=\"Edited\" aria-hidden=\"true\"></i>\n </td>\n\n <!-- TYPE -->\n <td class=\"align-middle\">\n <span class=\"badge\" [ngClass]=\"typeBadgeClass(flag.value)\">\n {{ typeLabel(flag.value) }}\n </span>\n </td>\n\n <!-- VALUE (view or inline-edit) -->\n <td class=\"align-middle\">\n <ng-container *ngIf=\"editingKey !== flag.key; else editMode\">\n <!-- Boolean as ON/OFF badge -->\n <span *ngIf=\"isBooleanFlag(flag.value); else plainValue\">\n <span class=\"badge\" [ngClass]=\"flag.value ? 'bg-success' : 'bg-danger'\">\n {{ flag.value ? 'ON' : 'OFF' }}\n </span>\n </span>\n <ng-template #plainValue>\n <span class=\"text-break\">{{ displayValue(flag.value) }}</span>\n </ng-template>\n </ng-container>\n\n <!-- Inline edit mode -->\n <ng-template #editMode>\n <!-- Boolean: simple toggle, no type dropdown -->\n <ng-container *ngIf=\"editType === 'boolean'; else nonBooleanEdit\">\n <select\n class=\"form-select form-select-sm\"\n style=\"max-width: 120px\"\n [(ngModel)]=\"editValue\"\n aria-label=\"Flag value\">\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n </ng-container>\n <!-- Non-boolean: type dropdown + value input -->\n <ng-template #nonBooleanEdit>\n <div class=\"input-group input-group-sm\">\n <select\n class=\"form-select\"\n style=\"max-width: 100px\"\n [(ngModel)]=\"editType\"\n aria-label=\"Flag type\">\n <option value=\"string\">string</option>\n <option value=\"number\">number</option>\n </select>\n <input\n type=\"text\"\n class=\"form-control\"\n [(ngModel)]=\"editValue\"\n (keyup.enter)=\"commitEdit(flag)\"\n aria-label=\"Flag value\" />\n </div>\n </ng-template>\n </ng-template>\n </td>\n\n <!-- ACTIONS -->\n <td class=\"align-middle text-end\">\n <ng-container *ngIf=\"editingKey !== flag.key; else editActions\">\n <button\n class=\"btn btn-sm btn-outline-secondary me-1\"\n (click)=\"startEdit(flag)\"\n title=\"Edit flag\"\n aria-label=\"Edit flag\">\n <i class=\"bi bi-pencil\" aria-hidden=\"true\"></i>\n </button>\n <button *ngIf=\"isDirty(flag.key)\"\n class=\"btn btn-sm btn-outline-warning me-1\"\n (click)=\"revertFlag.emit(flag.key)\"\n title=\"Revert change\"\n aria-label=\"Revert change\">\n <i class=\"bi bi-arrow-counterclockwise\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"btn btn-sm\"\n [ngClass]=\"confirmDeleteKey === flag.key ? 'btn-danger' : 'btn-outline-danger'\"\n (click)=\"onDeleteFlag(flag.key)\"\n [title]=\"confirmDeleteKey === flag.key ? 'Click again to confirm' : 'Delete flag'\"\n [attr.aria-label]=\"confirmDeleteKey === flag.key ? 'Confirm delete' : 'Delete flag'\">\n <i class=\"bi\" [ngClass]=\"confirmDeleteKey === flag.key ? 'bi-exclamation-triangle' : 'bi-trash'\" aria-hidden=\"true\"></i>\n </button>\n </ng-container>\n <ng-template #editActions>\n <button\n class=\"btn btn-sm btn-success me-1\"\n (click)=\"commitEdit(flag)\"\n title=\"Stage change\"\n aria-label=\"Stage change\">\n <i class=\"bi bi-check-lg\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"cancelEdit()\"\n title=\"Cancel\"\n aria-label=\"Cancel edit\">\n <i class=\"bi bi-x-lg\" aria-hidden=\"true\"></i>\n </button>\n </ng-template>\n </td>\n </tr>\n\n <!-- Empty row -->\n <tr *ngIf=\"flags.length === 0 && !isAddingFlag\">\n <td colspan=\"4\" class=\"text-center text-muted py-3\">\n No flags in this group.\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Add Flag Form -->\n <div *ngIf=\"isAddingFlag\" class=\"card-footer bg-light\">\n <h6 class=\"text-success mb-2\">\n <i class=\"bi bi-plus-circle me-1\" aria-hidden=\"true\"></i>Add Flag to \"{{ groupName }}\"\n </h6>\n <div class=\"row g-2 align-items-end\">\n <div class=\"col-md-3\">\n <label class=\"form-label form-label-sm\">Key</label>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n placeholder=\"flagKey\"\n [ngModel]=\"newFlagKey\"\n (ngModelChange)=\"newFlagKeyChange.emit($event)\"\n (keyup.enter)=\"confirmAddFlag.emit()\"\n aria-label=\"New flag key\" />\n </div>\n <div class=\"col-md-2\">\n <label class=\"form-label form-label-sm\">Type</label>\n <select\n class=\"form-select form-select-sm\"\n [ngModel]=\"newFlagType\"\n (ngModelChange)=\"newFlagTypeChange.emit($event)\"\n aria-label=\"New flag type\">\n <option value=\"string\">string</option>\n <option value=\"boolean\">boolean</option>\n <option value=\"number\">number</option>\n </select>\n </div>\n <div class=\"col-md-3\">\n <label class=\"form-label form-label-sm\">Value</label>\n <ng-container *ngIf=\"newFlagType === 'boolean'; else newTextInput\">\n <select\n class=\"form-select form-select-sm\"\n [ngModel]=\"newFlagValue\"\n (ngModelChange)=\"newFlagValueChange.emit($event)\"\n aria-label=\"New flag value\">\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n </ng-container>\n <ng-template #newTextInput>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n placeholder=\"value\"\n [ngModel]=\"newFlagValue\"\n (ngModelChange)=\"newFlagValueChange.emit($event)\"\n (keyup.enter)=\"confirmAddFlag.emit()\"\n aria-label=\"New flag value\" />\n </ng-template>\n </div>\n <div class=\"col-md-4\">\n <button class=\"btn btn-success btn-sm me-1\" (click)=\"confirmAddFlag.emit()\" [disabled]=\"!newFlagKey.trim()\">\n <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\"></i>Stage\n </button>\n <button class=\"btn btn-outline-secondary btn-sm\" (click)=\"cancelAddFlag.emit()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n </div>\n</div>\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { PendingChange, FlagPrimitive } from '../../models';\n\n@Component({\n selector: 'valar-changes-summary-modal',\n templateUrl: './changes-summary-modal.component.html',\n styleUrls: ['./changes-summary-modal.component.css'],\n standalone: false,\n})\nexport class ChangesSummaryModalComponent {\n @Input() changes: PendingChange[] = [];\n @Input() tenantId = '';\n @Output() confirm = new EventEmitter<void>();\n @Output() dismiss = new EventEmitter<void>();\n\n displayValue(value: FlagPrimitive): string {\n if (value === null) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n return String(value);\n }\n\n typeLabel(value: FlagPrimitive): string {\n if (value === null) return 'null';\n return typeof value;\n }\n}\n","<!-- Backdrop -->\n<div class=\"modal-backdrop fade show\" (click)=\"dismiss.emit()\"></div>\n\n<!-- Modal -->\n<div class=\"modal fade show d-block\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"changesSummaryTitle\" aria-modal=\"true\">\n <div class=\"modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable\">\n <div class=\"modal-content\">\n <!-- Header -->\n <div class=\"modal-header bg-primary text-white\">\n <h5 class=\"modal-title\" id=\"changesSummaryTitle\">\n <i class=\"bi bi-clipboard-check me-2\" aria-hidden=\"true\"></i>\n Review Changes\n </h5>\n <button type=\"button\" class=\"btn-close btn-close-white\" aria-label=\"Close\" (click)=\"dismiss.emit()\"></button>\n </div>\n\n <!-- Body -->\n <div class=\"modal-body\">\n <p class=\"text-muted mb-3\">\n You're about to update <strong>{{ changes.length }}</strong>\n flag{{ changes.length !== 1 ? 's' : '' }} for tenant\n <code>{{ tenantId }}</code>.\n </p>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0\" aria-label=\"Pending changes summary\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\">Group</th>\n <th scope=\"col\">Key</th>\n <th scope=\"col\">Old Value</th>\n <th scope=\"col\" class=\"text-center\" style=\"width: 40px\">\n <i class=\"bi bi-arrow-right\" aria-hidden=\"true\"></i>\n </th>\n <th scope=\"col\">New Value</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let change of changes\">\n <td>\n <i class=\"bi bi-folder2 me-1 text-primary\" aria-hidden=\"true\"></i>\n {{ change.group }}\n </td>\n <td><code>{{ change.key }}</code></td>\n <td>\n <span class=\"text-danger text-decoration-line-through\">\n {{ displayValue(change.oldValue) }}\n </span>\n <span class=\"badge bg-secondary ms-1\">{{ typeLabel(change.oldValue) }}</span>\n <span *ngIf=\"change.isNew\" class=\"badge bg-success ms-1\">NEW</span>\n </td>\n <td class=\"text-center text-muted\">\n <i class=\"bi bi-arrow-right\" aria-hidden=\"true\"></i>\n </td>\n <td>\n <span class=\"fw-semibold text-success\">\n {{ displayValue(change.newValue) }}\n </span>\n <span class=\"badge bg-secondary ms-1\">{{ typeLabel(change.newValue) }}</span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"dismiss.emit()\">\n <i class=\"bi bi-x-lg me-1\" aria-hidden=\"true\"></i>Cancel\n </button>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"confirm.emit()\">\n <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\"></i>Looks Good\n </button>\n </div>\n </div>\n </div>\n</div>\n","import { Component, Inject, OnDestroy, OnInit } from '@angular/core';\nimport { Subject, takeUntil, finalize } from 'rxjs';\nimport { FeatureFlagsApiService } from '../../services/feature-flags-api.service';\nimport {\n FEATURE_FLAGS_CONFIG,\n FeatureFlagsConfig,\n FeatureFlagsDocument,\n FlagGroup,\n FlagPrimitive,\n PendingChange,\n RESERVED_KEYS,\n} from '../../models';\n\nexport interface ParsedGroup {\n name: string;\n flags: { key: string; value: FlagPrimitive }[];\n}\n\n@Component({\n selector: 'valar-feature-flags-manager',\n templateUrl: './feature-flags-manager.component.html',\n styleUrls: ['./feature-flags-manager.component.css'],\n standalone: false,\n})\nexport class FeatureFlagsManagerComponent implements OnDestroy, OnInit {\n tenantId = '';\n groups: ParsedGroup[] = [];\n loading = false;\n error: string | null = null;\n successMsg: string | null = null;\n loaded = false;\n\n // Add-group form\n showAddGroup = false;\n newGroupName = '';\n\n // Add-flag form (per group)\n addingFlagToGroup: string | null = null;\n newFlagKey = '';\n newFlagValue = '';\n newFlagType: 'string' | 'boolean' | 'number' = 'string';\n\n // ── Batch-edit state ──────────────────────────────────────────────\n /**\n * Pending changes keyed by \"group.key\".\n * Stores old/new values so we can display a diff summary.\n */\n pendingChanges = new Map<string, PendingChange>();\n showSummaryModal = false;\n private readonly newlyCreatedGroups = new Set<string>();\n\n private readonly destroy$ = new Subject<void>();\n\n constructor(\n private readonly api: FeatureFlagsApiService,\n @Inject(FEATURE_FLAGS_CONFIG) config: FeatureFlagsConfig,\n ) {\n this.tenantId = config.tenantId;\n }\n\n ngOnInit(): void {\n this.loadFlags();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ── Computed helpers ──────────────────────────────────────────────\n\n get hasPendingChanges(): boolean {\n return this.pendingChanges.size > 0;\n }\n\n get pendingChangeCount(): number {\n return this.pendingChanges.size;\n }\n\n get pendingChangesList(): PendingChange[] {\n return Array.from(this.pendingChanges.values());\n }\n\n /** Returns the set of dirty keys for a given group */\n dirtyKeysForGroup(groupName: string): Set<string> {\n const keys = new Set<string>();\n for (const [compositeKey] of this.pendingChanges) {\n const [g, k] = this.splitCompositeKey(compositeKey);\n if (g === groupName) keys.add(k);\n }\n return keys;\n }\n\n // ── Load ──────────────────────────────────────────────────────────\n\n loadFlags(): void {\n this.clearMessages();\n this.pendingChanges.clear();\n this.loading = true;\n this.api.getByTenantId()\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.reconcileNewGroupBadges();\n this.loaded = true;\n if (!doc || this.groups.length === 0) {\n this.successMsg = 'No flags found for this tenant yet. Start adding some!';\n }\n },\n error: (err) => {\n this.error = err.error?.message ?? 'Failed to load flags.';\n },\n });\n }\n\n // ── Stage / unstage edits ─────────────────────────────────────────\n\n /** Called when a flag value is changed inline */\n onFlagChanged(groupName: string, event: { key: string; oldValue: FlagPrimitive; newValue: FlagPrimitive }): void {\n const compositeKey = this.makeCompositeKey(groupName, event.key);\n\n // If there's already a pending change, preserve the ORIGINAL old value\n const existing = this.pendingChanges.get(compositeKey);\n const originalOld = existing ? existing.oldValue : event.oldValue;\n\n // If user reverted back to original value, unstage it\n if (this.valuesEqual(originalOld, event.newValue)) {\n this.pendingChanges.delete(compositeKey);\n } else {\n this.pendingChanges.set(compositeKey, {\n group: groupName,\n key: event.key,\n oldValue: originalOld,\n newValue: event.newValue,\n isNew: existing?.isNew ?? false,\n });\n }\n }\n\n /** Revert a single flag's pending change */\n revertFlag(groupName: string, key: string): void {\n const compositeKey = this.makeCompositeKey(groupName, key);\n this.pendingChanges.delete(compositeKey);\n }\n\n /** Discard all pending changes */\n discardAll(): void {\n this.pendingChanges.clear();\n }\n\n // ── Submit (batch upsert) ─────────────────────────────────────────\n\n openSummaryModal(): void {\n this.showSummaryModal = true;\n }\n\n closeSummaryModal(): void {\n this.showSummaryModal = false;\n }\n\n /** Confirm and fire the API call with all pending changes */\n submitAll(): void {\n this.showSummaryModal = false;\n this.clearMessages();\n this.loading = true;\n\n // Build the upsert payload: { group1: { k1: v1 }, group2: { k2: v2 } }\n const payload: Record<string, any> = {};\n for (const change of this.pendingChanges.values()) {\n if (!payload[change.group]) {\n payload[change.group] = {};\n }\n payload[change.group][change.key] = change.newValue;\n }\n\n this.api.upsert(payload as any)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n const count = this.pendingChanges.size;\n this.pendingChanges.clear();\n this.parseDocument(doc);\n this.reconcileNewGroupBadges();\n this.successMsg = `${count} flag${count !== 1 ? 's' : ''} updated successfully!`;\n },\n error: (err) => {\n this.error = err.error?.message ?? 'Failed to save changes.';\n },\n });\n }\n\n // ── Add group / add flag (staged as pending) ─────────────────────\n\n addGroup(): void {\n const name = this.newGroupName.trim();\n if (!name) return;\n this.clearMessages();\n\n if (this.groups.some(g => g.name === name)) {\n this.showAddGroup = false;\n this.addingFlagToGroup = name;\n this.newGroupName = '';\n return;\n }\n\n this.loading = true;\n this.api.upsert({ [name]: {} } as any)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.ensureGroupVisible(name);\n this.newlyCreatedGroups.add(name);\n this.loaded = true;\n this.successMsg = `Group \"${name}\" created.`;\n this.showAddGroup = false;\n this.addingFlagToGroup = name;\n this.newGroupName = '';\n },\n error: (err) => {\n this.error = err.error?.message ?? 'Failed to create group.';\n },\n });\n }\n\n /** Stage a new flag as a pending change */\n confirmAddFlag(groupName: string): void {\n const key = this.newFlagKey.trim();\n if (!key) return;\n const value = this.coerceValue(this.newFlagValue, this.newFlagType);\n\n const compositeKey = this.makeCompositeKey(groupName, key);\n this.pendingChanges.set(compositeKey, {\n group: groupName,\n key,\n oldValue: null,\n newValue: value,\n isNew: true,\n });\n\n this.addingFlagToGroup = null;\n this.newFlagKey = '';\n this.newFlagValue = '';\n this.newFlagType = 'string';\n }\n\n cancelAddFlag(): void {\n this.addingFlagToGroup = null;\n this.newFlagKey = '';\n this.newFlagValue = '';\n }\n\n // ── Deletes (immediate, not batched) ──────────────────────────────\n\n deleteFlag(groupName: string, flagKey: string): void {\n this.clearMessages();\n this.loading = true;\n // Also clear any pending change for this flag\n this.pendingChanges.delete(this.makeCompositeKey(groupName, flagKey));\n\n this.api.deleteFlag(groupName, flagKey)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.reconcileNewGroupBadges();\n this.successMsg = `Flag \"${groupName}.${flagKey}\" deleted.`;\n },\n error: (err) => this.error = err.error?.message ?? 'Failed to delete flag.',\n });\n }\n\n deleteGroup(groupName: string): void {\n this.clearMessages();\n this.loading = true;\n // Clear any pending changes for this group\n for (const [key] of this.pendingChanges) {\n if (key.startsWith(groupName + '::')) {\n this.pendingChanges.delete(key);\n }\n }\n\n this.api.deleteGroup(groupName)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.newlyCreatedGroups.delete(groupName);\n this.reconcileNewGroupBadges();\n this.successMsg = `Group \"${groupName}\" deleted.`;\n },\n error: (err) => this.error = err.error?.message ?? 'Failed to delete group.',\n });\n }\n\n // ── Helpers ───────────────────────────────────────────────────────\n\n trackGroup(_index: number, group: ParsedGroup): string {\n return group.name;\n }\n\n isNewGroup(group: ParsedGroup): boolean {\n if (!this.newlyCreatedGroups.has(group.name)) return false;\n const hasStagedFirstFlag = this.dirtyKeysForGroup(group.name).size > 0;\n return group.flags.length === 0 && !hasStagedFirstFlag;\n }\n\n private parseDocument(doc: FeatureFlagsDocument | null): void {\n this.groups = [];\n if (!doc) return;\n for (const [key, value] of Object.entries(doc)) {\n if (RESERVED_KEYS.has(key)) continue;\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const flags = Object.entries(value as FlagGroup).map(([k, v]) => ({ key: k, value: v }));\n this.groups.push({ name: key, flags });\n }\n }\n this.groups.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n private coerceValue(raw: string, type: 'string' | 'boolean' | 'number'): FlagPrimitive {\n const trimmed = raw.trim();\n if (trimmed === '' || trimmed.toLowerCase() === 'null') return null;\n switch (type) {\n case 'boolean':\n return trimmed.toLowerCase() === 'true';\n case 'number': {\n const n = Number(trimmed);\n return isNaN(n) ? trimmed : n;\n }\n default:\n return trimmed;\n }\n }\n\n private clearMessages(): void {\n this.error = null;\n this.successMsg = null;\n }\n\n private makeCompositeKey(group: string, key: string): string {\n return `${group}::${key}`;\n }\n\n private splitCompositeKey(composite: string): [string, string] {\n const idx = composite.indexOf('::');\n return [composite.substring(0, idx), composite.substring(idx + 2)];\n }\n\n private valuesEqual(a: FlagPrimitive, b: FlagPrimitive): boolean {\n return a === b;\n }\n\n private ensureGroupVisible(groupName: string): void {\n if (this.groups.some(g => g.name === groupName)) return;\n this.groups.push({ name: groupName, flags: [] });\n this.groups.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n private reconcileNewGroupBadges(): void {\n const existingGroupNames = new Set(this.groups.map(g => g.name));\n for (const groupName of Array.from(this.newlyCreatedGroups)) {\n const group = this.groups.find(g => g.name === groupName);\n const hasGroup = existingGroupNames.has(groupName);\n const hasFlags = !!group && group.flags.length > 0;\n if (!hasGroup || hasFlags) {\n this.newlyCreatedGroups.delete(groupName);\n }\n }\n }\n}\n","<div class=\"container-fluid py-4 pb-5\">\n <!-- Header -->\n <div class=\"d-flex align-items-center mb-4\">\n <h2 class=\"mb-0\">\n <i class=\"bi bi-toggles2 me-2\" aria-hidden=\"true\"></i>Feature Flags\n </h2>\n </div>\n\n <!-- Alerts -->\n <div *ngIf=\"error\" class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle-fill me-2\" aria-hidden=\"true\"></i>{{ error }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"error = null\"></button>\n </div>\n <div *ngIf=\"successMsg\" class=\"alert alert-success alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-check-circle-fill me-2\" aria-hidden=\"true\"></i>{{ successMsg }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"successMsg = null\"></button>\n </div>\n <!-- Tenant Context -->\n <div class=\"card shadow-sm mb-4\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <label class=\"form-label fw-semibold mb-1\">Tenant ID</label>\n <div><code>{{ tenantId }}</code></div>\n </div>\n <button class=\"btn btn-primary\" [disabled]=\"loading\" (click)=\"loadFlags()\">\n <span *ngIf=\"loading\" class=\"spinner-border spinner-border-sm me-1\"\n role=\"status\" aria-hidden=\"true\"></span>\n {{ loading ? 'Loading...' : 'Reload Flags' }}\n </button>\n </div>\n <div class=\"form-text\">\n Tenant context is provided by client configuration and sent as the <code>x-tenant-id</code> header.\n </div>\n </div>\n </div>\n\n <!-- Content area (only after load) -->\n <div *ngIf=\"loaded\">\n <!-- Toolbar -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <span class=\"text-muted\">\n {{ groups.length }} group{{ groups.length !== 1 ? 's' : '' }} found\n </span>\n <button class=\"btn btn-success btn-sm\" (click)=\"showAddGroup = true\" [disabled]=\"showAddGroup\">\n <i class=\"bi bi-plus-circle me-1\" aria-hidden=\"true\"></i>Add Group\n </button>\n </div>\n\n <!-- Add Group Form -->\n <div *ngIf=\"showAddGroup\" class=\"card border-success mb-3\">\n <div class=\"card-body\">\n <h6 class=\"card-title text-success\">\n <i class=\"bi bi-folder-plus me-1\" aria-hidden=\"true\"></i>New Group\n </h6>\n <div class=\"input-group input-group-sm\">\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Group name (e.g. uiOptions)\"\n [(ngModel)]=\"newGroupName\"\n (keyup.enter)=\"addGroup()\"\n aria-label=\"New group name\" />\n <button class=\"btn btn-success\" (click)=\"addGroup()\" [disabled]=\"loading || !newGroupName.trim()\">\n {{ loading ? 'Creating...' : 'Create Group' }}\n </button>\n <button class=\"btn btn-outline-secondary\" (click)=\"showAddGroup = false; newGroupName = ''\">\n Cancel\n </button>\n </div>\n <div class=\"form-text\">Letters, numbers, _ and - only. Must start with a letter.</div>\n </div>\n </div>\n\n <!-- Flag Groups -->\n <div *ngFor=\"let group of groups; trackBy: trackGroup\" class=\"mb-3\">\n <valar-flag-group-card\n [groupName]=\"group.name\"\n [flags]=\"group.flags\"\n [isNewGroup]=\"isNewGroup(group)\"\n [dirtyKeys]=\"dirtyKeysForGroup(group.name)\"\n [isAddingFlag]=\"addingFlagToGroup === group.name\"\n [newFlagKey]=\"newFlagKey\"\n [newFlagValue]=\"newFlagValue\"\n [newFlagType]=\"newFlagType\"\n (flagChanged)=\"onFlagChanged(group.name, $event)\"\n (revertFlag)=\"revertFlag(group.name, $event)\"\n (addFlagRequest)=\"addingFlagToGroup = group.name\"\n (cancelAddFlag)=\"cancelAddFlag()\"\n (confirmAddFlag)=\"confirmAddFlag(group.name)\"\n (newFlagKeyChange)=\"newFlagKey = $event\"\n (newFlagValueChange)=\"newFlagValue = $event\"\n (newFlagTypeChange)=\"newFlagType = $event\"\n (deleteFlagRequest)=\"deleteFlag(group.name, $event)\"\n (deleteGroupRequest)=\"deleteGroup(group.name)\">\n </valar-flag-group-card>\n </div>\n\n <!-- Empty state -->\n <div *ngIf=\"groups.length === 0 && !showAddGroup\" class=\"text-center text-muted py-5\">\n <i class=\"bi bi-inbox display-1\" aria-hidden=\"true\"></i>\n <p class=\"mt-3\">No feature flag groups yet. Click <strong>Add Group</strong> to get started.</p>\n </div>\n </div>\n</div>\n\n<!-- Sticky bottom action bar (visible when there are pending changes) -->\n<div *ngIf=\"hasPendingChanges\" class=\"action-bar\">\n <div class=\"container-fluid d-flex justify-content-between align-items-center\">\n <span class=\"text-white\">\n <i class=\"bi bi-pencil-square me-1\" aria-hidden=\"true\"></i>\n <strong>{{ pendingChangeCount }}</strong>\n unsaved change{{ pendingChangeCount !== 1 ? 's' : '' }}\n </span>\n <div>\n <button class=\"btn btn-outline-light btn-sm me-2\" (click)=\"discardAll()\">\n <i class=\"bi bi-x-lg me-1\" aria-hidden=\"true\"></i>Discard All\n </button>\n <button class=\"btn btn-warning btn-sm fw-semibold\" (click)=\"openSummaryModal()\">\n <i class=\"bi bi-send me-1\" aria-hidden=\"true\"></i>Review & Submit\n </button>\n </div>\n </div>\n</div>\n\n<!-- Changes Summary Modal -->\n<valar-changes-summary-modal\n *ngIf=\"showSummaryModal\"\n [changes]=\"pendingChangesList\"\n [tenantId]=\"tenantId\"\n (confirm)=\"submitAll()\"\n (dismiss)=\"closeSummaryModal()\">\n</valar-changes-summary-modal>\n\n","import { Injectable } from '@angular/core';\nimport { Observable, of, delay, throwError } from 'rxjs';\nimport {\n FeatureFlagsDocument,\n FlagGroup,\n RESERVED_KEYS,\n UpsertFlagsPayload,\n} from '../models';\n\n/**\n * In-memory mock of FeatureFlagsApiService.\n * Ships with 2 tenants pre-loaded so you can test the UI\n * without spinning up the backend.\n *\n * Use via `FeatureFlagsModule.forTesting()`.\n */\n@Injectable()\nexport class MockFeatureFlagsApiService {\n private readonly store = new Map<string, FeatureFlagsDocument>();\n private readonly activeTenantId = 'tenant_acme';\n\n constructor() {\n this.seedData();\n }\n\n // ────────────────────────── Public API (same shape as real service) ──\n\n getByTenantId(): Observable<FeatureFlagsDocument | null> {\n const doc = this.store.get(this.activeTenantId) ?? null;\n return of(this.clone(doc)).pipe(delay(250));\n }\n\n upsert(payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument> {\n const { tenantId: _tenantId, ...groups } = payload as UpsertFlagsPayload & { tenantId?: string };\n let doc = this.store.get(this.activeTenantId);\n if (!doc) {\n doc = this.newDoc(this.activeTenantId);\n this.store.set(this.activeTenantId, doc);\n }\n\n // Merge groups via dot-path (mirrors the backend's $set behavior)\n for (const [groupName, groupFlags] of Object.entries(groups)) {\n if (RESERVED_KEYS.has(groupName)) continue;\n const existing = (doc[groupName] as FlagGroup) ?? {};\n doc[groupName] = { ...existing, ...(groupFlags as FlagGroup) };\n }\n doc.updatedAt = new Date().toISOString();\n\n return of(this.clone(doc)!).pipe(delay(200));\n }\n\n deleteFlag(group: string, key: string): Observable<FeatureFlagsDocument> {\n const doc = this.store.get(this.activeTenantId);\n if (!doc) {\n return throwError(() => ({\n error: { message: `No feature flags found for tenantId='${this.activeTenantId}'` },\n }));\n }\n\n const g = doc[group] as FlagGroup | undefined;\n if (g && key in g) {\n delete g[key];\n }\n doc.updatedAt = new Date().toISOString();\n\n return of(this.clone(doc)!).pipe(delay(200));\n }\n\n deleteGroup(group: string): Observable<FeatureFlagsDocument> {\n const doc = this.store.get(this.activeTenantId);\n if (!doc) {\n return throwError(() => ({\n error: { message: `No feature flags found for tenantId='${this.activeTenantId}'` },\n }));\n }\n\n delete doc[group];\n doc.updatedAt = new Date().toISOString();\n\n return of(this.clone(doc)!).pipe(delay(200));\n }\n\n // ────────────────────────── Seed data ────────────────────────────────\n\n private seedData(): void {\n this.store.set('tenant_acme', this.buildAcmeTenant());\n this.store.set('tenant_globex', this.buildGlobexTenant());\n }\n\n private buildAcmeTenant(): FeatureFlagsDocument {\n return {\n _id: '6650a1b2c3d4e5f6a7b8c9d0',\n tenantId: 'tenant_acme',\n createdAt: '2025-11-01T08:00:00.000Z',\n updatedAt: '2026-02-15T14:30:00.000Z',\n uiOptions: {\n darkMode: true,\n sidebarCollapsed: false,\n maxItemsPerPage: 25,\n accentColor: '#4f46e5',\n showBetaBanner: true,\n },\n checkout: {\n newCheckoutFlow: true,\n enableGiftCards: false,\n maxCartItems: 50,\n taxCalculation: 'inclusive',\n },\n notifications: {\n emailEnabled: true,\n smsEnabled: false,\n pushEnabled: true,\n dailyDigest: true,\n marketingOptIn: false,\n },\n search: {\n enableFuzzySearch: true,\n maxResults: 100,\n highlightMatches: true,\n searchDelay: 300,\n },\n } as unknown as FeatureFlagsDocument;\n }\n\n private buildGlobexTenant(): FeatureFlagsDocument {\n return {\n _id: '6650b2c3d4e5f6a7b8c9d0e1',\n tenantId: 'tenant_globex',\n createdAt: '2025-12-10T10:00:00.000Z',\n updatedAt: '2026-02-16T09:15:00.000Z',\n uiOptions: {\n darkMode: false,\n sidebarCollapsed: true,\n maxItemsPerPage: 10,\n accentColor: '#059669',\n showBetaBanner: false,\n },\n billing: {\n enableAutoPay: true,\n gracePeriodDays: 7,\n currency: 'USD',\n showInvoiceBreakdown: true,\n lateFeePercent: 1.5,\n },\n featureAccess: {\n analyticsV2: true,\n exportToCsv: true,\n bulkOperations: false,\n advancedReporting: true,\n apiRateLimit: 1000,\n },\n integrations: {\n slackEnabled: true,\n slackWebhookConfigured: true,\n jiraEnabled: false,\n githubEnabled: true,\n webhookRetries: 3,\n },\n } as unknown as FeatureFlagsDocument;\n }\n\n // ────────────────────────── Helpers ──────────────────────────────────\n\n private newDoc(tenantId: string): FeatureFlagsDocument {\n const now = new Date().toISOString();\n return { tenantId, createdAt: now, updatedAt: now };\n }\n\n /** Deep-clone to avoid leaking mutable references */\n private clone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n }\n}\n","import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { provideHttpClient } from '@angular/common/http';\nimport { FeatureFlagsManagerComponent } from './components/feature-flags-manager/feature-flags-manager.component';\nimport { FlagGroupCardComponent } from './components/flag-group-card/flag-group-card.component';\nimport { ChangesSummaryModalComponent } from './components/changes-summary-modal/changes-summary-modal.component';\nimport { FEATURE_FLAGS_CONFIG, FeatureFlagsConfig } from './models';\nimport { FeatureFlagsApiService } from './services/feature-flags-api.service';\nimport { MockFeatureFlagsApiService } from './services/mock-feature-flags-api.service';\n\n@NgModule({\n declarations: [\n FeatureFlagsManagerComponent,\n FlagGroupCardComponent,\n ChangesSummaryModalComponent,\n ],\n imports: [\n CommonModule,\n FormsModule,\n ],\n exports: [\n FeatureFlagsManagerComponent,\n FlagGroupCardComponent,\n ChangesSummaryModalComponent,\n ],\n})\nexport class FeatureFlagsModule {\n /**\n * Call forRoot() in the consuming app to provide\n * the API configuration.\n *\n * @example\n * FeatureFlagsModule.forRoot({\n * apiBaseUrl: 'http://localhost:3000',\n * apiVersion: 'v1',\n * tenantId: 'abc123', // used in /feature-flags/tenant_abc123\n * rootTenantId: 'root001', // sent in x-tenant-id header\n * })\n */\n static forRoot(config: FeatureFlagsConfig): ModuleWithProviders<FeatureFlagsModule> {\n return {\n ngModule: FeatureFlagsModule,\n providers: [\n { provide: FEATURE_FLAGS_CONFIG, useValue: config },\n FeatureFlagsApiService,\n provideHttpClient(),\n ],\n };\n }\n\n /**\n * Call forTesting() in the consuming app to use\n * in-memory mock data (no backend needed).\n *\n * Ships with 2 pre-loaded tenants:\n * - \"tenant_acme\" (Acme Corp)\n * - \"tenant_globex\" (Globex Inc)\n *\n * @example\n * FeatureFlagsModule.forTesting()\n */\n static forTesting(): ModuleWithProviders<FeatureFlagsModule> {\n return {\n ngModule: FeatureFlagsModule,\n providers: [\n { provide: FeatureFlagsApiService, useClass: MockFeatureFlagsApiService },\n ],\n };\n }\n}\n","/*\n * Public API Surface of valar-ui\n */\nimport '@angular/localize/init';\n\n// Feature Flags\nexport * from './lib/feature-flags/feature-flags.module';\nexport * from './lib/feature-flags/models';\nexport * from './lib/feature-flags/services/feature-flags-api.service';\nexport * from './lib/feature-flags/services/mock-feature-flags-api.service';\nexport * from './lib/feature-flags/components/feature-flags-manager/feature-flags-manager.component';\nexport * from './lib/feature-flags/components/flag-group-card/flag-group-card.component';\nexport * from './lib/feature-flags/components/changes-summary-modal/changes-summary-modal.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1","i2","i3","i4.FlagGroupCardComponent","i5.ChangesSummaryModalComponent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pl4yzonellc-valar-ui.mjs","sources":["../../../projects/valar-ui/src/lib/feature-flags/models/feature-flags.model.ts","../../../projects/valar-ui/src/lib/feature-flags/services/feature-flags-api.service.ts","../../../projects/valar-ui/src/lib/feature-flags/components/flag-group-card/flag-group-card.component.ts","../../../projects/valar-ui/src/lib/feature-flags/components/flag-group-card/flag-group-card.component.html","../../../projects/valar-ui/src/lib/feature-flags/components/changes-summary-modal/changes-summary-modal.component.ts","../../../projects/valar-ui/src/lib/feature-flags/components/changes-summary-modal/changes-summary-modal.component.html","../../../projects/valar-ui/src/lib/feature-flags/components/feature-flags-manager/feature-flags-manager.component.ts","../../../projects/valar-ui/src/lib/feature-flags/components/feature-flags-manager/feature-flags-manager.component.html","../../../projects/valar-ui/src/lib/feature-flags/services/mock-feature-flags-api.service.ts","../../../projects/valar-ui/src/lib/feature-flags/feature-flags.module.ts","../../../projects/valar-ui/src/public-api.ts","../../../projects/valar-ui/src/pl4yzonellc-valar-ui.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\n/**\n * Primitive types allowed as feature flag values.\n * Mirrors the backend's FlagPrimitive type.\n */\nexport type FlagPrimitive = boolean | string | number | null;\n\n/**\n * A group of feature flags: key → primitive value.\n */\nexport type FlagGroup = Record<string, FlagPrimitive>;\n\n/**\n * The full feature flags document for a tenant.\n * Dynamic group names sit at the top level alongside metadata.\n */\nexport interface FeatureFlagsDocument {\n _id?: string;\n tenantId: string;\n createdAt?: string;\n updatedAt?: string;\n [groupName: string]: FlagGroup | string | undefined;\n}\n\n/**\n * Shape of the upsert request body sent to the backend.\n * Service will inject tenantId + any number of group objects.\n */\nexport interface UpsertFlagsPayload {\n [groupName: string]: FlagGroup | string;\n}\n\n/**\n * Configuration for the FeatureFlags library module.\n * The consuming app provides this via forRoot().\n */\nexport interface FeatureFlagsConfig {\n /** Base URL of the Valar API, e.g. 'http://localhost:3000' */\n apiBaseUrl: string;\n /** API version prefix, defaults to 'v1' */\n apiVersion?: string;\n /** Root tenant id sent in request headers */\n rootTenantId: string;\n /** Tenant header name, defaults to 'x-tenant-id' */\n tenantHeaderName?: string;\n}\n\n/** Injection token for the library config */\nexport const FEATURE_FLAGS_CONFIG = new InjectionToken<FeatureFlagsConfig>(\n 'FEATURE_FLAGS_CONFIG'\n);\n\n/** Reserved keys that are NOT flag groups */\nexport const RESERVED_KEYS = new Set([\n '_id', 'tenantId', 'createdAt', 'updatedAt', '__v', 'id',\n]);\n\n/** A single staged change for the batch-edit workflow */\nexport interface PendingChange {\n group: string;\n key: string;\n oldValue: FlagPrimitive;\n newValue: FlagPrimitive;\n isNew?: boolean; // true if this is a brand-new flag\n}\n","import { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Inject, Injectable } from '@angular/core';\nimport { Observable, map } from 'rxjs';\nimport {\n FEATURE_FLAGS_CONFIG,\n FeatureFlagsConfig,\n FeatureFlagsDocument,\n UpsertFlagsPayload,\n} from '../models';\n\n/**\n * HTTP service that talks to the Valar feature-flags backend.\n * Base URL is injected via FEATURE_FLAGS_CONFIG so the consuming\n * app controls where requests go.\n */\n@Injectable()\nexport class FeatureFlagsApiService {\n private readonly baseUrl: string;\n private readonly rootTenantId: string;\n private readonly tenantHeaderName: string;\n\n constructor(\n private readonly http: HttpClient,\n @Inject(FEATURE_FLAGS_CONFIG) config: FeatureFlagsConfig,\n ) {\n const version = config.apiVersion ?? 'v1';\n this.baseUrl = `${config.apiBaseUrl}/${version}/feature-flags`;\n this.rootTenantId = config.rootTenantId;\n this.tenantHeaderName = config.tenantHeaderName ?? 'x-tenant-id';\n }\n\n /** GET /v1/feature-flags/tenant_<tenantId> */\n getByTenantId(tenantId: string): Observable<FeatureFlagsDocument | null> {\n return this.http.get<FeatureFlagsDocument | null>(this.tenantScopedBaseUrl(tenantId), this.requestOptions())\n .pipe(map(doc => doc ?? null));\n }\n\n /** PUT /v1/feature-flags (merge-upsert) */\n upsert(tenantId: string, payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument> {\n const requestPayload: UpsertFlagsPayload = {\n ...payload,\n tenantId: this.toTenantToken(tenantId),\n };\n return this.http.put<FeatureFlagsDocument>(this.baseUrl, requestPayload, this.requestOptions());\n }\n\n /** DELETE /v1/feature-flags/tenant_<tenantId>/:group/:key */\n deleteFlag(tenantId: string, group: string, key: string): Observable<FeatureFlagsDocument> {\n const url = [\n this.tenantScopedBaseUrl(tenantId),\n encodeURIComponent(group),\n encodeURIComponent(key),\n ].join('/');\n return this.http.delete<FeatureFlagsDocument>(url, this.requestOptions());\n }\n\n /** DELETE /v1/feature-flags/tenant_<tenantId>/:group */\n deleteGroup(tenantId: string, group: string): Observable<FeatureFlagsDocument> {\n const url = [\n this.tenantScopedBaseUrl(tenantId),\n encodeURIComponent(group),\n ].join('/');\n return this.http.delete<FeatureFlagsDocument>(url, this.requestOptions());\n }\n\n private tenantScopedBaseUrl(tenantId: string): string {\n return `${this.baseUrl}/${encodeURIComponent(this.toTenantToken(tenantId))}`;\n }\n\n private toTenantToken(tenantId: string): string {\n const normalized = tenantId.trim();\n if (normalized.startsWith('tenant_')) return normalized;\n return `tenant_${normalized}`;\n }\n\n private requestOptions(): { headers: HttpHeaders } {\n return {\n headers: new HttpHeaders({\n [this.tenantHeaderName]: this.rootTenantId,\n }),\n };\n }\n}\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { FlagPrimitive } from '../../models';\n\nexport interface FlagEntry {\n key: string;\n value: FlagPrimitive;\n}\n\nexport interface FlagChangedEvent {\n key: string;\n oldValue: FlagPrimitive;\n newValue: FlagPrimitive;\n}\n\n@Component({\n selector: 'valar-flag-group-card',\n templateUrl: './flag-group-card.component.html',\n styleUrls: ['./flag-group-card.component.css'],\n standalone: false,\n})\nexport class FlagGroupCardComponent {\n @Input() groupName = '';\n @Input() flags: FlagEntry[] = [];\n @Input() isNewGroup = false;\n @Input() dirtyKeys: Set<string> = new Set();\n @Input() isAddingFlag = false;\n @Input() newFlagKey = '';\n @Input() newFlagValue = '';\n @Input() newFlagType: 'string' | 'boolean' | 'number' = 'string';\n\n @Output() flagChanged = new EventEmitter<FlagChangedEvent>();\n @Output() revertFlag = new EventEmitter<string>();\n @Output() addFlagRequest = new EventEmitter<void>();\n @Output() cancelAddFlag = new EventEmitter<void>();\n @Output() confirmAddFlag = new EventEmitter<void>();\n @Output() newFlagKeyChange = new EventEmitter<string>();\n @Output() newFlagValueChange = new EventEmitter<string>();\n @Output() newFlagTypeChange = new EventEmitter<'string' | 'boolean' | 'number'>();\n @Output() deleteFlagRequest = new EventEmitter<string>();\n @Output() deleteGroupRequest = new EventEmitter<void>();\n\n collapsed = false;\n confirmDeleteGroup = false;\n confirmDeleteKey: string | null = null;\n\n /** Which flag key is currently in inline-edit mode */\n editingKey: string | null = null;\n editValue = '';\n editType: 'string' | 'boolean' | 'number' = 'string';\n\n isDirty(key: string): boolean {\n return this.dirtyKeys.has(key);\n }\n\n /** Enter inline-edit mode for a flag */\n startEdit(flag: FlagEntry): void {\n this.editingKey = flag.key;\n this.editType = this.detectType(flag.value);\n this.editValue = flag.value === null ? '' : String(flag.value);\n }\n\n /** Commit the inline edit — emits change to parent, stays in view mode */\n commitEdit(flag: FlagEntry): void {\n const newValue = this.coerceValue(this.editValue, this.editType);\n this.flagChanged.emit({ key: flag.key, oldValue: flag.value, newValue });\n this.editingKey = null;\n }\n\n /** Cancel inline edit without staging anything */\n cancelEdit(): void {\n this.editingKey = null;\n }\n\n displayValue(value: FlagPrimitive): string {\n if (value === null) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n return String(value);\n }\n\n typeBadgeClass(value: FlagPrimitive): string {\n if (typeof value === 'boolean') return 'bg-info text-dark';\n if (typeof value === 'number') return 'bg-warning text-dark';\n if (value === null) return 'bg-secondary';\n return 'bg-primary';\n }\n\n typeLabel(value: FlagPrimitive): string {\n if (value === null) return 'null';\n return typeof value;\n }\n\n isBooleanFlag(value: FlagPrimitive): boolean {\n return typeof value === 'boolean';\n }\n\n trackFlag(_index: number, flag: FlagEntry): string {\n return flag.key;\n }\n\n onDeleteFlag(key: string): void {\n if (this.confirmDeleteKey === key) {\n this.deleteFlagRequest.emit(key);\n this.confirmDeleteKey = null;\n } else {\n this.confirmDeleteKey = key;\n }\n }\n\n onDeleteGroup(): void {\n if (this.confirmDeleteGroup) {\n this.deleteGroupRequest.emit();\n this.confirmDeleteGroup = false;\n } else {\n this.confirmDeleteGroup = true;\n }\n }\n\n private detectType(value: FlagPrimitive): 'string' | 'boolean' | 'number' {\n if (typeof value === 'boolean') return 'boolean';\n if (typeof value === 'number') return 'number';\n return 'string';\n }\n\n private coerceValue(raw: string, type: 'string' | 'boolean' | 'number'): FlagPrimitive {\n const trimmed = raw.trim();\n if (trimmed === '' || trimmed.toLowerCase() === 'null') return null;\n switch (type) {\n case 'boolean':\n return trimmed.toLowerCase() === 'true';\n case 'number': {\n const n = Number(trimmed);\n return isNaN(n) ? trimmed : n;\n }\n default:\n return trimmed;\n }\n }\n}\n","<div class=\"card shadow-sm\">\n <!-- Card Header -->\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <div class=\"d-flex align-items-center\">\n <button\n class=\"btn btn-sm btn-link text-decoration-none p-0 me-2\"\n (click)=\"collapsed = !collapsed\"\n [attr.aria-expanded]=\"!collapsed\"\n [attr.aria-controls]=\"'group-' + groupName\"\n aria-label=\"Toggle group\">\n <i class=\"bi\" [ngClass]=\"collapsed ? 'bi-chevron-right' : 'bi-chevron-down'\" aria-hidden=\"true\"></i>\n </button>\n <h6 class=\"mb-0 fw-bold\">\n <i class=\"bi bi-folder2 me-1 text-primary\" aria-hidden=\"true\"></i>\n {{ groupName }}\n </h6>\n <span *ngIf=\"isNewGroup\" class=\"badge bg-success ms-2\">NEW</span>\n <span class=\"badge bg-secondary ms-2\">{{ flags.length }}</span>\n <span *ngIf=\"dirtyKeys.size > 0\" class=\"badge bg-warning text-dark ms-1\" title=\"Unsaved changes\">\n {{ dirtyKeys.size }} edited\n </span>\n </div>\n <div class=\"btn-group btn-group-sm\">\n <button\n class=\"btn btn-outline-primary btn-sm\"\n (click)=\"addFlagRequest.emit()\"\n title=\"Add flag to this group\"\n aria-label=\"Add flag\">\n <i class=\"bi bi-plus\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"btn btn-sm\"\n [ngClass]=\"confirmDeleteGroup ? 'btn-danger' : 'btn-outline-danger'\"\n (click)=\"onDeleteGroup()\"\n [title]=\"confirmDeleteGroup ? 'Click again to confirm' : 'Delete entire group'\"\n [attr.aria-label]=\"confirmDeleteGroup ? 'Confirm delete group' : 'Delete group'\">\n <i class=\"bi\" [ngClass]=\"confirmDeleteGroup ? 'bi-exclamation-triangle' : 'bi-trash'\" aria-hidden=\"true\"></i>\n <span *ngIf=\"confirmDeleteGroup\" class=\"ms-1\">Confirm?</span>\n </button>\n </div>\n </div>\n\n <!-- Card Body -->\n <div [id]=\"'group-' + groupName\" *ngIf=\"!collapsed\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover table-sm mb-0\" [attr.aria-label]=\"'Flags in ' + groupName\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\" style=\"width: 28%\">Key</th>\n <th scope=\"col\" style=\"width: 12%\">Type</th>\n <th scope=\"col\" style=\"width: 38%\">Value</th>\n <th scope=\"col\" style=\"width: 22%\" class=\"text-end\">Actions</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let flag of flags; trackBy: trackFlag\"\n [class.row-dirty]=\"isDirty(flag.key)\">\n <!-- KEY -->\n <td class=\"align-middle\">\n <code>{{ flag.key }}</code>\n <i *ngIf=\"isDirty(flag.key)\" class=\"bi bi-pencil-fill text-warning ms-1 small\"\n title=\"Edited\" aria-hidden=\"true\"></i>\n </td>\n\n <!-- TYPE -->\n <td class=\"align-middle\">\n <span class=\"badge\" [ngClass]=\"typeBadgeClass(flag.value)\">\n {{ typeLabel(flag.value) }}\n </span>\n </td>\n\n <!-- VALUE (view or inline-edit) -->\n <td class=\"align-middle\">\n <ng-container *ngIf=\"editingKey !== flag.key; else editMode\">\n <!-- Boolean as ON/OFF badge -->\n <span *ngIf=\"isBooleanFlag(flag.value); else plainValue\">\n <span class=\"badge\" [ngClass]=\"flag.value ? 'bg-success' : 'bg-danger'\">\n {{ flag.value ? 'ON' : 'OFF' }}\n </span>\n </span>\n <ng-template #plainValue>\n <span class=\"text-break\">{{ displayValue(flag.value) }}</span>\n </ng-template>\n </ng-container>\n\n <!-- Inline edit mode -->\n <ng-template #editMode>\n <!-- Boolean: simple toggle, no type dropdown -->\n <ng-container *ngIf=\"editType === 'boolean'; else nonBooleanEdit\">\n <select\n class=\"form-select form-select-sm\"\n style=\"max-width: 120px\"\n [(ngModel)]=\"editValue\"\n aria-label=\"Flag value\">\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n </ng-container>\n <!-- Non-boolean: type dropdown + value input -->\n <ng-template #nonBooleanEdit>\n <div class=\"input-group input-group-sm\">\n <select\n class=\"form-select\"\n style=\"max-width: 100px\"\n [(ngModel)]=\"editType\"\n aria-label=\"Flag type\">\n <option value=\"string\">string</option>\n <option value=\"number\">number</option>\n </select>\n <input\n type=\"text\"\n class=\"form-control\"\n [(ngModel)]=\"editValue\"\n (keyup.enter)=\"commitEdit(flag)\"\n aria-label=\"Flag value\" />\n </div>\n </ng-template>\n </ng-template>\n </td>\n\n <!-- ACTIONS -->\n <td class=\"align-middle text-end\">\n <ng-container *ngIf=\"editingKey !== flag.key; else editActions\">\n <button\n class=\"btn btn-sm btn-outline-secondary me-1\"\n (click)=\"startEdit(flag)\"\n title=\"Edit flag\"\n aria-label=\"Edit flag\">\n <i class=\"bi bi-pencil\" aria-hidden=\"true\"></i>\n </button>\n <button *ngIf=\"isDirty(flag.key)\"\n class=\"btn btn-sm btn-outline-warning me-1\"\n (click)=\"revertFlag.emit(flag.key)\"\n title=\"Revert change\"\n aria-label=\"Revert change\">\n <i class=\"bi bi-arrow-counterclockwise\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"btn btn-sm\"\n [ngClass]=\"confirmDeleteKey === flag.key ? 'btn-danger' : 'btn-outline-danger'\"\n (click)=\"onDeleteFlag(flag.key)\"\n [title]=\"confirmDeleteKey === flag.key ? 'Click again to confirm' : 'Delete flag'\"\n [attr.aria-label]=\"confirmDeleteKey === flag.key ? 'Confirm delete' : 'Delete flag'\">\n <i class=\"bi\" [ngClass]=\"confirmDeleteKey === flag.key ? 'bi-exclamation-triangle' : 'bi-trash'\" aria-hidden=\"true\"></i>\n </button>\n </ng-container>\n <ng-template #editActions>\n <button\n class=\"btn btn-sm btn-success me-1\"\n (click)=\"commitEdit(flag)\"\n title=\"Stage change\"\n aria-label=\"Stage change\">\n <i class=\"bi bi-check-lg\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"cancelEdit()\"\n title=\"Cancel\"\n aria-label=\"Cancel edit\">\n <i class=\"bi bi-x-lg\" aria-hidden=\"true\"></i>\n </button>\n </ng-template>\n </td>\n </tr>\n\n <!-- Empty row -->\n <tr *ngIf=\"flags.length === 0 && !isAddingFlag\">\n <td colspan=\"4\" class=\"text-center text-muted py-3\">\n No flags in this group.\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Add Flag Form -->\n <div *ngIf=\"isAddingFlag\" class=\"card-footer bg-light\">\n <h6 class=\"text-success mb-2\">\n <i class=\"bi bi-plus-circle me-1\" aria-hidden=\"true\"></i>Add Flag to \"{{ groupName }}\"\n </h6>\n <div class=\"row g-2 align-items-end\">\n <div class=\"col-md-3\">\n <label class=\"form-label form-label-sm\">Key</label>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n placeholder=\"flagKey\"\n [ngModel]=\"newFlagKey\"\n (ngModelChange)=\"newFlagKeyChange.emit($event)\"\n (keyup.enter)=\"confirmAddFlag.emit()\"\n aria-label=\"New flag key\" />\n </div>\n <div class=\"col-md-2\">\n <label class=\"form-label form-label-sm\">Type</label>\n <select\n class=\"form-select form-select-sm\"\n [ngModel]=\"newFlagType\"\n (ngModelChange)=\"newFlagTypeChange.emit($event)\"\n aria-label=\"New flag type\">\n <option value=\"string\">string</option>\n <option value=\"boolean\">boolean</option>\n <option value=\"number\">number</option>\n </select>\n </div>\n <div class=\"col-md-3\">\n <label class=\"form-label form-label-sm\">Value</label>\n <ng-container *ngIf=\"newFlagType === 'boolean'; else newTextInput\">\n <select\n class=\"form-select form-select-sm\"\n [ngModel]=\"newFlagValue\"\n (ngModelChange)=\"newFlagValueChange.emit($event)\"\n aria-label=\"New flag value\">\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n </ng-container>\n <ng-template #newTextInput>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n placeholder=\"value\"\n [ngModel]=\"newFlagValue\"\n (ngModelChange)=\"newFlagValueChange.emit($event)\"\n (keyup.enter)=\"confirmAddFlag.emit()\"\n aria-label=\"New flag value\" />\n </ng-template>\n </div>\n <div class=\"col-md-4\">\n <button class=\"btn btn-success btn-sm me-1\" (click)=\"confirmAddFlag.emit()\" [disabled]=\"!newFlagKey.trim()\">\n <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\"></i>Stage\n </button>\n <button class=\"btn btn-outline-secondary btn-sm\" (click)=\"cancelAddFlag.emit()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n </div>\n</div>\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { PendingChange, FlagPrimitive } from '../../models';\n\n@Component({\n selector: 'valar-changes-summary-modal',\n templateUrl: './changes-summary-modal.component.html',\n styleUrls: ['./changes-summary-modal.component.css'],\n standalone: false,\n})\nexport class ChangesSummaryModalComponent {\n @Input() changes: PendingChange[] = [];\n @Input() tenantId = '';\n @Output() confirm = new EventEmitter<void>();\n @Output() dismiss = new EventEmitter<void>();\n\n displayValue(value: FlagPrimitive): string {\n if (value === null) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n return String(value);\n }\n\n typeLabel(value: FlagPrimitive): string {\n if (value === null) return 'null';\n return typeof value;\n }\n}\n","<!-- Backdrop -->\n<div class=\"modal-backdrop fade show\" (click)=\"dismiss.emit()\"></div>\n\n<!-- Modal -->\n<div class=\"modal fade show d-block\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"changesSummaryTitle\" aria-modal=\"true\">\n <div class=\"modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable\">\n <div class=\"modal-content\">\n <!-- Header -->\n <div class=\"modal-header bg-primary text-white\">\n <h5 class=\"modal-title\" id=\"changesSummaryTitle\">\n <i class=\"bi bi-clipboard-check me-2\" aria-hidden=\"true\"></i>\n Review Changes\n </h5>\n <button type=\"button\" class=\"btn-close btn-close-white\" aria-label=\"Close\" (click)=\"dismiss.emit()\"></button>\n </div>\n\n <!-- Body -->\n <div class=\"modal-body\">\n <p class=\"text-muted mb-3\">\n You're about to update <strong>{{ changes.length }}</strong>\n flag{{ changes.length !== 1 ? 's' : '' }} for tenant\n <code>{{ tenantId }}</code>.\n </p>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-bordered mb-0\" aria-label=\"Pending changes summary\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\">Group</th>\n <th scope=\"col\">Key</th>\n <th scope=\"col\">Old Value</th>\n <th scope=\"col\" class=\"text-center\" style=\"width: 40px\">\n <i class=\"bi bi-arrow-right\" aria-hidden=\"true\"></i>\n </th>\n <th scope=\"col\">New Value</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let change of changes\">\n <td>\n <i class=\"bi bi-folder2 me-1 text-primary\" aria-hidden=\"true\"></i>\n {{ change.group }}\n </td>\n <td><code>{{ change.key }}</code></td>\n <td>\n <span class=\"text-danger text-decoration-line-through\">\n {{ displayValue(change.oldValue) }}\n </span>\n <span class=\"badge bg-secondary ms-1\">{{ typeLabel(change.oldValue) }}</span>\n <span *ngIf=\"change.isNew\" class=\"badge bg-success ms-1\">NEW</span>\n </td>\n <td class=\"text-center text-muted\">\n <i class=\"bi bi-arrow-right\" aria-hidden=\"true\"></i>\n </td>\n <td>\n <span class=\"fw-semibold text-success\">\n {{ displayValue(change.newValue) }}\n </span>\n <span class=\"badge bg-secondary ms-1\">{{ typeLabel(change.newValue) }}</span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"dismiss.emit()\">\n <i class=\"bi bi-x-lg me-1\" aria-hidden=\"true\"></i>Cancel\n </button>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"confirm.emit()\">\n <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\"></i>Looks Good\n </button>\n </div>\n </div>\n </div>\n</div>\n","import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges } from '@angular/core';\nimport { Subject, takeUntil, finalize } from 'rxjs';\nimport { FeatureFlagsApiService } from '../../services/feature-flags-api.service';\nimport {\n FeatureFlagsDocument,\n FlagGroup,\n FlagPrimitive,\n PendingChange,\n RESERVED_KEYS,\n} from '../../models';\n\nexport interface ParsedGroup {\n name: string;\n flags: { key: string; value: FlagPrimitive }[];\n}\n\n@Component({\n selector: 'valar-feature-flags-manager',\n templateUrl: './feature-flags-manager.component.html',\n styleUrls: ['./feature-flags-manager.component.css'],\n standalone: false,\n})\nexport class FeatureFlagsManagerComponent implements OnDestroy, OnChanges {\n @Input() tenantId = '';\n @Output() tenantIdChange = new EventEmitter<string>();\n groups: ParsedGroup[] = [];\n loading = false;\n error: string | null = null;\n successMsg: string | null = null;\n loaded = false;\n\n // Add-group form\n showAddGroup = false;\n newGroupName = '';\n\n // Add-flag form (per group)\n addingFlagToGroup: string | null = null;\n newFlagKey = '';\n newFlagValue = '';\n newFlagType: 'string' | 'boolean' | 'number' = 'string';\n\n // ── Batch-edit state ──────────────────────────────────────────────\n /**\n * Pending changes keyed by \"group.key\".\n * Stores old/new values so we can display a diff summary.\n */\n pendingChanges = new Map<string, PendingChange>();\n showSummaryModal = false;\n private readonly newlyCreatedGroups = new Set<string>();\n\n private readonly destroy$ = new Subject<void>();\n\n constructor(private readonly api: FeatureFlagsApiService) {}\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!changes['tenantId']) return;\n const id = this.tenantId.trim();\n if (!id) {\n this.groups = [];\n this.loaded = false;\n this.pendingChanges.clear();\n this.clearMessages();\n return;\n }\n this.tenantIdChange.emit(id);\n this.loadFlags();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ── Computed helpers ──────────────────────────────────────────────\n\n get hasPendingChanges(): boolean {\n return this.pendingChanges.size > 0;\n }\n\n get pendingChangeCount(): number {\n return this.pendingChanges.size;\n }\n\n get pendingChangesList(): PendingChange[] {\n return Array.from(this.pendingChanges.values());\n }\n\n /** Returns the set of dirty keys for a given group */\n dirtyKeysForGroup(groupName: string): Set<string> {\n const keys = new Set<string>();\n for (const [compositeKey] of this.pendingChanges) {\n const [g, k] = this.splitCompositeKey(compositeKey);\n if (g === groupName) keys.add(k);\n }\n return keys;\n }\n\n // ── Load ──────────────────────────────────────────────────────────\n\n loadFlags(): void {\n const id = this.tenantId.trim();\n if (!id) {\n this.error = 'Tenant ID is required.';\n return;\n }\n this.clearMessages();\n this.pendingChanges.clear();\n this.loading = true;\n this.api.getByTenantId(id)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.reconcileNewGroupBadges();\n this.loaded = true;\n if (!doc || this.groups.length === 0) {\n this.successMsg = 'No flags found for this tenant yet. Start adding some!';\n }\n },\n error: (err) => {\n this.error = err.error?.message ?? 'Failed to load flags.';\n },\n });\n }\n\n // ── Stage / unstage edits ─────────────────────────────────────────\n\n /** Called when a flag value is changed inline */\n onFlagChanged(groupName: string, event: { key: string; oldValue: FlagPrimitive; newValue: FlagPrimitive }): void {\n const compositeKey = this.makeCompositeKey(groupName, event.key);\n\n // If there's already a pending change, preserve the ORIGINAL old value\n const existing = this.pendingChanges.get(compositeKey);\n const originalOld = existing ? existing.oldValue : event.oldValue;\n\n // If user reverted back to original value, unstage it\n if (this.valuesEqual(originalOld, event.newValue)) {\n this.pendingChanges.delete(compositeKey);\n } else {\n this.pendingChanges.set(compositeKey, {\n group: groupName,\n key: event.key,\n oldValue: originalOld,\n newValue: event.newValue,\n isNew: existing?.isNew ?? false,\n });\n }\n }\n\n /** Revert a single flag's pending change */\n revertFlag(groupName: string, key: string): void {\n const compositeKey = this.makeCompositeKey(groupName, key);\n this.pendingChanges.delete(compositeKey);\n }\n\n /** Discard all pending changes */\n discardAll(): void {\n this.pendingChanges.clear();\n }\n\n // ── Submit (batch upsert) ─────────────────────────────────────────\n\n openSummaryModal(): void {\n this.showSummaryModal = true;\n }\n\n closeSummaryModal(): void {\n this.showSummaryModal = false;\n }\n\n /** Confirm and fire the API call with all pending changes */\n submitAll(): void {\n this.showSummaryModal = false;\n this.clearMessages();\n this.loading = true;\n\n // Build the upsert payload: { group1: { k1: v1 }, group2: { k2: v2 } }\n const payload: Record<string, any> = {};\n for (const change of this.pendingChanges.values()) {\n if (!payload[change.group]) {\n payload[change.group] = {};\n }\n payload[change.group][change.key] = change.newValue;\n }\n\n this.api.upsert(this.tenantId, payload as any)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n const count = this.pendingChanges.size;\n this.pendingChanges.clear();\n this.parseDocument(doc);\n this.reconcileNewGroupBadges();\n this.successMsg = `${count} flag${count !== 1 ? 's' : ''} updated successfully!`;\n },\n error: (err) => {\n this.error = err.error?.message ?? 'Failed to save changes.';\n },\n });\n }\n\n // ── Add group / add flag (staged as pending) ─────────────────────\n\n addGroup(): void {\n const name = this.newGroupName.trim();\n if (!name) return;\n this.clearMessages();\n\n if (this.groups.some(g => g.name === name)) {\n this.showAddGroup = false;\n this.addingFlagToGroup = name;\n this.newGroupName = '';\n return;\n }\n\n this.loading = true;\n this.api.upsert(this.tenantId, { [name]: {} } as any)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.ensureGroupVisible(name);\n this.newlyCreatedGroups.add(name);\n this.loaded = true;\n this.successMsg = `Group \"${name}\" created.`;\n this.showAddGroup = false;\n this.addingFlagToGroup = name;\n this.newGroupName = '';\n },\n error: (err) => {\n this.error = err.error?.message ?? 'Failed to create group.';\n },\n });\n }\n\n /** Stage a new flag as a pending change */\n confirmAddFlag(groupName: string): void {\n const key = this.newFlagKey.trim();\n if (!key) return;\n const value = this.coerceValue(this.newFlagValue, this.newFlagType);\n\n const compositeKey = this.makeCompositeKey(groupName, key);\n this.pendingChanges.set(compositeKey, {\n group: groupName,\n key,\n oldValue: null,\n newValue: value,\n isNew: true,\n });\n\n this.addingFlagToGroup = null;\n this.newFlagKey = '';\n this.newFlagValue = '';\n this.newFlagType = 'string';\n }\n\n cancelAddFlag(): void {\n this.addingFlagToGroup = null;\n this.newFlagKey = '';\n this.newFlagValue = '';\n }\n\n // ── Deletes (immediate, not batched) ──────────────────────────────\n\n deleteFlag(groupName: string, flagKey: string): void {\n this.clearMessages();\n this.loading = true;\n // Also clear any pending change for this flag\n this.pendingChanges.delete(this.makeCompositeKey(groupName, flagKey));\n\n this.api.deleteFlag(this.tenantId, groupName, flagKey)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.reconcileNewGroupBadges();\n this.successMsg = `Flag \"${groupName}.${flagKey}\" deleted.`;\n },\n error: (err) => this.error = err.error?.message ?? 'Failed to delete flag.',\n });\n }\n\n deleteGroup(groupName: string): void {\n this.clearMessages();\n this.loading = true;\n // Clear any pending changes for this group\n for (const [key] of this.pendingChanges) {\n if (key.startsWith(groupName + '::')) {\n this.pendingChanges.delete(key);\n }\n }\n\n this.api.deleteGroup(this.tenantId, groupName)\n .pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))\n .subscribe({\n next: (doc) => {\n this.parseDocument(doc);\n this.newlyCreatedGroups.delete(groupName);\n this.reconcileNewGroupBadges();\n this.successMsg = `Group \"${groupName}\" deleted.`;\n },\n error: (err) => this.error = err.error?.message ?? 'Failed to delete group.',\n });\n }\n\n // ── Helpers ───────────────────────────────────────────────────────\n\n trackGroup(_index: number, group: ParsedGroup): string {\n return group.name;\n }\n\n isNewGroup(group: ParsedGroup): boolean {\n if (!this.newlyCreatedGroups.has(group.name)) return false;\n const hasStagedFirstFlag = this.dirtyKeysForGroup(group.name).size > 0;\n return group.flags.length === 0 && !hasStagedFirstFlag;\n }\n\n private parseDocument(doc: FeatureFlagsDocument | null): void {\n this.groups = [];\n if (!doc) return;\n for (const [key, value] of Object.entries(doc)) {\n if (RESERVED_KEYS.has(key)) continue;\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const flags = Object.entries(value as FlagGroup).map(([k, v]) => ({ key: k, value: v }));\n this.groups.push({ name: key, flags });\n }\n }\n this.groups.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n private coerceValue(raw: string, type: 'string' | 'boolean' | 'number'): FlagPrimitive {\n const trimmed = raw.trim();\n if (trimmed === '' || trimmed.toLowerCase() === 'null') return null;\n switch (type) {\n case 'boolean':\n return trimmed.toLowerCase() === 'true';\n case 'number': {\n const n = Number(trimmed);\n return isNaN(n) ? trimmed : n;\n }\n default:\n return trimmed;\n }\n }\n\n private clearMessages(): void {\n this.error = null;\n this.successMsg = null;\n }\n\n private makeCompositeKey(group: string, key: string): string {\n return `${group}::${key}`;\n }\n\n private splitCompositeKey(composite: string): [string, string] {\n const idx = composite.indexOf('::');\n return [composite.substring(0, idx), composite.substring(idx + 2)];\n }\n\n private valuesEqual(a: FlagPrimitive, b: FlagPrimitive): boolean {\n return a === b;\n }\n\n private ensureGroupVisible(groupName: string): void {\n if (this.groups.some(g => g.name === groupName)) return;\n this.groups.push({ name: groupName, flags: [] });\n this.groups.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n private reconcileNewGroupBadges(): void {\n const existingGroupNames = new Set(this.groups.map(g => g.name));\n for (const groupName of Array.from(this.newlyCreatedGroups)) {\n const group = this.groups.find(g => g.name === groupName);\n const hasGroup = existingGroupNames.has(groupName);\n const hasFlags = !!group && group.flags.length > 0;\n if (!hasGroup || hasFlags) {\n this.newlyCreatedGroups.delete(groupName);\n }\n }\n }\n}\n","<div class=\"container-fluid py-4 pb-5\">\n <!-- Header -->\n <div class=\"d-flex align-items-center mb-4\">\n <h2 class=\"mb-0\">\n <i class=\"bi bi-toggles2 me-2\" aria-hidden=\"true\"></i>Feature Flags\n </h2>\n </div>\n\n <!-- Alerts -->\n <div *ngIf=\"error\" class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle-fill me-2\" aria-hidden=\"true\"></i>{{ error }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"error = null\"></button>\n </div>\n <div *ngIf=\"successMsg\" class=\"alert alert-success alert-dismissible fade show\" role=\"alert\">\n <i class=\"bi bi-check-circle-fill me-2\" aria-hidden=\"true\"></i>{{ successMsg }}\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"successMsg = null\"></button>\n </div>\n <!-- Tenant Context -->\n <div class=\"card shadow-sm mb-4\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <label class=\"form-label fw-semibold mb-1\">Tenant ID</label>\n <div><code>{{ tenantId }}</code></div>\n </div>\n <button class=\"btn btn-primary\" [disabled]=\"loading\" (click)=\"loadFlags()\">\n <span *ngIf=\"loading\" class=\"spinner-border spinner-border-sm me-1\"\n role=\"status\" aria-hidden=\"true\"></span>\n {{ loading ? 'Loading...' : 'Reload Flags' }}\n </button>\n </div>\n <div class=\"form-text\">\n Tenant is provided by parent component input, while <code>x-tenant-id</code> comes from module config.\n </div>\n </div>\n </div>\n\n <!-- Content area (only after load) -->\n <div *ngIf=\"loaded\">\n <!-- Toolbar -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <span class=\"text-muted\">\n {{ groups.length }} group{{ groups.length !== 1 ? 's' : '' }} found\n </span>\n <button class=\"btn btn-success btn-sm\" (click)=\"showAddGroup = true\" [disabled]=\"showAddGroup\">\n <i class=\"bi bi-plus-circle me-1\" aria-hidden=\"true\"></i>Add Group\n </button>\n </div>\n\n <!-- Add Group Form -->\n <div *ngIf=\"showAddGroup\" class=\"card border-success mb-3\">\n <div class=\"card-body\">\n <h6 class=\"card-title text-success\">\n <i class=\"bi bi-folder-plus me-1\" aria-hidden=\"true\"></i>New Group\n </h6>\n <div class=\"input-group input-group-sm\">\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Group name (e.g. uiOptions)\"\n [(ngModel)]=\"newGroupName\"\n (keyup.enter)=\"addGroup()\"\n aria-label=\"New group name\" />\n <button class=\"btn btn-success\" (click)=\"addGroup()\" [disabled]=\"loading || !newGroupName.trim()\">\n {{ loading ? 'Creating...' : 'Create Group' }}\n </button>\n <button class=\"btn btn-outline-secondary\" (click)=\"showAddGroup = false; newGroupName = ''\">\n Cancel\n </button>\n </div>\n <div class=\"form-text\">Letters, numbers, _ and - only. Must start with a letter.</div>\n </div>\n </div>\n\n <!-- Flag Groups -->\n <div *ngFor=\"let group of groups; trackBy: trackGroup\" class=\"mb-3\">\n <valar-flag-group-card\n [groupName]=\"group.name\"\n [flags]=\"group.flags\"\n [isNewGroup]=\"isNewGroup(group)\"\n [dirtyKeys]=\"dirtyKeysForGroup(group.name)\"\n [isAddingFlag]=\"addingFlagToGroup === group.name\"\n [newFlagKey]=\"newFlagKey\"\n [newFlagValue]=\"newFlagValue\"\n [newFlagType]=\"newFlagType\"\n (flagChanged)=\"onFlagChanged(group.name, $event)\"\n (revertFlag)=\"revertFlag(group.name, $event)\"\n (addFlagRequest)=\"addingFlagToGroup = group.name\"\n (cancelAddFlag)=\"cancelAddFlag()\"\n (confirmAddFlag)=\"confirmAddFlag(group.name)\"\n (newFlagKeyChange)=\"newFlagKey = $event\"\n (newFlagValueChange)=\"newFlagValue = $event\"\n (newFlagTypeChange)=\"newFlagType = $event\"\n (deleteFlagRequest)=\"deleteFlag(group.name, $event)\"\n (deleteGroupRequest)=\"deleteGroup(group.name)\">\n </valar-flag-group-card>\n </div>\n\n <!-- Empty state -->\n <div *ngIf=\"groups.length === 0 && !showAddGroup\" class=\"text-center text-muted py-5\">\n <i class=\"bi bi-inbox display-1\" aria-hidden=\"true\"></i>\n <p class=\"mt-3\">No feature flag groups yet. Click <strong>Add Group</strong> to get started.</p>\n </div>\n </div>\n</div>\n\n<!-- Sticky bottom action bar (visible when there are pending changes) -->\n<div *ngIf=\"hasPendingChanges\" class=\"action-bar\">\n <div class=\"container-fluid d-flex justify-content-between align-items-center\">\n <span class=\"text-white\">\n <i class=\"bi bi-pencil-square me-1\" aria-hidden=\"true\"></i>\n <strong>{{ pendingChangeCount }}</strong>\n unsaved change{{ pendingChangeCount !== 1 ? 's' : '' }}\n </span>\n <div>\n <button class=\"btn btn-outline-light btn-sm me-2\" (click)=\"discardAll()\">\n <i class=\"bi bi-x-lg me-1\" aria-hidden=\"true\"></i>Discard All\n </button>\n <button class=\"btn btn-warning btn-sm fw-semibold\" (click)=\"openSummaryModal()\">\n <i class=\"bi bi-send me-1\" aria-hidden=\"true\"></i>Review & Submit\n </button>\n </div>\n </div>\n</div>\n\n<!-- Changes Summary Modal -->\n<valar-changes-summary-modal\n *ngIf=\"showSummaryModal\"\n [changes]=\"pendingChangesList\"\n [tenantId]=\"tenantId\"\n (confirm)=\"submitAll()\"\n (dismiss)=\"closeSummaryModal()\">\n</valar-changes-summary-modal>\n\n","import { Injectable } from '@angular/core';\nimport { Observable, of, delay, throwError } from 'rxjs';\nimport {\n FeatureFlagsDocument,\n FlagGroup,\n RESERVED_KEYS,\n UpsertFlagsPayload,\n} from '../models';\n\n/**\n * In-memory mock of FeatureFlagsApiService.\n * Ships with 2 tenants pre-loaded so you can test the UI\n * without spinning up the backend.\n *\n * Use via `FeatureFlagsModule.forTesting()`.\n */\n@Injectable()\nexport class MockFeatureFlagsApiService {\n private readonly store = new Map<string, FeatureFlagsDocument>();\n\n constructor() {\n this.seedData();\n }\n\n // ────────────────────────── Public API (same shape as real service) ──\n\n getByTenantId(tenantId: string): Observable<FeatureFlagsDocument | null> {\n const doc = this.store.get(this.toTenantToken(tenantId)) ?? null;\n return of(this.clone(doc)).pipe(delay(250));\n }\n\n upsert(tenantId: string, payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument> {\n const tenantToken = this.toTenantToken(tenantId);\n let doc = this.store.get(tenantToken);\n if (!doc) {\n doc = this.newDoc(tenantToken);\n this.store.set(tenantToken, doc);\n }\n\n // Merge groups via dot-path (mirrors the backend's $set behavior)\n for (const [groupName, groupFlags] of Object.entries(payload)) {\n if (RESERVED_KEYS.has(groupName)) continue;\n const existing = (doc[groupName] as FlagGroup) ?? {};\n doc[groupName] = { ...existing, ...(groupFlags as FlagGroup) };\n }\n doc.updatedAt = new Date().toISOString();\n\n return of(this.clone(doc)!).pipe(delay(200));\n }\n\n deleteFlag(tenantId: string, group: string, key: string): Observable<FeatureFlagsDocument> {\n const tenantToken = this.toTenantToken(tenantId);\n const doc = this.store.get(tenantToken);\n if (!doc) {\n return throwError(() => ({\n error: { message: `No feature flags found for tenantId='${tenantToken}'` },\n }));\n }\n\n const g = doc[group] as FlagGroup | undefined;\n if (g && key in g) {\n delete g[key];\n }\n doc.updatedAt = new Date().toISOString();\n\n return of(this.clone(doc)!).pipe(delay(200));\n }\n\n deleteGroup(tenantId: string, group: string): Observable<FeatureFlagsDocument> {\n const tenantToken = this.toTenantToken(tenantId);\n const doc = this.store.get(tenantToken);\n if (!doc) {\n return throwError(() => ({\n error: { message: `No feature flags found for tenantId='${tenantToken}'` },\n }));\n }\n\n delete doc[group];\n doc.updatedAt = new Date().toISOString();\n\n return of(this.clone(doc)!).pipe(delay(200));\n }\n\n // ────────────────────────── Seed data ────────────────────────────────\n\n private seedData(): void {\n this.store.set('tenant_acme', this.buildAcmeTenant());\n this.store.set('tenant_globex', this.buildGlobexTenant());\n }\n\n private buildAcmeTenant(): FeatureFlagsDocument {\n return {\n _id: '6650a1b2c3d4e5f6a7b8c9d0',\n tenantId: 'tenant_acme',\n createdAt: '2025-11-01T08:00:00.000Z',\n updatedAt: '2026-02-15T14:30:00.000Z',\n uiOptions: {\n darkMode: true,\n sidebarCollapsed: false,\n maxItemsPerPage: 25,\n accentColor: '#4f46e5',\n showBetaBanner: true,\n },\n checkout: {\n newCheckoutFlow: true,\n enableGiftCards: false,\n maxCartItems: 50,\n taxCalculation: 'inclusive',\n },\n notifications: {\n emailEnabled: true,\n smsEnabled: false,\n pushEnabled: true,\n dailyDigest: true,\n marketingOptIn: false,\n },\n search: {\n enableFuzzySearch: true,\n maxResults: 100,\n highlightMatches: true,\n searchDelay: 300,\n },\n } as unknown as FeatureFlagsDocument;\n }\n\n private buildGlobexTenant(): FeatureFlagsDocument {\n return {\n _id: '6650b2c3d4e5f6a7b8c9d0e1',\n tenantId: 'tenant_globex',\n createdAt: '2025-12-10T10:00:00.000Z',\n updatedAt: '2026-02-16T09:15:00.000Z',\n uiOptions: {\n darkMode: false,\n sidebarCollapsed: true,\n maxItemsPerPage: 10,\n accentColor: '#059669',\n showBetaBanner: false,\n },\n billing: {\n enableAutoPay: true,\n gracePeriodDays: 7,\n currency: 'USD',\n showInvoiceBreakdown: true,\n lateFeePercent: 1.5,\n },\n featureAccess: {\n analyticsV2: true,\n exportToCsv: true,\n bulkOperations: false,\n advancedReporting: true,\n apiRateLimit: 1000,\n },\n integrations: {\n slackEnabled: true,\n slackWebhookConfigured: true,\n jiraEnabled: false,\n githubEnabled: true,\n webhookRetries: 3,\n },\n } as unknown as FeatureFlagsDocument;\n }\n\n // ────────────────────────── Helpers ──────────────────────────────────\n\n private newDoc(tenantId: string): FeatureFlagsDocument {\n const now = new Date().toISOString();\n return { tenantId, createdAt: now, updatedAt: now };\n }\n\n private toTenantToken(tenantId: string): string {\n const normalized = tenantId.trim();\n if (normalized.startsWith('tenant_')) return normalized;\n return `tenant_${normalized}`;\n }\n\n /** Deep-clone to avoid leaking mutable references */\n private clone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n }\n}\n","import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { provideHttpClient } from '@angular/common/http';\nimport { FeatureFlagsManagerComponent } from './components/feature-flags-manager/feature-flags-manager.component';\nimport { FlagGroupCardComponent } from './components/flag-group-card/flag-group-card.component';\nimport { ChangesSummaryModalComponent } from './components/changes-summary-modal/changes-summary-modal.component';\nimport { FEATURE_FLAGS_CONFIG, FeatureFlagsConfig } from './models';\nimport { FeatureFlagsApiService } from './services/feature-flags-api.service';\nimport { MockFeatureFlagsApiService } from './services/mock-feature-flags-api.service';\n\n@NgModule({\n declarations: [\n FeatureFlagsManagerComponent,\n FlagGroupCardComponent,\n ChangesSummaryModalComponent,\n ],\n imports: [\n CommonModule,\n FormsModule,\n ],\n exports: [\n FeatureFlagsManagerComponent,\n FlagGroupCardComponent,\n ChangesSummaryModalComponent,\n ],\n})\nexport class FeatureFlagsModule {\n /**\n * Call forRoot() in the consuming app to provide\n * the API configuration.\n *\n * @example\n * FeatureFlagsModule.forRoot({\n * apiBaseUrl: 'http://localhost:3000',\n * apiVersion: 'v1',\n * rootTenantId: 'root001', // sent in x-tenant-id header\n * })\n *\n * <valar-feature-flags-manager\n * [(tenantId)]=\"selectedTenantId\">\n * </valar-feature-flags-manager>\n */\n static forRoot(config: FeatureFlagsConfig): ModuleWithProviders<FeatureFlagsModule> {\n return {\n ngModule: FeatureFlagsModule,\n providers: [\n { provide: FEATURE_FLAGS_CONFIG, useValue: config },\n FeatureFlagsApiService,\n provideHttpClient(),\n ],\n };\n }\n\n /**\n * Call forTesting() in the consuming app to use\n * in-memory mock data (no backend needed).\n *\n * Ships with 2 pre-loaded tenants:\n * - \"tenant_acme\" (Acme Corp)\n * - \"tenant_globex\" (Globex Inc)\n *\n * @example\n * FeatureFlagsModule.forTesting()\n */\n static forTesting(): ModuleWithProviders<FeatureFlagsModule> {\n return {\n ngModule: FeatureFlagsModule,\n providers: [\n { provide: FeatureFlagsApiService, useClass: MockFeatureFlagsApiService },\n ],\n };\n }\n}\n","/*\n * Public API Surface of valar-ui\n */\nimport '@angular/localize/init';\n\n// Feature Flags\nexport * from './lib/feature-flags/feature-flags.module';\nexport * from './lib/feature-flags/models';\nexport * from './lib/feature-flags/services/feature-flags-api.service';\nexport * from './lib/feature-flags/services/mock-feature-flags-api.service';\nexport * from './lib/feature-flags/components/feature-flags-manager/feature-flags-manager.component';\nexport * from './lib/feature-flags/components/flag-group-card/flag-group-card.component';\nexport * from './lib/feature-flags/components/changes-summary-modal/changes-summary-modal.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1","i1.FeatureFlagsApiService","i2","i3","i4.FlagGroupCardComponent","i5.ChangesSummaryModalComponent"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, OnDestroy,
|
|
2
|
+
import { InjectionToken, OnDestroy, OnChanges, EventEmitter, SimpleChanges, ModuleWithProviders } from '@angular/core';
|
|
3
3
|
import { HttpClient } from '@angular/common/http';
|
|
4
4
|
import { Observable } from 'rxjs';
|
|
5
5
|
import * as i4 from '@angular/common';
|
|
@@ -27,7 +27,7 @@ interface FeatureFlagsDocument {
|
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
29
|
* Shape of the upsert request body sent to the backend.
|
|
30
|
-
* tenantId + any number of group objects.
|
|
30
|
+
* Service will inject tenantId + any number of group objects.
|
|
31
31
|
*/
|
|
32
32
|
interface UpsertFlagsPayload {
|
|
33
33
|
[groupName: string]: FlagGroup | string;
|
|
@@ -41,8 +41,6 @@ interface FeatureFlagsConfig {
|
|
|
41
41
|
apiBaseUrl: string;
|
|
42
42
|
/** API version prefix, defaults to 'v1' */
|
|
43
43
|
apiVersion?: string;
|
|
44
|
-
/** Target tenant id used in API paths (e.g. /feature-flags/tenant_<tenantId>) */
|
|
45
|
-
tenantId: string;
|
|
46
44
|
/** Root tenant id sent in request headers */
|
|
47
45
|
rootTenantId: string;
|
|
48
46
|
/** Tenant header name, defaults to 'x-tenant-id' */
|
|
@@ -69,19 +67,19 @@ interface PendingChange {
|
|
|
69
67
|
declare class FeatureFlagsApiService {
|
|
70
68
|
private readonly http;
|
|
71
69
|
private readonly baseUrl;
|
|
72
|
-
private readonly tenantId;
|
|
73
70
|
private readonly rootTenantId;
|
|
74
71
|
private readonly tenantHeaderName;
|
|
75
72
|
constructor(http: HttpClient, config: FeatureFlagsConfig);
|
|
76
73
|
/** GET /v1/feature-flags/tenant_<tenantId> */
|
|
77
|
-
getByTenantId(): Observable<FeatureFlagsDocument | null>;
|
|
74
|
+
getByTenantId(tenantId: string): Observable<FeatureFlagsDocument | null>;
|
|
78
75
|
/** PUT /v1/feature-flags (merge-upsert) */
|
|
79
|
-
upsert(payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument>;
|
|
76
|
+
upsert(tenantId: string, payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument>;
|
|
80
77
|
/** DELETE /v1/feature-flags/tenant_<tenantId>/:group/:key */
|
|
81
|
-
deleteFlag(group: string, key: string): Observable<FeatureFlagsDocument>;
|
|
78
|
+
deleteFlag(tenantId: string, group: string, key: string): Observable<FeatureFlagsDocument>;
|
|
82
79
|
/** DELETE /v1/feature-flags/tenant_<tenantId>/:group */
|
|
83
|
-
deleteGroup(group: string): Observable<FeatureFlagsDocument>;
|
|
80
|
+
deleteGroup(tenantId: string, group: string): Observable<FeatureFlagsDocument>;
|
|
84
81
|
private tenantScopedBaseUrl;
|
|
82
|
+
private toTenantToken;
|
|
85
83
|
private requestOptions;
|
|
86
84
|
static ɵfac: i0.ɵɵFactoryDeclaration<FeatureFlagsApiService, never>;
|
|
87
85
|
static ɵprov: i0.ɵɵInjectableDeclaration<FeatureFlagsApiService>;
|
|
@@ -94,9 +92,10 @@ interface ParsedGroup {
|
|
|
94
92
|
value: FlagPrimitive;
|
|
95
93
|
}[];
|
|
96
94
|
}
|
|
97
|
-
declare class FeatureFlagsManagerComponent implements OnDestroy,
|
|
95
|
+
declare class FeatureFlagsManagerComponent implements OnDestroy, OnChanges {
|
|
98
96
|
private readonly api;
|
|
99
97
|
tenantId: string;
|
|
98
|
+
tenantIdChange: EventEmitter<string>;
|
|
100
99
|
groups: ParsedGroup[];
|
|
101
100
|
loading: boolean;
|
|
102
101
|
error: string | null;
|
|
@@ -116,8 +115,8 @@ declare class FeatureFlagsManagerComponent implements OnDestroy, OnInit {
|
|
|
116
115
|
showSummaryModal: boolean;
|
|
117
116
|
private readonly newlyCreatedGroups;
|
|
118
117
|
private readonly destroy$;
|
|
119
|
-
constructor(api: FeatureFlagsApiService
|
|
120
|
-
|
|
118
|
+
constructor(api: FeatureFlagsApiService);
|
|
119
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
121
120
|
ngOnDestroy(): void;
|
|
122
121
|
get hasPendingChanges(): boolean;
|
|
123
122
|
get pendingChangeCount(): number;
|
|
@@ -156,7 +155,7 @@ declare class FeatureFlagsManagerComponent implements OnDestroy, OnInit {
|
|
|
156
155
|
private ensureGroupVisible;
|
|
157
156
|
private reconcileNewGroupBadges;
|
|
158
157
|
static ɵfac: i0.ɵɵFactoryDeclaration<FeatureFlagsManagerComponent, never>;
|
|
159
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<FeatureFlagsManagerComponent, "valar-feature-flags-manager", never, {}, {}, never, never, false, never>;
|
|
158
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<FeatureFlagsManagerComponent, "valar-feature-flags-manager", never, { "tenantId": { "alias": "tenantId"; "required": false; }; }, { "tenantIdChange": "tenantIdChange"; }, never, never, false, never>;
|
|
160
159
|
}
|
|
161
160
|
|
|
162
161
|
interface FlagEntry {
|
|
@@ -234,9 +233,12 @@ declare class FeatureFlagsModule {
|
|
|
234
233
|
* FeatureFlagsModule.forRoot({
|
|
235
234
|
* apiBaseUrl: 'http://localhost:3000',
|
|
236
235
|
* apiVersion: 'v1',
|
|
237
|
-
* tenantId: 'abc123', // used in /feature-flags/tenant_abc123
|
|
238
236
|
* rootTenantId: 'root001', // sent in x-tenant-id header
|
|
239
237
|
* })
|
|
238
|
+
*
|
|
239
|
+
* <valar-feature-flags-manager
|
|
240
|
+
* [(tenantId)]="selectedTenantId">
|
|
241
|
+
* </valar-feature-flags-manager>
|
|
240
242
|
*/
|
|
241
243
|
static forRoot(config: FeatureFlagsConfig): ModuleWithProviders<FeatureFlagsModule>;
|
|
242
244
|
/**
|
|
@@ -265,16 +267,16 @@ declare class FeatureFlagsModule {
|
|
|
265
267
|
*/
|
|
266
268
|
declare class MockFeatureFlagsApiService {
|
|
267
269
|
private readonly store;
|
|
268
|
-
private readonly activeTenantId;
|
|
269
270
|
constructor();
|
|
270
|
-
getByTenantId(): Observable<FeatureFlagsDocument | null>;
|
|
271
|
-
upsert(payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument>;
|
|
272
|
-
deleteFlag(group: string, key: string): Observable<FeatureFlagsDocument>;
|
|
273
|
-
deleteGroup(group: string): Observable<FeatureFlagsDocument>;
|
|
271
|
+
getByTenantId(tenantId: string): Observable<FeatureFlagsDocument | null>;
|
|
272
|
+
upsert(tenantId: string, payload: UpsertFlagsPayload): Observable<FeatureFlagsDocument>;
|
|
273
|
+
deleteFlag(tenantId: string, group: string, key: string): Observable<FeatureFlagsDocument>;
|
|
274
|
+
deleteGroup(tenantId: string, group: string): Observable<FeatureFlagsDocument>;
|
|
274
275
|
private seedData;
|
|
275
276
|
private buildAcmeTenant;
|
|
276
277
|
private buildGlobexTenant;
|
|
277
278
|
private newDoc;
|
|
279
|
+
private toTenantToken;
|
|
278
280
|
/** Deep-clone to avoid leaking mutable references */
|
|
279
281
|
private clone;
|
|
280
282
|
static ɵfac: i0.ɵɵFactoryDeclaration<MockFeatureFlagsApiService, never>;
|