simple-module-feature-flags 0.0.30__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.30 → simple_module_feature_flags-0.0.32}/.gitignore +1 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/PKG-INFO +5 -5
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/locales/en.json +12 -4
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/module.py +3 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/pages/Browse.tsx +27 -56
- simple_module_feature_flags-0.0.32/feature_flags/pages/components/TenantPicker.tsx +112 -0
- simple_module_feature_flags-0.0.32/feature_flags/pages/components/ToggleConfirmDialog.tsx +69 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/pyproject.toml +4 -4
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/LICENSE +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/README.md +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/__init__.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/constants.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/contracts/__init__.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/contracts/schemas.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/deps.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/__init__.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/api.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/endpoints/views.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/models.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/py.typed +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/service.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/package.json +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/tests/test_feature_flags_api.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/tests/test_feature_flags_service.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/tests/test_store_outage_fallback.py +0 -0
- {simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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
|
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
"viewing_tenant": "Viewing overrides for tenant \"{tenant_id}\". Tenant overrides beat the system value.",
|
|
11
11
|
"tenant_id_label": "Tenant ID",
|
|
12
12
|
"tenant_id_placeholder": "e.g. acme",
|
|
13
|
-
"scope_label": "Scope",
|
|
14
13
|
"scope_system": "System (all tenants)",
|
|
15
|
-
"scope_tenant": "Specific tenant",
|
|
16
14
|
"go": "View",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
15
|
+
"viewing_label": "Viewing",
|
|
16
|
+
"scope_custom": "Other tenant…",
|
|
17
|
+
"cancel": "Cancel"
|
|
19
18
|
},
|
|
20
19
|
"table": {
|
|
21
20
|
"name": "Flag",
|
|
@@ -31,6 +30,15 @@
|
|
|
31
30
|
"system_value": "System: {value}",
|
|
32
31
|
"clear_override": "Clear override"
|
|
33
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
|
+
},
|
|
34
42
|
"toasts": {
|
|
35
43
|
"enabled": "\"{name}\" enabled",
|
|
36
44
|
"disabled": "\"{name}\" disabled",
|
{simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/module.py
RENAMED
|
@@ -48,6 +48,9 @@ class FeatureFlagsModule(ModuleBase):
|
|
|
48
48
|
order=MENU_ORDER,
|
|
49
49
|
section=MenuSection.SIDEBAR,
|
|
50
50
|
group="Administration",
|
|
51
|
+
# Mirrors the view router's guard. Without it the entry shows
|
|
52
|
+
# for every signed-in account and 403s on click.
|
|
53
|
+
permissions=[PERM_FEATURE_FLAGS_VIEW],
|
|
51
54
|
)
|
|
52
55
|
)
|
|
53
56
|
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
EmptyMedia,
|
|
11
11
|
EmptyTitle,
|
|
12
12
|
} from '@simple-module-py/ui/components/ui/empty';
|
|
13
|
-
import { Input } from '@simple-module-py/ui/components/ui/input';
|
|
14
13
|
import { Switch } from '@simple-module-py/ui/components/ui/switch';
|
|
15
14
|
import {
|
|
16
15
|
Table,
|
|
@@ -25,6 +24,8 @@ import { AuthenticatedLayout } from '@simple-module-py/ui/layouts/AuthenticatedL
|
|
|
25
24
|
import { Flag, RotateCcw } from 'lucide-react';
|
|
26
25
|
import { useState } from 'react';
|
|
27
26
|
import { toast } from 'sonner';
|
|
27
|
+
import { TenantPicker } from './components/TenantPicker';
|
|
28
|
+
import { type PendingToggle, ToggleConfirmDialog } from './components/ToggleConfirmDialog';
|
|
28
29
|
|
|
29
30
|
interface FeatureFlag {
|
|
30
31
|
name: string;
|
|
@@ -55,19 +56,24 @@ function Browse() {
|
|
|
55
56
|
const { t } = useT();
|
|
56
57
|
const { can } = usePermissions();
|
|
57
58
|
const canManage = can('feature_flags.manage');
|
|
58
|
-
const [tenantInput, setTenantInput] = useState(tenant_id ?? '');
|
|
59
59
|
|
|
60
|
-
|
|
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);
|
|
61
67
|
router.post(
|
|
62
|
-
actionUrl(
|
|
68
|
+
actionUrl(name, 'toggle', tenant_id),
|
|
63
69
|
{ enabled: next },
|
|
64
70
|
{
|
|
65
71
|
preserveScroll: true,
|
|
66
72
|
onSuccess: () =>
|
|
67
73
|
toast.success(
|
|
68
74
|
next
|
|
69
|
-
? t(keys.feature_flags.toasts.enabled, { name
|
|
70
|
-
: t(keys.feature_flags.toasts.disabled, { name
|
|
75
|
+
? t(keys.feature_flags.toasts.enabled, { name })
|
|
76
|
+
: t(keys.feature_flags.toasts.disabled, { name }),
|
|
71
77
|
),
|
|
72
78
|
onError: () => toast.error(t(keys.feature_flags.toasts.toggle_failed)),
|
|
73
79
|
},
|
|
@@ -86,10 +92,6 @@ function Browse() {
|
|
|
86
92
|
);
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
function visitTenant(value: string) {
|
|
90
|
-
router.visit(buildPath(value.trim() || null));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
95
|
return (
|
|
94
96
|
<>
|
|
95
97
|
<Head title="Feature Flags" />
|
|
@@ -98,51 +100,11 @@ function Browse() {
|
|
|
98
100
|
description={t(keys.feature_flags.browse.description)}
|
|
99
101
|
>
|
|
100
102
|
<Card className="mb-4 p-4">
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}}
|
|
107
|
-
>
|
|
108
|
-
<div className="flex-1 min-w-[200px]">
|
|
109
|
-
<label className="block text-sm font-medium mb-1" htmlFor="tenant_id">
|
|
110
|
-
{t(keys.feature_flags.browse.tenant_id_label)}
|
|
111
|
-
</label>
|
|
112
|
-
<Input
|
|
113
|
-
id="tenant_id"
|
|
114
|
-
value={tenantInput}
|
|
115
|
-
onChange={(e) => setTenantInput(e.target.value)}
|
|
116
|
-
placeholder={t(keys.feature_flags.browse.tenant_id_placeholder)}
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
<Button type="submit" variant="default">
|
|
120
|
-
{t(keys.feature_flags.browse.go)}
|
|
121
|
-
</Button>
|
|
122
|
-
{tenant_id && (
|
|
123
|
-
<Button type="button" variant="ghost" onClick={() => visitTenant('')}>
|
|
124
|
-
{t(keys.feature_flags.browse.back_to_system)}
|
|
125
|
-
</Button>
|
|
126
|
-
)}
|
|
127
|
-
</form>
|
|
128
|
-
{tenants.length > 0 && (
|
|
129
|
-
<div className="mt-3 flex flex-wrap items-center gap-2 text-sm">
|
|
130
|
-
<span className="text-muted-foreground">
|
|
131
|
-
{t(keys.feature_flags.browse.tenants_with_overrides)}:
|
|
132
|
-
</span>
|
|
133
|
-
{tenants.map((tid) => (
|
|
134
|
-
<Button
|
|
135
|
-
key={tid}
|
|
136
|
-
type="button"
|
|
137
|
-
size="sm"
|
|
138
|
-
variant={tid === tenant_id ? 'default' : 'outline'}
|
|
139
|
-
onClick={() => visitTenant(tid)}
|
|
140
|
-
>
|
|
141
|
-
{tid}
|
|
142
|
-
</Button>
|
|
143
|
-
))}
|
|
144
|
-
</div>
|
|
145
|
-
)}
|
|
103
|
+
<TenantPicker
|
|
104
|
+
tenantId={tenant_id}
|
|
105
|
+
tenants={tenants}
|
|
106
|
+
onSelect={(next) => router.visit(buildPath(next))}
|
|
107
|
+
/>
|
|
146
108
|
<p className="mt-3 text-sm text-muted-foreground">
|
|
147
109
|
{tenant_id
|
|
148
110
|
? t(keys.feature_flags.browse.viewing_tenant, { tenant_id })
|
|
@@ -212,7 +174,9 @@ function Browse() {
|
|
|
212
174
|
<div className="flex items-center gap-3">
|
|
213
175
|
<Switch
|
|
214
176
|
checked={flag.enabled}
|
|
215
|
-
onCheckedChange={(checked) =>
|
|
177
|
+
onCheckedChange={(checked) =>
|
|
178
|
+
setPending({ name: flag.name, next: checked === true })
|
|
179
|
+
}
|
|
216
180
|
disabled={!canManage}
|
|
217
181
|
aria-label={flag.name}
|
|
218
182
|
/>
|
|
@@ -277,6 +241,13 @@ function Browse() {
|
|
|
277
241
|
</TableBody>
|
|
278
242
|
</Table>
|
|
279
243
|
</Card>
|
|
244
|
+
|
|
245
|
+
<ToggleConfirmDialog
|
|
246
|
+
pending={pending}
|
|
247
|
+
tenantId={tenant_id}
|
|
248
|
+
onConfirm={handleToggle}
|
|
249
|
+
onCancel={() => setPending(null)}
|
|
250
|
+
/>
|
|
280
251
|
</PageShell>
|
|
281
252
|
</>
|
|
282
253
|
);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { keys, useT } from '@simple-module-py/i18n';
|
|
2
|
+
import { Button } from '@simple-module-py/ui/components/ui/button';
|
|
3
|
+
import { Input } from '@simple-module-py/ui/components/ui/input';
|
|
4
|
+
import {
|
|
5
|
+
Select,
|
|
6
|
+
SelectContent,
|
|
7
|
+
SelectItem,
|
|
8
|
+
SelectSeparator,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from '@simple-module-py/ui/components/ui/select';
|
|
12
|
+
import { useState } from 'react';
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Currently-viewed tenant, or null for the system scope. */
|
|
16
|
+
tenantId: string | null;
|
|
17
|
+
/** Tenants that already carry at least one override. */
|
|
18
|
+
tenants: string[];
|
|
19
|
+
onSelect: (tenantId: string | null) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const SYSTEM = '__system__';
|
|
23
|
+
const CUSTOM = '__custom__';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Scope picker for the flags table.
|
|
27
|
+
*
|
|
28
|
+
* This was a free-text box, so viewing a tenant meant knowing and correctly
|
|
29
|
+
* typing its id — and a typo silently showed a scope with no overrides
|
|
30
|
+
* instead of an error.
|
|
31
|
+
*
|
|
32
|
+
* The list cannot be closed: there is no tenant registry in the framework
|
|
33
|
+
* (ids arrive on auth claims), so the only tenants this app can enumerate are
|
|
34
|
+
* those that already have an override. Creating the *first* override for a
|
|
35
|
+
* tenant therefore still needs a way to name one by hand, which is what the
|
|
36
|
+
* "other tenant" branch is for.
|
|
37
|
+
*/
|
|
38
|
+
export function TenantPicker({ tenantId, tenants, onSelect }: Props) {
|
|
39
|
+
const { t } = useT();
|
|
40
|
+
const [custom, setCustom] = useState(false);
|
|
41
|
+
const [draft, setDraft] = useState('');
|
|
42
|
+
|
|
43
|
+
// The tenant being viewed may not have overrides yet, so it can be absent
|
|
44
|
+
// from `tenants` — without this it would vanish from its own picker.
|
|
45
|
+
const options = [...new Set(tenantId ? [...tenants, tenantId] : tenants)].sort();
|
|
46
|
+
|
|
47
|
+
function handleChange(value: string) {
|
|
48
|
+
if (value === CUSTOM) {
|
|
49
|
+
setCustom(true);
|
|
50
|
+
setDraft('');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setCustom(false);
|
|
54
|
+
onSelect(value === SYSTEM ? null : value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="flex flex-wrap items-end gap-3">
|
|
59
|
+
<div className="min-w-[240px]">
|
|
60
|
+
<span className="mb-1 block text-sm font-medium">
|
|
61
|
+
{t(keys.feature_flags.browse.viewing_label)}
|
|
62
|
+
</span>
|
|
63
|
+
<Select value={custom ? CUSTOM : (tenantId ?? SYSTEM)} onValueChange={handleChange}>
|
|
64
|
+
<SelectTrigger className="w-full">
|
|
65
|
+
<SelectValue />
|
|
66
|
+
</SelectTrigger>
|
|
67
|
+
<SelectContent>
|
|
68
|
+
<SelectItem value={SYSTEM}>{t(keys.feature_flags.browse.scope_system)}</SelectItem>
|
|
69
|
+
{options.length > 0 && <SelectSeparator />}
|
|
70
|
+
{options.map((tid) => (
|
|
71
|
+
<SelectItem key={tid} value={tid}>
|
|
72
|
+
{tid}
|
|
73
|
+
</SelectItem>
|
|
74
|
+
))}
|
|
75
|
+
<SelectSeparator />
|
|
76
|
+
<SelectItem value={CUSTOM}>{t(keys.feature_flags.browse.scope_custom)}</SelectItem>
|
|
77
|
+
</SelectContent>
|
|
78
|
+
</Select>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{custom && (
|
|
82
|
+
<form
|
|
83
|
+
className="flex items-end gap-2"
|
|
84
|
+
onSubmit={(e) => {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
const trimmed = draft.trim();
|
|
87
|
+
if (trimmed) onSelect(trimmed);
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<div className="min-w-[200px]">
|
|
91
|
+
<label className="mb-1 block text-sm font-medium" htmlFor="tenant_id">
|
|
92
|
+
{t(keys.feature_flags.browse.tenant_id_label)}
|
|
93
|
+
</label>
|
|
94
|
+
<Input
|
|
95
|
+
id="tenant_id"
|
|
96
|
+
autoFocus
|
|
97
|
+
value={draft}
|
|
98
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
99
|
+
placeholder={t(keys.feature_flags.browse.tenant_id_placeholder)}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<Button type="submit" disabled={!draft.trim()}>
|
|
103
|
+
{t(keys.feature_flags.browse.go)}
|
|
104
|
+
</Button>
|
|
105
|
+
<Button type="button" variant="ghost" onClick={() => setCustom(false)}>
|
|
106
|
+
{t(keys.feature_flags.browse.cancel)}
|
|
107
|
+
</Button>
|
|
108
|
+
</form>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -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
|
{simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/__init__.py
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.30 → 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.30 → 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.30 → simple_module_feature_flags-0.0.32}/feature_flags/models.py
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.30 → simple_module_feature_flags-0.0.32}/feature_flags/py.typed
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.30 → 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
|