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.
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/PKG-INFO +4 -4
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/locales/en.json +9 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/pages/Browse.tsx +22 -5
- simple_module_feature_flags-0.0.32/feature_flags/pages/components/ToggleConfirmDialog.tsx +69 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/pyproject.toml +4 -4
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/.gitignore +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/LICENSE +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/README.md +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/__init__.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/constants.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/contracts/__init__.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/contracts/schemas.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/deps.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/__init__.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/api.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/views.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/models.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/module.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/pages/components/TenantPicker.tsx +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/py.typed +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/service.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/package.json +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/tests/test_feature_flags_api.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/tests/test_feature_flags_service.py +0 -0
- {simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/tests/test_store_outage_fallback.py +0 -0
- {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.
|
|
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.
|
|
25
|
-
Requires-Dist: simple-module-db==0.0.
|
|
26
|
-
Requires-Dist: simple-module-hosting==0.0.
|
|
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
|
-
|
|
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(
|
|
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
|
|
68
|
-
: t(keys.feature_flags.toasts.disabled, { 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) =>
|
|
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.
|
|
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.
|
|
25
|
-
"simple_module_db==0.0.
|
|
26
|
-
"simple_module_hosting==0.0.
|
|
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]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/__init__.py
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/deps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/models.py
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/module.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/py.typed
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.31 → simple_module_feature_flags-0.0.32}/feature_flags/service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|