diff --git a/src/server/components/features/features-list/EnableNewAccessDialog.ts b/src/server/components/features/features-list/EnableNewAccessDialog.ts new file mode 100644 index 0000000000..a70acf030f --- /dev/null +++ b/src/server/components/features/features-list/EnableNewAccessDialog.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableNewAccessDialog, + state: { + development: false, + production: false, + }, +}); diff --git a/src/shared/schema/extensions/actions/iam-access-dialog.ts b/src/shared/schema/extensions/actions/iam-access-dialog.ts index 6e69dab45d..83c74e1210 100644 --- a/src/shared/schema/extensions/actions/iam-access-dialog.ts +++ b/src/shared/schema/extensions/actions/iam-access-dialog.ts @@ -1,6 +1,8 @@ import {createAction} from '../../gateway-utils'; import type {GetDatalensOperationResponse} from '../../us/types/operations'; import type { + BatchListAccessBindingsArgs, + BatchListAccessBindingsResponse, BatchListMembersArgs, BatchListMembersResponse, GetClaimsArgs, @@ -53,4 +55,13 @@ export const iamAccessDialogActions = { batchListMembers: createAction(async () => { return {members: [], nextPageToken: ''}; }), + batchListAccessBindings: createAction< + BatchListAccessBindingsResponse, + BatchListAccessBindingsArgs + >(async () => { + return { + subjectsWithBindings: [], + nextPageToken: '', + }; + }), }; diff --git a/src/shared/schema/extensions/types/iam-access-dialog.ts b/src/shared/schema/extensions/types/iam-access-dialog.ts index e306975577..48f7aadfcb 100644 --- a/src/shared/schema/extensions/types/iam-access-dialog.ts +++ b/src/shared/schema/extensions/types/iam-access-dialog.ts @@ -1,6 +1,7 @@ import type {Lang} from '../../..'; export enum AccessServiceResourceType { + Organization = 'organization-manager.organization', Collection = 'datalens.collection', Workbook = 'datalens.workbook', } @@ -20,6 +21,7 @@ export enum SubjectType { } export enum ClaimsSubjectType { + Unspecified = 'SUBJECT_TYPE_UNSPECIFIED', UserAccount = 'USER_ACCOUNT', Group = 'GROUP', Invitee = 'INVITEE', @@ -122,6 +124,7 @@ export interface SubjectClaims { pictureData?: string; picture?: string; idpType?: string | null; + displayName?: string; } export type SubjectDetails = { @@ -146,3 +149,41 @@ export type BatchListMembersResponse = { members: SubjectClaims[]; nextPageToken: string; }; + +export interface BatchListAccessBindingsResponse { + subjectsWithBindings: SubjectWithBindings[]; + nextPageToken: string; +} + +export interface SubjectWithBindings { + subjectClaims: SubjectClaims; + accessBindings: InheritedAccessBindings[]; + inheritedAccessBindings: InheritedAccessBindings[]; +} + +export interface InheritedAccessBindings { + roleId: string; + inheritedFrom: AccessBindingsResource | null; +} + +export interface AccessBindingsResource { + id: string; + type: string; +} + +export type BatchListAccessBindingsArgs = { + resourcePath: AccessBindingsResource[]; + getInheritedBindings?: boolean; + filter?: string; + pageSize?: number; + pageToken?: string; +}; + +export enum ResourceType { + Collection = 'collection', + Workbook = 'workbook', +} + +export type UpdateAccessBindingsRequest = { + accessBindingDeltas: AccessBindingDelta[]; +}; diff --git a/src/shared/types/feature.ts b/src/shared/types/feature.ts index c5ad8f987e..5a927759c2 100644 --- a/src/shared/types/feature.ts +++ b/src/shared/types/feature.ts @@ -93,6 +93,7 @@ export enum Feature { EnableSharedEntries = 'EnableSharedEntries', EnableMobileFixedHeader = 'EnableMobileFixedHeader', + /** enabled redesign/moving to drawers existing settings */ EnableCommonChartDashSettings = 'EnableCommonChartDashSettings', /** Enable a setting in the Selector settings dialog that allows you to make the selector pass-through for all or several tabs */ @@ -101,6 +102,8 @@ export enum Feature { EnableNewDashSettings = 'EnableNewDashSettings', /** Shows updated settings page */ EnableNewServiceSettings = 'EnableNewServiceSettings', + /** Enable new access dialog (AccessDialog) */ + EnableNewAccessDialog = 'EnableNewAccessDialog', } export type FeatureConfig = Record; diff --git a/src/ui/components/AccessDialog/index.tsx b/src/ui/components/AccessDialog/index.tsx new file mode 100644 index 0000000000..1313985fae --- /dev/null +++ b/src/ui/components/AccessDialog/index.tsx @@ -0,0 +1,8 @@ +import type {AccessDialogProps} from 'ui/registry/units/common/types/components/AccessDialog'; + +export const DIALOG_ACCESS = Symbol('DIALOG_ACCESS'); + +export type OpenDialogAccessDialogArgs = { + id: typeof DIALOG_ACCESS; + props: AccessDialogProps; +}; diff --git a/src/ui/registry/units/common/components-map.tsx b/src/ui/registry/units/common/components-map.tsx index 87c4c78ea6..e2b8a43cef 100644 --- a/src/ui/registry/units/common/components-map.tsx +++ b/src/ui/registry/units/common/components-map.tsx @@ -9,6 +9,7 @@ import {makeDefaultEmpty} from '../../components/DefaultEmpty'; import {Example} from './components/Example/Example'; import {EXAMPLE_COMPONENT} from './constants/components'; +import type {AccessDialogProps} from './types/components/AccessDialog'; import type {AccessRightsProps} from './types/components/AccessRights'; import type {AccessRightsUrlOpenProps} from './types/components/AccessRightsUrlOpen'; import type {AclSubjectProps} from './types/components/AclSubject'; @@ -66,4 +67,5 @@ export const commonComponentsMap = { WorkbookEntriesTableTabs: makeDefaultEmpty(), WorkbookEntryExtended: makeDefaultEmpty(), DialogEntryDescription: makeDefaultEmpty(), + AccessDialog: makeDefaultEmpty(), } as const; diff --git a/src/ui/registry/units/common/types/components/AccessDialog.ts b/src/ui/registry/units/common/types/components/AccessDialog.ts new file mode 100644 index 0000000000..7ed04ba880 --- /dev/null +++ b/src/ui/registry/units/common/types/components/AccessDialog.ts @@ -0,0 +1,9 @@ +export type AccessDialogProps = { + entryId?: string; + workbookId?: string; + collectionId?: string; + resourceTitle?: string; + canUpdateAccessBindings: boolean; + onClose?: () => void; + defaultTab?: string; +}; diff --git a/src/ui/registry/units/common/types/functions/useSubjectsListId.ts b/src/ui/registry/units/common/types/functions/useSubjectsListId.ts index 56de2fbc63..807bb4f009 100644 --- a/src/ui/registry/units/common/types/functions/useSubjectsListId.ts +++ b/src/ui/registry/units/common/types/functions/useSubjectsListId.ts @@ -1 +1,5 @@ -export type UseSubjectsListId = {type: 'organizationId' | 'cloudId'; id: string | undefined}; +export type UseSubjectsListId = { + type: 'organizationId' | 'cloudId'; + id: string | undefined; + title: string | undefined; +}; diff --git a/src/ui/store/actions/openDialogTypes.ts b/src/ui/store/actions/openDialogTypes.ts index 52846f2556..d6044d8109 100644 --- a/src/ui/store/actions/openDialogTypes.ts +++ b/src/ui/store/actions/openDialogTypes.ts @@ -61,6 +61,7 @@ import type {OpenDialogExportWorkbookArgs} from 'ui/components/CollectionsStruct import type {OpenDialogDefaultArgs} from 'ui/components/DialogDefault/DialogDefault'; import type {OpenDialogCreatePublicGalleryWorkbookArgs} from 'ui/components/CollectionsStructure/CreatePublicGalleryWorkbookDialog'; import type {OpenDialogEntryDescriptionArgs} from 'ui/components/DialogEntryDescription/DialogEntryDescriptionWrapper'; +import type {OpenDialogAccessDialogArgs} from 'ui/components/AccessDialog'; import type {OpenDialogSharedEntryBindingArgs} from 'ui/components/DialogSharedEntryBindings/DialogSharedEntryBindings'; import type {OpenDialogSharedEntryUnbindArgs} from 'ui/components/DialogSharedEntryUnbind/DialogSharedEntryUnbind'; import type {OpenDialogSharedEntryPermissionsArgs} from 'ui/components/DialogSharedEntryPermissions/DialogSharedEntryPermissions'; @@ -126,6 +127,8 @@ export type OpenDialogArgs = | OpenDialogExportWorkbookArgs | OpenDialogDefaultArgs | OpenDialogCreatePublicGalleryWorkbookArgs + | OpenDialogEntryDescriptionArgs + | OpenDialogAccessDialogArgs | OpenDialogSharedEntryBindingArgs | OpenDialogSharedEntryUnbindArgs | OpenDialogSharedEntryPermissionsArgs diff --git a/src/ui/units/collections/components/CollectionContent/hooks/useActions.tsx b/src/ui/units/collections/components/CollectionContent/hooks/useActions.tsx index 61dca9ee33..be30b75358 100644 --- a/src/ui/units/collections/components/CollectionContent/hooks/useActions.tsx +++ b/src/ui/units/collections/components/CollectionContent/hooks/useActions.tsx @@ -6,6 +6,7 @@ import {I18n} from 'i18n'; import {useDispatch} from 'react-redux'; import {useHistory} from 'react-router-dom'; import {WORKBOOK_STATUS} from 'shared/constants/workbooks'; +import {DIALOG_ACCESS} from 'ui/components/AccessDialog'; import {DIALOG_EXPORT_WORKBOOK} from 'ui/components/CollectionsStructure/ExportWorkbookDialog/ExportWorkbookDialog'; import {DIALOG_SHARED_ENTRY_BINDINGS} from 'ui/components/DialogSharedEntryBindings/DialogSharedEntryBindings'; import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; @@ -49,6 +50,31 @@ type UseActionsArgs = { onCloseMoveDialog: (structureChanged: boolean) => void; }; +const openAccessDialog = ( + dispatch: AppDispatch, + params: { + collectionId?: string; + workbookId?: string; + resourceTitle?: string; + canUpdateAccessBindings?: boolean; + }, +) => { + dispatch( + openDialog({ + id: DIALOG_ACCESS, + props: { + collectionId: params.collectionId, + workbookId: params.workbookId, + resourceTitle: params.resourceTitle, + canUpdateAccessBindings: params.canUpdateAccessBindings ?? false, + onClose: () => { + dispatch(closeDialog()); + }, + }, + }), + ); +}; + export const useActions = ({fetchStructureItems, onCloseMoveDialog}: UseActionsArgs) => { const collectionsAccessEnabled = isEnabledFeature(Feature.CollectionsAccessEnabled); @@ -115,25 +141,34 @@ export const useActions = ({fetchStructureItems, onCloseMoveDialog}: UseActionsA } if (collectionsAccessEnabled && item.permissions.listAccessBindings) { + const isNewAccessDialogEnabled = isEnabledFeature(Feature.EnableNewAccessDialog); actions.push({ text: , action: () => { - dispatch( - openDialog({ - id: DIALOG_IAM_ACCESS, - props: { - open: true, - resourceId: item.collectionId, - resourceType: ResourceType.Collection, - resourceTitle: item.title, - parentId: item.parentId, - canUpdate: item.permissions.updateAccessBindings, - onClose: () => { - dispatch(closeDialog()); + if (isNewAccessDialogEnabled) { + openAccessDialog(dispatch, { + collectionId: item?.collectionId ?? undefined, + resourceTitle: item?.title, + canUpdateAccessBindings: item?.permissions.updateAccessBindings, + }); + } else { + dispatch( + openDialog({ + id: DIALOG_IAM_ACCESS, + props: { + open: true, + resourceId: item.collectionId, + resourceType: ResourceType.Collection, + resourceTitle: item.title, + parentId: item.parentId, + canUpdate: item.permissions.updateAccessBindings, + onClose: () => { + dispatch(closeDialog()); + }, }, - }, - }), - ); + }), + ); + } }, }); } @@ -293,25 +328,35 @@ export const useActions = ({fetchStructureItems, onCloseMoveDialog}: UseActionsA } if (collectionsAccessEnabled && item.permissions.listAccessBindings) { + const isNewAccessDialogEnabled = isEnabledFeature(Feature.EnableNewAccessDialog); actions.push({ text: , action: () => { - dispatch( - openDialog({ - id: DIALOG_IAM_ACCESS, - props: { - open: true, - resourceId: item.workbookId, - resourceType: ResourceType.Workbook, - resourceTitle: item.title, - parentId: item.collectionId, - canUpdate: item.permissions.updateAccessBindings, - onClose: () => { - dispatch(closeDialog()); + if (isNewAccessDialogEnabled) { + openAccessDialog(dispatch, { + workbookId: item?.workbookId ?? undefined, + collectionId: item?.collectionId ?? undefined, + resourceTitle: item?.title, + canUpdateAccessBindings: item?.permissions.updateAccessBindings, + }); + } else { + dispatch( + openDialog({ + id: DIALOG_IAM_ACCESS, + props: { + open: true, + resourceId: item.workbookId, + resourceType: ResourceType.Workbook, + resourceTitle: item.title, + parentId: item.collectionId, + canUpdate: item.permissions.updateAccessBindings, + onClose: () => { + dispatch(closeDialog()); + }, }, - }, - }), - ); + }), + ); + } }, }); } diff --git a/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx b/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx index dde68c1ed8..e88e1abfcd 100644 --- a/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx +++ b/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx @@ -7,6 +7,7 @@ import block from 'bem-cn-lite'; import {I18n} from 'i18n'; import {batch, useDispatch, useSelector} from 'react-redux'; import {useHistory} from 'react-router-dom'; +import {DIALOG_ACCESS} from 'ui/components/AccessDialog'; import {getParentCollectionPath} from 'ui/units/collections-navigation/utils'; import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; @@ -259,24 +260,47 @@ export const useLayout = ({ }} onEditAccessClick={() => { if (collectionsAccessEnabled && curCollectionId && collection) { - dispatch( - openDialog({ - id: DIALOG_IAM_ACCESS, - props: { - open: true, - resourceId: collection.collectionId, - resourceType: ResourceType.Collection, - resourceTitle: collection.title, - parentId: collection.parentId, - canUpdate: Boolean( - collection.permissions?.updateAccessBindings, - ), - onClose: () => { - dispatch(closeDialog()); - }, - }, - }), + const isNewAccessDialogEnabled = isEnabledFeature( + Feature.EnableNewAccessDialog, ); + if (isNewAccessDialogEnabled) { + dispatch( + openDialog({ + id: DIALOG_ACCESS, + props: { + collectionId: collection.collectionId, + resourceTitle: collection.title, + canUpdateAccessBindings: Boolean( + collection.permissions + ?.updateAccessBindings, + ), + onClose: () => { + dispatch(closeDialog()); + }, + }, + }), + ); + } else { + dispatch( + openDialog({ + id: DIALOG_IAM_ACCESS, + props: { + open: true, + resourceId: collection.collectionId, + resourceType: ResourceType.Collection, + resourceTitle: collection.title, + parentId: collection.parentId, + canUpdate: Boolean( + collection.permissions + ?.updateAccessBindings, + ), + onClose: () => { + dispatch(closeDialog()); + }, + }, + }), + ); + } } }} /> diff --git a/src/ui/units/workbooks/components/WorkbookActions/WorkbookActions.tsx b/src/ui/units/workbooks/components/WorkbookActions/WorkbookActions.tsx index 1a985fe88d..22d7a50335 100644 --- a/src/ui/units/workbooks/components/WorkbookActions/WorkbookActions.tsx +++ b/src/ui/units/workbooks/components/WorkbookActions/WorkbookActions.tsx @@ -13,6 +13,7 @@ import {I18N} from 'i18n'; import {useDispatch} from 'react-redux'; import {useHistory, useLocation} from 'react-router-dom'; import {WorkbookPageActionsMoreQA} from 'shared/constants/qa'; +import {DIALOG_ACCESS} from 'ui/components/AccessDialog'; import {DIALOG_EXPORT_WORKBOOK} from 'ui/components/CollectionsStructure/ExportWorkbookDialog/ExportWorkbookDialog'; import {DropdownAction} from 'ui/components/DropdownAction/DropdownAction'; import {closeDialog, openDialog} from 'ui/store/actions/dialog'; @@ -193,6 +194,23 @@ export const WorkbookActions: React.FC = ({workbook, refreshWorkbookInfo} dropdownActions.push([...otherActions]); } + const onOpenAccessDialog = () => { + dispatch( + openDialog({ + id: DIALOG_ACCESS, + props: { + workbookId: workbook?.workbookId ?? undefined, + collectionId: workbook?.collectionId ?? undefined, + resourceTitle: workbook.title, + canUpdateAccessBindings: workbook.permissions.updateAccessBindings, + onClose: () => { + dispatch(closeDialog()); + }, + }, + }), + ); + }; + return (
{Boolean(dropdownActions.length) && ( @@ -210,7 +228,11 @@ export const WorkbookActions: React.FC = ({workbook, refreshWorkbookInfo}