diff --git a/examples/alerting_example/server/plugin.ts b/examples/alerting_example/server/plugin.ts index 8e246960937ec..4141b48ffeeaf 100644 --- a/examples/alerting_example/server/plugin.ts +++ b/examples/alerting_example/server/plugin.ts @@ -19,6 +19,7 @@ import { Plugin, CoreSetup } from 'kibana/server'; import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../src/core/server'; import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerts/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../../x-pack/plugins/features/server'; @@ -47,6 +48,7 @@ export class AlertingExamplePlugin implements Plugin + + + + +
+ +
+ +
+
; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ec457704e89c7..b55136411caff 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -537,6 +537,7 @@ export const DEFAULT_APP_CATEGORIES: Readonly<{ id: string; label: string; order: number; + euiIconType: string; }; }>; diff --git a/src/core/utils/default_app_categories.ts b/src/core/utils/default_app_categories.ts index 1fb7c284c0dfd..8fe1861a7910f 100644 --- a/src/core/utils/default_app_categories.ts +++ b/src/core/utils/default_app_categories.ts @@ -59,5 +59,6 @@ export const DEFAULT_APP_CATEGORIES = Object.freeze({ defaultMessage: 'Management', }), order: 5000, + euiIconType: 'managementApp', }, }); diff --git a/x-pack/plugins/actions/server/feature.ts b/x-pack/plugins/actions/server/feature.ts index 321509a7b9de6..abe5921fda7f1 100644 --- a/x-pack/plugins/actions/server/feature.ts +++ b/x-pack/plugins/actions/server/feature.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { ACTION_SAVED_OBJECT_TYPE, ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './saved_objects'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; export const ACTIONS_FEATURE = { id: 'actions', @@ -14,6 +15,7 @@ export const ACTIONS_FEATURE = { }), icon: 'bell', navLinkId: 'actions', + category: DEFAULT_APP_CATEGORIES.management, app: [], management: { insightsAndAlerting: ['triggersActions'], diff --git a/x-pack/plugins/alerting_builtins/server/feature.ts b/x-pack/plugins/alerting_builtins/server/feature.ts index 316bae98bf8c1..a7c8b940fbf06 100644 --- a/x-pack/plugins/alerting_builtins/server/feature.ts +++ b/x-pack/plugins/alerting_builtins/server/feature.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { ID as IndexThreshold } from './alert_types/index_threshold/alert_type'; import { BUILT_IN_ALERTS_FEATURE_ID } from '../common'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; export const BUILT_IN_ALERTS_FEATURE = { id: BUILT_IN_ALERTS_FEATURE_ID, @@ -15,6 +16,7 @@ export const BUILT_IN_ALERTS_FEATURE = { }), icon: 'bell', app: [], + category: DEFAULT_APP_CATEGORIES.management, management: { insightsAndAlerting: ['triggersActions'], }, diff --git a/x-pack/plugins/alerts/server/authorization/alerts_authorization.test.ts b/x-pack/plugins/alerts/server/authorization/alerts_authorization.test.ts index c2506381b9df9..f9fde9755e0ad 100644 --- a/x-pack/plugins/alerts/server/authorization/alerts_authorization.test.ts +++ b/x-pack/plugins/alerts/server/authorization/alerts_authorization.test.ts @@ -48,6 +48,7 @@ function mockFeature(appName: string, typeName?: string) { id: appName, name: appName, app: [], + category: { id: 'foo', label: 'foo' }, ...(typeName ? { alerting: [typeName], @@ -91,6 +92,7 @@ function mockFeatureWithSubFeature(appName: string, typeName: string) { id: appName, name: appName, app: [], + category: { id: 'foo', label: 'foo' }, ...(typeName ? { alerting: [typeName], diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index 026aa0c5238dc..b13a1c62f6602 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -164,6 +164,7 @@ function mockFeatures() { id: 'appName', name: 'appName', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index 0f6061653f352..d5ec1503a8ed1 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { AlertType } from '../common/alert_types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; export const APM_FEATURE = { id: 'apm', @@ -13,6 +14,7 @@ export const APM_FEATURE = { defaultMessage: 'APM', }), order: 900, + category: DEFAULT_APP_CATEGORIES.observability, icon: 'apmApp', navLinkId: 'apm', app: ['apm', 'csm', 'kibana'], diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts index 9a41a00883c13..fe8e097d1b250 100644 --- a/x-pack/plugins/canvas/server/plugin.ts +++ b/x-pack/plugins/canvas/server/plugin.ts @@ -10,6 +10,7 @@ import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { HomeServerPluginSetup } from 'src/plugins/home/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { initRoutes } from './routes'; import { registerCanvasUsageCollector } from './collectors'; @@ -41,6 +42,7 @@ export class CanvasPlugin implements Plugin { id: 'canvas', name: 'Canvas', order: 400, + category: DEFAULT_APP_CATEGORIES.kibana, icon: 'canvasApp', navLinkId: 'canvas', app: ['canvas', 'kibana'], diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 3d28a05a4b7b4..a9bd03e8f97d4 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -16,6 +16,7 @@ import { KibanaRequest, } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { SecurityPluginSetup } from '../../security/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; @@ -82,6 +83,7 @@ export class EnterpriseSearchPlugin implements Plugin { id: ENTERPRISE_SEARCH_PLUGIN.ID, name: ENTERPRISE_SEARCH_PLUGIN.NAME, order: 0, + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, icon: 'logoEnterpriseSearch', app: [ 'kibana', diff --git a/x-pack/plugins/features/common/kibana_feature.ts b/x-pack/plugins/features/common/kibana_feature.ts index a600ada554afd..32a7502956728 100644 --- a/x-pack/plugins/features/common/kibana_feature.ts +++ b/x-pack/plugins/features/common/kibana_feature.ts @@ -5,6 +5,7 @@ */ import { RecursiveReadonly } from '@kbn/utility-types'; +import { AppCategory } from 'src/core/types'; import { FeatureKibanaPrivileges } from './feature_kibana_privileges'; import { SubFeatureConfig, SubFeature as KibanaSubFeature } from './sub_feature'; import { ReservedKibanaPrivilege } from './reserved_kibana_privilege'; @@ -29,6 +30,13 @@ export interface KibanaFeatureConfig { */ name: string; + /** + * The category for this feature. + * This will be used to organize the list of features for display within the + * Spaces and Roles management screens. + */ + category: AppCategory; + /** * An ordinal used to sort features relative to one another for display. */ @@ -158,6 +166,10 @@ export class KibanaFeature { return this.config.order; } + public get category() { + return this.config.category; + } + public get navLinkId() { return this.config.navLinkId; } diff --git a/x-pack/plugins/features/server/feature_registry.test.ts b/x-pack/plugins/features/server/feature_registry.test.ts index 24aae3a69ee5d..34f2984e8cbc2 100644 --- a/x-pack/plugins/features/server/feature_registry.test.ts +++ b/x-pack/plugins/features/server/feature_registry.test.ts @@ -14,6 +14,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }; @@ -35,6 +36,7 @@ describe('FeatureRegistry', () => { icon: 'addDataApp', navLinkId: 'someNavLink', app: ['app1'], + category: { id: 'foo', label: 'foo' }, validLicenses: ['standard', 'basic', 'gold', 'platinum'], catalogue: ['foo'], management: { @@ -143,11 +145,64 @@ describe('FeatureRegistry', () => { expect(result[0].toRaw()).toEqual(feature); }); + describe('category', () => { + it('is required', () => { + const feature: KibanaFeatureConfig = { + id: 'test-feature', + name: 'Test Feature', + app: [], + privileges: null, + } as any; + + const featureRegistry = new FeatureRegistry(); + expect(() => + featureRegistry.registerKibanaFeature(feature) + ).toThrowErrorMatchingInlineSnapshot( + `"child \\"category\\" fails because [\\"category\\" is required]"` + ); + }); + + it('must have an id', () => { + const feature: KibanaFeatureConfig = { + id: 'test-feature', + name: 'Test Feature', + app: [], + privileges: null, + category: { label: 'foo' }, + } as any; + + const featureRegistry = new FeatureRegistry(); + expect(() => + featureRegistry.registerKibanaFeature(feature) + ).toThrowErrorMatchingInlineSnapshot( + `"child \\"category\\" fails because [child \\"id\\" fails because [\\"id\\" is required]]"` + ); + }); + + it('must have a label', () => { + const feature: KibanaFeatureConfig = { + id: 'test-feature', + name: 'Test Feature', + app: [], + privileges: null, + category: { id: 'foo' }, + } as any; + + const featureRegistry = new FeatureRegistry(); + expect(() => + featureRegistry.registerKibanaFeature(feature) + ).toThrowErrorMatchingInlineSnapshot( + `"child \\"category\\" fails because [child \\"label\\" fails because [\\"label\\" is required]]"` + ); + }); + }); + it(`requires a value for privileges`, () => { const feature: KibanaFeatureConfig = { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, } as any; const featureRegistry = new FeatureRegistry(); @@ -163,6 +218,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, subFeatures: [ { @@ -201,6 +257,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { ui: [], @@ -235,6 +292,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { ui: [], @@ -271,6 +329,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', @@ -303,6 +362,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { ui: [], @@ -340,6 +400,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }; @@ -347,6 +408,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Duplicate Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }; @@ -367,6 +429,7 @@ describe('FeatureRegistry', () => { name: 'some feature', navLinkId: prohibitedChars, app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }) ).toThrowErrorMatchingSnapshot(); @@ -382,6 +445,7 @@ describe('FeatureRegistry', () => { kibana: [prohibitedChars], }, app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }) ).toThrowErrorMatchingSnapshot(); @@ -395,6 +459,7 @@ describe('FeatureRegistry', () => { name: 'some feature', catalogue: [prohibitedChars], app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }) ).toThrowErrorMatchingSnapshot(); @@ -409,6 +474,7 @@ describe('FeatureRegistry', () => { id: prohibitedId, name: 'some feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }) ).toThrowErrorMatchingSnapshot(); @@ -420,6 +486,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: ['app1', 'app2'], + category: { id: 'foo', label: 'foo' }, privileges: { foo: { name: 'Foo', @@ -447,6 +514,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: ['bar'], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -481,6 +549,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: ['foo', 'bar', 'baz'], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -538,6 +607,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: ['bar'], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'something', @@ -571,6 +641,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: ['foo', 'bar', 'baz'], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'something', @@ -604,6 +675,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['bar'], privileges: { all: { @@ -641,6 +713,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['foo', 'bar', 'baz'], privileges: { all: { @@ -701,6 +774,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['bar'], privileges: null, reserved: { @@ -736,6 +810,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['foo', 'bar', 'baz'], privileges: null, reserved: { @@ -771,6 +846,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, alerting: ['bar'], privileges: { all: { @@ -811,6 +887,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, alerting: ['foo', 'bar', 'baz'], privileges: { all: { @@ -871,6 +948,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, alerting: ['bar'], privileges: null, reserved: { @@ -906,6 +984,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, alerting: ['foo', 'bar', 'baz'], privileges: null, reserved: { @@ -941,6 +1020,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['bar'], management: { kibana: ['hey'], @@ -987,6 +1067,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['bar'], management: { kibana: ['hey'], @@ -1060,6 +1141,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['bar'], management: { kibana: ['hey'], @@ -1101,6 +1183,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['bar'], management: { kibana: ['hey', 'hey-there'], @@ -1142,6 +1225,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'my reserved privileges', @@ -1184,6 +1268,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'my reserved privileges', @@ -1216,12 +1301,14 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }; const feature2: KibanaFeatureConfig = { id: 'test-feature-2', name: 'Test Feature 2', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }; @@ -1346,6 +1433,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }; @@ -1371,6 +1459,7 @@ describe('FeatureRegistry', () => { id: 'test-feature', name: 'Test Feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }; diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index 06a3eb158d99d..c6ec2d52c6d1a 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -28,6 +28,14 @@ const managementSchema = Joi.object().pattern( const catalogueSchema = Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)); const alertingSchema = Joi.array().items(Joi.string()); +const appCategorySchema = Joi.object({ + id: Joi.string().required(), + label: Joi.string().required(), + ariaLabel: Joi.string(), + euiIconType: Joi.string(), + order: Joi.number(), +}).required(); + const kibanaPrivilegeSchema = Joi.object({ excludeFromBasePrivileges: Joi.boolean(), management: managementSchema, @@ -80,6 +88,7 @@ const kibanaFeatureSchema = Joi.object({ .invalid(...prohibitedFeatureIds) .required(), name: Joi.string().required(), + category: appCategorySchema, order: Joi.number(), excludeFromBasePrivileges: Joi.boolean(), validLicenses: Joi.array().items( diff --git a/x-pack/plugins/features/server/oss_features.ts b/x-pack/plugins/features/server/oss_features.ts index 3ff6b1b7bf44f..4788b720cf83d 100644 --- a/x-pack/plugins/features/server/oss_features.ts +++ b/x-pack/plugins/features/server/oss_features.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; import { KibanaFeatureConfig } from '../common'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; export interface BuildOSSFeaturesParams { savedObjectTypes: string[]; @@ -19,6 +20,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS defaultMessage: 'Discover', }), order: 100, + category: DEFAULT_APP_CATEGORIES.kibana, icon: 'discoverApp', navLinkId: 'discover', app: ['discover', 'kibana'], @@ -79,6 +81,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS defaultMessage: 'Visualize', }), order: 200, + category: DEFAULT_APP_CATEGORIES.kibana, icon: 'visualizeApp', navLinkId: 'visualize', app: ['visualize', 'lens', 'kibana'], @@ -139,6 +142,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS defaultMessage: 'Dashboard', }), order: 300, + category: DEFAULT_APP_CATEGORIES.kibana, icon: 'dashboardApp', navLinkId: 'dashboards', app: ['dashboards', 'kibana'], @@ -217,6 +221,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS defaultMessage: 'Dev Tools', }), order: 1300, + category: DEFAULT_APP_CATEGORIES.management, icon: 'devToolsApp', navLinkId: 'dev_tools', app: ['dev_tools', 'kibana'], @@ -254,6 +259,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS defaultMessage: 'Advanced Settings', }), order: 1500, + category: DEFAULT_APP_CATEGORIES.management, icon: 'advancedSettingsApp', app: ['kibana'], catalogue: ['advanced_settings'], @@ -293,6 +299,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS defaultMessage: 'Index Pattern Management', }), order: 1600, + category: DEFAULT_APP_CATEGORIES.management, icon: 'indexPatternApp', app: ['kibana'], catalogue: ['indexPatterns'], @@ -332,6 +339,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS defaultMessage: 'Saved Objects Management', }), order: 1700, + category: DEFAULT_APP_CATEGORIES.management, icon: 'savedObjectsApp', app: ['kibana'], catalogue: ['saved_objects'], @@ -375,6 +383,7 @@ const timelionFeature: KibanaFeatureConfig = { id: 'timelion', name: 'Timelion', order: 350, + category: DEFAULT_APP_CATEGORIES.kibana, icon: 'timelionApp', navLinkId: 'timelion', app: ['timelion', 'kibana'], diff --git a/x-pack/plugins/features/server/plugin.test.ts b/x-pack/plugins/features/server/plugin.test.ts index ee11e0e2bbe2e..ce6fb548ae6d2 100644 --- a/x-pack/plugins/features/server/plugin.test.ts +++ b/x-pack/plugins/features/server/plugin.test.ts @@ -35,6 +35,7 @@ describe('Features Plugin', () => { id: 'baz', name: 'baz', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }); @@ -63,6 +64,7 @@ describe('Features Plugin', () => { id: 'baz', name: 'baz', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }); diff --git a/x-pack/plugins/features/server/routes/index.test.ts b/x-pack/plugins/features/server/routes/index.test.ts index 30aa6d07f6b5a..692a889203131 100644 --- a/x-pack/plugins/features/server/routes/index.test.ts +++ b/x-pack/plugins/features/server/routes/index.test.ts @@ -28,6 +28,7 @@ describe('GET /api/features', () => { id: 'feature_1', name: 'Feature 1', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }); @@ -36,6 +37,7 @@ describe('GET /api/features', () => { name: 'Feature 2', order: 2, app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }); @@ -44,6 +46,7 @@ describe('GET /api/features', () => { name: 'Feature 2', order: 1, app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }); @@ -51,6 +54,7 @@ describe('GET /api/features', () => { id: 'licensed_feature', name: 'Licensed Feature', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, validLicenses: ['gold'], privileges: null, }); diff --git a/x-pack/plugins/features/server/ui_capabilities_for_features.test.ts b/x-pack/plugins/features/server/ui_capabilities_for_features.test.ts index 7532bc0573b08..f5ba17a632c92 100644 --- a/x-pack/plugins/features/server/ui_capabilities_for_features.test.ts +++ b/x-pack/plugins/features/server/ui_capabilities_for_features.test.ts @@ -46,6 +46,7 @@ describe('populateUICapabilities', () => { id: 'newFeature', name: 'my new feature', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: { all: createKibanaFeaturePrivilege(), read: createKibanaFeaturePrivilege(), @@ -93,6 +94,7 @@ describe('populateUICapabilities', () => { name: 'my new feature', navLinkId: 'newFeatureNavLink', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: { all: createKibanaFeaturePrivilege(['capability1', 'capability2']), read: createKibanaFeaturePrivilege(), @@ -146,6 +148,7 @@ describe('populateUICapabilities', () => { name: 'my new feature', navLinkId: 'newFeatureNavLink', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, catalogue: ['anotherFooEntry', 'anotherBarEntry'], privileges: { all: createKibanaFeaturePrivilege(['capability1', 'capability2']), @@ -215,6 +218,7 @@ describe('populateUICapabilities', () => { name: 'my new feature', navLinkId: 'newFeatureNavLink', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: { all: createKibanaFeaturePrivilege(['capability1', 'capability2']), read: createKibanaFeaturePrivilege(['capability3', 'capability4', 'capability5']), @@ -245,6 +249,7 @@ describe('populateUICapabilities', () => { name: 'my new feature', navLinkId: 'newFeatureNavLink', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: '', @@ -289,6 +294,7 @@ describe('populateUICapabilities', () => { name: 'my new feature', navLinkId: 'newFeatureNavLink', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: { all: createKibanaFeaturePrivilege(['capability1', 'capability2']), read: createKibanaFeaturePrivilege(['capability3', 'capability4']), @@ -360,6 +366,7 @@ describe('populateUICapabilities', () => { name: 'my new feature', navLinkId: 'newFeatureNavLink', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: { all: createKibanaFeaturePrivilege(['capability1', 'capability2']), read: createKibanaFeaturePrivilege(['capability3', 'capability4']), @@ -369,6 +376,7 @@ describe('populateUICapabilities', () => { id: 'anotherNewFeature', name: 'another new feature', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: { all: createKibanaFeaturePrivilege(['capability1', 'capability2']), read: createKibanaFeaturePrivilege(['capability3', 'capability4']), @@ -379,6 +387,7 @@ describe('populateUICapabilities', () => { name: 'yet another new feature', navLinkId: 'yetAnotherNavLink', app: ['bar-app'], + category: { id: 'foo', label: 'foo' }, privileges: { all: createKibanaFeaturePrivilege(['capability1', 'capability2']), read: createKibanaFeaturePrivilege(['something1', 'something2', 'something3']), diff --git a/x-pack/plugins/graph/server/plugin.ts b/x-pack/plugins/graph/server/plugin.ts index d69c592655fb5..cf31f8c6efa6f 100644 --- a/x-pack/plugins/graph/server/plugin.ts +++ b/x-pack/plugins/graph/server/plugin.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { Plugin, CoreSetup, CoreStart } from 'src/core/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server'; import { LicenseState } from './lib/license_state'; import { registerSearchRoute } from './routes/search'; @@ -47,6 +48,7 @@ export class GraphPlugin implements Plugin { defaultMessage: 'Graph', }), order: 1200, + category: DEFAULT_APP_CATEGORIES.kibana, icon: 'graphApp', navLinkId: 'graph', app: ['graph', 'kibana'], diff --git a/x-pack/plugins/infra/server/features.ts b/x-pack/plugins/infra/server/features.ts index b75e831ac875c..97968a87e9b1a 100644 --- a/x-pack/plugins/infra/server/features.ts +++ b/x-pack/plugins/infra/server/features.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../common/alerting/logs/types'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from './lib/alerting/inventory_metric_threshold/types'; import { METRIC_THRESHOLD_ALERT_TYPE_ID } from './lib/alerting/metric_threshold/types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; export const METRICS_FEATURE = { id: 'infrastructure', @@ -15,6 +16,7 @@ export const METRICS_FEATURE = { defaultMessage: 'Metrics', }), order: 700, + category: DEFAULT_APP_CATEGORIES.observability, icon: 'metricsApp', navLinkId: 'metrics', app: ['infra', 'metrics', 'kibana'], @@ -65,6 +67,7 @@ export const LOGS_FEATURE = { defaultMessage: 'Logs', }), order: 800, + category: DEFAULT_APP_CATEGORIES.observability, icon: 'logsApp', navLinkId: 'logs', app: ['infra', 'logs', 'kibana'], diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index b10f3527a0459..b5198cc967041 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -15,6 +15,7 @@ import { HttpServiceSetup, } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { LicensingPluginSetup, ILicense } from '../../licensing/server'; import { EncryptedSavedObjectsPluginStart, @@ -177,6 +178,7 @@ export class IngestManagerPlugin id: PLUGIN_ID, name: 'Ingest Manager', icon: 'savedObjectsApp', + category: DEFAULT_APP_CATEGORIES.security, navLinkId: PLUGIN_ID, app: [PLUGIN_ID, 'kibana'], catalogue: ['ingestManager'], diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 5eb0482905e36..26a4906fc8b49 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; import { take } from 'rxjs/operators'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server'; // @ts-ignore import { getEcommerceSavedObjects } from './sample_data/ecommerce_saved_objects'; @@ -169,6 +170,7 @@ export class MapsPlugin implements Plugin { defaultMessage: 'Maps', }), order: 600, + category: DEFAULT_APP_CATEGORIES.kibana, icon: APP_ICON, navLinkId: APP_ID, app: [APP_ID, 'kibana'], diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index cf248fcc60896..7224eacf84e90 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -15,6 +15,7 @@ import { CapabilitiesStart, IClusterClient, } from 'kibana/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { PluginsSetup, RouteInitialization } from './types'; import { PLUGIN_ID, PLUGIN_ICON } from '../common/constants/app'; import { MlCapabilities } from '../common/types/capabilities'; @@ -74,6 +75,7 @@ export class MlServerPlugin implements Plugin { name: 'Feature 1', icon: 'addDataApp', app: ['feature1App'], + category: { id: 'foo', label: 'foo' }, privileges: { all: { app: ['feature1App'], @@ -56,6 +57,7 @@ const buildFeatures = () => { name: 'Feature 2', icon: 'addDataApp', app: ['feature2App'], + category: { id: 'foo', label: 'foo' }, privileges: { all: { app: ['feature2App'], diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index 7ecf32ee45b85..77b6da2a00487 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -18,6 +18,7 @@ const buildProps = (customProps: any = {}) => { id: 'feature1', name: 'Feature 1', app: ['app'], + category: { id: 'foo', label: 'foo' }, icon: 'spacesApp', privileges: { all: { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx index bc60613345910..0242fddc957c9 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx @@ -28,6 +28,7 @@ const features = [ id: 'normal', name: 'normal feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { all: [], read: [] }, @@ -43,6 +44,7 @@ const features = [ id: 'normal_with_sub', name: 'normal feature with sub features', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { all: [], read: [] }, @@ -96,6 +98,7 @@ const features = [ id: 'bothPrivilegesExcludedFromBase', name: 'bothPrivilegesExcludedFromBase', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { excludeFromBasePrivileges: true, @@ -113,6 +116,7 @@ const features = [ id: 'allPrivilegeExcludedFromBase', name: 'allPrivilegeExcludedFromBase', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { excludeFromBasePrivileges: true, diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts index 98faae6edab2c..ea24560c8ddc9 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts @@ -80,6 +80,7 @@ describe('usingPrivileges', () => { id: 'fooFeature', name: 'Foo KibanaFeature', app: ['fooApp', 'foo'], + category: { id: 'foo', label: 'foo' }, navLinkId: 'foo', privileges: null, }), @@ -168,6 +169,7 @@ describe('usingPrivileges', () => { id: 'fooFeature', name: 'Foo KibanaFeature', app: ['foo'], + category: { id: 'foo', label: 'foo' }, navLinkId: 'foo', privileges: null, }), @@ -322,6 +324,7 @@ describe('usingPrivileges', () => { name: 'Foo KibanaFeature', navLinkId: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }), new KibanaFeature({ @@ -329,6 +332,7 @@ describe('usingPrivileges', () => { name: 'Bar KibanaFeature', navLinkId: 'bar', app: ['bar'], + category: { id: 'foo', label: 'foo' }, privileges: null, }), ], @@ -469,6 +473,7 @@ describe('usingPrivileges', () => { name: 'Foo KibanaFeature', navLinkId: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }), new KibanaFeature({ @@ -476,6 +481,7 @@ describe('usingPrivileges', () => { name: 'Bar KibanaFeature', navLinkId: 'bar', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }), ], @@ -552,6 +558,7 @@ describe('all', () => { id: 'fooFeature', name: 'Foo KibanaFeature', app: ['foo'], + category: { id: 'foo', label: 'foo' }, navLinkId: 'foo', privileges: null, }), diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts index dc261e2eec982..5f19c911fd5d3 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts @@ -33,6 +33,7 @@ describe(`feature_privilege_builder`, () => { id: 'my-feature', name: 'my-feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: privilege, read: privilege, @@ -64,6 +65,7 @@ describe(`feature_privilege_builder`, () => { id: 'my-feature', name: 'my-feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: privilege, read: privilege, @@ -101,6 +103,7 @@ describe(`feature_privilege_builder`, () => { id: 'my-feature', name: 'my-feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: privilege, read: privilege, @@ -148,6 +151,7 @@ describe(`feature_privilege_builder`, () => { id: 'my-feature', name: 'my-feature', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: privilege, read: privilege, diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts index 033040fd2f14b..bdf2c87f40f0b 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_iterator/feature_privilege_iterator.test.ts @@ -14,6 +14,7 @@ describe('featurePrivilegeIterator', () => { name: 'foo', privileges: null, app: [], + category: { id: 'foo', label: 'foo' }, }); const actualPrivileges = Array.from( @@ -29,6 +30,7 @@ describe('featurePrivilegeIterator', () => { const feature = new KibanaFeature({ id: 'foo', name: 'foo', + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], @@ -120,6 +122,7 @@ describe('featurePrivilegeIterator', () => { const feature = new KibanaFeature({ id: 'foo', name: 'foo', + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], @@ -194,6 +197,7 @@ describe('featurePrivilegeIterator', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], @@ -317,6 +321,7 @@ describe('featurePrivilegeIterator', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], @@ -440,6 +445,7 @@ describe('featurePrivilegeIterator', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], @@ -567,6 +573,7 @@ describe('featurePrivilegeIterator', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], @@ -690,6 +697,7 @@ describe('featurePrivilegeIterator', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], @@ -815,6 +823,7 @@ describe('featurePrivilegeIterator', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -923,6 +932,7 @@ describe('featurePrivilegeIterator', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { api: ['all-api', 'read-api'], diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts index dd8ac44386dbd..6f721c91fbd67 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts @@ -21,6 +21,7 @@ describe('features', () => { icon: 'arrowDown', navLinkId: 'kibana:foo', app: ['app-1', 'app-2'], + category: { id: 'foo', label: 'foo' }, catalogue: ['catalogue-1', 'catalogue-2'], management: { foo: ['management-1', 'management-2'], @@ -66,6 +67,7 @@ describe('features', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -165,6 +167,7 @@ describe('features', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }), ]; @@ -207,6 +210,7 @@ describe('features', () => { icon: 'arrowDown', navLinkId: 'kibana:foo', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['ignore-me-1', 'ignore-me-2'], management: { foo: ['ignore-me-1', 'ignore-me-2'], @@ -327,6 +331,7 @@ describe('features', () => { icon: 'arrowDown', navLinkId: 'kibana:foo', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['ignore-me-1', 'ignore-me-2'], management: { foo: ['ignore-me-1', 'ignore-me-2'], @@ -409,6 +414,7 @@ describe('features', () => { icon: 'arrowDown', navLinkId: 'kibana:foo', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['ignore-me-1', 'ignore-me-2'], management: { foo: ['ignore-me-1', 'ignore-me-2'], @@ -467,6 +473,7 @@ describe('features', () => { icon: 'arrowDown', navLinkId: 'kibana:foo', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['ignore-me-1', 'ignore-me-2'], management: { foo: ['ignore-me-1', 'ignore-me-2'], @@ -532,6 +539,7 @@ describe('features', () => { icon: 'arrowDown', navLinkId: 'kibana:foo', app: [], + category: { id: 'foo', label: 'foo' }, catalogue: ['ignore-me-1', 'ignore-me-2'], management: { foo: ['ignore-me-1', 'ignore-me-2'], @@ -602,6 +610,7 @@ describe('reserved', () => { icon: 'arrowDown', navLinkId: 'kibana:foo', app: ['app-1', 'app-2'], + category: { id: 'foo', label: 'foo' }, catalogue: ['catalogue-1', 'catalogue-2'], management: { foo: ['management-1', 'management-2'], @@ -644,6 +653,7 @@ describe('reserved', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { privileges: [ @@ -708,6 +718,7 @@ describe('reserved', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -749,6 +760,7 @@ describe('subFeatures', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -876,6 +888,7 @@ describe('subFeatures', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -1075,6 +1088,7 @@ describe('subFeatures', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, excludeFromBasePrivileges: true, privileges: { all: { @@ -1216,6 +1230,7 @@ describe('subFeatures', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -1379,6 +1394,7 @@ describe('subFeatures', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, excludeFromBasePrivileges: true, privileges: { all: { @@ -1508,6 +1524,7 @@ describe('subFeatures', () => { name: 'Foo KibanaFeature', icon: 'arrowDown', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { diff --git a/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts b/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts index 8e6d72670c8d9..d449eb29d53d8 100644 --- a/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts @@ -12,6 +12,7 @@ it('allows features to be defined without privileges', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }); @@ -23,6 +24,7 @@ it('allows features with reserved privileges to be defined', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', @@ -49,6 +51,7 @@ it('allows features with sub-features to be defined', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -112,6 +115,7 @@ it('does not allow features with sub-features which have id conflicts with the m id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -162,6 +166,7 @@ it('does not allow features with sub-features which have id conflicts with the p id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { @@ -212,6 +217,7 @@ it('does not allow features with sub-features which have id conflicts each other id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { savedObject: { diff --git a/x-pack/plugins/security/server/authorization/validate_reserved_privileges.test.ts b/x-pack/plugins/security/server/authorization/validate_reserved_privileges.test.ts index d91a4d4151316..0c7d12f67f4b9 100644 --- a/x-pack/plugins/security/server/authorization/validate_reserved_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/validate_reserved_privileges.test.ts @@ -13,6 +13,7 @@ it('allows features to be defined without privileges', () => { name: 'foo', app: [], privileges: null, + category: { id: 'foo', label: 'foo' }, }); validateReservedPrivileges([feature]); @@ -23,6 +24,7 @@ it('allows features with a single reserved privilege to be defined', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', @@ -49,6 +51,7 @@ it('allows multiple features with reserved privileges to be defined', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', @@ -71,6 +74,7 @@ it('allows multiple features with reserved privileges to be defined', () => { id: 'foo2', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', @@ -97,6 +101,7 @@ it('prevents a feature from specifying the same reserved privilege id', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', @@ -135,6 +140,7 @@ it('prevents features from sharing a reserved privilege id', () => { id: 'foo', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', @@ -157,6 +163,7 @@ it('prevents features from sharing a reserved privilege id', () => { id: 'foo2', name: 'foo', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, reserved: { description: 'foo', diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 6e9b88f30479f..811ea080b4316 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -87,6 +87,7 @@ const putRoleTest = ( id: 'feature_1', name: 'feature 1', app: [], + category: { id: 'foo', label: 'foo' }, privileges: { all: { ui: [], diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index d203c6dcc48c4..a81cde27183ca 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -16,6 +16,7 @@ import { Plugin as IPlugin, PluginInitializerContext, SavedObjectsClient, + DEFAULT_APP_CATEGORIES, } from '../../../../src/core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { DataPluginSetup, DataPluginStart } from '../../../../src/plugins/data/server/plugin'; @@ -178,6 +179,7 @@ export class Plugin implements IPlugin { return ( - +

{ description={this.getPanelDescription()} fullWidth > - - - - - - - - - - - - } - closePopover={this.closePopover} - {...extraPopoverProps} - ownFocus={true} - isOpen={this.state.customizingAvatar} - > -

- -
- - - - + + + @@ -175,6 +134,37 @@ export class CustomizeSpace extends Component { rows={2} /> + + + + + + } + closePopover={this.closePopover} + {...extraPopoverProps} + ownFocus={true} + isOpen={this.state.customizingAvatar} + > +
+ +
+
+
); diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap index 3835fa085c26e..ee1eb7c5e9aba 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap @@ -2,14 +2,14 @@ exports[`EnabledFeatures renders as expected 1`] = ` @@ -41,7 +41,7 @@ exports[`EnabledFeatures renders as expected 1`] = ` >

@@ -63,16 +63,16 @@ exports[`EnabledFeatures renders as expected 1`] = `

@@ -89,6 +89,12 @@ exports[`EnabledFeatures renders as expected 1`] = ` Array [ Object { "app": Array [], + "category": Object { + "euiIconType": "logoKibana", + "id": "kibana", + "label": "Kibana", + "order": 1000, + }, "icon": "spacesApp", "id": "feature-1", "name": "Feature 1", @@ -96,6 +102,12 @@ exports[`EnabledFeatures renders as expected 1`] = ` }, Object { "app": Array [], + "category": Object { + "euiIconType": "logoKibana", + "id": "kibana", + "label": "Kibana", + "order": 1000, + }, "icon": "spacesApp", "id": "feature-2", "name": "Feature 2", diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx index 0eed6793ddbe0..046a92b09f737 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Space } from '../../../../common/model/space'; -import { SectionPanel } from '../section_panel'; +import { mountWithIntl, nextTick, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { EnabledFeatures } from './enabled_features'; import { KibanaFeatureConfig } from '../../../../../features/public'; +import { DEFAULT_APP_CATEGORIES } from '../../../../../../../src/core/public'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { EuiCheckboxProps } from '@elastic/eui'; const features: KibanaFeatureConfig[] = [ { @@ -18,6 +18,7 @@ const features: KibanaFeatureConfig[] = [ name: 'Feature 1', icon: 'spacesApp', app: [], + category: DEFAULT_APP_CATEGORIES.kibana, privileges: null, }, { @@ -25,16 +26,11 @@ const features: KibanaFeatureConfig[] = [ name: 'Feature 2', icon: 'spacesApp', app: [], + category: DEFAULT_APP_CATEGORIES.kibana, privileges: null, }, ]; -const space: Space = { - id: 'my-space', - name: 'my space', - disabledFeatures: ['feature-1', 'feature-2'], -}; - describe('EnabledFeatures', () => { const getUrlForApp = (appId: string) => appId; @@ -43,7 +39,11 @@ describe('EnabledFeatures', () => { shallowWithIntl( { ).toMatchSnapshot(); }); - it('allows all features to be toggled on', () => { + it('allows all features in a category to be toggled on', () => { const changeHandler = jest.fn(); const wrapper = mountWithIntl( ); - // expand section panel - wrapper.find(SectionPanel).find(EuiLink).simulate('click'); - - // Click the "Change all" link - wrapper.find('.spcToggleAllFeatures__changeAllLink').first().simulate('click'); + // Click category-level toggle + const { + onChange = () => { + throw new Error('expected onChange to be defined'); + }, + } = wrapper.find('input#featureCategoryCheckbox_kibana').props() as EuiCheckboxProps; + onChange({ target: { checked: true } } as any); // Ask to show all features - wrapper.find('button[data-test-subj="spc-toggle-all-features-show"]').simulate('click'); + findTestSubject(wrapper, `featureCategoryButton_kibana`).simulate('click'); expect(changeHandler).toBeCalledTimes(1); @@ -81,27 +87,67 @@ describe('EnabledFeatures', () => { expect(updatedSpace.disabledFeatures).toEqual([]); }); - it('allows all features to be toggled off', () => { + it('allows all features in a category to be toggled off', async () => { const changeHandler = jest.fn(); const wrapper = mountWithIntl( ); - // expand section panel - wrapper.find(SectionPanel).find(EuiLink).simulate('click'); + // Click category-level toggle + const { + onChange = () => { + throw new Error('expected onChange to be defined'); + }, + } = wrapper.find('input#featureCategoryCheckbox_kibana').props() as EuiCheckboxProps; + onChange({ target: { checked: false } } as any); + + // Ask to show all features + findTestSubject(wrapper, `featureCategoryButton_kibana`).simulate('click'); + + await nextTick(); + wrapper.update(); + + expect(changeHandler).toBeCalledTimes(1); + + const updatedSpace = changeHandler.mock.calls[0][0]; + + expect(updatedSpace.disabledFeatures).toEqual(['feature-1', 'feature-2']); + }); + + it('allows all features to be toggled off', async () => { + const changeHandler = jest.fn(); - // Click the "Change all" link - wrapper.find('.spcToggleAllFeatures__changeAllLink').first().simulate('click'); + const wrapper = mountWithIntl( + + ); + + // show should not be visible when all features are already visible + expect(findTestSubject(wrapper, 'showAllFeaturesLink')).toHaveLength(0); + findTestSubject(wrapper, 'hideAllFeaturesLink').simulate('click'); - // Ask to hide all features - wrapper.find('button[data-test-subj="spc-toggle-all-features-hide"]').simulate('click'); + await nextTick(); + wrapper.update(); expect(changeHandler).toBeCalledTimes(1); @@ -109,4 +155,53 @@ describe('EnabledFeatures', () => { expect(updatedSpace.disabledFeatures).toEqual(['feature-1', 'feature-2']); }); + + it('allows all features to be toggled on', async () => { + const changeHandler = jest.fn(); + + const wrapper = mountWithIntl( + + ); + + // hide should not be visible when all features are already hidden + expect(findTestSubject(wrapper, 'hideAllFeaturesLink')).toHaveLength(0); + findTestSubject(wrapper, 'showAllFeaturesLink').simulate('click'); + + await nextTick(); + wrapper.update(); + + expect(changeHandler).toBeCalledTimes(1); + + const updatedSpace = changeHandler.mock.calls[0][0]; + + expect(updatedSpace.disabledFeatures).toEqual([]); + }); + + it('displays both show and hide options when a non-zero subset of features are toggled on', async () => { + const wrapper = mountWithIntl( + + ); + expect(findTestSubject(wrapper, 'hideAllFeaturesLink')).toHaveLength(1); + expect(findTestSubject(wrapper, 'showAllFeaturesLink')).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx index 689bb610d5f38..5e7629c29bbdd 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx @@ -34,8 +34,8 @@ export class EnabledFeatures extends Component { return ( {

@@ -114,7 +114,7 @@ export class EnabledFeatures extends Component { {' '} {details} @@ -135,16 +135,16 @@ export class EnabledFeatures extends Component {

), diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss new file mode 100644 index 0000000000000..4f73349edac20 --- /dev/null +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss @@ -0,0 +1,4 @@ +.spcFeatureTableAccordionContent { + // Align accordion content with the feature category logo in the accordion's buttonContent + padding-left: $euiSizeXL; +} \ No newline at end of file diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx index 9265ca46e3a3a..aa1baddcd4a0b 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx @@ -4,14 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon, EuiInMemoryTable, EuiSwitch, EuiText, IconType } from '@elastic/eui'; +import { + EuiAccordion, + EuiCheckbox, + EuiCheckboxProps, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; -import React, { ChangeEvent, Component } from 'react'; +import React, { ChangeEvent, Component, ReactElement } from 'react'; import { KibanaFeatureConfig } from '../../../../../../plugins/features/public'; import { Space } from '../../../../common/model/space'; -import { ToggleAllFeatures } from './toggle_all_features'; +import { getEnabledFeatures } from '../../lib/feature_utils'; +import './feature_table.scss'; interface Props { space: Partial; @@ -20,15 +32,175 @@ interface Props { } export class FeatureTable extends Component { + private featureCategories: Map = new Map(); + + constructor(props: Props) { + super(props); + // features are static for the lifetime of the page, so this is safe to do here in a non-reactive manner + props.features.forEach((feature) => { + if (!this.featureCategories.has(feature.category.id)) { + this.featureCategories.set(feature.category.id, []); + } + this.featureCategories.get(feature.category.id)!.push(feature); + }); + } + public render() { - const { space, features } = this.props; + const { space } = this.props; + + const accordians: Array<{ order: number; element: ReactElement }> = []; + this.featureCategories.forEach((featuresInCategory) => { + const { category } = featuresInCategory[0]; + + const featureCount = featuresInCategory.length; + const enabledCount = getEnabledFeatures(featuresInCategory, space).length; + + const checkboxProps: EuiCheckboxProps = { + id: `featureCategoryCheckbox_${category.id}`, + indeterminate: enabledCount > 0 && enabledCount < featureCount, + checked: featureCount === enabledCount, + ['aria-label']: i18n.translate( + 'xpack.spaces.management.enabledFeatures.featureCategoryButtonLabel', + { defaultMessage: 'Category toggle' } + ), + onClick: (e) => { + // Clicking the checkbox should not cause the accordion to expand. + // Stopping event propagation ensures this. + e.stopPropagation(); + }, + onChange: (e) => { + this.setFeaturesVisibility( + featuresInCategory.map((f) => f.id), + e.target.checked + ); + }, + }; - const items = features.map((feature) => ({ - feature, - space, - })); + const buttonContent = ( + + + + + {category.euiIconType ? ( + + + + ) : null} + + +

{category.label}

+ + + + ); - return ; + // TODO: Enable once https://github.com/elastic/eui/issues/3881 is resolved (will land in version 28.4.0) + // const label: string = i18n.translate('xpack.spaces.management.featureAccordionSwitchLabel', { + // defaultMessage: '{enabledCount} / {featureCount} features visible', + // values: { + // enabledCount, + // featureCount, + // }, + // }); + // const extraAction = ( + // + // ); + + const accordian = ( + +
+ + {featuresInCategory.map((feature) => { + const featureChecked = !( + space.disabledFeatures && space.disabledFeatures.includes(feature.id) + ); + + return ( + + + + + + ); + })} +
+
+ ); + + accordians.push({ + order: category.order ?? Number.MAX_SAFE_INTEGER, + element: accordian, + }); + }); + + accordians.sort((a1, a2) => a1.order - a2.order); + + const featureCount = this.props.features.length; + const enabledCount = getEnabledFeatures(this.props.features, this.props.space).length; + const controls = []; + if (enabledCount < featureCount) { + controls.push( + this.showAll()} data-test-subj="showAllFeaturesLink"> + + {i18n.translate('xpack.spaces.management.showAllFeaturesLink', { + defaultMessage: 'Show all', + })} + + + ); + } + if (enabledCount > 0) { + controls.push( + this.hideAll()} data-test-subj="hideAllFeaturesLink"> + + {i18n.translate('xpack.spaces.management.hideAllFeaturesLink', { + defaultMessage: 'Hide all', + })} + + + ); + } + + return ( +
+ + + + Feature visibility + + + {controls.map((control, idx) => ( + + {control} + + ))} + + + {accordians.flatMap((a, idx) => [ + a.element, + , + ])} +
+ ); } public onChange = (featureId: string) => (e: ChangeEvent) => { @@ -49,67 +221,32 @@ export class FeatureTable extends Component { this.props.onChange(updatedSpace); }; - private onChangeAll = (visible: boolean) => { + private getAllFeatureIds = () => + [...this.featureCategories.values()].flat().map((feature) => feature.id); + + private hideAll = () => { + this.setFeaturesVisibility(this.getAllFeatureIds(), false); + }; + + private showAll = () => { + this.setFeaturesVisibility(this.getAllFeatureIds(), true); + }; + + private setFeaturesVisibility = (features: string[], visible: boolean) => { const updatedSpace: Partial = { ...this.props.space, }; if (visible) { - updatedSpace.disabledFeatures = []; + updatedSpace.disabledFeatures = (updatedSpace.disabledFeatures ?? []).filter( + (df) => !features.includes(df) + ); } else { - updatedSpace.disabledFeatures = this.props.features.map((feature) => feature.id); + updatedSpace.disabledFeatures = Array.from( + new Set([...(updatedSpace.disabledFeatures ?? []), ...features]) + ); } this.props.onChange(updatedSpace); }; - - private getColumns = () => [ - { - field: 'feature', - name: i18n.translate('xpack.spaces.management.enabledSpaceFeaturesFeatureColumnTitle', { - defaultMessage: 'Feature', - }), - render: ( - feature: KibanaFeatureConfig, - _item: { feature: KibanaFeatureConfig; space: Props['space'] } - ) => { - return ( - - -   {feature.name} - - ); - }, - }, - { - field: 'space', - width: '150', - name: ( - - - - - ), - - render: (spaceEntry: Space, record: Record) => { - const checked = !( - spaceEntry.disabledFeatures && spaceEntry.disabledFeatures.includes(record.feature.id) - ); - - return ( - - ); - }, - }, - ]; } diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index f580720848875..66f5ea87551d3 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -4,19 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiLink, EuiSwitch } from '@elastic/eui'; +import { EuiButton, EuiCheckboxProps } from '@elastic/eui'; import { ReactWrapper } from 'enzyme'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { ManageSpacePage } from './manage_space_page'; -import { SectionPanel } from './section_panel'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks'; import { featuresPluginMock } from '../../../../features/public/mocks'; import { KibanaFeature } from '../../../../features/public'; +import { DEFAULT_APP_CATEGORIES } from '../../../../../../src/core/public'; // To be resolved by EUI team. // https://github.com/elastic/eui/issues/3712 @@ -39,6 +39,7 @@ featuresStart.getFeatures.mockResolvedValue([ name: 'feature 1', icon: 'spacesApp', app: [], + category: DEFAULT_APP_CATEGORIES.kibana, privileges: null, }), ]); @@ -309,16 +310,12 @@ function updateSpace(wrapper: ReactWrapper, updateFeature = true) { } function toggleFeature(wrapper: ReactWrapper) { - const featureSectionButton = wrapper - .find(SectionPanel) - .filter('[data-test-subj="enabled-features-panel"]') - .find(EuiLink); - - featureSectionButton.simulate('click'); - - wrapper.update(); - - wrapper.find(EuiSwitch).find('button').simulate('click'); + const { + onChange = () => { + throw new Error('expected onChange to be defined'); + }, + } = wrapper.find('input#featureCategoryCheckbox_kibana').props() as EuiCheckboxProps; + onChange({ target: { checked: false } } as any); wrapper.update(); } diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx index 5338710b7c8a4..6943e27501554 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -177,11 +177,16 @@ export class ManageSpacePage extends Component { }; public getFormHeading = () => ( - -

- {this.getTitle()} -

-
+ + + +

{this.getTitle()}

+
+
+ + + +
); public getTitle = () => { diff --git a/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx index 2d1ec727b3348..d9ad63c30adde 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon } from '@elastic/eui'; +import { EuiBadge } from '@elastic/eui'; import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { ReservedSpaceBadge } from './reserved_space_badge'; @@ -24,7 +24,7 @@ const unreservedSpace = { test('it renders without crashing', () => { const wrapper = shallowWithIntl(); - expect(wrapper.find(EuiIcon)).toHaveLength(1); + expect(wrapper.find(EuiBadge)).toHaveLength(1); }); test('it renders nothing for an unreserved space', () => { diff --git a/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx index 38bf351902096..f3a2273d90e8c 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { isReservedSpace } from '../../../common'; import { Space } from '../../../common/model/space'; @@ -28,7 +28,9 @@ export const ReservedSpaceBadge = (props: Props) => { /> } > - + + Reserved space + ); } diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx index fe4bdc865094f..c1d19eb06c2e7 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx @@ -47,6 +47,7 @@ featuresStart.getFeatures.mockResolvedValue([ name: 'feature 1', icon: 'spacesApp', app: [], + category: { id: 'foo', label: 'foo' }, privileges: null, }), ]); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 1e8520a2617dd..e345657a785c1 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -88,7 +88,11 @@ describe('spacesManagementApp', () => { expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Spaces' }]); expect(container).toMatchInlineSnapshot(`
- Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"securityEnabled":true} +
`); @@ -107,7 +111,11 @@ describe('spacesManagementApp', () => { ]); expect(container).toMatchInlineSnapshot(`
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"securityEnabled":true} +
`); @@ -128,7 +136,11 @@ describe('spacesManagementApp', () => { ]); expect(container).toMatchInlineSnapshot(`
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"securityEnabled":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 5b8b993d96adc..a328c50af4e7a 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -9,6 +9,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; +import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public'; import { SecurityLicense } from '../../../security/public'; import { RegisterManagementAppArgs } from '../../../../../src/plugins/management/public'; import { PluginsStart } from '../plugin'; @@ -32,6 +33,7 @@ export const spacesManagementApp = Object.freeze({ title: i18n.translate('xpack.spaces.displayName', { defaultMessage: 'Spaces', }), + async mount({ element, setBreadcrumbs, history }) { const [ { notifications, i18n: i18nStart, application }, @@ -114,19 +116,21 @@ export const spacesManagementApp = Object.freeze({ render( - - - - - - - - - - - - - + + + + + + + + + + + + + + + , element ); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3938ed163f6cc..c7161a10dc234 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17578,13 +17578,10 @@ "xpack.spaces.management.enabledSpaceFeatures.allFeaturesEnabledMessage": "(表示されているすべての機能)", "xpack.spaces.management.enabledSpaceFeatures.enabledFeaturesSectionMessage": "機能の表示をカスタマイズ", "xpack.spaces.management.enabledSpaceFeatures.enableFeaturesInSpaceMessage": "このスペースでどの機能が表示されるかを管理します。", - "xpack.spaces.management.enabledSpaceFeatures.goToRolesLink": "セキュアなアクセスをご希望の場合は、{rolesLink} にアクセスしてください。", "xpack.spaces.management.enabledSpaceFeatures.noFeaturesEnabledMessage": "(表示されている機能がありません)", "xpack.spaces.management.enabledSpaceFeatures.notASecurityMechanismMessage": "この機能は UI で非表示になっていますが、無効ではありません。", "xpack.spaces.management.enabledSpaceFeatures.rolesLinkText": "ロール", "xpack.spaces.management.enabledSpaceFeatures.someFeaturesEnabledMessage": "({featureCount} 件中 {enabledCount} 件の機能を表示中)", - "xpack.spaces.management.enabledSpaceFeaturesEnabledColumnTitle": "表示しますか?", - "xpack.spaces.management.enabledSpaceFeaturesFeatureColumnTitle": "機能", "xpack.spaces.management.hideAllFeaturesText": "すべて非表示", "xpack.spaces.management.manageSpacePage.avatarFormRowLabel": "アバター", "xpack.spaces.management.manageSpacePage.awesomeSpacePlaceholder": "素晴らしいスペース", @@ -17592,10 +17589,8 @@ "xpack.spaces.management.manageSpacePage.clickToCustomizeTooltip": "クリックしてこのスペースのアバターをカスタマイズします", "xpack.spaces.management.manageSpacePage.createSpaceButton": "スペースを作成", "xpack.spaces.management.manageSpacePage.createSpaceTitle": "スペースの作成", - "xpack.spaces.management.manageSpacePage.customizeSpacePanelDescription": "スペースに名前を付けてアバターをカスタマイズします", "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierEditable": "URL 識別子に注意してください。スペースの作成後に変更することはできません。", "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierNotEditable": "URL 識別子は変更できません。", - "xpack.spaces.management.manageSpacePage.customizeSpaceTitle": "スペースのカスタマイズ", "xpack.spaces.management.manageSpacePage.customizeVisibleFeatures": "表示される機能のカスタマイズ", "xpack.spaces.management.manageSpacePage.errorLoadingSpaceTitle": "スペースの読み込み中にエラーが発生: {message}", "xpack.spaces.management.manageSpacePage.errorSavingSpaceTitle": "スペースの保存中にエラーが発生: {message}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c8eefb45ea9f5..9e061faf8467f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17588,13 +17588,10 @@ "xpack.spaces.management.enabledSpaceFeatures.allFeaturesEnabledMessage": "(所有可见功能)", "xpack.spaces.management.enabledSpaceFeatures.enabledFeaturesSectionMessage": "定制功能显示", "xpack.spaces.management.enabledSpaceFeatures.enableFeaturesInSpaceMessage": "控制哪些功能在此工作区中可见。", - "xpack.spaces.management.enabledSpaceFeatures.goToRolesLink": "想保护访问?前往 {rolesLink}。", "xpack.spaces.management.enabledSpaceFeatures.noFeaturesEnabledMessage": "(没有可见功能)", "xpack.spaces.management.enabledSpaceFeatures.notASecurityMechanismMessage": "该功能在 UI 中已隐藏,但未禁用。", "xpack.spaces.management.enabledSpaceFeatures.rolesLinkText": "角色", "xpack.spaces.management.enabledSpaceFeatures.someFeaturesEnabledMessage": "({enabledCount} / {featureCount} 个功能可见)", - "xpack.spaces.management.enabledSpaceFeaturesEnabledColumnTitle": "显示?", - "xpack.spaces.management.enabledSpaceFeaturesFeatureColumnTitle": "功能", "xpack.spaces.management.hideAllFeaturesText": "全部隐藏", "xpack.spaces.management.manageSpacePage.avatarFormRowLabel": "头像", "xpack.spaces.management.manageSpacePage.awesomeSpacePlaceholder": "超卓的空间", @@ -17602,10 +17599,8 @@ "xpack.spaces.management.manageSpacePage.clickToCustomizeTooltip": "单击可定制此工作区头像", "xpack.spaces.management.manageSpacePage.createSpaceButton": "创建工作区", "xpack.spaces.management.manageSpacePage.createSpaceTitle": "创建一个空间", - "xpack.spaces.management.manageSpacePage.customizeSpacePanelDescription": "命名您的工作区并定制其头像。", "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierEditable": "记下 URL 标识符。创建工作区后,将不能更改它。", "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierNotEditable": "URL 标识符无法更改。", - "xpack.spaces.management.manageSpacePage.customizeSpaceTitle": "定制您的工作区", "xpack.spaces.management.manageSpacePage.customizeVisibleFeatures": "定制可见功能", "xpack.spaces.management.manageSpacePage.errorLoadingSpaceTitle": "加载空间时出错:{message}", "xpack.spaces.management.manageSpacePage.errorSavingSpaceTitle": "保存空间时出错:{message}", diff --git a/x-pack/plugins/uptime/server/kibana.index.ts b/x-pack/plugins/uptime/server/kibana.index.ts index 5c3211eff3b4e..cd2dc5018e110 100644 --- a/x-pack/plugins/uptime/server/kibana.index.ts +++ b/x-pack/plugins/uptime/server/kibana.index.ts @@ -5,6 +5,7 @@ */ import { Request, Server } from 'hapi'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { PLUGIN } from '../common/constants/plugin'; import { compose } from './lib/compose/kibana'; import { initUptimeServer } from './uptime_server'; @@ -31,6 +32,7 @@ export const initServerWithKibana = (server: UptimeCoreSetup, plugins: UptimeCor id: PLUGIN.ID, name: PLUGIN.NAME, order: 1000, + category: DEFAULT_APP_CATEGORIES.observability, navLinkId: PLUGIN.ID, icon: 'uptimeApp', app: ['uptime', 'kibana'], diff --git a/x-pack/test/accessibility/apps/spaces.ts b/x-pack/test/accessibility/apps/spaces.ts index e11de1376e400..f4553e4c3a6fe 100644 --- a/x-pack/test/accessibility/apps/spaces.ts +++ b/x-pack/test/accessibility/apps/spaces.ts @@ -51,7 +51,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('a11y test for for customize space card', async () => { await PageObjects.spaceSelector.clickEnterSpaceName(); await PageObjects.spaceSelector.addSpaceName('space_a'); - await PageObjects.spaceSelector.clickSpaceAcustomAvatar(); + await PageObjects.spaceSelector.clickCustomizeSpaceAvatar('space_a'); await a11y.testAppSnapshot(); await browser.pressKeys(browser.keys.ESCAPE); }); @@ -75,30 +75,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); - it('a11y test for click on "show" button to open customize feature display', async () => { - await retry.waitFor( - 'show button is visible', - async () => await testSubjects.exists('show-hide-section-link') - ); - await PageObjects.spaceSelector.clickShowFeatures(); - await a11y.testAppSnapshot(); - }); - - it('a11y test for change all option for feature visibility popover', async () => { - await PageObjects.spaceSelector.clickFeaturesVisibilityButton(); + it('a11y test for toggling an entire feature category', async () => { + await PageObjects.spaceSelector.toggleFeatureCategoryVisibility('kibana'); await a11y.testAppSnapshot(); - }); - it('a11y test for hide all feature visibility popover option', async () => { - await PageObjects.spaceSelector.clickHideAllFeatures(); + await PageObjects.spaceSelector.openFeatureCategory('kibana'); await a11y.testAppSnapshot(); - }); - it('a11y test for toggle individual feature - using enterprise feature visibility', async () => { - await PageObjects.spaceSelector.clickFeaturesVisibilityButton(); - await PageObjects.spaceSelector.clickShowAllFeatures(); - await PageObjects.spaceSelector.toggleFeatureVisibility('enterpriseSearch'); - await a11y.testAppSnapshot(); + await PageObjects.spaceSelector.toggleFeatureCategoryVisibility('kibana'); }); it('a11y test for space listing page', async () => { diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts index 68ff3dad9ae86..43e4f642bb943 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts @@ -76,6 +76,7 @@ export class FixturePlugin implements Plugin