chore: consolidate platform split checks
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Module Matrix / module-matrix (push) Has been cancelled

This commit is contained in:
2026-07-10 12:51:19 +02:00
parent 150b720f12
commit 635d25c74c
216 changed files with 23336 additions and 4077 deletions

View File

@@ -1,3 +1,4 @@
import { i18nMessage } from "../../i18n/LanguageContext";
import { useEffect, useMemo, useState, type ReactNode } from "react";
import type { ApiSettings } from "../../types";
import {
@@ -8,15 +9,21 @@ import {
type PrivacyRetentionPolicy,
type PrivacyRetentionPolicyFieldKey,
type PrivacyRetentionPolicyPatch,
type PrivacyRetentionPolicyScope
} from "../../api/admin";
type PrivacyRetentionPolicyScope } from
"../../api/admin";
import Button from "../../components/Button";
import Card from "../../components/Card";
import DismissibleAlert from "../../components/DismissibleAlert";
import LoadingFrame from "../../components/LoadingFrame";
import { PolicyRow, PolicySection, PolicyTable } from "../../components/PolicyTable";
import type { PolicySourcePathItem } from "../../components/PolicySourcePath";
import FieldLabel from "../../components/help/FieldLabel";
import FormField from "../../components/FormField";
import ToggleSwitch from "../../components/ToggleSwitch";
import { useUnsavedDraftGuard } from "../../components/UnsavedChangesGuard";
import {
privacyRetentionLocalAllowsLowerLevel,
privacyRetentionParentAllowsField } from
"./policyLogic";
export type RetentionPolicyTargetOption = {
id: string;
@@ -47,11 +54,11 @@ type RetentionPolicyEditorProps = {
};
type DayKey =
| "raw_campaign_json_retention_days"
| "generated_eml_retention_days"
| "stored_report_detail_retention_days"
| "mock_mailbox_retention_days"
| "audit_detail_retention_days";
"raw_campaign_json_retention_days" |
"generated_eml_retention_days" |
"stored_report_detail_retention_days" |
"mock_mailbox_retention_days" |
"audit_detail_retention_days";
type FieldDefinition = {
key: PrivacyRetentionPolicyFieldKey;
@@ -87,22 +94,22 @@ const defaultPrivacyPolicy: PrivacyRetentionPolicy = {
};
const fieldDefinitions: FieldDefinition[] = [
{ key: "store_raw_campaign_json", label: "Raw campaign JSON", kind: "raw-json" },
{ key: "raw_campaign_json_retention_days", label: "Raw campaign JSON days", kind: "days" },
{ key: "generated_eml_retention_days", label: "Generated EML days", kind: "days" },
{ key: "stored_report_detail_retention_days", label: "Stored report detail days", kind: "days" },
{ key: "mock_mailbox_retention_days", label: "Mock mailbox days", kind: "days", systemOnly: true },
{ key: "audit_detail_retention_days", label: "Audit detail days", kind: "days" },
{ key: "audit_detail_level", label: "Audit detail level", kind: "audit" }
];
{ key: "store_raw_campaign_json", label: "i18n:govoplan-core.raw_campaign_json.4ca2265b", kind: "raw-json" },
{ key: "raw_campaign_json_retention_days", label: "i18n:govoplan-core.raw_campaign_json_days.48805a59", kind: "days" },
{ key: "generated_eml_retention_days", label: "i18n:govoplan-core.generated_eml_days.bcb273d9", kind: "days" },
{ key: "stored_report_detail_retention_days", label: "i18n:govoplan-core.stored_report_detail_days.296f6dab", kind: "days" },
{ key: "mock_mailbox_retention_days", label: "i18n:govoplan-core.mock_mailbox_days.a0cf3209", kind: "days", systemOnly: true },
{ key: "audit_detail_retention_days", label: "i18n:govoplan-core.audit_detail_days.21c84dd1", kind: "days" },
{ key: "audit_detail_level", label: "i18n:govoplan-core.audit_detail_level.b0565260", kind: "audit" }];
export function RetentionPolicyScopeManager({
settings,
scopeType,
scopeId = null,
targetOptions = [],
targetLabel = "Target",
title = "Retention policy",
targetLabel = "i18n:govoplan-core.target.61ad50a9",
title = "i18n:govoplan-core.retention_policy.962fb418",
description,
canWrite,
locked = false
@@ -112,10 +119,10 @@ export function RetentionPolicyScopeManager({
const targetSelectionRequired = requiresTarget && !scopeId;
const hasSelectableTarget = targetOptions.length > 0;
const activeScopeId = scopeId || selectedTargetId || null;
const defaultDescription = scopeType === "system"
? "System retention defaults and the fields lower levels may override."
: "Local retention limits for this scope. Values inherit from the parent unless explicitly narrowed.";
const targetEmptyText = `No ${pluralizeLabel(targetLabel.toLowerCase())} available`;
const defaultDescription = scopeType === "system" ?
"i18n:govoplan-core.system_retention_defaults_and_the_fields_lower_l.90a9e923" :
"i18n:govoplan-core.local_retention_limits_for_this_scope_values_inh.6114dade";
const targetEmptyText = i18nMessage("i18n:govoplan-core.no_value_available", { value0: targetPluralLabel(scopeType, targetLabel) });
useEffect(() => {
if (scopeId) {
@@ -131,44 +138,45 @@ export function RetentionPolicyScopeManager({
return (
<div className="retention-policy-manager">
{targetSelectionRequired && (
<Card title={`${targetLabel} scope`}>
{targetSelectionRequired &&
<Card title={i18nMessage("i18n:govoplan-core.value_scope", { value0: targetLabel })}>
<div className="retention-policy-target-row">
<FormField label={targetLabel}>
<select value={selectedTargetId} disabled={!hasSelectableTarget} onChange={(event) => setSelectedTargetId(event.target.value)}>
{!hasSelectableTarget && <option value="">{targetEmptyText}</option>}
{targetOptions.map((option) => <option key={option.id} value={option.id}>{option.secondary ? `${option.label} (${option.secondary})` : option.label}</option>)}
{targetOptions.map((option) => <option key={option.id} value={option.id}>{option.secondary ? i18nMessage("i18n:govoplan-core.value_value.c189e8bc", { value0: option.label, value1: option.secondary }) : option.label}</option>)}
</select>
</FormField>
</div>
</Card>
)}
{(!requiresTarget || Boolean(activeScopeId)) && (
<RetentionPolicyEditor
settings={settings}
scopeType={scopeType}
scopeId={activeScopeId}
title={title}
description={description ?? defaultDescription}
canWrite={canWrite}
locked={locked}
/>
)}
</div>
);
}
{(!requiresTarget || Boolean(activeScopeId)) &&
<RetentionPolicyEditor
settings={settings}
scopeType={scopeType}
scopeId={activeScopeId}
title={title}
description={description ?? defaultDescription}
canWrite={canWrite}
locked={locked} />
}
</div>);
}
function pluralizeLabel(label: string): string {
if (label.endsWith("s")) return label;
if (label.endsWith("y")) return `${label.slice(0, -1)}ies`;
return `${label}s`;
function targetPluralLabel(scopeType: PrivacyRetentionPolicyScope, fallback: string): string {
if (scopeType === "user") return "i18n:govoplan-core.users";
if (scopeType === "group") return "i18n:govoplan-core.groups";
if (scopeType === "campaign") return "i18n:govoplan-core.campaigns.01a23a28";
return fallback;
}
export function RetentionPolicyEditor({
settings,
scopeType,
scopeId = null,
title = "Retention policy",
title = "i18n:govoplan-core.retention_policy.962fb418",
description,
canWrite,
locked = false
@@ -177,6 +185,7 @@ export function RetentionPolicyEditor({
const [effectivePolicy, setEffectivePolicy] = useState<PrivacyRetentionPolicy | null>(null);
const [parentPolicy, setParentPolicy] = useState<PrivacyRetentionPolicy | null>(null);
const [effectivePolicySources, setEffectivePolicySources] = useState<PolicySourcePathItem[]>([]);
const [savedPolicyKey, setSavedPolicyKey] = useState("");
const [loading, setLoading] = useState(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
@@ -191,8 +200,18 @@ export function RetentionPolicyEditor({
const activeParentPolicy = parentPolicy ? normalizeFullPolicy(parentPolicy) : null;
const activeFullPolicy = normalizeFullPolicy({ ...defaultPrivacyPolicy, ...policy, allow_lower_level_limits: { ...defaultAllowLowerLevelLimits, ...(policy.allow_lower_level_limits ?? {}) } });
const visibleFields = useMemo(() => fieldDefinitions.filter((field) => isSystem || !field.systemOnly), [isSystem]);
const policyDirty = scopeReady && Boolean(savedPolicyKey) && policyDraftKey(policy, isSystem) !== savedPolicyKey;
useEffect(() => { void loadPolicy(); }, [settings.accessToken, settings.apiBaseUrl, settings.apiKey, scopeType, scopeId]);
useUnsavedDraftGuard({
dirty: policyDirty,
onSave: savePolicy,
onDiscard: () => {
if (!savedPolicyKey) return;
setPolicy(JSON.parse(savedPolicyKey) as PrivacyRetentionPolicyPatch);
}
});
useEffect(() => {void loadPolicy();}, [settings.accessToken, settings.apiBaseUrl, settings.apiKey, scopeType, scopeId]);
async function loadPolicy() {
setError("");
@@ -207,12 +226,15 @@ export function RetentionPolicyEditor({
setLoading(true);
try {
const response = await getPrivacyRetentionPolicy(settings, scopeType, scopeId);
setPolicy(isSystem ? normalizeFullPolicy(response.policy) : normalizePatch(response.policy));
const loadedPolicy = isSystem ? normalizeFullPolicy(response.policy) : normalizePatch(response.policy);
setPolicy(loadedPolicy);
setSavedPolicyKey(policyDraftKey(loadedPolicy, isSystem));
setEffectivePolicy(normalizeFullPolicy(response.effective_policy));
setParentPolicy(response.parent_policy ? normalizeFullPolicy(response.parent_policy) : null);
setEffectivePolicySources(response.effective_policy_sources ?? []);
} catch (err) {
setPolicy({});
setSavedPolicyKey("");
setEffectivePolicy(null);
setParentPolicy(null);
setEffectivePolicySources([]);
@@ -222,21 +244,25 @@ export function RetentionPolicyEditor({
}
}
async function savePolicy() {
if (!scopeReady) return;
async function savePolicy(): Promise<boolean> {
if (!scopeReady) return false;
setBusy(true);
setError("");
setSuccess("");
try {
const payload = isSystem ? normalizeFullPolicy(policy) : normalizePatchForSave(policy, activeParentPolicy, scopeType);
const response = await updatePrivacyRetentionPolicy(settings, scopeType, payload, scopeId);
setPolicy(isSystem ? normalizeFullPolicy(response.policy) : normalizePatch(response.policy));
const savedPolicy = isSystem ? normalizeFullPolicy(response.policy) : normalizePatch(response.policy);
setPolicy(savedPolicy);
setSavedPolicyKey(policyDraftKey(savedPolicy, isSystem));
setEffectivePolicy(normalizeFullPolicy(response.effective_policy));
setParentPolicy(response.parent_policy ? normalizeFullPolicy(response.parent_policy) : null);
setEffectivePolicySources(response.effective_policy_sources ?? []);
setSuccess("Retention policy saved.");
setSuccess("i18n:govoplan-core.retention_policy_saved.eb577758");
return true;
} catch (err) {
setError(errorMessage(err));
return false;
} finally {
setBusy(false);
}
@@ -260,7 +286,7 @@ export function RetentionPolicyEditor({
function setRetentionDays(key: DayKey, value: string) {
const trimmed = value.trim();
patchPolicy({ [key]: trimmed === "" ? (isSystem ? null : undefined) : Math.max(0, Number(trimmed) || 0) });
patchPolicy({ [key]: trimmed === "" ? isSystem ? null : undefined : Math.max(0, Number(trimmed) || 0) });
}
function setAuditDetail(value: AuditDetailValue) {
@@ -272,84 +298,89 @@ export function RetentionPolicyEditor({
}
function parentAllows(key: PrivacyRetentionPolicyFieldKey): boolean {
return !activeParentPolicy || activeParentPolicy.allow_lower_level_limits[key] !== false;
return privacyRetentionParentAllowsField(activeParentPolicy, key);
}
function localAllowsLower(key: PrivacyRetentionPolicyFieldKey): boolean {
if (isSystem) return activeFullPolicy.allow_lower_level_limits[key] !== false;
const localValue = policy.allow_lower_level_limits?.[key];
if (localValue !== undefined) return localValue && parentAllows(key);
return parentAllows(key);
return privacyRetentionLocalAllowsLowerLevel(isSystem ? activeFullPolicy : policy, activeParentPolicy, key, isSystem);
}
const localPolicySummary = useMemo(() => isSystem ? "System baseline" : localPolicyCount(policy), [isSystem, policy]);
const defaultDescription = isSystem
? "Set concrete system retention values. Blank day fields mean unlimited retention."
: "Blank values inherit. Numeric values may only shorten the inherited retention period; audit detail may only become more restrictive.";
const localPolicySummary = useMemo(() => isSystem ? "i18n:govoplan-core.system_baseline.219ce334" : localPolicyCount(policy), [isSystem, policy]);
const defaultDescription = isSystem ?
"i18n:govoplan-core.set_concrete_system_retention_values_blank_day_f.98b9a627" :
"i18n:govoplan-core.blank_values_inherit_numeric_values_may_only_sho.1a96ce70";
return (
<Card
title={title}
actions={
<div className="button-row compact-actions">
<Button onClick={() => void loadPolicy()} disabled={loading || !scopeReady}>{loading ? "Loading…" : "Reload"}</Button>
<Button variant="primary" onClick={() => void savePolicy()} disabled={disabled}>{busy ? "Saving…" : "Save policy"}</Button>
<div className="button-row compact-actions">
<Button onClick={() => void loadPolicy()} disabled={loading || !scopeReady}>{loading ? "i18n:govoplan-core.loading.33ce4174" : "i18n:govoplan-core.reload.cce71553"}</Button>
<Button variant="primary" onClick={() => void savePolicy()} disabled={disabled}>{busy ? "i18n:govoplan-core.saving.56a2285c" : "i18n:govoplan-core.save_policy.77d67ce3"}</Button>
</div>
}
>
}>
<LoadingFrame loading={loading} label="i18n:govoplan-core.loading_retention_policy.dcd30fb6">
<div className="retention-policy-editor">
<p className="muted small-note retention-policy-description">{description ?? defaultDescription}</p>
{!isSystem && <p className="muted small-note retention-policy-description">Mock mailbox retention is currently managed at system level because mock mailbox records do not carry tenant or campaign ownership metadata.</p>}
{!isSystem && <p className="muted small-note retention-policy-description">i18n:govoplan-core.mock_mailbox_retention_is_currently_managed_at_s.8c6a8ec7</p>}
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
{success && <DismissibleAlert tone="success" resetKey={success} floating>{success}</DismissibleAlert>}
<section className="retention-policy-section">
<div className="subsection-heading split">
<h3>{isSystem ? "System policy" : "Local policy"}</h3>
<span className="muted small-note">{localPolicySummary}</span>
</div>
<div className={`retention-policy-table policy-table${showEffectiveColumn ? " with-effective-column" : ""}${showAllowColumn ? " with-allow-column" : ""}`}>
<div className="retention-policy-row policy-row retention-policy-row-header policy-row-header">
<span>Field</span>
<span>{isSystem ? "Value" : "Local setting"}</span>
{showEffectiveColumn && <span>Effective policy</span>}
{showAllowColumn && <span>Lower levels</span>}
</div>
<PolicySection
className="retention-policy-section"
title={isSystem ? "i18n:govoplan-core.system_policy.3e1aac4b" : "i18n:govoplan-core.local_policy.942f8665"}
summary={<span className="muted small-note">{localPolicySummary}</span>}>
<PolicyTable
className="retention-policy-table"
rowClassName="retention-policy-row"
headerClassName="retention-policy-row-header"
fieldLabel="i18n:govoplan-core.field.c326a466"
settingLabel={isSystem ? "i18n:govoplan-core.value.8dce170d" : "i18n:govoplan-core.local_setting.967607a9"}
effectiveLabel="i18n:govoplan-core.effective_policy.feedb950"
lowerLevelLabel="i18n:govoplan-core.lower_levels.940821ee"
showEffectiveColumn={showEffectiveColumn}
showAllowColumn={showAllowColumn}>
{visibleFields.map((field) => {
const fieldLocked = !parentAllows(field.key);
const fieldDisabled = disabled || fieldLocked;
return (
<div className="retention-policy-row policy-row" key={field.key}>
<div className="retention-policy-field-label policy-field-label">
<strong>{field.label}</strong>
{fieldLocked && <small>Locked by parent policy</small>}
{field.systemOnly && <small>System-level cleanup scope</small>}
</div>
<div className="policy-control">{renderFieldControl(field, activeFullPolicy, policy, isSystem, fieldDisabled, setRawJson, setRetentionDays, setAuditDetail, activeParentPolicy)}</div>
{showEffectiveColumn && (
<div className="retention-policy-effective-cell policy-effective-cell">
<FieldLabel className="retention-policy-effective-value policy-effective-value" help={retentionPolicyPathHelp(field, effectivePolicySources, scopeType)}>
{retentionFieldValue(field, effectivePolicy, loading)}
</FieldLabel>
</div>
)}
{showAllowColumn && (
<ToggleSwitch
checked={localAllowsLower(field.key)}
disabled={disabled || fieldLocked}
onChange={(checked) => setAllowLowerLevelLimit(field.key, checked)}
label="Allow override"
/>
)}
</div>
);
<PolicyRow
key={field.key}
className="retention-policy-row"
labelClassName="retention-policy-field-label"
effectiveCellClassName="retention-policy-effective-cell"
effectiveClassName="retention-policy-effective-value"
label={field.label}
note={
<>
{fieldLocked && <small>i18n:govoplan-core.locked_by_parent_policy.66f587c1</small>}
{field.systemOnly && <small>i18n:govoplan-core.system_level_cleanup_scope.2bce57b5</small>}
</>
}
control={renderFieldControl(field, activeFullPolicy, policy, isSystem, fieldDisabled, setRawJson, setRetentionDays, setAuditDetail, activeParentPolicy)}
effective={showEffectiveColumn ? retentionFieldValue(field, effectivePolicy, loading) : undefined}
effectiveHelp={showEffectiveColumn ? retentionPolicyPathHelp(field, effectivePolicySources, scopeType) : undefined}
allowControl={showAllowColumn ?
<ToggleSwitch
checked={localAllowsLower(field.key)}
disabled={disabled || fieldLocked}
onChange={(checked) => setAllowLowerLevelLimit(field.key, checked)}
label="i18n:govoplan-core.allow_override.ffa6e9a0" /> :
undefined} />);
})}
</div>
</section>
</PolicyTable>
</PolicySection>
</div>
</Card>
);
</LoadingFrame>
</Card>);
}
function policyDraftKey(policy: PrivacyRetentionPolicyPatch, isSystem: boolean): string {
return JSON.stringify(isSystem ? normalizeFullPolicy(policy) : normalizePatch(policy));
}
type PolicySourceItem = {
@@ -362,16 +393,16 @@ function retentionPolicyPathHelp(field: FieldDefinition, sources: PolicySourcePa
return <PolicyPathHelp lines={retentionPolicyPathLines(field, items)} />;
}
function PolicyPathHelp({ lines }: { lines: string[] }) {
function PolicyPathHelp({ lines }: {lines: string[];}) {
return (
<span className="policy-path-help">
{lines.map((line, index) => <span className="policy-path-help-line" key={`${line}-${index}`}>{line}</span>)}
</span>
);
</span>);
}
function retentionPolicyPathLines(field: FieldDefinition, items: PolicySourceItem[]): string[] {
if (items.length === 0) return ["System: Default"];
if (items.length === 0) return ["i18n:govoplan-core.system_default.1f06f3ed"];
const lines: string[] = [];
for (const [index, item] of items.entries()) {
const sourcePolicy = asRecord(item.policy);
@@ -384,18 +415,18 @@ function retentionPolicyPathLines(field: FieldDefinition, items: PolicySourceIte
}
function retentionSourceValue(field: FieldDefinition, sourcePolicy: Record<string, unknown>, isSystem: boolean): string {
if (Object.keys(sourcePolicy).length === 0) return isSystem ? "Default" : "Inherit";
if (!isSystem && !(field.key in sourcePolicy)) return "Inherit";
if (Object.keys(sourcePolicy).length === 0) return isSystem ? "i18n:govoplan-core.default.808d7dca" : "i18n:govoplan-core.inherit.18f99833";
if (!isSystem && !(field.key in sourcePolicy)) return "i18n:govoplan-core.inherit.18f99833";
if (field.kind === "raw-json") {
const value = sourcePolicy.store_raw_campaign_json;
return value === false ? "Disabled" : value === true ? "Stored" : "Inherit";
return value === false ? "i18n:govoplan-core.disabled.f4f4473d" : value === true ? "i18n:govoplan-core.stored.b2b53ee4" : "i18n:govoplan-core.inherit.18f99833";
}
if (field.kind === "audit") {
const value = sourcePolicy.audit_detail_level;
return value === "full" || value === "redacted" || value === "minimal" ? auditDetailLabel(value) : "Inherit";
return value === "full" || value === "redacted" || value === "minimal" ? auditDetailLabel(value) : "i18n:govoplan-core.inherit.18f99833";
}
const value = sourcePolicy[field.key];
return typeof value === "number" ? daysLabel(value) : value === null && isSystem ? daysLabel(null) : "Inherit";
return typeof value === "number" ? daysLabel(value) : value === null && isSystem ? daysLabel(null) : "i18n:govoplan-core.inherit.18f99833";
}
function normalizePolicySourceItems(items: PolicySourcePathItem[]): PolicySourceItem[] {
@@ -414,23 +445,23 @@ function policyPathPrefix(index: number): string {
}
function retentionFieldValue(field: FieldDefinition, policy: PrivacyRetentionPolicy | null, loading: boolean): string {
if (!policy) return loading ? "Loading..." : "Unavailable";
if (field.kind === "raw-json") return policy.store_raw_campaign_json ? "Stored" : "Disabled";
if (!policy) return loading ? "i18n:govoplan-core.loading.b04ba49f" : "i18n:govoplan-core.unavailable.2c9c1f79";
if (field.kind === "raw-json") return policy.store_raw_campaign_json ? "i18n:govoplan-core.stored.b2b53ee4" : "i18n:govoplan-core.disabled.f4f4473d";
if (field.kind === "audit") return auditDetailLabel(policy.audit_detail_level);
return daysLabel(policy[field.key as DayKey]);
}
function renderFieldControl(
field: FieldDefinition,
fullPolicy: PrivacyRetentionPolicy,
patchPolicy: PrivacyRetentionPolicyPatch,
isSystem: boolean,
disabled: boolean,
setRawJson: (value: RawJsonValue | "store" | "disabled") => void,
setRetentionDays: (key: DayKey, value: string) => void,
setAuditDetail: (value: AuditDetailValue) => void,
parentPolicy: PrivacyRetentionPolicy | null
) {
field: FieldDefinition,
fullPolicy: PrivacyRetentionPolicy,
patchPolicy: PrivacyRetentionPolicyPatch,
isSystem: boolean,
disabled: boolean,
setRawJson: (value: RawJsonValue | "store" | "disabled") => void,
setRetentionDays: (key: DayKey, value: string) => void,
setAuditDetail: (value: AuditDetailValue) => void,
parentPolicy: PrivacyRetentionPolicy | null)
{
if (field.kind === "raw-json") {
if (isSystem) {
return <RawJsonSystemSelect value={fullPolicy.store_raw_campaign_json ? "store" : "disabled"} disabled={disabled} onChange={setRawJson} />;
@@ -445,33 +476,33 @@ function renderFieldControl(
<RetentionDaysField
value={isSystem ? fullPolicy[field.key as DayKey] : patchPolicy[field.key as DayKey]}
disabled={disabled}
placeholder={isSystem ? "Unlimited" : "Inherit"}
placeholder={isSystem ? "i18n:govoplan-core.unlimited.b8bef37b" : "i18n:govoplan-core.inherit.18f99833"}
max={parentDayLimit}
onChange={(value) => setRetentionDays(field.key as DayKey, value)}
/>
);
onChange={(value) => setRetentionDays(field.key as DayKey, value)} />);
}
function RawJsonSystemSelect({ value, disabled, onChange }: { value: "store" | "disabled"; disabled: boolean; onChange: (value: "store" | "disabled") => void }) {
function RawJsonSystemSelect({ value, disabled, onChange }: {value: "store" | "disabled";disabled: boolean;onChange: (value: "store" | "disabled") => void;}) {
return (
<select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as "store" | "disabled")}>
<option value="store">Store</option>
<option value="disabled">Disable storage</option>
</select>
);
<option value="store">i18n:govoplan-core.store.0d8a7046</option>
<option value="disabled">i18n:govoplan-core.disable_storage.07c44d8b</option>
</select>);
}
function RawJsonScopedSelect({ value, parentStoresRawJson, disabled, onChange }: { value: RawJsonValue; parentStoresRawJson: boolean; disabled: boolean; onChange: (value: RawJsonValue) => void }) {
function RawJsonScopedSelect({ value, parentStoresRawJson, disabled, onChange }: {value: RawJsonValue;parentStoresRawJson: boolean;disabled: boolean;onChange: (value: RawJsonValue) => void;}) {
return (
<select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as RawJsonValue)}>
<option value="inherit">Inherit</option>
<option value="keep" disabled={!parentStoresRawJson}>Store if parent allows</option>
<option value="disable">Disable storage</option>
</select>
);
<option value="inherit">i18n:govoplan-core.inherit.18f99833</option>
<option value="keep" disabled={!parentStoresRawJson}>i18n:govoplan-core.store_if_parent_allows.36dcb07b</option>
<option value="disable">i18n:govoplan-core.disable_storage.07c44d8b</option>
</select>);
}
function RetentionDaysField({ value, disabled, placeholder, max, onChange }: { value?: number | null; disabled: boolean; placeholder: string; max?: number | null; onChange: (value: string) => void }) {
function RetentionDaysField({ value, disabled, placeholder, max, onChange }: {value?: number | null;disabled: boolean;placeholder: string;max?: number | null;onChange: (value: string) => void;}) {
function handleChange(nextValue: string) {
const trimmed = nextValue.trim();
if (trimmed === "" || max === null || max === undefined) {
@@ -485,7 +516,7 @@ function RetentionDaysField({ value, disabled, placeholder, max, onChange }: { v
return <input type="number" min={0} max={max ?? undefined} value={value ?? ""} disabled={disabled} placeholder={placeholder} onChange={(event) => handleChange(event.target.value)} />;
}
function AuditDetailSelect({ value, includeInherit, parentValue, disabled, onChange }: { value: AuditDetailValue; includeInherit: boolean; parentValue?: PrivacyRetentionPolicy["audit_detail_level"] | null; disabled: boolean; onChange: (value: AuditDetailValue) => void }) {
function AuditDetailSelect({ value, includeInherit, parentValue, disabled, onChange }: {value: AuditDetailValue;includeInherit: boolean;parentValue?: PrivacyRetentionPolicy["audit_detail_level"] | null;disabled: boolean;onChange: (value: AuditDetailValue) => void;}) {
const minimumOrder = parentValue ? auditDetailOrder[parentValue] : 0;
function optionDisabled(option: PrivacyRetentionPolicy["audit_detail_level"]): boolean {
return auditDetailOrder[option] < minimumOrder;
@@ -493,12 +524,12 @@ function AuditDetailSelect({ value, includeInherit, parentValue, disabled, onCha
return (
<select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as AuditDetailValue)}>
{includeInherit && <option value="inherit">Inherit</option>}
<option value="full" disabled={optionDisabled("full")}>Full</option>
<option value="redacted" disabled={optionDisabled("redacted")}>Redacted</option>
<option value="minimal" disabled={optionDisabled("minimal")}>Minimal</option>
</select>
);
{includeInherit && <option value="inherit">i18n:govoplan-core.inherit.18f99833</option>}
<option value="full" disabled={optionDisabled("full")}>i18n:govoplan-core.full.10b28a8c</option>
<option value="redacted" disabled={optionDisabled("redacted")}>i18n:govoplan-core.redacted.8780785a</option>
<option value="minimal" disabled={optionDisabled("minimal")}>i18n:govoplan-core.minimal.a711cca9</option>
</select>);
}
function rawJsonValue(value: boolean | undefined): RawJsonValue {
@@ -511,13 +542,13 @@ function auditDetailValue(value: PrivacyRetentionPolicyPatch["audit_detail_level
}
function daysLabel(value?: number | null): string {
return value === null || value === undefined ? "Unlimited" : `${value} day${value === 1 ? "" : "s"}`;
return value === null || value === undefined ? "i18n:govoplan-core.unlimited.b8bef37b" : `${value} day${value === 1 ? "" : "s"}`;
}
function auditDetailLabel(value: PrivacyRetentionPolicy["audit_detail_level"]): string {
if (value === "full") return "Full";
if (value === "redacted") return "Redacted";
return "Minimal";
if (value === "full") return "i18n:govoplan-core.full.10b28a8c";
if (value === "redacted") return "i18n:govoplan-core.redacted.8780785a";
return "i18n:govoplan-core.minimal.a711cca9";
}
function localPolicyCount(policy: PrivacyRetentionPolicyPatch): string {
@@ -528,15 +559,15 @@ function localPolicyCount(policy: PrivacyRetentionPolicyPatch): string {
}
const allowCount = Object.keys(policy.allow_lower_level_limits ?? {}).length;
count += allowCount;
return count === 0 ? "Fully inherited" : `${count} local override${count === 1 ? "" : "s"}`;
return count === 0 ? "i18n:govoplan-core.fully_inherited.5e48132a" : `${count} local override${count === 1 ? "" : "s"}`;
}
function retentionPolicySourcePath(scopeType: PrivacyRetentionPolicyScope): string[] {
if (scopeType === "system") return ["System"];
if (scopeType === "tenant") return ["System", "Tenant"];
if (scopeType === "user") return ["System", "Tenant", "User"];
if (scopeType === "group") return ["System", "Tenant", "Group"];
return ["System", "Tenant", "Owner policy", "Campaign"];
if (scopeType === "system") return ["i18n:govoplan-core.system.bc0792d8"];
if (scopeType === "tenant") return ["i18n:govoplan-core.system.bc0792d8", "i18n:govoplan-core.tenant.3ca93c78"];
if (scopeType === "user") return ["i18n:govoplan-core.system.bc0792d8", "i18n:govoplan-core.tenant.3ca93c78", "i18n:govoplan-core.user.9f8a2389"];
if (scopeType === "group") return ["i18n:govoplan-core.system.bc0792d8", "i18n:govoplan-core.tenant.3ca93c78", "i18n:govoplan-core.group.171a0606"];
return ["i18n:govoplan-core.system.bc0792d8", "i18n:govoplan-core.tenant.3ca93c78", "i18n:govoplan-core.owner_policy.1e8df143", "i18n:govoplan-core.campaign.69390e16"];
}
function normalizeAllowLimits(value: PrivacyRetentionLimitPermissionPatch | PrivacyRetentionLimitPermissions | null | undefined): PrivacyRetentionLimitPermissions {
@@ -581,12 +612,12 @@ function normalizePatchForSave(policy: PrivacyRetentionPolicyPatch, parentPolicy
delete allowPatch[field.key];
}
}
if (Object.keys(allowPatch).length > 0) normalized.allow_lower_level_limits = allowPatch;
else delete normalized.allow_lower_level_limits;
if (Object.keys(allowPatch).length > 0) normalized.allow_lower_level_limits = allowPatch;else
delete normalized.allow_lower_level_limits;
return normalized;
}
function errorMessage(err: unknown): string {
if (err instanceof Error) return err.message;
return "Request failed";
return "i18n:govoplan-core.request_failed.9fcda32c";
}