@gooddata/sdk-ui-ext 11.52.0-alpha.6 → 11.52.0-alpha.7
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.
- package/esm/dataLoaders/InsightDataLoader.d.ts +6 -0
- package/esm/dataLoaders/InsightDataLoader.d.ts.map +1 -1
- package/esm/dataLoaders/InsightDataLoader.js +4 -1
- package/esm/dataLoaders/index.d.ts +13 -0
- package/esm/dataLoaders/index.d.ts.map +1 -1
- package/esm/dataLoaders/index.js +14 -0
- package/esm/index.d.ts +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -1
- package/esm/insightView/InsightRenderer.d.ts +2 -5
- package/esm/insightView/InsightRenderer.d.ts.map +1 -1
- package/esm/insightView/InsightRenderer.js +1 -2
- package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.d.ts +6 -1
- package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.d.ts.map +1 -1
- package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.js +25 -4
- package/esm/internal/components/pluggableVisualizations/pivotTable/PluggablePivotTable.d.ts.map +1 -1
- package/esm/internal/components/pluggableVisualizations/pivotTable/PluggablePivotTable.js +0 -1
- package/esm/internal/translations/en-US.localization-bundle.d.ts +4 -0
- package/esm/internal/translations/en-US.localization-bundle.d.ts.map +1 -1
- package/esm/internal/translations/en-US.localization-bundle.js +4 -0
- package/esm/notificationsPanel/Notification/Notification.d.ts.map +1 -1
- package/esm/notificationsPanel/Notification/Notification.js +6 -3
- package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetail.d.ts +3 -3
- package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetail.d.ts.map +1 -1
- package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetail.js +4 -4
- package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetailDialog.d.ts +9 -3
- package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetailDialog.d.ts.map +1 -1
- package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetailDialog.js +4 -4
- package/esm/sdk-ui-ext.d.ts +13 -0
- package/esm/share/ObjectShareDialog.js +22 -6
- package/esm/share/messages.d.ts +7 -0
- package/esm/share/messages.d.ts.map +1 -1
- package/esm/share/messages.js +5 -0
- package/esm/share/objectShareController.helpers.d.ts +117 -17
- package/esm/share/objectShareController.helpers.d.ts.map +1 -1
- package/esm/share/objectShareController.helpers.js +181 -44
- package/esm/share/objectShareController.types.d.ts +34 -10
- package/esm/share/objectShareController.types.d.ts.map +1 -1
- package/esm/share/useAccessList.d.ts +6 -0
- package/esm/share/useAccessList.d.ts.map +1 -1
- package/esm/share/useAccessList.js +26 -6
- package/esm/share/useLabelScope.d.ts +46 -1
- package/esm/share/useLabelScope.d.ts.map +1 -1
- package/esm/share/useLabelScope.js +98 -6
- package/esm/share/useObjectShareController.d.ts.map +1 -1
- package/esm/share/useObjectShareController.js +213 -45
- package/esm/share/useObjectShareDialog.d.ts +7 -0
- package/esm/share/useObjectShareDialog.d.ts.map +1 -1
- package/esm/share/useObjectShareDialog.js +22 -2
- package/package.json +21 -21
|
@@ -5,7 +5,7 @@ import { convertError, useBackendStrict, useCancelablePromise, useWorkspaceStric
|
|
|
5
5
|
import { useToastMessage } from "@gooddata/sdk-ui-kit";
|
|
6
6
|
import { composeEffectiveWorkspaceAccess, deriveGeneralAccess, deriveInheritedWorkspaceLevel, deriveWorkspacePermissionLevel, } from "./accessSummary.js";
|
|
7
7
|
import { objectShareMessages } from "./messages.js";
|
|
8
|
-
import { assigneeIdentityFacts, assigneeMatchesQuery,
|
|
8
|
+
import { assigneeIdentityFacts, assigneeMatchesQuery, granteeId, granteesFromAccessList, mergeGrantees, userDisplayPair, userIdentityFacts, withDirectLevel, } from "./objectShareController.helpers.js";
|
|
9
9
|
/**
|
|
10
10
|
* Owns the backend access list for ONE dialog session: the hook is mounted while
|
|
11
11
|
* the dialog is open, for a single target that must not change while mounted
|
|
@@ -167,16 +167,25 @@ export function useAccessList(target) {
|
|
|
167
167
|
const settled = current ? { ...current, settled: undefined } : undefined;
|
|
168
168
|
if (current?.kind === "added") {
|
|
169
169
|
// Overlay-born row: stay an `added` entry, or the merge would drop it.
|
|
170
|
-
const grantee =
|
|
171
|
-
...current.grantee,
|
|
172
|
-
level,
|
|
173
|
-
effectivePermission: effectivePermissionAbove(level, current.grantee.inheritedLevel),
|
|
174
|
-
};
|
|
170
|
+
const grantee = withDirectLevel(current.grantee, level);
|
|
175
171
|
return { ...prev, [id]: { kind: "added", grantee, pending: true, settled } };
|
|
176
172
|
}
|
|
177
173
|
return { ...prev, [id]: { kind: "level", level, pending: true, settled } };
|
|
178
174
|
});
|
|
179
175
|
}, []);
|
|
176
|
+
// Lock a row for a write that does NOT change its access (a label-scope edit). The
|
|
177
|
+
// lock WRAPS whatever the row committed rather than modifying it: reusing the
|
|
178
|
+
// existing entry and flipping `pending` would re-arm it, and `pending` means
|
|
179
|
+
// something different per kind — a settled removal would read as a revoke in flight
|
|
180
|
+
// and render the row as removing at its pre-removal level. Both settling and failing
|
|
181
|
+
// unwrap back to the entry underneath, because a label write changes neither.
|
|
182
|
+
const applyGranteeLock = useCallback((id) => {
|
|
183
|
+
setGranteeEdits((prev) => {
|
|
184
|
+
const current = prev[id];
|
|
185
|
+
const settled = current ? { ...current, settled: undefined } : undefined;
|
|
186
|
+
return { ...prev, [id]: { kind: "locked", pending: true, settled } };
|
|
187
|
+
});
|
|
188
|
+
}, []);
|
|
180
189
|
const applyGranteeRemove = useCallback((id) => {
|
|
181
190
|
setGranteeEdits((prev) => {
|
|
182
191
|
const current = prev[id];
|
|
@@ -190,6 +199,16 @@ export function useAccessList(target) {
|
|
|
190
199
|
if (!current) {
|
|
191
200
|
return prev;
|
|
192
201
|
}
|
|
202
|
+
// A lock is a wrapper over the row's committed state, so settling it unwraps —
|
|
203
|
+
// same as failing. Keeping a settled `locked` entry would discard the entry it
|
|
204
|
+
// wrapped and drop the row back to its fetched state.
|
|
205
|
+
if (current.kind === "locked") {
|
|
206
|
+
if (!current.settled) {
|
|
207
|
+
const { [id]: _omit, ...rest } = prev;
|
|
208
|
+
return rest;
|
|
209
|
+
}
|
|
210
|
+
return { ...prev, [id]: current.settled };
|
|
211
|
+
}
|
|
193
212
|
// A settled removal always keeps its entry: for a base row (including a
|
|
194
213
|
// removed re-added one) it is what keeps the row hidden; for an
|
|
195
214
|
// overlay-born row it renders nothing and is harmless.
|
|
@@ -238,6 +257,7 @@ export function useAccessList(target) {
|
|
|
238
257
|
loadOptions,
|
|
239
258
|
applyGranteeAdd,
|
|
240
259
|
applyGranteeLevel,
|
|
260
|
+
applyGranteeLock,
|
|
241
261
|
applyGranteeRemove,
|
|
242
262
|
settleGranteeEdit,
|
|
243
263
|
failGranteeEdit,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IObjectPermissionsObject } from "@gooddata/sdk-backend-spi";
|
|
2
|
-
import { type LabelScopePrincipal } from "./objectShareController.helpers.js";
|
|
2
|
+
import { type ILabelScopeChange, type LabelScopePrincipal } from "./objectShareController.helpers.js";
|
|
3
3
|
import type { IObjectShareLabel } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Per-label access scope for the share dialog: which labels each grantee can
|
|
@@ -38,6 +38,30 @@ export interface ILabelScope {
|
|
|
38
38
|
/** Per-grantee label scope: grantee id → label ids in scope (primary always in). */
|
|
39
39
|
selectedLabelIdsByGrantee: Record<string, string[]>;
|
|
40
40
|
setSelectedLabelIdsByGrantee: React.Dispatch<React.SetStateAction<Record<string, string[]>>>;
|
|
41
|
+
/**
|
|
42
|
+
* In-scope labels whose access the grantee INHERITS, per grantee id — including ones
|
|
43
|
+
* they also hold a local grant on. Callers render these locked: unchecking cannot take
|
|
44
|
+
* the access away. Derived straight from the probe, never optimistic, because only the
|
|
45
|
+
* backend can say where a grant lives.
|
|
46
|
+
*/
|
|
47
|
+
inheritedLabelIdsByGrantee: Record<string, string[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Labels THIS workspace grants the grantee, per grantee id — the only ones a write may
|
|
50
|
+
* target, and the complement of "inherited only". An optimistic overlay, not a
|
|
51
|
+
* derivation: the per-label probe runs once per session, so after the dialog revokes or
|
|
52
|
+
* grants a label the probe's answer is stale and acting on it re-creates access that was
|
|
53
|
+
* just removed. Every label write reports its outcome through
|
|
54
|
+
* {@link recordDirectLabelWrites}.
|
|
55
|
+
*/
|
|
56
|
+
directLabelIdsByGrantee: Record<string, string[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Record the outcome of the dialog's OWN label writes so the direct set stays true:
|
|
59
|
+
* `granted` ids gain a local grant, `revoked` ids lose theirs. Pass only the writes
|
|
60
|
+
* that actually landed.
|
|
61
|
+
*/
|
|
62
|
+
recordDirectLabelWrites: (granteeId: string, granted: readonly string[], revoked: readonly string[]) => void;
|
|
63
|
+
/** Forget a grantee's label bookkeeping once their row is gone for good. */
|
|
64
|
+
forgetGranteeLabels: (granteeId: string) => void;
|
|
41
65
|
/**
|
|
42
66
|
* The single per-label write path. Diffs `desired` vs `current` over the
|
|
43
67
|
* permissionable labels and issues one `manageObjectPermissions` per changed
|
|
@@ -49,6 +73,27 @@ export interface ILabelScope {
|
|
|
49
73
|
ok: boolean;
|
|
50
74
|
failedLabelIds: string[];
|
|
51
75
|
}>;
|
|
76
|
+
/**
|
|
77
|
+
* Per-principal variant of {@link reconcileLabelScope}: each principal moves its
|
|
78
|
+
* OWN scope, for grantees added in one step that each picked a different label
|
|
79
|
+
* scope. Still one write per label carrying every principal that changes on it,
|
|
80
|
+
* so a shared label costs one call regardless of how the scopes differ.
|
|
81
|
+
*/
|
|
82
|
+
reconcileLabelScopes: (changes: ILabelScopeChange[]) => Promise<{
|
|
83
|
+
ok: boolean;
|
|
84
|
+
failedLabelIds: string[];
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* Re-grades an UNCHANGED label scope to each principal's `level` — what a
|
|
88
|
+
* permission-level change on the object needs so its labels don't keep the level
|
|
89
|
+
* they were granted at. Not a diff: every in-scope label is rewritten (a diff
|
|
90
|
+
* over an unchanged set produces no writes). Same independent settling and
|
|
91
|
+
* `failedLabelIds` reporting as {@link reconcileLabelScope}.
|
|
92
|
+
*/
|
|
93
|
+
regradeLabelScope: (principals: LabelScopePrincipal[], scopeLabelIds: ReadonlySet<string>) => Promise<{
|
|
94
|
+
ok: boolean;
|
|
95
|
+
failedLabelIds: string[];
|
|
96
|
+
}>;
|
|
52
97
|
}
|
|
53
98
|
/**
|
|
54
99
|
* Owns label-scope resolution + writes for {@link useObjectShareController}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLabelScope.d.ts","sourceRoot":"","sources":["../../src/share/useLabelScope.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAK1E,OAAO,EACH,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"useLabelScope.d.ts","sourceRoot":"","sources":["../../src/share/useLabelScope.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAK1E,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAI3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA6EpD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB;;;;OAIG;IACH,eAAe,EAAE,iBAAiB,EAAE,CAAC;IACrC;;;;;;;;OAQG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,sFAAoF;IACpF,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpD,4BAA4B,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7F;;;;;OAKG;IACH,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACrD;;;;;;;OAOG;IACH,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClD;;;;OAIG;IACH,uBAAuB,EAAE,CACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,KACzB,IAAI,CAAC;IACV,4EAA4E;IAC5E,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD;;;;;;OAMG;IACH,mBAAmB,EAAE,CACjB,UAAU,EAAE,mBAAmB,EAAE,EACjC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,KACnC,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxD;;;;;OAKG;IACH,oBAAoB,EAAE,CAClB,OAAO,EAAE,iBAAiB,EAAE,KAC3B,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxD;;;;;;OAMG;IACH,iBAAiB,EAAE,CACf,UAAU,EAAE,mBAAmB,EAAE,EACjC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,KACjC,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,OAAO,EAAE,OAAO,EAChB,mBAAmB,EAAE,MAAM,EAAE,EAC7B,WAAW,EAAE,OAAO,EACpB,aAAa,EAAE,OAAO,GACvB,WAAW,CA6Ob"}
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
import { useCallback, useMemo, useState } from "react";
|
|
3
3
|
import { useBackendStrict, useCancelablePromise, useWorkspaceStrict } from "@gooddata/sdk-ui";
|
|
4
4
|
import { isPermissionsNotAvailable } from "./accessErrors.js";
|
|
5
|
-
import {
|
|
6
|
-
/**
|
|
5
|
+
import { buildLabelMutationsForScopes, buildLabelRegrades, granteeGrantIn, } from "./objectShareController.helpers.js";
|
|
6
|
+
/**
|
|
7
|
+
* Pure: each grantee's in-scope label ids — primary always counts, others wherever
|
|
8
|
+
* the grantee holds the label, whether granted here or inherited. An inherited label
|
|
9
|
+
* is genuinely in scope: the grantee can read it, so the checklist must show it
|
|
10
|
+
* checked, exactly as the parent workspace shows it.
|
|
11
|
+
*/
|
|
7
12
|
function resolveScopes(results, committedGranteeIds) {
|
|
8
13
|
const resolved = {};
|
|
9
14
|
for (const id of committedGranteeIds) {
|
|
@@ -14,13 +19,52 @@ function resolveScopes(results, committedGranteeIds) {
|
|
|
14
19
|
continue;
|
|
15
20
|
}
|
|
16
21
|
for (const id of committedGranteeIds) {
|
|
17
|
-
if (result.label.isPrimary ||
|
|
22
|
+
if (result.label.isPrimary || granteeGrantIn(result.list, id)) {
|
|
18
23
|
resolved[id].push(result.label.id);
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
return resolved;
|
|
23
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Pure: per-grantee label provenance, split by which question it answers. A label that is
|
|
31
|
+
* both granted here and inherited appears in BOTH maps, and that is the point — the two
|
|
32
|
+
* facts drive different decisions.
|
|
33
|
+
*
|
|
34
|
+
* - `inherited` — access survives revoking whatever this workspace granted, so the
|
|
35
|
+
* checkbox stays checked and locked. Immutable for the session: no write from this
|
|
36
|
+
* workspace can change a group's or a parent's grant, so deriving it from the probe
|
|
37
|
+
* cannot go stale.
|
|
38
|
+
* - `direct` — granted HERE, so it is the only thing a write may target. This one DOES
|
|
39
|
+
* change under our own writes, so the hook keeps it as an optimistic overlay seeded from
|
|
40
|
+
* here rather than re-deriving it from a probe that runs once per session.
|
|
41
|
+
*
|
|
42
|
+
* The primary label is excluded from both: it is locked for every grantee regardless, and
|
|
43
|
+
* never written.
|
|
44
|
+
*/
|
|
45
|
+
function resolveLabelProvenance(results, committedGranteeIds) {
|
|
46
|
+
const inherited = {};
|
|
47
|
+
const direct = {};
|
|
48
|
+
for (const id of committedGranteeIds) {
|
|
49
|
+
inherited[id] = [];
|
|
50
|
+
direct[id] = [];
|
|
51
|
+
}
|
|
52
|
+
for (const result of results) {
|
|
53
|
+
if ("transient" in result || result.label.isPrimary) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
for (const id of committedGranteeIds) {
|
|
57
|
+
const grant = granteeGrantIn(result.list, id);
|
|
58
|
+
if (grant?.inherited) {
|
|
59
|
+
inherited[id].push(result.label.id);
|
|
60
|
+
}
|
|
61
|
+
if (grant?.direct) {
|
|
62
|
+
direct[id].push(result.label.id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { inherited, direct };
|
|
67
|
+
}
|
|
24
68
|
/**
|
|
25
69
|
* Owns label-scope resolution + writes for {@link useObjectShareController}.
|
|
26
70
|
* Resolves each grantee's scope by fetching every label's access list, tracks
|
|
@@ -41,6 +85,10 @@ export function useLabelScope(target, labels, hasList, committedGranteeIds, labe
|
|
|
41
85
|
// a fresh add / labels edit must survive the backend's lagging re-read, so the
|
|
42
86
|
// probe below only seeds grantees it doesn't already know.
|
|
43
87
|
const [selectedLabelIdsByGrantee, setSelectedLabelIdsByGrantee] = useState({});
|
|
88
|
+
// Which labels THIS workspace grants each grantee. Optimistic for the same reason the
|
|
89
|
+
// scope is, and for a sharper one: our own revokes and grants change it, and the probe
|
|
90
|
+
// will not run again to notice.
|
|
91
|
+
const [directLabelIdsByGrantee, setDirectLabelIdsByGrantee] = useState({});
|
|
44
92
|
// Whether the session's first probe has settled (see `labelsInitializing`) —
|
|
45
93
|
// a fact about session history, so it is state, flipped once in onSuccess.
|
|
46
94
|
const [everResolved, setEverResolved] = useState(false);
|
|
@@ -52,6 +100,7 @@ export function useLabelScope(target, labels, hasList, committedGranteeIds, labe
|
|
|
52
100
|
if (seenLabelsKey !== labelsKey) {
|
|
53
101
|
setSeenLabelsKey(labelsKey);
|
|
54
102
|
setSelectedLabelIdsByGrantee({});
|
|
103
|
+
setDirectLabelIdsByGrantee({});
|
|
55
104
|
}
|
|
56
105
|
// Probe each label's access list to learn which are permissionable (some 404) and
|
|
57
106
|
// which each grantee holds. `hasList` gates it: a list with no named grantees keeps
|
|
@@ -73,6 +122,7 @@ export function useLabelScope(target, labels, hasList, committedGranteeIds, labe
|
|
|
73
122
|
onSuccess: (results) => {
|
|
74
123
|
setEverResolved(true);
|
|
75
124
|
const resolved = resolveScopes(results, committedGranteeIds);
|
|
125
|
+
const { direct } = resolveLabelProvenance(results, committedGranteeIds);
|
|
76
126
|
// Seed only grantees we don't already have a scope for — an optimistic
|
|
77
127
|
// scope is authoritative and must survive this re-resolution. Fired once
|
|
78
128
|
// per resolution (onSuccess), so reading `prev` is safe.
|
|
@@ -83,6 +133,14 @@ export function useLabelScope(target, labels, hasList, committedGranteeIds, labe
|
|
|
83
133
|
}
|
|
84
134
|
return next;
|
|
85
135
|
});
|
|
136
|
+
// Same rule for the direct set: what we wrote outranks what the probe read.
|
|
137
|
+
setDirectLabelIdsByGrantee((prev) => {
|
|
138
|
+
const next = {};
|
|
139
|
+
for (const id of committedGranteeIds) {
|
|
140
|
+
next[id] = prev[id] ?? direct[id];
|
|
141
|
+
}
|
|
142
|
+
return next;
|
|
143
|
+
});
|
|
86
144
|
},
|
|
87
145
|
},
|
|
88
146
|
// Keyed on the label set's stable string hash, not the array (rebuilt every
|
|
@@ -112,6 +170,30 @@ export function useLabelScope(target, labels, hasList, committedGranteeIds, labe
|
|
|
112
170
|
}
|
|
113
171
|
return permissionable;
|
|
114
172
|
}, [labelLists]);
|
|
173
|
+
// Inheritance is the one fact no write from here can change, so it stays a pure
|
|
174
|
+
// derivation of the probe — there is no optimistic version of "someone else granted
|
|
175
|
+
// this", and nothing we do can make it stale.
|
|
176
|
+
const inheritedLabelIdsByGrantee = useMemo(() => (labelLists ? resolveLabelProvenance(labelLists, committedGranteeIds).inherited : {}), [labelLists, committedGranteeIds]);
|
|
177
|
+
const recordDirectLabelWrites = useCallback((granteeId, granted, revoked) => {
|
|
178
|
+
if (granted.length === 0 && revoked.length === 0) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setDirectLabelIdsByGrantee((prev) => {
|
|
182
|
+
const dropped = new Set(revoked);
|
|
183
|
+
const kept = (prev[granteeId] ?? []).filter((id) => !dropped.has(id));
|
|
184
|
+
return { ...prev, [granteeId]: Array.from(new Set([...kept, ...granted])) };
|
|
185
|
+
});
|
|
186
|
+
}, []);
|
|
187
|
+
const forgetGranteeLabels = useCallback((granteeId) => {
|
|
188
|
+
setSelectedLabelIdsByGrantee((prev) => {
|
|
189
|
+
const { [granteeId]: _scope, ...rest } = prev;
|
|
190
|
+
return rest;
|
|
191
|
+
});
|
|
192
|
+
setDirectLabelIdsByGrantee((prev) => {
|
|
193
|
+
const { [granteeId]: _direct, ...rest } = prev;
|
|
194
|
+
return rest;
|
|
195
|
+
});
|
|
196
|
+
}, []);
|
|
115
197
|
// Only permissionable labels are scope-controllable; assume all until resolved.
|
|
116
198
|
const effectiveLabels = useMemo(() => hasList && permissionableLabelIds
|
|
117
199
|
? labels.filter((l) => permissionableLabelIds.has(l.id))
|
|
@@ -137,8 +219,9 @@ export function useLabelScope(target, labels, hasList, committedGranteeIds, labe
|
|
|
137
219
|
// drift. Writes settle independently (allSettled), and the failed labels are
|
|
138
220
|
// reported by id — never collapsed into one boolean, or a partial failure would
|
|
139
221
|
// roll callers back past writes that actually landed.
|
|
140
|
-
|
|
141
|
-
|
|
222
|
+
// Issues one manageObjectPermissions per label and reports the failures by id.
|
|
223
|
+
// Shared by the scope diff and the level re-grade so both settle identically.
|
|
224
|
+
const writeLabels = useCallback(async (writes) => {
|
|
142
225
|
if (writes.length === 0) {
|
|
143
226
|
return { ok: true, failedLabelIds: [] };
|
|
144
227
|
}
|
|
@@ -148,13 +231,22 @@ export function useLabelScope(target, labels, hasList, committedGranteeIds, labe
|
|
|
148
231
|
.filter((_, i) => results[i].status === "rejected")
|
|
149
232
|
.map((w) => w.id);
|
|
150
233
|
return { ok: failedLabelIds.length === 0, failedLabelIds };
|
|
151
|
-
}, [
|
|
234
|
+
}, [backend, workspace]);
|
|
235
|
+
const reconcileLabelScopes = useCallback((changes) => writeLabels(buildLabelMutationsForScopes(changes, effectiveLabels)), [effectiveLabels, writeLabels]);
|
|
236
|
+
const reconcileLabelScope = useCallback((principals, desiredLabelIds, currentLabelIds) => reconcileLabelScopes(principals.map((principal) => ({ principal, desiredLabelIds, currentLabelIds }))), [reconcileLabelScopes]);
|
|
237
|
+
const regradeLabelScope = useCallback((principals, scopeLabelIds) => writeLabels(buildLabelRegrades(principals, scopeLabelIds, effectiveLabels)), [effectiveLabels, writeLabels]);
|
|
152
238
|
return {
|
|
153
239
|
effectiveLabels,
|
|
154
240
|
labelsResolved,
|
|
155
241
|
labelsInitializing,
|
|
156
242
|
selectedLabelIdsByGrantee,
|
|
157
243
|
setSelectedLabelIdsByGrantee,
|
|
244
|
+
inheritedLabelIdsByGrantee,
|
|
245
|
+
directLabelIdsByGrantee,
|
|
246
|
+
recordDirectLabelWrites,
|
|
247
|
+
forgetGranteeLabels,
|
|
158
248
|
reconcileLabelScope,
|
|
249
|
+
reconcileLabelScopes,
|
|
250
|
+
regradeLabelScope,
|
|
159
251
|
};
|
|
160
252
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useObjectShareController.d.ts","sourceRoot":"","sources":["../../src/share/useObjectShareController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"useObjectShareController.d.ts","sourceRoot":"","sources":["../../src/share/useObjectShareController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAkB1E,OAAO,EACH,KAAK,sBAAsB,EAI3B,KAAK,sBAAsB,EAE9B,MAAM,kCAAkC,CAAC;AAI1C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,OAAO,CAAC,EAAE,sBAAsB,GACjC,sBAAsB,CAkxBxB"}
|