feat(quick-access): expose effective preference provenance
This commit is contained in:
@@ -25,6 +25,12 @@ override that decision, but may configure any still-available item. Stale
|
|||||||
preferences are retained and diagnosed so uninstalling and reinstalling a
|
preferences are retained and diagnosed so uninstalling and reinstalling a
|
||||||
contributing module does not silently discard a user's arrangement.
|
contributing module does not silently discard a user's arrangement.
|
||||||
|
|
||||||
|
The effective API reports each item's `availability_state`,
|
||||||
|
`availability_source`, and `order_source`. It also returns every unavailable
|
||||||
|
stored id as a `stale_preferences` entry with its system, tenant, or user
|
||||||
|
source. Administration and support tooling can therefore explain a result
|
||||||
|
without reading or copying any contributing module's state.
|
||||||
|
|
||||||
## Categories
|
## Categories
|
||||||
|
|
||||||
The initial stable categories are Work, Calendar, Messages, and Files. Messages
|
The initial stable categories are Work, Calendar, Messages, and Files. Messages
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ DOCUMENTATION = (
|
|||||||
body=(
|
body=(
|
||||||
"The catalogue follows installed module registrations. System settings constrain tenants; "
|
"The catalogue follows installed module registrations. System settings constrain tenants; "
|
||||||
"tenant settings constrain users. An item may remain available, be blocked, or be forced. "
|
"tenant settings constrain users. An item may remain available, be blocked, or be forced. "
|
||||||
|
"Effective entries identify the system, tenant, user, or module source of availability and ordering; "
|
||||||
|
"preferences for retired entries retain their scope provenance. "
|
||||||
"Views and permissions form additional ceilings and Quick Access never grants access to domain data. "
|
"Views and permissions form additional ceilings and Quick Access never grants access to domain data. "
|
||||||
"A View may recommend tools or focus the rail to a subset, but only currently enabled, context-compatible, authorized "
|
"A View may recommend tools or focus the rail to a subset, but only currently enabled, context-compatible, authorized "
|
||||||
"tools participate. The All available tools escape only restores that permission-derived set for the current session; "
|
"tools participate. The All available tools escape only restores that permission-derived set for the current session; "
|
||||||
@@ -191,6 +193,8 @@ DOCUMENTATION = (
|
|||||||
"body": (
|
"body": (
|
||||||
"Der Katalog folgt den Registrierungen installierter Module. Systemeinstellungen begrenzen Mandanten, "
|
"Der Katalog folgt den Registrierungen installierter Module. Systemeinstellungen begrenzen Mandanten, "
|
||||||
"Mandanteneinstellungen begrenzen Benutzer. Ein Eintrag kann verfuegbar, gesperrt oder erzwungen sein. "
|
"Mandanteneinstellungen begrenzen Benutzer. Ein Eintrag kann verfuegbar, gesperrt oder erzwungen sein. "
|
||||||
|
"Effektive Eintraege nennen System, Mandant, Benutzer oder Modul als Quelle fuer Verfuegbarkeit und Reihenfolge; "
|
||||||
|
"Einstellungen fuer entfernte Eintraege behalten ihren Ebenennachweis. "
|
||||||
"Ansichten und Berechtigungen bilden weitere Grenzen; Schnellzugriff erteilt selbst keinen Datenzugriff. "
|
"Ansichten und Berechtigungen bilden weitere Grenzen; Schnellzugriff erteilt selbst keinen Datenzugriff. "
|
||||||
"Eine Ansicht darf Werkzeuge empfehlen oder die Leiste auf eine Teilmenge fokussieren, jedoch nur innerhalb "
|
"Eine Ansicht darf Werkzeuge empfehlen oder die Leiste auf eine Teilmenge fokussieren, jedoch nur innerhalb "
|
||||||
"der aktivierten, kontextgeeigneten und berechtigten Werkzeuge. Alle verfuegbaren Werkzeuge stellt nur diese "
|
"der aktivierten, kontextgeeigneten und berechtigten Werkzeuge. Alle verfuegbaren Werkzeuge stellt nur diese "
|
||||||
|
|||||||
@@ -71,17 +71,30 @@ class EffectiveToolResponse(CatalogueToolResponse):
|
|||||||
enabled: bool
|
enabled: bool
|
||||||
forced: bool
|
forced: bool
|
||||||
locked_by: str | None = None
|
locked_by: str | None = None
|
||||||
|
availability_state: Literal["available", "forced", "blocked"]
|
||||||
|
availability_source: Literal["module", "system", "tenant", "user"]
|
||||||
|
order_source: Literal["module", "system", "tenant", "user"]
|
||||||
|
|
||||||
|
|
||||||
class EffectiveCategoryResponse(CatalogueCategoryResponse):
|
class EffectiveCategoryResponse(CatalogueCategoryResponse):
|
||||||
enabled: bool
|
enabled: bool
|
||||||
forced: bool
|
forced: bool
|
||||||
locked_by: str | None = None
|
locked_by: str | None = None
|
||||||
|
availability_state: Literal["available", "forced", "blocked"]
|
||||||
|
availability_source: Literal["module", "system", "tenant", "user"]
|
||||||
|
order_source: Literal["module", "system", "tenant", "user"]
|
||||||
tools: list[EffectiveToolResponse]
|
tools: list[EffectiveToolResponse]
|
||||||
|
|
||||||
|
|
||||||
|
class EffectiveStalePreferenceResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
kind: Literal["category", "tool"]
|
||||||
|
source: Literal["system", "tenant", "user"]
|
||||||
|
|
||||||
|
|
||||||
class EffectiveQuickAccessResponse(BaseModel):
|
class EffectiveQuickAccessResponse(BaseModel):
|
||||||
categories: list[EffectiveCategoryResponse]
|
categories: list[EffectiveCategoryResponse]
|
||||||
|
stale_preferences: list[EffectiveStalePreferenceResponse] = Field(default_factory=list)
|
||||||
diagnostics: list[str] = Field(default_factory=list)
|
diagnostics: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from govoplan_quick_access.backend.schemas import (
|
|||||||
CatalogueToolResponse,
|
CatalogueToolResponse,
|
||||||
EffectiveCategoryResponse,
|
EffectiveCategoryResponse,
|
||||||
EffectiveQuickAccessResponse,
|
EffectiveQuickAccessResponse,
|
||||||
|
EffectiveStalePreferenceResponse,
|
||||||
EffectiveToolResponse,
|
EffectiveToolResponse,
|
||||||
PreferenceEntry,
|
PreferenceEntry,
|
||||||
ProfileResponse,
|
ProfileResponse,
|
||||||
@@ -241,6 +242,7 @@ def resolve_effective(
|
|||||||
("user", get_profile(session, scope_type="user", tenant_id=tenant_id, scope_id=account_id)),
|
("user", get_profile(session, scope_type="user", tenant_id=tenant_id, scope_id=account_id)),
|
||||||
)
|
)
|
||||||
diagnostics: list[str] = []
|
diagnostics: list[str] = []
|
||||||
|
stale_preferences = _stale_preferences(profiles, catalogue)
|
||||||
category_states: dict[str, _EffectiveState] = {}
|
category_states: dict[str, _EffectiveState] = {}
|
||||||
for category in catalogue.categories:
|
for category in catalogue.categories:
|
||||||
state = _EffectiveState(enabled=True, order=category.order)
|
state = _EffectiveState(enabled=True, order=category.order)
|
||||||
@@ -259,6 +261,11 @@ def resolve_effective(
|
|||||||
state.apply(entry, source=source)
|
state.apply(entry, source=source)
|
||||||
category_state = category_states.get(tool.category_id)
|
category_state = category_states.get(tool.category_id)
|
||||||
enabled = state.enabled and bool(category_state and category_state.enabled)
|
enabled = state.enabled and bool(category_state and category_state.enabled)
|
||||||
|
availability_source = (
|
||||||
|
category_state.availability_source
|
||||||
|
if state.enabled and category_state and not category_state.enabled
|
||||||
|
else state.availability_source
|
||||||
|
)
|
||||||
tool_payload = tool.model_dump()
|
tool_payload = tool.model_dump()
|
||||||
tool_payload["order"] = state.order
|
tool_payload["order"] = state.order
|
||||||
tools_by_category.setdefault(tool.category_id, []).append(
|
tools_by_category.setdefault(tool.category_id, []).append(
|
||||||
@@ -267,6 +274,9 @@ def resolve_effective(
|
|||||||
enabled=enabled,
|
enabled=enabled,
|
||||||
forced=state.forced,
|
forced=state.forced,
|
||||||
locked_by=state.locked_by,
|
locked_by=state.locked_by,
|
||||||
|
availability_state=_availability_state(enabled, state.forced),
|
||||||
|
availability_source=availability_source,
|
||||||
|
order_source=state.order_source,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -286,12 +296,16 @@ def resolve_effective(
|
|||||||
enabled=enabled,
|
enabled=enabled,
|
||||||
forced=state.forced,
|
forced=state.forced,
|
||||||
locked_by=state.locked_by,
|
locked_by=state.locked_by,
|
||||||
|
availability_state=_availability_state(enabled, state.forced),
|
||||||
|
availability_source=state.availability_source,
|
||||||
|
order_source=state.order_source,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
categories.sort(key=lambda item: (item.order, item.id))
|
categories.sort(key=lambda item: (item.order, item.id))
|
||||||
return EffectiveQuickAccessResponse(
|
return EffectiveQuickAccessResponse(
|
||||||
categories=categories,
|
categories=categories,
|
||||||
|
stale_preferences=stale_preferences,
|
||||||
diagnostics=list(dict.fromkeys(diagnostics)),
|
diagnostics=list(dict.fromkeys(diagnostics)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -302,16 +316,20 @@ class _EffectiveState:
|
|||||||
order: int
|
order: int
|
||||||
forced: bool = False
|
forced: bool = False
|
||||||
locked_by: str | None = None
|
locked_by: str | None = None
|
||||||
|
availability_source: str = "module"
|
||||||
|
order_source: str = "module"
|
||||||
|
|
||||||
def apply(self, entry: PreferenceEntry | None, *, source: str) -> None:
|
def apply(self, entry: PreferenceEntry | None, *, source: str) -> None:
|
||||||
if entry is None:
|
if entry is None:
|
||||||
return
|
return
|
||||||
if entry.order is not None:
|
if entry.order is not None:
|
||||||
self.order = entry.order
|
self.order = entry.order
|
||||||
|
self.order_source = source
|
||||||
if self.locked_by is not None:
|
if self.locked_by is not None:
|
||||||
return
|
return
|
||||||
if entry.enabled is not None:
|
if entry.enabled is not None:
|
||||||
self.enabled = entry.enabled
|
self.enabled = entry.enabled
|
||||||
|
self.availability_source = source
|
||||||
if source != "user" and entry.enabled is False:
|
if source != "user" and entry.enabled is False:
|
||||||
self.forced = False
|
self.forced = False
|
||||||
self.locked_by = source
|
self.locked_by = source
|
||||||
@@ -319,6 +337,38 @@ class _EffectiveState:
|
|||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.forced = True
|
self.forced = True
|
||||||
self.locked_by = source
|
self.locked_by = source
|
||||||
|
self.availability_source = source
|
||||||
|
|
||||||
|
|
||||||
|
def _availability_state(enabled: bool, forced: bool) -> str:
|
||||||
|
if not enabled:
|
||||||
|
return "blocked"
|
||||||
|
return "forced" if forced else "available"
|
||||||
|
|
||||||
|
|
||||||
|
def _stale_preferences(
|
||||||
|
profiles: tuple[tuple[str, QuickAccessProfile | None], ...],
|
||||||
|
catalogue: CatalogueResponse,
|
||||||
|
) -> list[EffectiveStalePreferenceResponse]:
|
||||||
|
category_ids = {item.id for item in catalogue.categories}
|
||||||
|
tool_ids = {item.id for item in catalogue.tools}
|
||||||
|
stale: list[EffectiveStalePreferenceResponse] = []
|
||||||
|
for source, profile in profiles:
|
||||||
|
if profile is None:
|
||||||
|
continue
|
||||||
|
for kind, field, known_ids in (
|
||||||
|
("category", "category_preferences", category_ids),
|
||||||
|
("tool", "tool_preferences", tool_ids),
|
||||||
|
):
|
||||||
|
values = getattr(profile, field, {})
|
||||||
|
if not isinstance(values, Mapping):
|
||||||
|
continue
|
||||||
|
stale.extend(
|
||||||
|
EffectiveStalePreferenceResponse(id=str(item_id), kind=kind, source=source)
|
||||||
|
for item_id in values
|
||||||
|
if str(item_id) not in known_ids
|
||||||
|
)
|
||||||
|
return sorted(stale, key=lambda item: (item.source, item.kind, item.id))
|
||||||
|
|
||||||
|
|
||||||
def _profile_entry(
|
def _profile_entry(
|
||||||
|
|||||||
@@ -136,6 +136,91 @@ class QuickAccessTests(unittest.TestCase):
|
|||||||
self.assertTrue(work.enabled)
|
self.assertTrue(work.enabled)
|
||||||
self.assertTrue(work.forced)
|
self.assertTrue(work.forced)
|
||||||
self.assertEqual("tenant", work.locked_by)
|
self.assertEqual("tenant", work.locked_by)
|
||||||
|
self.assertEqual("forced", work.availability_state)
|
||||||
|
self.assertEqual("tenant", work.availability_source)
|
||||||
|
|
||||||
|
def test_effective_resolution_reports_availability_and_order_provenance(self) -> None:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.add_all(
|
||||||
|
(
|
||||||
|
QuickAccessProfile(
|
||||||
|
scope_type="system",
|
||||||
|
tenant_id=None,
|
||||||
|
scope_id=None,
|
||||||
|
scope_key="system:*",
|
||||||
|
category_preferences={},
|
||||||
|
tool_preferences={"example.work": {"enabled": True, "order": 40}},
|
||||||
|
revision=2,
|
||||||
|
),
|
||||||
|
QuickAccessProfile(
|
||||||
|
scope_type="user",
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
scope_id="account-1",
|
||||||
|
scope_key="user:tenant-1:account-1",
|
||||||
|
category_preferences={},
|
||||||
|
tool_preferences={"example.work": {"order": 5}},
|
||||||
|
revision=2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
effective = resolve_effective(
|
||||||
|
session,
|
||||||
|
registry=registry_with_tools(),
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
account_id="account-1",
|
||||||
|
permission_checker=lambda _scope: True,
|
||||||
|
)
|
||||||
|
|
||||||
|
tool = next(item for item in effective.categories if item.id == "work").tools[0]
|
||||||
|
self.assertEqual("available", tool.availability_state)
|
||||||
|
self.assertEqual("system", tool.availability_source)
|
||||||
|
self.assertEqual("user", tool.order_source)
|
||||||
|
self.assertEqual(5, tool.order)
|
||||||
|
|
||||||
|
def test_effective_resolution_retains_stale_preferences_with_scope_provenance(self) -> None:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.add_all(
|
||||||
|
(
|
||||||
|
QuickAccessProfile(
|
||||||
|
scope_type="tenant",
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
scope_id="tenant-1",
|
||||||
|
scope_key="tenant:tenant-1",
|
||||||
|
category_preferences={"retired.category": {"enabled": False}},
|
||||||
|
tool_preferences={"retired.tool": {"order": 5}},
|
||||||
|
revision=2,
|
||||||
|
),
|
||||||
|
QuickAccessProfile(
|
||||||
|
scope_type="user",
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
scope_id="account-1",
|
||||||
|
scope_key="user:tenant-1:account-1",
|
||||||
|
category_preferences={},
|
||||||
|
tool_preferences={"missing.user-tool": {"enabled": True}},
|
||||||
|
revision=2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
effective = resolve_effective(
|
||||||
|
session,
|
||||||
|
registry=registry_with_tools(),
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
account_id="account-1",
|
||||||
|
permission_checker=lambda _scope: True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
("retired.category", "category", "tenant"),
|
||||||
|
("retired.tool", "tool", "tenant"),
|
||||||
|
("missing.user-tool", "tool", "user"),
|
||||||
|
],
|
||||||
|
[(item.id, item.kind, item.source) for item in effective.stale_preferences],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -56,17 +56,28 @@ export type EffectiveQuickAccessTool = QuickAccessTool & {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
forced: boolean;
|
forced: boolean;
|
||||||
locked_by?: string | null;
|
locked_by?: string | null;
|
||||||
|
availability_state: "available" | "forced" | "blocked";
|
||||||
|
availability_source: "module" | "system" | "tenant" | "user";
|
||||||
|
order_source: "module" | "system" | "tenant" | "user";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EffectiveQuickAccessCategory = QuickAccessCategory & {
|
export type EffectiveQuickAccessCategory = QuickAccessCategory & {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
forced: boolean;
|
forced: boolean;
|
||||||
locked_by?: string | null;
|
locked_by?: string | null;
|
||||||
|
availability_state: "available" | "forced" | "blocked";
|
||||||
|
availability_source: "module" | "system" | "tenant" | "user";
|
||||||
|
order_source: "module" | "system" | "tenant" | "user";
|
||||||
tools: EffectiveQuickAccessTool[];
|
tools: EffectiveQuickAccessTool[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EffectiveQuickAccess = {
|
export type EffectiveQuickAccess = {
|
||||||
categories: EffectiveQuickAccessCategory[];
|
categories: EffectiveQuickAccessCategory[];
|
||||||
|
stale_preferences: Array<{
|
||||||
|
id: string;
|
||||||
|
kind: "category" | "tool";
|
||||||
|
source: "system" | "tenant" | "user";
|
||||||
|
}>;
|
||||||
diagnostics: string[];
|
diagnostics: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ type Draft = {
|
|||||||
tools: Record<string, QuickAccessPreference>;
|
tools: Record<string, QuickAccessPreference>;
|
||||||
};
|
};
|
||||||
type AvailabilityMode = "inherit" | "available" | "blocked" | "forced";
|
type AvailabilityMode = "inherit" | "available" | "blocked" | "forced";
|
||||||
|
type EffectiveProvenance = {
|
||||||
|
state: "available" | "forced" | "blocked";
|
||||||
|
availabilitySource: "module" | "system" | "tenant" | "user";
|
||||||
|
orderSource: "module" | "system" | "tenant" | "user";
|
||||||
|
};
|
||||||
|
|
||||||
const EMPTY_DRAFT: Draft = { categories: {}, tools: {} };
|
const EMPTY_DRAFT: Draft = { categories: {}, tools: {} };
|
||||||
|
|
||||||
@@ -186,6 +191,14 @@ export default function QuickAccessSettingsPanel({
|
|||||||
const lockedTools = new Map(
|
const lockedTools = new Map(
|
||||||
(effective?.categories ?? []).flatMap((category) => category.tools).filter((item) => item.locked_by).map((item) => [item.id, item.locked_by])
|
(effective?.categories ?? []).flatMap((category) => category.tools).filter((item) => item.locked_by).map((item) => [item.id, item.locked_by])
|
||||||
);
|
);
|
||||||
|
const categoryProvenance = new Map(
|
||||||
|
(effective?.categories ?? []).map((item) => [item.id, effectiveProvenance(item)])
|
||||||
|
);
|
||||||
|
const toolProvenance = new Map(
|
||||||
|
(effective?.categories ?? []).flatMap((category) =>
|
||||||
|
category.tools.map((item) => [item.id, effectiveProvenance(item)] as const)
|
||||||
|
)
|
||||||
|
);
|
||||||
const categoryIds = categories.map((item) => item.id);
|
const categoryIds = categories.map((item) => item.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -219,6 +232,7 @@ export default function QuickAccessSettingsPanel({
|
|||||||
preference={draft.categories[category.id]}
|
preference={draft.categories[category.id]}
|
||||||
isPersonal={isPersonal}
|
isPersonal={isPersonal}
|
||||||
lockedBy={editableConstraintSource(scope, lockedCategories.get(category.id))}
|
lockedBy={editableConstraintSource(scope, lockedCategories.get(category.id))}
|
||||||
|
provenance={categoryProvenance.get(category.id)}
|
||||||
disabled={!canWrite || saving}
|
disabled={!canWrite || saving}
|
||||||
onChange={(value) => setPreference("categories", category.id, value)}
|
onChange={(value) => setPreference("categories", category.id, value)}
|
||||||
onMoveUp={() => move("categories", categoryIds, category.id, -1)}
|
onMoveUp={() => move("categories", categoryIds, category.id, -1)}
|
||||||
@@ -243,6 +257,7 @@ export default function QuickAccessSettingsPanel({
|
|||||||
defaultEnabled={tool.default_enabled}
|
defaultEnabled={tool.default_enabled}
|
||||||
isPersonal={isPersonal}
|
isPersonal={isPersonal}
|
||||||
lockedBy={editableConstraintSource(scope, lockedTools.get(tool.id))}
|
lockedBy={editableConstraintSource(scope, lockedTools.get(tool.id))}
|
||||||
|
provenance={toolProvenance.get(tool.id)}
|
||||||
disabled={!canWrite || saving}
|
disabled={!canWrite || saving}
|
||||||
onChange={(value) => setPreference("tools", tool.id, value)}
|
onChange={(value) => setPreference("tools", tool.id, value)}
|
||||||
onMoveUp={() => move("tools", toolIds, tool.id, -1)}
|
onMoveUp={() => move("tools", toolIds, tool.id, -1)}
|
||||||
@@ -268,6 +283,7 @@ function PreferenceRow({
|
|||||||
defaultEnabled = true,
|
defaultEnabled = true,
|
||||||
isPersonal,
|
isPersonal,
|
||||||
lockedBy,
|
lockedBy,
|
||||||
|
provenance,
|
||||||
disabled,
|
disabled,
|
||||||
onChange,
|
onChange,
|
||||||
onMoveUp,
|
onMoveUp,
|
||||||
@@ -282,6 +298,7 @@ function PreferenceRow({
|
|||||||
defaultEnabled?: boolean;
|
defaultEnabled?: boolean;
|
||||||
isPersonal: boolean;
|
isPersonal: boolean;
|
||||||
lockedBy?: string | null;
|
lockedBy?: string | null;
|
||||||
|
provenance?: EffectiveProvenance;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
onChange: (value: QuickAccessPreference | null) => void;
|
onChange: (value: QuickAccessPreference | null) => void;
|
||||||
onMoveUp: () => void;
|
onMoveUp: () => void;
|
||||||
@@ -303,6 +320,15 @@ function PreferenceRow({
|
|||||||
})}
|
})}
|
||||||
</small>
|
</small>
|
||||||
) : null}
|
) : null}
|
||||||
|
{provenance ? (
|
||||||
|
<small>
|
||||||
|
{i18nMessage("i18n:govoplan-quick-access.effective_provenance", {
|
||||||
|
value0: provenance.state,
|
||||||
|
value1: provenance.availabilitySource,
|
||||||
|
value2: provenance.orderSource
|
||||||
|
})}
|
||||||
|
</small>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="quick-access-preference-controls">
|
<div className="quick-access-preference-controls">
|
||||||
{isPersonal ? (
|
{isPersonal ? (
|
||||||
@@ -338,6 +364,18 @@ function PreferenceRow({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function effectiveProvenance(item: {
|
||||||
|
availability_state: "available" | "forced" | "blocked";
|
||||||
|
availability_source: "module" | "system" | "tenant" | "user";
|
||||||
|
order_source: "module" | "system" | "tenant" | "user";
|
||||||
|
}): EffectiveProvenance {
|
||||||
|
return {
|
||||||
|
state: item.availability_state,
|
||||||
|
availabilitySource: item.availability_source,
|
||||||
|
orderSource: item.order_source
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function preferenceMode(preference?: QuickAccessPreference): AvailabilityMode {
|
function preferenceMode(preference?: QuickAccessPreference): AvailabilityMode {
|
||||||
if (!preference || preference.enabled === null || preference.enabled === undefined) return "inherit";
|
if (!preference || preference.enabled === null || preference.enabled === undefined) return "inherit";
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export const generatedTranslations: PlatformTranslations = {
|
|||||||
"i18n:govoplan-quick-access.category.messages_description": "Mail, Postfach und künftige Gesprächskanäle in einer Einblendung.",
|
"i18n:govoplan-quick-access.category.messages_description": "Mail, Postfach und künftige Gesprächskanäle in einer Einblendung.",
|
||||||
"i18n:govoplan-quick-access.category.files": "Dateien",
|
"i18n:govoplan-quick-access.category.files": "Dateien",
|
||||||
"i18n:govoplan-quick-access.category.files_description": "Aktuelle und kontextbezogene Dateien, ohne die laufende Aufgabe zu verlassen.",
|
"i18n:govoplan-quick-access.category.files_description": "Aktuelle und kontextbezogene Dateien, ohne die laufende Aufgabe zu verlassen.",
|
||||||
"i18n:govoplan-quick-access.locked_by_value": "Durch {value0} festgelegt"
|
"i18n:govoplan-quick-access.locked_by_value": "Durch {value0} festgelegt",
|
||||||
|
"i18n:govoplan-quick-access.effective_provenance": "Effektiv {value0}; Verfügbarkeit von {value1}, Reihenfolge von {value2}"
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
"i18n:govoplan-quick-access.quick_access": "Quick Access",
|
"i18n:govoplan-quick-access.quick_access": "Quick Access",
|
||||||
@@ -89,6 +90,7 @@ export const generatedTranslations: PlatformTranslations = {
|
|||||||
"i18n:govoplan-quick-access.category.messages_description": "Mail, Postbox, and future conversational channels in one overlay.",
|
"i18n:govoplan-quick-access.category.messages_description": "Mail, Postbox, and future conversational channels in one overlay.",
|
||||||
"i18n:govoplan-quick-access.category.files": "Files",
|
"i18n:govoplan-quick-access.category.files": "Files",
|
||||||
"i18n:govoplan-quick-access.category.files_description": "Recent and contextual files without leaving the current task.",
|
"i18n:govoplan-quick-access.category.files_description": "Recent and contextual files without leaving the current task.",
|
||||||
"i18n:govoplan-quick-access.locked_by_value": "Set by {value0}"
|
"i18n:govoplan-quick-access.locked_by_value": "Set by {value0}",
|
||||||
|
"i18n:govoplan-quick-access.effective_provenance": "Effective {value0}; availability from {value1}, order from {value2}"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user