Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make spaces plugin optional #149044

Merged
merged 9 commits into from
Jan 31, 2023
20 changes: 15 additions & 5 deletions x-pack/plugins/alerting/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
},
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "alerting"],
"configPath": [
"xpack",
"alerting"
],
"requiredPlugins": [
"actions",
"data",
Expand All @@ -19,9 +22,16 @@
"features",
"kibanaUtils",
"licensing",
"spaces",
"taskManager"
],
"optionalPlugins": ["usageCollection", "security", "monitoringCollection"],
"extraPublicDirs": ["common", "common/parse_duration"]
}
"optionalPlugins": [
"usageCollection",
"security",
"monitoringCollection",
"spaces"
],
"extraPublicDirs": [
"common",
"common/parse_duration"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface AlertingAuthorizationClientFactoryOpts {
ruleTypeRegistry: RuleTypeRegistry;
securityPluginSetup?: SecurityPluginSetup;
securityPluginStart?: SecurityPluginStart;
getSpace: (request: KibanaRequest) => Promise<Space>;
getSpace: (request: KibanaRequest) => Promise<Space | undefined>;
getSpaceId: (request: KibanaRequest) => string;
features: FeaturesPluginStart;
}
Expand All @@ -26,7 +26,7 @@ export class AlertingAuthorizationClientFactory {
private ruleTypeRegistry!: RuleTypeRegistry;
private securityPluginStart?: SecurityPluginStart;
private features!: FeaturesPluginStart;
private getSpace!: (request: KibanaRequest) => Promise<Space>;
private getSpace!: (request: KibanaRequest) => Promise<Space | undefined>;
private getSpaceId!: (request: KibanaRequest) => string;

public initialize(options: AlertingAuthorizationClientFactoryOpts) {
Expand Down
9 changes: 5 additions & 4 deletions x-pack/plugins/alerting/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import {
KibanaRequest,
Expand Down Expand Up @@ -162,7 +163,7 @@ export interface AlertingPluginsStart {
features: FeaturesPluginStart;
eventLog: IEventLogClientService;
licensing: LicensingPluginStart;
spaces: SpacesPluginStart;
spaces?: SpacesPluginStart;
security?: SecurityPluginStart;
data: DataPluginStart;
dataViews: DataViewsPluginStart;
Expand Down Expand Up @@ -417,10 +418,10 @@ export class AlertingPlugin {
securityPluginSetup: security,
securityPluginStart: plugins.security,
async getSpace(request: KibanaRequest) {
return plugins.spaces.spacesService.getActiveSpace(request);
return plugins.spaces?.spacesService.getActiveSpace(request);
},
getSpaceId(request: KibanaRequest) {
return plugins.spaces.spacesService.getSpaceId(request);
return plugins.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
},
features: plugins.features,
});
Expand All @@ -434,7 +435,7 @@ export class AlertingPlugin {
encryptedSavedObjectsClient,
spaceIdToNamespace,
getSpaceId(request: KibanaRequest) {
return plugins.spaces?.spacesService.getSpaceId(request);
return plugins.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
},
actions: plugins.actions,
eventLog: plugins.eventLog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { KibanaFeature } from '@kbn/features-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { Space } from '@kbn/spaces-plugin/public';
import { spacesManagerMock } from '@kbn/spaces-plugin/public/spaces_manager/mocks';
import { getUiApi } from '@kbn/spaces-plugin/public/ui_api';
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';

import { licenseMock } from '../../../../common/licensing/index.mock';
Expand All @@ -23,9 +25,14 @@ import { userAPIClientMock } from '../../users/index.mock';
import { createRawKibanaPrivileges } from '../__fixtures__/kibana_privileges';
import { indicesAPIClientMock, privilegesAPIClientMock, rolesAPIClientMock } from '../index.mock';
import { EditRolePage } from './edit_role_page';
import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section';
import { SpaceAwarePrivilegeSection } from './privileges/kibana/space_aware_privilege_section';
import { TransformErrorSection } from './privileges/kibana/transform_error_section';

const spacesManager = spacesManagerMock.create();
const { getStartServices } = coreMock.createSetup();
const spacesApiUi = getUiApi({ spacesManager, getStartServices });

const buildFeatures = () => {
return [
new KibanaFeature({
Expand Down Expand Up @@ -132,10 +139,12 @@ function getProps({
action,
role,
canManageSpaces = true,
spacesEnabled = true,
}: {
action: 'edit' | 'clone';
role?: Role;
canManageSpaces?: boolean;
spacesEnabled?: boolean;
}) {
const rolesAPIClient = rolesAPIClientMock.create();
rolesAPIClient.getRole.mockResolvedValue(role);
Expand All @@ -162,6 +171,9 @@ function getProps({
const { fatalErrors } = coreMock.createSetup();
const { http, docLinks, notifications } = coreMock.createStart();
http.get.mockImplementation(async (path: any) => {
if (!spacesEnabled) {
throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal
}
if (path === '/api/spaces/space') {
return buildSpaces();
}
Expand All @@ -183,6 +195,7 @@ function getProps({
fatalErrors,
uiCapabilities: buildUICapabilities(canManageSpaces),
history: scopedHistoryMock.create(),
spacesApiUi,
};
}

Expand Down Expand Up @@ -411,6 +424,194 @@ describe('<EditRolePage />', () => {
});
});

describe('with spaces disabled', () => {
it('can render readonly view when not enough privileges', async () => {
coreStart.application.capabilities = {
...coreStart.application.capabilities,
roles: {
save: false,
},
};

const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
name: 'my custom role',
metadata: {},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
expectReadOnlyFormButtons(wrapper);
});

it('can render a reserved role', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
name: 'superuser',
metadata: { _reserved: true },
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
expectReadOnlyFormButtons(wrapper);
});

it('can render a user defined role', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
name: 'my custom role',
metadata: {},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
expectSaveFormButtons(wrapper);
});

it('can render when creating a new role', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage {...getProps({ action: 'edit', spacesEnabled: false })} />
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
false
);
expectSaveFormButtons(wrapper);
});

it('redirects back to roles when creating a new role without privileges', async () => {
coreStart.application.capabilities = {
...coreStart.application.capabilities,
roles: {
save: false,
},
};

const props = getProps({ action: 'edit', spacesEnabled: false });
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage {...props} />
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(props.history.push).toHaveBeenCalledWith('/');
});

it('can render when cloning an existing role', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
name: '',
metadata: { _reserved: false },
elasticsearch: {
cluster: ['all', 'manage'],
indices: [
{
names: ['foo*'],
privileges: ['all'],
field_security: { except: ['f'], grant: ['b*'] },
},
],
run_as: ['elastic'],
},
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
false
);
expectSaveFormButtons(wrapper);
});

it('renders a partial read-only view when there is a transform error', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
canManageSpaces: false,
role: {
name: 'my custom role',
metadata: {},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [],
_transform_error: ['kibana'],
},
})}
/>
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find(TransformErrorSection)).toHaveLength(1);
expectReadOnlyFormButtons(wrapper);
});
});

it('registers fatal error if features endpoint fails unexpectedly', async () => {
const error = { response: { status: 500 } };
const getFeatures = jest.fn().mockRejectedValue(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
<KibanaPrivilegesRegion
kibanaPrivileges={new KibanaPrivileges(kibanaPrivileges, features)}
spaces={spaces.list}
spacesEnabled={spaces.enabled}
uiCapabilities={uiCapabilities}
canCustomizeSubFeaturePrivileges={license.getFeatures().allowSubFeaturePrivileges}
editable={!isRoleReadOnly}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading