`);
@@ -159,7 +176,7 @@ describe('spacesManagementApp', () => {
expect(setBreadcrumbs).toHaveBeenCalledTimes(1);
expect(setBreadcrumbs).toHaveBeenCalledWith([
{ href: `/`, text: 'Spaces' },
- { text: `space with id some-space` },
+ { text: `Edit "space with id some-space"` },
]);
expect(docTitle.change).toHaveBeenCalledWith('Spaces');
expect(docTitle.reset).not.toHaveBeenCalled();
@@ -169,7 +186,7 @@ describe('spacesManagementApp', () => {
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
data-test-subj="kbnRedirectAppLink"
>
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true,"eventTracker":{"analytics":{}}}
+ Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"serverBasePath":"","http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"overlays":{"banners":{}},"notifications":{"toasts":{}},"theme":{"theme$":{}},"i18n":{},"logger":{"context":[]},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true}
`);
diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
index 40532a7259521..fa74316779a7e 100644
--- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
+++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
@@ -12,6 +12,7 @@ import { useParams } from 'react-router-dom';
import type { StartServicesAccessor } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
+import type { Logger } from '@kbn/logging';
import type { RegisterManagementAppArgs } from '@kbn/management-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type {
@@ -31,6 +32,7 @@ interface CreateParams {
getStartServices: StartServicesAccessor;
spacesManager: SpacesManager;
config: ConfigType;
+ logger: Logger;
getRolesAPIClient: () => Promise;
eventTracker: EventTracker;
getPrivilegesAPIClient: () => Promise;
@@ -38,7 +40,15 @@ interface CreateParams {
export const spacesManagementApp = Object.freeze({
id: 'spaces',
- create({ getStartServices, spacesManager, config, eventTracker }: CreateParams) {
+ create({
+ getStartServices,
+ spacesManager,
+ config,
+ logger,
+ eventTracker,
+ getRolesAPIClient,
+ getPrivilegesAPIClient,
+ }: CreateParams) {
const title = i18n.translate('xpack.spaces.displayName', {
defaultMessage: 'Spaces',
});
@@ -49,14 +59,23 @@ export const spacesManagementApp = Object.freeze({
title,
async mount({ element, setBreadcrumbs, history }) {
- const [[coreStart, { features }], { SpacesGridPage }, { ManageSpacePage }] =
- await Promise.all([getStartServices(), import('./spaces_grid'), import('./edit_space')]);
+ const [
+ [coreStart, { features }],
+ { SpacesGridPage },
+ { CreateSpacePage },
+ { EditSpacePage },
+ ] = await Promise.all([
+ getStartServices(),
+ import('./spaces_grid'),
+ import('./create_space'),
+ import('./edit_space'),
+ ]);
const spacesFirstBreadcrumb = {
text: title,
href: `/`,
};
- const { notifications, application, chrome, http } = coreStart;
+ const { notifications, application, chrome, http, overlays, theme } = coreStart;
chrome.docTitle.change(title);
@@ -88,7 +107,7 @@ export const spacesManagementApp = Object.freeze({
]);
return (
- {
- const { spaceId } = useParams<{ spaceId: string }>();
+ const { spaceId, selectedTabId } = useParams<{
+ spaceId: string;
+ selectedTabId?: string;
+ }>();
+
+ const breadcrumbText = (space: Space) =>
+ i18n.translate('xpack.spaces.management.editSpaceBreadcrumb', {
+ defaultMessage: 'Edit "{space}"',
+ values: { space: space.name },
+ });
const onLoadSpace = (space: Space) => {
setBreadcrumbs([
spacesFirstBreadcrumb,
{
- text: space.name,
+ text: breadcrumbText(space),
},
]);
};
return (
-
);
};
@@ -141,7 +179,7 @@ export const spacesManagementApp = Object.freeze({
-
+
diff --git a/x-pack/plugins/spaces/public/management/types.ts b/x-pack/plugins/spaces/public/management/types.ts
new file mode 100644
index 0000000000000..dbf839256e43e
--- /dev/null
+++ b/x-pack/plugins/spaces/public/management/types.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { Space } from '../../common';
+
+/**
+ * Values used in the "Customize Space" form
+ */
+export interface CustomizeSpaceFormValues extends Partial {
+ customIdentifier?: boolean;
+ avatarType?: 'initials' | 'image';
+ customAvatarInitials?: boolean;
+ customAvatarColor?: boolean;
+}
diff --git a/x-pack/plugins/spaces/public/plugin.tsx b/x-pack/plugins/spaces/public/plugin.tsx
index 6d545cdb70e61..86196333c0883 100644
--- a/x-pack/plugins/spaces/public/plugin.tsx
+++ b/x-pack/plugins/spaces/public/plugin.tsx
@@ -125,6 +125,7 @@ export class SpacesPlugin implements Plugin;
}
diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts
index bed49326efea5..021620b41dc55 100644
--- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts
+++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts
@@ -183,4 +183,32 @@ describe('SpacesManager', () => {
});
});
});
+
+ describe('#getRolesForSpace', () => {
+ it('retrieves roles for the specified space', async () => {
+ const coreStart = coreMock.createStart();
+ const rolesForSpace = [Symbol()];
+ coreStart.http.get.mockResolvedValue(rolesForSpace);
+ const spacesManager = new SpacesManager(coreStart.http);
+
+ const result = await spacesManager.getRolesForSpace('foo');
+ expect(coreStart.http.get).toHaveBeenCalledTimes(1);
+ expect(coreStart.http.get).toHaveBeenLastCalledWith('/internal/security/roles/foo');
+ expect(result).toEqual(rolesForSpace);
+ });
+ });
+
+ describe('#getContentForSpace', () => {
+ it('retrieves content for the specified space', async () => {
+ const coreStart = coreMock.createStart();
+ const spaceContent = [Symbol()];
+ coreStart.http.get.mockResolvedValue({ summary: spaceContent, total: spaceContent.length });
+ const spacesManager = new SpacesManager(coreStart.http);
+
+ const result = await spacesManager.getContentForSpace('foo');
+ expect(coreStart.http.get).toHaveBeenCalledTimes(1);
+ expect(coreStart.http.get).toHaveBeenLastCalledWith('/internal/spaces/foo/content_summary');
+ expect(result).toEqual({ summary: spaceContent, total: spaceContent.length });
+ });
+ });
});
diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts
index 7762158a4379b..962f02ca2bd79 100644
--- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts
+++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts
@@ -11,9 +11,11 @@ import { BehaviorSubject, skipWhile } from 'rxjs';
import type { HttpSetup } from '@kbn/core/public';
import type { SavedObjectsCollectMultiNamespaceReferencesResponse } from '@kbn/core-saved-objects-api-server';
import type { LegacyUrlAliasTarget } from '@kbn/core-saved-objects-common';
+import type { Role } from '@kbn/security-plugin-types-common';
import type { GetAllSpacesOptions, GetSpaceResult, Space } from '../../common';
import type { CopySavedObjectsToSpaceResponse } from '../copy_saved_objects_to_space/types';
+import type { SpaceContentTypeSummaryItem } from '../types';
interface SavedObjectTarget {
type: string;
@@ -192,4 +194,14 @@ export class SpacesManager {
private isAnonymousPath() {
return this.http.anonymousPaths.isAnonymous(window.location.pathname);
}
+
+ public getContentForSpace(
+ id: string
+ ): Promise<{ summary: SpaceContentTypeSummaryItem[]; total: number }> {
+ return this.http.get(`/internal/spaces/${id}/content_summary`);
+ }
+
+ public getRolesForSpace(id: string): Promise {
+ return this.http.get(`/internal/security/roles/${id}`);
+ }
}
diff --git a/x-pack/plugins/spaces/public/types.ts b/x-pack/plugins/spaces/public/types.ts
index b5540df1fdd5a..874ed9e8fd7d3 100644
--- a/x-pack/plugins/spaces/public/types.ts
+++ b/x-pack/plugins/spaces/public/types.ts
@@ -66,3 +66,14 @@ export interface SpacesApi {
*/
isSolutionViewEnabled: boolean;
}
+
+/**
+ * The API for retrieving content associated with a space returns an array of summary data for each type of
+ * saved object content. SpaceContentTypeSummaryItem is the format of the items included in this summary data.
+ */
+export interface SpaceContentTypeSummaryItem {
+ displayName: string;
+ icon?: string;
+ count: number;
+ type: string; // the type of saved object content (dashboard, search, config, etc)
+}
diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json
index ba13f984ef66d..20d3f7d3175d8 100644
--- a/x-pack/plugins/spaces/tsconfig.json
+++ b/x-pack/plugins/spaces/tsconfig.json
@@ -37,7 +37,21 @@
"@kbn/utility-types-jest",
"@kbn/security-plugin-types-public",
"@kbn/cloud-plugin",
- "@kbn/core-analytics-browser"
+ "@kbn/core-analytics-browser",
+ "@kbn/core-analytics-browser",
+ "@kbn/security-plugin-types-common",
+ "@kbn/core-application-browser",
+ "@kbn/unsaved-changes-prompt",
+ "@kbn/core-lifecycle-browser",
+ "@kbn/security-role-management-model",
+ "@kbn/security-ui-components",
+ "@kbn/react-kibana-mount",
+ "@kbn/shared-ux-utility",
+ "@kbn/core-application-common",
+ "@kbn/security-authorization-core",
+ "@kbn/core-notifications-browser",
+ "@kbn/logging",
+ "@kbn/core-logging-browser-mocks",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring.ts
index a86db4a8c27e4..480150b9a97a6 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring.ts
@@ -13,18 +13,28 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function monitoringAlertTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
+ const retry = getService('retry');
- // Failing: See https://github.com/elastic/kibana/issues/193072
- describe.skip('monitoring', () => {
+ describe('monitoring', () => {
const objectRemover = new ObjectRemover(supertest);
+ const run = async (id: string) => {
+ await retry.try(async () => {
+ // Sometimes the rule may already be running, which returns a 200. Try until it isn't
+ const response = await supertest
+ .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${id}/_run_soon`)
+ .set('kbn-xsrf', 'foo');
+ expect(response.status).to.eql(204);
+ });
+ };
+
after(async () => await objectRemover.removeAll());
it('should return an accurate history for a single success', async () => {
const createResponse = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
- .send(getTestRuleData({ schedule: { interval: '3s' } }));
+ .send(getTestRuleData({ schedule: { interval: '1h' } }));
expect(createResponse.status).to.eql(200);
objectRemover.add(Spaces.space1.id, createResponse.body.id, 'rule', 'alerting');
@@ -45,15 +55,21 @@ export default function monitoringAlertTests({ getService }: FtrProviderContext)
const createResponse = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
- .send(getTestRuleData({ schedule: { interval: '3s' } }));
+ .send(getTestRuleData({ schedule: { interval: '1h' } }));
expect(createResponse.status).to.eql(200);
- objectRemover.add(Spaces.space1.id, createResponse.body.id, 'rule', 'alerting');
+ const ruleId = createResponse.body.id;
+ objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting');
+
+ for (let i = 1; i < 3; i++) {
+ await waitForExecutionCount(i, ruleId);
+ await run(ruleId);
+ }
// Allow at least three executions
- await waitForExecutionCount(3, createResponse.body.id);
+ await waitForExecutionCount(3, ruleId);
const getResponse = await supertest.get(
- `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createResponse.body.id}`
+ `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}`
);
expect(getResponse.status).to.eql(200);
@@ -72,20 +88,26 @@ export default function monitoringAlertTests({ getService }: FtrProviderContext)
.send(
getTestRuleData({
rule_type_id: 'test.patternSuccessOrFailure',
- schedule: { interval: '3s' },
+ schedule: { interval: '1h' },
params: {
pattern,
},
})
);
expect(createResponse.status).to.eql(200);
- objectRemover.add(Spaces.space1.id, createResponse.body.id, 'rule', 'alerting');
- // Allow at least three executions
- await waitForExecutionCount(5, createResponse.body.id);
+ const ruleId = createResponse.body.id;
+ objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting');
+
+ for (let i = 1; i < 5; i++) {
+ await waitForExecutionCount(i, ruleId);
+ await run(ruleId);
+ }
+ // Allow at least five executions
+ await waitForExecutionCount(5, ruleId);
const getResponse = await supertest.get(
- `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createResponse.body.id}`
+ `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}`
);
expect(getResponse.status).to.eql(200);
diff --git a/x-pack/test/functional/apps/spaces/create_edit_space.ts b/x-pack/test/functional/apps/spaces/create_edit_space.ts
deleted file mode 100644
index cfffc752cca0c..0000000000000
--- a/x-pack/test/functional/apps/spaces/create_edit_space.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { FtrProviderContext } from '../../ftr_provider_context';
-
-export default function ({ getPageObjects, getService }: FtrProviderContext) {
- const kibanaServer = getService('kibanaServer');
- const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']);
- const testSubjects = getService('testSubjects');
-
- describe('edit space', () => {
- before(async () => {
- await kibanaServer.savedObjects.cleanStandardList();
- });
-
- after(async () => {
- await kibanaServer.savedObjects.cleanStandardList();
- });
-
- describe('solution view', () => {
- it('does not show solution view panel', async () => {
- await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', {
- shouldUseHashForSubUrl: false,
- });
-
- await testSubjects.existOrFail('spaces-edit-page');
- await testSubjects.existOrFail('spaces-edit-page > generalPanel');
- await testSubjects.missingOrFail('spaces-edit-page > navigationPanel');
- });
- });
- });
-}
diff --git a/x-pack/test/functional/apps/spaces/create_edit_space/acme_logo.png b/x-pack/test/functional/apps/spaces/create_edit_space/acme_logo.png
new file mode 100644
index 0000000000000..8e8ed078b8b61
Binary files /dev/null and b/x-pack/test/functional/apps/spaces/create_edit_space/acme_logo.png differ
diff --git a/x-pack/test/functional/apps/spaces/create_edit_space/create_edit_space.ts b/x-pack/test/functional/apps/spaces/create_edit_space/create_edit_space.ts
new file mode 100644
index 0000000000000..4b100595a38c6
--- /dev/null
+++ b/x-pack/test/functional/apps/spaces/create_edit_space/create_edit_space.ts
@@ -0,0 +1,117 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import { faker } from '@faker-js/faker';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getPageObjects, getService }: FtrProviderContext) {
+ const kibanaServer = getService('kibanaServer');
+ const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']);
+ const testSubjects = getService('testSubjects');
+ const spacesServices = getService('spaces');
+ const log = getService('log');
+
+ describe('Spaces Management: Create and Edit', () => {
+ before(async () => {
+ await kibanaServer.savedObjects.cleanStandardList();
+ });
+
+ after(async () => {
+ await kibanaServer.savedObjects.cleanStandardList();
+ });
+
+ describe('create space', () => {
+ const spaceName = `${faker.word.adjective()} space`;
+ const spaceId = spaceName.replace(' ', '-');
+
+ before(async () => {
+ await PageObjects.common.navigateToApp('spacesManagement');
+ await testSubjects.existOrFail('spaces-grid-page');
+
+ await PageObjects.spaceSelector.clickCreateSpace();
+ await testSubjects.existOrFail('spaces-create-page');
+ });
+
+ after(async () => {
+ await spacesServices.delete(spaceId);
+ });
+
+ it('create a space with a given name', async () => {
+ await PageObjects.spaceSelector.addSpaceName(spaceName);
+ await PageObjects.spaceSelector.clickSaveSpaceCreation();
+ await testSubjects.existOrFail(`spacesListTableRow-${spaceId}`);
+ });
+ });
+
+ describe('edit space', () => {
+ const spaceName = `${faker.word.adjective()} space`;
+ const spaceId = spaceName.replace(' ', '-');
+
+ before(async () => {
+ log.debug(`Creating space named "${spaceName}" with ID "${spaceId}"`);
+
+ await spacesServices.create({
+ id: spaceId,
+ name: spaceName,
+ disabledFeatures: [],
+ color: '#AABBCC',
+ });
+
+ await PageObjects.common.navigateToApp('spacesManagement');
+ await testSubjects.existOrFail('spaces-grid-page');
+ });
+
+ after(async () => {
+ await spacesServices.delete(spaceId);
+ });
+
+ it('allows changing space initials', async () => {
+ const spaceInitials = faker.string.alpha(2);
+
+ await testSubjects.click(`${spaceId}-hyperlink`);
+ await testSubjects.existOrFail('spaces-view-page > generalPanel');
+
+ await testSubjects.setValue('spaceLetterInitial', spaceInitials);
+ await testSubjects.click('save-space-button');
+
+ await testSubjects.existOrFail('spaces-grid-page'); // wait for grid page to reload
+ await testSubjects.existOrFail(`space-avatar-${spaceId}`);
+ expect(await testSubjects.getVisibleText(`space-avatar-${spaceId}`)).to.be(spaceInitials);
+ });
+
+ it('allows changing space avatar', async () => {
+ await testSubjects.click(`${spaceId}-hyperlink`);
+ await testSubjects.existOrFail('spaces-view-page > generalPanel');
+
+ await testSubjects.click('image');
+
+ const avatarPath = require.resolve('./acme_logo.png');
+ log.debug(`Importing file '${avatarPath}' ...`);
+ await PageObjects.common.setFileInputPath(avatarPath);
+
+ await testSubjects.click('save-space-button');
+ await testSubjects.existOrFail('spaces-grid-page'); // wait for grid page to reload
+ await testSubjects.existOrFail(`space-avatar-${spaceId}`);
+ const avatarEl = await testSubjects.find(`space-avatar-${spaceId}`);
+ expect(await avatarEl.getAttribute('role')).to.be('img'); // expect that the space uses image avatar
+ });
+ });
+
+ describe('solution view', () => {
+ it('does not show solution view panel', async () => {
+ await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', {
+ shouldUseHashForSubUrl: false,
+ });
+
+ await testSubjects.existOrFail('spaces-view-page');
+ await testSubjects.existOrFail('spaces-view-page > generalPanel');
+ await testSubjects.missingOrFail('spaces-view-page > navigationPanel'); // xpack.spaces.allowSolutionVisibility is not enabled, so the solution view picker should not appear
+ });
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/spaces/create_edit_space/index.ts b/x-pack/test/functional/apps/spaces/create_edit_space/index.ts
new file mode 100644
index 0000000000000..dc96179e1cb7c
--- /dev/null
+++ b/x-pack/test/functional/apps/spaces/create_edit_space/index.ts
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function spacesApp({ loadTestFile }: FtrProviderContext) {
+ describe('Spaces app', function spacesAppTestSuite() {
+ loadTestFile(require.resolve('./create_edit_space'));
+ });
+}
diff --git a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts
index be03af7c896a0..66d5eb280d613 100644
--- a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts
+++ b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts
@@ -92,7 +92,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldUseHashForSubUrl: false,
});
- await testSubjects.existOrFail('spaces-edit-page');
+ await testSubjects.existOrFail('spaces-create-page');
});
it(`can navigate to edit space page`, async () => {
@@ -102,7 +102,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldUseHashForSubUrl: false,
});
- await testSubjects.existOrFail('spaces-edit-page');
+ await testSubjects.existOrFail('spaces-view-page');
});
});
diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts
index 3f00dda32c878..f6f69ada3c0c1 100644
--- a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts
+++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts
@@ -28,9 +28,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldUseHashForSubUrl: false,
});
- await testSubjects.existOrFail('spaces-edit-page');
- await testSubjects.existOrFail('spaces-edit-page > generalPanel');
- await testSubjects.existOrFail('spaces-edit-page > navigationPanel');
+ await testSubjects.existOrFail('spaces-view-page');
+ await testSubjects.existOrFail('spaces-view-page > generalPanel');
+ await testSubjects.existOrFail('spaces-view-page > navigationPanel');
});
it('changes the space solution and updates the side navigation', async () => {
@@ -58,9 +58,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldUseHashForSubUrl: false,
});
- await testSubjects.missingOrFail('userImpactWarning');
+ await testSubjects.missingOrFail('space-edit-page-user-impact-warning');
await PageObjects.spaceSelector.changeSolutionView('classic');
- await testSubjects.existOrFail('userImpactWarning'); // Warn that the change will impact other users
+ await testSubjects.existOrFail('space-edit-page-user-impact-warning'); // Warn that the change will impact other users
await PageObjects.spaceSelector.clickSaveSpaceCreation();
await PageObjects.spaceSelector.confirmModal();
diff --git a/x-pack/test/functional/apps/spaces/spaces_grid.ts b/x-pack/test/functional/apps/spaces/spaces_grid.ts
index 62363802db98a..bcb04f45b87cb 100644
--- a/x-pack/test/functional/apps/spaces/spaces_grid.ts
+++ b/x-pack/test/functional/apps/spaces/spaces_grid.ts
@@ -5,43 +5,120 @@
* 2.0.
*/
-import { FtrProviderContext } from '../../ftr_provider_context';
+import crypto from 'crypto';
+import expect from '@kbn/expect';
+import { type FtrProviderContext } from '../../ftr_provider_context';
-export default function enterSpaceFunctionalTests({
+export default function spaceDetailsViewFunctionalTests({
getService,
getPageObjects,
}: FtrProviderContext) {
- const kibanaServer = getService('kibanaServer');
- const PageObjects = getPageObjects(['security', 'spaceSelector', 'common']);
+ const PageObjects = getPageObjects(['common', 'settings', 'spaceSelector']);
const spacesService = getService('spaces');
const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
- const anotherSpace = {
- id: 'space2',
- name: 'space2',
- disabledFeatures: [],
- };
+ const testSpacesIds = [
+ 'odyssey',
+ // this number is chosen intentionally to not exceed the default 10 items displayed by spaces table
+ ...Array.from(new Array(5)).map((_) => `space-${crypto.randomUUID()}`),
+ ];
- describe('Spaces grid', function () {
+ describe('Spaces Management: List of Spaces', function () {
before(async () => {
- await spacesService.create(anotherSpace);
+ for (const testSpaceId of testSpacesIds) {
+ await spacesService.create({ id: testSpaceId, name: `${testSpaceId}-name` });
+ }
+
+ await PageObjects.settings.navigateTo();
+ await testSubjects.existOrFail('spaces');
+ });
+
+ beforeEach(async () => {
+ await PageObjects.common.navigateToUrl('management', 'kibana/spaces', {
+ ensureCurrentUrl: false,
+ shouldLoginIfPrompted: false,
+ shouldUseHashForSubUrl: false,
+ });
- await PageObjects.common.navigateToApp('spacesManagement');
await testSubjects.existOrFail('spaces-grid-page');
});
after(async () => {
- await spacesService.delete('another-space');
- await kibanaServer.savedObjects.cleanStandardList();
+ for (const testSpaceId of testSpacesIds) {
+ await spacesService.delete(testSpaceId);
+ }
+ });
+
+ it('should list all the spaces populated', async () => {
+ const renderedSpaceRow = await testSubjects.findAll('*spacesListTableRow-');
+
+ expect(renderedSpaceRow.length).to.equal(testSpacesIds.length + 1);
});
- it('can switch to a space from the row in the grid', async () => {
- // use the "current" badge confirm that Default is the current space
- await testSubjects.existOrFail('spacesListCurrentBadge-default');
- // click the switch button of "another space"
- await PageObjects.spaceSelector.clickSwitchSpaceButton('space2');
- // use the "current" badge confirm that "Another Space" is now the current space
- await testSubjects.existOrFail('spacesListCurrentBadge-space2');
+ it('does not display the space switcher button when viewing the details page for the current selected space', async () => {
+ const currentSpaceTitle = (
+ await PageObjects.spaceSelector.currentSelectedSpaceTitle()
+ )?.toLowerCase();
+
+ expect(currentSpaceTitle).to.equal('default');
+
+ await testSubjects.click('default-hyperlink');
+ await testSubjects.existOrFail('space-view-page-details-header');
+ expect(
+ (await testSubjects.getVisibleText('space-view-page-details-header'))
+ .toLowerCase()
+ .includes('default')
+ ).to.be(true);
+ await testSubjects.missingOrFail('spaces-view-page-switcher-button');
+ });
+
+ it("displays the space switcher button when viewing the details page of the space that's not the current selected one", async () => {
+ const testSpaceId = testSpacesIds[Math.floor(Math.random() * testSpacesIds.length)];
+
+ const currentSpaceTitle = (
+ await PageObjects.spaceSelector.currentSelectedSpaceTitle()
+ )?.toLowerCase();
+
+ expect(currentSpaceTitle).to.equal('default');
+
+ await testSubjects.click(`${testSpaceId}-hyperlink`);
+ await testSubjects.existOrFail('space-view-page-details-header');
+ expect(
+ (await testSubjects.getVisibleText('space-view-page-details-header'))
+ .toLowerCase()
+ .includes(`${testSpaceId}-name`)
+ ).to.be(true);
+ await testSubjects.existOrFail('spaces-view-page-switcher-button');
+ });
+
+ it('switches to a new space using the space switcher button', async () => {
+ const currentSpaceTitle = (
+ await PageObjects.spaceSelector.currentSelectedSpaceTitle()
+ )?.toLowerCase();
+
+ expect(currentSpaceTitle).to.equal('default');
+
+ const testSpaceId = testSpacesIds[Math.floor(Math.random() * testSpacesIds.length)];
+
+ await testSubjects.click(`${testSpaceId}-hyperlink`);
+ await testSubjects.click('spaces-view-page-switcher-button');
+
+ await retry.try(async () => {
+ const detailsTitle = (
+ await testSubjects.getVisibleText('space-view-page-details-header')
+ ).toLowerCase();
+
+ const currentSwitchSpaceTitle = (
+ await PageObjects.spaceSelector.currentSelectedSpaceTitle()
+ )?.toLocaleLowerCase();
+
+ return (
+ currentSwitchSpaceTitle &&
+ currentSwitchSpaceTitle === `${testSpaceId}-name` &&
+ detailsTitle.includes(currentSwitchSpaceTitle)
+ );
+ });
});
});
}
diff --git a/x-pack/test/functional/page_objects/space_selector_page.ts b/x-pack/test/functional/page_objects/space_selector_page.ts
index 5dce6ed2d7c94..e5afbd78fe767 100644
--- a/x-pack/test/functional/page_objects/space_selector_page.ts
+++ b/x-pack/test/functional/page_objects/space_selector_page.ts
@@ -288,4 +288,9 @@ export class SpaceSelectorPageObject extends FtrService {
);
expect(await msgElem.getVisibleText()).to.be('no spaces found');
}
+
+ async currentSelectedSpaceTitle() {
+ const spacesNavSelector = await this.testSubjects.find('spacesNavSelector');
+ return spacesNavSelector.getAttribute('title');
+ }
}
diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts
index fb58d22a40cdc..0b815e2a14033 100644
--- a/x-pack/test/security_solution_cypress/runner.ts
+++ b/x-pack/test/security_solution_cypress/runner.ts
@@ -7,6 +7,7 @@
import Url from 'url';
+import { createEsClientForFtrConfig } from '@kbn/test';
import { TransportResult } from '@elastic/elasticsearch';
import { FtrProviderContext } from '../common/ftr_provider_context';
import { tiAbusechMalware } from './pipelines/ti_abusech_malware';
@@ -20,7 +21,7 @@ export async function SecuritySolutionConfigurableCypressTestRunner({
}: FtrProviderContext) {
const log = getService('log');
const config = getService('config');
- const es = getService('es');
+ const es = createEsClientForFtrConfig(config);
const pipelines = [tiAbusechMalware, tiAbusechMalwareBazaar, tiAbusechUrl];