diff --git a/mscr-ui/public/locales/en/common.json b/mscr-ui/public/locales/en/common.json index 6b9e42be9..15831027a 100644 --- a/mscr-ui/public/locales/en/common.json +++ b/mscr-ui/public/locales/en/common.json @@ -23,6 +23,7 @@ "finish-editing": "Finish editing", "invalidate-crosswalk": "Invalidate crosswalk", "invalidate-schema": "Invalidate schema", + "mscr-copy": "Make MSCR copy", "publish-crosswalk": "Publish crosswalk", "publish-schema": "Publish schema", "revision": "Add new revision" diff --git a/mscr-ui/public/locales/fi/common.json b/mscr-ui/public/locales/fi/common.json index 851fce5fa..434b5cbb8 100644 --- a/mscr-ui/public/locales/fi/common.json +++ b/mscr-ui/public/locales/fi/common.json @@ -23,6 +23,7 @@ "finish-editing": "", "invalidate-crosswalk": "", "invalidate-schema": "", + "mscr-copy": "", "publish-crosswalk": "", "publish-schema": "", "revision": "" diff --git a/mscr-ui/public/locales/sv/common.json b/mscr-ui/public/locales/sv/common.json index e6acbcbdb..fef1d1951 100644 --- a/mscr-ui/public/locales/sv/common.json +++ b/mscr-ui/public/locales/sv/common.json @@ -23,6 +23,7 @@ "finish-editing": "", "invalidate-crosswalk": "", "invalidate-schema": "", + "mscr-copy": "", "publish-crosswalk": "", "publish-schema": "", "revision": "" diff --git a/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx b/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx index ea83dd439..f70772e08 100644 --- a/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx +++ b/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx @@ -20,7 +20,7 @@ import { CrosswalkWithVersionInfo } from '@app/common/interfaces/crosswalk.inter import { SchemaWithVersionInfo } from '@app/common/interfaces/schema.interface'; import { ActionMenuWrapper } from '@app/common/components/schema-and-crosswalk-actionmenu/schema-and-crosswalk-actionmenu.styles'; import FormModal, { ModalType } from '@app/modules/form'; -import { Format } from '@app/common/interfaces/format.interface'; +import { Format, formatsAvailableForMscrCopy } from '@app/common/interfaces/format.interface'; interface SchemaAndCrosswalkActionmenuProps { type: ActionMenuTypes; @@ -55,6 +55,7 @@ export default function SchemaAndCrosswalkActionMenu({ const [isDeleteConfirmModalOpen, setDeleteConfirmModalOpen] = useState(false); const [isRemoveConfirmModalOpen, setRemoveConfirmModalOpen] = useState(false); const [isRevisionModalOpen, setRevisionModalOpen] = useState(false); + const [isMscrCopyModalOpen, setMscrCopyModalOpen] = useState(false); const [isCrosswalkPublished, setCrosswalkPublished] = React.useState(false); const [isLatestVersion, setIsLatestVersion] = useState(false); @@ -202,6 +203,10 @@ export default function SchemaAndCrosswalkActionMenu({ } }, [metadata.revisions, metadata.pid]); + if (type == ActionMenuTypes.NoEditPermission) { + return renderStubMenu(); + } + return ( <> @@ -293,6 +298,18 @@ export default function SchemaAndCrosswalkActionMenu({ > {t('actionmenu.revision')} + setMscrCopyModalOpen(true)} + > + {t('actionmenu.mscr-copy')} + + ); + + function renderStubMenu() { + return ( + <> + + + setMscrCopyModalOpen(true)}> + {t('actionmenu.mscr-copy')} + + + + + + ); + } } diff --git a/mscr-ui/src/common/components/schema/schema.slice.tsx b/mscr-ui/src/common/components/schema/schema.slice.tsx index 17e326f6b..e346d3ebd 100644 --- a/mscr-ui/src/common/components/schema/schema.slice.tsx +++ b/mscr-ui/src/common/components/schema/schema.slice.tsx @@ -56,6 +56,16 @@ export const schemaApi = createApi({ }, }) }), + putSchemaMscrCopy: builder.mutation }>({ + query: ({pid, data }) => ({ + url: `/schema?action=mscrCopyOf&target=${pid}`, + method: 'PUT', + data: data, + headers: { + 'content-Type': 'application/json;', + }, + }), + }), patchSchema: builder.mutation< Metadata, { @@ -141,6 +151,7 @@ export const { useGetSchemasQuery, usePutSchemaFullMutation, usePutSchemaRevisionMutation, + usePutSchemaMscrCopyMutation, usePatchSchemaMutation, util: { getRunningQueriesThunk }, } = schemaApi; diff --git a/mscr-ui/src/common/interfaces/format.interface.ts b/mscr-ui/src/common/interfaces/format.interface.ts index 225d6ef91..27099e459 100644 --- a/mscr-ui/src/common/interfaces/format.interface.ts +++ b/mscr-ui/src/common/interfaces/format.interface.ts @@ -48,6 +48,14 @@ export const formatsAvailableForSchemaRegistration: Format[] = [ Format.Shacl ]; +export const formatsAvailableForMscrCopy: Format[] = [ + Format.Csv, + Format.Jsonschema, + Format.Mscr, + Format.Shacl, + Format.Xsd +]; + export const fileExtensionsAvailableForCrosswalkRegistrationAttachments: FileExtensions[] = [FileExtensions.Csv, FileExtensions.Xslt, FileExtensions.Pdf]; diff --git a/mscr-ui/src/common/interfaces/search.interface.ts b/mscr-ui/src/common/interfaces/search.interface.ts index 784dc8bd8..b3c874565 100644 --- a/mscr-ui/src/common/interfaces/search.interface.ts +++ b/mscr-ui/src/common/interfaces/search.interface.ts @@ -24,6 +24,7 @@ export enum ActionMenuTypes { CrosswalkVersionInfo = 'CROSSWALK_VERSIONINFO', Schema = 'SCHEMA', SchemaMetadata = 'SCHEMA_METADATA', + NoEditPermission = 'NO_EDIT_PERMISSION', } export interface ResultInfo { diff --git a/mscr-ui/src/common/utils/has-permission.tsx b/mscr-ui/src/common/utils/has-permission.tsx index bcc254a74..fd9bf5d12 100644 --- a/mscr-ui/src/common/utils/has-permission.tsx +++ b/mscr-ui/src/common/utils/has-permission.tsx @@ -9,40 +9,36 @@ import { import { User } from 'yti-common-ui/interfaces/user.interface'; import { Roles } from '../interfaces/format.interface'; -// Need to specify the acctions permitted for each type of user +// Need to specify the actions permitted for each type of user const actions = [ - 'CREATE_SCHEMA', - 'EDIT_SCHEMA', - 'EDIT_SCHEMA_METADATA', - 'EDIT_SCHEMA_FILES', - 'DELETE_SCHEMA', - 'CREATE_CROSSWALK', - 'EDIT_CROSSWALK_MAPPINGS', - 'EDIT_CROSSWALK_METADATA', - 'EDIT_CROSSWALK_FILES', - 'DELETE_CROSSWALK', + // 'CREATE_SCHEMA', + // 'EDIT_SCHEMA', + // 'EDIT_SCHEMA_METADATA', + // 'EDIT_SCHEMA_FILES', + // 'DELETE_SCHEMA', + // 'CREATE_CROSSWALK', + // 'EDIT_CROSSWALK_MAPPINGS', + // 'EDIT_CROSSWALK_METADATA', + // 'EDIT_CROSSWALK_FILES', + // 'DELETE_CROSSWALK', + 'EDIT_CONTENT', + 'MAKE_MSCR_COPY', ] as const; -export type Actions = typeof actions[number]; +export type Action = typeof actions[number]; export interface hasPermissionProps { - actions: Actions | Actions[]; - targetOrganization?: string; + action: Action; owner?: string[]; } export interface checkPermissionProps { user: User; - actions: Actions[]; - targetOrganizations?: string[]; + action: Action; owner?: string[]; } -export default function HasPermission({ - actions, - targetOrganization, - owner, -}: hasPermissionProps) { +export default function HasPermission({ action, owner }: hasPermissionProps) { const { data: authenticatedUser } = useGetAuthenticatedUserQuery(); const dispatch = useStoreDispatch(); const user = useSelector(selectLogin()); @@ -66,99 +62,48 @@ export default function HasPermission({ return false; } - //No Target Organization - if (!targetOrganization) { - if (owner && owner.length) { - //Editing Step as already has owner - return checkEditPermission({ - user, - actions: Array.isArray(actions) ? actions : [actions], - owner, - }); - } + if (!owner || owner.length == 0) { return checkPermission({ user, - actions: Array.isArray(actions) ? actions : [actions], + action, }); } - //If there is target organization - if (owner && owner.length) { - //Editing Step as already has owner - return checkEditPermission({ - user, - actions: Array.isArray(actions) ? actions : [actions], - targetOrganizations: [targetOrganization], - owner, - }); - } return checkPermission({ - //Content Creation user, - actions: Array.isArray(actions) ? actions : [actions], - targetOrganizations: [targetOrganization], + action, + owner, }); } -export function checkPermission({ - user, - actions, - targetOrganizations, -}: checkPermissionProps) { - - const rolesInOrganizations = Object.keys(user.organizationsInRole); - - const rolesInTargetOrganizations = - targetOrganizations && - targetOrganizations - ?.flatMap((org) => user.rolesInOrganizations[org]) - .filter((t) => t); - - // Return true if user is superuser - if (user.superuser) { - return true; - } - - // Return true if target organization is undefined and user has admin role - if (rolesInOrganizations.includes(Roles.admin) && !targetOrganizations) { - return true; - } - - // console.log(rolesInTargetOrganizations); - // Return true if user has data model editor role in target organization - if ( - rolesInTargetOrganizations?.includes(Roles.dataModelEditor)||rolesInTargetOrganizations?.includes(Roles.admin) - ) { +export function checkPermission({ user, action, owner }: checkPermissionProps) { + if (action == 'MAKE_MSCR_COPY') { return true; - } - - - return false; -} - -export function checkEditPermission({ - user, - owner -}: checkPermissionProps) { - if (owner?.includes(user.id)) { - //user is the owner, Check for personal Contents - return true; - } else { - //Gruop Content - - if (owner && user.organizationsInRole[Roles.admin]&& user.organizationsInRole[Roles.admin].includes(owner[0])) { - // User has admin right for this group - return true; - } - - if ( - owner &&user.organizationsInRole[Roles.dataModelEditor]&& - user.organizationsInRole[Roles.dataModelEditor].includes(owner[0]) - ) { + } else if (action == 'EDIT_CONTENT') { + if (owner?.includes(user.id)) { + //user is the owner, Check for personal Contents return true; + } else { + //Group Content + if ( + owner && + user.organizationsInRole[Roles.admin] && + user.organizationsInRole[Roles.admin].includes(owner[0]) + ) { + // User has admin right for this group + return true; + } + + if ( + owner && + user.organizationsInRole[Roles.dataModelEditor] && + user.organizationsInRole[Roles.dataModelEditor].includes(owner[0]) + ) { + // User has data model editor right for this group + return true; + } } } - return false; } diff --git a/mscr-ui/src/modules/crosswalk-editor/index.tsx b/mscr-ui/src/modules/crosswalk-editor/index.tsx index b32911a12..eaf200ffc 100644 --- a/mscr-ui/src/modules/crosswalk-editor/index.tsx +++ b/mscr-ui/src/modules/crosswalk-editor/index.tsx @@ -150,7 +150,7 @@ export default function CrosswalkEditor({ } = useGetCrosswalkWithRevisionsQuery(crosswalkId); const hasEditRights = HasPermission({ - actions: ['EDIT_CROSSWALK_MAPPINGS'], + action: 'EDIT_CONTENT', owner: getCrosswalkData?.owner, }); @@ -723,13 +723,17 @@ export default function CrosswalkEditor({
- + {hasEditRights && ( + + )}
diff --git a/mscr-ui/src/modules/crosswalk-editor/tabs/metadata-and-files/index.tsx b/mscr-ui/src/modules/crosswalk-editor/tabs/metadata-and-files/index.tsx index a5abd2dc4..949f42941 100644 --- a/mscr-ui/src/modules/crosswalk-editor/tabs/metadata-and-files/index.tsx +++ b/mscr-ui/src/modules/crosswalk-editor/tabs/metadata-and-files/index.tsx @@ -4,13 +4,18 @@ import { Type } from '@app/common/interfaces/search.interface'; import { CrosswalkWithVersionInfo } from '@app/common/interfaces/crosswalk.interface'; import MetadataFilesTable from '@app/common/components/metadata-files-table'; - export default function MetadataAndFiles(props: { crosswalkData: CrosswalkWithVersionInfo; refetch: () => void; }) { - const hasEditRights = HasPermission({ actions: ['EDIT_CROSSWALK_METADATA'],owner:props.crosswalkData.owner}); - const hasFileRights = HasPermission({ actions: ['EDIT_CROSSWALK_FILES'],owner:props.crosswalkData.owner }); + const hasEditRights = HasPermission({ + action: 'EDIT_CONTENT', + owner: props.crosswalkData.owner, + }); + const hasFileRights = HasPermission({ + action: 'EDIT_CONTENT', + owner: props.crosswalkData.owner, + }); return ( <> @@ -19,8 +24,9 @@ export default function MetadataAndFiles(props: { metadata={props.crosswalkData} refetchMetadata={props.refetch} hasEditPermission={hasEditRights} + isMscrCopyAvailable={false} /> -
+
(false); const formDataFromInitialData = useCallback(() => { if (!initialData) return; const existingData: FormType = { - format: initialData.format, + format: + modalType == ModalType.McsrCopy ? Format.Mscr : initialData.format, languages: [ { labelText: t('language-english-with-suffix'), @@ -141,7 +145,7 @@ export default function FormModal({ existingData.targetSchema = initialData.targetSchema ?? ''; } return existingData; - }, [contentType, initialData, lang, t]); + }, [contentType, initialData, lang, modalType, t]); useEffect(() => { if ( @@ -198,7 +202,6 @@ export default function FormModal({ ) { pid = resultCrosswalkRevision.data.pid; } - // TODO: Api slice for mscr schema revision, then the pid retrieval here break; case ModalType.RevisionFull: if ( @@ -216,7 +219,15 @@ export default function FormModal({ } break; case ModalType.McsrCopy: - // TODO: MscrCopy API slice and then pid retrieval for schema and crosswalk here + if ( + contentType == Type.Schema && + resultSchemaMscrCopy.isSuccess && + resultSchemaMscrCopy.data + ) { + pid = resultSchemaMscrCopy.data.pid; + } + break; + // TODO: MscrCopy API slice and then pid retrieval for crosswalk here } return pid; }, @@ -231,6 +242,8 @@ export default function FormModal({ resultCrosswalkRevision.isSuccess, resultSchemaFull.data, resultSchemaFull.isSuccess, + resultSchemaMscrCopy.data, + resultSchemaMscrCopy.isSuccess, resultSchemaRevision.data, resultSchemaRevision.isSuccess, ] @@ -246,7 +259,6 @@ export default function FormModal({ 'MscrSearch', ]) ); - //Get the pid from the result handleClose(); let notificationKey: NotificationKeys; if (contentType == Type.Schema) { @@ -314,7 +326,11 @@ export default function FormModal({ ); setErrors(formErrors); - if (formErrors && (Object.values(formErrors).includes(true) || formErrors.titleAmount.length > 0)) { + if ( + formErrors && + (Object.values(formErrors).includes(true) || + formErrors.titleAmount.length > 0) + ) { return; } @@ -361,8 +377,22 @@ export default function FormModal({ ]).then((_values) => { setSubmitAnimationVisible(false); }); - } else if (initialData) { - // Todo: Add mscrCopy option here + } else if ( + initialData && + modalType == ModalType.McsrCopy && + contentType == Type.Schema + ) { + Promise.all([ + spinnerDelay(), + putSchemaMscrCopy({ pid: initialData.pid, data: payload }), + ]).then((_values) => { + setSubmitAnimationVisible(false); + }); + } else if ( + initialData && + modalType == ModalType.RevisionMscr && + contentType == Type.Crosswalk + ) { Promise.all([ spinnerDelay(), putCrosswalkRevision({ pid: initialData.pid, data: payload }), @@ -370,6 +400,7 @@ export default function FormModal({ setSubmitAnimationVisible(false); }); } + // Missing scenarios: MSCR copy of a crosswalk, revision of an MSCR copy } }; @@ -406,8 +437,9 @@ export default function FormModal({ errorObject = getApiError(resultCrosswalkFull.error); } else if (resultSchemaFull.isError) { errorObject = getApiError(resultSchemaFull.error); + } else if (resultSchemaMscrCopy.isError) { + errorObject = getApiError(resultSchemaMscrCopy.error); } else { - // Todo: Add mscr copy error return undefined; } return errorObject; diff --git a/mscr-ui/src/modules/form/metadata-form/index.tsx b/mscr-ui/src/modules/form/metadata-form/index.tsx index b1b311cb1..bdaa518da 100644 --- a/mscr-ui/src/modules/form/metadata-form/index.tsx +++ b/mscr-ui/src/modules/form/metadata-form/index.tsx @@ -48,12 +48,14 @@ interface MetadataFormProps { metadata: SchemaWithVersionInfo | CrosswalkWithVersionInfo; refetchMetadata: () => void; hasEditPermission: boolean; + isMscrCopyAvailable?: boolean; } export default function MetadataForm({ type, metadata, refetchMetadata, hasEditPermission, + isMscrCopyAvailable, }: MetadataFormProps) { const { t } = useTranslation('common'); const router = useRouter(); @@ -171,19 +173,25 @@ export default function MetadataForm({
{hasEditPermission && ( - <> - - + + )} + {!hasEditPermission && isMscrCopyAvailable && ( + )} diff --git a/mscr-ui/src/modules/schema-view/index.tsx b/mscr-ui/src/modules/schema-view/index.tsx index a7d45fbf9..de911b354 100644 --- a/mscr-ui/src/modules/schema-view/index.tsx +++ b/mscr-ui/src/modules/schema-view/index.tsx @@ -17,6 +17,8 @@ import { SchemaVisualizationWrapper, VersionsHeading, } from '@app/modules/schema-view/schema-view-styles'; +import HasPermission from '@app/common/utils/has-permission'; +import { formatsAvailableForMscrCopy } from '@app/common/interfaces/format.interface'; export default function SchemaView({ schemaId }: { schemaId: string }) { const { t } = useTranslation('common'); @@ -30,6 +32,16 @@ export default function SchemaView({ schemaId }: { schemaId: string }) { error, } = useGetSchemaWithRevisionsQuery(schemaId); + const hasEditPermission = HasPermission({ + action: 'EDIT_CONTENT', + owner: schemaDetails?.owner, + }); + const hasCopyPermission = HasPermission({ action: 'MAKE_MSCR_COPY' }); + const isMscrCopyAvailable = + hasCopyPermission && + schemaDetails && + formatsAvailableForMscrCopy.includes(schemaDetails.format); + const theme = createTheme({ typography: { fontFamily: [ @@ -104,6 +116,8 @@ export default function SchemaView({ schemaId }: { schemaId: string }) { )} {selectedTab === 1 && ( @@ -114,6 +128,8 @@ export default function SchemaView({ schemaId }: { schemaId: string }) { format={schemaDetails.format} refetchMetadata={refetch} metadata={schemaDetails} + hasEditPermission={hasEditPermission} + isMscrCopyAvailable={isMscrCopyAvailable} /> @@ -127,12 +143,22 @@ export default function SchemaView({ schemaId }: { schemaId: string }) {
- + {hasEditPermission && ( + + )} + {!hasEditPermission && isMscrCopyAvailable && ( + + )}
diff --git a/mscr-ui/src/modules/schema-view/metadata-and-files/index.tsx b/mscr-ui/src/modules/schema-view/metadata-and-files/index.tsx index 1a505ebd3..7d7124680 100644 --- a/mscr-ui/src/modules/schema-view/metadata-and-files/index.tsx +++ b/mscr-ui/src/modules/schema-view/metadata-and-files/index.tsx @@ -1,20 +1,20 @@ import { SchemaWithVersionInfo } from '@app/common/interfaces/schema.interface'; import MetadataForm from '@app/modules/form/metadata-form'; import { Type } from '@app/common/interfaces/search.interface'; -import HasPermission from '@app/common/utils/has-permission'; import MetadataFilesTable from '@app/common/components/metadata-files-table'; export default function MetadataAndFiles({ - schemaDetails, - refetch, - }: { + schemaDetails, + refetch, + isMscrCopyAvailable, + hasEditPermission +}: { schemaDetails: SchemaWithVersionInfo; refetch: () => void; + hasEditPermission: boolean; + isMscrCopyAvailable?: boolean; }) { - const hasEditPermission = HasPermission({ - actions: ['EDIT_SCHEMA_METADATA'], - owner: schemaDetails?.owner - }); + const schemaFiles = schemaDetails?.fileMetadata; return ( @@ -24,6 +24,7 @@ export default function MetadataAndFiles({ metadata={schemaDetails} refetchMetadata={refetch} hasEditPermission={hasEditPermission} + isMscrCopyAvailable={isMscrCopyAvailable} /> void; }) { @@ -31,18 +33,28 @@ export default function SchemaVisualization({ <>
- +
- {!hasEditRights && ( - <> - - + {hasEditPermission && ( + + )} + {!hasEditPermission && isMscrCopyAvailable && ( + )}