Skip to content

Commit 1ae415e

Browse files
committed
fix(web): fix feature flag Zod UUID validation and add Feature Flags tab in System Suite detail
- Replace strict z.string().uuid() with relaxed guidSchema regex pattern for feature flag response schemas (systemSuiteId, featureFlagId, etc.) to accept SQL Server GUIDs that don't satisfy RFC 4122 variant bits. - Add 'feature-flags' tab to SystemSuiteDetailPanel with Flag icon. - Create SystemSuiteFeatureFlagsPanel component that loads flags for the selected system suite using useGetFeatureFlagsBySystemSuite hook. - Panel shows expandable flag rows with status badges, type badges, activate/deactivate/archive actions, and inline criteria management.
1 parent 96c7a02 commit 1ae415e

3 files changed

Lines changed: 369 additions & 8 deletions

File tree

src/apps/ums.web-app/src/domain/configuration/schemas/feature-flag.schema.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
*/
1313
import { z } from 'zod';
1414

15+
// Zod v4 enforces RFC 4122 variant bits in .uuid() — too strict for SQL Server
16+
// GUIDs which may not satisfy those bit constraints. Use a format-only check.
17+
const guidSchema = z.string().regex(
18+
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/,
19+
'Invalid GUID format',
20+
);
21+
1522
export const FlagTypeSchema = z.enum(['Boolean', 'Variant', 'Percentage']);
1623
export const FlagStatusSchema = z.enum(['Inactive', 'Active', 'Archived']);
1724
export const CriteriaTypeSchema = z.enum([
@@ -34,22 +41,22 @@ export const CriteriaOperatorSchema = z.enum([
3441
]);
3542

3643
export const FeatureFlagCriteriaSchema = z.object({
37-
criteriaId: z.string().uuid(),
44+
criteriaId: guidSchema,
3845
criteriaType: CriteriaTypeSchema,
3946
operator: CriteriaOperatorSchema,
4047
value: z.string(),
4148
createdAtUtc: z.string(),
4249
});
4350

4451
export const FeatureFlagSchema = z.object({
45-
featureFlagId: z.string().uuid(),
46-
systemSuiteId: z.string().uuid(),
52+
featureFlagId: guidSchema,
53+
systemSuiteId: guidSchema,
4754
flagCode: z.string().min(1),
4855
flagType: FlagTypeSchema,
4956
flagTargets: z.string(),
5057
status: FlagStatusSchema,
5158
linkedResourceType: z.string().nullable().optional(),
52-
linkedResourceId: z.string().uuid().nullable().optional(),
59+
linkedResourceId: guidSchema.nullable().optional(),
5360
rolloutPercentage: z.number().int().min(0).max(100).nullable().optional(),
5461
criteria: z.array(FeatureFlagCriteriaSchema).default([]),
5562
});
@@ -67,11 +74,11 @@ export const FeatureFlagPageSchema = z.object({
6774
// ── Response schemas ──────────────────────────────────────────────────────────
6875

6976
export const CreateFeatureFlagResponseSchema = z.object({
70-
featureFlagId: z.string().uuid(),
77+
featureFlagId: guidSchema,
7178
});
7279

7380
export const AddFeatureFlagCriteriaResponseSchema = z.object({
74-
criteriaId: z.string().uuid(),
81+
criteriaId: guidSchema,
7582
});
7683

7784
// ── Type exports ──────────────────────────────────────────────────────────────

src/apps/ums.web-app/src/presentation/authorization/system-suite/components/SystemSuiteDetailPanel.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState, useMemo } from 'react';
22
import {
33
Box, Shield, Key, ChevronDown, ChevronRight, Users,
44
Folder, FolderOpen, Layers, EyeOff, ShieldCheck, Trash2, KeyRound, Plus, Pencil,
5-
ChevronsUpDown, ChevronsDownUp,
5+
ChevronsUpDown, ChevronsDownUp, Flag,
66
} from 'lucide-react';
77
import { SystemSuite } from '@domain/authorization/models/system-suite.model';
88
import { SystemSuiteProfileCard } from './SystemSuiteProfileCard';
@@ -32,10 +32,11 @@ import { CodeBadge } from '@shared/components/CodeBadge';
3232
import { formatSystemCode } from '@app/utils/security';
3333
import { SystemSuiteRolesPanel } from './SystemSuiteRolesPanel';
3434
import { SystemSuiteDomainResourcesPanel } from './SystemSuiteDomainResourcesPanel';
35+
import { SystemSuiteFeatureFlagsPanel } from './SystemSuiteFeatureFlagsPanel';
3536
import { Database } from 'lucide-react';
3637
import { ChildEntityToolbar } from '@shared/components/ChildEntityToolbar';
3738

38-
type SystemSuiteTab = 'overview' | 'modules' | 'domain-resources' | 'actions' | 'roles';
39+
type SystemSuiteTab = 'overview' | 'modules' | 'domain-resources' | 'actions' | 'roles' | 'feature-flags';
3940

4041
interface SystemSuiteDetailPanelProps {
4142
activeSystemSuite: SystemSuite | undefined;
@@ -547,6 +548,7 @@ export const SystemSuiteDetailPanel: React.FC<SystemSuiteDetailPanelProps> = ({
547548
{ key: 'domain-resources', label: 'Recursos de Dominio', icon: <Database className="w-4 h-4" /> },
548549
{ key: 'actions', label: t.actions, icon: <Key className="w-4 h-4" /> },
549550
{ key: 'roles', label: t.roles, icon: <Users className="w-4 h-4" /> },
551+
{ key: 'feature-flags', label: 'Feature Flags', icon: <Flag className="w-4 h-4" /> },
550552
];
551553

552554
const suiteId = activeSystemSuite?.systemSuiteId ?? '';
@@ -949,6 +951,10 @@ export const SystemSuiteDetailPanel: React.FC<SystemSuiteDetailPanelProps> = ({
949951
{activeTab === 'roles' && (
950952
<SystemSuiteRolesPanel systemSuiteId={suiteId} />
951953
)}
954+
955+
{activeTab === 'feature-flags' && (
956+
<SystemSuiteFeatureFlagsPanel systemSuiteId={suiteId} />
957+
)}
952958
</div>
953959
</DetailPanelShell>
954960
);

0 commit comments

Comments
 (0)