diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
index be4edbe2ea270..9f3a65583ddb7 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
@@ -41,6 +41,7 @@ interface Props {
export function AgentConfigurationList({ status, data, refetch }: Props) {
const { core } = useApmPluginContext();
+ const canSave = core.application.capabilities.apm.save;
const { basePath } = core.http;
const { search } = useLocation();
const theme = useTheme();
@@ -180,28 +181,36 @@ export function AgentConfigurationList({ status, data, refetch }: Props) {
),
},
- {
- width: px(units.double),
- name: '',
- render: (config: Config) => (
-
- ),
- },
- {
- width: px(units.double),
- name: '',
- render: (config: Config) => (
- setConfigToBeDeleted(config)}
- />
- ),
- },
+ ...(canSave
+ ? [
+ {
+ width: px(units.double),
+ name: '',
+ render: (config: Config) => (
+
+ ),
+ },
+ {
+ width: px(units.double),
+ name: '',
+ render: (config: Config) => (
+ setConfigToBeDeleted(config)}
+ />
+ ),
+ },
+ ]
+ : []),
];
return (
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
index c408d5e960cf3..c1f5ec154792d 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
@@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { EuiToolTip } from '@elastic/eui';
import {
EuiButton,
EuiFlexGroup,
@@ -73,15 +74,35 @@ function CreateConfigurationButton() {
const { basePath } = core.http;
const { search } = useLocation();
const href = createAgentConfigurationHref(search, basePath);
+ const canSave = core.application.capabilities.apm.save;
return (
-
- {i18n.translate('xpack.apm.agentConfig.createConfigButtonLabel', {
- defaultMessage: 'Create configuration',
- })}
-
+
+
+ {i18n.translate('xpack.apm.agentConfig.createConfigButtonLabel', {
+ defaultMessage: 'Create configuration',
+ })}
+
+
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx
index 56b3eaf425af7..3b4c127aab1e5 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx
@@ -3,17 +3,40 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import { EuiButton } from '@elastic/eui';
+import { EuiButton, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
export function CreateCustomLinkButton({ onClick }: { onClick: () => void }) {
+ const { core } = useApmPluginContext();
+ const canSave = core.application.capabilities.apm.save;
return (
-
- {i18n.translate(
- 'xpack.apm.settings.customizeUI.customLink.createCustomLink',
- { defaultMessage: 'Create custom link' }
- )}
-
+
+
+ {i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.createCustomLink',
+ { defaultMessage: 'Create custom link' }
+ )}
+
+
);
}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx
index d512ea19c7892..4bc1adee04bf4 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx
@@ -13,6 +13,7 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { isEmpty } from 'lodash';
+import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
import { CustomLink } from '../../../../../../common/custom_link/custom_link_types';
import { units, px } from '../../../../../style/variables';
import { ManagedTable } from '../../../../shared/ManagedTable';
@@ -26,6 +27,8 @@ interface Props {
export function CustomLinkTable({ items = [], onCustomLinkSelected }: Props) {
const [searchTerm, setSearchTerm] = useState('');
+ const { core } = useApmPluginContext();
+ const canSave = core.application.capabilities.apm.save;
const columns = [
{
@@ -61,22 +64,26 @@ export function CustomLinkTable({ items = [], onCustomLinkSelected }: Props) {
width: px(units.triple),
name: '',
actions: [
- {
- name: i18n.translate(
- 'xpack.apm.settings.customizeUI.customLink.table.editButtonLabel',
- { defaultMessage: 'Edit' }
- ),
- description: i18n.translate(
- 'xpack.apm.settings.customizeUI.customLink.table.editButtonDescription',
- { defaultMessage: 'Edit this custom link' }
- ),
- icon: 'pencil',
- color: 'primary',
- type: 'icon',
- onClick: (customLink: CustomLink) => {
- onCustomLinkSelected(customLink);
- },
- },
+ ...(canSave
+ ? [
+ {
+ name: i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.table.editButtonLabel',
+ { defaultMessage: 'Edit' }
+ ),
+ description: i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.table.editButtonDescription',
+ { defaultMessage: 'Edit this custom link' }
+ ),
+ icon: 'pencil',
+ color: 'primary',
+ type: 'icon',
+ onClick: (customLink: CustomLink) => {
+ onCustomLinkSelected(customLink);
+ },
+ },
+ ]
+ : []),
],
},
];
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
index 1da7d415b5660..4477ee5a99be3 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
@@ -7,22 +7,26 @@
import {
fireEvent,
render,
- waitFor,
RenderResult,
+ waitFor,
} from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
-import * as apmApi from '../../../../../services/rest/createCallApmApi';
+import { CustomLinkOverview } from '.';
import { License } from '../../../../../../../licensing/common/license';
-import * as hooks from '../../../../../hooks/use_fetcher';
+import { ApmPluginContextValue } from '../../../../../context/apm_plugin/apm_plugin_context';
+import {
+ mockApmPluginContextValue,
+ MockApmPluginContextWrapper,
+} from '../../../../../context/apm_plugin/mock_apm_plugin_context';
import { LicenseContext } from '../../../../../context/license/license_context';
-import { CustomLinkOverview } from '.';
+import * as hooks from '../../../../../hooks/use_fetcher';
+import * as apmApi from '../../../../../services/rest/createCallApmApi';
import {
expectTextsInDocument,
expectTextsNotInDocument,
} from '../../../../../utils/testHelpers';
import * as saveCustomLink from './CreateEditCustomLinkFlyout/saveCustomLink';
-import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context';
const data = [
{
@@ -39,6 +43,16 @@ const data = [
},
];
+function getMockAPMContext({ canSave }: { canSave: boolean }) {
+ return ({
+ ...mockApmPluginContextValue,
+ core: {
+ ...mockApmPluginContextValue.core,
+ application: { capabilities: { apm: { save: canSave }, ml: {} } },
+ },
+ } as unknown) as ApmPluginContextValue;
+}
+
describe('CustomLink', () => {
beforeAll(() => {
jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({});
@@ -70,9 +84,11 @@ describe('CustomLink', () => {
});
it('shows when no link is available', () => {
const component = render(
-
-
-
+
+
+
+
+
);
expectTextsInDocument(component, ['No links found.']);
});
@@ -91,6 +107,34 @@ describe('CustomLink', () => {
jest.clearAllMocks();
});
+ it('enables create button when user has writte privileges', () => {
+ const mockContext = getMockAPMContext({ canSave: true });
+
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+ const createButton = getByTestId('createButton') as HTMLButtonElement;
+ expect(createButton.disabled).toBeFalsy();
+ });
+
+ it('enables edit button on custom link table when user has writte privileges', () => {
+ const mockContext = getMockAPMContext({ canSave: true });
+
+ const { getAllByText } = render(
+
+
+
+
+
+ );
+
+ expect(getAllByText('Edit').length).toEqual(2);
+ });
+
it('shows a table with all custom link', () => {
const component = render(
@@ -108,9 +152,11 @@ describe('CustomLink', () => {
});
it('checks if create custom link button is available and working', () => {
+ const mockContext = getMockAPMContext({ canSave: true });
+
const { queryByText, getByText } = render(
-
+
@@ -137,9 +183,10 @@ describe('CustomLink', () => {
});
const openFlyout = () => {
+ const mockContext = getMockAPMContext({ canSave: true });
const component = render(
-
+
@@ -173,9 +220,10 @@ describe('CustomLink', () => {
});
it('deletes a custom link', async () => {
+ const mockContext = getMockAPMContext({ canSave: true });
const component = render(
-
+
@@ -356,4 +404,34 @@ describe('CustomLink', () => {
expectTextsNotInDocument(component, ['Start free 30-day trial']);
});
});
+
+ describe('with read-only user', () => {
+ it('disables create custom link button', () => {
+ const mockContext = getMockAPMContext({ canSave: false });
+
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+ const createButton = getByTestId('createButton') as HTMLButtonElement;
+ expect(createButton.disabled).toBeTruthy();
+ });
+
+ it('removes edit button on custom link table', () => {
+ const mockContext = getMockAPMContext({ canSave: false });
+
+ const { queryAllByText } = render(
+
+
+
+
+
+ );
+
+ expect(queryAllByText('Edit').length).toEqual(0);
+ });
+ });
});
diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
index fdf2fe3521d7e..70755540721dd 100644
--- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts
+++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
@@ -64,7 +64,7 @@ export const createCustomLinkRoute = createRoute({
params: t.type({
body: payloadRt,
}),
- options: { tags: ['access:apm'] },
+ options: { tags: ['access:apm', 'access:apm_write'] },
handler: async ({ context, request }) => {
if (!isActiveGoldLicense(context.licensing.license)) {
throw Boom.forbidden(INVALID_LICENSE);