Skip to content

Commit

Permalink
[Security Solution][Serverless] AppFeatures improvements (#158935)
Browse files Browse the repository at this point in the history
## Summary

issue: #158810
follow-up of: #158179

Improves the Security AppFeatures architecture:
- SubFeatures now preserve always the same order in the Security Kibana
config, despite the order of processing of enabled appFeatures.


![Security_sub_features](https://github.com/elastic/kibana/assets/17747913/3fefa80d-dec6-4336-92db-66e43970fefc)

- Change the `productTypes` config format
- Update `getProductAppFeatures` to:
  - process the new `productTypes` format.
- include _essentials_ tiers PLIs inside _complete_ tiers automatically.
- AppFeatures module now receives an array of PLIs instead of an object
- AppFeatures config now uses only SubFeature IDS instead of
`subActions` config objects directly
- Upselling components updated and `useProductTypeByPLI` implemented to
display the Product Type required
  • Loading branch information
semd authored Jun 2, 2023
1 parent e2e03ca commit 0fe67b2
Show file tree
Hide file tree
Showing 27 changed files with 801 additions and 472 deletions.
2 changes: 1 addition & 1 deletion config/serverless.security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ xpack.uptime.enabled: false

## Enable the Serverless Security plugin
xpack.serverless.security.enabled: true
xpack.serverless.security.productLineIds: ['securityComplete']
xpack.serverless.security.productTypes: [{ product_line: 'security', product_tier: 'complete' }]

## Set the home route
uiSettings.overrides.defaultRoute: /app/security/get_started
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pageLoadAssetSize:
serverless: 16573
serverlessObservability: 30000
serverlessSearch: 30000
serverlessSecurity: 41807
serverlessSecurity: 66019
sessionView: 77750
share: 71239
snapshotRestore: 79032
Expand Down
9 changes: 4 additions & 5 deletions x-pack/plugins/ess_security/server/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
* 2.0.
*/

import { AppFeatureKey, AppFeatureKeys } from '@kbn/security-solution-plugin/common';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-plugin/common';

export const DEFAULT_APP_FEATURES: AppFeatureKeys = {
[AppFeatureKey.advancedInsights]: true,
[AppFeatureKey.casesConnectors]: true,
};
// Just copying all feature keys for now.
// We may need a different set of keys in the future if we create serverless-specific appFeatures
export const DEFAULT_APP_FEATURES = [...ALL_APP_FEATURE_KEYS];
2 changes: 1 addition & 1 deletion x-pack/plugins/security_solution/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export { APP_UI_ID, APP_ID, CASES_FEATURE_ID, SERVER_APP_ID, SecurityPageName }
export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants';
export { allowedExperimentalValues, type ExperimentalFeatures } from './experimental_features';
export type { AppFeatureKeys } from './types/app_features';
export { AppFeatureKey } from './types/app_features';
export { AppFeatureKey, ALL_APP_FEATURE_KEYS } from './types/app_features';

// Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase.
// If you're using functions/types/etc... internally it's best to import directly from their paths than expose the functions/types/etc... here.
Expand Down
9 changes: 4 additions & 5 deletions x-pack/plugins/security_solution/common/types/app_features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ export enum AppFeatureCasesKey {
}

// Merges the two enums.
// We need to merge the value and the type and export both to replicate how enum works.
export const AppFeatureKey = { ...AppFeatureSecurityKey, ...AppFeatureCasesKey };
export type AppFeatureKey = AppFeatureSecurityKey | AppFeatureCasesKey;
export type AppFeatureKeys = AppFeatureKey[];

type AppFeatureSecurityKeys = { [key in AppFeatureSecurityKey]: boolean };
type AppFeatureCasesKeys = { [key in AppFeatureCasesKey]: boolean };
export type AppFeatureKeys = AppFeatureSecurityKeys & AppFeatureCasesKeys;
// We need to merge the value and the type and export both to replicate how enum works.
export const AppFeatureKey = { ...AppFeatureSecurityKey, ...AppFeatureCasesKey };
export const ALL_APP_FEATURE_KEYS = Object.freeze(Object.values(AppFeatureKey));
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,28 @@ const CASES_APP_FEATURE_CONFIG = {

jest.mock('./security_kibana_features', () => {
return {
getSecurityBaseKibanaFeature: jest.fn().mockReturnValue(SECURITY_BASE_CONFIG),
getSecurityAppFeaturesConfig: jest.fn().mockReturnValue(SECURITY_APP_FEATURE_CONFIG),
getSecurityBaseKibanaFeature: jest.fn(() => SECURITY_BASE_CONFIG),
getSecurityBaseKibanaSubFeatureIds: jest.fn(() => ['subFeature1']),
getSecurityAppFeaturesConfig: jest.fn(() => SECURITY_APP_FEATURE_CONFIG),
};
});
jest.mock('./security_kibana_sub_features', () => {
return {
securitySubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]),
};
});

jest.mock('./security_cases_kibana_features', () => {
return {
getCasesBaseKibanaFeature: jest.fn().mockReturnValue(CASES_BASE_CONFIG),
getCasesAppFeaturesConfig: jest.fn().mockReturnValue(CASES_APP_FEATURE_CONFIG),
getCasesBaseKibanaFeature: jest.fn(() => CASES_BASE_CONFIG),
getCasesBaseKibanaSubFeatureIds: jest.fn(() => ['subFeature1']),
getCasesAppFeaturesConfig: jest.fn(() => CASES_APP_FEATURE_CONFIG),
};
});

jest.mock('./security_cases_kibana_sub_features', () => {
return {
casesSubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]),
};
});

Expand All @@ -69,9 +82,7 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract;

const appFeatureKeys = {
'test-base-feature': true,
} as unknown as AppFeatureKeys;
const appFeatureKeys = ['test-base-feature'] as unknown as AppFeatureKeys;

const appFeatures = new AppFeatures(
{} as unknown as Logger,
Expand All @@ -83,6 +94,7 @@ describe('AppFeatures', () => {
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
...SECURITY_BASE_CONFIG,
...SECURITY_APP_FEATURE_CONFIG['test-base-feature'],
subFeatures: [{ baz: 'baz' }],
});
});

Expand All @@ -91,9 +103,7 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract;

const appFeatureKeys = {
'test-cases-feature': true,
} as unknown as AppFeatureKeys;
const appFeatureKeys = ['test-cases-feature'] as unknown as AppFeatureKeys;

const appFeatures = new AppFeatures(
{} as unknown as Logger,
Expand All @@ -105,6 +115,7 @@ describe('AppFeatures', () => {
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
...CASES_BASE_CONFIG,
...CASES_APP_FEATURE_CONFIG['test-cases-feature'],
subFeatures: [{ baz: 'baz' }],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,32 @@ import type { AppFeatureKibanaConfig, AppFeaturesConfig } from './types';
import {
getSecurityAppFeaturesConfig,
getSecurityBaseKibanaFeature,
getSecurityBaseKibanaSubFeatureIds,
} from './security_kibana_features';
import {
getCasesBaseKibanaFeature,
getCasesAppFeaturesConfig,
getCasesBaseKibanaSubFeatureIds,
} from './security_cases_kibana_features';
import { AppFeaturesConfigMerger } from './app_features_config_merger';

type AppFeaturesMap = Map<AppFeatureKey, boolean>;
import { casesSubFeaturesMap } from './security_cases_kibana_sub_features';
import { securitySubFeaturesMap } from './security_kibana_sub_features';

export class AppFeatures {
private merger: AppFeaturesConfigMerger;
private appFeatures?: AppFeaturesMap;
private securityFeatureConfigMerger: AppFeaturesConfigMerger;
private casesFeatureConfigMerger: AppFeaturesConfigMerger;
private appFeatures?: Set<AppFeatureKey>;
private featuresSetup?: FeaturesPluginSetup;

constructor(
private readonly logger: Logger,
private readonly experimentalFeatures: ExperimentalFeatures
) {
this.merger = new AppFeaturesConfigMerger(this.logger);
this.securityFeatureConfigMerger = new AppFeaturesConfigMerger(
this.logger,
securitySubFeaturesMap
);
this.casesFeatureConfigMerger = new AppFeaturesConfigMerger(this.logger, casesSubFeaturesMap);
}

public init(featuresSetup: FeaturesPluginSetup) {
Expand All @@ -41,15 +48,15 @@ export class AppFeatures {
if (this.appFeatures) {
throw new Error('AppFeatures has already been initialized');
}
this.appFeatures = new Map(Object.entries(appFeatureKeys) as Array<[AppFeatureKey, boolean]>);
this.appFeatures = new Set(appFeatureKeys);
this.registerEnabledKibanaFeatures();
}

public isEnabled(appFeatureKey: AppFeatureKey): boolean {
if (!this.appFeatures) {
throw new Error('AppFeatures has not been initialized');
}
return this.appFeatures.get(appFeatureKey) ?? false;
return this.appFeatures.has(appFeatureKey);
}

private registerEnabledKibanaFeatures() {
Expand All @@ -59,25 +66,31 @@ export class AppFeatures {
);
}
// register main security Kibana features
const securityBaseKibanaFeature = getSecurityBaseKibanaFeature(this.experimentalFeatures);
const securityBaseKibanaFeature = getSecurityBaseKibanaFeature();
const securityBaseKibanaSubFeatureIds = getSecurityBaseKibanaSubFeatureIds(
this.experimentalFeatures
);
const enabledSecurityAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs(
getSecurityAppFeaturesConfig()
);
this.featuresSetup.registerKibanaFeature(
this.merger.mergeAppFeatureConfigs(
this.securityFeatureConfigMerger.mergeAppFeatureConfigs(
securityBaseKibanaFeature,
securityBaseKibanaSubFeatureIds,
enabledSecurityAppFeaturesConfigs
)
);

// register security cases Kibana features
const securityCasesBaseKibanaFeature = getCasesBaseKibanaFeature();
const securityCasesBaseKibanaSubFeatureIds = getCasesBaseKibanaSubFeatureIds();
const enabledCasesAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs(
getCasesAppFeaturesConfig()
);
this.featuresSetup.registerKibanaFeature(
this.merger.mergeAppFeatureConfigs(
this.casesFeatureConfigMerger.mergeAppFeatureConfigs(
securityCasesBaseKibanaFeature,
securityCasesBaseKibanaSubFeatureIds,
enabledCasesAppFeaturesConfigs
)
);
Expand Down
Loading

0 comments on commit 0fe67b2

Please sign in to comment.