diff --git a/.tekton/insights-rbac-ui-pull-request.yaml b/.tekton/insights-rbac-ui-pull-request.yaml index b3a3e6b04..061087240 100644 --- a/.tekton/insights-rbac-ui-pull-request.yaml +++ b/.tekton/insights-rbac-ui-pull-request.yaml @@ -194,7 +194,7 @@ spec: - name: name value: prefetch-dependencies - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.1@sha256:47d8d3320b4e29360108f18235598dd247bc316a4792063d970bffb00e61b71a + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.1@sha256:53fc6d82b06534878e509f3e37f05b818f38fba01729dd1fbee6f97a9562c1ed - name: kind value: task resolver: bundles @@ -285,7 +285,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta@sha256:0f4360ce144d46171ebd2e8f4d4575539a0600e02208ba5fc9beeb2c27ddfd4c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta@sha256:4bf48d038ff12d25bdeb5ab3e98dc2271818056f454c83d7393ebbd413028147 - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: sidecars: steps: - name: use-trusted-artifact - image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:81c4864dae6bb11595f657be887e205262e70086a05ed16ada827fd6391926ac + image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:52f1391e6f1c472fd10bb838f64fae2ed3320c636f536014978a5ddbdfc6b3af args: - use - $(params.SOURCE_ARTIFACT)=/var/workdir @@ -376,7 +376,7 @@ spec: - name: name value: buildah - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:d588db7bfd8cd0b951de7f7a3929ba5870e8ab1e35bd45056d03fd9c2972fcf1 + value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:23883131ea90adb2b8e411420084e13e1dd4d2a9350ee567200a60360e820d10 - name: kind value: task resolver: bundles @@ -399,7 +399,7 @@ spec: - name: name value: source-build - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.1@sha256:ddfa1fb418c1d9d55d7d70d39fe8f35ce05e96073bcd057bb6aaacd1f839cc51 + value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.1@sha256:bacd55a3caa34a30bcf51c00f3f719cb3f783e325257f04c27a91f688cbe9644 - name: kind value: task resolver: bundles @@ -428,7 +428,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:28aaf87d61078a0aeeeabcae455eda7d05c4f9b81d8995bdcf3dde95c1a7a77b + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:8f3b23bf1b0ef55cc79d28604d2397a0101ac9c0c42ae26e26532eb2778c801b - name: kind value: task resolver: bundles @@ -534,29 +534,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:b4f450f1447b166da671f1d5819ab5a1485083e5c27ab91f7d8b7a2ff994c8c2 - - name: kind - value: task - resolver: bundles - when: - - input: $(params.skip-checks) - operator: in - values: - - "false" - - name: sbom-json-check - params: - - name: IMAGE_URL - value: $(tasks.build-container.results.IMAGE_URL) - - name: IMAGE_DIGEST - value: $(tasks.build-container.results.IMAGE_DIGEST) - runAfter: - - build-container - taskRef: - params: - - name: name - value: sbom-json-check - - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sbom-json-check:0.2@sha256:f3f441de3002c5654acdff0553fd54cb1409e6bef6ff68e514d1731c9688b5cc + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:6e08cf608240f57442ca5458f3c0dade3558f4f2953be8ea939232f5d5378d58 - name: kind value: task resolver: bundles diff --git a/.tekton/insights-rbac-ui-push.yaml b/.tekton/insights-rbac-ui-push.yaml index c87bd9c03..0f10b3683 100644 --- a/.tekton/insights-rbac-ui-push.yaml +++ b/.tekton/insights-rbac-ui-push.yaml @@ -36,7 +36,7 @@ spec: - name: name value: show-sbom - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:52f8b96b96ce4203d4b74d850a85f963125bf8eef0683ea5acdd80818d335a28 + value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:945a7c9066d3e0a95d3fddb7e8a6992e4d632a2a75d8f3a9bd2ff2fef0ec9aa0 - name: kind value: task resolver: bundles @@ -55,7 +55,7 @@ spec: - name: name value: summary - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:d97c04ab42f277b1103eb6f3a053b247849f4f5b3237ea302a8ecada3b24e15b + value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:870d9a04d9784840a90b7bf6817cd0d0c4edfcda04b1ba1868cae625a3c3bfcc - name: kind value: task resolver: bundles @@ -149,7 +149,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:092c113b614f6551113f17605ae9cb7e822aa704d07f0e37ed209da23ce392cc + value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:0523b51c28375a3f222da91690e22eff11888ebc98a0c73c468af44762265c69 - name: kind value: task resolver: bundles @@ -166,7 +166,7 @@ spec: - name: name value: git-clone - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:2cccdf8729ad4d5adf65e8b66464f8efa1e1c87ba16d343b4a6c621a2a40f7e1 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:d091a9e19567a4cbdc5acd57903c71ba71dc51d749a4ba7477e689608851e981 - name: kind value: task resolver: bundles @@ -191,7 +191,7 @@ spec: - name: name value: prefetch-dependencies - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.1@sha256:fe7234e3824d1e65d6a7aac352e7a6bbce623d90d8d7da9aceeee108ad2c61be + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.1@sha256:53fc6d82b06534878e509f3e37f05b818f38fba01729dd1fbee6f97a9562c1ed - name: kind value: task resolver: bundles @@ -282,7 +282,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta@sha256:0f4360ce144d46171ebd2e8f4d4575539a0600e02208ba5fc9beeb2c27ddfd4c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta@sha256:4bf48d038ff12d25bdeb5ab3e98dc2271818056f454c83d7393ebbd413028147 - name: kind value: task resolver: bundles @@ -321,14 +321,13 @@ spec: sidecars: steps: - name: use-trusted-artifact - image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:8391272c4e5011120e9e7fee2c1f339e9405366110bf239dadcbc21e953ce099 + image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:52f1391e6f1c472fd10bb838f64fae2ed3320c636f536014978a5ddbdfc6b3af args: - use - $(params.SOURCE_ARTIFACT)=/var/workdir - image: registry.access.redhat.com/ubi8/nodejs-18 workingDir: /var/workdir name: unit-tests - name: unit-tests computeResources: requests: memory: 4Gi @@ -374,7 +373,7 @@ spec: - name: name value: buildah - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:67f0290a8ad9a147cd28bb06af182b3e4b2b3ef17070196d476d8e2ae4302ecf + value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:23883131ea90adb2b8e411420084e13e1dd4d2a9350ee567200a60360e820d10 - name: kind value: task resolver: bundles @@ -397,7 +396,7 @@ spec: - name: name value: source-build - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.1@sha256:21cb5ebaff7a9216903cf78933dc4ec4dd6283a52636b16590a5f52ceb278269 + value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.1@sha256:bacd55a3caa34a30bcf51c00f3f719cb3f783e325257f04c27a91f688cbe9644 - name: kind value: task resolver: bundles @@ -426,7 +425,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:7aa4d3c95e2b963e82fdda392f7cb3d61e3dab035416cf4a3a34e43cf3c9c9b8 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:8f3b23bf1b0ef55cc79d28604d2397a0101ac9c0c42ae26e26532eb2778c801b - name: kind value: task resolver: bundles @@ -443,7 +442,7 @@ spec: - name: name value: deprecated-image-check - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:b4f9599f5770ea2e6e4d031224ccc932164c1ecde7f85f68e16e99c98d754003 + value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:5a1a165fa02270f0a947d8a2131ee9d8be0b8e9d34123828c2bef589e504ee84 - name: kind value: task resolver: bundles @@ -465,7 +464,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:28fee4bf5da87f2388c973d9336086749cad8436003f9a514e22ac99735e056b + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:0a5421111e7092740398691d5bd7c125cc0896f29531d19414bb5724ae41692a - name: kind value: task resolver: bundles @@ -485,7 +484,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.1@sha256:5131cce0f93d0b728c7bcc0d6cee4c61d4c9f67c6d619c627e41e3c9775b497d + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.1@sha256:df8a25a3431a70544172ed4844f9d0c6229d39130633960729f825a031a7dea9 - name: kind value: task resolver: bundles @@ -507,7 +506,7 @@ spec: - name: name value: sast-snyk-check - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.2@sha256:c1ea706405f9ae146e31baef4abfea49b1e855a75bfc44c33eb0eb29516831b3 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.3@sha256:9fa8acbd4331e5f7c7ba39c6283a219b084e8b2332996e0988a7907a4a75feb4 - name: kind value: task resolver: bundles @@ -532,29 +531,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:a94b6523ba0b691dc276e37594321c2eff3594d2753014e5c920803b47627df1 - - name: kind - value: task - resolver: bundles - when: - - input: $(params.skip-checks) - operator: in - values: - - "false" - - name: sbom-json-check - params: - - name: IMAGE_URL - value: $(tasks.build-container.results.IMAGE_URL) - - name: IMAGE_DIGEST - value: $(tasks.build-container.results.IMAGE_DIGEST) - runAfter: - - build-container - taskRef: - params: - - name: name - value: sbom-json-check - - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sbom-json-check:0.2@sha256:468b5615993bb6d75df3d66180df5eb8728bbef59efe509eb5ac89b7ac582f16 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:6e08cf608240f57442ca5458f3c0dade3558f4f2953be8ea939232f5d5378d58 - name: kind value: task resolver: bundles @@ -574,7 +551,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:f485e250fb060060892b633c495a3d7e38de1ec105ae1be48608b0401530ab2c + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:87fd7fc0e937aad1a8db9b6e377d7e444f53394dafde512d68adbea6966a4702 - name: kind value: task resolver: bundles @@ -595,7 +572,7 @@ spec: - name: name value: push-dockerfile - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:674e70f7d724aaf1dd631ba9be2998ab0305fb3e0d9ec361351cc5e57bcdd3ec + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:48bb2ee92ea528b28c0814c9cc126021e499a081b69431987a774561e9ac8047 - name: kind value: task resolver: bundles diff --git a/CHANGELOG.md b/CHANGELOG.md index be3a3028c..91149af90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +## [1.15.4](https://github.com/RedHatInsights/insights-rbac-ui/compare/v1.15.3...v1.15.4) (2024-12-03) + + +### Bug Fixes + +* **konflux:** remove duplicate name of task ([#1723](https://github.com/RedHatInsights/insights-rbac-ui/issues/1723)) ([991729a](https://github.com/RedHatInsights/insights-rbac-ui/commit/991729a25b0121bda9802c762cb707e3a4757aa2)) + +## [1.15.3](https://github.com/RedHatInsights/insights-rbac-ui/compare/v1.15.2...v1.15.3) (2024-12-03) + + +### Bug Fixes + +* **konflux:** remove sbom json check ([#1722](https://github.com/RedHatInsights/insights-rbac-ui/issues/1722)) ([9fe1bce](https://github.com/RedHatInsights/insights-rbac-ui/commit/9fe1bcefd693fa180631164c10c9bd28f51d91d3)) + +## [1.15.2](https://github.com/RedHatInsights/insights-rbac-ui/compare/v1.15.1...v1.15.2) (2024-11-28) + + +### Bug Fixes + +* **konflux:** update oci-ta and rpms scan image dependencies ([#1720](https://github.com/RedHatInsights/insights-rbac-ui/issues/1720)) ([1b2cf81](https://github.com/RedHatInsights/insights-rbac-ui/commit/1b2cf8171fb64ce0f3417335dbfda54b4e988973)) + +## [1.15.1](https://github.com/RedHatInsights/insights-rbac-ui/compare/v1.15.0...v1.15.1) (2024-11-28) + + +### Bug Fixes + +* **konflux:** update buildah image sha ([#1719](https://github.com/RedHatInsights/insights-rbac-ui/issues/1719)) ([7d637b8](https://github.com/RedHatInsights/insights-rbac-ui/commit/7d637b801e731a7f5a88fb97e4d9062b18412d7c)) + +# [1.15.0](https://github.com/RedHatInsights/insights-rbac-ui/compare/v1.14.0...v1.15.0) (2024-11-27) + + +### Features + +* **common-auth:** add it api to invite and edit users ([#1718](https://github.com/RedHatInsights/insights-rbac-ui/issues/1718)) ([0ac978c](https://github.com/RedHatInsights/insights-rbac-ui/commit/0ac978c95903f6a1a79cd82899f15cc405e56089)) + # [1.14.0](https://github.com/RedHatInsights/insights-rbac-ui/compare/v1.13.0...v1.14.0) (2024-11-20) diff --git a/cypress/e2e/create-workspace.cy.ts b/cypress/e2e/create-workspace.cy.ts new file mode 100644 index 000000000..45eebecb2 --- /dev/null +++ b/cypress/e2e/create-workspace.cy.ts @@ -0,0 +1,60 @@ +describe('Workspaces page', () => { + it('Displays create workspace button', () => { + cy.login(); + + cy.intercept('GET', '**/api/rbac/v2/workspaces/', { + statusCode: 200, + body: { data: [], meta: {} }, + }).as('getWorkspaces'); + + cy.visit('/iam/access-management/workspaces'); + cy.wait('@getWorkspaces', { timeout: 30000 }); + + // check if Workspaces heading exists on the page + cy.contains('Workspaces').should('exist'); + + cy.get('button[data-ouia-component-id="create-workspace-button"]').should('exist'); + }); + + it('Opens the create workspace wizard', () => { + cy.login(); + + cy.intercept('GET', '**/api/rbac/v2/workspaces/', { + statusCode: 200, + body: { data: [], meta: {} }, + }).as('getWorkspaces'); + + cy.visit('/iam/access-management/workspaces'); + cy.wait('@getWorkspaces', { timeout: 30000 }); + + cy.contains('Workspaces').should('exist'); + + cy.get('button[data-ouia-component-id="create-workspace-button"]').click(); + + cy.contains('Create new workspace').should('be.visible'); + cy.contains('Workspace name').should('be.visible'); + cy.contains('Workspace description').should('be.visible'); + }); + + it('Closes the create workspace wizard on cancel', () => { + cy.login(); + + cy.intercept('GET', '**/api/rbac/v2/workspaces/', { + statusCode: 200, + body: { data: [], meta: {} }, + }).as('getWorkspaces'); + + cy.visit('/iam/access-management/workspaces'); + cy.wait('@getWorkspaces', { timeout: 30000 }); + + cy.contains('Workspaces').should('exist'); + + cy.get('button[data-ouia-component-id="create-workspace-button"]').click(); + + cy.get('div[data-ouia-component-id="create-workspace-wizard"]').should('exist'); + + cy.contains('Cancel').click(); + + cy.get('div[data-ouia-component-id="create-workspace-wizard"]').should('not.exist'); + }); +}); diff --git a/locales/translation-template.json b/locales/translation-template.json index ace4d6176..1cdddab12 100644 --- a/locales/translation-template.json +++ b/locales/translation-template.json @@ -295,6 +295,10 @@ "defaultMessage": "Associating service accounts", "description": "Adding service accounts label" }, + "availableFeatures": { + "defaultMessage": "Available feature(s)", + "description": "Available features label" + }, "backToPreviousPage": { "defaultMessage": "Back to previous page", "description": "Back to previous page label" @@ -307,6 +311,10 @@ "defaultMessage": "Begin Quick start", "description": "Begin Quick start link" }, + "billingAccount": { + "defaultMessage": "Billing account", + "description": "Billing account label" + }, "bindingsServiceCardDescription": { "defaultMessage": "Grant access to your workspaces. This connects roles and user groups to specific workspaces. These bindings determine who can access what, and the actions they're allowed to perform.", "description": "bindings service card description" @@ -395,6 +403,10 @@ "defaultMessage": "Copy to all", "description": "Copy to all label" }, + "cores": { + "defaultMessage": "Cores", + "description": "Cores label" + }, "createAnotherGroup": { "defaultMessage": "Create another group", "description": "Create another group message" @@ -411,6 +423,10 @@ "defaultMessage": "Create group", "description": "Create group wizard title" }, + "createNewWorkspace": { + "defaultMessage": "Create new workspace", + "description": "Create newworkspace action label" + }, "createRole": { "defaultMessage": "Create role", "description": "Create role label" @@ -427,6 +443,26 @@ "defaultMessage": "Create a role from scratch", "description": "Create role from scratch option" }, + "createUserGroup": { + "defaultMessage": "Create user group", + "description": "create user group button label" + }, + "createWorkspace": { + "defaultMessage": "Create workspace", + "description": "Create workspace action label" + }, + "createWorkspaceErrorDescription": { + "defaultMessage": "The workspace was not created successfuly.", + "description": "Create workspace error notification description" + }, + "createWorkspaceErrorTitle": { + "defaultMessage": "Failed creating {name} workspace", + "description": "Create workspace error notification title" + }, + "createWorkspaceSuccessTitle": { + "defaultMessage": "New {name} workspace have been successfully created", + "description": "Create workspace success notification title" + }, "creatingGroup": { "defaultMessage": "Creating a group", "description": "Creating group label" @@ -488,7 +524,7 @@ "description": "Delete button text" }, "deleteCustomRoleModalBody": { - "defaultMessage": "Deleting the {name} may remove acess to certain user groups in your organization", + "defaultMessage": "Deleting the {count, plural, one {the {name} role} other {{count} roles}} may remove acess to certain user groups in your organization", "description": "Modal body text for deleting custom role" }, "deleteCustomRoleModalHeader": { @@ -523,6 +559,18 @@ "defaultMessage": "Delete role?", "description": "Delete role question message" }, + "deleteRolesAction": { + "defaultMessage": "Delete Roles", + "description": "delete roles" + }, + "deleteUserGroupModalBody": { + "defaultMessage": "Deleting {count, plural, one {the {name} user group} other {{count} user groups}} will impact user access configuration.", + "description": "Modal body text for delete user group" + }, + "deleteUserGroupModalTitle": { + "defaultMessage": "Delete user {count, plural, one {group} other {groups}}?", + "description": "Title for delete user group modal" + }, "deleteUserModalBody": { "defaultMessage": "will lose all the roles associated with the user groups it belongs to.", "description": "Modal body text for delete user" @@ -551,6 +599,14 @@ "defaultMessage": "All inputs will be discarded", "description": "Warning saying that all inputs will be discarded" }, + "earMark": { + "defaultMessage": "Ear mark", + "description": "Ear mark label" + }, + "earMarkOfFeatures": { + "defaultMessage": "Ear mark of feature(s)", + "description": "Ear mark of features label" + }, "edit": { "defaultMessage": "Edit", "description": "Edit button text" @@ -1134,6 +1190,10 @@ "defaultMessage": "Page you are looking for does not exist.", "description": "Placeholder text for non existing page" }, + "parentWorkspace": { + "defaultMessage": "Parent workspace", + "description": "Parent workspace label" + }, "permission": { "defaultMessage": "Permission", "description": "Permission label" @@ -1446,10 +1506,18 @@ "defaultMessage": "Review details", "description": "Review details label" }, + "reviewNewWorkspace": { + "defaultMessage": "Review new workspace", + "description": "Review new workspace label" + }, "reviewRoleDetails": { "defaultMessage": "Review and confirm the details for your role, or click Back to revise.", "description": "Review role details text" }, + "reviewWorkspaceDescription": { + "defaultMessage": "Review the information below to make sure everything is correct before creating a new workspace.", + "description": "Review workspace description" + }, "role": { "defaultMessage": "role", "description": "Role singular" @@ -1510,6 +1578,10 @@ "defaultMessage": "Select applications to view your personal permissions.", "description": "Select applications to view your personal permissions message" }, + "selectFeatures": { + "defaultMessage": "Select feature(s)", + "description": "Select features label" + }, "selectGroups": { "defaultMessage": "Select groups", "description": "Select groups label" @@ -1558,6 +1630,10 @@ "defaultMessage": "Service Accounts admin page", "description": "Service accounts page message" }, + "setEarmark": { + "defaultMessage": "Set ear mark for {bundle} features", + "description": "Set ear mark step label" + }, "status": { "defaultMessage": "Status", "description": "Status label" @@ -1578,6 +1654,10 @@ "defaultMessage": "To manage users, go to your", "description": "To manage users text" }, + "totalAccountAvailability": { + "defaultMessage": "Total availability from {billingAccount}: {count} Cores", + "description": "Total account availability text" + }, "triggerMyCatalog": { "defaultMessage": "Trigger my catalog", "description": "Trigger my catalog text" @@ -1602,10 +1682,6 @@ "defaultMessage": "user", "description": "User label" }, - "user-groups": { - "defaultMessage": "User group", - "description": "User group singular" - }, "userAccessAdmin": { "defaultMessage": "User Access Admin", "description": "User Access Admin name" @@ -1618,6 +1694,14 @@ "defaultMessage": "{username}'s roles, groups and permissions.", "description": "User description text" }, + "userGroup": { + "defaultMessage": "User group", + "description": "User group singular" + }, + "userGroups": { + "defaultMessage": "User groups", + "description": "User groups plural" + }, "userGroupsEmptyStateSubtitle": { "defaultMessage": "This filter criteria matches no user groups.{br}Try changing your filter input.", "description": "Empty state subtitle User groups" @@ -1758,6 +1842,42 @@ "defaultMessage": "Workspace assignment (TBD)", "description": "column header for assigned user groups table" }, + "workspaceBillingAccountHelperText": { + "defaultMessage": "The default billing account is based on the parent workspace's billing account. You can switch to a different billing account as needed. This change is independent of the workspace hierarchy.", + "description": "Workspace billing account field helper text" + }, + "workspaceDescription": { + "defaultMessage": "Workspace description", + "description": "Create newworkspace action label" + }, + "workspaceDescriptionMaxLength": { + "defaultMessage": "The first {count} characters will appear in the description field.", + "description": "Workspace description max length helper text" + }, + "workspaceDetails": { + "defaultMessage": "Workspace details", + "description": "Workspace details label" + }, + "workspaceDetailsDescription": { + "defaultMessage": "Complete the fields to create a workspace.", + "description": "Workspace details step description" + }, + "workspaceDetailsTitle": { + "defaultMessage": "Provide details for a workspace", + "description": "Workspace details step title" + }, + "workspaceName": { + "defaultMessage": "Workspace name", + "description": "Workspace name label" + }, + "workspaceNamingGuidelines": { + "defaultMessage": "{link} about the guidelines for naming your workspaces.", + "description": "Workspace naming guidelines hint" + }, + "workspaceParentHelperText": { + "defaultMessage": "This workspace will inherit access control settings, such as user groups and associated roles, from its parent workspace.", + "description": "Workspace parent field helper text" + }, "workspaces": { "defaultMessage": "Workspaces", "description": "Workspaces heading" diff --git a/package-lock.json b/package-lock.json index 63a0f01e1..3212439be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,11 @@ "@babel/compat-data": "^7.23.3", "@babel/runtime": "^7.23.2", "@babel/types": "^7.23.3", - "@data-driven-forms/pf4-component-mapper": "^3.22.4", - "@data-driven-forms/react-form-renderer": "^3.22.4", + "@data-driven-forms/pf4-component-mapper": "^3.23.5", + "@data-driven-forms/react-form-renderer": "^3.23.5", "@formatjs/cli": "6.2.2", "@patternfly/quickstarts": "^5.1.0", - "@patternfly/react-component-groups": "^5.5.4", + "@patternfly/react-component-groups": "^5.5.5", "@patternfly/react-core": "^5.1.1", "@patternfly/react-data-view": "^5.7.1", "@patternfly/react-icons": "^5.1.1", @@ -59,7 +59,7 @@ "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", - "@redhat-cloud-services/frontend-components-config": "^6.3.3", + "@redhat-cloud-services/frontend-components-config": "^6.3.5", "@redhat-cloud-services/tsc-transform-imports": "^1.0.4", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^13.0.0", @@ -2496,8 +2496,9 @@ } }, "node_modules/@data-driven-forms/common": { - "version": "3.22.4", - "license": "Apache-2.0", + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/@data-driven-forms/common/-/common-3.23.5.tgz", + "integrity": "sha512-84LJdFMHYXvni9V6yCGzwRlk3Ng39r6qr8x2jSaE1ldTVV4X/24VugjAC+k4WFczEpWCIbaSgOodzChi01pCKA==", "dependencies": { "clsx": "^1.0.4", "lodash": "^4.17.15", @@ -2509,16 +2510,17 @@ } }, "node_modules/@data-driven-forms/pf4-component-mapper": { - "version": "3.22.4", - "license": "Apache-2.0", + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/@data-driven-forms/pf4-component-mapper/-/pf4-component-mapper-3.23.5.tgz", + "integrity": "sha512-Me5bTu2NTkxVHzMhZebVfAdczXuaNxO5Dxir5Ab9s0eWGvMxlG0TRrThwmZAtApN/1zMnbLAYsFUfyx1dl2HjA==", "dependencies": { - "@data-driven-forms/common": "^3.22.4", + "@data-driven-forms/common": "^3.23.5", "downshift": "^5.4.3", "lodash": "^4.17.21", "prop-types": "^15.7.2" }, "peerDependencies": { - "@data-driven-forms/react-form-renderer": "^3.22.4", + "@data-driven-forms/react-form-renderer": "^3.23.5", "@patternfly/react-core": "^5.0.0", "@patternfly/react-icons": "^5.0.0", "react": "^17.0.2 || ^18.0.0", @@ -2526,8 +2528,9 @@ } }, "node_modules/@data-driven-forms/react-form-renderer": { - "version": "3.22.4", - "license": "Apache-2.0", + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/@data-driven-forms/react-form-renderer/-/react-form-renderer-3.23.5.tgz", + "integrity": "sha512-lO7kdxm8EHzIAxZWt38GjQDYfkBg+8BctfsOXou0C5IpF//TW301UI2j3laNl4/QEUCWXGAQGhGlqBrjwTm+dQ==", "dependencies": { "final-form": "^4.20.4", "final-form-arrays": "^3.0.2", @@ -3987,6 +3990,302 @@ "node": ">=10" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", + "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.0", + "@parcel/watcher-darwin-arm64": "2.5.0", + "@parcel/watcher-darwin-x64": "2.5.0", + "@parcel/watcher-freebsd-x64": "2.5.0", + "@parcel/watcher-linux-arm-glibc": "2.5.0", + "@parcel/watcher-linux-arm-musl": "2.5.0", + "@parcel/watcher-linux-arm64-glibc": "2.5.0", + "@parcel/watcher-linux-arm64-musl": "2.5.0", + "@parcel/watcher-linux-x64-glibc": "2.5.0", + "@parcel/watcher-linux-x64-musl": "2.5.0", + "@parcel/watcher-win32-arm64": "2.5.0", + "@parcel/watcher-win32-ia32": "2.5.0", + "@parcel/watcher-win32-x64": "2.5.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", + "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", + "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", + "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", + "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", + "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", + "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", + "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", + "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", + "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", + "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@patternfly/quickstarts": { "version": "5.1.0", "license": "MIT", @@ -4015,9 +4314,9 @@ } }, "node_modules/@patternfly/react-component-groups": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-5.5.4.tgz", - "integrity": "sha512-3qf0CU3vtHGGb38LkfCfZAfrm1zXReFVjpI5E0WryLOpWl+NamuYbKfyY7j1SCj8Zq4kqCTLdXj+je3WBs+GGg==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-5.5.5.tgz", + "integrity": "sha512-Cgp1XxyBWnEDKAQsP+B7A4wlz6Bcp0bjwSMamdOiCR4GALtpBXXGrv6daAomoVCkL9l3zibcAfm/o9d9XBE9Ag==", "dependencies": { "@patternfly/react-core": "^5.4.1", "@patternfly/react-icons": "^5.4.0", @@ -4632,9 +4931,9 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-config": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.3.3.tgz", - "integrity": "sha512-+vprQN6SYW8gTbv9mdwbFzfZbBziVhdpUpDstDq2rBv42Qb96WEY1titfVypCZC4iGsbRu6uYJfnWYKvpZXekQ==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.3.5.tgz", + "integrity": "sha512-JmeMHlAna+7sDwS8OsL5SLB0pwh+Z5opQLdBUI7QUogxUyTdcDvCg4p6qeYbqyNXuRLsCALiy9cvzxKZyBxjIQ==", "dev": true, "dependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", @@ -4661,12 +4960,12 @@ "js-yaml": "^4.1.0", "jws": "^4.0.0", "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.7.3", + "mini-css-extract-plugin": "^2.9.1", "path-browserify": "^1.0.1", "process": "^0.11.10", "react-refresh": "^0.14.0", - "sass": "^1.55.0", - "sass-loader": "^11.1.1", + "sass": "^1.79.4", + "sass-loader": "^16.0.2", "source-map-loader": "^3.0.1", "stream-browserify": "^3.0.0", "swc-loader": "^0.2.3", @@ -9165,7 +9464,8 @@ }, "node_modules/clsx": { "version": "1.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", "engines": { "node": ">=6" } @@ -10544,6 +10844,19 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "dev": true, @@ -12051,7 +12364,8 @@ }, "node_modules/final-form": { "version": "4.20.10", - "license": "MIT", + "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.10.tgz", + "integrity": "sha512-TL48Pi1oNHeMOHrKv1bCJUrWZDcD3DIG6AGYVNOnyZPr7Bd/pStN0pL+lfzF5BNoj/FclaoiaLenk4XUIFVYng==", "dependencies": { "@babel/runtime": "^7.10.0" }, @@ -12065,14 +12379,16 @@ }, "node_modules/final-form-arrays": { "version": "3.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/final-form-arrays/-/final-form-arrays-3.1.0.tgz", + "integrity": "sha512-TWBvun+AopgBLw9zfTFHBllnKMVNEwCEyDawphPuBGGqNsuhGzhT7yewHys64KFFwzIs6KEteGLpKOwvTQEscQ==", "peerDependencies": { "final-form": "^4.20.8" } }, "node_modules/final-form-focus": { "version": "1.1.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/final-form-focus/-/final-form-focus-1.1.2.tgz", + "integrity": "sha512-Gd+Bd2Ll7ijo3/sd6kJ/bwLkhc2bUJPxTON6fIqee/008EJpACWhT+zoWCm9q6NcfMcWRS+Sp5ikRX8iqdXeGQ==", "peerDependencies": { "final-form": ">=1.3.0" } @@ -12505,20 +12821,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "dev": true, @@ -13501,9 +13803,10 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "dev": true, - "license": "MIT" + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -16798,14 +17101,6 @@ "node": ">=6" } }, - "node_modules/klona": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/launch-editor": { "version": "2.6.1", "dev": true, @@ -17536,11 +17831,13 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", "dev": true, - "license": "MIT", "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -17554,14 +17851,15 @@ } }, "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.12.0", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -17570,8 +17868,9 @@ }, "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -17581,13 +17880,15 @@ }, "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -17749,6 +18050,13 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, "node_modules/node-emoji": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", @@ -22483,7 +22791,8 @@ }, "node_modules/react-final-form": { "version": "6.5.9", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", + "integrity": "sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==", "dependencies": { "@babel/runtime": "^7.15.4" }, @@ -22498,7 +22807,8 @@ }, "node_modules/react-final-form-arrays": { "version": "3.1.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react-final-form-arrays/-/react-final-form-arrays-3.1.4.tgz", + "integrity": "sha512-siVFAolUAe29rMR6u8VwepoysUcUdh6MLV2OWnCtKpsPRUdT9VUgECjAPaVMAH2GROZNiVB9On1H9MMrm9gdpg==", "dependencies": { "@babel/runtime": "^7.19.4" }, @@ -23343,12 +23653,13 @@ } }, "node_modules/sass": { - "version": "1.69.5", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.82.0.tgz", + "integrity": "sha512-j4GMCTa8elGyN9A7x7bEglx0VgSpNUG4W4wNedQ33wSMdnkqQCT8HTwOaVSV4e6yQovcu/3Oc4coJP/l0xhL2Q==", "dev": true, - "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -23356,31 +23667,35 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-loader": { - "version": "11.1.1", + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz", + "integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==", "dev": true, - "license": "MIT", "dependencies": { - "klona": "^2.0.4", "neo-async": "^2.6.2" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0", + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", + "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { - "fibers": { + "@rspack/core": { "optional": true }, "node-sass": { @@ -23388,9 +23703,43 @@ }, "sass": { "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true } } }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/saxes": { "version": "6.0.0", "dev": true, diff --git a/package.json b/package.json index 0b7f265b7..8f2a776e4 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "@babel/compat-data": "^7.23.3", "@babel/runtime": "^7.23.2", "@babel/types": "^7.23.3", - "@data-driven-forms/pf4-component-mapper": "^3.22.4", - "@data-driven-forms/react-form-renderer": "^3.22.4", + "@data-driven-forms/pf4-component-mapper": "^3.23.5", + "@data-driven-forms/react-form-renderer": "^3.23.5", "@formatjs/cli": "6.2.2", "@patternfly/quickstarts": "^5.1.0", - "@patternfly/react-component-groups": "^5.5.4", + "@patternfly/react-component-groups": "^5.5.5", "@patternfly/react-core": "^5.1.1", "@patternfly/react-data-view": "^5.7.1", "@patternfly/react-icons": "^5.1.1", @@ -87,7 +87,7 @@ "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", - "@redhat-cloud-services/frontend-components-config": "^6.3.3", + "@redhat-cloud-services/frontend-components-config": "^6.3.5", "@redhat-cloud-services/tsc-transform-imports": "^1.0.4", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^13.0.0", diff --git a/src/Messages.js b/src/Messages.js index 3bbeacd71..48447be0d 100644 --- a/src/Messages.js +++ b/src/Messages.js @@ -842,12 +842,12 @@ export default defineMessages({ defaultMessage: 'Roles', }, userGroups: { - id: 'user-groups', + id: 'userGroups', description: 'User groups plural', defaultMessage: 'User groups', }, userGroup: { - id: 'user-groups', + id: 'userGroup', description: 'User group singular', defaultMessage: 'User group', }, @@ -1023,11 +1023,46 @@ export default defineMessages({ description: 'Overview Hero third list item', defaultMessage: `Assign users to these groups, allowing them to inherit the permissions associated with their group's roles`, }, + cores: { + id: 'cores', + description: 'Cores label', + defaultMessage: 'Cores', + }, + totalAccountAvailability: { + id: 'totalAccountAvailability', + description: 'Total account availability text', + defaultMessage: 'Total availability from {billingAccount}: {count} Cores', + }, + billingAccount: { + id: 'billingAccount', + description: 'Billing account label', + defaultMessage: 'Billing account', + }, + createWorkspaceSuccessTitle: { + id: 'createWorkspaceSuccessTitle', + description: 'Create workspace success notification title', + defaultMessage: 'New {name} workspace have been successfully created', + }, + createWorkspaceErrorTitle: { + id: 'createWorkspaceErrorTitle', + description: 'Create workspace error notification title', + defaultMessage: 'Failed creating {name} workspace', + }, + createWorkspaceErrorDescription: { + id: 'createWorkspaceErrorDescription', + description: 'Create workspace error notification description', + defaultMessage: 'The workspace was not created successfuly.', + }, workspace: { id: 'workspace', description: 'Workspace singular label', defaultMessage: 'Workspace', }, + parentWorkspace: { + id: 'parentWorkspace', + description: 'Parent workspace label', + defaultMessage: 'Parent workspace', + }, workspaces: { id: 'workspaces', description: 'Workspaces heading', @@ -1084,6 +1119,97 @@ export default defineMessages({ description: 'Workspace detail breadcrumb title', defaultMessage: 'Workspace hierarchy: ', }, + createWorkspace: { + id: 'createWorkspace', + description: 'Create workspace action label', + defaultMessage: 'Create workspace', + }, + createNewWorkspace: { + id: 'createNewWorkspace', + description: 'Create newworkspace action label', + defaultMessage: 'Create new workspace', + }, + workspaceDetails: { + id: 'workspaceDetails', + description: 'Workspace details label', + defaultMessage: 'Workspace details', + }, + workspaceDetailsTitle: { + id: 'workspaceDetailsTitle', + description: 'Workspace details step title', + defaultMessage: 'Provide details for a workspace', + }, + workspaceDetailsDescription: { + id: 'workspaceDetailsDescription', + description: 'Workspace details step description', + defaultMessage: 'Complete the fields to create a workspace.', + }, + workspaceNamingGuidelines: { + id: 'workspaceNamingGuidelines', + description: 'Workspace naming guidelines hint', + defaultMessage: '{link} about the guidelines for naming your workspaces.', + }, + workspaceDescriptionMaxLength: { + id: 'workspaceDescriptionMaxLength', + description: 'Workspace description max length helper text', + defaultMessage: 'The first {count} characters will appear in the description field.', + }, + workspaceParentHelperText: { + id: 'workspaceParentHelperText', + description: 'Workspace parent field helper text', + defaultMessage: 'This workspace will inherit access control settings, such as user groups and associated roles, from its parent workspace.', + }, + workspaceBillingAccountHelperText: { + id: 'workspaceBillingAccountHelperText', + description: 'Workspace billing account field helper text', + defaultMessage: + "The default billing account is based on the parent workspace's billing account. You can switch to a different billing account as needed. This change is independent of the workspace hierarchy.", + }, + workspaceName: { + id: 'workspaceName', + description: 'Workspace name label', + defaultMessage: 'Workspace name', + }, + workspaceDescription: { + id: 'workspaceDescription', + description: 'Create newworkspace action label', + defaultMessage: 'Workspace description', + }, + setEarmark: { + id: 'setEarmark', + description: 'Set ear mark step label', + defaultMessage: 'Set ear mark for {bundle} features', + }, + selectFeatures: { + id: 'selectFeatures', + description: 'Select features label', + defaultMessage: 'Select feature(s)', + }, + availableFeatures: { + id: 'availableFeatures', + description: 'Available features label', + defaultMessage: 'Available feature(s)', + }, + earMark: { + id: 'earMark', + description: 'Ear mark label', + defaultMessage: 'Ear mark', + }, + earMarkOfFeatures: { + id: 'earMarkOfFeatures', + description: 'Ear mark of features label', + defaultMessage: 'Ear mark of feature(s)', + }, + reviewNewWorkspace: { + id: 'reviewNewWorkspace', + description: 'Review new workspace label', + defaultMessage: 'Review new workspace', + }, + reviewWorkspaceDescription: { + id: 'reviewWorkspaceDescription', + description: 'Review workspace description', + defaultMessage: 'Review the information below to make sure everything is correct before creating a new workspace.', + }, viewGroupsBtn: { id: 'viewGroupsBtn', description: 'View groups button', diff --git a/src/Routing.tsx b/src/Routing.tsx index 209aca213..6fa19413f 100644 --- a/src/Routing.tsx +++ b/src/Routing.tsx @@ -12,6 +12,7 @@ const Overview = lazy(() => import('./smart-components/overview/overview')); const WorkspacesOverview = lazy(() => import('./smart-components/workspaces/overview/about-access-tab')); const WorkspaceList = lazy(() => import('./smart-components/workspaces/WorkspaceList')); +const CreateWorkspaceWizard = lazy(() => import('./smart-components/workspaces/create-workspace/CreateWorkspaceWizard')); const WorkspaceDetail = lazy(() => import('./smart-components/workspaces/WorkspaceDetail')); const Users = lazy(() => import('./smart-components/user/users')); const UserDetail = lazy(() => import('./smart-components/user/user')); @@ -63,6 +64,12 @@ const getRoutes = ({ enableServiceAccounts, isITLess, isWorkspacesFlag, isCommon { path: pathnames.workspaces.path, element: WorkspaceList, + childRoutes: [ + { + path: pathnames['create-workspace'].path, + element: CreateWorkspaceWizard, + }, + ], }, { path: pathnames['workspace-detail'].path, diff --git a/src/helpers/user/user-helper.js b/src/helpers/user/user-helper.js index 958bb2dca..b1d475772 100644 --- a/src/helpers/user/user-helper.js +++ b/src/helpers/user/user-helper.js @@ -6,6 +6,8 @@ export const MANAGE_SUBSCRIPTIONS_VIEW_EDIT_USER = 'view-edit-user'; export const MANAGE_SUBSCRIPTIONS_VIEW_ALL = 'view-all'; export const MANAGE_SUBSCRIPTIONS_VIEW_EDIT_ALL = 'view-edit-all'; +const getITApiUrl = (isProd) => `https://api.access${isProd ? '' : '.stage'}.redhat.com`; + const principalApi = getPrincipalApi(); const principalStatusApiMap = { @@ -59,9 +61,21 @@ function handleError(error, reject) { reject(new Error(error.message)); } -export async function addUsers(usersData = { emails: [], isAdmin: undefined, message: undefined }, isCommonAuth = false) { - if (isCommonAuth) { - return Promise.resolve(); +export async function addUsers(usersData = { emails: [], isAdmin: undefined, message: undefined }, config) { + if (config) { + const currURL = `${getITApiUrl(config.isProd)}/account/v1/accounts/${config.accountId}/users/invite`; + return fetch(currURL, { + body: JSON.stringify({ + emails: usersData.emails, + message: usersData.message, + ...(usersData.isAdmin && { roles: ['organization_administrator'] }), + }), + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${config.token}`, + }), + }); } const token = await insights.chrome.auth.getToken(); const requestOpts = { @@ -86,9 +100,19 @@ export async function addUsers(usersData = { emails: [], isAdmin: undefined, mes return promise; } -export async function updateUserIsOrgAdminStatus(user, isCommonAuth = false) { - if (isCommonAuth) { - return Promise.resolve(); +export async function updateUserIsOrgAdminStatus(user, config) { + if (config) { + const currURL = `${getITApiUrl(config.isProd)}/account/v1/accounts/${config.accountId}/users/${user.id}/roles`; + return fetch(currURL, { + method: user.is_org_admin ? 'POST' : 'DELETE', + body: JSON.stringify({ + roles: ['organization_administrator'], + }), + headers: new Headers({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${config.token}`, + }), + }); } const token = await insights.chrome.auth.getToken(); let requestOpts = { @@ -111,9 +135,21 @@ export async function updateUserIsOrgAdminStatus(user, isCommonAuth = false) { return promise; } -export async function updateUsers(users, isCommonAuth = false) { - if (isCommonAuth) { - return Promise.resolve(); +export async function chageUsersStatus(users, config) { + if (config) { + return users.map((user) => { + const currURL = `${getITApiUrl(config.isProd)}/account/v1/accounts/${config.accountId}/users/${user.id}/status`; + return fetch(currURL, { + method: 'POST', + body: JSON.stringify({ + status: user.is_active ? 'enabled' : 'disabled', + }), + headers: new Headers({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${config.token}`, + }), + }); + }); } const token = await insights.chrome.auth.getToken(); let requestOpts = { diff --git a/src/helpers/workspaces/workspaces-helper.ts b/src/helpers/workspaces/workspaces-helper.ts index f24c9a2ff..11bca5a4b 100644 --- a/src/helpers/workspaces/workspaces-helper.ts +++ b/src/helpers/workspaces/workspaces-helper.ts @@ -1,3 +1,4 @@ +import { WorkspaceCreateBody } from '../../redux/reducers/workspaces-reducer'; import { getWorkspacesApi } from './api'; const workspacesApi = getWorkspacesApi(); @@ -9,3 +10,7 @@ export async function getWorkspaces() { export async function getWorkspace(ws: string) { return await workspacesApi.getWorkspace({ id: ws }); } + +export async function createWorkspace(config: WorkspaceCreateBody) { + return await workspacesApi.createWorkspace(config); +} diff --git a/src/locales/data.json b/src/locales/data.json index 0e9acf324..10404963d 100644 --- a/src/locales/data.json +++ b/src/locales/data.json @@ -74,9 +74,11 @@ "assignedUserGroupsTooltipBody": "User groups are granted roles that contain a set of permissions. Roles are limited to the workspace in which they were assigned", "assignedUserGroupsTooltipHeader": "Assigned user groups", "associatingServiceAccounts": "Associating service accounts", + "availableFeatures": "Available feature(s)", "backToPreviousPage": "Back to previous page", "baseRole": "Base role", "beginQuickStartLink": "Begin Quick start", + "billingAccount": "Billing account", "bindingsServiceCardDescription": "Grant access to your workspaces. This connects roles and user groups to specific workspaces. These bindings determine who can access what, and the actions they're allowed to perform.", "cancel": "Cancel", "changesWillBeLost": "All changes will be lost", @@ -99,14 +101,21 @@ "continue": "Continue", "copyAnExistingRole": "Copy an existing role", "copyToAllPermissions": "Copy to all", + "cores": "Cores", "createAnotherGroup": "Create another group", "createAnotherRole": "Create another role", "createAtLeastOneItem": "create at least one {item}", "createGroup": "Create group", + "createNewWorkspace": "Create new workspace", "createRole": "Create role", "createRoleErrorDescription": "The role was not added successfuly.", "createRoleErrorTitle": "Failed adding role", "createRoleFromScratch": "Create a role from scratch", + "createUserGroup": "Create user group", + "createWorkspace": "Create workspace", + "createWorkspaceErrorDescription": "The workspace was not created successfuly.", + "createWorkspaceErrorTitle": "Failed creating {name} workspace", + "createWorkspaceSuccessTitle": "New {name} workspace have been successfully created", "creatingGroup": "Creating a group", "creatingRoleCanceled": "Creating role was canceled by the user", "deactivateUsersButton": "Deactivate users", @@ -122,7 +131,7 @@ "defineCostResources": "Define Cost Management resources", "definedResources": "Defined resources", "delete": "Delete", - "deleteCustomRoleModalBody": "Deleting the {name} may remove acess to certain user groups in your organization", + "deleteCustomRoleModalBody": "Deleting the {count, plural, one {the {name} role} other {{count} roles}} may remove acess to certain user groups in your organization", "deleteCustomRoleModalHeader": "Delete role?", "deleteGroup": "Delete group", "deleteGroupQuestion": "Delete group?", @@ -131,6 +140,9 @@ "deleteRole": "Delete role", "deleteRoleConfirm": "Delete role", "deleteRoleQuestion": "Delete role?", + "deleteRolesAction": "Delete Roles", + "deleteUserGroupModalBody": "Deleting {count, plural, one {the {name} user group} other {{count} user groups}} will impact user access configuration.", + "deleteUserGroupModalTitle": "Delete user {count, plural, one {group} other {groups}}?", "deleteUserModalBody": "will lose all the roles associated with the user groups it belongs to.", "deleteUserModalTitle": "Remove from user groups?", "deletingGroupRemovesRoles": "Deleting the {name} group removes all roles from the members inside the group.", @@ -138,6 +150,8 @@ "description": "Description", "discard": "Discard", "discardedInputsWarning": "All inputs will be discarded", + "earMark": "Ear mark", + "earMarkOfFeatures": "Ear mark of feature(s)", "edit": "Edit", "editGroupCanceledDescription": "Edit group was canceled by the user.", "editGroupErrorDescription": "The group was not updated successfuly.", @@ -284,6 +298,7 @@ "overviewSupportingFeaturesTitle": "About default groups", "owner": "Owner", "pageNotExists": "Page you are looking for does not exist.", + "parentWorkspace": "Parent workspace", "permission": "Permission", "permissionNotDisplayedDescription": "The permission either does not exist or has already been added to this role. Adjust your filters and try again. Note: Applications that only have wildcard permissions (for example, compliance:*:*) aren't included in this table and can't be added to your custom role.", "permissionResourcesDetails": "If there needs to be more details on the resources the permission is to be used for, it would be detailed here.", @@ -362,7 +377,9 @@ "returnToStepNumber": "Return to step {number}", "review": "Review", "reviewDetails": "Review details", + "reviewNewWorkspace": "Review new workspace", "reviewRoleDetails": "Review and confirm the details for your role, or click Back to revise.", + "reviewWorkspaceDescription": "Review the information below to make sure everything is correct before creating a new workspace.", "role": "role", "roleCreatedSuccessfully": "You have successfully created a new role", "roleDescription": "Role description", @@ -378,6 +395,7 @@ "seeMore": "See more", "selectAll": "Select all ({length})", "selectAppsToViewPermissions": "Select applications to view your personal permissions.", + "selectFeatures": "Select feature(s)", "selectGroups": "Select groups", "selectNone": "Select none (0)", "selectPage": "Select page ({length})", @@ -390,21 +408,24 @@ "serviceAccount": "Service account", "serviceAccounts": "Service accounts", "serviceAccountsPage": "Service Accounts admin page", + "setEarmark": "Set ear mark for {bundle} features", "status": "Status", "stay": "Stay", "timeCreated": "Time created", "toConfigureUserAccess": "To configure user access to applications", "toManageUsersText": "To manage users, go to your", + "totalAccountAvailability": "Total availability from {billingAccount}: {count} Cores", "triggerMyCatalog": "Trigger my catalog", "triggerMyQuickstart": "Trigger my quickstart", "tryChangingFilters": "Try changing your filter settings.", "understandActionIrreversible": "I understand that this action cannot be undone", "ungroupedSystems": "Ungrouped systems", "user": "user", - "user-groups": "User group", "userAccessAdmin": "User Access Admin", "userAccessAdminHint": "You can manage other users' permissions with 'User access'", "userDescription": "{username}'s roles, groups and permissions.", + "userGroup": "User group", + "userGroups": "User groups", "userGroupsEmptyStateSubtitle": "This filter criteria matches no user groups.{br}Try changing your filter input.", "userGroupsEmptyStateTitle": "No user group found", "userNotFound": "User not found", @@ -440,6 +461,15 @@ "whyNotSeeingAllPermissions": "Why am I not seeing all of my permissions?", "workspace": "Workspace", "workspaceAssignment": "Workspace assignment (TBD)", + "workspaceBillingAccountHelperText": "The default billing account is based on the parent workspace's billing account. You can switch to a different billing account as needed. This change is independent of the workspace hierarchy.", + "workspaceDescription": "Workspace description", + "workspaceDescriptionMaxLength": "The first {count} characters will appear in the description field.", + "workspaceDetails": "Workspace details", + "workspaceDetailsDescription": "Complete the fields to create a workspace.", + "workspaceDetailsTitle": "Provide details for a workspace", + "workspaceName": "Workspace name", + "workspaceNamingGuidelines": "{link} about the guidelines for naming your workspaces.", + "workspaceParentHelperText": "This workspace will inherit access control settings, such as user groups and associated roles, from its parent workspace.", "workspaces": "Workspaces", "workspacesAccessDescription": "Specify which workspaces you'd like to apply your selected permissions to, using the dropdowns below.", "workspacesAccessTitle": "Define Workspaces access", diff --git a/src/locales/translations.json b/src/locales/translations.json index 964e3cf05..b2be203fd 100644 --- a/src/locales/translations.json +++ b/src/locales/translations.json @@ -73,9 +73,11 @@ "assignedUserGroupsTooltipBody": "User groups are granted roles that contain a set of permissions. Roles are limited to the workspace in which they were assigned", "assignedUserGroupsTooltipHeader": "Assigned user groups", "associatingServiceAccounts": "Associating service accounts", + "availableFeatures": "Available feature(s)", "backToPreviousPage": "Back to previous page", "baseRole": "Base role", "beginQuickStartLink": "Begin Quick start", + "billingAccount": "Billing account", "bindingsServiceCardDescription": "Grant access to your workspaces. This connects roles and user groups to specific workspaces. These bindings determine who can access what, and the actions they're allowed to perform.", "cancel": "Cancel", "changesWillBeLost": "All changes will be lost", @@ -98,14 +100,21 @@ "continue": "Continue", "copyAnExistingRole": "Copy an existing role", "copyToAllPermissions": "Copy to all", + "cores": "Cores", "createAnotherGroup": "Create another group", "createAnotherRole": "Create another role", "createAtLeastOneItem": "create at least one {item}", "createGroup": "Create group", + "createNewWorkspace": "Create new workspace", "createRole": "Create role", "createRoleErrorDescription": "The role was not added successfuly.", "createRoleErrorTitle": "Failed adding role", "createRoleFromScratch": "Create a role from scratch", + "createUserGroup": "Create user group", + "createWorkspace": "Create workspace", + "createWorkspaceErrorDescription": "The workspace was not created successfuly.", + "createWorkspaceErrorTitle": "Failed creating {name} workspace", + "createWorkspaceSuccessTitle": "New {name} workspace have been successfully created", "creatingGroup": "Creating a group", "creatingRoleCanceled": "Creating role was canceled by the user", "deactivateUsersButton": "Deactivate users", @@ -121,7 +130,7 @@ "defineCostResources": "Define Cost Management resources", "definedResources": "Defined resources", "delete": "Delete", - "deleteCustomRoleModalBody": "Deleting the {name} may remove acess to certain user groups in your organization", + "deleteCustomRoleModalBody": "Deleting the {count, plural, one {the {name} role} other {{count} roles}} may remove acess to certain user groups in your organization", "deleteCustomRoleModalHeader": "Delete role?", "deleteGroup": "Delete group", "deleteGroupQuestion": "Delete group?", @@ -130,6 +139,9 @@ "deleteRole": "Delete role", "deleteRoleConfirm": "Delete role", "deleteRoleQuestion": "Delete role?", + "deleteRolesAction": "Delete Roles", + "deleteUserGroupModalBody": "Deleting {count, plural, one {the {name} user group} other {{count} user groups}} will impact user access configuration.", + "deleteUserGroupModalTitle": "Delete user {count, plural, one {group} other {groups}}?", "deleteUserModalBody": "will lose all the roles associated with the user groups it belongs to.", "deleteUserModalTitle": "Remove from user groups?", "deletingGroupRemovesRoles": "Deleting the {name} group removes all roles from the members inside the group.", @@ -137,6 +149,8 @@ "description": "Description", "discard": "Discard", "discardedInputsWarning": "All inputs will be discarded", + "earMark": "Ear mark", + "earMarkOfFeatures": "Ear mark of feature(s)", "edit": "Edit", "editGroupCanceledDescription": "Edit group was canceled by the user.", "editGroupErrorDescription": "The group was not updated successfuly.", @@ -283,6 +297,7 @@ "overviewSupportingFeaturesTitle": "About default groups", "owner": "Owner", "pageNotExists": "Page you are looking for does not exist.", + "parentWorkspace": "Parent workspace", "permission": "Permission", "permissionNotDisplayedDescription": "The permission either does not exist or has already been added to this role. Adjust your filters and try again. Note: Applications that only have wildcard permissions (for example, compliance:*:*) aren't included in this table and can't be added to your custom role.", "permissionResourcesDetails": "If there needs to be more details on the resources the permission is to be used for, it would be detailed here.", @@ -361,7 +376,9 @@ "returnToStepNumber": "Return to step {number}", "review": "Review", "reviewDetails": "Review details", + "reviewNewWorkspace": "Review new workspace", "reviewRoleDetails": "Review and confirm the details for your role, or click Back to revise.", + "reviewWorkspaceDescription": "Review the information below to make sure everything is correct before creating a new workspace.", "role": "role", "roleCreatedSuccessfully": "You have successfully created a new role", "roleDescription": "Role description", @@ -377,6 +394,7 @@ "seeMore": "See more", "selectAll": "Select all ({length})", "selectAppsToViewPermissions": "Select applications to view your personal permissions.", + "selectFeatures": "Select feature(s)", "selectGroups": "Select groups", "selectNone": "Select none (0)", "selectPage": "Select page ({length})", @@ -389,21 +407,24 @@ "serviceAccount": "Service account", "serviceAccounts": "Service accounts", "serviceAccountsPage": "Service Accounts admin page", + "setEarmark": "Set ear mark for {bundle} features", "status": "Status", "stay": "Stay", "timeCreated": "Time created", "toConfigureUserAccess": "To configure user access to applications", "toManageUsersText": "To manage users, go to your", + "totalAccountAvailability": "Total availability from {billingAccount}: {count} Cores", "triggerMyCatalog": "Trigger my catalog", "triggerMyQuickstart": "Trigger my quickstart", "tryChangingFilters": "Try changing your filter settings.", "understandActionIrreversible": "I understand that this action cannot be undone", "ungroupedSystems": "Ungrouped systems", "user": "user", - "user-groups": "User group", "userAccessAdmin": "User Access Admin", "userAccessAdminHint": "You can manage other users' permissions with 'User access'", "userDescription": "{username}'s roles, groups and permissions.", + "userGroup": "User group", + "userGroups": "User groups", "userGroupsEmptyStateSubtitle": "This filter criteria matches no user groups.{br}Try changing your filter input.", "userGroupsEmptyStateTitle": "No user group found", "userNotFound": "User not found", @@ -439,6 +460,15 @@ "whyNotSeeingAllPermissions": "Why am I not seeing all of my permissions?", "workspace": "Workspace", "workspaceAssignment": "Workspace assignment (TBD)", + "workspaceBillingAccountHelperText": "The default billing account is based on the parent workspace's billing account. You can switch to a different billing account as needed. This change is independent of the workspace hierarchy.", + "workspaceDescription": "Workspace description", + "workspaceDescriptionMaxLength": "The first {count} characters will appear in the description field.", + "workspaceDetails": "Workspace details", + "workspaceDetailsDescription": "Complete the fields to create a workspace.", + "workspaceDetailsTitle": "Provide details for a workspace", + "workspaceName": "Workspace name", + "workspaceNamingGuidelines": "{link} about the guidelines for naming your workspaces.", + "workspaceParentHelperText": "This workspace will inherit access control settings, such as user groups and associated roles, from its parent workspace.", "workspaces": "Workspaces", "workspacesAccessDescription": "Specify which workspaces you'd like to apply your selected permissions to, using the dropdowns below.", "workspacesAccessTitle": "Define Workspaces access", diff --git a/src/presentational-components/InputHelpPopover.tsx b/src/presentational-components/InputHelpPopover.tsx new file mode 100644 index 000000000..ea95e0a93 --- /dev/null +++ b/src/presentational-components/InputHelpPopover.tsx @@ -0,0 +1,25 @@ +import React, { ReactNode } from 'react'; +import { HelpIcon } from '@patternfly/react-icons'; +import { Button, Popover } from '@patternfly/react-core'; + +interface InputHelpPopoverProps { + headerContent?: ReactNode; + bodyContent?: ReactNode; + field?: string; +} + +const InputHelpPopover: React.FC = ({ headerContent = null, bodyContent = null, field = 'input' }) => ( + + + +); + +export default InputHelpPopover; diff --git a/src/redux/action-types.js b/src/redux/action-types.js index 75d91e867..ec2438de1 100644 --- a/src/redux/action-types.js +++ b/src/redux/action-types.js @@ -11,7 +11,7 @@ export const UPDATE_GROUPS_FILTERS = 'UPDATE_GROUPS_FILTERS'; export const ADD_USERS = 'ADD_USERS'; export const UPDATE_USER_IS_ORG_ADMIN_STATUS = 'UPDATE_USER_IS_ORG_ADMIN_STATUS'; -export const UPDATE_USERS = 'UPDATE_USERS'; +export const CHANGE_USERS_STATUS = 'CHANGE_USERS_STATUS'; export const FETCH_USERS = 'FETCH_USERS'; export const UPDATE_USERS_FILTERS = 'UPDATE_USERS_FILTERS'; @@ -70,3 +70,4 @@ export const API_ERROR = 'API_ERROR'; export const FETCH_WORKSPACES = 'FETCH_WORKSPACES'; export const FETCH_WORKSPACE = 'FETCH_WORKSPACE'; +export const CREATE_WORKSPACE = 'CREATE_WORKSPACE'; diff --git a/src/redux/actions/user-actions.js b/src/redux/actions/user-actions.js index 050b265e4..bc07ea02e 100644 --- a/src/redux/actions/user-actions.js +++ b/src/redux/actions/user-actions.js @@ -8,15 +8,15 @@ import { locale } from '../../AppEntry'; /** * An action creator function to invite new users to CRC. * @param { emails: string[], isOrgAdmin: boolean, message: string } usersData data to be sent to server. - * @param {boolean} isCommonAuth feature flag to indicate if common auth is used or fedramp branch. + * @param { isProd: boolean, token: string } config config object with env and token. * @returns action to be dispatched */ -export const addUsers = (usersData, isCommonAuth) => { +export const addUsers = (usersData, config) => { const cache = createIntlCache(); const intl = createIntl({ locale, messages: providerMessages }, cache); return { type: ActionTypes.ADD_USERS, - payload: UserHelper.addUsers(usersData, isCommonAuth), + payload: UserHelper.addUsers(usersData, config), meta: { notifications: { rejected: (payload) => { @@ -45,15 +45,15 @@ export const addUsers = (usersData, isCommonAuth) => { /** * An action creator function to promote/demote an user to be org. admin in CRC. * @param {id: UUID, is_org_admin: boolean} user to be promoted to organization administrator. - * @param {boolean} isCommonAuth feature flag to indicate if common auth is used or fedramp branch. + * @param { isProd: boolean, token: string } config config object with env and token. * @returns action to be dispatched */ -export const updateUserIsOrgAdminStatus = (user, isCommonAuth) => { +export const updateUserIsOrgAdminStatus = (user, config) => { const cache = createIntlCache(); const intl = createIntl({ locale, messages: providerMessages }, cache); return { type: ActionTypes.UPDATE_USER_IS_ORG_ADMIN_STATUS, - payload: UserHelper.updateUserIsOrgAdminStatus(user, isCommonAuth), + payload: UserHelper.updateUserIsOrgAdminStatus(user, config), meta: { notifications: { fulfilled: { @@ -78,15 +78,15 @@ export const updateUserIsOrgAdminStatus = (user, isCommonAuth) => { /** * An action creator function to change user status to active/inactive in CRC. * @param {User} userList list of users to change their status. - * @param {boolean} isCommonAuth feature flag to indicate if common auth is used or fedramp branch. + * @param { isProd: boolean, token: string } config config object with env and token. * @returns action to be dispatched */ -export const updateUsers = (userList, isCommonAuth) => { +export const chageUsersStatus = (userList, config) => { const cache = createIntlCache(); const intl = createIntl({ locale, messages: providerMessages }, cache); return { - type: ActionTypes.UPDATE_USERS, - payload: UserHelper.updateUsers(userList, isCommonAuth), + type: ActionTypes.CHANGE_USERS_STATUS, + payload: UserHelper.chageUsersStatus(userList, config), meta: { notifications: { fulfilled: { diff --git a/src/redux/actions/workspaces-actions.ts b/src/redux/actions/workspaces-actions.ts index f5518265b..1f4359698 100644 --- a/src/redux/actions/workspaces-actions.ts +++ b/src/redux/actions/workspaces-actions.ts @@ -1,5 +1,10 @@ import * as ActionTypes from '../action-types'; import * as WorkspacesHelper from '../../helpers/workspaces/workspaces-helper'; +import { WorkspaceCreateBody } from '../reducers/workspaces-reducer'; +import { createIntl, createIntlCache } from 'react-intl'; +import providerMessages from '../../locales/data.json'; +import { locale } from '../../AppEntry'; +import messages from '../../Messages'; export const fetchWorkspaces = () => ({ type: ActionTypes.FETCH_WORKSPACES, @@ -10,3 +15,28 @@ export const fetchWorkspace = (ws: string) => ({ type: ActionTypes.FETCH_WORKSPACE, payload: WorkspacesHelper.getWorkspace(ws), }); + +export const createWorkspace = (config: WorkspaceCreateBody) => { + const cache = createIntlCache(); + const intl = createIntl({ locale, messages: providerMessages as any }, cache); // eslint-disable-line @typescript-eslint/no-explicit-any + + return { + type: ActionTypes.CREATE_WORKSPACE, + payload: WorkspacesHelper.createWorkspace(config), + meta: { + notifications: { + rejected: (payload?: { detail: string }) => ({ + variant: 'danger', + title: intl.formatMessage(messages.createWorkspaceErrorTitle, { name: config.name }), + dismissDelay: 8000, + description: payload?.detail || intl.formatMessage(messages.createWorkspaceErrorDescription), + }), + fulfilled: { + variant: 'success', + title: intl.formatMessage(messages.createWorkspaceSuccessTitle, { name: config.name }), + dismissDelay: 8000, + }, + }, + }, + }; +}; diff --git a/src/redux/reducers/workspaces-reducer.ts b/src/redux/reducers/workspaces-reducer.ts index 6c0abdd2d..74cbb8fe9 100644 --- a/src/redux/reducers/workspaces-reducer.ts +++ b/src/redux/reducers/workspaces-reducer.ts @@ -1,12 +1,17 @@ import { FETCH_WORKSPACES, FETCH_WORKSPACE } from '../action-types'; -export interface Workspace { - id: string; +export interface WorkspaceCreateBody { + id?: string; name: string; description: string; children?: Workspace[]; parent_id?: string; } + +export interface Workspace extends WorkspaceCreateBody { + id: string; +} + export interface WorkspacesStore { isLoading: boolean; workspaces: Workspace[]; diff --git a/src/smart-components/common/inline-error.tsx b/src/smart-components/common/inline-error.tsx new file mode 100644 index 000000000..534648325 --- /dev/null +++ b/src/smart-components/common/inline-error.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import useFieldApi, { UseFieldApiConfig } from '@data-driven-forms/react-form-renderer/use-field-api'; +import { Alert } from '@patternfly/react-core'; + +const InlineErrorCmp: React.FC = (props) => { + const { title, description } = useFieldApi(props); + return ( + +

{description}

+
+ ); +}; + +export default InlineErrorCmp; diff --git a/src/smart-components/group/add-group/users-list-itless.js b/src/smart-components/group/add-group/users-list-itless.js index 3927f199a..486c64535 100644 --- a/src/smart-components/group/add-group/users-list-itless.js +++ b/src/smart-components/group/add-group/users-list-itless.js @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import { useLocation, useNavigate, Outlet } from 'react-router-dom'; import { TableToolbarView } from '../../../presentational-components/shared/table-toolbar-view'; import AppLink, { mergeToBasename } from '../../../presentational-components/shared/AppLink'; -import { fetchUsers, updateUsersFilters, updateUsers, updateUserIsOrgAdminStatus } from '../../../redux/actions/user-actions'; +import { fetchUsers, updateUsersFilters, chageUsersStatus, updateUserIsOrgAdminStatus } from '../../../redux/actions/user-actions'; import { Button, Switch as PF4Switch, Label, Modal, ModalVariant, List, ListItem, Checkbox, Stack, StackItem } from '@patternfly/react-core'; import { Dropdown, DropdownItem, DropdownToggle } from '@patternfly/react-core/deprecated'; import { sortable, nowrap } from '@patternfly/react-table'; @@ -226,7 +226,7 @@ const UsersListItless = ({ selectedUsers, setSelectedUsers, userLinks, usesMetaI const newUserList = users.map((user) => { return { id: user?.uuid || user?.external_source_id, is_active: isActivated }; }); - dispatch(updateUsers(newUserList)) + dispatch(chageUsersStatus(newUserList)) .then(() => { setFilters(newFilters); if (setSelectedUsers) { diff --git a/src/smart-components/user/invite-users/invite-users-modal-common-auth.tsx b/src/smart-components/user/invite-users/invite-users-modal-common-auth.tsx index 320beffbc..6385895f8 100644 --- a/src/smart-components/user/invite-users/invite-users-modal-common-auth.tsx +++ b/src/smart-components/user/invite-users/invite-users-modal-common-auth.tsx @@ -6,6 +6,7 @@ import messages from '../../../Messages'; import { componentTypes, validatorTypes } from '@data-driven-forms/react-form-renderer'; import componentMapper from '@data-driven-forms/pf4-component-mapper/component-mapper'; import AccordionCheckbox from '../../common/expandable-checkbox'; +import InlineError from '../../common/inline-error'; import { addUsers } from '../../../redux/actions/user-actions'; import { useDispatch } from 'react-redux'; import { useFlag } from '@unleash/proxy-client-react'; @@ -15,8 +16,10 @@ import { MANAGE_SUBSCRIPTIONS_VIEW_EDIT_USER, } from '../../../helpers/user/user-helper'; import { useOutletContext } from 'react-router-dom'; +import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -const ExpandableCheckboxComponent = 'expandableCheckbox'; +const ExpandableCheckboxComponent = 'expandable-checkbox'; +const InlineErrorComponent = 'inline-error'; const EMAIL_REGEXP = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; type SubmitValues = { @@ -35,24 +38,46 @@ type SubmitValues = { const InviteUsers = () => { const { fetchData } = useOutletContext<{ fetchData: (isSubmit: boolean) => void }>(); - const isCommonAuth = useFlag('platform.rbac.common-auth-model'); + const advancedPermissions = useFlag('platform.rbac.common-auth-model_advanced-permissions'); + const [token, setToken] = React.useState(null); + const [accountId, setAccountId] = React.useState(null); + const [responseError, setResponseError] = React.useState<{ title: string; description: string; url?: string } | null>(null); + const { auth, isProd } = useChrome(); const dispatch = useDispatch(); const onCancel = () => { fetchData(false); }; + + React.useEffect(() => { + const getToken = async () => { + setAccountId((await auth.getUser())?.identity?.internal?.account_id as string); + setToken((await auth.getToken()) as string); + }; + getToken(); + }, [auth]); const onSubmit = (values: SubmitValues) => { const action = addUsers( { emails: values['email-addresses']?.split(/[\s,]+/), message: values['invite-message'], isAdmin: values['customer-portal-permissions']?.['is-org-admin'], - manageSupportCases: values['customer-portal-permissions']?.['manage-support-cases'], - downloadUpdates: values['customer-portal-permissions']?.['download-software-updates'], - mangeSubscriptions: values['customer-portal-permissions']?.['manage-subscriptions'], + portal_manage_cases: values['customer-portal-permissions']?.['manage-support-cases'], + portal_download: values['customer-portal-permissions']?.['download-software-updates'], + portal_manage_subscriptions: values['customer-portal-permissions']?.['manage-subscriptions'], }, - isCommonAuth + { isProd: isProd(), token, accountId } ); - action.payload.then(() => fetchData(true)); + action.payload.then(async (response) => { + if (response.status === 200) { + fetchData(true); + } + const data = await response.json(); + setResponseError({ + title: data.title, + description: data.detail, + url: data.type, + }); + }); dispatch(action); }; const intl = useIntl(); @@ -60,6 +85,16 @@ const InviteUsers = () => { () => ({ description: intl.formatMessage(messages.inviteUsersDescription), fields: [ + ...(responseError + ? [ + { + component: InlineErrorComponent, + title: responseError.title, + description: responseError.description, + name: 'response-error', + }, + ] + : []), { component: componentTypes.TEXTAREA, label: intl.formatMessage(messages.inviteUsersFormEmailsFieldTitle), @@ -91,44 +126,48 @@ const InviteUsers = () => { title: intl.formatMessage(messages.inviteUsersFormIsAdminFieldTitle), description: intl.formatMessage(messages.inviteUsersFormIsAdminFieldDescription), }, - { - name: 'manage-support-cases', - title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldTitle), - description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldDescription), - }, - { - name: 'download-software-updates', - title: intl.formatMessage(messages.inviteUsersFormDownloadSoftwareUpdatesFieldTitle), - description: intl.formatMessage(messages.inviteUsersFormDownloadSoftwareUpdatesFieldDescription), - }, - { - name: 'manage-subscriptions', - title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldTitle), - description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldDescription), - options: [ - { - name: MANAGE_SUBSCRIPTIONS_VIEW_EDIT_USER, - title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditUsersOnlyTitle), - description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditUsersOnlyDescription), - }, - { - name: MANAGE_SUBSCRIPTIONS_VIEW_ALL, - title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewAllTitle), - description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewAllDescription), - }, - { - name: MANAGE_SUBSCRIPTIONS_VIEW_EDIT_ALL, - title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditAllTitle), - description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditAllDescription), - }, - ], - }, + ...(advancedPermissions + ? [ + { + name: 'manage-support-cases', + title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldTitle), + description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldDescription), + }, + { + name: 'download-software-updates', + title: intl.formatMessage(messages.inviteUsersFormDownloadSoftwareUpdatesFieldTitle), + description: intl.formatMessage(messages.inviteUsersFormDownloadSoftwareUpdatesFieldDescription), + }, + { + name: 'manage-subscriptions', + title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldTitle), + description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsFieldDescription), + options: [ + { + name: MANAGE_SUBSCRIPTIONS_VIEW_EDIT_USER, + title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditUsersOnlyTitle), + description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditUsersOnlyDescription), + }, + { + name: MANAGE_SUBSCRIPTIONS_VIEW_ALL, + title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewAllTitle), + description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewAllDescription), + }, + { + name: MANAGE_SUBSCRIPTIONS_VIEW_EDIT_ALL, + title: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditAllTitle), + description: intl.formatMessage(messages.inviteUsersFormManageSubscriptionsViewEditAllDescription), + }, + ], + }, + ] + : []), ], name: 'customer-portal-permissions', }, ], }), - [] + [responseError] ); return ( { formFields={[]} componentMapper={{ ...componentMapper, + [InlineErrorComponent]: InlineError, [ExpandableCheckboxComponent]: AccordionCheckbox, }} onCancel={onCancel} diff --git a/src/smart-components/workspaces/WorkspaceListTable.tsx b/src/smart-components/workspaces/WorkspaceListTable.tsx index 978fbe6e7..d5c75c8d0 100644 --- a/src/smart-components/workspaces/WorkspaceListTable.tsx +++ b/src/smart-components/workspaces/WorkspaceListTable.tsx @@ -1,15 +1,21 @@ -import React, { useEffect, useState } from 'react'; +import React, { Suspense, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { Outlet } from 'react-router-dom'; +import { useIntl } from 'react-intl'; import { fetchWorkspaces } from '../../redux/actions/workspaces-actions'; -import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups'; +import { BulkSelect, BulkSelectValue, ResponsiveAction, ResponsiveActions } from '@patternfly/react-component-groups'; import { DataView, DataViewTable, DataViewTh, DataViewToolbar, DataViewTrTree, useDataViewSelection } from '@patternfly/react-data-view'; import { Workspace } from '../../redux/reducers/workspaces-reducer'; import { RBACStore } from '../../redux/store'; import AppLink from '../../presentational-components/shared/AppLink'; import pathnames from '../../utilities/pathnames'; +import messages from '../../Messages'; +import useAppNavigate from '../../hooks/useAppNavigate'; const WorkspaceListTable = () => { + const intl = useIntl(); const dispatch = useDispatch(); + const navigate = useAppNavigate(); const selection = useDataViewSelection({ matchOption: (a, b) => a.id === b.id }); const { isLoading, workspaces, error } = useSelector((state: RBACStore) => state.workspacesReducer); @@ -72,7 +78,7 @@ const WorkspaceListTable = () => { const rows: DataViewTrTree[] = buildRows(hierarchicalWorkspaces); - const columns: DataViewTh[] = ['Name', 'Description']; + const columns: DataViewTh[] = [intl.formatMessage(messages.name), intl.formatMessage(messages.description)]; const handleBulkSelect = (value: BulkSelectValue) => { value === BulkSelectValue.none && selection.onSelect(false); @@ -95,10 +101,34 @@ const WorkspaceListTable = () => { onSelect={handleBulkSelect} /> } + actions={ + + navigate({ pathname: pathnames['create-workspace'].link })} + > + {intl.formatMessage(messages.createWorkspace)} + + + } /> )} + + { + dispatch(fetchWorkspaces()); + navigate({ pathname: pathnames.workspaces.link }); + }, + onCancel: () => navigate({ pathname: pathnames.workspaces.link }), + }, + }} + /> + ); }; diff --git a/src/smart-components/workspaces/create-workspace/CreateWorkspaceWizard.tsx b/src/smart-components/workspaces/create-workspace/CreateWorkspaceWizard.tsx new file mode 100644 index 000000000..c65d4a7a7 --- /dev/null +++ b/src/smart-components/workspaces/create-workspace/CreateWorkspaceWizard.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; +import { useFlag } from '@unleash/proxy-client-react'; +import FormRenderer from '@data-driven-forms/react-form-renderer/form-renderer'; +import Pf4FormTemplate from '@data-driven-forms/pf4-component-mapper/form-template'; +import componentMapper from '@data-driven-forms/pf4-component-mapper/component-mapper'; +import FormTemplateCommonProps from '@data-driven-forms/common/form-template'; +import { schemaBuilder, WORKSPACE_DESCRIPTION, WORKSPACE_NAME, WORKSPACE_PARENT } from './schema'; +import { createWorkspace } from '../../../redux/actions/workspaces-actions'; +import SetEarMark from './SetEarMark'; +import Review from './Review'; +import SetDetails from './SetDetails'; + +interface CreateWorkspaceWizardProps { + afterSubmit: () => void; + onCancel: () => void; +} + +const FormTemplate = (props: FormTemplateCommonProps) => ; + +export const mapperExtension = { + SetDetails, + SetEarMark, + Review, +}; + +export const CreateWorkspaceWizard: React.FunctionComponent = ({ afterSubmit, onCancel }) => { + const dispatch = useDispatch(); + const enableFeatures = useFlag('platform.rbac.workspaces-billing-features'); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const onSubmit = async (_v: any, form: any) => { + const values = form.getState().values; + await dispatch( + createWorkspace({ + name: values[WORKSPACE_NAME], + description: values[WORKSPACE_DESCRIPTION], + parent_id: values[WORKSPACE_PARENT].id, + }) + ); + afterSubmit(); + }; + + return ( + + ); +}; + +export default CreateWorkspaceWizard; diff --git a/src/smart-components/workspaces/create-workspace/NumberPicker.tsx b/src/smart-components/workspaces/create-workspace/NumberPicker.tsx new file mode 100644 index 000000000..7b77070e9 --- /dev/null +++ b/src/smart-components/workspaces/create-workspace/NumberPicker.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import useFieldApi, { UseFieldApiConfig } from '@data-driven-forms/react-form-renderer/use-field-api'; +import { NumberInput, NumberInputProps } from '@patternfly/react-core'; + +export interface NumberPickerProps extends Omit, UseFieldApiConfig {} + +const NumberPicker: React.FC = (props: NumberPickerProps) => { + const { + title, + disabled, + input: { onChange, checked, ...input }, + } = useFieldApi({ + name: props.name, + type: 'number', + }); + + return ( + null} + onChange={onChange} + onPlus={() => null} + inputName={props.name} + inputAriaLabel="number input" + minusBtnAriaLabel="minus" + plusBtnAriaLabel="plus" + /> + ); +}; + +export default NumberPicker; diff --git a/src/smart-components/workspaces/create-workspace/Review.tsx b/src/smart-components/workspaces/create-workspace/Review.tsx new file mode 100644 index 000000000..e8312d2ed --- /dev/null +++ b/src/smart-components/workspaces/create-workspace/Review.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { useFlag } from '@unleash/proxy-client-react'; +import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; +import { Title, DescriptionListGroup, DescriptionList, DescriptionListTerm, DescriptionListDescription, Text } from '@patternfly/react-core'; +import { useIntl } from 'react-intl'; +import { BUNDLES, WORKSPACE_ACCOUNT, WORKSPACE_DESCRIPTION, WORKSPACE_FEATURES, WORKSPACE_NAME, WORKSPACE_PARENT } from './schema'; +import messages from '../../../Messages'; + +const ReviewStep = () => { + const intl = useIntl(); + const formOptions = useFormApi(); + const values = formOptions.getState().values; + const enableBillingFeatures = useFlag('platform.rbac.workspaces-billing-features'); + + return ( +
+ + {intl.formatMessage(messages.reviewNewWorkspace)} + + {intl.formatMessage(messages.reviewWorkspaceDescription)} + + + {intl.formatMessage(messages.workspaceName)} + {values[WORKSPACE_NAME]} + + + {intl.formatMessage(messages.parentWorkspace)} + {values[WORKSPACE_PARENT].name} + + + {intl.formatMessage(messages.workspaceDetails)} + {values[WORKSPACE_DESCRIPTION] ?? '-'} + + {enableBillingFeatures && ( + <> + + {intl.formatMessage(messages.billingAccount)} + {values[WORKSPACE_ACCOUNT]} + + + {intl.formatMessage(messages.availableFeatures)} + + {values[WORKSPACE_FEATURES]?.length > 0 ? ( + values[WORKSPACE_FEATURES].map((item: string) => {BUNDLES.find((bundle) => bundle.value === item)?.label}) + ) : ( + - + )} + + + + )} + {values[WORKSPACE_FEATURES]?.length > 0 ? ( + + {intl.formatMessage(messages.earMarkOfFeatures)} + + {values[WORKSPACE_FEATURES].map((item: string) => { + const bundle = BUNDLES.find((bundle) => bundle.value === item); + return {`${bundle?.label}: ${values[`ear-mark-${bundle?.value}-cores`] ?? 0} Cores`}; + })} + + + ) : null} + +
+ ); +}; + +export default ReviewStep; diff --git a/src/smart-components/workspaces/create-workspace/SetDetails.tsx b/src/smart-components/workspaces/create-workspace/SetDetails.tsx new file mode 100644 index 000000000..16fa98e75 --- /dev/null +++ b/src/smart-components/workspaces/create-workspace/SetDetails.tsx @@ -0,0 +1,138 @@ +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import { useFlag } from '@unleash/proxy-client-react'; +import { + Button, + FormGroup, + FormSelect, + FormSelectOption, + Grid, + GridItem, + MenuToggle, + MenuToggleElement, + Select, + SelectList, + SelectOption, + Skeleton, + Text, +} from '@patternfly/react-core'; +import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; +import { WORKSPACE_ACCOUNT, WORKSPACE_PARENT } from './schema'; +import { RBACStore } from '../../../redux/store'; +import { fetchWorkspaces } from '../../../redux/actions/workspaces-actions'; +import messages from '../../../Messages'; +import InputHelpPopover from '../../../presentational-components/InputHelpPopover'; + +const SetDetails = () => { + const intl = useIntl(); + const dispatch = useDispatch(); + const enableBillingFeatures = useFlag('platform.rbac.workspaces-billing-features'); + const formOptions = useFormApi(); + const values = formOptions.getState().values; + const [isOpen, setIsOpen] = useState(false); + const { isLoading, workspaces } = useSelector((state: RBACStore) => state.workspacesReducer); + + useEffect(() => { + dispatch(fetchWorkspaces()); + }, [dispatch]); + + useEffect(() => { + !values[WORKSPACE_PARENT] && + formOptions.change( + WORKSPACE_PARENT, + workspaces.find((workspace) => workspace.parent_id === null) + ); + }, [workspaces.length]); + + const toggle = (toggleRef: React.Ref) => ( + setIsOpen(!isOpen)} isExpanded={isOpen}> + {values[WORKSPACE_PARENT]?.name} + + ); + + return ( + + + + {intl.formatMessage(messages.workspaceParentHelperText)} + + + } + field="parent workspace" + /> + } + > + {isLoading ? ( + + ) : ( + + )} + + + {enableBillingFeatures && ( + + + {intl.formatMessage(messages.workspaceBillingAccountHelperText)} + + + } + field="billing features" + /> + } + > + formOptions.change(WORKSPACE_ACCOUNT, value)} + aria-label="Workspace billing account select" + ouiaId="SetDetails-billing-account-select" + > + + + + + )} + + ); +}; + +export default SetDetails; diff --git a/src/smart-components/workspaces/create-workspace/SetEarMark.tsx b/src/smart-components/workspaces/create-workspace/SetEarMark.tsx new file mode 100644 index 000000000..569c8870a --- /dev/null +++ b/src/smart-components/workspaces/create-workspace/SetEarMark.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import { Stack, StackItem, TextContent, Title, Text, NumberInput } from '@patternfly/react-core'; +import useFieldApi, { UseFieldApiConfig } from '@data-driven-forms/react-form-renderer/use-field-api'; +import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; +import { WORKSPACE_ACCOUNT } from './schema'; +import messages from '../../../Messages'; + +const SetEarMark = ({ feature, ...props }: UseFieldApiConfig) => { + const intl = useIntl(); + const { input } = useFieldApi(props); + const formOptions = useFormApi(); + + return ( + + + + {intl.formatMessage(messages.setEarmark, { bundle: feature.label })} + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad + minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + + + {intl.formatMessage(messages.totalAccountAvailability, { + billingAccount: formOptions.getState().values[WORKSPACE_ACCOUNT] ?? 'XXX', + count: input.value || 0, + })} + + + + + {intl.formatMessage(messages.cores)} + + + ); +}; + +export default SetEarMark; diff --git a/src/smart-components/workspaces/create-workspace/schema.tsx b/src/smart-components/workspaces/create-workspace/schema.tsx new file mode 100644 index 000000000..320b60806 --- /dev/null +++ b/src/smart-components/workspaces/create-workspace/schema.tsx @@ -0,0 +1,218 @@ +import React from 'react'; +import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; +import InputHelpPopover from '../../../presentational-components/InputHelpPopover'; +import { locale } from '../../../AppEntry'; +import { createIntl, createIntlCache, FormattedMessage } from 'react-intl'; +import { componentTypes } from '@data-driven-forms/react-form-renderer'; +import { Workspace } from '../../../redux/reducers/workspaces-reducer'; +import providerMessages from '../../../locales/data.json'; +import messages from '../../../Messages'; +import { Button, Text } from '@patternfly/react-core'; + +// hardcoded for now +export const BUNDLES = [ + { + label: 'OpenShift', + value: 'openshift', + }, + { + label: 'RHEL', + value: 'rhel', + }, + { + label: 'Ansible Lightspeed', + value: 'lightspeed', + }, +]; + +export const WORKSPACE_NAME = 'workspace-name'; +export const WORKSPACE_DESCRIPTION = 'workspace-description'; +export const WORKSPACE_FEATURES = 'workspace-features'; +export const WORKSPACE_PARENT = 'workspace-parent'; +export const WORKSPACE_ACCOUNT = 'workspace-account'; + +export interface CreateWorkspaceFormValues { + [WORKSPACE_NAME]: string; + [WORKSPACE_DESCRIPTION]: string; + [WORKSPACE_FEATURES]: string[]; + [WORKSPACE_PARENT]: Workspace; + [WORKSPACE_ACCOUNT]: string; +} + +export const schemaBuilder = (enableBillingFeatures: boolean) => { + const cache = createIntlCache(); + const intl = createIntl({ locale, messages: providerMessages as any }, cache); // eslint-disable-line @typescript-eslint/no-explicit-any + + return { + fields: [ + { + component: 'wizard', + name: 'wizard', + isDynamic: true, + crossroads: ['workspace-features'], + 'data-ouia-component-id': 'create-workspace-wizard', + inModal: true, + showTitles: true, + title: intl.formatMessage(messages.createNewWorkspace), + fields: [ + { + title: intl.formatMessage(messages.workspaceDetails), + showTitle: false, + name: 'details', + nextStep: () => (enableBillingFeatures ? 'select-features' : 'review'), + fields: [ + { + name: 'details-title', + component: componentTypes.PLAIN_TEXT, + className: 'pf-v5-c-title pf-m-xl', + label: intl.formatMessage(messages.workspaceDetailsTitle), + }, + { + name: 'details-description', + component: componentTypes.PLAIN_TEXT, + className: 'pf-v5-u-my-md', + label: intl.formatMessage(messages.workspaceDetailsDescription), + }, + { + name: 'workspace-name', + component: componentTypes.TEXT_FIELD, + label: intl.formatMessage(messages.workspaceName), + isRequired: true, + FormGroupProps: { + labelIcon: ( + + + {intl.formatMessage(messages.learnMore)} + + ), + }} + /> + + } + field="workspace name" + /> + ), + }, + validate: [ + { + type: validatorTypes.REQUIRED, + }, + { + type: validatorTypes.MAX_LENGTH, + threshold: 150, + }, + ], + }, + { + name: 'workspace-details', + component: 'SetDetails', + fields: [ + { + name: WORKSPACE_PARENT, + component: componentTypes.TEXT_FIELD, + isRequired: true, + hideField: true, + validate: [ + { + type: validatorTypes.REQUIRED, + }, + ], + }, + { + name: WORKSPACE_ACCOUNT, + component: componentTypes.TEXT_FIELD, + isRequired: true, + hideField: true, + validate: [ + { + type: validatorTypes.REQUIRED, + }, + ], + }, + ], + }, + { + name: 'workspace-description', + component: componentTypes.TEXTAREA, + label: intl.formatMessage(messages.workspaceDescription), + FormGroupProps: { + labelIcon: ( + {intl.formatMessage(messages.workspaceDescriptionMaxLength, { count: 255 })}} + field="workspace description" + /> + ), + }, + validate: [ + { + type: validatorTypes.MAX_LENGTH, + threshold: 255, + }, + ], + }, + ], + }, + { + title: intl.formatMessage(messages.selectFeatures), + name: 'select-features', + nextStep: ({ values }: { values: CreateWorkspaceFormValues }) => { + const selectedFeatures = values['workspace-features'] || []; + return selectedFeatures.length > 0 ? `ear-mark-${selectedFeatures[0]}` : 'review'; + }, + fields: [ + { + name: 'features-description', + className: 'pf-v5-u-my-md', + component: componentTypes.PLAIN_TEXT, + label: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', + }, + { + name: 'workspace-features', + className: 'pf-v5-u-my-sm', + component: componentTypes.CHECKBOX, + options: BUNDLES, + }, + ], + }, + ...BUNDLES.map((feature) => ({ + name: `ear-mark-${feature.value}`, + title: feature.label, + showTitle: false, + substepOf: intl.formatMessage(messages.earMark), + nextStep: ({ values }: { values: CreateWorkspaceFormValues }) => { + const currIndex = values['workspace-features'].indexOf(feature.value); + return currIndex < values['workspace-features'].length - 1 ? `ear-mark-${values['workspace-features'][currIndex + 1]}` : 'review'; + }, + fields: [ + { + component: 'SetEarMark', + name: `ear-mark-${feature.value}-cores`, + feature, + isRequired: true, + }, + ], + })), + { + name: 'review', + title: intl.formatMessage(messages.review), + showTitle: false, + fields: [ + { + component: 'Review', + name: 'review', + }, + ], + }, + ], + }, + ], + }; +}; diff --git a/src/utilities/pathnames.js b/src/utilities/pathnames.js index 54036516a..092da5077 100644 --- a/src/utilities/pathnames.js +++ b/src/utilities/pathnames.js @@ -13,6 +13,11 @@ const pathnames = { path: '/workspaces/*', title: 'Workspaces', }, + 'create-workspace': { + link: '/workspaces/create-workspace', + path: 'create-workspace', + title: 'Create workspace', + }, 'workspace-detail': { link: '/workspaces/:workspaceId', path: '/workspaces/:workspaceId/*',