Skip to content

Commit

Permalink
[APM] Custom links can still be created with a read only user. (#87089)
Browse files Browse the repository at this point in the history
* disabling buttons when user does not permission

* fixing test

* disabling create/edit button when user does not have write permission

* addressing PR comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
cauemarcondes and kibanamachine authored Jan 4, 2021
1 parent d797b0c commit cd06251
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -180,28 +181,36 @@ export function AgentConfigurationList({ status, data, refetch }: Props) {
<TimestampTooltip time={value} timeUnit="minutes" />
),
},
{
width: px(units.double),
name: '',
render: (config: Config) => (
<EuiButtonIcon
aria-label="Edit"
iconType="pencil"
href={editAgentConfigurationHref(config.service, search, basePath)}
/>
),
},
{
width: px(units.double),
name: '',
render: (config: Config) => (
<EuiButtonIcon
aria-label="Delete"
iconType="trash"
onClick={() => setConfigToBeDeleted(config)}
/>
),
},
...(canSave
? [
{
width: px(units.double),
name: '',
render: (config: Config) => (
<EuiButtonIcon
aria-label="Edit"
iconType="pencil"
href={editAgentConfigurationHref(
config.service,
search,
basePath
)}
/>
),
},
{
width: px(units.double),
name: '',
render: (config: Config) => (
<EuiButtonIcon
aria-label="Delete"
iconType="trash"
onClick={() => setConfigToBeDeleted(config)}
/>
),
},
]
: []),
];

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 (
<EuiFlexItem>
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButton color="primary" fill iconType="plusInCircle" href={href}>
{i18n.translate('xpack.apm.agentConfig.createConfigButtonLabel', {
defaultMessage: 'Create configuration',
})}
</EuiButton>
<EuiToolTip
content={
!canSave &&
i18n.translate(
'xpack.apm.agentConfig.configurationsPanelTitle.noPermissionTooltipLabel',
{
defaultMessage:
"Your user role doesn't have permissions to create agent configurations",
}
)
}
>
<EuiButton
color="primary"
fill
iconType="plusInCircle"
href={href}
isDisabled={!canSave}
>
{i18n.translate('xpack.apm.agentConfig.createConfigButtonLabel', {
defaultMessage: 'Create configuration',
})}
</EuiButton>
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<EuiButton color="primary" fill iconType="plusInCircle" onClick={onClick}>
{i18n.translate(
'xpack.apm.settings.customizeUI.customLink.createCustomLink',
{ defaultMessage: 'Create custom link' }
)}
</EuiButton>
<EuiToolTip
content={
!canSave &&
i18n.translate(
'xpack.apm.settings.customizeUI.customLink.noPermissionTooltipLabel',
{
defaultMessage:
"Your user role doesn't have permissions to create custom links",
}
)
}
>
<EuiButton
color="primary"
fill
iconType="plusInCircle"
onClick={onClick}
isDisabled={!canSave}
data-test-subj="createButton"
>
{i18n.translate(
'xpack.apm.settings.customizeUI.customLink.createCustomLink',
{ defaultMessage: 'Create custom link' }
)}
</EuiButton>
</EuiToolTip>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = [
{
Expand Down Expand Up @@ -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);
},
},
]
: []),
],
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand All @@ -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({});
Expand Down Expand Up @@ -70,9 +84,11 @@ describe('CustomLink', () => {
});
it('shows when no link is available', () => {
const component = render(
<LicenseContext.Provider value={goldLicense}>
<CustomLinkOverview />
</LicenseContext.Provider>
<MockApmPluginContextWrapper>
<LicenseContext.Provider value={goldLicense}>
<CustomLinkOverview />
</LicenseContext.Provider>
</MockApmPluginContextWrapper>
);
expectTextsInDocument(component, ['No links found.']);
});
Expand All @@ -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(
<LicenseContext.Provider value={goldLicense}>
<MockApmPluginContextWrapper value={mockContext}>
<CustomLinkOverview />
</MockApmPluginContextWrapper>
</LicenseContext.Provider>
);
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(
<LicenseContext.Provider value={goldLicense}>
<MockApmPluginContextWrapper value={mockContext}>
<CustomLinkOverview />
</MockApmPluginContextWrapper>
</LicenseContext.Provider>
);

expect(getAllByText('Edit').length).toEqual(2);
});

it('shows a table with all custom link', () => {
const component = render(
<LicenseContext.Provider value={goldLicense}>
Expand All @@ -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(
<LicenseContext.Provider value={goldLicense}>
<MockApmPluginContextWrapper>
<MockApmPluginContextWrapper value={mockContext}>
<CustomLinkOverview />
</MockApmPluginContextWrapper>
</LicenseContext.Provider>
Expand All @@ -137,9 +183,10 @@ describe('CustomLink', () => {
});

const openFlyout = () => {
const mockContext = getMockAPMContext({ canSave: true });
const component = render(
<LicenseContext.Provider value={goldLicense}>
<MockApmPluginContextWrapper>
<MockApmPluginContextWrapper value={mockContext}>
<CustomLinkOverview />
</MockApmPluginContextWrapper>
</LicenseContext.Provider>
Expand Down Expand Up @@ -173,9 +220,10 @@ describe('CustomLink', () => {
});

it('deletes a custom link', async () => {
const mockContext = getMockAPMContext({ canSave: true });
const component = render(
<LicenseContext.Provider value={goldLicense}>
<MockApmPluginContextWrapper>
<MockApmPluginContextWrapper value={mockContext}>
<CustomLinkOverview />
</MockApmPluginContextWrapper>
</LicenseContext.Provider>
Expand Down Expand Up @@ -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(
<LicenseContext.Provider value={goldLicense}>
<MockApmPluginContextWrapper value={mockContext}>
<CustomLinkOverview />
</MockApmPluginContextWrapper>
</LicenseContext.Provider>
);
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(
<LicenseContext.Provider value={goldLicense}>
<MockApmPluginContextWrapper value={mockContext}>
<CustomLinkOverview />
</MockApmPluginContextWrapper>
</LicenseContext.Provider>
);

expect(queryAllByText('Edit').length).toEqual(0);
});
});
});
Loading

0 comments on commit cd06251

Please sign in to comment.