* Grouped features for space management * Apply suggestions from code review Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * Address PR Feedback * docs changes * updating types/docs * update APM feature name * Reintroduce extraAction following EUI update * change ordering of infra features, and render callout for management category Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
91d0a3b665
commit
cdbf4ffb8c
70 changed files with 965 additions and 313 deletions
|
@ -38,6 +38,12 @@ Registering a feature consists of the following fields. For more information, co
|
|||
|`"Sample Feature"`
|
||||
|A human readable name for your feature.
|
||||
|
||||
|`category` (required)
|
||||
|{kib-repo}blob/{branch}/src/core/types/app_category.ts[`AppCategory`]
|
||||
|`DEFAULT_APP_CATEGORIES.kibana`
|
||||
|The `AppCategory` which best represents your feature. Used to organize the display
|
||||
of features within the management screens.
|
||||
|
||||
|`app` (required)
|
||||
|`string[]`
|
||||
|`["sample_app", "kibana"]`
|
||||
|
@ -96,6 +102,7 @@ public setup(core, { features }) {
|
|||
name: 'Canvas',
|
||||
icon: 'canvasApp',
|
||||
navLinkId: 'canvas',
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
app: ['canvas', 'kibana'],
|
||||
catalogue: ['canvas'],
|
||||
privileges: {
|
||||
|
@ -155,6 +162,7 @@ public setup(core, { features }) {
|
|||
}),
|
||||
icon: 'devToolsApp',
|
||||
navLinkId: 'dev_tools',
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
app: ['kibana'],
|
||||
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
|
||||
privileges: {
|
||||
|
@ -217,6 +225,7 @@ public setup(core, { features }) {
|
|||
order: 100,
|
||||
icon: 'discoverApp',
|
||||
navLinkId: 'discover',
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
app: ['kibana'],
|
||||
catalogue: ['discover'],
|
||||
privileges: {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AppCategory](./kibana-plugin-core-server.appcategory.md) > [ariaLabel](./kibana-plugin-core-server.appcategory.arialabel.md)
|
||||
|
||||
## AppCategory.ariaLabel property
|
||||
|
||||
If the visual label isn't appropriate for screen readers, can override it here
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
ariaLabel?: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AppCategory](./kibana-plugin-core-server.appcategory.md) > [euiIconType](./kibana-plugin-core-server.appcategory.euiicontype.md)
|
||||
|
||||
## AppCategory.euiIconType property
|
||||
|
||||
Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
euiIconType?: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AppCategory](./kibana-plugin-core-server.appcategory.md) > [id](./kibana-plugin-core-server.appcategory.id.md)
|
||||
|
||||
## AppCategory.id property
|
||||
|
||||
Unique identifier for the categories
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
id: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AppCategory](./kibana-plugin-core-server.appcategory.md) > [label](./kibana-plugin-core-server.appcategory.label.md)
|
||||
|
||||
## AppCategory.label property
|
||||
|
||||
Label used for category name. Also used as aria-label if one isn't set.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
label: string;
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AppCategory](./kibana-plugin-core-server.appcategory.md)
|
||||
|
||||
## AppCategory interface
|
||||
|
||||
A category definition for nav links to know where to sort them in the left hand nav
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface AppCategory
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [ariaLabel](./kibana-plugin-core-server.appcategory.arialabel.md) | <code>string</code> | If the visual label isn't appropriate for screen readers, can override it here |
|
||||
| [euiIconType](./kibana-plugin-core-server.appcategory.euiicontype.md) | <code>string</code> | Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined |
|
||||
| [id](./kibana-plugin-core-server.appcategory.id.md) | <code>string</code> | Unique identifier for the categories |
|
||||
| [label](./kibana-plugin-core-server.appcategory.label.md) | <code>string</code> | Label used for category name. Also used as aria-label if one isn't set. |
|
||||
| [order](./kibana-plugin-core-server.appcategory.order.md) | <code>number</code> | The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AppCategory](./kibana-plugin-core-server.appcategory.md) > [order](./kibana-plugin-core-server.appcategory.order.md)
|
||||
|
||||
## AppCategory.order property
|
||||
|
||||
The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
order?: number;
|
||||
```
|
|
@ -50,6 +50,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [AppCategory](./kibana-plugin-core-server.appcategory.md) | A category definition for nav links to know where to sort them in the left hand nav |
|
||||
| [AssistanceAPIResponse](./kibana-plugin-core-server.assistanceapiresponse.md) | |
|
||||
| [AssistantAPIClientParams](./kibana-plugin-core-server.assistantapiclientparams.md) | |
|
||||
| [AuditableEvent](./kibana-plugin-core-server.auditableevent.md) | Event to audit. |
|
||||
|
|
|
@ -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<void, void, AlertingExample
|
|||
management: {
|
||||
insightsAndAlerting: ['triggersActions'],
|
||||
},
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
alerting: [alwaysFiringAlert.id, peopleInSpaceAlert.id, INDEX_THRESHOLD_ID],
|
||||
privileges: {
|
||||
all: {
|
||||
|
|
|
@ -171,6 +171,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
|
|||
Object {
|
||||
"baseUrl": "/",
|
||||
"category": Object {
|
||||
"euiIconType": "managementApp",
|
||||
"id": "management",
|
||||
"label": "Management",
|
||||
"order": 5000,
|
||||
|
@ -1606,6 +1607,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
|
|||
</EuiCollapsibleNavGroup>
|
||||
<EuiCollapsibleNavGroup
|
||||
data-test-subj="collapsibleNavGroup-management"
|
||||
iconType="managementApp"
|
||||
initialIsOpen={true}
|
||||
isCollapsible={true}
|
||||
key="management"
|
||||
|
@ -1621,6 +1623,14 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
|
|||
gutterSize="m"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiIcon
|
||||
size="l"
|
||||
type="managementApp"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle
|
||||
size="xxs"
|
||||
|
@ -1686,6 +1696,23 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
|
|||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<EuiIcon
|
||||
size="l"
|
||||
type="managementApp"
|
||||
>
|
||||
<div
|
||||
data-euiicon-type="managementApp"
|
||||
size="l"
|
||||
/>
|
||||
</EuiIcon>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
|
|
|
@ -446,37 +446,7 @@ export class CoreSystem {
|
|||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export const DEFAULT_APP_CATEGORIES: Readonly<{
|
||||
kibana: {
|
||||
id: string;
|
||||
label: string;
|
||||
euiIconType: string;
|
||||
order: number;
|
||||
};
|
||||
enterpriseSearch: {
|
||||
id: string;
|
||||
label: string;
|
||||
order: number;
|
||||
euiIconType: string;
|
||||
};
|
||||
observability: {
|
||||
id: string;
|
||||
label: string;
|
||||
euiIconType: string;
|
||||
order: number;
|
||||
};
|
||||
security: {
|
||||
id: string;
|
||||
label: string;
|
||||
order: number;
|
||||
euiIconType: string;
|
||||
};
|
||||
management: {
|
||||
id: string;
|
||||
label: string;
|
||||
order: number;
|
||||
};
|
||||
}>;
|
||||
export const DEFAULT_APP_CATEGORIES: Record<string, AppCategory>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DocLinksStart {
|
||||
|
|
|
@ -323,6 +323,7 @@ export {
|
|||
MetricsServiceStart,
|
||||
} from './metrics';
|
||||
|
||||
export { AppCategory } from '../types';
|
||||
export { DEFAULT_APP_CATEGORIES } from '../utils';
|
||||
|
||||
export {
|
||||
|
|
|
@ -164,6 +164,15 @@ import { UpdateDocumentByQueryParams } from 'elasticsearch';
|
|||
import { UpdateDocumentParams } from 'elasticsearch';
|
||||
import { Url } from 'url';
|
||||
|
||||
// @public
|
||||
export interface AppCategory {
|
||||
ariaLabel?: string;
|
||||
euiIconType?: string;
|
||||
id: string;
|
||||
label: string;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ConsoleAppenderConfig" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "FileAppenderConfig" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "LegacyAppenderConfig" needs to be exported by the entry point index.d.ts
|
||||
|
@ -484,37 +493,7 @@ export interface CustomHttpResponseOptions<T extends HttpResponsePayload | Respo
|
|||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export const DEFAULT_APP_CATEGORIES: Readonly<{
|
||||
kibana: {
|
||||
id: string;
|
||||
label: string;
|
||||
euiIconType: string;
|
||||
order: number;
|
||||
};
|
||||
enterpriseSearch: {
|
||||
id: string;
|
||||
label: string;
|
||||
order: number;
|
||||
euiIconType: string;
|
||||
};
|
||||
observability: {
|
||||
id: string;
|
||||
label: string;
|
||||
euiIconType: string;
|
||||
order: number;
|
||||
};
|
||||
security: {
|
||||
id: string;
|
||||
label: string;
|
||||
order: number;
|
||||
euiIconType: string;
|
||||
};
|
||||
management: {
|
||||
id: string;
|
||||
label: string;
|
||||
order: number;
|
||||
};
|
||||
}>;
|
||||
export const DEFAULT_APP_CATEGORIES: Record<string, AppCategory>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DeleteDocumentResponse {
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AppCategory } from '../types';
|
||||
|
||||
/** @internal */
|
||||
export const DEFAULT_APP_CATEGORIES = Object.freeze({
|
||||
export const DEFAULT_APP_CATEGORIES: Record<string, AppCategory> = Object.freeze({
|
||||
kibana: {
|
||||
id: 'kibana',
|
||||
label: i18n.translate('core.ui.kibanaNavList.label', {
|
||||
|
@ -59,5 +60,6 @@ export const DEFAULT_APP_CATEGORIES = Object.freeze({
|
|||
defaultMessage: 'Management',
|
||||
}),
|
||||
order: 5000,
|
||||
euiIconType: 'managementApp',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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'],
|
||||
},
|
||||
|
|
|
@ -44,6 +44,7 @@ function mockFeature(appName: string, typeName?: string) {
|
|||
id: appName,
|
||||
name: appName,
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
...(typeName
|
||||
? {
|
||||
alerting: [typeName],
|
||||
|
@ -87,6 +88,7 @@ function mockFeatureWithSubFeature(appName: string, typeName: string) {
|
|||
id: appName,
|
||||
name: appName,
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
...(typeName
|
||||
? {
|
||||
alerting: [typeName],
|
||||
|
|
|
@ -164,6 +164,7 @@ function mockFeatures() {
|
|||
id: 'appName',
|
||||
name: 'appName',
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
privileges: {
|
||||
all: {
|
||||
savedObject: {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { LicenseType } from '../../licensing/common/types';
|
||||
import { AlertType } from '../common/alert_types';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
|
||||
import {
|
||||
LicensingPluginSetup,
|
||||
LicensingRequestHandlerContext,
|
||||
|
@ -15,9 +16,10 @@ import {
|
|||
export const APM_FEATURE = {
|
||||
id: 'apm',
|
||||
name: i18n.translate('xpack.apm.featureRegistry.apmFeatureName', {
|
||||
defaultMessage: 'APM',
|
||||
defaultMessage: 'APM and Client Side Monitoring',
|
||||
}),
|
||||
order: 900,
|
||||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
icon: 'apmApp',
|
||||
navLinkId: 'apm',
|
||||
app: ['apm', 'csm', 'kibana'],
|
||||
|
|
|
@ -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';
|
||||
|
@ -40,7 +41,8 @@ export class CanvasPlugin implements Plugin {
|
|||
plugins.features.registerKibanaFeature({
|
||||
id: 'canvas',
|
||||
name: 'Canvas',
|
||||
order: 400,
|
||||
order: 300,
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
icon: 'canvasApp',
|
||||
navLinkId: 'canvas',
|
||||
app: ['canvas', 'kibana'],
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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'],
|
||||
|
@ -78,7 +80,8 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
|
|||
name: i18n.translate('xpack.features.visualizeFeatureName', {
|
||||
defaultMessage: 'Visualize',
|
||||
}),
|
||||
order: 200,
|
||||
order: 700,
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
icon: 'visualizeApp',
|
||||
navLinkId: 'visualize',
|
||||
app: ['visualize', 'lens', 'kibana'],
|
||||
|
@ -138,7 +141,8 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
|
|||
name: i18n.translate('xpack.features.dashboardFeatureName', {
|
||||
defaultMessage: 'Dashboard',
|
||||
}),
|
||||
order: 300,
|
||||
order: 200,
|
||||
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'],
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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']),
|
||||
|
|
|
@ -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';
|
||||
|
@ -46,7 +47,8 @@ export class GraphPlugin implements Plugin {
|
|||
name: i18n.translate('xpack.graph.featureRegistry.graphFeatureName', {
|
||||
defaultMessage: 'Graph',
|
||||
}),
|
||||
order: 1200,
|
||||
order: 600,
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
icon: 'graphApp',
|
||||
navLinkId: 'graph',
|
||||
app: ['graph', 'kibana'],
|
||||
|
|
|
@ -8,13 +8,15 @@ 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',
|
||||
name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
order: 700,
|
||||
order: 800,
|
||||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
icon: 'metricsApp',
|
||||
navLinkId: 'metrics',
|
||||
app: ['infra', 'metrics', 'kibana'],
|
||||
|
@ -64,7 +66,8 @@ export const LOGS_FEATURE = {
|
|||
name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', {
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
order: 800,
|
||||
order: 700,
|
||||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
icon: 'logsApp',
|
||||
navLinkId: 'logs',
|
||||
app: ['infra', 'logs', 'kibana'],
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
SavedObjectsClientContract,
|
||||
} 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,
|
||||
|
@ -181,6 +182,7 @@ export class IngestManagerPlugin
|
|||
id: PLUGIN_ID,
|
||||
name: 'Ingest Manager',
|
||||
icon: 'savedObjectsApp',
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
navLinkId: PLUGIN_ID,
|
||||
app: [PLUGIN_ID, 'kibana'],
|
||||
catalogue: ['ingestManager'],
|
||||
|
|
|
@ -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';
|
||||
|
@ -168,7 +169,8 @@ export class MapsPlugin implements Plugin {
|
|||
name: i18n.translate('xpack.maps.featureRegistry.mapsFeatureName', {
|
||||
defaultMessage: 'Maps',
|
||||
}),
|
||||
order: 600,
|
||||
order: 400,
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
icon: APP_ICON,
|
||||
navLinkId: APP_ID,
|
||||
app: [APP_ID, 'kibana'],
|
||||
|
|
|
@ -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<MlPluginSetup, MlPluginStart, Plug
|
|||
}),
|
||||
icon: PLUGIN_ICON,
|
||||
order: 500,
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
navLinkId: PLUGIN_ID,
|
||||
app: [PLUGIN_ID, 'kibana'],
|
||||
catalogue: [PLUGIN_ID, `${PLUGIN_ID}_file_data_visualizer`],
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
CustomHttpResponseOptions,
|
||||
ResponseError,
|
||||
} from 'kibana/server';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
|
||||
import {
|
||||
LOGGING_TAG,
|
||||
KIBANA_MONITORING_LOGGING_TAG,
|
||||
|
@ -248,6 +249,7 @@ export class Plugin {
|
|||
name: i18n.translate('xpack.monitoring.featureRegistry.monitoringFeatureName', {
|
||||
defaultMessage: 'Stack Monitoring',
|
||||
}),
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
icon: 'monitoringApp',
|
||||
navLinkId: 'monitoring',
|
||||
app: ['monitoring', 'kibana'],
|
||||
|
|
|
@ -21,6 +21,7 @@ export const createFeature = (
|
|||
icon: 'discoverApp',
|
||||
navLinkId: 'discover',
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
catalogue: [],
|
||||
privileges:
|
||||
privileges === null
|
||||
|
|
|
@ -32,6 +32,7 @@ const buildFeatures = () => {
|
|||
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'],
|
||||
|
|
|
@ -18,6 +18,7 @@ const buildProps = (customProps: any = {}) => {
|
|||
id: 'feature1',
|
||||
name: 'Feature 1',
|
||||
app: ['app'],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
icon: 'spacesApp',
|
||||
privileges: {
|
||||
all: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -87,6 +87,7 @@ const putRoleTest = (
|
|||
id: 'feature_1',
|
||||
name: 'feature 1',
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
privileges: {
|
||||
all: {
|
||||
ui: [],
|
||||
|
|
|
@ -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<PluginSetup, PluginStart, SetupPlugins, S
|
|||
}),
|
||||
order: 1100,
|
||||
icon: 'logoSecurity',
|
||||
category: DEFAULT_APP_CATEGORIES.security,
|
||||
navLinkId: APP_ID,
|
||||
app: [...securitySubPlugins, 'kibana'],
|
||||
catalogue: ['securitySolution'],
|
||||
|
|
|
@ -22,7 +22,7 @@ export const SecureSpaceMessage = (props: SecureSpaceMessageProps) => {
|
|||
return (
|
||||
<Fragment>
|
||||
<EuiHorizontalRule />
|
||||
<EuiText className="eui-textCenter">
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.secureSpaceMessage.howToAssignRoleToSpaceDescription"
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
import {
|
||||
EuiDescribedFormGroup,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiPopover,
|
||||
EuiPopoverProps,
|
||||
|
@ -75,66 +73,27 @@ export class CustomizeSpace extends Component<Props, State> {
|
|||
description={this.getPanelDescription()}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.spaces.management.manageSpacePage.nameFormRowLabel', {
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
{...validator.validateSpaceName(this.props.space)}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
name="name"
|
||||
data-test-subj="addSpaceName"
|
||||
placeholder={i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.awesomeSpacePlaceholder',
|
||||
{
|
||||
defaultMessage: 'Awesome space',
|
||||
}
|
||||
)}
|
||||
value={name}
|
||||
onChange={this.onNameChange}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.avatarFormRowLabel',
|
||||
{
|
||||
defaultMessage: 'Avatar',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiPopover
|
||||
id="customizeAvatarPopover"
|
||||
button={
|
||||
<button
|
||||
title={i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.clickToCustomizeTooltip',
|
||||
{
|
||||
defaultMessage: 'Click to customize this space avatar',
|
||||
}
|
||||
)}
|
||||
onClick={this.togglePopover}
|
||||
>
|
||||
<SpaceAvatar space={this.props.space} size="l" />
|
||||
</button>
|
||||
}
|
||||
closePopover={this.closePopover}
|
||||
{...extraPopoverProps}
|
||||
ownFocus={true}
|
||||
isOpen={this.state.customizingAvatar}
|
||||
>
|
||||
<div style={{ maxWidth: 240 }}>
|
||||
<CustomizeSpaceAvatar space={this.props.space} onChange={this.onAvatarChange} />
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.spaces.management.manageSpacePage.nameFormRowLabel', {
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
{...validator.validateSpaceName(this.props.space)}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
name="name"
|
||||
data-test-subj="addSpaceName"
|
||||
placeholder={i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.awesomeSpacePlaceholder',
|
||||
{
|
||||
defaultMessage: 'Awesome space',
|
||||
}
|
||||
)}
|
||||
value={name}
|
||||
onChange={this.onNameChange}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
|
@ -175,6 +134,37 @@ export class CustomizeSpace extends Component<Props, State> {
|
|||
rows={2}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.spaces.management.manageSpacePage.avatarFormRowLabel', {
|
||||
defaultMessage: 'Avatar',
|
||||
})}
|
||||
>
|
||||
<EuiPopover
|
||||
id="customizeAvatarPopover"
|
||||
button={
|
||||
<button
|
||||
title={i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.clickToCustomizeTooltip',
|
||||
{
|
||||
defaultMessage: 'Click to customize this space avatar',
|
||||
}
|
||||
)}
|
||||
onClick={this.togglePopover}
|
||||
>
|
||||
<SpaceAvatar space={this.props.space} size="l" />
|
||||
</button>
|
||||
}
|
||||
closePopover={this.closePopover}
|
||||
{...extraPopoverProps}
|
||||
ownFocus={true}
|
||||
isOpen={this.state.customizingAvatar}
|
||||
>
|
||||
<div style={{ maxWidth: 240 }}>
|
||||
<CustomizeSpaceAvatar space={this.props.space} onChange={this.onAvatarChange} />
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</SectionPanel>
|
||||
);
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
exports[`EnabledFeatures renders as expected 1`] = `
|
||||
<SectionPanel
|
||||
collapsible={true}
|
||||
collapsible={false}
|
||||
data-test-subj="enabled-features-panel"
|
||||
description="Customize visible features"
|
||||
initiallyCollapsed={true}
|
||||
initiallyCollapsed={false}
|
||||
title={
|
||||
<span>
|
||||
<FormattedMessage
|
||||
defaultMessage="Customize feature display"
|
||||
defaultMessage="Features"
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.enabledFeaturesSectionMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
|
@ -41,7 +41,7 @@ exports[`EnabledFeatures renders as expected 1`] = `
|
|||
>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
defaultMessage="Control which features are visible in this space."
|
||||
defaultMessage="Set feature visibility for this space"
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.enableFeaturesInSpaceMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
|
@ -63,16 +63,16 @@ exports[`EnabledFeatures renders as expected 1`] = `
|
|||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Want to secure access? Go to {rolesLink}."
|
||||
defaultMessage="If you wish to secure access to features, please {manageSecurityRoles}."
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.goToRolesLink"
|
||||
values={
|
||||
Object {
|
||||
"rolesLink": <EuiLink
|
||||
"manageSecurityRoles": <EuiLink
|
||||
data-test-subj="goToRoles"
|
||||
href="management"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Roles"
|
||||
defaultMessage="manage security roles"
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.rolesLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
|
@ -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",
|
||||
|
|
|
@ -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<EnabledFeatures>(
|
||||
<EnabledFeatures
|
||||
features={features}
|
||||
space={space}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: ['feature-1', 'feature-2'],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={jest.fn()}
|
||||
getUrlForApp={getUrlForApp}
|
||||
|
@ -52,27 +52,33 @@ describe('EnabledFeatures', () => {
|
|||
).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(
|
||||
<EnabledFeatures
|
||||
features={features}
|
||||
space={space}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: ['feature-1', 'feature-2'],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={changeHandler}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
);
|
||||
|
||||
// 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,36 @@ 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(
|
||||
<EnabledFeatures
|
||||
features={features}
|
||||
space={space}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: [],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={changeHandler}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
// Click the "Change all" link
|
||||
wrapper.find('.spcToggleAllFeatures__changeAllLink').first().simulate('click');
|
||||
// Ask to show all features
|
||||
findTestSubject(wrapper, `featureCategoryButton_kibana`).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 +124,140 @@ describe('EnabledFeatures', () => {
|
|||
|
||||
expect(updatedSpace.disabledFeatures).toEqual(['feature-1', 'feature-2']);
|
||||
});
|
||||
|
||||
it('allows all features to be toggled off', async () => {
|
||||
const changeHandler = jest.fn();
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<EnabledFeatures
|
||||
features={features}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: [],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={changeHandler}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
);
|
||||
|
||||
// show should not be visible when all features are already visible
|
||||
expect(findTestSubject(wrapper, 'showAllFeaturesLink')).toHaveLength(0);
|
||||
findTestSubject(wrapper, 'hideAllFeaturesLink').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 on', async () => {
|
||||
const changeHandler = jest.fn();
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<EnabledFeatures
|
||||
features={features}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: ['feature-1', 'feature-2'],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={changeHandler}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
);
|
||||
|
||||
// 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(
|
||||
<EnabledFeatures
|
||||
features={features}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: ['feature-1'],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={jest.fn()}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
);
|
||||
expect(findTestSubject(wrapper, 'hideAllFeaturesLink')).toHaveLength(1);
|
||||
expect(findTestSubject(wrapper, 'showAllFeaturesLink')).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('feature category button', () => {
|
||||
it(`does not toggle visibility when it contains more than one item`, () => {
|
||||
const changeHandler = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
<EnabledFeatures
|
||||
features={features}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: [],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={changeHandler}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
);
|
||||
|
||||
findTestSubject(wrapper, `featureCategoryButton_kibana`).simulate('click');
|
||||
expect(changeHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggles item visibility when the category contains a single item', () => {
|
||||
const changeHandler = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
<EnabledFeatures
|
||||
features={[
|
||||
...features,
|
||||
{
|
||||
id: 'feature-3',
|
||||
name: 'Feature 3',
|
||||
icon: 'spacesApp',
|
||||
app: [],
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
privileges: null,
|
||||
},
|
||||
]}
|
||||
space={{
|
||||
id: 'my-space',
|
||||
name: 'my space',
|
||||
disabledFeatures: [],
|
||||
}}
|
||||
securityEnabled={true}
|
||||
onChange={changeHandler}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
);
|
||||
|
||||
findTestSubject(wrapper, `featureCategoryButton_management`).simulate('click');
|
||||
expect(changeHandler).toBeCalledTimes(1);
|
||||
|
||||
const updatedSpace = changeHandler.mock.calls[0][0];
|
||||
|
||||
expect(updatedSpace.disabledFeatures).toEqual(['feature-3']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,8 +34,8 @@ export class EnabledFeatures extends Component<Props, {}> {
|
|||
|
||||
return (
|
||||
<SectionPanel
|
||||
collapsible
|
||||
initiallyCollapsed
|
||||
collapsible={false}
|
||||
initiallyCollapsed={false}
|
||||
title={this.getPanelTitle()}
|
||||
description={description}
|
||||
data-test-subj="enabled-features-panel"
|
||||
|
@ -46,7 +46,7 @@ export class EnabledFeatures extends Component<Props, {}> {
|
|||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.enableFeaturesInSpaceMessage"
|
||||
defaultMessage="Control which features are visible in this space."
|
||||
defaultMessage="Set feature visibility for this space"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
|
@ -114,7 +114,7 @@ export class EnabledFeatures extends Component<Props, {}> {
|
|||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.enabledFeaturesSectionMessage"
|
||||
defaultMessage="Customize feature display"
|
||||
defaultMessage="Features"
|
||||
/>{' '}
|
||||
{details}
|
||||
</span>
|
||||
|
@ -135,16 +135,16 @@ export class EnabledFeatures extends Component<Props, {}> {
|
|||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.goToRolesLink"
|
||||
defaultMessage="Want to secure access? Go to {rolesLink}."
|
||||
defaultMessage="If you wish to secure access to features, please {manageSecurityRoles}."
|
||||
values={{
|
||||
rolesLink: (
|
||||
manageSecurityRoles: (
|
||||
<EuiLink
|
||||
data-test-subj="goToRoles"
|
||||
href={this.props.getUrlForApp('management', { path: 'security/roles' })}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.enabledSpaceFeatures.rolesLinkText"
|
||||
defaultMessage="Roles"
|
||||
defaultMessage="manage security roles"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.spcFeatureTableAccordionContent {
|
||||
// Align accordion content with the feature category logo in the accordion's buttonContent
|
||||
padding-left: $euiSizeXL;
|
||||
}
|
|
@ -4,14 +4,29 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiIcon, EuiInMemoryTable, EuiSwitch, EuiText, IconType } from '@elastic/eui';
|
||||
import { EuiCallOut } 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 { AppCategory } from 'kibana/public';
|
||||
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<Space>;
|
||||
|
@ -20,15 +35,201 @@ interface Props {
|
|||
}
|
||||
|
||||
export class FeatureTable extends Component<Props, {}> {
|
||||
private featureCategories: Map<string, KibanaFeatureConfig[]> = 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 items = features.map((feature) => ({
|
||||
feature,
|
||||
space,
|
||||
}));
|
||||
const accordions: Array<{ order: number; element: ReactElement }> = [];
|
||||
this.featureCategories.forEach((featuresInCategory) => {
|
||||
const { category } = featuresInCategory[0];
|
||||
|
||||
return <EuiInMemoryTable columns={this.getColumns()} items={items} />;
|
||||
const featureCount = featuresInCategory.length;
|
||||
const enabledCount = getEnabledFeatures(featuresInCategory, space).length;
|
||||
|
||||
const canExpandCategory = featuresInCategory.length > 1;
|
||||
|
||||
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 buttonContent = (
|
||||
<EuiFlexGroup
|
||||
data-test-subj={`featureCategoryButton_${category.id}`}
|
||||
alignItems={'center'}
|
||||
responsive={false}
|
||||
gutterSize="m"
|
||||
onClick={() => {
|
||||
if (!canExpandCategory) {
|
||||
const isChecked = enabledCount > 0;
|
||||
this.setFeaturesVisibility(
|
||||
featuresInCategory.map((f) => f.id),
|
||||
!isChecked
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCheckbox {...checkboxProps} />
|
||||
</EuiFlexItem>
|
||||
{category.euiIconType ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon size="m" type={category.euiIconType} />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiTitle size="xs">
|
||||
<h4 className="eui-displayInlineBlock">{category.label}</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
const label: string = i18n.translate('xpack.spaces.management.featureAccordionSwitchLabel', {
|
||||
defaultMessage: '{enabledCount} / {featureCount} features visible',
|
||||
values: {
|
||||
enabledCount,
|
||||
featureCount,
|
||||
},
|
||||
});
|
||||
const extraAction = (
|
||||
<EuiText size="s" aria-hidden="true" color={'subdued'}>
|
||||
{label}
|
||||
</EuiText>
|
||||
);
|
||||
|
||||
const helpText = this.getCategoryHelpText(category);
|
||||
|
||||
const accordion = (
|
||||
<EuiAccordion
|
||||
id={`featureCategory_${category.id}`}
|
||||
data-test-subj={`featureCategory_${category.id}`}
|
||||
key={category.id}
|
||||
arrowDisplay={canExpandCategory ? 'right' : 'none'}
|
||||
forceState={canExpandCategory ? undefined : 'closed'}
|
||||
buttonContent={buttonContent}
|
||||
extraAction={canExpandCategory ? extraAction : undefined}
|
||||
>
|
||||
<div className="spcFeatureTableAccordionContent">
|
||||
<EuiSpacer size="s" />
|
||||
{helpText && (
|
||||
<>
|
||||
<EuiCallOut iconType="iInCircle" size="s">
|
||||
{helpText}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
{featuresInCategory.map((feature) => {
|
||||
const featureChecked = !(
|
||||
space.disabledFeatures && space.disabledFeatures.includes(feature.id)
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup key={`${feature.id}-toggle`}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCheckbox
|
||||
id={`featureCheckbox_${feature.id}`}
|
||||
data-test-subj={`featureCheckbox_${feature.id}`}
|
||||
checked={featureChecked}
|
||||
onChange={this.onChange(feature.id) as any}
|
||||
label={feature.name}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</EuiAccordion>
|
||||
);
|
||||
|
||||
accordions.push({
|
||||
order: category.order ?? Number.MAX_SAFE_INTEGER,
|
||||
element: accordion,
|
||||
});
|
||||
});
|
||||
|
||||
accordions.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(
|
||||
<EuiLink onClick={() => this.showAll()} data-test-subj="showAllFeaturesLink">
|
||||
<EuiText size="xs">
|
||||
{i18n.translate('xpack.spaces.management.selectAllFeaturesLink', {
|
||||
defaultMessage: 'Select all',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
if (enabledCount > 0) {
|
||||
controls.push(
|
||||
<EuiLink onClick={() => this.hideAll()} data-test-subj="hideAllFeaturesLink">
|
||||
<EuiText size="xs">
|
||||
{i18n.translate('xpack.spaces.management.deselectAllFeaturesLink', {
|
||||
defaultMessage: 'Deselect all',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiFlexGroup alignItems={'flexEnd'}>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="xs">
|
||||
<b>
|
||||
{i18n.translate('xpack.spaces.management.featureVisibilityTitle', {
|
||||
defaultMessage: 'Feature visibility',
|
||||
})}
|
||||
</b>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{controls.map((control, idx) => (
|
||||
<EuiFlexItem grow={false} key={idx}>
|
||||
{control}
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin={'m'} />
|
||||
{accordions.flatMap((a, idx) => [
|
||||
a.element,
|
||||
<EuiHorizontalRule key={`accordion-hr-${idx}`} margin={'m'} />,
|
||||
])}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public onChange = (featureId: string) => (e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
@ -49,67 +250,41 @@ export class FeatureTable extends Component<Props, {}> {
|
|||
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<Space> = {
|
||||
...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 (
|
||||
<EuiText>
|
||||
<EuiIcon size="m" type={feature.icon as IconType} />
|
||||
  {feature.name}
|
||||
</EuiText>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'space',
|
||||
width: '150',
|
||||
name: (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.enabledSpaceFeaturesEnabledColumnTitle"
|
||||
defaultMessage="Show?"
|
||||
/>
|
||||
<ToggleAllFeatures onChange={this.onChangeAll} />
|
||||
</span>
|
||||
),
|
||||
|
||||
render: (spaceEntry: Space, record: Record<string, any>) => {
|
||||
const checked = !(
|
||||
spaceEntry.disabledFeatures && spaceEntry.disabledFeatures.includes(record.feature.id)
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiSwitch
|
||||
data-test-subj={`feature-${record.feature.id}-toggle`}
|
||||
id={record.feature.id}
|
||||
checked={checked}
|
||||
onChange={this.onChange(record.feature.id) as any}
|
||||
label={`${record.feature.name} visible`}
|
||||
showLabel={false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
private getCategoryHelpText = (category: AppCategory) => {
|
||||
if (category.id === 'management') {
|
||||
return i18n.translate('xpack.spaces.management.managementCategoryHelpText', {
|
||||
defaultMessage:
|
||||
'Access to Stack Management is determined by your privileges, and cannot be hidden by Spaces.',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<any, any>, updateFeature = true) {
|
|||
}
|
||||
|
||||
function toggleFeature(wrapper: ReactWrapper<any, any>) {
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -177,11 +177,16 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
};
|
||||
|
||||
public getFormHeading = () => (
|
||||
<EuiTitle size="m">
|
||||
<h1>
|
||||
{this.getTitle()} <ReservedSpaceBadge space={this.state.space as Space} />
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="m">
|
||||
<h1 className="eui-displayInlineBlock">{this.getTitle()}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ReservedSpaceBadge space={this.state.space as Space} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
public getTitle = () => {
|
||||
|
|
|
@ -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(<ReservedSpaceBadge space={reservedSpace} />);
|
||||
expect(wrapper.find(EuiIcon)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiBadge)).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('it renders nothing for an unreserved space', () => {
|
||||
|
|
|
@ -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) => {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiIcon style={{ verticalAlign: 'super' }} type={'lock'} />
|
||||
<EuiBadge color="warning" iconType="questionInCircle" iconSide="right">
|
||||
Reserved space
|
||||
</EuiBadge>
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ featuresStart.getFeatures.mockResolvedValue([
|
|||
name: 'feature 1',
|
||||
icon: 'spacesApp',
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
privileges: null,
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -88,7 +88,11 @@ describe('spacesManagementApp', () => {
|
|||
expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Spaces' }]);
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"securityEnabled":true}
|
||||
<div
|
||||
class="kbnRedirectCrossAppLinks"
|
||||
>
|
||||
Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"securityEnabled":true}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
@ -107,7 +111,11 @@ describe('spacesManagementApp', () => {
|
|||
]);
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
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}
|
||||
<div
|
||||
class="kbnRedirectCrossAppLinks"
|
||||
>
|
||||
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}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
@ -128,7 +136,11 @@ describe('spacesManagementApp', () => {
|
|||
]);
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
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}
|
||||
<div
|
||||
class="kbnRedirectCrossAppLinks"
|
||||
>
|
||||
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}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
|
|
@ -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(
|
||||
<i18nStart.Context>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={['', '/']} exact>
|
||||
<SpacesGridPageWithBreadcrumbs />
|
||||
</Route>
|
||||
<Route path="/create">
|
||||
<CreateSpacePageWithBreadcrumbs />
|
||||
</Route>
|
||||
<Route path="/edit/:spaceId">
|
||||
<EditSpacePageWithBreadcrumbs />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
<RedirectAppLinks application={application}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={['', '/']} exact>
|
||||
<SpacesGridPageWithBreadcrumbs />
|
||||
</Route>
|
||||
<Route path="/create">
|
||||
<CreateSpacePageWithBreadcrumbs />
|
||||
</Route>
|
||||
<Route path="/edit/:spaceId">
|
||||
<EditSpacePageWithBreadcrumbs />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
</RedirectAppLinks>
|
||||
</i18nStart.Context>,
|
||||
element
|
||||
);
|
||||
|
|
|
@ -17549,13 +17549,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": "素晴らしいスペース",
|
||||
|
@ -17563,10 +17560,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}",
|
||||
|
|
|
@ -17559,13 +17559,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": "超卓的空间",
|
||||
|
@ -17573,10 +17570,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}",
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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();
|
||||
it('a11y test for toggling an entire feature category', async () => {
|
||||
await PageObjects.spaceSelector.toggleFeatureCategoryVisibility('kibana');
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('a11y test for change all option for feature visibility popover', async () => {
|
||||
await PageObjects.spaceSelector.clickFeaturesVisibilityButton();
|
||||
await PageObjects.spaceSelector.openFeatureCategory('kibana');
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
it('a11y test for hide all feature visibility popover option', async () => {
|
||||
await PageObjects.spaceSelector.clickHideAllFeatures();
|
||||
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 () => {
|
||||
|
|
|
@ -76,6 +76,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
id: 'actionsSimulators',
|
||||
name: 'actionsSimulators',
|
||||
app: ['actions', 'kibana'],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
privileges: {
|
||||
all: {
|
||||
app: ['actions', 'kibana'],
|
||||
|
|
|
@ -36,6 +36,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
id: 'alertsFixture',
|
||||
name: 'Alerts',
|
||||
app: ['alerts', 'kibana'],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
alerting: [
|
||||
'test.always-firing',
|
||||
'test.cumulative-firing',
|
||||
|
|
|
@ -27,6 +27,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
id: 'alertsRestrictedFixture',
|
||||
name: 'AlertRestricted',
|
||||
app: ['alerts', 'kibana'],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
alerting: ['test.restricted-noop', 'test.unrestricted-noop', 'test.noop'],
|
||||
privileges: {
|
||||
all: {
|
||||
|
|
|
@ -66,8 +66,8 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }: FtrPro
|
|||
await testSubjects.setValue('addSpaceName', spaceName);
|
||||
}
|
||||
|
||||
async clickSpaceAcustomAvatar() {
|
||||
await testSubjects.click('space-avatar-space_a');
|
||||
async clickCustomizeSpaceAvatar(spaceId: string) {
|
||||
await testSubjects.click(`space-avatar-${spaceId}`);
|
||||
}
|
||||
|
||||
async clickSpaceInitials() {
|
||||
|
@ -122,10 +122,6 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }: FtrPro
|
|||
await testSubjects.setValue('spaceURLDisplay', spaceURL);
|
||||
}
|
||||
|
||||
async clickFeaturesVisibilityButton() {
|
||||
await testSubjects.click('changeAllFeatureVisibilityPopover');
|
||||
}
|
||||
|
||||
async clickHideAllFeatures() {
|
||||
await testSubjects.click('spc-toggle-all-features-hide');
|
||||
}
|
||||
|
@ -134,8 +130,28 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }: FtrPro
|
|||
await testSubjects.click('spc-toggle-all-features-show');
|
||||
}
|
||||
|
||||
async toggleFeatureVisibility(featureName: string) {
|
||||
await testSubjects.click(`feature-${featureName}-toggle`);
|
||||
async openFeatureCategory(categoryName: string) {
|
||||
const category = await find.byCssSelector(
|
||||
`button[aria-controls=featureCategory_${categoryName}]`
|
||||
);
|
||||
const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true';
|
||||
if (!isCategoryExpanded) {
|
||||
await category.click();
|
||||
}
|
||||
}
|
||||
|
||||
async closeFeatureCategory(categoryName: string) {
|
||||
const category = await find.byCssSelector(
|
||||
`button[aria-controls=featureCategory_${categoryName}]`
|
||||
);
|
||||
const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true';
|
||||
if (isCategoryExpanded) {
|
||||
await category.click();
|
||||
}
|
||||
}
|
||||
|
||||
async toggleFeatureCategoryVisibility(categoryName: string) {
|
||||
await testSubjects.click(`featureCategoryButton_${categoryName}`);
|
||||
}
|
||||
|
||||
async clickOnDescriptionOfSpace() {
|
||||
|
|
|
@ -25,6 +25,7 @@ export class AlertingFixturePlugin implements Plugin<void, void, AlertingExample
|
|||
id: 'alerting_fixture',
|
||||
name: 'alerting_fixture',
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
alerting: ['test.always-firing', 'test.noop'],
|
||||
privileges: {
|
||||
all: {
|
||||
|
|
|
@ -18,6 +18,7 @@ class FooPlugin implements Plugin {
|
|||
id: 'foo',
|
||||
name: 'Foo',
|
||||
icon: 'upArrow',
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
navLinkId: 'foo_plugin',
|
||||
app: ['foo_plugin', 'kibana'],
|
||||
catalogue: ['foo'],
|
||||
|
|
Loading…
Reference in a new issue