diff --git a/src/i18n-keysets/component.dialog-collection-no-create-permission.view/en.json b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/en.json new file mode 100644 index 0000000000..539fd364d6 --- /dev/null +++ b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/en.json @@ -0,0 +1,5 @@ +{ + "button_ok": "Ok", + "label_title": "Insufficient permissions", + "section_message": "To create collections and workbooks, you need the datalens.editor role. Please contact your administrator." +} diff --git a/src/i18n-keysets/component.dialog-collection-no-create-permission.view/ru.json b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/ru.json new file mode 100644 index 0000000000..b3b88d7a57 --- /dev/null +++ b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_ok": "Понятно", + "label_title": "Недостаточно прав", + "section_message": "Для создания коллекций и воркбуков необходима роль datalens.editor, обратитесь к администратору." +} diff --git a/src/shared/constants/qa/components.ts b/src/shared/constants/qa/components.ts index d650566a23..102cc4b4dc 100644 --- a/src/shared/constants/qa/components.ts +++ b/src/shared/constants/qa/components.ts @@ -107,6 +107,11 @@ export enum DialogConfirmQA { CancelButton = 'dialog-confirm-cancel-button', } +export enum DialogInfoQA { + Dialog = 'dialog-info', + CloseButton = 'dialog-close-button', +} + export enum DialogWarningQA { Dialog = 'dialog-warning', ApplyButton = 'dialog-warning-apply-button', diff --git a/src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/CollectionNoCreatePermissionDialog.tsx b/src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/CollectionNoCreatePermissionDialog.tsx new file mode 100644 index 0000000000..78f4dc0ae7 --- /dev/null +++ b/src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/CollectionNoCreatePermissionDialog.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +import {I18n} from 'i18n'; +import DialogWarning from 'ui/components/DialogWarning/DialogWarning'; + +import {registry} from '../../../registry'; +import DialogManager from '../../DialogManager/DialogManager'; +import {YfmWrapperContent as YfmWrapper} from '../../YfmWrapper/YfmWrapperContent'; + +const i18n = I18n.keyset('component.dialog-collection-no-create-permission.view'); + +export const DIALOG_NO_CREATE_COLLECTION_PERMISSION = Symbol( + 'DIALOG_NO_CREATE_COLLECTION_PERMISSION', +); + +export type Props = { + visible: boolean; + onClose: () => void; +}; + +export type OpenDialogCollectionNoCreatePermissionArgs = { + id: typeof DIALOG_NO_CREATE_COLLECTION_PERMISSION; + props: Props; +}; + +export const CollectionNoCreatePermissionDialog = ({visible, onClose}: Props) => { + const {customizeNoCreatePermissionDialog} = registry.collections.functions.getAll(); + + const {message} = customizeNoCreatePermissionDialog({ + message: , + }); + + return ( + + ); +}; + +CollectionNoCreatePermissionDialog.displayName = 'CollectionNoCreatePermissionDialog'; + +DialogManager.registerDialog( + DIALOG_NO_CREATE_COLLECTION_PERMISSION, + CollectionNoCreatePermissionDialog, +); diff --git a/src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/index.ts b/src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/index.ts new file mode 100644 index 0000000000..678ee4c4b1 --- /dev/null +++ b/src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/index.ts @@ -0,0 +1 @@ +export * from './CollectionNoCreatePermissionDialog'; diff --git a/src/ui/components/CollectionsStructure/index.ts b/src/ui/components/CollectionsStructure/index.ts index 9aee017db4..1d06b9aabb 100644 --- a/src/ui/components/CollectionsStructure/index.ts +++ b/src/ui/components/CollectionsStructure/index.ts @@ -17,6 +17,10 @@ export { MigrateEntryToWorkbookDialog, DIALOG_MIGRATE_ENTRY_TO_WORKBOOK, } from './MigrateEntryToWorkbookDialog'; +export { + CollectionNoCreatePermissionDialog, + DIALOG_NO_CREATE_COLLECTION_PERMISSION, +} from './CollectionNoCreatePermissionDialog'; export {CopyEntriesDialog, DIALOG_COPY_ENTRIES} from './CopyEntriesDialog'; export {DeleteCollectionDialog, DIALOG_DELETE_COLLECTION} from './DeleteCollectionDialog'; export {DeleteWorkbookDialog, DIALOG_DELETE_WORKBOOK} from './DeleteWorkbookDialog'; diff --git a/src/ui/registry/units/collections/functionts-map.ts b/src/ui/registry/units/collections/functionts-map.ts index a35da447f2..424b13b446 100644 --- a/src/ui/registry/units/collections/functionts-map.ts +++ b/src/ui/registry/units/collections/functionts-map.ts @@ -2,7 +2,7 @@ import type {DropdownMenuItem} from '@gravity-ui/uikit'; import type {CollectionWithPermissions, WorkbookWithPermissions} from 'shared/schema'; import {makeFunctionTemplate} from 'shared/utils/makeFunctionTemplate'; -import type {CustomizeEmptyPlaceholder} from './types'; +import type {CustomizeEmptyPlaceholder, CustomizeNoCreatePermissionDialog} from './types'; export const collectionsFunctionsMap = { customizeCollectionsActions: @@ -20,4 +20,5 @@ export const collectionsFunctionsMap = { ) => (DropdownMenuItem[] | DropdownMenuItem)[] >(), customizeEmptyPlaceholder: makeFunctionTemplate(), + customizeNoCreatePermissionDialog: makeFunctionTemplate(), } as const; diff --git a/src/ui/registry/units/collections/register.ts b/src/ui/registry/units/collections/register.ts index 6f4134628e..2b291cb03d 100644 --- a/src/ui/registry/units/collections/register.ts +++ b/src/ui/registry/units/collections/register.ts @@ -1,6 +1,7 @@ import { customizeCollectionsActions, customizeEmptyPlaceholder, + customizeNoCreatePermissionDialog, customizeWorkbooksActions, } from '../../../units/collections/components/CollectionContent/utils'; import {registry} from '../../index'; @@ -10,5 +11,6 @@ export const registerCollectionsPlugins = () => { customizeWorkbooksActions, customizeCollectionsActions, customizeEmptyPlaceholder, + customizeNoCreatePermissionDialog, }); }; diff --git a/src/ui/registry/units/collections/types/customizeEmptyPlaceholder.ts b/src/ui/registry/units/collections/types/customizeEmptyPlaceholder.ts index 95e52f9701..23c5ef5244 100644 --- a/src/ui/registry/units/collections/types/customizeEmptyPlaceholder.ts +++ b/src/ui/registry/units/collections/types/customizeEmptyPlaceholder.ts @@ -7,6 +7,7 @@ export type CustomizeEmptyPlaceholder = (args: { dispatch: AppDispatch; history: History; canCreateWorkbook: boolean; + showCreateWorkbookButton: boolean; curCollectionId: string | null; title: string; description?: string; diff --git a/src/ui/registry/units/collections/types/customizeNoCreatePermissionDialog.ts b/src/ui/registry/units/collections/types/customizeNoCreatePermissionDialog.ts new file mode 100644 index 0000000000..8661c358fb --- /dev/null +++ b/src/ui/registry/units/collections/types/customizeNoCreatePermissionDialog.ts @@ -0,0 +1,5 @@ +import type React from 'react'; + +export type CustomizeNoCreatePermissionDialog = (args: {message?: React.ReactNode}) => { + message?: React.ReactNode; +}; diff --git a/src/ui/registry/units/collections/types/index.ts b/src/ui/registry/units/collections/types/index.ts index 8e103f8f24..2bd8ab4c64 100644 --- a/src/ui/registry/units/collections/types/index.ts +++ b/src/ui/registry/units/collections/types/index.ts @@ -1 +1,2 @@ export * from './customizeEmptyPlaceholder'; +export * from './customizeNoCreatePermissionDialog'; diff --git a/src/ui/store/actions/openDialogTypes.ts b/src/ui/store/actions/openDialogTypes.ts index 651f0b4ecd..4feb1f6857 100644 --- a/src/ui/store/actions/openDialogTypes.ts +++ b/src/ui/store/actions/openDialogTypes.ts @@ -54,6 +54,7 @@ import type {OpenDialogCreateEntryInWorkbookArgs} from '../../components/Collect import type {OpenDialogIamAccessArgs} from '../../components/IamAccessDialog'; import type {OpenDialogTooltipSettingsArgs} from '../../units/wizard/components/Dialogs/DialogTooltipSettings/DialogTooltipSettings'; import type {OpenDialogChangeDatasetFieldsArgs} from '../../units/datasets/components/DatasetTable/components/BatchActionPanel/components/DialogChangeDatasetFields/DialogChangeDatasetFields'; +import type {OpenDialogCollectionNoCreatePermissionArgs} from 'ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog'; export type OpenDialogArgs = | OpenDialogMetricSettingsArgs @@ -109,4 +110,5 @@ export type OpenDialogArgs = | OpenDialogIamAccessArgs | OpenDialogCreateEntryInWorkbookArgs | OpenDialogTooltipSettingsArgs - | OpenDialogChangeDatasetFieldsArgs; + | OpenDialogChangeDatasetFieldsArgs + | OpenDialogCollectionNoCreatePermissionArgs; diff --git a/src/ui/units/collections/components/CollectionActions/CollectionActions.tsx b/src/ui/units/collections/components/CollectionActions/CollectionActions.tsx index 7fab65896b..6b5b49479f 100644 --- a/src/ui/units/collections/components/CollectionActions/CollectionActions.tsx +++ b/src/ui/units/collections/components/CollectionActions/CollectionActions.tsx @@ -13,7 +13,7 @@ import {Feature} from '../../../../../shared'; import {DL} from '../../../../constants'; import {registry} from '../../../../registry'; import Utils from '../../../../utils'; -import {selectCollection, selectRootCollectionPermissions} from '../../store/selectors'; +import {selectCollection} from '../../store/selectors'; import collectionIcon from '../../../../assets/icons/collections/collection.svg'; import workbookDemoIcon from '../../../../assets/icons/collections/workbook-demo.svg'; @@ -49,17 +49,12 @@ export const CollectionActions = React.memo( onDeleteClick, }) => { const collection = useSelector(selectCollection); - const rootCollectionPermissions = useSelector(selectRootCollectionPermissions); const {CustomActionPanelCollectionActions} = registry.collections.components.getAll(); - const showCreateCollection = collection - ? collection.permissions?.createCollection - : rootCollectionPermissions?.createCollectionInRoot; + const showCreateCollection = collection ? collection.permissions?.createCollection : true; - const showCreateWorkbook = collection - ? collection.permissions?.createWorkbook - : rootCollectionPermissions?.createWorkbookInRoot; + const showCreateWorkbook = collection ? collection.permissions?.createWorkbook : true; const showAddDemoWorkbook = showCreateWorkbook && DL.TEMPLATE_WORKBOOK_ID; const showAddLearningMaterialsWorkbook = diff --git a/src/ui/units/collections/components/CollectionContent/CollectionContent.tsx b/src/ui/units/collections/components/CollectionContent/CollectionContent.tsx index d985e31003..702e6bf180 100644 --- a/src/ui/units/collections/components/CollectionContent/CollectionContent.tsx +++ b/src/ui/units/collections/components/CollectionContent/CollectionContent.tsx @@ -52,6 +52,7 @@ interface Props { itemsAvailableForSelection: (CollectionWithPermissions | WorkbookWithPermissions)[]; isOpenSelectionMode: boolean; canCreateWorkbook: boolean; + showCreateWorkbookButton: boolean; isEmptyItems: boolean; getStructureItemsRecursively: ( args: GetStructureItemsArgs, @@ -76,6 +77,7 @@ export const CollectionContent: React.FC = ({ selectedMapWithDeletePermission, itemsAvailableForSelection, isOpenSelectionMode, + showCreateWorkbookButton, canCreateWorkbook, isEmptyItems, getStructureItemsRecursively, @@ -162,7 +164,7 @@ export const CollectionContent: React.FC = ({ if (isDefaultFilters || DL.IS_MOBILE) { const actions: EmptyPlaceholderAction[] = []; - if (canCreateWorkbook) { + if (showCreateWorkbookButton) { actions.push({ id: EmptyPlaceholderActionId.ConnectYourData, title: i18n('action_connect-your-data'), @@ -181,6 +183,7 @@ export const CollectionContent: React.FC = ({ dispatch, history, curCollectionId, + showCreateWorkbookButton, canCreateWorkbook, title: i18n('label_empty-list'), description: canCreateWorkbook ? i18n('section_create-first') : undefined, diff --git a/src/ui/units/collections/components/CollectionContent/utils.ts b/src/ui/units/collections/components/CollectionContent/utils.ts index d924ae0d67..373746ec6d 100644 --- a/src/ui/units/collections/components/CollectionContent/utils.ts +++ b/src/ui/units/collections/components/CollectionContent/utils.ts @@ -2,7 +2,10 @@ import type {DropdownMenuItem} from '@gravity-ui/uikit'; import type {CollectionWithPermissions} from 'shared/schema/us/types/collections'; import type {WorkbookWithPermissions} from 'shared/schema/us/types/workbooks'; -import type {CustomizeEmptyPlaceholder} from '../../../../registry/units/collections/types'; +import type { + CustomizeEmptyPlaceholder, + CustomizeNoCreatePermissionDialog, +} from '../../../../registry/units/collections/types'; export const customizeCollectionsActions = ( _item: CollectionWithPermissions, @@ -21,3 +24,9 @@ export const customizeEmptyPlaceholder: CustomizeEmptyPlaceholder = ({ }) => { return {title, description, actions}; }; + +export const customizeNoCreatePermissionDialog: CustomizeNoCreatePermissionDialog = ({message}) => { + return { + message, + }; +}; diff --git a/src/ui/units/collections/components/CollectionPage/CollectionPage.tsx b/src/ui/units/collections/components/CollectionPage/CollectionPage.tsx index 947307eb51..b026e1956b 100644 --- a/src/ui/units/collections/components/CollectionPage/CollectionPage.tsx +++ b/src/ui/units/collections/components/CollectionPage/CollectionPage.tsx @@ -12,6 +12,7 @@ import { DIALOG_CREATE_WORKBOOK, DIALOG_DELETE_COLLECTIONS_WORKBOOKS, DIALOG_MOVE_COLLECTIONS_WORKBOOKS, + DIALOG_NO_CREATE_COLLECTION_PERMISSION, } from '../../../../components/CollectionsStructure'; import {ViewError} from '../../../../components/ViewError/ViewError'; import type {AppDispatch} from '../../../../store'; @@ -110,6 +111,20 @@ export const CollectionPage = () => { ); }, [curCollectionId, dispatch, history]); + const handleShowNoPermissionsDialog = React.useCallback(() => { + dispatch( + openDialog({ + id: DIALOG_NO_CREATE_COLLECTION_PERMISSION, + props: { + visible: true, + onClose: () => { + dispatch(closeDialog()); + }, + }, + }), + ); + }, [dispatch]); + const handleCreateWorkbookWithConnection = React.useCallback(() => { dispatch( openDialog({ @@ -218,6 +233,19 @@ export const CollectionPage = () => { fetchStructureItems, ]); + const isRootCollection = curCollectionId === null; + + const hasPermissionToCreate = + curCollectionId && collection + ? Boolean(collection.permissions?.createWorkbook) + : Boolean(rootCollectionPermissions?.createWorkbookInRoot); + + const showCreateWorkbookButton = DL.IS_MOBILE + ? false + : hasPermissionToCreate || isRootCollection; + + const isFiltersHidden = DL.IS_MOBILE && Utils.isEnabledFeature(Feature.HideMultitenant); + useLayout({ curCollectionId, filters, @@ -230,7 +258,9 @@ export const CollectionPage = () => { resetSelected, fetchCollectionInfo, fetchStructureItems, - handleCreateWorkbook, + handleCreateWorkbook: hasPermissionToCreate + ? handleCreateWorkbook + : handleShowNoPermissionsDialog, handeCloseMoveDialog, updateAllCheckboxes, }); @@ -255,15 +285,6 @@ export const CollectionPage = () => { ); } - const hasPermissionToCreate = - curCollectionId && collection - ? Boolean(collection.permissions?.createWorkbook) - : Boolean(rootCollectionPermissions?.createWorkbookInRoot); - - const canCreateWorkbook = DL.IS_MOBILE ? false : hasPermissionToCreate; - - const isFiltersHidden = DL.IS_MOBILE && Utils.isEnabledFeature(Feature.HideMultitenant); - return (
@@ -286,11 +307,16 @@ export const CollectionPage = () => { selectedMapWithDeletePermission={selectedMapWithDeletePermission} itemsAvailableForSelection={itemsAvailableForSelection} isOpenSelectionMode={isOpenSelectionMode} - canCreateWorkbook={canCreateWorkbook} + canCreateWorkbook={hasPermissionToCreate} + showCreateWorkbookButton={showCreateWorkbookButton} getStructureItemsRecursively={getStructureItemsRecursively} fetchStructureItems={fetchStructureItems} onCloseMoveDialog={handeCloseMoveDialog} - onCreateWorkbookWithConnectionClick={handleCreateWorkbookWithConnection} + onCreateWorkbookWithConnectionClick={ + hasPermissionToCreate + ? handleCreateWorkbookWithConnection + : handleShowNoPermissionsDialog + } onClearFiltersClick={() => { updateFilters({ ...DEFAULT_FILTERS, diff --git a/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx b/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx index b24f9f31b3..c0b200ecac 100644 --- a/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx +++ b/src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx @@ -22,6 +22,7 @@ import { DIALOG_DELETE_COLLECTION, DIALOG_EDIT_COLLECTION, DIALOG_MOVE_COLLECTION, + DIALOG_NO_CREATE_COLLECTION_PERMISSION, } from '../../../../../components/CollectionsStructure'; import {DIALOG_IAM_ACCESS} from '../../../../../components/IamAccessDialog'; import {DL} from '../../../../../constants'; @@ -201,25 +202,45 @@ export const useLayout = ({ } }} onCreateCollectionClick={() => { - dispatch( - openDialog({ - id: DIALOG_CREATE_COLLECTION, - props: { - open: true, - parentId: curCollectionId, - onApply: (result: CreateCollectionResponse | null) => { - if (result) { - history.push( - `${COLLECTIONS_PATH}/${result.collectionId}`, - ); - } + if ( + isRootCollection && + Boolean(rootCollectionPermissions?.createCollectionInRoot) === + false + ) { + dispatch( + openDialog({ + id: DIALOG_NO_CREATE_COLLECTION_PERMISSION, + props: { + visible: true, + onClose: () => { + dispatch(closeDialog()); + }, }, - onClose: () => { - dispatch(closeDialog()); + }), + ); + } else { + dispatch( + openDialog({ + id: DIALOG_CREATE_COLLECTION, + props: { + open: true, + parentId: curCollectionId, + onApply: ( + result: CreateCollectionResponse | null, + ) => { + if (result) { + history.push( + `${COLLECTIONS_PATH}/${result.collectionId}`, + ); + } + }, + onClose: () => { + dispatch(closeDialog()); + }, }, - }, - }), - ); + }), + ); + } }} onAddDemoWorkbookClick={() => { if (DL.TEMPLATE_WORKBOOK_ID) { @@ -337,6 +358,7 @@ export const useLayout = ({ curCollectionId, rootCollectionPermissions, fetchCollectionInfo, + goToParentCollection, ]); React.useEffect(() => {