@gooddata/sdk-ui-ext 11.44.0 → 11.45.0-alpha.1
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/internal/translations/en-US.localization-bundle.d.ts +24 -0
- package/esm/internal/translations/en-US.localization-bundle.d.ts.map +1 -1
- package/esm/internal/translations/en-US.localization-bundle.js +24 -0
- package/esm/sdk-ui-ext.d.ts +45 -4
- package/esm/share/ObjectShareDialog.d.ts.map +1 -1
- package/esm/share/ObjectShareDialog.js +37 -5
- package/esm/share/messages.d.ts +18 -0
- package/esm/share/messages.d.ts.map +1 -1
- package/esm/share/messages.js +6 -0
- package/esm/share/objectShareController.types.d.ts +45 -5
- package/esm/share/objectShareController.types.d.ts.map +1 -1
- package/esm/share/useAccessList.d.ts +14 -2
- package/esm/share/useAccessList.d.ts.map +1 -1
- package/esm/share/useAccessList.js +29 -6
- package/esm/share/useObjectShareController.d.ts.map +1 -1
- package/esm/share/useObjectShareController.js +200 -2
- package/package.json +21 -21
|
@@ -2395,6 +2395,30 @@ export declare const en_US: {
|
|
|
2395
2395
|
text: string;
|
|
2396
2396
|
crowdinContext: string;
|
|
2397
2397
|
};
|
|
2398
|
+
"objectShare.transfer.alreadyOwner.title": {
|
|
2399
|
+
text: string;
|
|
2400
|
+
crowdinContext: string;
|
|
2401
|
+
};
|
|
2402
|
+
"objectShare.transfer.alreadyOwner.description": {
|
|
2403
|
+
text: string;
|
|
2404
|
+
crowdinContext: string;
|
|
2405
|
+
};
|
|
2406
|
+
"objectShare.transfer.alreadyOwner.keepAccess": {
|
|
2407
|
+
text: string;
|
|
2408
|
+
crowdinContext: string;
|
|
2409
|
+
};
|
|
2410
|
+
"objectShare.transfer.alreadyOwner.removeAccess": {
|
|
2411
|
+
text: string;
|
|
2412
|
+
crowdinContext: string;
|
|
2413
|
+
};
|
|
2414
|
+
"objectShare.transfer.confirmButton": {
|
|
2415
|
+
text: string;
|
|
2416
|
+
crowdinContext: string;
|
|
2417
|
+
};
|
|
2418
|
+
"objectShare.toast.ownershipTransferred": {
|
|
2419
|
+
text: string;
|
|
2420
|
+
crowdinContext: string;
|
|
2421
|
+
};
|
|
2398
2422
|
"objectShare.toast.granteeAdded": {
|
|
2399
2423
|
text: string;
|
|
2400
2424
|
crowdinContext: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en-US.localization-bundle.d.ts","sourceRoot":"","sources":["../../../src/internal/translations/en-US.localization-bundle.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK
|
|
1
|
+
{"version":3,"file":"en-US.localization-bundle.d.ts","sourceRoot":"","sources":["../../../src/internal/translations/en-US.localization-bundle.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqjFjB,CAAC"}
|
|
@@ -2397,6 +2397,30 @@ export const en_US = {
|
|
|
2397
2397
|
"text": "Confirm",
|
|
2398
2398
|
"crowdinContext": "Primary action label in the general-access confirm dialog."
|
|
2399
2399
|
},
|
|
2400
|
+
"objectShare.transfer.alreadyOwner.title": {
|
|
2401
|
+
"text": "{name} is already an owner",
|
|
2402
|
+
"crowdinContext": "Title of the confirm dialog shown when the user picked to receive ownership already owns the object. {name} is that user's name."
|
|
2403
|
+
},
|
|
2404
|
+
"objectShare.transfer.alreadyOwner.description": {
|
|
2405
|
+
"text": "There's nothing to transfer. If you want to remove your ownership, choose what happens to your access:",
|
|
2406
|
+
"crowdinContext": "Description in the confirm dialog shown when the picked user already owns the object. Offers to remove the current user's own access instead of transferring."
|
|
2407
|
+
},
|
|
2408
|
+
"objectShare.transfer.alreadyOwner.keepAccess": {
|
|
2409
|
+
"text": "Keep view access",
|
|
2410
|
+
"crowdinContext": "Radio option in the 'already an owner' dialog: the current user keeps view access to the object."
|
|
2411
|
+
},
|
|
2412
|
+
"objectShare.transfer.alreadyOwner.removeAccess": {
|
|
2413
|
+
"text": "Remove my access",
|
|
2414
|
+
"crowdinContext": "Radio option in the 'already an owner' dialog: the current user's access to the object is removed entirely."
|
|
2415
|
+
},
|
|
2416
|
+
"objectShare.transfer.confirmButton": {
|
|
2417
|
+
"text": "Confirm",
|
|
2418
|
+
"crowdinContext": "Primary button in the 'already an owner' dialog. Applies the chosen change to the current user's own access."
|
|
2419
|
+
},
|
|
2420
|
+
"objectShare.toast.ownershipTransferred": {
|
|
2421
|
+
"text": "The ownership has been transferred.",
|
|
2422
|
+
"crowdinContext": "Toast shown after ownership of the shared object was successfully transferred to another user."
|
|
2423
|
+
},
|
|
2400
2424
|
"objectShare.toast.granteeAdded": {
|
|
2401
2425
|
"text": "Access added.",
|
|
2402
2426
|
"crowdinContext": "Toast shown after a new user or group is granted access via the share dialog."
|
package/esm/sdk-ui-ext.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { IPivotTableConfig } from '@gooddata/sdk-ui-pivot';
|
|
|
47
47
|
import { ISettings } from '@gooddata/sdk-model';
|
|
48
48
|
import { ITab } from '@gooddata/sdk-ui-kit';
|
|
49
49
|
import { ITheme } from '@gooddata/sdk-model';
|
|
50
|
+
import type { IUiGranteeAsyncOption } from '@gooddata/sdk-ui-kit';
|
|
50
51
|
import type { IUiGranteeAsyncOptions } from '@gooddata/sdk-ui-kit';
|
|
51
52
|
import type { IUiPickedGrantee } from '@gooddata/sdk-ui-kit';
|
|
52
53
|
import { IUserWorkspaceSettings } from '@gooddata/sdk-backend-spi';
|
|
@@ -1454,11 +1455,13 @@ export declare interface IObjectShareControllerActions {
|
|
|
1454
1455
|
closeAddGrantee: () => void;
|
|
1455
1456
|
setPendingGrantees: (next: IUiPickedGrantee[]) => void;
|
|
1456
1457
|
/**
|
|
1457
|
-
* Loader for the
|
|
1458
|
+
* Loader for the grantee pickers. Wraps `getAvailableAssignees` with
|
|
1458
1459
|
* client-side search + already-picked filtering, returning the picker's
|
|
1459
|
-
* `{ groups, users }` shape.
|
|
1460
|
+
* `{ groups, users }` shape. By default excludes already-granted grantees
|
|
1461
|
+
* (add-grantee picker); pass `includeGranted` to keep them (transfer-ownership
|
|
1462
|
+
* picker, which may promote an existing viewer to owner).
|
|
1460
1463
|
*/
|
|
1461
|
-
loadOptions: (search: string) => Promise<IUiGranteeAsyncOptions>;
|
|
1464
|
+
loadOptions: (search: string, includeGranted?: boolean) => Promise<IUiGranteeAsyncOptions>;
|
|
1462
1465
|
/** Commit all pending grantees to the backend. */
|
|
1463
1466
|
confirmAddGrantees: () => Promise<void>;
|
|
1464
1467
|
/** Change the permission level for a single grantee. Auto-saves. */
|
|
@@ -1482,13 +1485,31 @@ export declare interface IObjectShareControllerActions {
|
|
|
1482
1485
|
* already-granted rule.
|
|
1483
1486
|
*/
|
|
1484
1487
|
changeWorkspaceLevel: (level: "VIEW" | "SHARE") => Promise<void>;
|
|
1488
|
+
/** Open the transfer-ownership subview with an empty picker. */
|
|
1489
|
+
openTransferOwnership: () => void;
|
|
1490
|
+
/** Close the transfer-ownership subview and clear its buffers; returns to main. */
|
|
1491
|
+
closeTransferOwnership: () => void;
|
|
1492
|
+
/**
|
|
1493
|
+
* Pick (or replace) the candidate new owner. Recomputes whether that user
|
|
1494
|
+
* already owns the object (`transferTargetIsOwner`).
|
|
1495
|
+
*/
|
|
1496
|
+
setTransferTarget: (owner: IUiGranteeAsyncOption) => void;
|
|
1497
|
+
/** Toggle "Also remove my access" in the transfer dialog. */
|
|
1498
|
+
setTransferAlsoRemoveSelf: (next: boolean) => void;
|
|
1499
|
+
/**
|
|
1500
|
+
* Commit the ownership transfer: grant the picked user EDIT and downgrade the
|
|
1501
|
+
* current user to VIEW (or remove them if "Also remove my access" is set), in
|
|
1502
|
+
* one write. When the picked user already owns the object this transfers
|
|
1503
|
+
* nothing and only applies the self-access choice. Auto-saves.
|
|
1504
|
+
*/
|
|
1505
|
+
confirmTransferOwnership: () => Promise<void>;
|
|
1485
1506
|
}
|
|
1486
1507
|
|
|
1487
1508
|
/**
|
|
1488
1509
|
* @internal
|
|
1489
1510
|
*/
|
|
1490
1511
|
export declare interface IObjectShareControllerState {
|
|
1491
|
-
subview: "main" | "addGrantee";
|
|
1512
|
+
subview: "main" | "addGrantee" | "transferOwnership";
|
|
1492
1513
|
status: "idle" | "loading" | "success" | "error" | "saving";
|
|
1493
1514
|
error?: Error;
|
|
1494
1515
|
/**
|
|
@@ -1537,6 +1558,26 @@ export declare interface IObjectShareControllerState {
|
|
|
1537
1558
|
pendingGeneralAccess?: GeneralAccessValue;
|
|
1538
1559
|
/** Grantees staged in the add-grantee dialog before confirmation. */
|
|
1539
1560
|
pendingGrantees: IUiPickedGrantee[];
|
|
1561
|
+
/**
|
|
1562
|
+
* The user picked as the new owner in the transfer-ownership subview, or
|
|
1563
|
+
* undefined before one is chosen. Only users can own an object, so this is
|
|
1564
|
+
* never a group.
|
|
1565
|
+
*/
|
|
1566
|
+
transferTarget: IUiGranteeAsyncOption | undefined;
|
|
1567
|
+
/**
|
|
1568
|
+
* Whether "Also remove my access" is checked in the transfer dialog. When
|
|
1569
|
+
* true the current user's grant is removed after the transfer; otherwise it
|
|
1570
|
+
* is downgraded to VIEW.
|
|
1571
|
+
*/
|
|
1572
|
+
transferAlsoRemoveSelf: boolean;
|
|
1573
|
+
/**
|
|
1574
|
+
* Whether `transferTarget` already owns the object (already holds EDIT). When
|
|
1575
|
+
* true there is nothing to transfer — consumers show the "already an owner"
|
|
1576
|
+
* variant offering to remove the current user's own access instead.
|
|
1577
|
+
*/
|
|
1578
|
+
transferTargetIsOwner: boolean;
|
|
1579
|
+
/** Whether the transfer write (or self-removal) is in flight. */
|
|
1580
|
+
transferSaving: boolean;
|
|
1540
1581
|
}
|
|
1541
1582
|
|
|
1542
1583
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectShareDialog.d.ts","sourceRoot":"","sources":["../../src/share/ObjectShareDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"ObjectShareDialog.d.ts","sourceRoot":"","sources":["../../src/share/ObjectShareDialog.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAW1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,gDAAgD;IAChD,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC;IAC7C,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,sFAAsF;IACtF,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,4FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,sBAAsB,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,MAAM,EACN,WAAW,EACX,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACb,EAAE,uBAAuB,2CAkPzB"}
|
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
// (C) 2026 GoodData Corporation
|
|
3
3
|
import { useCallback } from "react";
|
|
4
4
|
import { useIntl } from "react-intl";
|
|
5
|
-
import { UiAddGranteeDialog, UiConfirmDialog, UiGranteeRowControls, UiObjectShareDialog, } from "@gooddata/sdk-ui-kit";
|
|
5
|
+
import { UiAddGranteeDialog, UiConfirmDialog, UiGranteeRowControls, UiObjectShareDialog, UiRadioRow, UiTransferOwnershipDialog, } from "@gooddata/sdk-ui-kit";
|
|
6
6
|
import { objectShareMessages } from "./messages.js";
|
|
7
7
|
import { useObjectShareController } from "./useObjectShareController.js";
|
|
8
8
|
// The workspace row has no labels menu (see workspaceControls below), so its
|
|
@@ -43,8 +43,27 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
|
|
|
43
43
|
actions.reset();
|
|
44
44
|
onClose();
|
|
45
45
|
}, [actions, onClose]);
|
|
46
|
+
// Owner is always an individual user, so the transfer picker drops any groups
|
|
47
|
+
// the shared loader returns. `includeGranted` keeps already-granted users in
|
|
48
|
+
// the list — promoting an existing viewer to owner is a primary case.
|
|
49
|
+
//
|
|
50
|
+
// Depend on the stable `loadOptions` alone, not the whole `actions` object:
|
|
51
|
+
// `actions` changes identity as transfer state ticks (and as the picker's own
|
|
52
|
+
// name-cache writes re-derive grantees), and the autocomplete refetches page 0
|
|
53
|
+
// whenever its loader's identity changes — depending on `actions` here would
|
|
54
|
+
// self-feed into an infinite assignee-fetch loop.
|
|
55
|
+
const { loadOptions } = actions;
|
|
56
|
+
const loadOwnerOptions = useCallback(async (search) => {
|
|
57
|
+
const { users } = await loadOptions(search, true);
|
|
58
|
+
return { groups: [], users };
|
|
59
|
+
}, [loadOptions]);
|
|
46
60
|
const isAddGranteeOpen = isOpen && state.subview === "addGrantee";
|
|
47
|
-
|
|
61
|
+
// The transfer subview splits into two co-mounted dialogs: the transfer card
|
|
62
|
+
// while picking a non-owner, and the "already an owner" confirm once the picked
|
|
63
|
+
// user already owns the object.
|
|
64
|
+
const isTransferOpen = isOpen && state.subview === "transferOwnership" && !state.transferTargetIsOwner;
|
|
65
|
+
const isAlreadyOwnerOpen = isOpen && state.subview === "transferOwnership" && state.transferTargetIsOwner;
|
|
66
|
+
const isShareOpen = isOpen && state.subview === "main";
|
|
48
67
|
const isConfirmOpen = isOpen && !!state.pendingGeneralAccess;
|
|
49
68
|
// Don't allow mutations until the access list has loaded: before then the
|
|
50
69
|
// controller reports empty grantees + RESTRICTED, which is a placeholder, not
|
|
@@ -55,8 +74,13 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
|
|
|
55
74
|
// wrong labels). `labelsResolved` is false while labels are still loading and
|
|
56
75
|
// when their fetch failed, so a pending/failed label set blocks every
|
|
57
76
|
// access-changing control here — Add, row controls and general access alike.
|
|
77
|
+
// Also locked while a transfer-ownership write is committing: the transfer
|
|
78
|
+
// dialog can be dismissed (Back/Cancel/close) mid-save, so without this the
|
|
79
|
+
// user could return here and mutate other grantees concurrently with the
|
|
80
|
+
// in-flight ownership write — racing the same object's permissions with no
|
|
81
|
+
// refetch to reconcile. `transferSaving` clears when the transfer settles.
|
|
58
82
|
const isLoaded = state.status === "success";
|
|
59
|
-
const isMutable = isLoaded && state.labelsResolved;
|
|
83
|
+
const isMutable = isLoaded && state.labelsResolved && !state.transferSaving;
|
|
60
84
|
const isAddDisabled = !isMutable;
|
|
61
85
|
// Map the controller's labels to the picker's item shape; the primary label
|
|
62
86
|
// is rendered locked (always selected, can't be unchecked).
|
|
@@ -78,13 +102,13 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
|
|
|
78
102
|
// probe in flight) — removing or re-scoping before then
|
|
79
103
|
// would diff against the "assume all"/empty placeholder
|
|
80
104
|
// and silently orphan real per-label grants.
|
|
81
|
-
isDisabled: g.pending !== undefined || !state.labelsResolved, onLabelsChange: (selectedIds) => {
|
|
105
|
+
isDisabled: g.pending !== undefined || !state.labelsResolved || state.transferSaving, onLabelsChange: (selectedIds) => {
|
|
82
106
|
void actions.changeGranteeLabels(g.id, selectedIds);
|
|
83
107
|
}, onPermissionChange: (level) => {
|
|
84
108
|
void actions.changePermissionLevel(g.id, level);
|
|
85
109
|
}, onRemoveAccess: () => {
|
|
86
110
|
void actions.removeGrantee(g.id);
|
|
87
|
-
} })),
|
|
111
|
+
}, onTransferOwnership: actions.openTransferOwnership })),
|
|
88
112
|
})), onAddClick: actions.openAddGrantee, isAddDisabled: isAddDisabled, generalAccess: state.generalAccess, onGeneralAccessChange: actions.requestGeneralAccessChange,
|
|
89
113
|
// Permission dropdown on the "All workspace members" row — only while
|
|
90
114
|
// workspace access is on (the rule must exist to be re-graded). No
|
|
@@ -112,6 +136,14 @@ export function ObjectShareDialog({ target, objectTitle, isOpen, onClose, onSave
|
|
|
112
136
|
// read as "no one has access".
|
|
113
137
|
error: state.status === "error" ? intl.formatMessage(objectShareMessages.loadError) : undefined }), _jsx(UiAddGranteeDialog, { isOpen: isAddGranteeOpen, objectTitle: objectTitle, loadOptions: actions.loadOptions, selectedGrantees: state.pendingGrantees, onSelectedGranteesChange: actions.setPendingGrantees, onBack: actions.closeAddGrantee, onClose: handleClose, onCancel: actions.closeAddGrantee, onShare: () => {
|
|
114
138
|
void actions.confirmAddGrantees();
|
|
139
|
+
} }), _jsx(UiTransferOwnershipDialog, { isOpen: isTransferOpen, objectTitle: objectTitle, loadOptions: loadOwnerOptions, selectedOwner: state.transferTarget, onSelectedOwnerChange: actions.setTransferTarget, alsoRemoveMyAccess: state.transferAlsoRemoveSelf, onAlsoRemoveMyAccessChange: actions.setTransferAlsoRemoveSelf, onBack: actions.closeTransferOwnership, onClose: handleClose, onCancel: actions.closeTransferOwnership, onTransfer: () => {
|
|
140
|
+
void actions.confirmTransferOwnership();
|
|
141
|
+
}, isSaving: state.transferSaving }), _jsx(UiConfirmDialog, { isOpen: isAlreadyOwnerOpen, title: intl.formatMessage(objectShareMessages.transferAlreadyOwnerTitle, {
|
|
142
|
+
name: state.transferTarget?.name ?? "",
|
|
143
|
+
}), description: _jsxs(_Fragment, { children: [
|
|
144
|
+
_jsx("p", { children: intl.formatMessage(objectShareMessages.transferAlreadyOwnerDescription) }), _jsx(UiRadioRow, { name: "transfer-already-owner", checked: !state.transferAlsoRemoveSelf, onChange: () => actions.setTransferAlsoRemoveSelf(false), title: intl.formatMessage(objectShareMessages.transferAlreadyOwnerKeepAccess) }), _jsx(UiRadioRow, { name: "transfer-already-owner", checked: state.transferAlsoRemoveSelf, onChange: () => actions.setTransferAlsoRemoveSelf(true), title: intl.formatMessage(objectShareMessages.transferAlreadyOwnerRemoveAccess) })
|
|
145
|
+
] }), confirmLabel: intl.formatMessage(objectShareMessages.transferConfirmButton), confirmVariant: "primary", onCancel: actions.closeTransferOwnership, onClose: actions.closeTransferOwnership, onConfirm: () => {
|
|
146
|
+
void actions.confirmTransferOwnership();
|
|
115
147
|
} }), _jsx(UiConfirmDialog, { isOpen: isConfirmOpen, title: intl.formatMessage(state.pendingGeneralAccess === "RESTRICTED"
|
|
116
148
|
? objectShareMessages.confirmRestrictTitle
|
|
117
149
|
: objectShareMessages.confirmGrantWorkspaceTitle), description: intl.formatMessage(state.pendingGeneralAccess === "RESTRICTED"
|
package/esm/share/messages.d.ts
CHANGED
|
@@ -22,6 +22,21 @@ export declare const objectShareMessages: {
|
|
|
22
22
|
confirmButton: {
|
|
23
23
|
id: string;
|
|
24
24
|
};
|
|
25
|
+
transferAlreadyOwnerTitle: {
|
|
26
|
+
id: string;
|
|
27
|
+
};
|
|
28
|
+
transferAlreadyOwnerDescription: {
|
|
29
|
+
id: string;
|
|
30
|
+
};
|
|
31
|
+
transferAlreadyOwnerKeepAccess: {
|
|
32
|
+
id: string;
|
|
33
|
+
};
|
|
34
|
+
transferAlreadyOwnerRemoveAccess: {
|
|
35
|
+
id: string;
|
|
36
|
+
};
|
|
37
|
+
transferConfirmButton: {
|
|
38
|
+
id: string;
|
|
39
|
+
};
|
|
25
40
|
toastGranteeAdded: {
|
|
26
41
|
id: string;
|
|
27
42
|
};
|
|
@@ -31,6 +46,9 @@ export declare const objectShareMessages: {
|
|
|
31
46
|
toastGeneralAccessUpdated: {
|
|
32
47
|
id: string;
|
|
33
48
|
};
|
|
49
|
+
toastOwnershipTransferred: {
|
|
50
|
+
id: string;
|
|
51
|
+
};
|
|
34
52
|
toastError: {
|
|
35
53
|
id: string;
|
|
36
54
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/share/messages.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/share/messages.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkB9B,CAAC"}
|
package/esm/share/messages.js
CHANGED
|
@@ -14,9 +14,15 @@ export const objectShareMessages = defineMessages({
|
|
|
14
14
|
confirmGrantWorkspaceTitle: { id: "objectShare.confirm.grantWorkspace.title" },
|
|
15
15
|
confirmGrantWorkspaceDescription: { id: "objectShare.confirm.grantWorkspace.description" },
|
|
16
16
|
confirmButton: { id: "objectShare.confirm.button" },
|
|
17
|
+
transferAlreadyOwnerTitle: { id: "objectShare.transfer.alreadyOwner.title" },
|
|
18
|
+
transferAlreadyOwnerDescription: { id: "objectShare.transfer.alreadyOwner.description" },
|
|
19
|
+
transferAlreadyOwnerKeepAccess: { id: "objectShare.transfer.alreadyOwner.keepAccess" },
|
|
20
|
+
transferAlreadyOwnerRemoveAccess: { id: "objectShare.transfer.alreadyOwner.removeAccess" },
|
|
21
|
+
transferConfirmButton: { id: "objectShare.transfer.confirmButton" },
|
|
17
22
|
toastGranteeAdded: { id: "objectShare.toast.granteeAdded" },
|
|
18
23
|
toastAccessUpdated: { id: "objectShare.toast.accessUpdated" },
|
|
19
24
|
toastGeneralAccessUpdated: { id: "objectShare.toast.generalAccessUpdated" },
|
|
25
|
+
toastOwnershipTransferred: { id: "objectShare.toast.ownershipTransferred" },
|
|
20
26
|
toastError: { id: "objectShare.toast.error" },
|
|
21
27
|
toastLabelScopePartial: { id: "objectShare.toast.labelScopePartial" },
|
|
22
28
|
loadError: { id: "objectShare.loadError" },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AccessGranularPermission, ObjRef } from "@gooddata/sdk-model";
|
|
2
|
-
import type { GeneralAccessValue, IUiGranteeAsyncOptions, IUiPickedGrantee } from "@gooddata/sdk-ui-kit";
|
|
2
|
+
import type { GeneralAccessValue, IUiGranteeAsyncOption, IUiGranteeAsyncOptions, IUiPickedGrantee } from "@gooddata/sdk-ui-kit";
|
|
3
3
|
import type { IObjectAccessSummary, IObjectShareLabel } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Permission level surfaced in the share dialog — the model's granular access
|
|
@@ -48,7 +48,7 @@ export interface IObjectShareGrantee {
|
|
|
48
48
|
* @internal
|
|
49
49
|
*/
|
|
50
50
|
export interface IObjectShareControllerState {
|
|
51
|
-
subview: "main" | "addGrantee";
|
|
51
|
+
subview: "main" | "addGrantee" | "transferOwnership";
|
|
52
52
|
status: "idle" | "loading" | "success" | "error" | "saving";
|
|
53
53
|
error?: Error;
|
|
54
54
|
/**
|
|
@@ -97,6 +97,26 @@ export interface IObjectShareControllerState {
|
|
|
97
97
|
pendingGeneralAccess?: GeneralAccessValue;
|
|
98
98
|
/** Grantees staged in the add-grantee dialog before confirmation. */
|
|
99
99
|
pendingGrantees: IUiPickedGrantee[];
|
|
100
|
+
/**
|
|
101
|
+
* The user picked as the new owner in the transfer-ownership subview, or
|
|
102
|
+
* undefined before one is chosen. Only users can own an object, so this is
|
|
103
|
+
* never a group.
|
|
104
|
+
*/
|
|
105
|
+
transferTarget: IUiGranteeAsyncOption | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Whether "Also remove my access" is checked in the transfer dialog. When
|
|
108
|
+
* true the current user's grant is removed after the transfer; otherwise it
|
|
109
|
+
* is downgraded to VIEW.
|
|
110
|
+
*/
|
|
111
|
+
transferAlsoRemoveSelf: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Whether `transferTarget` already owns the object (already holds EDIT). When
|
|
114
|
+
* true there is nothing to transfer — consumers show the "already an owner"
|
|
115
|
+
* variant offering to remove the current user's own access instead.
|
|
116
|
+
*/
|
|
117
|
+
transferTargetIsOwner: boolean;
|
|
118
|
+
/** Whether the transfer write (or self-removal) is in flight. */
|
|
119
|
+
transferSaving: boolean;
|
|
100
120
|
}
|
|
101
121
|
/**
|
|
102
122
|
* @internal
|
|
@@ -108,11 +128,13 @@ export interface IObjectShareControllerActions {
|
|
|
108
128
|
closeAddGrantee: () => void;
|
|
109
129
|
setPendingGrantees: (next: IUiPickedGrantee[]) => void;
|
|
110
130
|
/**
|
|
111
|
-
* Loader for the
|
|
131
|
+
* Loader for the grantee pickers. Wraps `getAvailableAssignees` with
|
|
112
132
|
* client-side search + already-picked filtering, returning the picker's
|
|
113
|
-
* `{ groups, users }` shape.
|
|
133
|
+
* `{ groups, users }` shape. By default excludes already-granted grantees
|
|
134
|
+
* (add-grantee picker); pass `includeGranted` to keep them (transfer-ownership
|
|
135
|
+
* picker, which may promote an existing viewer to owner).
|
|
114
136
|
*/
|
|
115
|
-
loadOptions: (search: string) => Promise<IUiGranteeAsyncOptions>;
|
|
137
|
+
loadOptions: (search: string, includeGranted?: boolean) => Promise<IUiGranteeAsyncOptions>;
|
|
116
138
|
/** Commit all pending grantees to the backend. */
|
|
117
139
|
confirmAddGrantees: () => Promise<void>;
|
|
118
140
|
/** Change the permission level for a single grantee. Auto-saves. */
|
|
@@ -136,6 +158,24 @@ export interface IObjectShareControllerActions {
|
|
|
136
158
|
* already-granted rule.
|
|
137
159
|
*/
|
|
138
160
|
changeWorkspaceLevel: (level: "VIEW" | "SHARE") => Promise<void>;
|
|
161
|
+
/** Open the transfer-ownership subview with an empty picker. */
|
|
162
|
+
openTransferOwnership: () => void;
|
|
163
|
+
/** Close the transfer-ownership subview and clear its buffers; returns to main. */
|
|
164
|
+
closeTransferOwnership: () => void;
|
|
165
|
+
/**
|
|
166
|
+
* Pick (or replace) the candidate new owner. Recomputes whether that user
|
|
167
|
+
* already owns the object (`transferTargetIsOwner`).
|
|
168
|
+
*/
|
|
169
|
+
setTransferTarget: (owner: IUiGranteeAsyncOption) => void;
|
|
170
|
+
/** Toggle "Also remove my access" in the transfer dialog. */
|
|
171
|
+
setTransferAlsoRemoveSelf: (next: boolean) => void;
|
|
172
|
+
/**
|
|
173
|
+
* Commit the ownership transfer: grant the picked user EDIT and downgrade the
|
|
174
|
+
* current user to VIEW (or remove them if "Also remove my access" is set), in
|
|
175
|
+
* one write. When the picked user already owns the object this transfers
|
|
176
|
+
* nothing and only applies the self-access choice. Auto-saves.
|
|
177
|
+
*/
|
|
178
|
+
confirmTransferOwnership: () => Promise<void>;
|
|
139
179
|
}
|
|
140
180
|
/**
|
|
141
181
|
* @internal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"objectShareController.types.d.ts","sourceRoot":"","sources":["../../src/share/objectShareController.types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"objectShareController.types.d.ts","sourceRoot":"","sources":["../../src/share/objectShareController.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,8 +36,20 @@ export interface IAccessList {
|
|
|
36
36
|
commit: (mutate: IGranularAccessGrantee[], successMessage: {
|
|
37
37
|
id: string;
|
|
38
38
|
}) => Promise<boolean>;
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Picker loader — available assignees filtered by query. By default excludes
|
|
41
|
+
* already-granted grantees (add-grantee picker); pass `includeGranted` to keep
|
|
42
|
+
* them (transfer-ownership picker, which may promote an existing viewer).
|
|
43
|
+
*/
|
|
44
|
+
loadOptions: (search: string, includeGranted?: boolean) => Promise<IUiGranteeAsyncOptions>;
|
|
45
|
+
/**
|
|
46
|
+
* The current user's ref, resolved on demand (and cached). Used by the
|
|
47
|
+
* transfer-ownership flow to write the current user's own grant change. The
|
|
48
|
+
* add-grantee *picker* excludes the current user, but `getAccessList` does
|
|
49
|
+
* not, so a self grant can still appear as a row. Rejects if the profile
|
|
50
|
+
* can't be read.
|
|
51
|
+
*/
|
|
52
|
+
getCurrentUserRef: () => Promise<ObjRef>;
|
|
41
53
|
/** Resolve a grantee id back to the picker's original ObjRef (preserves Uri vs Id ref). */
|
|
42
54
|
refForId: (id: string) => ObjRef;
|
|
43
55
|
/** Write through a local grantee-row change (insert / level / remove); rolled back by the caller. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAccessList.d.ts","sourceRoot":"","sources":["../../src/share/useAccessList.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAEH,KAAK,sBAAsB,EAC3B,KAAK,MAAM,EAEd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAmB,MAAM,sBAAsB,CAAC;AAW7G,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACzG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,wFAAwF;IACxF,OAAO,EAAE,OAAO,CAAC;IACjB,iGAA+F;IAC/F,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,uFAAqF;IACrF,aAAa,EAAE,kBAAkB,CAAC;IAClC,6FAA2F;IAC3F,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,iEAAiE;IACjE,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC9C,iDAAiD;IACjD,SAAS,EAAE,KAAK,GAAG,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B,mFAAmF;IACnF,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,EAAE,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F
|
|
1
|
+
{"version":3,"file":"useAccessList.d.ts","sourceRoot":"","sources":["../../src/share/useAccessList.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAEH,KAAK,sBAAsB,EAC3B,KAAK,MAAM,EAEd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAmB,MAAM,sBAAsB,CAAC;AAW7G,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACzG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,wFAAwF;IACxF,OAAO,EAAE,OAAO,CAAC;IACjB,iGAA+F;IAC/F,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,uFAAqF;IACrF,aAAa,EAAE,kBAAkB,CAAC;IAClC,6FAA2F;IAC3F,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,iEAAiE;IACjE,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC9C,iDAAiD;IACjD,SAAS,EAAE,KAAK,GAAG,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B,mFAAmF;IACnF,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,EAAE,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F;;;;OAIG;IACH,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC3F;;;;;;OAMG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,2FAA2F;IAC3F,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IAEjC,qGAAqG;IACrG,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC;IACzE,8EAA8E;IAC9E,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC3E;;;;OAIG;IACH,iBAAiB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAC1E,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CAC/E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GAClC,WAAW,CAoSb"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { objRefToString, } from "@gooddata/sdk-model";
|
|
4
4
|
import { useBackendStrict, useCancelablePromise, useWorkspaceStrict } from "@gooddata/sdk-ui";
|
|
5
5
|
import { useToastMessage } from "@gooddata/sdk-ui-kit";
|
|
@@ -41,6 +41,9 @@ export function useAccessList(target, onSaved) {
|
|
|
41
41
|
// Original ObjRef per grantee id, learned from the picker (the access-list id
|
|
42
42
|
// is a serialized `kind:identifier`, which loses Uri-vs-Id). Reused for writes.
|
|
43
43
|
const [knownRefs, setKnownRefs] = useState({});
|
|
44
|
+
// Memoized current-user-ref fetch — the profile doesn't change while mounted,
|
|
45
|
+
// so resolve it at most once and share the promise across callers.
|
|
46
|
+
const currentUserRefCache = useRef(undefined);
|
|
44
47
|
const targetKey = target ? objRefToString(target.ref) : undefined;
|
|
45
48
|
// Learn each assignee's real name + ref into the caches, keyed by the same
|
|
46
49
|
// grantee id the rows use. Written from both the picker (loadOptions) and the
|
|
@@ -190,10 +193,14 @@ export function useAccessList(target, onSaved) {
|
|
|
190
193
|
}
|
|
191
194
|
}, [backend, workspace, target, toast, onSaved]);
|
|
192
195
|
// Picker loader — fetches available assignees on demand, filters by the typed
|
|
193
|
-
// query, excludes anything already granted. Depends only on the
|
|
194
|
-
// (grantees), so its identity changes only when that set actually
|
|
195
|
-
// must NOT write state that feeds its own deps.
|
|
196
|
-
|
|
196
|
+
// query, and by default excludes anything already granted. Depends only on the
|
|
197
|
+
// granted set (grantees), so its identity changes only when that set actually
|
|
198
|
+
// changes. It must NOT write state that feeds its own deps.
|
|
199
|
+
//
|
|
200
|
+
// `includeGranted` keeps already-granted grantees in the result. The
|
|
201
|
+
// transfer-ownership picker needs this: promoting an existing viewer to owner
|
|
202
|
+
// is a primary case, and the add-grantee exclusion would otherwise hide them.
|
|
203
|
+
const loadOptions = useCallback(async (search, includeGranted = false) => {
|
|
197
204
|
if (!target) {
|
|
198
205
|
return { groups: [], users: [] };
|
|
199
206
|
}
|
|
@@ -210,7 +217,7 @@ export function useAccessList(target, onSaved) {
|
|
|
210
217
|
// even when the access-list grant later returns only a raw id. Safe here:
|
|
211
218
|
// the caches aren't dependencies of loadOptions, so this won't re-trigger it.
|
|
212
219
|
cacheAssignees(assignees);
|
|
213
|
-
const excluded = new Set(excludedIdsKey ? excludedIdsKey.split(",")
|
|
220
|
+
const excluded = new Set(includeGranted || !excludedIdsKey ? [] : excludedIdsKey.split(","));
|
|
214
221
|
const selectable = withIds
|
|
215
222
|
.filter(({ id }) => !excluded.has(id)) // hide anyone already granted
|
|
216
223
|
.filter(({ assignee }) => assigneeMatchesQuery(assignee, query));
|
|
@@ -231,6 +238,21 @@ export function useAccessList(target, onSaved) {
|
|
|
231
238
|
// Reuse the picker's original ref (preserves UriRef vs IdentifierRef);
|
|
232
239
|
// fall back to the serialized id only if it wasn't cached.
|
|
233
240
|
const refForId = useCallback((id) => knownRefs[id] ?? { identifier: id.split(":", 2)[1] }, [knownRefs]);
|
|
241
|
+
const getCurrentUserRef = useCallback(async () => {
|
|
242
|
+
if (!currentUserRefCache.current) {
|
|
243
|
+
currentUserRefCache.current = backend
|
|
244
|
+
.currentUser()
|
|
245
|
+
.getUser()
|
|
246
|
+
.then((user) => user.ref)
|
|
247
|
+
.catch((error) => {
|
|
248
|
+
// Don't cache a rejected promise, or a transient profile-read
|
|
249
|
+
// failure would make every later transfer fail immediately.
|
|
250
|
+
currentUserRefCache.current = undefined;
|
|
251
|
+
throw error;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return currentUserRefCache.current;
|
|
255
|
+
}, [backend]);
|
|
234
256
|
return {
|
|
235
257
|
targetKey,
|
|
236
258
|
hasList,
|
|
@@ -244,6 +266,7 @@ export function useAccessList(target, onSaved) {
|
|
|
244
266
|
commit,
|
|
245
267
|
loadOptions,
|
|
246
268
|
refForId,
|
|
269
|
+
getCurrentUserRef,
|
|
247
270
|
setGrantees,
|
|
248
271
|
setGeneralAccess,
|
|
249
272
|
setWorkspaceLevel,
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,OAAO,CAAC,EAAE,sBAAsB,GACjC,sBAAsB,CAwyBxB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { areObjRefsEqual } from "@gooddata/sdk-model";
|
|
4
|
+
import { useToastMessage, } from "@gooddata/sdk-ui-kit";
|
|
4
5
|
import { objectShareMessages } from "./messages.js";
|
|
5
6
|
import { EMPTY_IDS, NO_LABELS, effectivePermissionAbove, granularGranteeFor, toGranularGrantee, } from "./objectShareController.helpers.js";
|
|
6
7
|
import { useAccessList } from "./useAccessList.js";
|
|
@@ -35,6 +36,10 @@ export function useObjectShareController(target, options) {
|
|
|
35
36
|
const [subview, setSubview] = useState("main");
|
|
36
37
|
const [pendingGrantees, setPendingGrantees] = useState([]);
|
|
37
38
|
const [pendingGeneralAccess, setPendingGeneralAccess] = useState(undefined);
|
|
39
|
+
// Transfer-ownership buffers. Cleared on close and on target switch.
|
|
40
|
+
const [transferTarget, setTransferTargetState] = useState(undefined);
|
|
41
|
+
const [transferAlsoRemoveSelf, setTransferAlsoRemoveSelf] = useState(false);
|
|
42
|
+
const [transferSaving, setTransferSaving] = useState(false);
|
|
38
43
|
// True while a workspace-level re-grade is committing. The workspace rule has no
|
|
39
44
|
// grantee row to carry a `pending` flag (unlike named grantees), so this gates
|
|
40
45
|
// re-entry and disables the dropdown to keep rapid VIEW↔SHARE toggles from issuing
|
|
@@ -42,7 +47,7 @@ export function useObjectShareController(target, options) {
|
|
|
42
47
|
const [workspaceLevelSaving, setWorkspaceLevelSaving] = useState(false);
|
|
43
48
|
// The backend access list, seeded into local state (rows, general access,
|
|
44
49
|
// summary, the commit/picker primitives) lives in its own hook.
|
|
45
|
-
const { targetKey, hasList, grantees, generalAccess, workspaceLevel, summary, status, loadError, accessUnavailable, commit, loadOptions, refForId, setGrantees, setGeneralAccess, setWorkspaceLevel, setKnownNames, } = useAccessList(target, onSaved);
|
|
50
|
+
const { targetKey, hasList, grantees, generalAccess, workspaceLevel, summary, status, loadError, accessUnavailable, commit, loadOptions, refForId, getCurrentUserRef, setGrantees, setGeneralAccess, setWorkspaceLevel, setKnownNames, } = useAccessList(target, onSaved);
|
|
46
51
|
// Always-current target key, read inside async mutation finalizers that captured
|
|
47
52
|
// an older one. A write started for object A resolves after the user navigated to
|
|
48
53
|
// object B; because local state is authoritative (no refetch), applying A's
|
|
@@ -69,12 +74,21 @@ export function useObjectShareController(target, options) {
|
|
|
69
74
|
setPendingGrantees([]);
|
|
70
75
|
setPendingGeneralAccess(undefined);
|
|
71
76
|
setWorkspaceLevelSaving(false);
|
|
77
|
+
setTransferTargetState(undefined);
|
|
78
|
+
setTransferAlsoRemoveSelf(false);
|
|
79
|
+
setTransferSaving(false);
|
|
72
80
|
}
|
|
73
81
|
const reset = useCallback(() => {
|
|
74
82
|
setSubview("main");
|
|
75
83
|
setPendingGrantees([]);
|
|
76
84
|
setPendingGeneralAccess(undefined);
|
|
77
85
|
setWorkspaceLevelSaving(false);
|
|
86
|
+
setTransferTargetState(undefined);
|
|
87
|
+
setTransferAlsoRemoveSelf(false);
|
|
88
|
+
// Deliberately NOT clearing transferSaving here: closing the dialog must not
|
|
89
|
+
// release the transfer lock while the write is still in flight, or a reopened
|
|
90
|
+
// dialog could mutate concurrently with the unfinished transfer. The in-flight
|
|
91
|
+
// confirmTransferOwnership clears it itself once the write settles.
|
|
78
92
|
}, []);
|
|
79
93
|
const openAddGrantee = useCallback(() => {
|
|
80
94
|
setSubview("addGrantee");
|
|
@@ -426,6 +440,173 @@ export function useObjectShareController(target, options) {
|
|
|
426
440
|
}
|
|
427
441
|
setWorkspaceLevelSaving(false);
|
|
428
442
|
}, [generalAccess, workspaceLevel, workspaceLevelSaving, targetKey, commit, setWorkspaceLevel]);
|
|
443
|
+
const openTransferOwnership = useCallback(() => {
|
|
444
|
+
setSubview("transferOwnership");
|
|
445
|
+
setTransferTargetState(undefined);
|
|
446
|
+
setTransferAlsoRemoveSelf(false);
|
|
447
|
+
}, []);
|
|
448
|
+
const closeTransferOwnership = useCallback(() => {
|
|
449
|
+
setSubview("main");
|
|
450
|
+
setTransferTargetState(undefined);
|
|
451
|
+
setTransferAlsoRemoveSelf(false);
|
|
452
|
+
}, []);
|
|
453
|
+
const setTransferTarget = useCallback((owner) => {
|
|
454
|
+
setTransferTargetState(owner);
|
|
455
|
+
}, []);
|
|
456
|
+
// The picked user already owns the object when a grantee row for them holds
|
|
457
|
+
// EDIT — then there's nothing to transfer (the dialog offers to remove the
|
|
458
|
+
// current user's own access instead).
|
|
459
|
+
const transferTargetIsOwner = useMemo(() => transferTarget ? grantees.some((g) => g.id === transferTarget.id && g.level === "EDIT") : false, [transferTarget, grantees]);
|
|
460
|
+
// Insert a brand-new grantee row with its label scope and display name — the
|
|
461
|
+
// shared seed for a freshly-granted principal (used when a transfer promotes a
|
|
462
|
+
// user who wasn't already a grantee; the same row shape the add flow creates).
|
|
463
|
+
const seedNewGranteeRow = useCallback((row, labelIds) => {
|
|
464
|
+
setGrantees((prev) => [...prev, row]);
|
|
465
|
+
setKnownNames((prev) => ({ ...prev, [row.id]: row.name }));
|
|
466
|
+
setSelectedLabelIdsByGrantee((prev) => ({ ...prev, [row.id]: labelIds }));
|
|
467
|
+
}, [setGrantees, setKnownNames, setSelectedLabelIdsByGrantee]);
|
|
468
|
+
const confirmTransferOwnership = useCallback(async () => {
|
|
469
|
+
if (!transferTarget || transferSaving) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const startedFor = targetKey;
|
|
473
|
+
const ownerRef = refForId(transferTarget.id);
|
|
474
|
+
let selfRef;
|
|
475
|
+
try {
|
|
476
|
+
selfRef = await getCurrentUserRef();
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
toast.addError(objectShareMessages.toastError);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (targetKeyRef.current !== startedFor) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
// Guard against transferring to yourself: the picker's loader already
|
|
486
|
+
// excludes the current user, but if a backend ever returned self the batch
|
|
487
|
+
// below would emit conflicting grants for one ref (EDIT + VIEW/none). Bail
|
|
488
|
+
// and just close — there is nothing to transfer to oneself.
|
|
489
|
+
if (areObjRefsEqual(ownerRef, selfRef)) {
|
|
490
|
+
closeTransferOwnership();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Build the write. A real transfer grants the picked user EDIT and changes
|
|
494
|
+
// the current user's own grant: kept as VIEW by default, or removed when
|
|
495
|
+
// "Also remove my access" is set. When the picked user already owns the
|
|
496
|
+
// object there's nothing to transfer — only the self-access choice applies,
|
|
497
|
+
// and "keep" is then a pure no-op (don't downgrade an existing owner-self to
|
|
498
|
+
// VIEW just for confirming).
|
|
499
|
+
const mutations = [];
|
|
500
|
+
if (!transferTargetIsOwner) {
|
|
501
|
+
mutations.push(toGranularGrantee("user", ownerRef, "EDIT"));
|
|
502
|
+
mutations.push(toGranularGrantee("user", selfRef, transferAlsoRemoveSelf ? "none" : "VIEW"));
|
|
503
|
+
}
|
|
504
|
+
else if (transferAlsoRemoveSelf) {
|
|
505
|
+
mutations.push(toGranularGrantee("user", selfRef, "none"));
|
|
506
|
+
}
|
|
507
|
+
if (mutations.length === 0) {
|
|
508
|
+
closeTransferOwnership();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
// Whether the new owner already has a grantee row (was granted before this
|
|
512
|
+
// transfer). A brand-new owner needs a row seeded and their labels mirrored,
|
|
513
|
+
// exactly like the add-grantee flow; an existing grantee already has both.
|
|
514
|
+
const existingOwnerRow = transferTargetIsOwner
|
|
515
|
+
? undefined
|
|
516
|
+
: grantees.find((g) => g.id === transferTarget.id);
|
|
517
|
+
const ownerIsNew = !transferTargetIsOwner && !existingOwnerRow;
|
|
518
|
+
const allLabelIds = effectiveLabels.map((l) => l.id);
|
|
519
|
+
setTransferSaving(true);
|
|
520
|
+
// "Ownership transferred" only when ownership actually moves; the
|
|
521
|
+
// already-owner path just changes the current user's own access.
|
|
522
|
+
const successMessage = transferTargetIsOwner
|
|
523
|
+
? objectShareMessages.toastAccessUpdated
|
|
524
|
+
: objectShareMessages.toastOwnershipTransferred;
|
|
525
|
+
const ok = await commit(mutations, successMessage);
|
|
526
|
+
if (targetKeyRef.current !== startedFor) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (!ok) {
|
|
530
|
+
setTransferSaving(false);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
// Mirror per-label grants for the same reason add/remove do: label
|
|
534
|
+
// permissions are independent access-list entries, so the object write
|
|
535
|
+
// alone would leave a new owner without label access, or leave a removed
|
|
536
|
+
// self with orphaned label grants. A failed label write is non-fatal here
|
|
537
|
+
// (the object grant is what matters) — surfaced as a warning, like remove.
|
|
538
|
+
//
|
|
539
|
+
// Do every backend label write first, then apply all local state behind a
|
|
540
|
+
// single target guard. Since no local write happens before that guard, a
|
|
541
|
+
// target switch during a label await can't land a half-applied transfer on
|
|
542
|
+
// the next object's seeded list — unlike the optimistic finalizers, which
|
|
543
|
+
// write before committing and so must re-guard after each await.
|
|
544
|
+
let labelsOk = true;
|
|
545
|
+
if (ownerIsNew) {
|
|
546
|
+
// Grant the brand-new owner every label — the add-grantee default scope.
|
|
547
|
+
const granted = await reconcileLabelScope({ kind: "user", granteeRef: ownerRef }, new Set(allLabelIds), EMPTY_IDS);
|
|
548
|
+
labelsOk = labelsOk && granted;
|
|
549
|
+
}
|
|
550
|
+
if (transferAlsoRemoveSelf) {
|
|
551
|
+
// Self lost object access — revoke their per-label grants too.
|
|
552
|
+
const revoked = await reconcileLabelScope({ kind: "user", granteeRef: selfRef }, EMPTY_IDS, new Set(allLabelIds));
|
|
553
|
+
labelsOk = labelsOk && revoked;
|
|
554
|
+
}
|
|
555
|
+
if (targetKeyRef.current !== startedFor) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
// Reflect the object-grant changes locally (authoritative, no refetch).
|
|
559
|
+
if (!transferTargetIsOwner) {
|
|
560
|
+
const nextEffective = effectivePermissionAbove("EDIT", []);
|
|
561
|
+
if (ownerIsNew) {
|
|
562
|
+
seedNewGranteeRow({
|
|
563
|
+
id: transferTarget.id,
|
|
564
|
+
kind: "user",
|
|
565
|
+
granteeRef: ownerRef,
|
|
566
|
+
name: transferTarget.name,
|
|
567
|
+
level: "EDIT",
|
|
568
|
+
effectivePermission: nextEffective,
|
|
569
|
+
}, allLabelIds);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
// Existing grantee: their label scope is unchanged by an
|
|
573
|
+
// object-level promotion, so only the row's level is raised.
|
|
574
|
+
setGrantees((prev) => prev.map((g) => g.id === transferTarget.id
|
|
575
|
+
? { ...g, level: "EDIT", effectivePermission: nextEffective }
|
|
576
|
+
: g));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// The current user may have their own grantee row (the access list keeps
|
|
580
|
+
// every grant, including self) — reflect their object-grant change locally
|
|
581
|
+
// too: dropped when "remove my access", otherwise downgraded to VIEW. Match
|
|
582
|
+
// by ref since self isn't tied to a picker-derived row id.
|
|
583
|
+
setGrantees((prev) => transferAlsoRemoveSelf
|
|
584
|
+
? prev.filter((g) => !areObjRefsEqual(g.granteeRef, selfRef))
|
|
585
|
+
: prev.map((g) => areObjRefsEqual(g.granteeRef, selfRef)
|
|
586
|
+
? { ...g, level: "VIEW", effectivePermission: undefined }
|
|
587
|
+
: g));
|
|
588
|
+
if (!labelsOk) {
|
|
589
|
+
toast.addWarning(objectShareMessages.toastLabelScopePartial);
|
|
590
|
+
}
|
|
591
|
+
setTransferSaving(false);
|
|
592
|
+
closeTransferOwnership();
|
|
593
|
+
}, [
|
|
594
|
+
transferTarget,
|
|
595
|
+
transferSaving,
|
|
596
|
+
transferTargetIsOwner,
|
|
597
|
+
transferAlsoRemoveSelf,
|
|
598
|
+
targetKey,
|
|
599
|
+
grantees,
|
|
600
|
+
effectiveLabels,
|
|
601
|
+
refForId,
|
|
602
|
+
getCurrentUserRef,
|
|
603
|
+
commit,
|
|
604
|
+
reconcileLabelScope,
|
|
605
|
+
toast,
|
|
606
|
+
setGrantees,
|
|
607
|
+
seedNewGranteeRow,
|
|
608
|
+
closeTransferOwnership,
|
|
609
|
+
]);
|
|
429
610
|
const actions = useMemo(() => ({
|
|
430
611
|
reset,
|
|
431
612
|
openAddGrantee,
|
|
@@ -440,6 +621,11 @@ export function useObjectShareController(target, options) {
|
|
|
440
621
|
cancelGeneralAccessChange: () => setPendingGeneralAccess(undefined),
|
|
441
622
|
confirmGeneralAccessChange,
|
|
442
623
|
changeWorkspaceLevel,
|
|
624
|
+
openTransferOwnership,
|
|
625
|
+
closeTransferOwnership,
|
|
626
|
+
setTransferTarget,
|
|
627
|
+
setTransferAlsoRemoveSelf,
|
|
628
|
+
confirmTransferOwnership,
|
|
443
629
|
}), [
|
|
444
630
|
reset,
|
|
445
631
|
openAddGrantee,
|
|
@@ -452,6 +638,10 @@ export function useObjectShareController(target, options) {
|
|
|
452
638
|
requestGeneralAccessChange,
|
|
453
639
|
confirmGeneralAccessChange,
|
|
454
640
|
changeWorkspaceLevel,
|
|
641
|
+
openTransferOwnership,
|
|
642
|
+
closeTransferOwnership,
|
|
643
|
+
setTransferTarget,
|
|
644
|
+
confirmTransferOwnership,
|
|
455
645
|
]);
|
|
456
646
|
const state = useMemo(() => ({
|
|
457
647
|
subview,
|
|
@@ -471,6 +661,10 @@ export function useObjectShareController(target, options) {
|
|
|
471
661
|
selectedLabelIdsByGrantee,
|
|
472
662
|
pendingGeneralAccess,
|
|
473
663
|
pendingGrantees,
|
|
664
|
+
transferTarget,
|
|
665
|
+
transferAlsoRemoveSelf,
|
|
666
|
+
transferTargetIsOwner,
|
|
667
|
+
transferSaving,
|
|
474
668
|
}), [
|
|
475
669
|
subview,
|
|
476
670
|
status,
|
|
@@ -486,6 +680,10 @@ export function useObjectShareController(target, options) {
|
|
|
486
680
|
selectedLabelIdsByGrantee,
|
|
487
681
|
pendingGeneralAccess,
|
|
488
682
|
pendingGrantees,
|
|
683
|
+
transferTarget,
|
|
684
|
+
transferAlsoRemoveSelf,
|
|
685
|
+
transferTargetIsOwner,
|
|
686
|
+
transferSaving,
|
|
489
687
|
]);
|
|
490
688
|
return useMemo(() => ({ state, actions }), [state, actions]);
|
|
491
689
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-ext",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.45.0-alpha.1",
|
|
4
4
|
"description": "GoodData.UI SDK - Extensions",
|
|
5
5
|
"license": "LicenseRef-LICENSE",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -63,20 +63,20 @@
|
|
|
63
63
|
"ts-invariant": "0.10.3",
|
|
64
64
|
"tslib": "2.8.1",
|
|
65
65
|
"uuid": "11.1.1",
|
|
66
|
-
"@gooddata/sdk-backend-base": "11.
|
|
67
|
-
"@gooddata/sdk-backend-spi": "11.
|
|
68
|
-
"@gooddata/sdk-embedding": "11.
|
|
69
|
-
"@gooddata/sdk-model": "11.
|
|
70
|
-
"@gooddata/sdk-ui": "11.
|
|
71
|
-
"@gooddata/sdk-ui-
|
|
72
|
-
"@gooddata/sdk-ui-
|
|
73
|
-
"@gooddata/sdk-ui-geo": "11.
|
|
74
|
-
"@gooddata/sdk-ui-
|
|
75
|
-
"@gooddata/sdk-ui-
|
|
76
|
-
"@gooddata/sdk-ui-theme-provider": "11.
|
|
77
|
-
"@gooddata/sdk-ui-
|
|
78
|
-
"@gooddata/
|
|
79
|
-
"@gooddata/
|
|
66
|
+
"@gooddata/sdk-backend-base": "11.45.0-alpha.1",
|
|
67
|
+
"@gooddata/sdk-backend-spi": "11.45.0-alpha.1",
|
|
68
|
+
"@gooddata/sdk-embedding": "11.45.0-alpha.1",
|
|
69
|
+
"@gooddata/sdk-model": "11.45.0-alpha.1",
|
|
70
|
+
"@gooddata/sdk-ui": "11.45.0-alpha.1",
|
|
71
|
+
"@gooddata/sdk-ui-charts": "11.45.0-alpha.1",
|
|
72
|
+
"@gooddata/sdk-ui-filters": "11.45.0-alpha.1",
|
|
73
|
+
"@gooddata/sdk-ui-geo": "11.45.0-alpha.1",
|
|
74
|
+
"@gooddata/sdk-ui-pivot": "11.45.0-alpha.1",
|
|
75
|
+
"@gooddata/sdk-ui-semantic-search": "11.45.0-alpha.1",
|
|
76
|
+
"@gooddata/sdk-ui-theme-provider": "11.45.0-alpha.1",
|
|
77
|
+
"@gooddata/sdk-ui-kit": "11.45.0-alpha.1",
|
|
78
|
+
"@gooddata/util": "11.45.0-alpha.1",
|
|
79
|
+
"@gooddata/sdk-ui-vis-commons": "11.45.0-alpha.1"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@microsoft/api-documenter": "^7.17.0",
|
|
@@ -124,12 +124,12 @@
|
|
|
124
124
|
"typescript": "5.9.3",
|
|
125
125
|
"vitest": "4.1.8",
|
|
126
126
|
"vitest-dom": "0.1.1",
|
|
127
|
-
"@gooddata/
|
|
128
|
-
"@gooddata/oxlint-config": "11.
|
|
129
|
-
"@gooddata/reference-workspace": "11.
|
|
130
|
-
"@gooddata/
|
|
131
|
-
"@gooddata/
|
|
132
|
-
"@gooddata/stylelint-config": "11.
|
|
127
|
+
"@gooddata/eslint-config": "11.45.0-alpha.1",
|
|
128
|
+
"@gooddata/oxlint-config": "11.45.0-alpha.1",
|
|
129
|
+
"@gooddata/reference-workspace": "11.45.0-alpha.1",
|
|
130
|
+
"@gooddata/i18n-toolkit": "11.45.0-alpha.1",
|
|
131
|
+
"@gooddata/sdk-backend-mockingbird": "11.45.0-alpha.1",
|
|
132
|
+
"@gooddata/stylelint-config": "11.45.0-alpha.1"
|
|
133
133
|
},
|
|
134
134
|
"peerDependencies": {
|
|
135
135
|
"react": "^18.0.0 || ^19.0.0",
|