diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js new file mode 100644 index 0000000000..2d08392ac6 --- /dev/null +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import PropTypes from 'prop-types'; +import { Link } from '@strapi/helper-plugin'; +import { useHistory } from 'react-router-dom'; +import styled from 'styled-components'; + +const MESSAGES_MAP = { + edit: { + id: 'app.component.table.edit', + defaultMessage: 'Edit {target}', + }, + read: { + id: 'app.component.table.read', + defaultMessage: 'Read {target}', + }, +}; + +const LinkStyled = styled(Link)` + svg { + path { + fill: ${({ theme }) => theme.colors.neutral500}; + } + } + + &:hover, + &:focus { + svg { + path { + fill: ${({ theme }) => theme.colors.neutral800}; + } + } + } +`; + +const DefaultButton = ({ tokenName, tokenId, buttonType, children }) => { + const { formatMessage } = useIntl(); + const { + location: { pathname }, + } = useHistory(); + + return ( + + {children} + + ); +}; + +DefaultButton.propTypes = { + tokenName: PropTypes.string.isRequired, + tokenId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + buttonType: PropTypes.string, + children: PropTypes.node.isRequired, +}; + +DefaultButton.defaultProps = { + buttonType: 'edit', +}; + +export default DefaultButton; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js index 2ed062fbfa..2ae7b441cb 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js @@ -24,6 +24,7 @@ const DeleteButton = ({ tokenName, onClickDelete }) => { }, { target: `${tokenName}` } )} + name="delete" noBorder icon={} /> diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js new file mode 100644 index 0000000000..85c9f96dbf --- /dev/null +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js @@ -0,0 +1,19 @@ +import React from 'react'; +import Eye from '@strapi/icons/Eye'; +import PropTypes from 'prop-types'; +import DefaultButton from '../DefaultButton'; + +const ReadButton = ({ tokenName, tokenId }) => { + return ( + + + + ); +}; + +ReadButton.propTypes = { + tokenName: PropTypes.string.isRequired, + tokenId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, +}; + +export default ReadButton; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js index 0c5a77ab5e..f781e03543 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js @@ -1,46 +1,13 @@ import React from 'react'; import Pencil from '@strapi/icons/Pencil'; -import { useIntl } from 'react-intl'; import PropTypes from 'prop-types'; -import { Link } from '@strapi/helper-plugin'; -import { useHistory } from 'react-router-dom'; -import styled from 'styled-components'; - -const LinkUpdate = styled(Link)` - svg { - path { - fill: ${({ theme }) => theme.colors.neutral500}; - } - } - - &:hover { - svg { - path { - fill: ${({ theme }) => theme.colors.neutral800}; - } - } - } -`; +import DefaultButton from '../DefaultButton'; const UpdateButton = ({ tokenName, tokenId }) => { - const { formatMessage } = useIntl(); - const { - location: { pathname }, - } = useHistory(); - return ( - + - + ); }; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js index 7b65618724..e91430ab73 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js @@ -14,8 +14,9 @@ import PropTypes from 'prop-types'; import { useHistory } from 'react-router-dom'; import DeleteButton from './DeleteButton'; import UpdateButton from './UpdateButton'; +import ReadButton from './ReadButton'; -const TableRows = ({ canDelete, canUpdate, onClickDelete, withBulkActions, rows }) => { +const TableRows = ({ canDelete, canUpdate, canRead, onClickDelete, withBulkActions, rows }) => { const { formatMessage } = useIntl(); const [{ query }] = useQueryParams(); const [, sortOrder] = query.sort.split(':'); @@ -73,6 +74,9 @@ const TableRows = ({ canDelete, canUpdate, onClickDelete, withBulkActions, rows {canUpdate && } + {!canUpdate && canRead && ( + + )} {canDelete && ( { headers={tableHeaders} contentType="api-tokens" rows={apiTokens} - withBulkActions={canDelete || canUpdate} + withBulkActions={canDelete || canUpdate || canRead} isLoading={isLoading} onConfirmDelete={(id) => deleteMutation.mutateAsync(id)} > )} diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/tests/index.test.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/tests/index.test.js index fcb5769a3d..ed83bb207c 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/tests/index.test.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/tests/index.test.js @@ -15,9 +15,7 @@ jest.mock('@strapi/helper-plugin', () => ({ ...jest.requireActual('@strapi/helper-plugin'), useNotification: jest.fn(), useFocusWhenNavigate: jest.fn(), - useRBAC: jest.fn(() => ({ - allowedActions: { canCreate: true, canDelete: true, canRead: true, canUpdate: true }, - })), + useRBAC: jest.fn(), useGuidedTour: jest.fn(() => ({ startSection: jest.fn(), })), @@ -73,6 +71,9 @@ describe('ADMIN | Pages | API TOKENS | ListPage', () => { }); it('should show a list of api tokens', async () => { + useRBAC.mockImplementation(() => ({ + allowedActions: { canCreate: true, canDelete: true, canRead: true, canUpdate: true }, + })); const history = createMemoryHistory(); history.push('/settings/api-tokens'); const app = makeApp(history); @@ -631,7 +632,8 @@ describe('ADMIN | Pages | API TOKENS | ListPage', () => { fill: #8e8ea9; } - .c36:hover svg path { + .c36:hover svg path, + .c36:focus svg path { fill: #32324d; } @@ -956,6 +958,7 @@ describe('ADMIN | Pages | API TOKENS | ListPage', () => { aria-disabled="false" aria-labelledby="tooltip-3" class="c25 c26" + name="delete" tabindex="-1" type="button" > @@ -988,7 +991,7 @@ describe('ADMIN | Pages | API TOKENS | ListPage', () => { }); it('should not show the create button when the user does not have the rights to create', async () => { - useRBAC.mockImplementationOnce(() => ({ + useRBAC.mockImplementation(() => ({ allowedActions: { canCreate: false, canDelete: true, canRead: true, canUpdate: true }, })); @@ -999,4 +1002,34 @@ describe('ADMIN | Pages | API TOKENS | ListPage', () => { await waitFor(() => expect(queryByTestId('create-api-token-button')).not.toBeInTheDocument()); }); + + it('should show the delete button when the user have the rights to delete', async () => { + useRBAC.mockImplementation(() => ({ + allowedActions: { canCreate: false, canDelete: true, canRead: true, canUpdate: false }, + })); + const history = createMemoryHistory(); + history.push('/settings/api-tokens'); + const app = makeApp(history); + + const { container } = render(app); + + await waitFor(() => { + expect(container.querySelector('button[name="delete"]')).toBeInTheDocument(); + }); + }); + + it('should show the read button when the user have the rights to read and not to update', async () => { + useRBAC.mockImplementation(() => ({ + allowedActions: { canCreate: false, canDelete: true, canRead: true, canUpdate: false }, + })); + const history = createMemoryHistory(); + history.push('/settings/api-tokens'); + const app = makeApp(history); + + const { container } = render(app); + + await waitFor(() => { + expect(container.querySelector('a[title*="Read"]')).toBeInTheDocument(); + }); + }); }); diff --git a/packages/core/admin/admin/src/permissions/defaultPermissions.js b/packages/core/admin/admin/src/permissions/defaultPermissions.js index a2ff388692..be3e6b9b9d 100644 --- a/packages/core/admin/admin/src/permissions/defaultPermissions.js +++ b/packages/core/admin/admin/src/permissions/defaultPermissions.js @@ -76,12 +76,7 @@ const permissions = { update: [{ action: 'admin::webhooks.update', subject: null }], }, 'api-tokens': { - main: [ - { action: 'admin::api-tokens.create', subject: null }, - { action: 'admin::api-tokens.read', subject: null }, - { action: 'admin::api-tokens.update', subject: null }, - { action: 'admin::api-tokens.delete', subject: null }, - ], + main: [], create: [{ action: 'admin::api-tokens.create', subject: null }], delete: [{ action: 'admin::api-tokens.delete', subject: null }], read: [{ action: 'admin::api-tokens.read', subject: null }], diff --git a/packages/core/admin/package.json b/packages/core/admin/package.json index 86df18afa6..2935455ae7 100644 --- a/packages/core/admin/package.json +++ b/packages/core/admin/package.json @@ -34,7 +34,8 @@ "test:front": "cross-env IS_EE=true jest --config ./jest.config.front.js", "test:front:watch": "cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll", "test:front:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js", - "test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll" + "test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll", + "test:front:ce:cov": "cross-env IS_EE=false jest --config ./jest.config.front.js --coverage --collectCoverageFrom='/packages/core/admin/admin/**/*.js' --coverageDirectory='/packages/core/admin/coverage'" }, "dependencies": { "@babel/core": "7.18.10",