simple-module-feature-flags 0.0.31__tar.gz → 0.0.32__tar.gz

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.
Files changed (26) hide show
  1. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/PKG-INFO +4 -4
  2. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/locales/en.json +9 -0
  3. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/pages/Browse.tsx +22 -5
  4. simple_module_feature_flags-0.0.32/feature_flags/pages/components/ToggleConfirmDialog.tsx +69 -0
  5. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/pyproject.toml +4 -4
  6. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/.gitignore +0 -0
  7. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/LICENSE +0 -0
  8. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/README.md +0 -0
  9. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/__init__.py +0 -0
  10. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/constants.py +0 -0
  11. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/contracts/__init__.py +0 -0
  12. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/contracts/schemas.py +0 -0
  13. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/deps.py +0 -0
  14. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/__init__.py +0 -0
  15. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/api.py +0 -0
  16. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/views.py +0 -0
  17. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/models.py +0 -0
  18. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/module.py +0 -0
  19. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/pages/components/TenantPicker.tsx +0 -0
  20. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/py.typed +0 -0
  21. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/service.py +0 -0
  22. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/package.json +0 -0
  23. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/tests/test_feature_flags_api.py +0 -0
  24. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/tests/test_feature_flags_service.py +0 -0
  25. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/tests/test_store_outage_fallback.py +0 -0
  26. {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/tsconfig.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: simple_module_feature_flags
3
- Version: 0.0.31
3
+ Version: 0.0.32
4
4
  Summary: Simple feature-flag module with per-tenant overrides and a consumer API for simple_module
5
5
  Project-URL: Homepage, https://github.com/antosubash/simple_module_python
6
6
  Project-URL: Repository, https://github.com/antosubash/simple_module_python
@@ -21,9 +21,9 @@ Classifier: Topic :: Internet :: WWW/HTTP
21
21
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.12
24
- Requires-Dist: simple-module-core==0.0.31
25
- Requires-Dist: simple-module-db==0.0.31
26
- Requires-Dist: simple-module-hosting==0.0.31
24
+ Requires-Dist: simple-module-core==0.0.32
25
+ Requires-Dist: simple-module-db==0.0.32
26
+ Requires-Dist: simple-module-hosting==0.0.32
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # simple_module_feature_flags
@@ -30,6 +30,15 @@
30
30
  "system_value": "System: {value}",
31
31
  "clear_override": "Clear override"
32
32
  },
33
+ "confirm": {
34
+ "enable_title": "Enable \"{name}\"?",
35
+ "disable_title": "Disable \"{name}\"?",
36
+ "scope_system": "This changes the value for every tenant, immediately.",
37
+ "scope_tenant": "This sets an override for tenant \"{tenant_id}\" only. Other tenants keep following the system value.",
38
+ "enable_action": "Enable",
39
+ "disable_action": "Disable",
40
+ "cancel": "Cancel"
41
+ },
33
42
  "toasts": {
34
43
  "enabled": "\"{name}\" enabled",
35
44
  "disabled": "\"{name}\" disabled",
@@ -22,8 +22,10 @@ import {
22
22
  import { usePermissions } from '@simple-module-py/ui/hooks/use-permissions';
23
23
  import { AuthenticatedLayout } from '@simple-module-py/ui/layouts/AuthenticatedLayout';
24
24
  import { Flag, RotateCcw } from 'lucide-react';
25
+ import { useState } from 'react';
25
26
  import { toast } from 'sonner';
26
27
  import { TenantPicker } from './components/TenantPicker';
28
+ import { type PendingToggle, ToggleConfirmDialog } from './components/ToggleConfirmDialog';
27
29
 
28
30
  interface FeatureFlag {
29
31
  name: string;
@@ -55,17 +57,23 @@ function Browse() {
55
57
  const { can } = usePermissions();
56
58
  const canManage = can('feature_flags.manage');
57
59
 
58
- function handleToggle(flag: FeatureFlag, next: boolean) {
60
+ // The switch stages the change; the confirm is what writes it. The Switch
61
+ // stays bound to the server value, so cancelling leaves it where it was
62
+ // without needing to undo anything.
63
+ const [pending, setPending] = useState<PendingToggle | null>(null);
64
+
65
+ function handleToggle({ name, next }: PendingToggle) {
66
+ setPending(null);
59
67
  router.post(
60
- actionUrl(flag.name, 'toggle', tenant_id),
68
+ actionUrl(name, 'toggle', tenant_id),
61
69
  { enabled: next },
62
70
  {
63
71
  preserveScroll: true,
64
72
  onSuccess: () =>
65
73
  toast.success(
66
74
  next
67
- ? t(keys.feature_flags.toasts.enabled, { name: flag.name })
68
- : t(keys.feature_flags.toasts.disabled, { name: flag.name }),
75
+ ? t(keys.feature_flags.toasts.enabled, { name })
76
+ : t(keys.feature_flags.toasts.disabled, { name }),
69
77
  ),
70
78
  onError: () => toast.error(t(keys.feature_flags.toasts.toggle_failed)),
71
79
  },
@@ -166,7 +174,9 @@ function Browse() {
166
174
  <div className="flex items-center gap-3">
167
175
  <Switch
168
176
  checked={flag.enabled}
169
- onCheckedChange={(checked) => handleToggle(flag, checked === true)}
177
+ onCheckedChange={(checked) =>
178
+ setPending({ name: flag.name, next: checked === true })
179
+ }
170
180
  disabled={!canManage}
171
181
  aria-label={flag.name}
172
182
  />
@@ -231,6 +241,13 @@ function Browse() {
231
241
  </TableBody>
232
242
  </Table>
233
243
  </Card>
244
+
245
+ <ToggleConfirmDialog
246
+ pending={pending}
247
+ tenantId={tenant_id}
248
+ onConfirm={handleToggle}
249
+ onCancel={() => setPending(null)}
250
+ />
234
251
  </PageShell>
235
252
  </>
236
253
  );
@@ -0,0 +1,69 @@
1
+ import { keys, useT } from '@simple-module-py/i18n';
2
+ import {
3
+ AlertDialog,
4
+ AlertDialogAction,
5
+ AlertDialogCancel,
6
+ AlertDialogContent,
7
+ AlertDialogDescription,
8
+ AlertDialogFooter,
9
+ AlertDialogHeader,
10
+ AlertDialogTitle,
11
+ } from '@simple-module-py/ui/components/ui/alert-dialog';
12
+
13
+ export interface PendingToggle {
14
+ name: string;
15
+ next: boolean;
16
+ }
17
+
18
+ interface Props {
19
+ pending: PendingToggle | null;
20
+ tenantId: string | null;
21
+ onConfirm: (pending: PendingToggle) => void;
22
+ onCancel: () => void;
23
+ }
24
+
25
+ /**
26
+ * Confirms a flag flip before it is written.
27
+ *
28
+ * A flag toggle takes effect the instant the switch moves, with no undo and no
29
+ * local record of who moved it — and the same switch means "for this tenant" or
30
+ * "for everyone" depending on a scope selector further up the page. That scope
31
+ * is the part worth restating: it is the difference between an experiment and
32
+ * an outage, and it is the piece you cannot see from the switch itself.
33
+ */
34
+ export function ToggleConfirmDialog({ pending, tenantId, onConfirm, onCancel }: Props) {
35
+ const { t } = useT();
36
+ if (!pending) return null;
37
+
38
+ // Spelled out per branch — `t` needs a literal key, not one built at runtime.
39
+ const title = pending.next
40
+ ? t(keys.feature_flags.confirm.enable_title, { name: pending.name })
41
+ : t(keys.feature_flags.confirm.disable_title, { name: pending.name });
42
+ const scope = tenantId
43
+ ? t(keys.feature_flags.confirm.scope_tenant, { tenant_id: tenantId })
44
+ : t(keys.feature_flags.confirm.scope_system);
45
+
46
+ return (
47
+ <AlertDialog open onOpenChange={(open) => !open && onCancel()}>
48
+ <AlertDialogContent>
49
+ <AlertDialogHeader>
50
+ <AlertDialogTitle>{title}</AlertDialogTitle>
51
+ <AlertDialogDescription>{scope}</AlertDialogDescription>
52
+ </AlertDialogHeader>
53
+ <AlertDialogFooter>
54
+ <AlertDialogCancel onClick={onCancel}>
55
+ {t(keys.feature_flags.confirm.cancel)}
56
+ </AlertDialogCancel>
57
+ <AlertDialogAction
58
+ variant={pending.next ? 'default' : 'destructive'}
59
+ onClick={() => onConfirm(pending)}
60
+ >
61
+ {pending.next
62
+ ? t(keys.feature_flags.confirm.enable_action)
63
+ : t(keys.feature_flags.confirm.disable_action)}
64
+ </AlertDialogAction>
65
+ </AlertDialogFooter>
66
+ </AlertDialogContent>
67
+ </AlertDialog>
68
+ );
69
+ }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simple_module_feature_flags"
3
- version = "0.0.31"
3
+ version = "0.0.32"
4
4
  description = "Simple feature-flag module with per-tenant overrides and a consumer API for simple_module"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -21,9 +21,9 @@ classifiers = [
21
21
  "Typing :: Typed",
22
22
  ]
23
23
  dependencies = [
24
- "simple_module_core==0.0.31",
25
- "simple_module_db==0.0.31",
26
- "simple_module_hosting==0.0.31",
24
+ "simple_module_core==0.0.32",
25
+ "simple_module_db==0.0.32",
26
+ "simple_module_hosting==0.0.32",
27
27
  ]
28
28
 
29
29
  [project.entry-points.simple_module]