;
version: {
diff --git a/x-pack/plugins/cloud_security_posture/README.md b/x-pack/plugins/cloud_security_posture/README.md
index 0befebb667de6..bd0b7de6ac661 100755
--- a/x-pack/plugins/cloud_security_posture/README.md
+++ b/x-pack/plugins/cloud_security_posture/README.md
@@ -67,7 +67,7 @@ Run [**End-to-End Tests**](https://www.elastic.co/guide/en/kibana/current/develo
```bash
yarn test:ftr --config x-pack/test/cloud_security_posture_functional/config.ts
-yarn test:ftr --config x-pack/test/api_integration/config.ts --include-tag=cloud_security_posture
+yarn test:ftr --config x-pack/test/api_integration/apis/cloud_security_posture/config.ts
yarn test:ftr --config x-pack/test/cloud_security_posture_api/config.ts
yarn test:ftr --config x-pack/test_serverless/api_integration/test_suites/security/config.ts --include-tag=cloud_security_posture
yarn test:ftr --config x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts
diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts
index 9f267e07569c2..7641745b897f4 100644
--- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts
+++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts
@@ -63,6 +63,7 @@ export interface CloudPostureIntegrationProps {
icon?: string;
tooltip?: string;
isBeta?: boolean;
+ testId?: string;
}>;
}
@@ -85,6 +86,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
defaultMessage: 'CIS AWS',
}),
icon: 'logoAWS',
+ testId: 'cisAwsTestId',
},
{
type: CLOUDBEAT_GCP,
@@ -95,6 +97,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
defaultMessage: 'CIS GCP',
}),
icon: googleCloudLogo,
+ testId: 'cisGcpTestId',
},
// needs to be a function that disables/enabled based on integration version
{
@@ -108,6 +111,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
disabled: false,
isBeta: true,
icon: 'logoAzure',
+ testId: 'cisAzureTestId',
},
],
},
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx
index 9a50658a6a1f3..829d148097889 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx
@@ -24,6 +24,7 @@ export interface CspRadioOption {
icon?: string;
tooltip?: string;
isBeta?: boolean;
+ testId?: string;
}
export const RadioGroup = ({
@@ -34,7 +35,6 @@ export const RadioGroup = ({
onChange,
}: CspRadioGroupProps) => {
const { euiTheme } = useEuiTheme();
-
return (
=> [
{
id: SETUP_ACCESS_CLOUD_SHELL,
@@ -240,6 +243,7 @@ const getSetupFormatOptions = (): Array<{
defaultMessage: 'Google Cloud Shell',
}),
disabled: false,
+ testId: 'gcpGoogleCloudShellOptionTestId',
},
{
id: SETUP_ACCESS_MANUAL,
@@ -247,6 +251,7 @@ const getSetupFormatOptions = (): Array<{
defaultMessage: 'Manual',
}),
disabled: false,
+ testId: 'gcpManualOptionTestId',
},
];
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
index 9d1c52a0c1ee3..a4ec591d51c75 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
@@ -127,12 +127,14 @@ const getGcpAccountTypeOptions = (isGcpOrgDisabled: boolean): CspRadioGroupProps
defaultMessage: 'Supported from integration version 1.6.0 and above',
})
: undefined,
+ testId: 'gcpOrganizationAccountTestId',
},
{
id: GCP_SINGLE_ACCOUNT,
label: i18n.translate('xpack.csp.fleetIntegration.gcpAccountType.gcpSingleAccountLabel', {
defaultMessage: 'Single Account',
}),
+ testId: 'gcpSingleAccountTestId',
},
];
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts
index 9db4c9bf22a75..148a482714a10 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts
@@ -225,6 +225,7 @@ export const getPolicyTemplateInputOptions = (policyTemplate: CloudSecurityPolic
icon: o.icon,
disabled: o.disabled,
isBeta: o.isBeta,
+ testId: o.testId,
}));
export const getMaxPackageName = (
diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_overview_table.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_overview_table.tsx
index 1c03e18f52e8e..57d2f28af4c18 100644
--- a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_overview_table.tsx
+++ b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_overview_table.tsx
@@ -112,6 +112,9 @@ export const DataDriftOverviewTable = ({
return (
toggleDetails(item)}
aria-label={itemIdToExpandedRowMapValues[item.featureName] ? COLLAPSE_ROW : EXPAND_ROW}
iconType={itemIdToExpandedRowMapValues[item.featureName] ? 'arrowDown' : 'arrowRight'}
@@ -149,7 +152,10 @@ export const DataDriftOverviewTable = ({
'data-test-subj': 'mlDataDriftOverviewTableDriftDetected',
sortable: true,
textOnly: true,
- render: (driftDetected: boolean) => {
+ render: (driftDetected: boolean, item) => {
+ // @ts-expect-error currently ES two_sided does return string NaN, will be fixed
+ // NaN happens when the distributions are non overlapping. This means there is a drift.
+ if (item.similarityTestPValue === 'NaN') return dataComparisonYesLabel;
return {driftDetected ? dataComparisonYesLabel : dataComparisonNoLabel};
},
},
diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx
index 86ddff5c25e3f..2411c9096be70 100644
--- a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx
+++ b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx
@@ -94,7 +94,11 @@ export const PageHeader: FC = () => {
return (
{dataView.getName()}
}
+ pageTitle={
+
+ {dataView.getName()}
+
+ }
rightSideItems={[
{hasValidTimeField ? (
diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_utils.ts b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_utils.ts
index bb9b5cbc5a99b..43c0afee07e3e 100644
--- a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_utils.ts
+++ b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_utils.ts
@@ -9,8 +9,11 @@
* formatSignificanceLevel
* @param significanceLevel
*/
-export const formatSignificanceLevel = (significanceLevel: number) => {
+export const formatSignificanceLevel = (significanceLevel: number | 'NaN') => {
+ // NaN happens when the distributions are non overlapping. This means there is a drift, and the p-value would be astronomically small.
+ if (significanceLevel === 'NaN') return '< 0.000001';
if (typeof significanceLevel !== 'number' || isNaN(significanceLevel)) return '';
+
if (significanceLevel < 1e-6) {
return '< 0.000001';
} else if (significanceLevel < 0.01) {
diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx
index 63d717cec0393..0d83879c37486 100644
--- a/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx
+++ b/x-pack/plugins/data_visualizer/public/application/data_drift/document_count_with_dual_brush.tsx
@@ -141,7 +141,7 @@ export const DocumentCountWithDualBrush: FC = ({
timeRangeEarliest === undefined ||
timeRangeLatest === undefined
) {
- return totalCount !== undefined ? : null;
+ return totalCount !== undefined ? : null;
}
const chartPoints: LogRateHistogramItem[] = Object.entries(documentCountStats.buckets).map(
@@ -166,7 +166,7 @@ export const DocumentCountWithDualBrush: FC = ({
data-test-subj={getDataTestSubject('dataDriftTotalDocCountHeader', id)}
>
-
+
diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts
index d3bf05d486d82..dc7cb2a9e52d5 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts
@@ -68,6 +68,7 @@ describe('#setupSavedObjects', () => {
type: 'known-type',
attributes: { attrOne: 'one', attrSecret: '*secret*' },
references: [],
+ namespaces: ['some-ns'],
};
mockSavedObjectsRepository.get.mockResolvedValue(mockSavedObject);
mockSavedObjectTypeRegistry.isSingleNamespace.mockReturnValue(true);
@@ -101,6 +102,7 @@ describe('#setupSavedObjects', () => {
type: 'known-type',
attributes: { attrOne: 'one', attrSecret: '*secret*' },
references: [],
+ namespaces: ['some-ns2', 'some-ns'],
};
mockSavedObjectsRepository.get.mockResolvedValue(mockSavedObject);
mockSavedObjectTypeRegistry.isSingleNamespace.mockReturnValue(false);
@@ -154,6 +156,7 @@ describe('#setupSavedObjects', () => {
type: 'known-type',
attributes: { attrOne: 'one', attrSecret: '*secret*' },
references: [],
+ namespaces: ['some-ns'],
};
mockSavedObjectsRepository.createPointInTimeFinder = jest.fn().mockReturnValue({
close: jest.fn(),
@@ -320,5 +323,51 @@ describe('#setupSavedObjects', () => {
await finder.close();
expect(mockClose).toHaveBeenCalledTimes(1);
});
+
+ it('includes `namespace` for * find option', async () => {
+ const mockSavedObject: SavedObject = {
+ id: 'some-id',
+ type: 'known-type',
+ attributes: { attrOne: 'one', attrSecret: '*secret*' },
+ references: [],
+ namespaces: ['some-ns'],
+ };
+ mockSavedObjectsRepository.createPointInTimeFinder = jest.fn().mockReturnValue({
+ close: jest.fn(),
+ find: function* asyncGenerator() {
+ yield { saved_objects: [mockSavedObject] };
+ },
+ });
+
+ mockSavedObjectTypeRegistry.isSingleNamespace.mockReturnValue(true);
+
+ const finder = await setupContract().createPointInTimeFinderDecryptedAsInternalUser({
+ type: 'known-type',
+ namespaces: ['*'],
+ });
+
+ for await (const res of finder.find()) {
+ expect(res).toEqual({
+ saved_objects: [
+ {
+ ...mockSavedObject,
+ attributes: { attrOne: 'one', attrSecret: 'secret' },
+ },
+ ],
+ });
+ }
+
+ expect(mockEncryptedSavedObjectsService.decryptAttributes).toHaveBeenCalledTimes(1);
+ expect(mockEncryptedSavedObjectsService.decryptAttributes).toHaveBeenCalledWith(
+ { type: mockSavedObject.type, id: mockSavedObject.id, namespace: 'some-ns' },
+ mockSavedObject.attributes
+ );
+
+ expect(mockSavedObjectsRepository.createPointInTimeFinder).toHaveBeenCalledTimes(1);
+ expect(mockSavedObjectsRepository.createPointInTimeFinder).toHaveBeenCalledWith(
+ { type: 'known-type', namespaces: ['*'] },
+ undefined
+ );
+ });
});
});
diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts
index 78100be78e5d4..6c7b9ef5513ac 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts
@@ -118,7 +118,7 @@ export function setupSavedObjects({
{
type,
id,
- namespace: getDescriptorNamespace(typeRegistry, type, options?.namespace),
+ namespace: getDescriptorNamespace(typeRegistry, type, savedObject.namespaces),
},
savedObject.attributes as Record
)) as T,
@@ -148,7 +148,7 @@ export function setupSavedObjects({
namespace: getDescriptorNamespace(
typeRegistry,
savedObject.type,
- findOptions.namespaces
+ savedObject.namespaces
),
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx
index 1b24e10010e55..940beb64c41b7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/select_connector/connector_checkable.tsx
@@ -103,18 +103,6 @@ export const ConnectorCheckable: React.FC = ({
value={serviceType}
>
- {documentationUrl && (
-
-
- {i18n.translate(
- 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.documentationLinkLabel',
- {
- defaultMessage: 'Documentation',
- }
- )}
-
-
- )}
= ({
)}
+ {documentationUrl && (
+
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.documentationLinkLabel',
+ {
+ defaultMessage: 'Documentation',
+ }
+ )}
+
+
+
+ )}
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
index f84be1ad660b3..6dd47fe869ede 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
@@ -418,6 +418,7 @@ class DocLinks {
this.connectorsMySQL = docLinks.links.enterpriseSearch.connectorsMySQL;
this.connectorsNative = docLinks.links.enterpriseSearch.connectorsNative;
this.connectorsNetworkDrive = docLinks.links.enterpriseSearch.connectorsNetworkDrive;
+ this.connectorsOneDrive = docLinks.links.enterpriseSearch.connectorsOneDrive;
this.connectorsOracle = docLinks.links.enterpriseSearch.connectorsOracle;
this.connectorsOutlook = docLinks.links.enterpriseSearch.connectorsOutlook;
this.connectorsPostgreSQL = docLinks.links.enterpriseSearch.connectorsPostgreSQL;
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts b/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts
index 59498b4d8587a..84af615841eaf 100644
--- a/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts
@@ -5,16 +5,12 @@
* 2.0.
*/
-import { ELASTIC_MODEL_DEFINITIONS } from '@kbn/ml-trained-models-utils';
-
import {
ElasticsearchResponseError,
isNotFoundException,
isResourceNotFoundException,
} from '../../utils/identify_exceptions';
-export const acceptableModelNames = Object.keys(ELASTIC_MODEL_DEFINITIONS);
-
export function isNotFoundExceptionError(error: unknown): boolean {
return (
isResourceNotFoundException(error as ElasticsearchResponseError) ||
@@ -23,20 +19,3 @@ export function isNotFoundExceptionError(error: unknown): boolean {
error?.statusCode === 404
);
}
-
-export function throwIfNotAcceptableModelName(modelName: string) {
- if (!acceptableModelNames.includes(modelName)) {
- const notFoundError: ElasticsearchResponseError = {
- meta: {
- body: {
- error: {
- type: 'resource_not_found_exception',
- },
- },
- statusCode: 404,
- },
- name: 'ResponseError',
- };
- throw notFoundError;
- }
-}
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts
index 75bac93c2d200..4f65dbf9ced64 100644
--- a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts
@@ -12,10 +12,7 @@ import { MlTrainedModels } from '@kbn/ml-plugin/server';
import { MlModelDeploymentStatus, MlModelDeploymentState } from '../../../common/types/ml';
import { getMlModelDeploymentStatus } from './get_ml_model_deployment_status';
-import {
- isNotFoundExceptionError,
- throwIfNotAcceptableModelName,
-} from './ml_model_deployment_common';
+import { isNotFoundExceptionError } from './ml_model_deployment_common';
export const startMlModelDeployment = async (
modelName: string,
@@ -25,10 +22,6 @@ export const startMlModelDeployment = async (
throw new Error('Machine Learning is not enabled');
}
- // before anything else, check our model name
- // to ensure we only allow those names we want
- throwIfNotAcceptableModelName(modelName);
-
try {
// try and get the deployment status of the model first
// and see if it's already deployed or deploying...
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.ts
index 4d7f3c21e1210..ffa51acc5bd32 100644
--- a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.ts
@@ -11,10 +11,7 @@ import { MlTrainedModels } from '@kbn/ml-plugin/server';
import { MlModelDeploymentState, MlModelDeploymentStatus } from '../../../common/types/ml';
import { getMlModelDeploymentStatus } from './get_ml_model_deployment_status';
-import {
- isNotFoundExceptionError,
- throwIfNotAcceptableModelName,
-} from './ml_model_deployment_common';
+import { isNotFoundExceptionError } from './ml_model_deployment_common';
export const startMlModelDownload = async (
modelName: string,
@@ -24,10 +21,6 @@ export const startMlModelDownload = async (
throw new Error('Machine Learning is not enabled');
}
- // before anything else, check our model name
- // to ensure we only allow those names we want
- throwIfNotAcceptableModelName(modelName);
-
try {
// try and get the deployment status of the model first
// and see if it's already deployed or deploying...
diff --git a/x-pack/plugins/fleet/server/config.test.ts b/x-pack/plugins/fleet/server/config.test.ts
index da654244aae30..53fc0545efcf3 100644
--- a/x-pack/plugins/fleet/server/config.test.ts
+++ b/x-pack/plugins/fleet/server/config.test.ts
@@ -5,9 +5,18 @@
* 2.0.
*/
+import { loggingSystemMock } from '@kbn/core/server/mocks';
+
import { config } from './config';
+import { appContextService } from './services';
+
+jest.mock('./services/app_context');
describe('Config schema', () => {
+ beforeEach(() => {
+ const mockedLogger = loggingSystemMock.createLogger();
+ jest.mocked(appContextService.getLogger).mockReturnValue(mockedLogger);
+ });
it('should not allow to specify both default output in xpack.fleet.ouputs and xpack.fleet.agents.elasticsearch.hosts ', () => {
expect(() => {
config.schema.validate({
@@ -70,4 +79,26 @@ describe('Config schema', () => {
});
}).not.toThrow();
});
+
+ it('should log a warning when trying to enable a non existing experimental feature', () => {
+ expect(() => {
+ config.schema.validate({
+ enableExperimental: ['notvalid'],
+ });
+ }).not.toThrow();
+
+ expect(appContextService.getLogger().warn).toBeCalledWith(
+ '[notvalid] is not a valid fleet experimental feature.'
+ );
+ });
+
+ it('should not log a warning when enabling an existing experimental feature', () => {
+ expect(() => {
+ config.schema.validate({
+ enableExperimental: ['displayAgentMetrics'],
+ });
+ }).not.toThrow();
+
+ expect(appContextService.getLogger().warn).not.toBeCalled();
+ });
});
diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts
index e6d007c058b74..e5fec70fa37a7 100644
--- a/x-pack/plugins/fleet/server/config.ts
+++ b/x-pack/plugins/fleet/server/config.ts
@@ -11,11 +11,7 @@ import { schema } from '@kbn/config-schema';
import type { TypeOf } from '@kbn/config-schema';
import type { PluginConfigDescriptor } from '@kbn/core/server';
-import {
- getExperimentalAllowedValues,
- isValidExperimentalValue,
-} from '../common/experimental_features';
-const allowedExperimentalValues = getExperimentalAllowedValues();
+import { isValidExperimentalValue } from '../common/experimental_features';
import {
PreconfiguredPackagesSchema,
@@ -25,6 +21,7 @@ import {
PreconfiguredFleetProxiesSchema,
} from './types';
import { BULK_CREATE_MAX_ARTIFACTS_BYTES } from './services/artifacts/artifacts';
+import { appContextService } from './services';
const DEFAULT_BUNDLED_PACKAGE_LOCATION = path.join(__dirname, '../target/bundled_packages');
const DEFAULT_GPG_KEY_PATH = path.join(__dirname, '../target/keys/GPG-KEY-elasticsearch');
@@ -162,9 +159,9 @@ export const config: PluginConfigDescriptor = {
validate(list) {
for (const key of list) {
if (!isValidExperimentalValue(key)) {
- return `[${key}] is not allowed. Allowed values are: ${allowedExperimentalValues.join(
- ', '
- )}`;
+ appContextService
+ .getLogger()
+ .warn(`[${key}] is not a valid fleet experimental feature.`);
}
}
},
diff --git a/x-pack/plugins/fleet/server/integration_tests/cloud_preconfiguration.test.ts b/x-pack/plugins/fleet/server/integration_tests/cloud_preconfiguration.test.ts
index ae19383f37216..c080aefacd12e 100644
--- a/x-pack/plugins/fleet/server/integration_tests/cloud_preconfiguration.test.ts
+++ b/x-pack/plugins/fleet/server/integration_tests/cloud_preconfiguration.test.ts
@@ -30,7 +30,7 @@ import {
const logFilePath = Path.join(__dirname, 'logs.log');
-describe('Fleet preconfiguration reset', () => {
+describe('Fleet cloud preconfiguration', () => {
let esServer: TestElasticsearchUtils;
let kbnServer: TestKibanaUtils;
@@ -324,6 +324,9 @@ describe('Fleet preconfiguration reset', () => {
},
],
},
+ 'elastic-cloud-fleet-server': {
+ indices: [],
+ },
},
},
outputs: {
diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts
index deb8c0a313f30..984a88d87e61f 100644
--- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts
@@ -25,7 +25,7 @@ import {
buildComponentTemplates,
installComponentAndIndexTemplateForDataStream,
} from '../template/install';
-import { processFields } from '../../fields/field';
+import { isFields, processFields } from '../../fields/field';
import { generateMappings } from '../template/template';
import { getESAssetMetadata } from '../meta';
import { updateEsAssetReferences } from '../../packages/install';
@@ -162,7 +162,6 @@ const processTransformAssetsPerModule = (
installablePackage,
path
);
-
// Since there can be multiple assets per transform definition
// We want to create a unique list of assets/specifications for each transform
if (transformsSpecifications.get(transformModuleId) === undefined) {
@@ -172,7 +171,8 @@ const processTransformAssetsPerModule = (
const content = safeLoad(getAsset(path).toString('utf-8'));
- if (fileName === TRANSFORM_SPECS_TYPES.FIELDS) {
+ // Handling fields.yml and all other files within 'fields' folder
+ if (fileName === TRANSFORM_SPECS_TYPES.FIELDS || isFields(path)) {
const validFields = processFields(content);
const mappings = generateMappings(validFields);
const templateName = getTransformAssetNameForInstallation(
@@ -198,7 +198,14 @@ const processTransformAssetsPerModule = (
} else {
destinationIndexTemplates[indexToModify] = template;
}
- packageAssets?.set('mappings', mappings);
+
+ // If there's already mappings set previously, append it to new
+ const previousMappings =
+ transformsSpecifications.get(transformModuleId)?.get('mappings') ?? {};
+
+ transformsSpecifications.get(transformModuleId)?.set('mappings', {
+ properties: { ...previousMappings.properties, ...mappings.properties },
+ });
}
if (fileName === TRANSFORM_SPECS_TYPES.TRANSFORM) {
diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts
index e0c5a123c2421..6ab6f20669baf 100644
--- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts
@@ -107,7 +107,34 @@ _meta:
type: date
- name: updated_at
type: alias
- path: event.ingested`,
+ path: event.ingested
+- external: ecs
+ name: ecs.version
+- external: ecs
+ name: message`,
+ BEATS_FIELDS: `- name: input.type
+ type: keyword
+ description: Type of Filebeat input.
+- name: log.flags
+ type: keyword
+ description: Flags for the log file.
+- name: log.offset
+ type: long
+ description: Offset of the entry in the log file.
+- name: log.file.path
+ type: keyword
+ description: Path to the log file.`,
+ AGENT_FIELDS: `- name: instance.name
+ level: extended
+ type: keyword
+ ignore_above: 1024
+ description: Instance name of the host machine.
+- name: machine.type
+ level: extended
+ type: keyword
+ ignore_above: 1024
+ description: Machine type of the host machine.
+ example: t2.medium`,
};
};
const getExpectedData = (transformVersion: string) => {
@@ -210,6 +237,8 @@ _meta:
],
} as unknown as Installation;
(getAsset as jest.MockedFunction)
+ .mockReturnValueOnce(Buffer.from(sourceData.BEATS_FIELDS, 'utf8'))
+ .mockReturnValueOnce(Buffer.from(sourceData.AGENT_FIELDS, 'utf8'))
.mockReturnValueOnce(Buffer.from(sourceData.FIELDS, 'utf8'))
.mockReturnValueOnce(Buffer.from(sourceData.MANIFEST, 'utf8'))
.mockReturnValueOnce(Buffer.from(sourceData.TRANSFORM, 'utf8'));
@@ -247,6 +276,8 @@ _meta:
version: '0.16.0-dev.0',
} as unknown as RegistryPackage,
paths: [
+ 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/fields/beats.yml',
+ 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/fields/agent.yml',
'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/fields/fields.yml',
'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/manifest.yml',
'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/transform.yml',
@@ -300,10 +331,19 @@ _meta:
},
mappings: {
properties: {
- '@timestamp': {
- ignore_malformed: false,
- type: 'date',
+ input: { properties: { type: { type: 'keyword', ignore_above: 1024 } } },
+ log: {
+ properties: {
+ flags: { type: 'keyword', ignore_above: 1024 },
+ offset: { type: 'long' },
+ file: { properties: { path: { type: 'keyword', ignore_above: 1024 } } },
+ },
},
+ instance: { properties: { name: { type: 'keyword', ignore_above: 1024 } } },
+ machine: { properties: { type: { type: 'keyword', ignore_above: 1024 } } },
+ '@timestamp': { ignore_malformed: false, type: 'date' },
+ ecs: { properties: { version: { type: 'keyword', ignore_above: 1024 } } },
+ message: { type: 'keyword', ignore_above: 1024 },
},
dynamic_templates: [
{
@@ -582,6 +622,8 @@ _meta:
ignore_malformed: false,
type: 'date',
},
+ ecs: { properties: { version: { type: 'keyword', ignore_above: 1024 } } },
+ message: { type: 'keyword', ignore_above: 1024 },
},
dynamic_templates: [
{
@@ -852,6 +894,8 @@ _meta:
ignore_malformed: false,
type: 'date',
},
+ ecs: { properties: { version: { type: 'keyword', ignore_above: 1024 } } },
+ message: { type: 'keyword', ignore_above: 1024 },
},
},
},
diff --git a/x-pack/plugins/fleet/server/services/epm/fields/field.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.ts
index 27e566d2e4339..437eed7c64192 100644
--- a/x-pack/plugins/fleet/server/services/epm/fields/field.ts
+++ b/x-pack/plugins/fleet/server/services/epm/fields/field.ts
@@ -284,7 +284,7 @@ export function processFields(fields: Fields): Fields {
return validateFields(dedupedFields, dedupedFields);
}
-const isFields = (path: string) => {
+export const isFields = (path: string) => {
return path.includes('/fields/');
};
diff --git a/x-pack/plugins/graph/common/content_management/index.ts b/x-pack/plugins/graph/common/content_management/index.ts
index c848adbce1747..cdaef8264125d 100644
--- a/x-pack/plugins/graph/common/content_management/index.ts
+++ b/x-pack/plugins/graph/common/content_management/index.ts
@@ -26,6 +26,7 @@ export type {
GraphSearchIn,
GraphSearchOut,
GraphSearchQuery,
+ GraphCrudTypes,
} from './latest';
export * as GraphV1 from './v1';
diff --git a/x-pack/plugins/graph/common/content_management/v1/index.ts b/x-pack/plugins/graph/common/content_management/v1/index.ts
index 3ce273575aca7..2e2b8b9dd6950 100644
--- a/x-pack/plugins/graph/common/content_management/v1/index.ts
+++ b/x-pack/plugins/graph/common/content_management/v1/index.ts
@@ -22,5 +22,6 @@ export type {
GraphSearchIn,
GraphSearchOut,
GraphSearchQuery,
+ GraphCrudTypes,
Reference,
} from './types';
diff --git a/x-pack/plugins/graph/common/content_management/v1/types.ts b/x-pack/plugins/graph/common/content_management/v1/types.ts
index 51bb9017c38a9..c6664cf5a162c 100644
--- a/x-pack/plugins/graph/common/content_management/v1/types.ts
+++ b/x-pack/plugins/graph/common/content_management/v1/types.ts
@@ -17,6 +17,7 @@ import {
CreateResult,
UpdateResult,
} from '@kbn/content-management-plugin/common';
+import { ContentManagementCrudTypes } from '@kbn/content-management-utils';
import { GraphContentType } from '../types';
@@ -113,3 +114,13 @@ export interface GraphSearchQuery {
export type GraphSearchIn = SearchIn;
export type GraphSearchOut = SearchResult;
+
+// ----------- CRUD TYPES --------------
+
+export type GraphCrudTypes = ContentManagementCrudTypes<
+ GraphContentType,
+ GraphSavedObjectAttributes,
+ CreateOptions,
+ UpdateOptions,
+ {}
+>;
diff --git a/x-pack/plugins/graph/server/content_management/graph_storage.ts b/x-pack/plugins/graph/server/content_management/graph_storage.ts
index e0faea8c99b9c..6487f942b29d4 100644
--- a/x-pack/plugins/graph/server/content_management/graph_storage.ts
+++ b/x-pack/plugins/graph/server/content_management/graph_storage.ts
@@ -4,325 +4,37 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import Boom from '@hapi/boom';
-import type { SearchQuery } from '@kbn/content-management-plugin/common';
-import type { ContentStorage, StorageContext } from '@kbn/content-management-plugin/server';
-import type {
- SavedObject,
- SavedObjectReference,
- SavedObjectsFindOptions,
-} from '@kbn/core-saved-objects-api-server';
+import { Logger } from '@kbn/logging';
+import { SOContentStorage } from '@kbn/content-management-utils';
import { cmServicesDefinition } from '../../common/content_management/cm_services';
-import type {
- GraphSavedObjectAttributes,
- GraphSavedObject,
- PartialGraphSavedObject,
- GraphGetOut,
- GraphCreateIn,
- GraphCreateOut,
- CreateOptions,
- GraphUpdateIn,
- GraphUpdateOut,
- UpdateOptions,
- GraphDeleteOut,
- GraphSearchQuery,
- GraphSearchOut,
-} from '../../common/content_management';
-
-const savedObjectClientFromRequest = async (ctx: StorageContext) => {
- if (!ctx.requestHandlerContext) {
- throw new Error('Storage context.requestHandlerContext missing.');
- }
-
- const { savedObjects } = await ctx.requestHandlerContext.core;
- return savedObjects.client;
-};
-
-type PartialSavedObject = Omit>, 'references'> & {
- references: SavedObjectReference[] | undefined;
-};
-
-function savedObjectToGraphSavedObject(
- savedObject: SavedObject,
- partial: false
-): GraphSavedObject;
-
-function savedObjectToGraphSavedObject(
- savedObject: PartialSavedObject,
- partial: true
-): PartialGraphSavedObject;
-
-function savedObjectToGraphSavedObject(
- savedObject:
- | SavedObject
- | PartialSavedObject
-): GraphSavedObject | PartialGraphSavedObject {
- const {
- id,
- type,
- updated_at: updatedAt,
- created_at: createdAt,
- attributes: {
- title,
- description,
- version,
- kibanaSavedObjectMeta,
- wsState,
- numVertices,
- numLinks,
- legacyIndexPatternRef,
- },
- references,
- error,
- namespaces,
- } = savedObject;
-
- return {
- id,
- type,
- updatedAt,
- createdAt,
- attributes: {
- title,
- description,
- kibanaSavedObjectMeta,
- wsState,
- version,
- numLinks,
- numVertices,
- legacyIndexPatternRef,
- },
- references,
- error,
- namespaces,
- };
-}
+import type { GraphCrudTypes } from '../../common/content_management';
const SO_TYPE = 'graph-workspace';
-export class GraphStorage implements ContentStorage {
- constructor() {}
-
- async get(ctx: StorageContext, id: string): Promise {
- const {
- utils: { getTransforms },
- version: { request: requestVersion },
- } = ctx;
- const transforms = getTransforms(cmServicesDefinition, requestVersion);
- const soClient = await savedObjectClientFromRequest(ctx);
-
- // Save data in DB
- const {
- saved_object: savedObject,
- alias_purpose: aliasPurpose,
- alias_target_id: aliasTargetId,
- outcome,
- } = await soClient.resolve(SO_TYPE, id);
-
- const response: GraphGetOut = {
- item: savedObjectToGraphSavedObject(savedObject, false),
- meta: {
- aliasPurpose,
- aliasTargetId,
- outcome,
- },
- };
-
- // Validate DB response and DOWN transform to the request version
- const { value, error: resultError } = transforms.get.out.result.down(
- response
- );
-
- if (resultError) {
- throw Boom.badRequest(`Invalid response. ${resultError.message}`);
- }
-
- return value;
- }
-
- async bulkGet(): Promise {
- // Not implemented. Graph does not use bulkGet
- throw new Error(`[bulkGet] has not been implemented. See GraphStorage class.`);
- }
-
- async create(
- ctx: StorageContext,
- data: GraphCreateIn['data'],
- options: CreateOptions
- ): Promise {
- const {
- utils: { getTransforms },
- version: { request: requestVersion },
- } = ctx;
- const transforms = getTransforms(cmServicesDefinition, requestVersion);
-
- // Validate input (data & options) & UP transform them to the latest version
- const { value: dataToLatest, error: dataError } = transforms.create.in.data.up<
- GraphSavedObjectAttributes,
- GraphSavedObjectAttributes
- >(data);
- if (dataError) {
- throw Boom.badRequest(`Invalid data. ${dataError.message}`);
- }
-
- const { value: optionsToLatest, error: optionsError } = transforms.create.in.options.up<
- CreateOptions,
- CreateOptions
- >(options);
- if (optionsError) {
- throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
- }
-
- // Save data in DB
- const soClient = await savedObjectClientFromRequest(ctx);
- const savedObject = await soClient.create(
- SO_TYPE,
- dataToLatest,
- optionsToLatest
- );
-
- // Validate DB response and DOWN transform to the request version
- const { value, error: resultError } = transforms.create.out.result.down<
- GraphCreateOut,
- GraphCreateOut
- >({
- item: savedObjectToGraphSavedObject(savedObject, false),
+export class GraphStorage extends SOContentStorage {
+ constructor({
+ logger,
+ throwOnResultValidationError,
+ }: {
+ logger: Logger;
+ throwOnResultValidationError: boolean;
+ }) {
+ super({
+ savedObjectType: SO_TYPE,
+ cmServicesDefinition,
+ allowedSavedObjectAttributes: [
+ 'title',
+ 'description',
+ 'kibanaSavedObjectMeta',
+ 'wsState',
+ 'version',
+ 'numLinks',
+ 'numVertices',
+ 'legacyIndexPatternRef',
+ ],
+ logger,
+ throwOnResultValidationError,
});
-
- if (resultError) {
- throw Boom.badRequest(`Invalid response. ${resultError.message}`);
- }
-
- return value;
- }
-
- async update(
- ctx: StorageContext,
- id: string,
- data: GraphUpdateIn['data'],
- options: UpdateOptions
- ): Promise {
- const {
- utils: { getTransforms },
- version: { request: requestVersion },
- } = ctx;
- const transforms = getTransforms(cmServicesDefinition, requestVersion);
-
- // Validate input (data & options) & UP transform them to the latest version
- const { value: dataToLatest, error: dataError } = transforms.update.in.data.up<
- GraphSavedObjectAttributes,
- GraphSavedObjectAttributes
- >(data);
- if (dataError) {
- throw Boom.badRequest(`Invalid data. ${dataError.message}`);
- }
-
- const { value: optionsToLatest, error: optionsError } = transforms.update.in.options.up<
- CreateOptions,
- CreateOptions
- >(options);
- if (optionsError) {
- throw Boom.badRequest(`Invalid options. ${optionsError.message}`);
- }
-
- // Save data in DB
- const soClient = await savedObjectClientFromRequest(ctx);
- const partialSavedObject = await soClient.update(
- SO_TYPE,
- id,
- dataToLatest,
- optionsToLatest
- );
-
- // Validate DB response and DOWN transform to the request version
- const { value, error: resultError } = transforms.update.out.result.down<
- GraphUpdateOut,
- GraphUpdateOut
- >({
- item: savedObjectToGraphSavedObject(partialSavedObject, true),
- });
-
- if (resultError) {
- throw Boom.badRequest(`Invalid response. ${resultError.message}`);
- }
-
- return value;
- }
-
- async delete(ctx: StorageContext, id: string): Promise {
- const soClient = await savedObjectClientFromRequest(ctx);
- await soClient.delete(SO_TYPE, id);
- return { success: true };
- }
-
- async search(
- ctx: StorageContext,
- query: SearchQuery,
- options: GraphSearchQuery = {}
- ): Promise {
- const {
- utils: { getTransforms },
- version: { request: requestVersion },
- } = ctx;
- const transforms = getTransforms(cmServicesDefinition, requestVersion);
- const soClient = await savedObjectClientFromRequest(ctx);
-
- // Validate and UP transform the options
- const { value: optionsToLatest, error: optionsError } = transforms.search.in.options.up<
- GraphSearchQuery,
- GraphSearchQuery
- >(options);
- if (optionsError) {
- throw Boom.badRequest(`Invalid payload. ${optionsError.message}`);
- }
- const { searchFields = ['title^3', 'description'], types = ['graph-workspace'] } =
- optionsToLatest;
-
- const { included, excluded } = query.tags ?? {};
- const hasReference: SavedObjectsFindOptions['hasReference'] = included
- ? included.map((id) => ({
- id,
- type: 'tag',
- }))
- : undefined;
-
- const hasNoReference: SavedObjectsFindOptions['hasNoReference'] = excluded
- ? excluded.map((id) => ({
- id,
- type: 'tag',
- }))
- : undefined;
-
- const soQuery: SavedObjectsFindOptions = {
- type: types,
- search: query.text,
- perPage: query.limit,
- page: query.cursor ? Number(query.cursor) : undefined,
- defaultSearchOperator: 'AND',
- searchFields,
- hasReference,
- hasNoReference,
- };
-
- // Execute the query in the DB
- const response = await soClient.find(soQuery);
-
- // Validate the response and DOWN transform to the request version
- const { value, error: resultError } = transforms.search.out.result.down<
- GraphSearchOut,
- GraphSearchOut
- >({
- hits: response.saved_objects.map((so) => savedObjectToGraphSavedObject(so, false)),
- pagination: {
- total: response.total,
- },
- });
-
- if (resultError) {
- throw Boom.badRequest(`Invalid response. ${resultError.message}`);
- }
-
- return value;
}
}
diff --git a/x-pack/plugins/graph/server/index.ts b/x-pack/plugins/graph/server/index.ts
index 886c8e4267cb6..86dca6c119604 100644
--- a/x-pack/plugins/graph/server/index.ts
+++ b/x-pack/plugins/graph/server/index.ts
@@ -5,12 +5,13 @@
* 2.0.
*/
-import { PluginConfigDescriptor } from '@kbn/core/server';
+import { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server';
import { configSchema, ConfigSchema } from '../config';
import { GraphPlugin } from './plugin';
-export const plugin = () => new GraphPlugin();
+export const plugin = (initializerContext: PluginInitializerContext) =>
+ new GraphPlugin(initializerContext);
export const config: PluginConfigDescriptor = {
exposeToBrowser: {
diff --git a/x-pack/plugins/graph/server/plugin.ts b/x-pack/plugins/graph/server/plugin.ts
index db33a04c6a0bf..88e45bc007e47 100644
--- a/x-pack/plugins/graph/server/plugin.ts
+++ b/x-pack/plugins/graph/server/plugin.ts
@@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { Plugin, CoreSetup, CoreStart } from '@kbn/core/server';
+import { Plugin, CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/server';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server';
import { HomeServerPluginSetup } from '@kbn/home-plugin/server';
@@ -23,6 +23,8 @@ import { GraphStorage } from './content_management/graph_storage';
export class GraphPlugin implements Plugin {
private licenseState: LicenseState | null = null;
+ constructor(private readonly initializerContext: PluginInitializerContext) {}
+
public setup(
core: CoreSetup,
{
@@ -45,7 +47,10 @@ export class GraphPlugin implements Plugin {
contentManagement.register({
id: CONTENT_ID,
- storage: new GraphStorage(),
+ storage: new GraphStorage({
+ throwOnResultValidationError: this.initializerContext.env.mode.dev,
+ logger: this.initializerContext.logger.get(),
+ }),
version: {
latest: LATEST_VERSION,
},
diff --git a/x-pack/plugins/graph/tsconfig.json b/x-pack/plugins/graph/tsconfig.json
index 1e8059c99c5d7..1ab9c265359a9 100644
--- a/x-pack/plugins/graph/tsconfig.json
+++ b/x-pack/plugins/graph/tsconfig.json
@@ -41,12 +41,13 @@
"@kbn/saved-objects-finder-plugin",
"@kbn/core-saved-objects-server",
"@kbn/content-management-plugin",
- "@kbn/core-saved-objects-api-server",
"@kbn/object-versioning",
"@kbn/content-management-table-list-view-table",
"@kbn/content-management-table-list-view",
"@kbn/core-ui-settings-browser",
"@kbn/react-kibana-mount",
+ "@kbn/content-management-utils",
+ "@kbn/logging",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
index b6656c0dae354..10e94bb2f718c 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
@@ -102,4 +102,5 @@ export type TestSubjects =
| 'filter-option-h'
| 'infiniteRetentionPeriod.input'
| 'saveButton'
+ | 'dataRetentionDetail'
| 'createIndexSaveButton';
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
index 6629502498c74..3a6add88c2840 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
@@ -278,6 +278,7 @@ export const createDataStreamPayload = (dataStream: Partial): DataSt
},
hidden: false,
lifecycle: {
+ enabled: true,
data_retention: '7d',
},
...dataStream,
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
index c40823509d640..bbb8d924fcd02 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -338,6 +338,55 @@ describe('Data Streams tab', () => {
});
describe('update data retention', () => {
+ test('Should show disabled or infinite retention period accordingly in table and flyout', async () => {
+ const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;
+
+ const ds1 = createDataStreamPayload({
+ name: 'dataStream1',
+ lifecycle: {
+ enabled: false,
+ },
+ });
+ const ds2 = createDataStreamPayload({
+ name: 'dataStream2',
+ lifecycle: {
+ enabled: true,
+ },
+ });
+
+ setLoadDataStreamsResponse([ds1, ds2]);
+ setLoadDataStreamResponse(ds1.name, ds1);
+
+ testBed = await setup(httpSetup, {
+ history: createMemoryHistory(),
+ url: urlServiceMock,
+ });
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+ testBed.component.update();
+
+ const { actions, find, table } = testBed;
+ const { tableCellsValues } = table.getMetaData('dataStreamTable');
+
+ expect(tableCellsValues).toEqual([
+ ['', 'dataStream1', 'green', '1', 'Disabled', 'Delete'],
+ ['', 'dataStream2', 'green', '1', '', 'Delete'],
+ ]);
+
+ await actions.clickNameAt(0);
+ expect(find('dataRetentionDetail').text()).toBe('Disabled');
+
+ await act(async () => {
+ testBed.find('closeDetailsButton').simulate('click');
+ });
+ testBed.component.update();
+
+ setLoadDataStreamResponse(ds2.name, ds2);
+ await actions.clickNameAt(1);
+ expect(find('dataRetentionDetail').text()).toBe('Keep data indefinitely');
+ });
+
test('can set data retention period', async () => {
const {
actions: { clickNameAt, clickEditDataRetentionButton },
diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts
index 8e2e5c3368ac3..f0bd12d96fde5 100644
--- a/x-pack/plugins/index_management/common/types/data_streams.ts
+++ b/x-pack/plugins/index_management/common/types/data_streams.ts
@@ -59,7 +59,9 @@ export interface DataStream {
_meta?: Metadata;
privileges: Privileges;
hidden: boolean;
- lifecycle?: IndicesDataLifecycleWithRollover;
+ lifecycle?: IndicesDataLifecycleWithRollover & {
+ enabled?: boolean;
+ };
}
export interface DataStreamIndex {
diff --git a/x-pack/plugins/index_management/public/application/lib/data_streams.test.tsx b/x-pack/plugins/index_management/public/application/lib/data_streams.test.tsx
new file mode 100644
index 0000000000000..a15cbaefbd0fa
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/lib/data_streams.test.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { getLifecycleValue } from './data_streams';
+
+describe('Data stream helpers', () => {
+ describe('getLifecycleValue', () => {
+ it('Knows when it should be marked as disabled', () => {
+ expect(
+ getLifecycleValue({
+ enabled: false,
+ })
+ ).toBe('Disabled');
+
+ expect(getLifecycleValue()).toBe('Disabled');
+ });
+
+ it('knows when it should be marked as infinite', () => {
+ expect(
+ getLifecycleValue({
+ enabled: true,
+ })
+ ).toBe('Keep data indefinitely');
+ });
+
+ it('knows when it has a defined data retention period', () => {
+ expect(
+ getLifecycleValue({
+ enabled: true,
+ data_retention: '2d',
+ })
+ ).toBe('2d');
+ });
+ });
+});
diff --git a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx
index 8cf45050855b0..ad068dde91a22 100644
--- a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx
+++ b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx
@@ -5,6 +5,10 @@
* 2.0.
*/
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiIcon, EuiToolTip } from '@elastic/eui';
+
import { DataStream } from '../../../common';
export const isFleetManaged = (dataStream: DataStream): boolean => {
@@ -38,3 +42,37 @@ export const isSelectedDataStreamHidden = (
?.hidden
);
};
+
+export const getLifecycleValue = (
+ lifecycle?: DataStream['lifecycle'],
+ inifniteAsIcon?: boolean
+) => {
+ if (!lifecycle?.enabled) {
+ return i18n.translate('xpack.idxMgmt.dataStreamList.dataRetentionDisabled', {
+ defaultMessage: 'Disabled',
+ });
+ } else if (!lifecycle?.data_retention) {
+ const infiniteDataRetention = i18n.translate(
+ 'xpack.idxMgmt.dataStreamList.dataRetentionInfinite',
+ {
+ defaultMessage: 'Keep data indefinitely',
+ }
+ );
+
+ if (inifniteAsIcon) {
+ return (
+
+
+
+ );
+ }
+
+ return infiniteDataRetention;
+ }
+
+ return lifecycle?.data_retention;
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
index d04976d157406..96449e6de5238 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
@@ -30,6 +30,7 @@ import {
} from '@elastic/eui';
import { DiscoverLink } from '../../../../lib/discover_link';
+import { getLifecycleValue } from '../../../../lib/data_streams';
import { SectionLoading, reactRouterNavigate } from '../../../../../shared_imports';
import { SectionError, Error, DataHealth } from '../../../../components';
import { useLoadDataStream } from '../../../../services/api';
@@ -147,19 +148,6 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
const getManagementDetails = () => {
const managementDetails = [];
- if (lifecycle?.data_retention) {
- managementDetails.push({
- name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionTitle', {
- defaultMessage: 'Data retention',
- }),
- toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionToolTip', {
- defaultMessage: 'The amount of time to retain the data in the data stream.',
- }),
- content: lifecycle.data_retention,
- dataTestSubj: 'dataRetentionDetail',
- });
- }
-
if (ilmPolicyName) {
managementDetails.push({
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', {
@@ -278,6 +266,16 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
),
dataTestSubj: 'indexTemplateDetail',
},
+ {
+ name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionTitle', {
+ defaultMessage: 'Data retention',
+ }),
+ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionToolTip', {
+ defaultMessage: 'The amount of time to retain the data in the data stream.',
+ }),
+ content: getLifecycleValue(lifecycle),
+ dataTestSubj: 'dataRetentionDetail',
+ },
];
const managementDetails = getManagementDetails();
@@ -376,7 +374,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
}
}}
dataStreamName={dataStreamName}
- dataRetention={dataStream?.lifecycle?.data_retention as string}
+ lifecycle={dataStream?.lifecycle}
/>
)}
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
index 485f1db9c06c9..0bf6aa68347de 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
@@ -19,6 +19,7 @@ import {
import { ScopedHistory } from '@kbn/core/public';
import { DataStream } from '../../../../../../common/types';
+import { getLifecycleValue } from '../../../../lib/data_streams';
import { UseRequestResponse, reactRouterNavigate } from '../../../../../shared_imports';
import { getDataStreamDetailsLink, getIndexListUri } from '../../../../services/routing';
import { DataHealth } from '../../../../components';
@@ -34,6 +35,8 @@ interface Props {
filters?: string;
}
+const INFINITE_AS_ICON = true;
+
export const DataStreamTable: React.FunctionComponent = ({
dataStreams,
reload,
@@ -144,7 +147,7 @@ export const DataStreamTable: React.FunctionComponent = ({
),
truncateText: true,
sortable: true,
- render: (lifecycle: DataStream['lifecycle']) => lifecycle?.data_retention,
+ render: (lifecycle: DataStream['lifecycle']) => getLifecycleValue(lifecycle, INFINITE_AS_ICON),
});
columns.push({
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx
index e33aaae7a9073..11e51a83b8e99 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx
@@ -35,13 +35,13 @@ import {
} from '../../../../../shared_imports';
import { documentationService } from '../../../../services/documentation';
-import { splitSizeAndUnits } from '../../../../../../common';
+import { splitSizeAndUnits, DataStream } from '../../../../../../common';
import { useAppContext } from '../../../../app_context';
import { UnitField } from './unit_field';
import { updateDataRetention } from '../../../../services/api';
interface Props {
- dataRetention: string;
+ lifecycle: DataStream['lifecycle'];
dataStreamName: string;
onClose: (data?: { hasUpdatedDataRetention: boolean }) => void;
}
@@ -83,33 +83,6 @@ export const timeUnits = [
}
),
},
- {
- value: 'ms',
- text: i18n.translate(
- 'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.timeUnits.millisecondsLabel',
- {
- defaultMessage: 'milliseconds',
- }
- ),
- },
- {
- value: 'micros',
- text: i18n.translate(
- 'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.timeUnits.microsecondsLabel',
- {
- defaultMessage: 'microseconds',
- }
- ),
- },
- {
- value: 'nanos',
- text: i18n.translate(
- 'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.timeUnits.nanosecondsLabel',
- {
- defaultMessage: 'nanoseconds',
- }
- ),
- },
];
const configurationFormSchema: FormSchema = {
@@ -124,7 +97,12 @@ const configurationFormSchema: FormSchema = {
formatters: [fieldFormatters.toInt],
validations: [
{
- validator: ({ value }) => {
+ validator: ({ value, formData }) => {
+ // If infiniteRetentionPeriod is set, we dont need to validate the data retention field
+ if (formData.infiniteRetentionPeriod) {
+ return undefined;
+ }
+
if (!value) {
return {
message: i18n.translate(
@@ -171,11 +149,11 @@ const configurationFormSchema: FormSchema = {
};
export const EditDataRetentionModal: React.FunctionComponent = ({
- dataRetention,
+ lifecycle,
dataStreamName,
onClose,
}) => {
- const { size, unit } = splitSizeAndUnits(dataRetention);
+ const { size, unit } = splitSizeAndUnits(lifecycle?.data_retention as string);
const {
services: { notificationService },
} = useAppContext();
@@ -184,7 +162,10 @@ export const EditDataRetentionModal: React.FunctionComponent = ({
defaultValue: {
dataRetention: size,
timeUnit: unit || 'd',
- infiniteRetentionPeriod: !dataRetention,
+ // When data retention is not set and lifecycle is enabled, is the only scenario in
+ // which data retention will be infinite. If lifecycle isnt set or is not enabled, we
+ // dont have inifinite data retention.
+ infiniteRetentionPeriod: lifecycle?.enabled && !lifecycle?.data_retention,
},
schema: configurationFormSchema,
id: 'editDataRetentionForm',
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts
index 838efe28580c0..9904f7cbb70c7 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/languages.ts
@@ -69,10 +69,10 @@ export const goDefinition: LanguageDefinition = {
"fmt"
"log"
"strings"
-
+
"github.com/elastic/elasticsearch-serverless-go"
)
-
+
func main() {
cfg := elasticsearch.Config{
Address: "${url}",
@@ -88,7 +88,7 @@ func main() {
{ "index": { "_id": "1"}}
{"name": "foo", "title": "bar"}\n\`)).
Do(context.Background())
-
+
fmt.Println(res, err)
}`,
};
diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts
index 151c804bf413f..7ca0e9e64ca34 100644
--- a/x-pack/plugins/infra/common/alerting/metrics/types.ts
+++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts
@@ -12,18 +12,15 @@ import { InventoryItemType, SnapshotMetricType } from '../../inventory_models/ty
export const METRIC_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.threshold';
export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold';
-export const METRIC_ANOMALY_ALERT_TYPE_ID = 'metrics.alert.anomaly';
export enum InfraRuleType {
MetricThreshold = 'metrics.alert.threshold',
InventoryThreshold = 'metrics.alert.inventory.threshold',
- Anomaly = 'metrics.alert.anomaly',
}
export interface InfraRuleTypeParams {
[InfraRuleType.MetricThreshold]: MetricThresholdParams;
[InfraRuleType.InventoryThreshold]: InventoryMetricConditions;
- [InfraRuleType.Anomaly]: MetricAnomalyParams;
}
export enum Comparator {
diff --git a/x-pack/plugins/infra/common/plugin_config_types.ts b/x-pack/plugins/infra/common/plugin_config_types.ts
index dd915e39cdec0..c5fada95e5a83 100644
--- a/x-pack/plugins/infra/common/plugin_config_types.ts
+++ b/x-pack/plugins/infra/common/plugin_config_types.ts
@@ -30,6 +30,9 @@ export interface InfraConfig {
logsUIEnabled: boolean;
metricsExplorerEnabled: boolean;
osqueryEnabled: boolean;
+ inventoryThresholdAlertRuleEnabled: boolean;
+ metricThresholdAlertRuleEnabled: boolean;
+ logThresholdAlertRuleEnabled: boolean;
};
}
diff --git a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx
index e80da08fc082e..e4284d154c104 100644
--- a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx
+++ b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx
@@ -7,11 +7,10 @@
import { i18n } from '@kbn/i18n';
import React, { useState, useCallback, useMemo } from 'react';
-import {
- EuiPopover,
- EuiHeaderLink,
- EuiContextMenu,
+import { EuiPopover, EuiHeaderLink, EuiContextMenu } from '@elastic/eui';
+import type {
EuiContextMenuPanelDescriptor,
+ EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
@@ -23,6 +22,118 @@ import { InfraClientStartDeps } from '../../../types';
type VisibleFlyoutType = 'inventory' | 'metricThreshold' | 'customThreshold';
+interface ContextMenuEntries {
+ items: EuiContextMenuPanelItemDescriptor[];
+ panels: EuiContextMenuPanelDescriptor[];
+}
+
+function useInfrastructureMenu(
+ onCreateRuleClick: (flyoutType: VisibleFlyoutType) => void
+): ContextMenuEntries {
+ const { featureFlags } = usePluginConfig();
+
+ return useMemo(() => {
+ if (!featureFlags.inventoryThresholdAlertRuleEnabled) {
+ return { items: [], panels: [] };
+ }
+
+ return {
+ items: [
+ {
+ 'data-test-subj': 'inventory-alerts-menu-option',
+ name: i18n.translate('xpack.infra.alerting.infrastructureDropdownMenu', {
+ defaultMessage: 'Infrastructure',
+ }),
+ panel: 1,
+ },
+ ],
+ panels: [
+ {
+ id: 1,
+ title: i18n.translate('xpack.infra.alerting.infrastructureDropdownTitle', {
+ defaultMessage: 'Infrastructure rules',
+ }),
+ items: [
+ {
+ 'data-test-subj': 'inventory-alerts-create-rule',
+ name: i18n.translate('xpack.infra.alerting.createInventoryRuleButton', {
+ defaultMessage: 'Create inventory rule',
+ }),
+ onClick: () => onCreateRuleClick('inventory'),
+ },
+ ],
+ },
+ ],
+ };
+ }, [featureFlags.inventoryThresholdAlertRuleEnabled, onCreateRuleClick]);
+}
+
+function useMetricsMenu(
+ onCreateRuleClick: (flyoutType: VisibleFlyoutType) => void
+): ContextMenuEntries {
+ const { featureFlags } = usePluginConfig();
+
+ return useMemo(() => {
+ if (!featureFlags.metricThresholdAlertRuleEnabled) {
+ return { items: [], panels: [] };
+ }
+
+ return {
+ items: [
+ {
+ 'data-test-subj': 'metrics-threshold-alerts-menu-option',
+ name: i18n.translate('xpack.infra.alerting.metricsDropdownMenu', {
+ defaultMessage: 'Metrics',
+ }),
+ panel: 2,
+ },
+ ],
+ panels: [
+ {
+ id: 2,
+ title: i18n.translate('xpack.infra.alerting.metricsDropdownTitle', {
+ defaultMessage: 'Metrics rules',
+ }),
+ items: [
+ {
+ 'data-test-subj': 'metrics-threshold-alerts-create-rule',
+ name: i18n.translate('xpack.infra.alerting.createThresholdRuleButton', {
+ defaultMessage: 'Create threshold rule',
+ }),
+ onClick: () => onCreateRuleClick('metricThreshold'),
+ },
+ ],
+ },
+ ],
+ };
+ }, [featureFlags.metricThresholdAlertRuleEnabled, onCreateRuleClick]);
+}
+
+function useCustomThresholdMenu(
+ onCreateRuleClick: (flyoutType: VisibleFlyoutType) => void
+): ContextMenuEntries {
+ const { featureFlags } = usePluginConfig();
+
+ return useMemo(() => {
+ if (!featureFlags.customThresholdAlertsEnabled) {
+ return { items: [], panels: [] };
+ }
+
+ return {
+ items: [
+ {
+ 'data-test-subj': 'custom-threshold-alerts-menu-option',
+ name: i18n.translate('xpack.infra.alerting.customThresholdDropdownMenu', {
+ defaultMessage: 'Create custom threshold rule',
+ }),
+ onClick: () => onCreateRuleClick('customThreshold'),
+ },
+ ],
+ panels: [],
+ };
+ }, [featureFlags.customThresholdAlertsEnabled, onCreateRuleClick]);
+}
+
export const MetricsAlertDropdown = () => {
const [popoverOpen, setPopoverOpen] = useState(false);
const [visibleFlyoutType, setVisibleFlyoutType] = useState(null);
@@ -34,8 +145,6 @@ export const MetricsAlertDropdown = () => {
() => Boolean(uiCapabilities?.infrastructure?.save),
[uiCapabilities]
);
- const { featureFlags } = usePluginConfig();
-
const closeFlyout = useCallback(() => setVisibleFlyoutType(null), [setVisibleFlyoutType]);
const closePopover = useCallback(() => {
@@ -45,50 +154,19 @@ export const MetricsAlertDropdown = () => {
const togglePopover = useCallback(() => {
setPopoverOpen(!popoverOpen);
}, [setPopoverOpen, popoverOpen]);
- const infrastructureAlertsPanel = useMemo(
- () => ({
- id: 1,
- title: i18n.translate('xpack.infra.alerting.infrastructureDropdownTitle', {
- defaultMessage: 'Infrastructure rules',
- }),
- items: [
- {
- 'data-test-subj': 'inventory-alerts-create-rule',
- name: i18n.translate('xpack.infra.alerting.createInventoryRuleButton', {
- defaultMessage: 'Create inventory rule',
- }),
- onClick: () => {
- closePopover();
- setVisibleFlyoutType('inventory');
- },
- },
- ],
- }),
- [setVisibleFlyoutType, closePopover]
- );
- const metricsAlertsPanel = useMemo(
- () => ({
- id: 2,
- title: i18n.translate('xpack.infra.alerting.metricsDropdownTitle', {
- defaultMessage: 'Metrics rules',
- }),
- items: [
- {
- 'data-test-subj': 'metrics-threshold-alerts-create-rule',
- name: i18n.translate('xpack.infra.alerting.createThresholdRuleButton', {
- defaultMessage: 'Create threshold rule',
- }),
- onClick: () => {
- closePopover();
- setVisibleFlyoutType('metricThreshold');
- },
- },
- ],
- }),
- [setVisibleFlyoutType, closePopover]
+ const onCreateRuleClick = useCallback(
+ (flyoutType: VisibleFlyoutType) => {
+ closePopover();
+ setVisibleFlyoutType(flyoutType);
+ },
+ [closePopover]
);
+ const infrastructureMenu = useInfrastructureMenu(onCreateRuleClick);
+ const metricsMenu = useMetricsMenu(onCreateRuleClick);
+ const customThresholdMenu = useCustomThresholdMenu(onCreateRuleClick);
+
const manageRulesLinkProps = observability.useRulesLink();
const manageAlertsMenuItem = useMemo(
@@ -102,56 +180,27 @@ export const MetricsAlertDropdown = () => {
[manageRulesLinkProps]
);
- const firstPanelMenuItems: EuiContextMenuPanelDescriptor['items'] = useMemo(
- () =>
- canCreateAlerts
- ? [
- {
- 'data-test-subj': 'inventory-alerts-menu-option',
- name: i18n.translate('xpack.infra.alerting.infrastructureDropdownMenu', {
- defaultMessage: 'Infrastructure',
- }),
- panel: 1,
- },
- {
- 'data-test-subj': 'metrics-threshold-alerts-menu-option',
- name: i18n.translate('xpack.infra.alerting.metricsDropdownMenu', {
- defaultMessage: 'Metrics',
- }),
- panel: 2,
- },
- ...(featureFlags.customThresholdAlertsEnabled
- ? [
- {
- 'data-test-subj': 'custom-threshold-alerts-menu-option',
- name: i18n.translate('xpack.infra.alerting.customThresholdDropdownMenu', {
- defaultMessage: 'Create custom threshold rule',
- }),
- onClick: () => {
- closePopover();
- setVisibleFlyoutType('customThreshold');
- },
- },
- ]
- : []),
- manageAlertsMenuItem,
- ]
- : [manageAlertsMenuItem],
- [canCreateAlerts, closePopover, featureFlags.customThresholdAlertsEnabled, manageAlertsMenuItem]
- );
-
const panels: EuiContextMenuPanelDescriptor[] = useMemo(
- () =>
- [
- {
- id: 0,
- title: i18n.translate('xpack.infra.alerting.alertDropdownTitle', {
- defaultMessage: 'Alerts and rules',
- }),
- items: firstPanelMenuItems,
- },
- ].concat(canCreateAlerts ? [infrastructureAlertsPanel, metricsAlertsPanel] : []),
- [infrastructureAlertsPanel, metricsAlertsPanel, firstPanelMenuItems, canCreateAlerts]
+ () => [
+ {
+ id: 0,
+ title: i18n.translate('xpack.infra.alerting.alertDropdownTitle', {
+ defaultMessage: 'Alerts and rules',
+ }),
+ items: canCreateAlerts
+ ? [
+ ...infrastructureMenu.items,
+ ...metricsMenu.items,
+ ...customThresholdMenu.items,
+ manageAlertsMenuItem,
+ ]
+ : [manageAlertsMenuItem],
+ },
+ ...(canCreateAlerts
+ ? [...infrastructureMenu.panels, ...metricsMenu.panels, ...customThresholdMenu.panels]
+ : []),
+ ],
+ [canCreateAlerts, infrastructureMenu, metricsMenu, customThresholdMenu, manageAlertsMenuItem]
);
return (
diff --git a/x-pack/plugins/infra/public/alerting/custom_threshold/components/alert_flyout.tsx b/x-pack/plugins/infra/public/alerting/custom_threshold/components/alert_flyout.tsx
index 3e064d26159e4..19e60e43fd877 100644
--- a/x-pack/plugins/infra/public/alerting/custom_threshold/components/alert_flyout.tsx
+++ b/x-pack/plugins/infra/public/alerting/custom_threshold/components/alert_flyout.tsx
@@ -27,6 +27,15 @@ export function AlertFlyout({ onClose }: Props) {
onClose,
canChangeTrigger: false,
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
+ metadata: {
+ currentOptions: {
+ /*
+ Setting the groupBy is currently required in custom threshold
+ rule for it to populate the rule with additional host context.
+ */
+ groupBy: 'host.name',
+ },
+ },
});
}, [onClose, triggersActionsUI]);
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/alert_flyout.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/alert_flyout.tsx
deleted file mode 100644
index a2b0284708459..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/alert_flyout.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useCallback, useContext, useMemo } from 'react';
-
-import { TriggerActionsContext } from '../../../utils/triggers_actions_context';
-import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../../common/alerting/metrics';
-import { InfraWaffleMapOptions } from '../../../lib/lib';
-import { InventoryItemType } from '../../../../common/inventory_models/types';
-import { useAlertPrefillContext } from '../../use_alert_prefill';
-
-interface Props {
- visible?: boolean;
- metric?: InfraWaffleMapOptions['metric'];
- nodeType?: InventoryItemType;
- filter?: string;
- setVisible(val: boolean): void;
-}
-
-export const AlertFlyout = ({ metric, nodeType, visible, setVisible }: Props) => {
- const { triggersActionsUI } = useContext(TriggerActionsContext);
-
- const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]);
- const AddAlertFlyout = useMemo(
- () =>
- triggersActionsUI &&
- triggersActionsUI.getAddRuleFlyout({
- consumer: 'infrastructure',
- onClose: onCloseFlyout,
- canChangeTrigger: false,
- ruleTypeId: METRIC_ANOMALY_ALERT_TYPE_ID,
- metadata: {
- metric,
- nodeType,
- },
- }),
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [triggersActionsUI, visible]
- );
-
- return <>{visible && AddAlertFlyout}>;
-};
-
-export const PrefilledAnomalyAlertFlyout = ({ onClose }: { onClose(): void }) => {
- const { inventoryPrefill } = useAlertPrefillContext();
- const { nodeType, metric } = inventoryPrefill;
-
- return ;
-};
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx
deleted file mode 100644
index de3fcd03df675..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
-// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock`
-import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
-import React from 'react';
-import { Expression, AlertContextMeta } from './expression';
-import { act } from 'react-dom/test-utils';
-import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
-
-jest.mock('../../../containers/metrics_source/source', () => ({
- withSourceProvider: () => jest.fn,
- useSourceContext: () => ({
- source: { id: 'default' },
- createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }),
- }),
-}));
-
-jest.mock('../../../hooks/use_kibana', () => ({
- useKibanaContextForPlugin: () => ({
- services: mockCoreMock.createStart(),
- }),
-}));
-
-jest.mock('../../../hooks/use_kibana_space', () => ({
- useActiveKibanaSpace: () => ({
- space: { id: 'default' },
- }),
-}));
-
-jest.mock('../../../containers/ml/infra_ml_capabilities', () => ({
- useInfraMLCapabilities: () => ({
- isLoading: false,
- hasInfraMLCapabilities: true,
- }),
-}));
-
-const dataViewMock = dataViewPluginMocks.createStartContract();
-
-describe('Expression', () => {
- async function setup(currentOptions: AlertContextMeta) {
- const ruleParams = {
- metric: undefined,
- nodeType: undefined,
- threshold: 50,
- };
- const wrapper = mountWithIntl(
- Reflect.set(ruleParams, key, value)}
- setRuleProperty={() => {}}
- metadata={currentOptions}
- dataViews={dataViewMock}
- />
- );
-
- const update = async () =>
- await act(async () => {
- await nextTick();
- wrapper.update();
- });
-
- await update();
-
- return { wrapper, update, ruleParams };
- }
-
- it('should prefill the alert using the context metadata', async () => {
- const currentOptions = {
- nodeType: 'pod',
- metric: { type: 'tx' },
- };
- const { ruleParams } = await setup(currentOptions as AlertContextMeta);
- expect(ruleParams.nodeType).toBe('k8s');
- expect(ruleParams.metric).toBe('network_out');
- });
-});
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx
deleted file mode 100644
index 76dbac0b8821b..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiFlexGroup, EuiSkeletonText, EuiSpacer, EuiText } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n-react';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { euiStyled, EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
-import {
- RuleTypeParams,
- RuleTypeParamsExpressionProps,
- WhenExpression,
-} from '@kbn/triggers-actions-ui-plugin/public';
-import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold';
-import { useSourceContext, withSourceProvider } from '../../../containers/metrics_source';
-import { MetricAnomalyParams } from '../../../../common/alerting/metrics';
-import { findInventoryModel } from '../../../../common/inventory_models';
-import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
-import { SubscriptionSplashPrompt } from '../../../components/subscription_splash_content';
-import { useInfraMLCapabilities } from '../../../containers/ml/infra_ml_capabilities';
-import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space';
-import { InfraWaffleMapOptions } from '../../../lib/lib';
-import { InfluencerFilter } from './influencer_filter';
-import { NodeTypeExpression } from './node_type';
-import { SeverityThresholdExpression } from './severity_threshold';
-
-export interface AlertContextMeta {
- metric?: InfraWaffleMapOptions['metric'];
- nodeType?: InventoryItemType;
-}
-
-type AlertParams = RuleTypeParams &
- MetricAnomalyParams & { sourceId: string; spaceId: string; hasInfraMLCapabilities: boolean };
-
-type Props = Omit<
- RuleTypeParamsExpressionProps,
- 'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' | 'onChangeMetaData'
->;
-
-export const defaultExpression = {
- metric: 'memory_usage' as MetricAnomalyParams['metric'],
- threshold: ML_ANOMALY_THRESHOLD.MAJOR as MetricAnomalyParams['threshold'],
- nodeType: 'hosts' as MetricAnomalyParams['nodeType'],
- influencerFilter: undefined,
-};
-
-export const Expression: React.FC = (props) => {
- const { hasInfraMLCapabilities, isLoading: isLoadingMLCapabilities } = useInfraMLCapabilities();
- const { space } = useActiveKibanaSpace();
-
- const { setRuleParams, ruleParams, ruleInterval, metadata } = props;
- const { source, createDerivedIndexPattern } = useSourceContext();
-
- const derivedIndexPattern = useMemo(
- () => createDerivedIndexPattern(),
- [createDerivedIndexPattern]
- );
-
- const [influencerFieldName, updateInfluencerFieldName] = useState(
- ruleParams.influencerFilter?.fieldName ?? 'host.name'
- );
-
- useEffect(() => {
- setRuleParams('hasInfraMLCapabilities', hasInfraMLCapabilities);
- }, [setRuleParams, hasInfraMLCapabilities]);
-
- useEffect(() => {
- if (ruleParams.influencerFilter) {
- setRuleParams('influencerFilter', {
- ...ruleParams.influencerFilter,
- fieldName: influencerFieldName,
- });
- }
- }, [influencerFieldName, ruleParams, setRuleParams]);
- const updateInfluencerFieldValue = useCallback(
- (value: string) => {
- if (value) {
- setRuleParams('influencerFilter', {
- ...ruleParams.influencerFilter,
- fieldValue: value,
- } as MetricAnomalyParams['influencerFilter']);
- } else {
- setRuleParams('influencerFilter', undefined);
- }
- },
- [setRuleParams, ruleParams]
- );
-
- useEffect(() => {
- setRuleParams('alertInterval', ruleInterval);
- }, [setRuleParams, ruleInterval]);
-
- const updateNodeType = useCallback(
- (nt: any) => {
- setRuleParams('nodeType', nt);
- },
- [setRuleParams]
- );
-
- const updateMetric = useCallback(
- (metric: string) => {
- setRuleParams('metric', metric as MetricAnomalyParams['metric']);
- },
- [setRuleParams]
- );
-
- const updateSeverityThreshold = useCallback(
- (threshold: any) => {
- setRuleParams('threshold', threshold);
- },
- [setRuleParams]
- );
-
- const prefillNodeType = useCallback(() => {
- const md = metadata;
- if (md && md.nodeType) {
- setRuleParams(
- 'nodeType',
- getMLNodeTypeFromInventoryNodeType(md.nodeType) ?? defaultExpression.nodeType
- );
- } else {
- setRuleParams('nodeType', defaultExpression.nodeType);
- }
- }, [metadata, setRuleParams]);
-
- const prefillMetric = useCallback(() => {
- const md = metadata;
- if (md && md.metric) {
- setRuleParams(
- 'metric',
- getMLMetricFromInventoryMetric(md.metric.type) ?? defaultExpression.metric
- );
- } else {
- setRuleParams('metric', defaultExpression.metric);
- }
- }, [metadata, setRuleParams]);
-
- useEffect(() => {
- if (!ruleParams.nodeType) {
- prefillNodeType();
- }
-
- if (!ruleParams.threshold) {
- setRuleParams('threshold', defaultExpression.threshold);
- }
-
- if (!ruleParams.metric) {
- prefillMetric();
- }
-
- if (!ruleParams.sourceId) {
- setRuleParams('sourceId', source?.id || 'default');
- }
-
- if (!ruleParams.spaceId) {
- setRuleParams('spaceId', space?.id || 'default');
- }
- }, [metadata, derivedIndexPattern, defaultExpression, source, space]); // eslint-disable-line react-hooks/exhaustive-deps
-
- if (isLoadingMLCapabilities) return ;
- if (!hasInfraMLCapabilities) return ;
-
- return (
- // https://github.com/elastic/kibana/issues/89506
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-// required for dynamic import
-// eslint-disable-next-line import/no-default-export
-export default withSourceProvider(Expression)('default');
-
-const StyledExpressionRow = euiStyled(EuiFlexGroup)`
- display: flex;
- flex-wrap: wrap;
- margin: 0 -4px;
-`;
-
-const StyledExpression = euiStyled.div`
- padding: 0 4px;
-`;
-
-const getDisplayNameForType = (type: InventoryItemType) => {
- const inventoryModel = findInventoryModel(type);
- return inventoryModel.displayName;
-};
-
-export const nodeTypes: { [key: string]: any } = {
- hosts: {
- text: getDisplayNameForType('host'),
- value: 'hosts',
- },
- k8s: {
- text: getDisplayNameForType('pod'),
- value: 'k8s',
- },
-};
-
-const getMLMetricFromInventoryMetric: (
- metric: SnapshotMetricType
-) => MetricAnomalyParams['metric'] | null = (metric) => {
- switch (metric) {
- case 'memory':
- return 'memory_usage';
- case 'tx':
- return 'network_out';
- case 'rx':
- return 'network_in';
- default:
- return null;
- }
-};
-
-const getMLNodeTypeFromInventoryNodeType: (
- nodeType: InventoryItemType
-) => MetricAnomalyParams['nodeType'] | null = (nodeType) => {
- switch (nodeType) {
- case 'host':
- return 'hosts';
- case 'pod':
- return 'k8s';
- default:
- return null;
- }
-};
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx
deleted file mode 100644
index cc1e664d6f9d9..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { debounce } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import React, { useState, useCallback, useEffect, useMemo } from 'react';
-import { first } from 'lodash';
-import { EuiFlexGroup, EuiFormRow, EuiCheckbox, EuiFlexItem, EuiSelect } from '@elastic/eui';
-import {
- MetricsExplorerKueryBar,
- CurryLoadSuggestionsType,
-} from '../../../pages/metrics/metrics_explorer/components/kuery_bar';
-import { MetricAnomalyParams } from '../../../../common/alerting/metrics';
-
-interface Props {
- fieldName: string;
- fieldValue: string;
- nodeType: MetricAnomalyParams['nodeType'];
- onChangeFieldName: (v: string) => void;
- onChangeFieldValue: (v: string) => void;
- derivedIndexPattern: Parameters[0]['derivedIndexPattern'];
-}
-
-const FILTER_TYPING_DEBOUNCE_MS = 500;
-
-export const InfluencerFilter = ({
- fieldName,
- fieldValue,
- nodeType,
- onChangeFieldName,
- onChangeFieldValue,
- derivedIndexPattern,
-}: Props) => {
- const fieldNameOptions = useMemo(
- () => (nodeType === 'k8s' ? k8sFieldNames : hostFieldNames),
- [nodeType]
- );
-
- // If initial props contain a fieldValue, assume it was passed in from loaded alertParams,
- // and enable the UI element
- const [isEnabled, updateIsEnabled] = useState(fieldValue ? true : false);
- const [storedFieldValue, updateStoredFieldValue] = useState(fieldValue);
-
- useEffect(
- () =>
- nodeType === 'k8s'
- ? onChangeFieldName(first(k8sFieldNames)!.value)
- : onChangeFieldName(first(hostFieldNames)!.value),
- [nodeType, onChangeFieldName]
- );
-
- const onSelectFieldName = useCallback(
- (e) => onChangeFieldName(e.target.value),
- [onChangeFieldName]
- );
- const onUpdateFieldValue = useCallback(
- (value) => {
- updateStoredFieldValue(value);
- onChangeFieldValue(value);
- },
- [onChangeFieldValue]
- );
-
- const toggleEnabled = useCallback(() => {
- const nextState = !isEnabled;
- updateIsEnabled(nextState);
- if (!nextState) {
- onChangeFieldValue('');
- } else {
- onChangeFieldValue(storedFieldValue);
- }
- }, [isEnabled, updateIsEnabled, onChangeFieldValue, storedFieldValue]);
-
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- const debouncedOnUpdateFieldValue = useCallback(
- debounce(onUpdateFieldValue, FILTER_TYPING_DEBOUNCE_MS),
- [onUpdateFieldValue]
- );
-
- const affixFieldNameToQuery: CurryLoadSuggestionsType =
- (fn) => (expression, cursorPosition, maxSuggestions) => {
- // Add the field name to the front of the passed-in query
- const prefix = `${fieldName}:`;
- // Trim whitespace to prevent AND/OR suggestions
- const modifiedExpression = `${prefix}${expression}`.trim();
- // Move the cursor position forward by the length of the field name
- const modifiedPosition = cursorPosition + prefix.length;
- return fn(modifiedExpression, modifiedPosition, maxSuggestions, (suggestions) =>
- suggestions
- .map((s) => ({
- ...s,
- // Remove quotes from suggestions
- text: s.text.replace(/\"/g, '').trim(),
- // Offset the returned suggestions' cursor positions so that they can be autocompleted accurately
- start: s.start - prefix.length,
- end: s.end - prefix.length,
- }))
- // Removing quotes can lead to an already-selected suggestion still coming up in the autocomplete list,
- // so filter these out
- .filter((s) => !expression.startsWith(s.text))
- );
- };
-
- return (
-
- }
- helpText={
- isEnabled ? (
- <>
- {i18n.translate('xpack.infra.metrics.alertFlyout.anomalyFilterHelpText', {
- defaultMessage:
- 'Limit the scope of your alert trigger to anomalies influenced by certain node(s).',
- })}
-
- {i18n.translate('xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample', {
- defaultMessage: 'For example: "my-node-1" or "my-node-*"',
- })}
- >
- ) : null
- }
- fullWidth
- display="rowCompressed"
- >
- {isEnabled ? (
-
-
-
-
-
-
-
-
- ) : (
- <>>
- )}
-
- );
-};
-
-const hostFieldNames = [
- {
- value: 'host.name',
- text: 'host.name',
- },
-];
-
-const k8sFieldNames = [
- {
- value: 'kubernetes.pod.uid',
- text: 'kubernetes.pod.uid',
- },
- {
- value: 'kubernetes.node.name',
- text: 'kubernetes.node.name',
- },
- {
- value: 'kubernetes.namespace',
- text: 'kubernetes.namespace',
- },
-];
-
-const filterByNodeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.filterByNodeLabel', {
- defaultMessage: 'Filter by node',
-});
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/node_type.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/node_type.tsx
deleted file mode 100644
index a3cfbc978388a..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/node_type.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useState } from 'react';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n-react';
-import { EuiExpression, EuiPopover, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui';
-import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui';
-import { MetricAnomalyParams } from '../../../../common/alerting/metrics';
-
-type Node = MetricAnomalyParams['nodeType'];
-
-interface WhenExpressionProps {
- value: Node;
- options: { [key: string]: { text: string; value: Node } };
- onChange: (value: Node) => void;
- popupPosition?:
- | 'upCenter'
- | 'upLeft'
- | 'upRight'
- | 'downCenter'
- | 'downLeft'
- | 'downRight'
- | 'leftCenter'
- | 'leftUp'
- | 'leftDown'
- | 'rightCenter'
- | 'rightUp'
- | 'rightDown';
-}
-
-export const NodeTypeExpression = ({
- value,
- options,
- onChange,
- popupPosition,
-}: WhenExpressionProps) => {
- const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false);
-
- return (
- {
- setAggTypePopoverOpen(true);
- }}
- />
- }
- isOpen={aggTypePopoverOpen}
- closePopover={() => {
- setAggTypePopoverOpen(false);
- }}
- ownFocus
- anchorPosition={popupPosition ?? 'downLeft'}
- >
-
- setAggTypePopoverOpen(false)}>
-
-
- {
- onChange(e.target.value as Node);
- setAggTypePopoverOpen(false);
- }}
- options={Object.values(options).map((o) => o)}
- />
-
-
- );
-};
-
-interface ClosablePopoverTitleProps {
- children: JSX.Element;
- onClose: () => void;
-}
-
-export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => {
- return (
-
-
- {children}
-
- onClose()}
- />
-
-
-
- );
-};
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/severity_threshold.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/severity_threshold.tsx
deleted file mode 100644
index d910de567a1e9..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/severity_threshold.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useState } from 'react';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n-react';
-import { EuiExpression, EuiPopover, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui';
-import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui';
-import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold';
-
-interface WhenExpressionProps {
- value: Exclude;
- onChange: (value: ML_ANOMALY_THRESHOLD) => void;
- popupPosition?:
- | 'upCenter'
- | 'upLeft'
- | 'upRight'
- | 'downCenter'
- | 'downLeft'
- | 'downRight'
- | 'leftCenter'
- | 'leftUp'
- | 'leftDown'
- | 'rightCenter'
- | 'rightUp'
- | 'rightDown';
-}
-
-const options = {
- [ML_ANOMALY_THRESHOLD.CRITICAL]: {
- text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.criticalLabel', {
- defaultMessage: 'Critical',
- }),
- value: ML_ANOMALY_THRESHOLD.CRITICAL,
- },
- [ML_ANOMALY_THRESHOLD.MAJOR]: {
- text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.majorLabel', {
- defaultMessage: 'Major',
- }),
- value: ML_ANOMALY_THRESHOLD.MAJOR,
- },
- [ML_ANOMALY_THRESHOLD.MINOR]: {
- text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.minorLabel', {
- defaultMessage: 'Minor',
- }),
- value: ML_ANOMALY_THRESHOLD.MINOR,
- },
- [ML_ANOMALY_THRESHOLD.WARNING]: {
- text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.warningLabel', {
- defaultMessage: 'Warning',
- }),
- value: ML_ANOMALY_THRESHOLD.WARNING,
- },
-};
-
-export const SeverityThresholdExpression = ({
- value,
- onChange,
- popupPosition,
-}: WhenExpressionProps) => {
- const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false);
-
- return (
- {
- setAggTypePopoverOpen(true);
- }}
- />
- }
- isOpen={aggTypePopoverOpen}
- closePopover={() => {
- setAggTypePopoverOpen(false);
- }}
- ownFocus
- anchorPosition={popupPosition ?? 'downLeft'}
- >
-
- setAggTypePopoverOpen(false)}>
-
-
- {
- onChange(Number(e.target.value) as ML_ANOMALY_THRESHOLD);
- setAggTypePopoverOpen(false);
- }}
- options={Object.values(options).map((o) => o)}
- />
-
-
- );
-};
-
-interface ClosablePopoverTitleProps {
- children: JSX.Element;
- onClose: () => void;
-}
-
-export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => {
- return (
-
-
- {children}
-
- onClose()}
- />
-
-
-
- );
-};
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/validation.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/validation.tsx
deleted file mode 100644
index fa278674d55e8..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/validation.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import type { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public';
-
-export function validateMetricAnomaly({
- hasInfraMLCapabilities,
-}: {
- hasInfraMLCapabilities: boolean;
-}): ValidationResult {
- const validationResult = { errors: {} };
- const errors: {
- hasInfraMLCapabilities: string[];
- } = {
- hasInfraMLCapabilities: [],
- };
-
- validationResult.errors = errors;
-
- if (!hasInfraMLCapabilities) {
- errors.hasInfraMLCapabilities.push(
- i18n.translate('xpack.infra.metrics.alertFlyout.error.mlCapabilitiesRequired', {
- defaultMessage: 'Cannot create an anomaly alert when machine learning is disabled.',
- })
- );
- }
-
- return validationResult;
-}
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts b/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts
deleted file mode 100644
index 2dfee3891b86b..0000000000000
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public';
-import { RuleTypeParams } from '@kbn/alerting-plugin/common';
-import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../common/alerting/metrics';
-import { validateMetricAnomaly } from './components/validation';
-
-interface MetricAnomalyRuleTypeParams extends RuleTypeParams {
- hasInfraMLCapabilities: boolean;
-}
-
-export function createMetricAnomalyRuleType(): RuleTypeModel {
- return {
- id: METRIC_ANOMALY_ALERT_TYPE_ID,
- description: i18n.translate('xpack.infra.metrics.anomaly.alertFlyout.alertDescription', {
- defaultMessage: 'Alert when the anomaly score exceeds a defined threshold.',
- }),
- iconClass: 'bell',
- documentationUrl(docLinks) {
- return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/infrastructure-anomaly-alert.html`;
- },
- ruleParamsExpression: React.lazy(() => import('./components/expression')),
- validate: validateMetricAnomaly,
- defaultActionMessage: i18n.translate(
- 'xpack.infra.metrics.alerting.anomaly.defaultActionMessage',
- {
- defaultMessage: `\\{\\{alertName\\}\\} is in a state of \\{\\{context.alertState\\}\\}
-
-\\{\\{context.metric\\}\\} was \\{\\{context.summary\\}\\} than normal at \\{\\{context.timestamp\\}\\}
-
-Typical value: \\{\\{context.typical\\}\\}
-Actual value: \\{\\{context.actual\\}\\}
-`,
- }
- ),
- requiresAppContext: false,
- };
-}
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/nginx_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/nginx_charts.ts
deleted file mode 100644
index 7c18f5a5d65e6..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/nginx_charts.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import { nginxLensFormulas } from '../../../formulas';
-import { XY_OVERRIDES } from '../../constants';
-import type { XYConfig } from '../../types';
-
-export const nginxStubstatusCharts: XYConfig[] = [
- {
- id: 'requestRate',
- title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.requestRate', {
- defaultMessage: 'Request Rate',
- }),
-
- layers: [
- {
- data: [nginxLensFormulas.requestRate],
- type: 'visualization',
- },
- ],
- dataViewOrigin: 'metrics',
- },
- {
- id: 'activeConnections',
- title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.activeConnections', {
- defaultMessage: 'Active Connections',
- }),
-
- layers: [
- {
- data: [nginxLensFormulas.activeConnections],
- type: 'visualization',
- },
- ],
- dataViewOrigin: 'metrics',
- },
- {
- id: 'requestsPerConnection',
- title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.requestsPerConnection', {
- defaultMessage: 'Requests Per Connection',
- }),
-
- layers: [
- {
- data: [nginxLensFormulas.requestsPerConnection],
- type: 'visualization',
- },
- ],
- dataViewOrigin: 'metrics',
- },
-];
-
-export const nginxAccessCharts: XYConfig[] = [
- {
- id: 'responseStatusCodes',
- title: i18n.translate('xpack.infra.assetDetails.metricsCharts.nginx.responseStatusCodes', {
- defaultMessage: 'Response Status Codes',
- }),
-
- layers: [
- {
- data: [
- nginxLensFormulas.successStatusCodes,
- nginxLensFormulas.redirectStatusCodes,
- nginxLensFormulas.clientErrorStatusCodes,
- nginxLensFormulas.serverErrorStatusCodes,
- ],
- options: {
- seriesType: 'area',
- },
- type: 'visualization',
- },
- ],
- overrides: {
- settings: XY_OVERRIDES.settings,
- },
- dataViewOrigin: 'metrics',
- },
-];
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts
index 772241da4b73b..aef6787c088fc 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts
@@ -7,17 +7,10 @@
import { hostMetricFlyoutCharts, hostMetricChartsFullPage } from './host/host_metric_charts';
import { hostKPICharts } from './host/host_kpi_charts';
-import { nginxAccessCharts, nginxStubstatusCharts } from './host/nginx_charts';
import { kubernetesCharts } from './host/kubernetes_charts';
export const assetDetailsDashboards = {
host: { hostMetricFlyoutCharts, hostMetricChartsFullPage, hostKPICharts, keyField: 'host.name' },
- nginx: {
- nginxStubstatusCharts,
- nginxAccessCharts,
- keyField: 'host.name',
- dependsOn: ['nginx.stubstatus', 'nginx.access'],
- },
kubernetes: {
kubernetesCharts,
keyField: 'kubernetes.node.name',
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/index.ts
index 18e993a0d0a99..fbdb959e0e945 100644
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/index.ts
+++ b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/index.ts
@@ -6,5 +6,4 @@
*/
export { hostLensFormulas } from './host';
-export { nginxLensFormulas } from './nginx';
export { kubernetesLensFormulas } from './kubernetes';
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/active_connections.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/active_connections.ts
deleted file mode 100644
index 90161ac7ebc5f..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/active_connections.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-
-export const activeConnections: FormulaValueConfig = {
- label: i18n.translate('xpack.infra.assetDetails.formulas.nginx.activeConnections', {
- defaultMessage: 'Active Connections',
- }),
- value: 'average(nginx.stubstatus.active)',
- format: {
- id: 'number',
- params: {
- decimals: 0,
- },
- },
-};
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/client_error_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/client_error_status_codes.ts
deleted file mode 100644
index 2588f71f1444b..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/client_error_status_codes.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-import { defaultPalette, Color } from '../../../../../../common/color_palette';
-
-export const clientErrorStatusCodes: FormulaValueConfig = {
- label: '400-499',
- value: `count(kql='http.response.status_code >= 400 and http.response.status_code <= 499')`,
- format: {
- id: 'number',
- params: {
- decimals: 0,
- },
- },
- color: defaultPalette[Color.color5],
-};
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/index.ts
deleted file mode 100644
index 87a1d5db40fd5..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { requestRate } from './request_rate';
-import { activeConnections } from './active_connections';
-import { requestsPerConnection } from './requests_per_connection';
-import { successStatusCodes } from './success_status_codes';
-import { redirectStatusCodes } from './redirect_status_codes';
-import { clientErrorStatusCodes } from './client_error_status_codes';
-import { serverErrorStatusCodes } from './server_error_status_codes';
-
-export const nginxLensFormulas = {
- activeConnections,
- requestRate,
- requestsPerConnection,
- successStatusCodes,
- redirectStatusCodes,
- clientErrorStatusCodes,
- serverErrorStatusCodes,
-};
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/redirect_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/redirect_status_codes.ts
deleted file mode 100644
index 637546667dbab..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/redirect_status_codes.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-import { defaultPalette, Color } from '../../../../../../common/color_palette';
-
-export const redirectStatusCodes: FormulaValueConfig = {
- label: '300-399',
- value: `count(kql='http.response.status_code >= 300 and http.response.status_code <= 399')`,
- format: {
- id: 'number',
- params: {
- decimals: 0,
- },
- },
- color: defaultPalette[Color.color0],
-};
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/request_rate.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/request_rate.ts
deleted file mode 100644
index 1e86f28a4bfce..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/request_rate.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-
-export const requestRate: FormulaValueConfig = {
- label: i18n.translate('xpack.infra.assetDetails.formulas.nginx.requestRate', {
- defaultMessage: 'Request Rate',
- }),
- value: 'differences(max(nginx.stubstatus.requests))',
- format: {
- id: 'number',
- params: {
- decimals: 0,
- },
- },
- timeScale: 's',
-};
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/requests_per_connection.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/requests_per_connection.ts
deleted file mode 100644
index a74f46e2014da..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/requests_per_connection.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-
-export const requestsPerConnection: FormulaValueConfig = {
- label: i18n.translate('xpack.infra.assetDetails.formulas.nginx.requestsPerConnection', {
- defaultMessage: 'Requests Per Connection',
- }),
- value: 'max(nginx.stubstatus.requests) / max(nginx.stubstatus.handled)',
- format: {
- id: 'number',
- params: {
- decimals: 0,
- },
- },
-};
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/server_error_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/server_error_status_codes.ts
deleted file mode 100644
index f5fba7775190b..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/server_error_status_codes.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-import { defaultPalette, Color } from '../../../../../../common/color_palette';
-
-export const serverErrorStatusCodes: FormulaValueConfig = {
- label: '500-599',
- value: `count(kql='http.response.status_code >= 500 and http.response.status_code <= 599')`,
- format: {
- id: 'number',
- params: {
- decimals: 0,
- },
- },
- color: defaultPalette[Color.color1],
-};
diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/success_status_codes.ts b/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/success_status_codes.ts
deleted file mode 100644
index 865ce8dd907b4..0000000000000
--- a/x-pack/plugins/infra/public/common/visualizations/lens/formulas/nginx/success_status_codes.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { FormulaValueConfig } from '@kbn/lens-embeddable-utils';
-import { defaultPalette, Color } from '../../../../../../common/color_palette';
-
-export const successStatusCodes: FormulaValueConfig = {
- label: '200-299',
- value: `count(kql='http.response.status_code >= 200 and http.response.status_code <= 299')`,
- format: {
- id: 'number',
- params: {
- decimals: 0,
- },
- },
- color: defaultPalette[Color.color2],
-};
diff --git a/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx b/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx
index da639ff88bd13..0e4b1b7baa5ca 100644
--- a/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/components/section_titles.tsx
@@ -64,14 +64,6 @@ export const MetricsSectionTitle = () => {
);
};
-export const NginxMetricsSectionTitle = () => (
-
-);
-
export const KubernetesMetricsSectionTitle = () => (
{
+ const { featureFlags } = usePluginConfig();
const [isAlertFlyoutVisible, { toggle: toggleAlertFlyout }] = useBoolean(false);
const { overrides } = useAssetDetailsRenderPropsContext();
@@ -51,9 +53,11 @@ export const AlertsSummaryContent = ({
-
-
-
+ {featureFlags.inventoryThresholdAlertRuleEnabled && (
+
+
+
+ )}
-
+
+ {featureFlags.inventoryThresholdAlertRuleEnabled && (
+
+ )}
>
);
};
diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx
index 2360bc1a3864c..beaa4f8c4e8ce 100644
--- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx
+++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_section.tsx
@@ -13,7 +13,6 @@ import { EuiFlexGroup } from '@elastic/eui';
import { assetDetailsDashboards } from '../../../../../common/visualizations';
import {
MetricsSectionTitle,
- NginxMetricsSectionTitle,
KubernetesMetricsSectionTitle,
} from '../../../components/section_titles';
import { useMetadataStateProviderContext } from '../../../hooks/use_metadata_state';
@@ -26,7 +25,7 @@ interface Props {
logsDataView?: DataView;
}
-const { host, nginx, kubernetes } = assetDetailsDashboards;
+const { host, kubernetes } = assetDetailsDashboards;
export const MetricsSection = ({ assetName, metricsDataView, logsDataView, dateRange }: Props) => {
return (
@@ -53,26 +52,6 @@ export const MetricsSection = ({ assetName, metricsDataView, logsDataView, dateR
data-test-subj="infraAssetDetailsKubernetesMetricsChart"
/>
-
- ({
- ...chart,
- dependsOn: ['nginx.stubstatus'],
- })),
- ...nginx.nginxAccessCharts.map((chart) => ({
- ...chart,
- dependsOn: ['nginx.access'],
- })),
- ]}
- metricsDataView={metricsDataView}
- logsDataView={logsDataView}
- data-test-subj="infraAssetDetailsNginxMetricsChart"
- />
-
);
};
diff --git a/x-pack/plugins/infra/public/components/asset_details/types.ts b/x-pack/plugins/infra/public/components/asset_details/types.ts
index cc985f8d782bd..0ac33ce08ecfd 100644
--- a/x-pack/plugins/infra/public/components/asset_details/types.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/types.ts
@@ -92,6 +92,5 @@ export interface RouteState {
export type DataViewOrigin = 'logs' | 'metrics';
export enum INTEGRATION_NAME {
- nginx = 'nginx',
kubernetes = 'kubernetes',
}
diff --git a/x-pack/plugins/infra/public/containers/plugin_config_context.test.tsx b/x-pack/plugins/infra/public/containers/plugin_config_context.test.tsx
index 70b3cf466f749..a8afb67fb6e32 100644
--- a/x-pack/plugins/infra/public/containers/plugin_config_context.test.tsx
+++ b/x-pack/plugins/infra/public/containers/plugin_config_context.test.tsx
@@ -24,6 +24,9 @@ describe('usePluginConfig()', () => {
logsUIEnabled: false,
metricsExplorerEnabled: false,
osqueryEnabled: false,
+ inventoryThresholdAlertRuleEnabled: true,
+ metricThresholdAlertRuleEnabled: true,
+ logThresholdAlertRuleEnabled: true,
},
};
const { result } = renderHook(() => usePluginConfig(), {
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts
index d956b30940f4c..a1381f9679d9b 100644
--- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts
@@ -11,6 +11,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { GetViewInAppRelativeUrlFnOpts, PluginSetupContract } from '@kbn/alerting-plugin/server';
import { observabilityPaths } from '@kbn/observability-plugin/common';
import { TimeUnitChar } from '@kbn/observability-plugin/common/utils/formatters/duration';
+import type { InfraConfig } from '../../../../common/plugin_config_types';
import {
Comparator,
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
@@ -81,10 +82,15 @@ const groupActionVariableDescription = i18n.translate(
}
);
-export async function registerMetricInventoryThresholdRuleType(
+export async function registerInventoryThresholdRuleType(
alertingPlugin: PluginSetupContract,
- libs: InfraBackendLibs
+ libs: InfraBackendLibs,
+ { featureFlags }: InfraConfig
) {
+ if (!featureFlags.inventoryThresholdAlertRuleEnabled) {
+ return;
+ }
+
alertingPlugin.registerType({
id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
name: i18n.translate('xpack.infra.metrics.inventory.alertName', {
diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts
index 40848b9a109ed..f16e7dfd284d2 100644
--- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts
@@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { GetViewInAppRelativeUrlFnOpts, PluginSetupContract } from '@kbn/alerting-plugin/server';
import { observabilityPaths } from '@kbn/observability-plugin/common';
+import type { InfraConfig } from '../../../../common/plugin_config_types';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor';
import { extractReferences, injectReferences } from './log_threshold_references_manager';
@@ -103,8 +104,13 @@ const viewInAppUrlActionVariableDescription = i18n.translate(
export async function registerLogThresholdRuleType(
alertingPlugin: PluginSetupContract,
- libs: InfraBackendLibs
+ libs: InfraBackendLibs,
+ { featureFlags }: InfraConfig
) {
+ if (!featureFlags.logThresholdAlertRuleEnabled) {
+ return;
+ }
+
if (!alertingPlugin) {
throw new Error(
'Cannot register log threshold alert type. Both the actions and alerting plugins need to be enabled.'
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts
deleted file mode 100644
index 362cf0bc5a073..0000000000000
--- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/evaluate_condition.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { MetricAnomalyParams } from '../../../../common/alerting/metrics';
-import { getMetricsHostsAnomalies, getMetricK8sAnomalies } from '../../infra_ml';
-import { MlSystem, MlAnomalyDetectors } from '../../../types';
-
-type ConditionParams = Omit & {
- spaceId: string;
- startTime: number;
- endTime: number;
- mlSystem: MlSystem;
- mlAnomalyDetectors: MlAnomalyDetectors;
-};
-
-export const evaluateCondition = async ({
- nodeType,
- spaceId,
- sourceId,
- mlSystem,
- mlAnomalyDetectors,
- startTime,
- endTime,
- metric,
- threshold,
- influencerFilter,
-}: ConditionParams) => {
- const getAnomalies = nodeType === 'k8s' ? getMetricK8sAnomalies : getMetricsHostsAnomalies;
-
- const result = await getAnomalies({
- context: {
- spaceId,
- mlSystem,
- mlAnomalyDetectors,
- },
- sourceId: sourceId ?? 'default',
- anomalyThreshold: threshold,
- startTime,
- endTime,
- metric,
- sort: { field: 'anomalyScore', direction: 'desc' },
- pagination: { pageSize: 100 },
- influencerFilter,
- });
-
- return result;
-};
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts
deleted file mode 100644
index b6d583cb17e6b..0000000000000
--- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import { KibanaRequest } from '@kbn/core/server';
-import { first } from 'lodash';
-import moment from 'moment';
-import {
- ActionGroup,
- AlertInstanceContext as AlertContext,
- AlertInstanceState as AlertState,
-} from '@kbn/alerting-plugin/common';
-import { RuleExecutorOptions } from '@kbn/alerting-plugin/server';
-import { MlPluginSetup } from '@kbn/ml-plugin/server';
-import { AlertStates, MetricAnomalyParams } from '../../../../common/alerting/metrics';
-import { getIntervalInSeconds } from '../../../../common/utils/get_interval_in_seconds';
-import { MappedAnomalyHit } from '../../infra_ml';
-import { InfraBackendLibs } from '../../infra_types';
-import { stateToAlertMessage } from '../common/messages';
-import { evaluateCondition } from './evaluate_condition';
-import { MetricAnomalyAllowedActionGroups } from './register_metric_anomaly_rule_type';
-
-export const createMetricAnomalyExecutor =
- (_libs: InfraBackendLibs, ml?: MlPluginSetup) =>
- async ({
- services,
- params,
- startedAt,
- }: RuleExecutorOptions<
- /**
- * TODO: Remove this use of `any` by utilizing a proper type
- */
- Record,
- Record,
- AlertState,
- AlertContext,
- MetricAnomalyAllowedActionGroups
- >) => {
- if (!ml) {
- return { state: {} };
- }
- const request = {} as KibanaRequest;
- const mlSystem = ml.mlSystemProvider(request, services.savedObjectsClient);
- const mlAnomalyDetectors = ml.anomalyDetectorsProvider(request, services.savedObjectsClient);
-
- const { metric, alertInterval, influencerFilter, sourceId, spaceId, nodeType, threshold } =
- params as MetricAnomalyParams;
-
- const bucketInterval = getIntervalInSeconds('15m') * 1000;
- const alertIntervalInMs = getIntervalInSeconds(alertInterval ?? '1m') * 1000;
-
- const endTime = startedAt.getTime();
- // Anomalies are bucketed at :00, :15, :30, :45 minutes every hour
- const previousBucketStartTime = endTime - (endTime % bucketInterval);
-
- // If the alert interval is less than 15m, make sure that it actually queries an anomaly bucket
- const startTime = Math.min(endTime - alertIntervalInMs, previousBucketStartTime);
-
- const { data } = await evaluateCondition({
- sourceId: sourceId ?? 'default',
- spaceId: spaceId ?? 'default',
- mlSystem,
- mlAnomalyDetectors,
- startTime,
- endTime,
- metric,
- threshold,
- nodeType,
- influencerFilter,
- });
-
- const shouldAlertFire = data.length > 0;
-
- if (shouldAlertFire) {
- const {
- startTime: anomalyStartTime,
- anomalyScore,
- actual,
- typical,
- influencers,
- } = first(data as MappedAnomalyHit[])!;
- const alert = services.alertFactory.create(`${nodeType}-${metric}`);
-
- alert.scheduleActions(FIRED_ACTIONS_ID, {
- alertState: stateToAlertMessage[AlertStates.ALERT],
- timestamp: moment(anomalyStartTime).toISOString(),
- anomalyScore,
- actual,
- typical,
- metric: metricNameMap[metric],
- summary: generateSummaryMessage(actual, typical),
- influencers: influencers.join(', '),
- });
- }
-
- return { state: {} };
- };
-
-export const FIRED_ACTIONS_ID = 'metrics.anomaly.fired';
-export const FIRED_ACTIONS: ActionGroup = {
- id: FIRED_ACTIONS_ID,
- name: i18n.translate('xpack.infra.metrics.alerting.anomaly.fired', {
- defaultMessage: 'Fired',
- }),
-};
-
-const generateSummaryMessage = (actual: number, typical: number) => {
- const differential = (Math.max(actual, typical) / Math.min(actual, typical))
- .toFixed(1)
- .replace('.0', '');
- if (actual > typical) {
- return i18n.translate('xpack.infra.metrics.alerting.anomaly.summaryHigher', {
- defaultMessage: '{differential}x higher',
- values: {
- differential,
- },
- });
- } else {
- return i18n.translate('xpack.infra.metrics.alerting.anomaly.summaryLower', {
- defaultMessage: '{differential}x lower',
- values: {
- differential,
- },
- });
- }
-};
-
-const metricNameMap = {
- memory_usage: i18n.translate('xpack.infra.metrics.alerting.anomaly.memoryUsage', {
- defaultMessage: 'Memory usage',
- }),
- network_in: i18n.translate('xpack.infra.metrics.alerting.anomaly.networkIn', {
- defaultMessage: 'Network in',
- }),
- network_out: i18n.translate('xpack.infra.metrics.alerting.anomaly.networkOut', {
- defaultMessage: 'Network out',
- }),
-};
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/preview_metric_anomaly_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/preview_metric_anomaly_alert.ts
deleted file mode 100644
index 5c55fa3499202..0000000000000
--- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/preview_metric_anomaly_alert.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Unit } from '@kbn/datemath';
-import { countBy } from 'lodash';
-import {
- isTooManyBucketsPreviewException,
- MetricAnomalyParams,
- TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
-} from '../../../../common/alerting/metrics';
-import { getIntervalInSeconds } from '../../../../common/utils/get_interval_in_seconds';
-import { MlAnomalyDetectors, MlSystem } from '../../../types';
-import { MappedAnomalyHit } from '../../infra_ml';
-import { evaluateCondition } from './evaluate_condition';
-
-interface PreviewMetricAnomalyAlertParams {
- mlSystem: MlSystem;
- mlAnomalyDetectors: MlAnomalyDetectors;
- spaceId: string;
- params: MetricAnomalyParams;
- sourceId: string;
- lookback: Unit;
- alertInterval: string;
- alertThrottle: string;
- alertOnNoData: boolean;
- alertNotifyWhen: string;
-}
-
-export const previewMetricAnomalyAlert = async ({
- mlSystem,
- mlAnomalyDetectors,
- spaceId,
- params,
- sourceId,
- lookback,
- alertInterval,
- alertThrottle,
- alertNotifyWhen,
-}: PreviewMetricAnomalyAlertParams) => {
- const { metric, threshold, influencerFilter, nodeType } = params as MetricAnomalyParams;
-
- const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
- const throttleIntervalInSeconds = getIntervalInSeconds(alertThrottle);
-
- const lookbackInterval = `1${lookback}`;
- const lookbackIntervalInSeconds = getIntervalInSeconds(lookbackInterval);
- const endTime = Date.now();
- const startTime = endTime - lookbackIntervalInSeconds * 1000;
-
- const numberOfExecutions = Math.floor(lookbackIntervalInSeconds / alertIntervalInSeconds);
- const bucketIntervalInSeconds = getIntervalInSeconds('15m');
- const bucketsPerExecution = Math.max(
- 1,
- Math.floor(alertIntervalInSeconds / bucketIntervalInSeconds)
- );
-
- try {
- let anomalies: MappedAnomalyHit[] = [];
- const { data } = await evaluateCondition({
- nodeType,
- spaceId,
- sourceId,
- mlSystem,
- mlAnomalyDetectors,
- startTime,
- endTime,
- metric,
- threshold,
- influencerFilter,
- });
- anomalies = [...anomalies, ...data];
-
- const anomaliesByTime = countBy(anomalies, ({ startTime: anomStartTime }) => anomStartTime);
-
- let numberOfTimesFired = 0;
- let numberOfNotifications = 0;
- let throttleTracker = 0;
- let previousActionGroup: string | null = null;
- const notifyWithThrottle = (actionGroup: string) => {
- if (alertNotifyWhen === 'onActionGroupChange') {
- if (previousActionGroup !== actionGroup) numberOfNotifications++;
- } else if (alertNotifyWhen === 'onThrottleInterval') {
- if (throttleTracker === 0) numberOfNotifications++;
- throttleTracker += alertIntervalInSeconds;
- } else {
- numberOfNotifications++;
- }
- previousActionGroup = actionGroup;
- };
- // Mock each alert evaluation
- for (let i = 0; i < numberOfExecutions; i++) {
- const executionTime = startTime + alertIntervalInSeconds * 1000 * i;
- // Get an array of bucket times this mock alert evaluation will be looking at
- // Anomalies are bucketed at :00, :15, :30, :45 minutes every hour,
- // so this is an array of how many of those times occurred between this evaluation
- // and the previous one
- const bucketsLookedAt = Array.from(Array(bucketsPerExecution), (_, idx) => {
- const previousBucketStartTime =
- executionTime -
- (executionTime % (bucketIntervalInSeconds * 1000)) -
- idx * bucketIntervalInSeconds * 1000;
- return previousBucketStartTime;
- });
- const anomaliesDetectedInBuckets = bucketsLookedAt.some((bucketTime) =>
- Reflect.has(anomaliesByTime, bucketTime)
- );
-
- if (anomaliesDetectedInBuckets) {
- numberOfTimesFired++;
- notifyWithThrottle('fired');
- } else {
- previousActionGroup = 'recovered';
- if (throttleTracker > 0) {
- throttleTracker += alertIntervalInSeconds;
- }
- }
- if (throttleTracker >= throttleIntervalInSeconds) {
- throttleTracker = 0;
- }
- }
-
- return { fired: numberOfTimesFired, notifications: numberOfNotifications };
- } catch (e) {
- if (!isTooManyBucketsPreviewException(e)) throw e;
- const { maxBuckets } = e;
- throw new Error(`${TOO_MANY_BUCKETS_PREVIEW_EXCEPTION}:${maxBuckets}`);
- }
-};
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_rule_type.ts
deleted file mode 100644
index dc3fd1b28546c..0000000000000
--- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_rule_type.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
-import { schema } from '@kbn/config-schema';
-import { i18n } from '@kbn/i18n';
-import { MlPluginSetup } from '@kbn/ml-plugin/server';
-import {
- RuleType,
- AlertInstanceState as AlertState,
- AlertInstanceContext as AlertContext,
- GetViewInAppRelativeUrlFnOpts,
-} from '@kbn/alerting-plugin/server';
-import { RecoveredActionGroupId } from '@kbn/alerting-plugin/common';
-import { observabilityPaths } from '@kbn/observability-plugin/common';
-import { O11Y_AAD_FIELDS } from '../../../../common/constants';
-import {
- createMetricAnomalyExecutor,
- FIRED_ACTIONS,
- FIRED_ACTIONS_ID,
-} from './metric_anomaly_executor';
-import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../../common/alerting/metrics';
-import { InfraBackendLibs } from '../../infra_types';
-import { oneOfLiterals, validateIsStringElasticsearchJSONFilter } from '../common/utils';
-import { alertStateActionVariableDescription } from '../common/messages';
-
-export type MetricAnomalyAllowedActionGroups = typeof FIRED_ACTIONS_ID;
-
-export const registerMetricAnomalyRuleType = (
- libs: InfraBackendLibs,
- ml?: MlPluginSetup
-): RuleType<
- /**
- * TODO: Remove this use of `any` by utilizing a proper type
- */
- Record,
- never, // Only use if defining useSavedObjectReferences hook
- Record,
- AlertState,
- AlertContext,
- MetricAnomalyAllowedActionGroups,
- RecoveredActionGroupId
-> => ({
- id: METRIC_ANOMALY_ALERT_TYPE_ID,
- name: i18n.translate('xpack.infra.metrics.anomaly.alertName', {
- defaultMessage: 'Infrastructure anomaly',
- }),
- validate: {
- params: schema.object(
- {
- nodeType: oneOfLiterals(['hosts', 'k8s']),
- alertInterval: schema.string(),
- metric: oneOfLiterals(['memory_usage', 'network_in', 'network_out']),
- threshold: schema.number(),
- filterQuery: schema.maybe(
- schema.string({ validate: validateIsStringElasticsearchJSONFilter })
- ),
- sourceId: schema.string(),
- spaceId: schema.string(),
- },
- { unknowns: 'allow' }
- ),
- },
- defaultActionGroupId: FIRED_ACTIONS_ID,
- actionGroups: [FIRED_ACTIONS],
- category: DEFAULT_APP_CATEGORIES.observability.id,
- producer: 'infrastructure',
- minimumLicenseRequired: 'basic',
- isExportable: true,
- executor: createMetricAnomalyExecutor(libs, ml),
- fieldsForAAD: O11Y_AAD_FIELDS,
- actionVariables: {
- context: [
- { name: 'alertState', description: alertStateActionVariableDescription },
- {
- name: 'metric',
- description: i18n.translate('xpack.infra.metrics.alerting.anomalyMetricDescription', {
- defaultMessage: 'The metric name in the specified condition.',
- }),
- },
- {
- name: 'timestamp',
- description: i18n.translate('xpack.infra.metrics.alerting.anomalyTimestampDescription', {
- defaultMessage: 'A timestamp of when the anomaly was detected.',
- }),
- },
- {
- name: 'anomalyScore',
- description: i18n.translate('xpack.infra.metrics.alerting.anomalyScoreDescription', {
- defaultMessage: 'The exact severity score of the detected anomaly.',
- }),
- },
- {
- name: 'actual',
- description: i18n.translate('xpack.infra.metrics.alerting.anomalyActualDescription', {
- defaultMessage: 'The actual value of the monitored metric at the time of the anomaly.',
- }),
- },
- {
- name: 'typical',
- description: i18n.translate('xpack.infra.metrics.alerting.anomalyTypicalDescription', {
- defaultMessage: 'The typical value of the monitored metric at the time of the anomaly.',
- }),
- },
- {
- name: 'summary',
- description: i18n.translate('xpack.infra.metrics.alerting.anomalySummaryDescription', {
- defaultMessage: 'A description of the anomaly, e.g. "2x higher."',
- }),
- },
- {
- name: 'influencers',
- description: i18n.translate('xpack.infra.metrics.alerting.anomalyInfluencersDescription', {
- defaultMessage: 'A list of node names that influenced the anomaly.',
- }),
- },
- ],
- },
- getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
- observabilityPaths.ruleDetails(rule.id),
-});
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
index b3b82602f11f1..f7052b3e1916f 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
@@ -1903,6 +1903,9 @@ const createMockStaticConfiguration = (sources: any): InfraConfig => ({
logsUIEnabled: true,
metricsExplorerEnabled: true,
osqueryEnabled: true,
+ inventoryThresholdAlertRuleEnabled: true,
+ metricThresholdAlertRuleEnabled: true,
+ logThresholdAlertRuleEnabled: true,
},
enabled: true,
sources,
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts
index ad6429eb2ba0f..e7ea693a0e74d 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts
@@ -15,6 +15,7 @@ import {
RuleType,
} from '@kbn/alerting-plugin/server';
import { observabilityPaths } from '@kbn/observability-plugin/common';
+import type { InfraConfig } from '../../../../common/plugin_config_types';
import { Comparator, METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics';
import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api';
import { InfraBackendLibs } from '../../infra_types';
@@ -56,8 +57,13 @@ export type MetricThresholdAlertType = Omit & {
export async function registerMetricThresholdRuleType(
alertingPlugin: PluginSetupContract,
- libs: InfraBackendLibs
+ libs: InfraBackendLibs,
+ { featureFlags }: InfraConfig
) {
+ if (!featureFlags.metricThresholdAlertRuleEnabled) {
+ return;
+ }
+
const baseCriterion = {
threshold: schema.arrayOf(schema.number()),
comparator: oneOfLiterals(Object.values(Comparator)),
diff --git a/x-pack/plugins/infra/server/lib/alerting/register_rule_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_rule_types.ts
index ee05dc38cc1f5..36c836fc50aa7 100644
--- a/x-pack/plugins/infra/server/lib/alerting/register_rule_types.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/register_rule_types.ts
@@ -7,12 +7,11 @@
import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils';
import { type IRuleTypeAlerts, PluginSetupContract } from '@kbn/alerting-plugin/server';
-import { MlPluginSetup } from '@kbn/ml-plugin/server';
import { registerMetricThresholdRuleType } from './metric_threshold/register_metric_threshold_rule_type';
-import { registerMetricInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type';
-import { registerMetricAnomalyRuleType } from './metric_anomaly/register_metric_anomaly_rule_type';
+import { registerInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type';
import { registerLogThresholdRuleType } from './log_threshold/register_log_threshold_rule_type';
import { InfraBackendLibs } from '../infra_types';
+import type { InfraConfig } from '../../types';
export const LOGS_RULES_ALERT_CONTEXT = 'observability.logs';
// Defines which alerts-as-data index logs rules will use
@@ -35,18 +34,16 @@ export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = {
const registerRuleTypes = (
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs,
- ml?: MlPluginSetup
+ config: InfraConfig
) => {
if (alertingPlugin) {
- alertingPlugin.registerType(registerMetricAnomalyRuleType(libs, ml));
-
const registerFns = [
registerLogThresholdRuleType,
- registerMetricInventoryThresholdRuleType,
+ registerInventoryThresholdRuleType,
registerMetricThresholdRuleType,
];
registerFns.forEach((fn) => {
- fn(alertingPlugin, libs);
+ fn(alertingPlugin, libs, config);
});
}
};
diff --git a/x-pack/plugins/infra/server/lib/sources/sources.test.ts b/x-pack/plugins/infra/server/lib/sources/sources.test.ts
index d9e3e3ee4dbac..bf31f4ed099d8 100644
--- a/x-pack/plugins/infra/server/lib/sources/sources.test.ts
+++ b/x-pack/plugins/infra/server/lib/sources/sources.test.ts
@@ -130,6 +130,9 @@ const createMockStaticConfiguration = (sources: any): InfraConfig => ({
logsUIEnabled: true,
metricsExplorerEnabled: true,
osqueryEnabled: true,
+ inventoryThresholdAlertRuleEnabled: true,
+ metricThresholdAlertRuleEnabled: true,
+ logThresholdAlertRuleEnabled: true,
},
sources,
enabled: true,
diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts
index 04a57f294303f..2dae74a2083b6 100644
--- a/x-pack/plugins/infra/server/plugin.ts
+++ b/x-pack/plugins/infra/server/plugin.ts
@@ -83,7 +83,7 @@ export const config: PluginConfigDescriptor = {
featureFlags: schema.object({
customThresholdAlertsEnabled: offeringBasedSchema({
traditional: schema.boolean({ defaultValue: false }),
- serverless: schema.boolean({ defaultValue: true }),
+ serverless: schema.boolean({ defaultValue: false }),
}),
logsUIEnabled: offeringBasedSchema({
traditional: schema.boolean({ defaultValue: true }),
@@ -97,6 +97,18 @@ export const config: PluginConfigDescriptor = {
traditional: schema.boolean({ defaultValue: true }),
serverless: schema.boolean({ defaultValue: false }),
}),
+ inventoryThresholdAlertRuleEnabled: offeringBasedSchema({
+ traditional: schema.boolean({ defaultValue: true }),
+ serverless: schema.boolean({ defaultValue: false }),
+ }),
+ metricThresholdAlertRuleEnabled: offeringBasedSchema({
+ traditional: schema.boolean({ defaultValue: true }),
+ serverless: schema.boolean({ defaultValue: false }),
+ }),
+ logThresholdAlertRuleEnabled: offeringBasedSchema({
+ traditional: schema.boolean({ defaultValue: true }),
+ serverless: schema.boolean({ defaultValue: false }),
+ }),
}),
}),
deprecations: configDeprecations,
@@ -238,7 +250,7 @@ export class InfraServerPlugin
}
initInfraServer(this.libs);
- registerRuleTypes(plugins.alerting, this.libs, plugins.ml);
+ registerRuleTypes(plugins.alerting, this.libs, this.config);
core.http.registerRouteHandlerContext(
'infra',
diff --git a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts
index 05b4452dc5557..6c5aafe34fa81 100644
--- a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts
+++ b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts
@@ -16,8 +16,7 @@ export const initGetLogAlertsChartPreviewDataRoute = ({
framework,
getStartServices,
}: Pick) => {
- // Replace with the corresponding logs alert rule feature flag
- if (!framework.config.featureFlags.logsUIEnabled) {
+ if (!framework.config.featureFlags.logThresholdAlertRuleEnabled) {
return;
}
diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts
index 4ee31881b0eb0..4274bdb17c747 100644
--- a/x-pack/plugins/lens/common/constants.ts
+++ b/x-pack/plugins/lens/common/constants.ts
@@ -87,3 +87,5 @@ export function getEditPath(
export function getFullPath(id?: string) {
return `/app/${PLUGIN_ID}${id ? getEditPath(id) : getBasePath()}`;
}
+
+export const COLOR_MAPPING_OFF_BY_DEFAULT = true;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
index d9cfa8c84c62f..e12182cd07bff 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
@@ -11,6 +11,7 @@ import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
import { difference } from 'lodash';
import type { DataViewsContract, DataViewSpec } from '@kbn/data-views-plugin/public';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
+import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
import type { DataPublicPluginStart, TimefilterContract } from '@kbn/data-plugin/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
@@ -18,7 +19,8 @@ import {
type EventAnnotationGroupConfig,
EVENT_ANNOTATION_GROUP_TYPE,
} from '@kbn/event-annotation-common';
-import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
+import { COLOR_MAPPING_OFF_BY_DEFAULT } from '../../../common/constants';
+
import type {
Datasource,
DatasourceMap,
@@ -28,6 +30,7 @@ import type {
InitializationOptions,
VisualizationMap,
VisualizeEditorContext,
+ SuggestionRequest,
} from '../../types';
import { buildExpression } from './expression_helpers';
import { Document } from '../../persistence/saved_object_store';
@@ -37,6 +40,19 @@ import { readFromStorage } from '../../settings_storage';
import { loadIndexPatternRefs, loadIndexPatterns } from '../../data_views_service/loader';
import { getDatasourceLayers } from '../../state_management/utils';
+// there are 2 ways of coloring, the color mapping where the user can map specific colors to
+// specific terms, and the palette assignment where the colors are assinged automatically
+// by a palette with rotating the colors
+const COLORING_METHOD: SuggestionRequest['mainPalette'] = COLOR_MAPPING_OFF_BY_DEFAULT
+ ? {
+ type: 'legacyPalette',
+ value: {
+ name: 'default',
+ type: 'palette',
+ },
+ }
+ : { type: 'colorMapping', value: { ...DEFAULT_COLOR_MAPPING_CONFIG } };
+
function getIndexPatterns(
annotationGroupDataviewIds: string[],
references?: SavedObjectReference[],
@@ -290,8 +306,8 @@ export function initializeVisualization({
visualizationMap[visualizationState.activeId]?.initialize(
() => '',
visualizationState.state,
- // initialize a new visualization always with the new color mapping
- { type: 'colorMapping', value: { ...DEFAULT_COLOR_MAPPING_CONFIG } },
+ // initialize a new visualization with the color mapping off
+ COLORING_METHOD,
annotationGroups,
references
) ?? visualizationState.state
diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts
index e30bfefa66a97..cddcf5ade4cf3 100644
--- a/x-pack/plugins/lens/public/lens_suggestions_api.ts
+++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts
@@ -7,12 +7,7 @@
import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { getSuggestions } from './editor_frame_service/editor_frame/suggestion_helpers';
-import type {
- DatasourceMap,
- VisualizationMap,
- VisualizeEditorContext,
- SuggestionRequest,
-} from './types';
+import type { DatasourceMap, VisualizationMap, VisualizeEditorContext } from './types';
import type { DataViewsState } from './state_management';
interface SuggestionsApi {
@@ -23,14 +18,6 @@ interface SuggestionsApi {
excludedVisualizations?: string[];
}
-const PREFERRED_PALETTE: SuggestionRequest['mainPalette'] = {
- type: 'legacyPalette',
- value: {
- name: 'default',
- type: 'palette',
- },
-};
-
export const suggestionsApi = ({
context,
dataView,
@@ -75,7 +62,6 @@ export const suggestionsApi = ({
visualizationState: undefined,
visualizeTriggerFieldContext: context,
dataViews,
- mainPalette: PREFERRED_PALETTE,
});
if (!suggestions.length) return [];
const activeVisualization = suggestions[0];
@@ -98,7 +84,6 @@ export const suggestionsApi = ({
activeVisualization: visualizationMap[activeVisualization.visualizationId],
visualizationState: activeVisualization.visualizationState,
dataViews,
- mainPalette: PREFERRED_PALETTE,
}).filter((sug) => !sug.hide && sug.visualizationId !== 'lnsLegacyMetric');
const suggestionsList = [activeVisualization, ...newSuggestions];
// until we separate the text based suggestions logic from the dataview one,
diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts
index 90011c6bad735..86c769444c69c 100644
--- a/x-pack/plugins/lens/public/utils.ts
+++ b/x-pack/plugins/lens/public/utils.ts
@@ -9,7 +9,7 @@ import { uniq, cloneDeep } from 'lodash';
import { i18n } from '@kbn/i18n';
import moment from 'moment-timezone';
import type { Serializable } from '@kbn/utility-types';
-
+import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
import type { TimefilterContract } from '@kbn/data-plugin/public';
import type { IUiSettingsClient, SavedObjectReference } from '@kbn/core/public';
import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public';
@@ -36,6 +36,7 @@ import {
} from './types';
import type { DatasourceStates, VisualizationState } from './state_management';
import type { IndexPatternServiceAPI } from './data_views_service/service';
+import { COLOR_MAPPING_OFF_BY_DEFAULT } from '../common/constants';
export function getVisualizeGeoFieldMessage(fieldType: string) {
return i18n.translate('xpack.lens.visualizeGeoFieldMessage', {
@@ -421,3 +422,10 @@ export function shouldRemoveSource(
dropType === 'replace_incompatible')
);
}
+
+export const getColorMappingDefaults = () => {
+ if (COLOR_MAPPING_OFF_BY_DEFAULT) {
+ return undefined;
+ }
+ return { ...DEFAULT_COLOR_MAPPING_CONFIG };
+};
diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts
index 6486c1a95558a..64b0488a79bb5 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { DEFAULT_COLOR_MAPPING_CONFIG, PaletteOutput } from '@kbn/coloring';
+import { PaletteOutput } from '@kbn/coloring';
import { suggestions } from './suggestions';
import type { DataType, SuggestionRequest } from '../../types';
import type { PieLayerState, PieVisualizationState } from '../../../common/types';
@@ -683,7 +683,7 @@ describe('suggestions', () => {
legendMaxLines: 1,
truncateLegend: true,
nestedLegend: true,
- colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+ colorMapping: undefined,
},
],
},
diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts
index e78c203670aec..eec86da39d606 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts
+++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts
@@ -7,7 +7,6 @@
import { partition } from 'lodash';
import { i18n } from '@kbn/i18n';
-import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
import type {
SuggestionRequest,
TableSuggestionColumn,
@@ -24,6 +23,7 @@ import { isPartitionShape } from '../../../common/visualizations';
import type { PieChartType } from '../../../common/types';
import { PartitionChartsMeta } from './partition_charts_meta';
import { layerTypes } from '../..';
+import { getColorMappingDefaults } from '../../utils';
function hasIntervalScale(columns: TableSuggestionColumn[]) {
return columns.some((col) => col.operation.scale === 'interval');
@@ -142,7 +142,7 @@ export function suggestions({
metrics: metricColumnIds,
layerType: layerTypes.DATA,
colorMapping: !mainPalette
- ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+ ? getColorMappingDefaults()
: mainPalette?.type === 'colorMapping'
? mainPalette.value
: state.layers[0].colorMapping,
@@ -157,7 +157,7 @@ export function suggestions({
nestedLegend: false,
layerType: layerTypes.DATA,
colorMapping: !mainPalette
- ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+ ? getColorMappingDefaults()
: mainPalette?.type === 'colorMapping'
? mainPalette.value
: undefined,
diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
index 429089f743c33..da42517ad30cc 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
@@ -51,13 +51,13 @@ import { DimensionDataExtraEditor, DimensionEditor } from './dimension_editor';
import { LayerSettings } from './layer_settings';
import { checkTableForContainsSmallValues } from './render_helpers';
import { DatasourcePublicAPI } from '../..';
-import { nonNullable } from '../../utils';
+import { nonNullable, getColorMappingDefaults } from '../../utils';
const metricLabel = i18n.translate('xpack.lens.pie.groupMetricLabelSingular', {
defaultMessage: 'Metric',
});
-function newLayerState(layerId: string, colorMapping: ColorMapping.Config): PieLayerState {
+function newLayerState(layerId: string, colorMapping?: ColorMapping.Config): PieLayerState {
return {
layerId,
primaryGroups: [],
@@ -168,9 +168,7 @@ export const getPieVisualization = ({
layers: [
newLayerState(
addNewLayer(),
- mainPalette?.type === 'colorMapping'
- ? mainPalette.value
- : { ...DEFAULT_COLOR_MAPPING_CONFIG }
+ mainPalette?.type === 'colorMapping' ? mainPalette.value : getColorMappingDefaults()
),
],
palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : undefined,
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts b/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts
index 4a528c99d41ad..0ce9624305692 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts
@@ -7,10 +7,10 @@
import { partition } from 'lodash';
import { IconChartTagcloud } from '@kbn/chart-icons';
-import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
import type { SuggestionRequest, VisualizationSuggestion } from '../../types';
import type { TagcloudState } from './types';
import { DEFAULT_STATE, TAGCLOUD_LABEL } from './constants';
+import { getColorMappingDefaults } from '../../utils';
export function getSuggestions({
table,
@@ -42,15 +42,14 @@ export function getSuggestions({
return {
previewIcon: IconChartTagcloud,
title: TAGCLOUD_LABEL,
- hide: true, // hide suggestions while in tech preview
- score: 0.1,
+ score: bucket.operation.dataType === 'string' ? 0.4 : 0.2,
state: {
layerId: table.layerId,
tagAccessor: bucket.columnId,
valueAccessor: metrics[0].columnId,
...DEFAULT_STATE,
colorMapping: !mainPalette
- ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+ ? getColorMappingDefaults()
: mainPalette?.type === 'colorMapping'
? mainPalette.value
: undefined,
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx
index 7e7a667c778bb..bb69dfda2b0a0 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx
@@ -16,11 +16,12 @@ import {
buildExpressionFunction,
ExpressionFunctionTheme,
} from '@kbn/expressions-plugin/common';
-import { PaletteRegistry, DEFAULT_COLOR_MAPPING_CONFIG, getColorsFromMapping } from '@kbn/coloring';
+import { PaletteRegistry, getColorsFromMapping } from '@kbn/coloring';
import { IconChartTagcloud } from '@kbn/chart-icons';
import { SystemPaletteExpressionFunctionDefinition } from '@kbn/charts-plugin/common';
import useObservable from 'react-use/lib/useObservable';
import type { OperationMetadata, Visualization } from '../..';
+import { getColorMappingDefaults } from '../../utils';
import type { TagcloudState } from './types';
import { getSuggestions } from './suggestions';
import { TagcloudToolbar } from './tagcloud_toolbar';
@@ -47,7 +48,6 @@ export const getTagcloudVisualization = ({
groupLabel: i18n.translate('xpack.lens.pie.groupLabel', {
defaultMessage: 'Proportion',
}),
- showExperimentalBadge: true,
},
],
@@ -102,13 +102,14 @@ export const getTagcloudVisualization = ({
triggers: [VIS_EVENT_TO_TRIGGER.filter],
- initialize(addNewLayer, state) {
+ initialize(addNewLayer, state, mainPalette) {
return (
state || {
layerId: addNewLayer(),
layerType: LayerTypes.DATA,
...DEFAULT_STATE,
- colorMapping: { ...DEFAULT_COLOR_MAPPING_CONFIG },
+ colorMapping:
+ mainPalette?.type === 'colorMapping' ? mainPalette.value : getColorMappingDefaults(),
}
);
},
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx
index 6be7d5a532759..4e0983e29860a 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx
@@ -28,11 +28,10 @@ import {
EuiBadge,
} from '@elastic/eui';
import { useState, MutableRefObject, useCallback } from 'react';
-import { PalettePicker } from '@kbn/coloring/src/shared_components/coloring/palette_picker';
import { useDebouncedValue } from '@kbn/visualization-ui-components';
import { getColorCategories } from '@kbn/chart-expressions-common';
import type { TagcloudState } from './types';
-import { PalettePanelContainer } from '../../shared_components';
+import { PalettePanelContainer, PalettePicker } from '../../shared_components';
import { FramePublicAPI } from '../../types';
import { trackUiCounterEvents } from '../../lens_ui_telemetry';
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
index 962766ee9c5d7..53d51849a6a7b 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
@@ -57,7 +57,6 @@ import {
} from './visualization_helpers';
import { cloneDeep } from 'lodash';
import { DataViewsServicePublic } from '@kbn/data-views-plugin/public';
-import { EUIAmsterdamColorBlindPalette } from '@kbn/coloring';
const DATE_HISTORGRAM_COLUMN_ID = 'date_histogram_column';
const exampleAnnotation: EventAnnotationConfig = {
@@ -222,27 +221,7 @@ describe('xy_visualization', () => {
"layers": Array [
Object {
"accessors": Array [],
- "colorMapping": Object {
- "assignmentMode": "auto",
- "assignments": Array [],
- "colorMode": Object {
- "type": "categorical",
- },
- "paletteId": "${EUIAmsterdamColorBlindPalette.id}",
- "specialAssignments": Array [
- Object {
- "color": Object {
- "colorIndex": 1,
- "paletteId": "neutral",
- "type": "categorical",
- },
- "rule": Object {
- "type": "other",
- },
- "touched": false,
- },
- ],
- },
+ "colorMapping": undefined,
"layerId": "l1",
"layerType": "data",
"palette": undefined,
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
index e1b453b7e9f52..d3bb805a0a381 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
@@ -25,7 +25,7 @@ import type { EventAnnotationGroupConfig } from '@kbn/event-annotation-common';
import { isEqual } from 'lodash';
import { type AccessorConfig, DimensionTrigger } from '@kbn/visualization-ui-components';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
-import { DEFAULT_COLOR_MAPPING_CONFIG, getColorsFromMapping } from '@kbn/coloring';
+import { getColorsFromMapping } from '@kbn/coloring';
import useObservable from 'react-use/lib/useObservable';
import { generateId } from '../../id_generator';
import {
@@ -34,6 +34,7 @@ import {
isOperationFromTheSameGroup,
nonNullable,
renewIDs,
+ getColorMappingDefaults,
} from '../../utils';
import { getSuggestions } from './xy_suggestions';
import { XyToolbar } from './xy_config_panel';
@@ -281,9 +282,7 @@ export const getXyVisualization = ({
layerType: LayerTypes.DATA,
palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : undefined,
colorMapping:
- mainPalette?.type === 'colorMapping'
- ? mainPalette.value
- : { ...DEFAULT_COLOR_MAPPING_CONFIG },
+ mainPalette?.type === 'colorMapping' ? mainPalette.value : getColorMappingDefaults(),
},
],
}
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
index 4d733d005dcf4..82368c2414c9a 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
@@ -18,7 +18,7 @@ import { generateId } from '../../id_generator';
import { getXyVisualization } from './xy_visualization';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks';
-import { DEFAULT_COLOR_MAPPING_CONFIG, PaletteOutput } from '@kbn/coloring';
+import { PaletteOutput } from '@kbn/coloring';
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
@@ -917,7 +917,7 @@ describe('xy_suggestions', () => {
{
...currentState.layers[0],
seriesType: 'line',
- colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+ colorMapping: undefined,
},
],
});
@@ -964,7 +964,7 @@ describe('xy_suggestions', () => {
{
...currentState.layers[0],
seriesType: 'line',
- colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+ colorMapping: undefined,
},
],
});
@@ -975,7 +975,7 @@ describe('xy_suggestions', () => {
{
...currentState.layers[0],
seriesType: 'bar_stacked',
- colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+ colorMapping: undefined,
},
],
});
@@ -1099,7 +1099,7 @@ describe('xy_suggestions', () => {
...currentState.layers[0],
xAccessor: 'product',
splitAccessor: 'category',
- colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+ colorMapping: undefined,
},
],
});
@@ -1145,7 +1145,7 @@ describe('xy_suggestions', () => {
...currentState.layers[0],
xAccessor: 'category',
splitAccessor: 'product',
- colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+ colorMapping: undefined,
},
],
});
@@ -1192,7 +1192,7 @@ describe('xy_suggestions', () => {
...currentState.layers[0],
xAccessor: 'timestamp',
splitAccessor: 'product',
- colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+ colorMapping: undefined,
},
],
});
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts
index 1d29cd32323fb..1e6e12cbc8c11 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts
@@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n';
import { partition } from 'lodash';
import { Position } from '@elastic/charts';
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
-import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
import type {
SuggestionRequest,
VisualizationSuggestion,
@@ -17,6 +16,7 @@ import type {
TableSuggestion,
TableChangeType,
} from '../../types';
+import { getColorMappingDefaults } from '../../utils';
import {
State,
XYState,
@@ -556,7 +556,7 @@ function buildSuggestion({
: undefined,
layerType: LayerTypes.DATA,
colorMapping: !mainPalette
- ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+ ? getColorMappingDefaults()
: mainPalette?.type === 'colorMapping'
? mainPalette.value
: undefined,
diff --git a/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts
index 5efdf9e125deb..b9f5037ced6b1 100644
--- a/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts
+++ b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts
@@ -249,6 +249,7 @@ describe('LogViewsClient class', () => {
},
],
"dataViewReference": DataView {
+ "allowHidden": false,
"allowNoIndex": false,
"deleteFieldFormat": [Function],
"fieldAttrs": Object {},
@@ -273,6 +274,7 @@ describe('LogViewsClient class', () => {
},
"fields": FldList [],
"flattenHit": [Function],
+ "getAllowHidden": [Function],
"getFieldAttrs": [Function],
"getIndexPattern": [Function],
"getName": [Function],
diff --git a/x-pack/plugins/ml/common/openapi/README.md b/x-pack/plugins/ml/common/openapi/README.md
index 7c482dc055b98..7a79e55608894 100644
--- a/x-pack/plugins/ml/common/openapi/README.md
+++ b/x-pack/plugins/ml/common/openapi/README.md
@@ -12,6 +12,9 @@ It is possible to validate the docs before bundling them by running these
commands in the `x-pack/plugins/ml/common/openapi/` folder:
```
-npx swagger-cli validate ml_apis_v3.yaml
-npx @redocly/cli lint ml_apis_v3.yaml
+npx swagger-cli validate ml_apis.yaml
+npx @redocly/cli lint ml_apis.yaml
+
+npx swagger-cli validate ml_apis_serverless.yaml
+npx @redocly/cli lint ml_apis_serverless.yaml
```
diff --git a/x-pack/plugins/ml/common/openapi/ml_apis.yaml b/x-pack/plugins/ml/common/openapi/ml_apis.yaml
new file mode 100644
index 0000000000000..bde6c4c5cca7a
--- /dev/null
+++ b/x-pack/plugins/ml/common/openapi/ml_apis.yaml
@@ -0,0 +1,213 @@
+openapi: 3.0.1
+info:
+ title: Machine learning APIs
+ description: Kibana APIs for the machine learning feature
+ version: "1.0.1"
+ license:
+ name: Elastic License 2.0
+ url: https://www.elastic.co/licensing/elastic-license
+tags:
+ - name: ml
+ description: Machine learning
+servers:
+ - url: https://localhost:5601
+paths:
+ /api/ml/saved_objects/sync:
+ get:
+ summary: Synchronizes Kibana saved objects for machine learning jobs and trained models.
+ description: This API runs automatically when you start Kibana and periodically thereafter.
+ operationId: mlSync
+ tags:
+ - ml
+ parameters:
+ - $ref: '#/components/parameters/simulateParam'
+ responses:
+ '200':
+ description: Indicates a successful call
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/mlSync200Response'
+ examples:
+ syncExample:
+ $ref: '#/components/examples/mlSyncExample'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/mlSync4xxResponse'
+
+ /s/{spaceId}/api/ml/saved_objects/sync:
+ get:
+ summary: Synchronizes Kibana saved objects for machine learning jobs and trained models.
+ description: >
+ You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges.
+ This API runs automatically when you start Kibana and periodically thereafter.
+ operationId: mlSyncWithSpaceId
+ tags:
+ - ml
+ parameters:
+ - $ref: '#/components/parameters/spaceParam'
+ - $ref: '#/components/parameters/simulateParam'
+ responses:
+ '200':
+ description: Indicates a successful call
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/mlSync200Response'
+ examples:
+ syncExample:
+ $ref: '#/components/examples/mlSyncExample'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/mlSync4xxResponse'
+components:
+ parameters:
+ spaceParam:
+ in: path
+ name: spaceId
+ description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.
+ required: true
+ schema:
+ type: string
+ simulateParam:
+ in: query
+ name: simulate
+ description: When true, simulates the synchronization by returning only the list of actions that would be performed.
+ required: false
+ schema:
+ type: boolean
+ example: 'true'
+ securitySchemes:
+ basicAuth:
+ type: http
+ scheme: basic
+ apiKeyAuth:
+ type: apiKey
+ in: header
+ name: ApiKey
+ schemas:
+ mlSyncResponseSuccess:
+ type: boolean
+ description: The success or failure of the synchronization.
+ mlSyncResponseAnomalyDetectors:
+ type: object
+ title: Sync API response for anomaly detection jobs
+ description: The sync machine learning saved objects API response contains this object when there are anomaly detection jobs affected by the synchronization. There is an object for each relevant job, which contains the synchronization status.
+ properties:
+ success:
+ $ref: '#/components/schemas/mlSyncResponseSuccess'
+ mlSyncResponseDatafeeds:
+ type: object
+ title: Sync API response for datafeeds
+ description: The sync machine learning saved objects API response contains this object when there are datafeeds affected by the synchronization. There is an object for each relevant datafeed, which contains the synchronization status.
+ properties:
+ success:
+ $ref: '#/components/schemas/mlSyncResponseSuccess'
+ mlSyncResponseDataFrameAnalytics:
+ type: object
+ title: Sync API response for data frame analytics jobs
+ description: The sync machine learning saved objects API response contains this object when there are data frame analytics jobs affected by the synchronization. There is an object for each relevant job, which contains the synchronization status.
+ properties:
+ success:
+ $ref: '#/components/schemas/mlSyncResponseSuccess'
+ mlSyncResponseSavedObjectsCreated:
+ type: object
+ title: Sync API response for created saved objects
+ description: If saved objects are missing for machine learning jobs or trained models, they are created when you run the sync machine learning saved objects API.
+ properties:
+ anomaly-detector:
+ type: object
+ description: If saved objects are missing for anomaly detection jobs, they are created.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseAnomalyDetectors'
+ data-frame-analytics:
+ type: object
+ description: If saved objects are missing for data frame analytics jobs, they are created.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseDataFrameAnalytics'
+ trained-model:
+ type: object
+ description: If saved objects are missing for trained models, they are created.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseTrainedModels'
+ mlSyncResponseSavedObjectsDeleted:
+ type: object
+ title: Sync API response for deleted saved objects
+ description: If saved objects exist for machine learning jobs or trained models that no longer exist, they are deleted when you run the sync machine learning saved objects API.
+ properties:
+ anomaly-detector:
+ type: object
+ description: If there are saved objects exist for nonexistent anomaly detection jobs, they are deleted.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseAnomalyDetectors'
+ data-frame-analytics:
+ type: object
+ description: If there are saved objects exist for nonexistent data frame analytics jobs, they are deleted.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseDataFrameAnalytics'
+ trained-model:
+ type: object
+ description: If there are saved objects exist for nonexistent trained models, they are deleted.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseTrainedModels'
+ mlSyncResponseTrainedModels:
+ type: object
+ title: Sync API response for trained models
+ description: The sync machine learning saved objects API response contains this object when there are trained models affected by the synchronization. There is an object for each relevant trained model, which contains the synchronization status.
+ properties:
+ success:
+ $ref: '#/components/schemas/mlSyncResponseSuccess'
+ mlSync200Response:
+ type: object
+ title: Successful sync API response
+ properties:
+ datafeedsAdded:
+ type: object
+ description: If a saved object for an anomaly detection job is missing a datafeed identifier, it is added when you run the sync machine learning saved objects API.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseDatafeeds'
+ datafeedsRemoved:
+ type: object
+ description: If a saved object for an anomaly detection job references a datafeed that no longer exists, it is deleted when you run the sync machine learning saved objects API.
+ additionalProperties:
+ $ref: '#/components/schemas/mlSyncResponseDatafeeds'
+ savedObjectsCreated:
+ $ref: '#/components/schemas/mlSyncResponseSavedObjectsCreated'
+ savedObjectsDeleted:
+ $ref: '#/components/schemas/mlSyncResponseSavedObjectsDeleted'
+ mlSync4xxResponse:
+ type: object
+ title: Unsuccessful sync API response
+ properties:
+ error:
+ type: string
+ example: Unauthorized
+ message:
+ type: string
+ statusCode:
+ type: integer
+ example: 401
+ examples:
+ mlSyncExample:
+ summary: Two anomaly detection jobs required synchronization in this example.
+ value:
+ {
+ "savedObjectsCreated": {
+ "anomaly-detector": {
+ "myjob1": { "success":true },
+ "myjob2": { "success":true }
+ }
+ },
+ "savedObjectsDeleted": {},
+ "datafeedsAdded": {},
+ "datafeedsRemoved": {}
+ }
+security:
+ - basicAuth: [ ]
+ - apiKeyAuth: [ ]
\ No newline at end of file
diff --git a/x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml b/x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml
similarity index 91%
rename from x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml
rename to x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml
index ff8e3b98cda5d..0cb7f3b3d9911 100644
--- a/x-pack/plugins/ml/common/openapi/ml_apis_v3.yaml
+++ b/x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml
@@ -12,17 +12,14 @@ tags:
servers:
- url: https://localhost:5601
paths:
- /s/{spaceId}/api/ml/saved_objects/sync:
+ /api/ml/saved_objects/sync:
get:
summary: Synchronizes Kibana saved objects for machine learning jobs and trained models.
- description: >
- You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges.
- This API runs automatically when you start Kibana and periodically thereafter.
- operationId: ml-sync
+ description: This API runs automatically when you start Kibana and periodically thereafter.
+ operationId: mlSync
tags:
- ml
parameters:
- - $ref: '#/components/parameters/spaceParam'
- $ref: '#/components/parameters/simulateParam'
responses:
'200':
@@ -42,13 +39,6 @@ paths:
$ref: '#/components/schemas/mlSync4xxResponse'
components:
parameters:
- spaceParam:
- in: path
- name: spaceId
- description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.
- required: true
- schema:
- type: string
simulateParam:
in: query
name: simulate
@@ -58,9 +48,6 @@ components:
type: boolean
example: 'true'
securitySchemes:
- basicAuth:
- type: http
- scheme: basic
apiKeyAuth:
type: apiKey
in: header
@@ -183,4 +170,4 @@ components:
"datafeedsRemoved": {}
}
security:
- - basicAuth: [ ]
\ No newline at end of file
+ - apiKeyAuth: [ ]
\ No newline at end of file
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_drift_index_patterns_editor.tsx b/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_drift_index_patterns_editor.tsx
index 76cf041426758..5e388d72af92c 100644
--- a/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_drift_index_patterns_editor.tsx
+++ b/x-pack/plugins/ml/public/application/datavisualizer/data_drift/data_drift_index_patterns_editor.tsx
@@ -246,7 +246,7 @@ export function DataDriftIndexPatternsEditor({
children: (
@@ -183,6 +186,12 @@ export function DataViewEditor({
columns={columns}
pagination={pagination}
onChange={onTableChange}
+ data-test-subject={`mlDataDriftIndexPatternTable-${id ?? ''}`}
+ rowProps={(item) => {
+ return {
+ 'data-test-subj': `mlDataDriftIndexPatternTableRow row-${id}`,
+ };
+ }}
/>
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx b/x-pack/plugins/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx
index 1f414d8224578..18f3dcea8ae70 100644
--- a/x-pack/plugins/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx
+++ b/x-pack/plugins/ml/public/application/datavisualizer/data_drift/index_patterns_picker.tsx
@@ -88,6 +88,7 @@ export const DataDriftIndexOrSearchRedirect: FC = () => {
iconType="plusInCircleFilled"
onClick={() => navigateToPath(createPath(ML_PAGES.DATA_DRIFT_CUSTOM))}
disabled={!canEditDataView}
+ data-test-subj={'dataDriftCreateDataViewButton'}
>
= ({ item }) => {
: []),
{
id: 'models_map',
- 'data-test-subj': 'mlTrainedModelsMap',
+ 'data-test-subj': 'mlTrainedModelMap',
name: (
= ({ item }) => {
/>
),
content: (
-
+
{
this.setInitializationFinished();
diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts
index 66e058dfad8cf..dc5b434e0a7e9 100644
--- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts
+++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts
@@ -166,7 +166,6 @@ export enum MetricsExplorerChartType {
export enum InfraRuleType {
MetricThreshold = 'metrics.alert.threshold',
InventoryThreshold = 'metrics.alert.inventory.threshold',
- Anomaly = 'metrics.alert.anomaly',
}
export enum AlertStates {
diff --git a/x-pack/plugins/observability/dev_docs/composite_slo.md b/x-pack/plugins/observability/dev_docs/composite_slo.md
deleted file mode 100644
index 4e34933c8560e..0000000000000
--- a/x-pack/plugins/observability/dev_docs/composite_slo.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# Composite SLO
-
-Composite SLO is available from 8.9.
-
-A composite SLO is an SLO that aggregates up to 30 other SLOs, so we can get a higher view of the performance of a service.
-A composite SLO uses the rollup data of the source SLOs with the applied weight to compute its SLI and error budget consumption & remaining.
-
-We currently support only weighted average composite method. This means every source SLO is given a weight (1 to +Infinity) that we use to compute the composite SLI.
-
-When creating a composite SLO, we validate that every source SLOs are of the same time window and budgeting method.
-
-## Examples
-
-Create a composite SLO:
-
-```
-curl --request POST \
- --url http://localhost:5601/kibana/api/observability/composite_slos \
- --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' \
- --header 'Content-Type: application/json' \
- --header 'kbn-xsrf: oui' \
- --data '{
- "name": "composite slo test",
- "compositeMethod": "weightedAverage",
- "sources": [
- { "id": "f6694b30-f97c-11ed-895c-170d13e61076", "revision": 1, "weight": 2 },
- { "id": "f9072790-f97c-11ed-895c-170d13e61076", "revision": 2, "weight": 1 }
- ],
- "timeWindow": {
- "duration": "7d",
- "type": "rolling"
- },
- "budgetingMethod": "occurrences",
- "objective": {
- "target": 0.95
- }
-}'
-```
-
-
-Delete a composite SLO:
-
-```
-curl --request DELETE \
- --url http://localhost:5601/kibana/api/observability/composite_slos/7ba92850-fbd6-11ed-8eb2-037af7d0dfa6 \
- --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' \
- --header 'Content-Type: application/json' \
- --header 'kbn-xsrf: oui'
-```
-
-Update an existing composite SLO:
-
-```
-curl --request PUT \
- --url http://localhost:5601/kibana/api/observability/composite_slos/01af9e10-fbf1-11ed-83f3-01ffee47b374 \
- --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' \
- --header 'Content-Type: application/json' \
- --header 'kbn-xsrf: oui' \
- --data '{
- "name": "new composite slo name",
- "objective": {
- "target": 0.90
- }
-}'
-```
\ No newline at end of file
diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.json b/x-pack/plugins/observability/docs/openapi/slo/bundled.json
index e51f3828886cf..3ba6ab7762e93 100644
--- a/x-pack/plugins/observability/docs/openapi/slo/bundled.json
+++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.json
@@ -30,10 +30,6 @@
{
"name": "slo",
"description": "SLO APIs enable you to define, manage and track service-level objectives"
- },
- {
- "name": "composite slo",
- "description": "Composite SLO APIs enable you to define, manage and track a group of SLOs."
}
],
"paths": {
diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml
index 4b0ca84bc7c52..c50403e5096f8 100644
--- a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml
+++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml
@@ -17,8 +17,6 @@ security:
tags:
- name: slo
description: SLO APIs enable you to define, manage and track service-level objectives
- - name: composite slo
- description: Composite SLO APIs enable you to define, manage and track a group of SLOs.
paths:
/s/{spaceId}/api/observability/slos:
post:
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/parameters/composite_slo_id.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/parameters/composite_slo_id.yaml
deleted file mode 100644
index d8f698b99b214..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/parameters/composite_slo_id.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-in: path
-name: compositeSloId
-description: An identifier for the composite slo.
-required: true
-schema:
- type: string
- example: 9c235211-6834-11ea-a78c-6feb38a34414
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/base_composite_slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/base_composite_slo_response.yaml
deleted file mode 100644
index 07dd47d1707fd..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/base_composite_slo_response.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-title: Composite SLO response
-type: object
-properties:
- id:
- description: The identifier of the composite SLO.
- type: string
- example: 8853df00-ae2e-11ed-90af-09bb6422b258
- name:
- description: The name of the composite SLO.
- type: string
- example: My Service SLO
- timeWindow:
- $ref: "time_window.yaml"
- budgetingMethod:
- $ref: "budgeting_method.yaml"
- compositeMethod:
- $ref: "composite_method.yaml"
- objective:
- $ref: "objective.yaml"
- sources:
- - $ref: "weighted_composite_sources.yaml"
- createdAt:
- description: The creation date
- type: string
- example: "2023-01-12T10:03:19.000Z"
- updatedAt:
- description: The last update date
- type: string
- example: "2023-01-12T10:03:19.000Z"
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_method.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_method.yaml
deleted file mode 100644
index 0414a68a48742..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_method.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-title: Composite method
-type: string
-description: The composite method to use for the composite SLO.
-enum:
- - weightedAverage
-example: weightedAverage
\ No newline at end of file
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_objective.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_objective.yaml
deleted file mode 100644
index a8946e9e2fca2..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_objective.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-title: Objective
-required:
- - target
-description: Defines properties for objective
-type: object
-properties:
- target:
- description: the target objective between 0 and 1 excluded
- type: number
- example: 0.95
\ No newline at end of file
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_slo_response.yaml
deleted file mode 100644
index 00a9c3426756e..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_slo_response.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-title: Composite SLO with summary response
-type: object
-properties:
- id:
- description: The identifier of the composite SLO.
- type: string
- example: 8853df00-ae2e-11ed-90af-09bb6422b258
- name:
- description: The name of the composite SLO.
- type: string
- example: My Service SLO
- timeWindow:
- $ref: "time_window.yaml"
- budgetingMethod:
- $ref: "budgeting_method.yaml"
- compositeMethod:
- $ref: "composite_method.yaml"
- objective:
- $ref: "objective.yaml"
- sources:
- - $ref: "weighted_composite_sources.yaml"
- summary:
- $ref: "summary.yaml"
- createdAt:
- description: The creation date
- type: string
- example: "2023-01-12T10:03:19.000Z"
- updatedAt:
- description: The last update date
- type: string
- example: "2023-01-12T10:03:19.000Z"
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_request.yaml
deleted file mode 100644
index 97536626c1287..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_request.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-title: Create composite SLO request
-description: >
- The create Composite SLO API request body. The provided source SLOs must exists and their budgeting method and time window must match the one from the composite SLO.
-type: object
-required:
- - name
- - timeWindow
- - budgetingMethod
- - compositeMethod
- - objective
- - sources
-properties:
- id:
- description: A unique identifier for the composite SLO. Must be between 8 and 36 chars
- type: string
- example: my-super-composite-slo-id
- name:
- description: A name for the composite SLO.
- type: string
- timeWindow:
- $ref: "time_window.yaml"
- budgetingMethod:
- $ref: "budgeting_method.yaml"
- compositeMethod:
- $ref: "composite_method.yaml"
- objective:
- $ref: "objective.yaml"
- sources:
- - $ref: "weighted_composite_sources.yaml"
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_response.yaml
deleted file mode 100644
index 7a1b6286e2310..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_response.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-title: Create composite SLO response
-type: object
-required:
- - id
-properties:
- id:
- type: string
- example: 8853df00-ae2e-11ed-90af-09bb6422b258
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/find_composite_slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/find_composite_slo_response.yaml
deleted file mode 100644
index 0c6ae26e1ec7c..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/find_composite_slo_response.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-title: Find composite SLO response
-description: A paginated response of composite SLOs matching the query.
-type: object
-properties:
- page:
- type: number
- example: 1
- perPage:
- type: number
- example: 25
- total:
- type: number
- example: 34
- results:
- type: array
- items:
- $ref: 'composite_slo_response.yaml'
\ No newline at end of file
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_composite_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_composite_slo_request.yaml
deleted file mode 100644
index c93578f4404c0..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_composite_slo_request.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-title: Update composite SLO request
-description: >
- The update composite SLO API request body. The provided source SLOs must exists and their budgeting method and time window must match the one from the composite SLO.
-type: object
-properties:
- id:
- description: A unique identifier for the composite SLO. Must be between 8 and 36 chars
- type: string
- example: my-super-composite-slo-id
- name:
- description: A name for the composite SLO.
- type: string
- timeWindow:
- $ref: "time_window.yaml"
- budgetingMethod:
- $ref: "budgeting_method.yaml"
- compositeMethod:
- $ref: "composite_method.yaml"
- objective:
- $ref: "objective.yaml"
- sources:
- - $ref: "weighted_composite_sources.yaml"
diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/weighted_composite_sources.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/weighted_composite_sources.yaml
deleted file mode 100644
index 1caca49407386..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/weighted_composite_sources.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-title: Weighted sources
-description: An array of source SLO to use for the weighted average composite.
-type: "array"
-items:
- type: object
- required:
- - id
- - revision
- - weight
- properties:
- id:
- description: The id of the SLO.
- type: string
- example: 8853df00-ae2e-11ed-90af-09bb6422b258
- revision:
- description: The revision number of the SLO.
- type: number
- example: 2
- weight:
- description: The weight to apply to this SLO.
- type: number
- example: 3
\ No newline at end of file
diff --git a/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml b/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml
index ee722573efa91..687fd94f006a4 100644
--- a/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml
+++ b/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml
@@ -11,16 +11,10 @@ info:
tags:
- name: slo
description: SLO APIs enable you to define, manage and track service-level objectives
- - name: composite slo
- description: Composite SLO APIs enable you to define, manage and track a group of SLOs.
servers:
- url: "http://localhost:5601"
description: local
paths:
- #'/s/{spaceId}/api/observability/composite_slos':
- # $ref: 'paths/s@{spaceid}@api@composite_slos.yaml'
- #'/s/{spaceId}/api/observability/composite_slos/{compositeSloId}':
- # $ref: 'paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml'
"/s/{spaceId}/api/observability/slos":
$ref: "paths/s@{spaceid}@api@slos.yaml"
"/s/{spaceId}/api/observability/slos/{sloId}":
diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos.yaml
deleted file mode 100644
index 94230cde86002..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos.yaml
+++ /dev/null
@@ -1,121 +0,0 @@
-post:
- summary: Creates a Composite SLO
- operationId: createCompositeSloOp
- description: >
- You must have `all` privileges for the **SLOs** feature in the
- **Observability** section of the Kibana feature privileges.
- tags:
- - composite slo
- parameters:
- - $ref: ../components/headers/kbn_xsrf.yaml
- - $ref: ../components/parameters/space_id.yaml
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '../components/schemas/create_composite_slo_request.yaml'
- responses:
- '200':
- description: Successful request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/create_composite_slo_response.yaml'
- '400':
- description: Bad request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/400_response.yaml'
- '401':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/401_response.yaml'
- '403':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/403_response.yaml'
- '409':
- description: Conflict - The Composite SLO id already exists
- content:
- application/json:
- schema:
- $ref: '../components/schemas/409_response.yaml'
-
-get:
- summary: Retrieves a paginated list of composite SLOs with summary
- operationId: findCompositeSloOp
- description: >
- You must have the `read` privileges for the **SLOs** feature in the
- **Observability** section of the Kibana feature privileges.
- tags:
- - composite slo
- parameters:
- - $ref: ../components/headers/kbn_xsrf.yaml
- - $ref: ../components/parameters/space_id.yaml
- - name: page
- in: query
- description: The page number to return
- schema:
- type: integer
- default: 1
- example: 1
- - name: perPage
- in: query
- description: The number of SLOs to return per page
- schema:
- type: integer
- default: 25
- example: 20
- - name: sortBy
- in: query
- description: Sort by field
- schema:
- type: string
- enum: [creationTime]
- default: creationTime
- example: creationTime
- - name: sortDirection
- in: query
- description: Sort order
- schema:
- type: string
- enum: [asc, desc]
- default: asc
- example: asc
- responses:
- '200':
- description: Successful request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/find_composite_slo_response.yaml'
- '400':
- description: Bad request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/400_response.yaml'
- '401':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/401_response.yaml'
- '403':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/403_response.yaml'
- '404':
- description: Not found response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/404_response.yaml'
diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml
deleted file mode 100644
index 826ed87a27cd8..0000000000000
--- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml
+++ /dev/null
@@ -1,133 +0,0 @@
-get:
- summary: Retrieves a composite SLO
- operationId: getCompositeSloOp
- description: >
- You must have the `read` privileges for the **SLOs** feature in the
- **Observability** section of the Kibana feature privileges.
- tags:
- - composite slo
- parameters:
- - $ref: ../components/headers/kbn_xsrf.yaml
- - $ref: ../components/parameters/space_id.yaml
- - $ref: ../components/parameters/composite_slo_id.yaml
- responses:
- '200':
- description: Successful request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/composite_slo_response.yaml'
- '400':
- description: Bad request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/400_response.yaml'
- '401':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/401_response.yaml'
- '403':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/403_response.yaml'
- '404':
- description: Not found response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/404_response.yaml'
-
-put:
- summary: Updates a composite SLO
- operationId: updateCompositeSloOp
- description: >
- You must have the `write` privileges for the **SLOs** feature in the
- **Observability** section of the Kibana feature privileges.
- tags:
- - composite slo
- parameters:
- - $ref: ../components/headers/kbn_xsrf.yaml
- - $ref: ../components/parameters/space_id.yaml
- - $ref: ../components/parameters/composite_slo_id.yaml
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '../components/schemas/update_composite_slo_request.yaml'
- responses:
- '200':
- description: Successful request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/base_composite_slo_response.yaml'
- '400':
- description: Bad request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/400_response.yaml'
- '401':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/401_response.yaml'
- '403':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/403_response.yaml'
- '404':
- description: Not found response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/404_response.yaml'
-
-delete:
- summary: Deletes a composite SLO
- operationId: deleteCompositeSloOp
- description: >
- You must have the `write` privileges for the **SLOs** feature in the
- **Observability** section of the Kibana feature privileges.
- tags:
- - composite slo
- parameters:
- - $ref: ../components/headers/kbn_xsrf.yaml
- - $ref: ../components/parameters/space_id.yaml
- - $ref: ../components/parameters/composite_slo_id.yaml
- responses:
- '204':
- description: Successful request
- '400':
- description: Bad request
- content:
- application/json:
- schema:
- $ref: '../components/schemas/400_response.yaml'
- '401':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/401_response.yaml'
- '403':
- description: Unauthorized response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/403_response.yaml'
- '404':
- description: Not found response
- content:
- application/json:
- schema:
- $ref: '../components/schemas/404_response.yaml'
diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx
index 2d251ac71660e..40724d6db5b9f 100644
--- a/x-pack/plugins/observability/public/application/index.tsx
+++ b/x-pack/plugins/observability/public/application/index.tsx
@@ -14,8 +14,9 @@ import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { AppMountParameters, APP_WRAPPER_CLASS, CoreStart } from '@kbn/core/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public';
-import { KibanaContextProvider, RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
+import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public';
@@ -111,8 +112,7 @@ export const renderApp = ({
diff --git a/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts b/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts
index 2175f67e807fb..feec4d475e3a8 100644
--- a/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts
@@ -14,12 +14,6 @@ interface SloListFilter {
sortDirection: string;
}
-interface CompositeSloKeyFilter {
- name: string;
- page: number;
- sortBy: string;
-}
-
export const sloKeys = {
all: ['slo'] as const,
lists: () => [...sloKeys.all, 'list'] as const,
@@ -41,20 +35,4 @@ export const sloKeys = {
preview: (indicator?: Indicator) => [...sloKeys.all, 'preview', indicator] as const,
};
-export const compositeSloKeys = {
- all: ['compositeSlo'] as const,
- lists: () => [...compositeSloKeys.all, 'list'] as const,
- list: (filters: CompositeSloKeyFilter) => [...compositeSloKeys.lists(), filters] as const,
- details: () => [...compositeSloKeys.all, 'details'] as const,
- detail: (sloId?: string) => [...compositeSloKeys.details(), sloId] as const,
- rules: () => [...compositeSloKeys.all, 'rules'] as const,
- rule: (sloIds: string[]) => [...compositeSloKeys.rules(), sloIds] as const,
- activeAlerts: () => [...compositeSloKeys.all, 'activeAlerts'] as const,
- activeAlert: (sloIds: string[]) => [...compositeSloKeys.activeAlerts(), sloIds] as const,
- historicalSummaries: () => [...compositeSloKeys.all, 'historicalSummary'] as const,
- historicalSummary: (sloIds: string[]) =>
- [...compositeSloKeys.historicalSummaries(), sloIds] as const,
- globalDiagnosis: () => [...compositeSloKeys.all, 'globalDiagnosis'] as const,
-};
-
-export type SloKeys = typeof compositeSloKeys | typeof sloKeys;
+export type SloKeys = typeof sloKeys;
diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_composite_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_composite_slo_list.ts
deleted file mode 100644
index 55ac51648e55d..0000000000000
--- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_composite_slo_list.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { useState } from 'react';
-import {
- QueryObserverResult,
- RefetchOptions,
- RefetchQueryFilters,
- useQuery,
- useQueryClient,
-} from '@tanstack/react-query';
-import { i18n } from '@kbn/i18n';
-import { FindCompositeSLOResponse } from '@kbn/slo-schema';
-
-import { useKibana } from '../../utils/kibana_react';
-import { compositeSloKeys } from './query_key_factory';
-
-interface SLOListParams {
- name?: string;
- page?: number;
- sortBy?: string;
- shouldRefetch?: boolean;
-}
-
-export interface UseFetchCompositeSloListResponse {
- isInitialLoading: boolean;
- isLoading: boolean;
- isRefetching: boolean;
- isSuccess: boolean;
- isError: boolean;
- sloList: FindCompositeSLOResponse | undefined;
- refetch: (
- options?: (RefetchOptions & RefetchQueryFilters) | undefined
- ) => Promise>;
-}
-
-const SHORT_REFETCH_INTERVAL = 1000 * 5; // 5 seconds
-const LONG_REFETCH_INTERVAL = 1000 * 60; // 1 minute
-
-export function useFetchCompositeSloList({
- name = '',
- page = 1,
- sortBy = 'creationTime',
- shouldRefetch,
-}: SLOListParams | undefined = {}): UseFetchCompositeSloListResponse {
- const {
- http,
- notifications: { toasts },
- } = useKibana().services;
- const queryClient = useQueryClient();
-
- const [stateRefetchInterval, setStateRefetchInterval] = useState(
- SHORT_REFETCH_INTERVAL
- );
-
- const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery(
- {
- queryKey: compositeSloKeys.list({ name, page, sortBy }),
- queryFn: async ({ signal }) => {
- try {
- const response = await http.get(`/api/observability/slos`, {
- query: {
- ...(page && { page }),
- ...(name && { name }),
- ...(sortBy && { sortBy }),
- },
- signal,
- });
-
- return response;
- } catch (error) {
- throw error;
- }
- },
- keepPreviousData: true,
- refetchOnWindowFocus: false,
- refetchInterval: shouldRefetch ? stateRefetchInterval : undefined,
- staleTime: 1000,
- retry: (failureCount, error) => {
- if (String(error) === 'Error: Forbidden') {
- return false;
- }
- return failureCount < 4;
- },
- onSuccess: ({ results }: FindCompositeSLOResponse) => {
- if (!shouldRefetch) {
- return;
- }
-
- if (results.find((slo) => slo.summary.status === 'NO_DATA' || !slo.summary)) {
- setStateRefetchInterval(SHORT_REFETCH_INTERVAL);
- } else {
- setStateRefetchInterval(LONG_REFETCH_INTERVAL);
- }
-
- queryClient.invalidateQueries(compositeSloKeys.historicalSummaries());
- queryClient.invalidateQueries(compositeSloKeys.activeAlerts());
- queryClient.invalidateQueries(compositeSloKeys.rules());
- },
- onError: (error: Error) => {
- toasts.addError(error, {
- title: i18n.translate('xpack.observability.slo.list.errorNotification', {
- defaultMessage: 'Something went wrong while fetching SLOs',
- }),
- });
- },
- }
- );
-
- return {
- sloList: data,
- isInitialLoading,
- isLoading,
- isRefetching,
- isSuccess,
- isError,
- refetch,
- };
-}
diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts.test.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts.test.tsx
index b307c0d858f12..247a700acfa6c 100644
--- a/x-pack/plugins/observability/public/pages/alerts/alerts.test.tsx
+++ b/x-pack/plugins/observability/public/pages/alerts/alerts.test.tsx
@@ -60,9 +60,6 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({
},
thresholdRule: { enabled: false },
},
- compositeSlo: {
- enabled: false,
- },
aiAssistant: {
enabled: false,
feedback: {
diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
index df869f41c6735..d0937d1f2c72b 100644
--- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
+++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
@@ -88,7 +88,6 @@ const withCore = makeDecorator({
},
thresholdRule: { enabled: false },
},
- compositeSlo: { enabled: false },
};
return (
diff --git a/x-pack/plugins/observability/public/pages/rules/rules.test.tsx b/x-pack/plugins/observability/public/pages/rules/rules.test.tsx
index 7a5fa20afa741..4239cdeee811e 100644
--- a/x-pack/plugins/observability/public/pages/rules/rules.test.tsx
+++ b/x-pack/plugins/observability/public/pages/rules/rules.test.tsx
@@ -47,9 +47,6 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({
},
thresholdRule: { enabled: false },
},
- compositeSlo: {
- enabled: false,
- },
},
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
ObservabilityPageTemplate: KibanaPageTemplate,
diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx
index 19c27928b292a..0fb29d5980ccb 100644
--- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx
@@ -258,7 +258,7 @@ export function SloEditForm({ slo }: Props) {
;
diff --git a/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx b/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx
index 3cebd71109ba6..d5714924bdc97 100644
--- a/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx
+++ b/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx
@@ -35,7 +35,6 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) {
},
thresholdRule: { enabled: false },
},
- compositeSlo: { enabled: false },
};
const mockTheme: CoreTheme = {
diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx
index 2f8035ae7be54..6c1610ac059c9 100644
--- a/x-pack/plugins/observability/public/utils/test_helper.tsx
+++ b/x-pack/plugins/observability/public/utils/test_helper.tsx
@@ -39,7 +39,6 @@ const defaultConfig: ConfigSchema = {
},
thresholdRule: { enabled: false },
},
- compositeSlo: { enabled: false },
};
const queryClient = new QueryClient({
diff --git a/x-pack/plugins/observability/server/domain/models/composite_slo.ts b/x-pack/plugins/observability/server/domain/models/composite_slo.ts
deleted file mode 100644
index aa70d5a9a0b2b..0000000000000
--- a/x-pack/plugins/observability/server/domain/models/composite_slo.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import * as t from 'io-ts';
-
-import {
- compositeSloSchema,
- compositeSloIdSchema,
- weightedAverageSourceSchema,
-} from '@kbn/slo-schema';
-
-type CompositeSLO = t.TypeOf;
-type CompositeSLOId = t.TypeOf;
-type StoredCompositeSLO = t.OutputOf;
-
-type WeightedAverageSource = t.TypeOf;
-
-export type { CompositeSLO, CompositeSLOId, StoredCompositeSLO, WeightedAverageSource };
diff --git a/x-pack/plugins/observability/server/domain/models/index.ts b/x-pack/plugins/observability/server/domain/models/index.ts
index a22e0cbdd95ac..16336c40928f8 100644
--- a/x-pack/plugins/observability/server/domain/models/index.ts
+++ b/x-pack/plugins/observability/server/domain/models/index.ts
@@ -11,4 +11,3 @@ export * from './error_budget';
export * from './indicators';
export * from './slo';
export * from './time_window';
-export * from './composite_slo';
diff --git a/x-pack/plugins/observability/server/domain/services/composite_slo/index.ts b/x-pack/plugins/observability/server/domain/services/composite_slo/index.ts
deleted file mode 100644
index cae963f894602..0000000000000
--- a/x-pack/plugins/observability/server/domain/services/composite_slo/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export * from './validate_composite_slo';
diff --git a/x-pack/plugins/observability/server/domain/services/composite_slo/validate_composite_slo.test.ts b/x-pack/plugins/observability/server/domain/services/composite_slo/validate_composite_slo.test.ts
deleted file mode 100644
index 1bf5772b2a6b9..0000000000000
--- a/x-pack/plugins/observability/server/domain/services/composite_slo/validate_composite_slo.test.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { validateCompositeSLO } from '.';
-import {
- createCompositeSLO,
- createWeightedAverageSource,
-} from '../../../services/composite_slo/fixtures/composite_slo';
-import { fiveMinute, twoMinute } from '../../../services/slo/fixtures/duration';
-import {
- createSLO,
- createSLOWithTimeslicesBudgetingMethod,
-} from '../../../services/slo/fixtures/slo';
-import {
- sevenDaysRolling,
- thirtyDaysRolling,
- weeklyCalendarAligned,
-} from '../../../services/slo/fixtures/time_window';
-
-describe('validateCompositeSLO', () => {
- it('throws when the number of source SLOs is less than 2', () => {
- const compositeSlo = createCompositeSLO({
- sources: [createWeightedAverageSource()],
- });
- expect(() => validateCompositeSLO(compositeSlo, [])).toThrowError(
- 'A composite SLO must contain between 2 and 30 source SLOs.'
- );
- });
-
- it('throws when the number of source SLOs is more than 30', () => {
- const compositeSlo = createCompositeSLO({
- sources: Array(31)
- .fill(0)
- .map((i) => createWeightedAverageSource()),
- });
- expect(() => validateCompositeSLO(compositeSlo, [])).toThrowError(
- 'A composite SLO must contain between 2 and 30 source SLOs.'
- );
- });
-
- it("throws when specified source SLOs don't match the actual SLO revision", () => {
- const sloOne = createSLO({ revision: 3 });
- const sloTwo = createSLO({ revision: 2 });
- const compositeSlo = createCompositeSLO({
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: 1 }),
- ],
- });
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).toThrowError(
- 'One or many source SLOs are not matching the specified id and revision.'
- );
- });
-
- it('throws when specified source SLOs refers to a non-existant SLO', () => {
- const sloOne = createSLO({ revision: 3 });
- const compositeSlo = createCompositeSLO({
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: 'non-existant' }),
- ],
- });
- expect(() => validateCompositeSLO(compositeSlo, [sloOne])).toThrowError(
- 'One or many source SLOs are not matching the specified id and revision.'
- );
- });
-
- it('throws when the time window is not the same accros all source SLOs', () => {
- const sloOne = createSLO({ timeWindow: sevenDaysRolling() });
- const sloTwo = createSLO({ timeWindow: weeklyCalendarAligned() });
- const compositeSlo = createCompositeSLO({
- timeWindow: sevenDaysRolling(),
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: sloTwo.revision }),
- ],
- });
-
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).toThrowError(
- 'Invalid time window. Every source SLO must use the same time window as the composite.'
- );
- });
-
- it('throws when the time window duration is not the same accros all source SLOs', () => {
- const sloOne = createSLO({ timeWindow: sevenDaysRolling() });
- const sloTwo = createSLO({ timeWindow: thirtyDaysRolling() });
- const compositeSlo = createCompositeSLO({
- timeWindow: sevenDaysRolling(),
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: sloTwo.revision }),
- ],
- });
-
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).toThrowError(
- 'Invalid time window. Every source SLO must use the same time window as the composite.'
- );
- });
-
- it('throws when the budgeting method is not the same accros all source SLOs', () => {
- const sloOne = createSLO({ budgetingMethod: 'occurrences' });
- const sloTwo = createSLO({ budgetingMethod: 'timeslices' });
- const compositeSlo = createCompositeSLO({
- budgetingMethod: 'occurrences',
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: sloTwo.revision }),
- ],
- });
-
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).toThrowError(
- 'Invalid budgeting method. Every source SLO must use the same budgeting method as the composite.'
- );
- });
-
- it('throws when the timeslices window is not defined on the composite SLO', () => {
- const sloOne = createSLO({
- budgetingMethod: 'timeslices',
- objective: { target: 0.98, timesliceTarget: 0.95, timesliceWindow: fiveMinute() },
- });
- const sloTwo = createSLO({
- budgetingMethod: 'timeslices',
- objective: { target: 0.98, timesliceTarget: 0.95, timesliceWindow: fiveMinute() },
- });
-
- const compositeSlo = createCompositeSLO({
- budgetingMethod: 'timeslices',
- objective: {
- target: 0.9,
- },
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: sloTwo.revision }),
- ],
- });
-
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).toThrowError(
- 'Invalid timeslices objective. A timeslice window must be set and equal to all source SLO.'
- );
- });
-
- it('throws when the timeslices window is not the same accros all source SLOs', () => {
- const sloOne = createSLO({
- budgetingMethod: 'timeslices',
- objective: { target: 0.98, timesliceTarget: 0.95, timesliceWindow: twoMinute() },
- });
- const sloTwo = createSLO({
- budgetingMethod: 'timeslices',
- objective: { target: 0.98, timesliceTarget: 0.95, timesliceWindow: fiveMinute() },
- });
-
- const compositeSlo = createCompositeSLO({
- budgetingMethod: 'timeslices',
- objective: {
- target: 0.9,
- timesliceTarget: 0.95,
- timesliceWindow: fiveMinute(),
- },
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: sloTwo.revision }),
- ],
- });
-
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).toThrowError(
- 'Invalid budgeting method. Every source SLO must use the same timeslice window.'
- );
- });
-
- describe('happy flow', () => {
- it('throws nothing', () => {
- const sloOne = createSLO({
- budgetingMethod: 'occurrences',
- timeWindow: sevenDaysRolling(),
- revision: 2,
- });
- const sloTwo = createSLO({
- budgetingMethod: 'occurrences',
- timeWindow: sevenDaysRolling(),
- revision: 3,
- });
- const compositeSlo = createCompositeSLO({
- budgetingMethod: 'occurrences',
- timeWindow: sevenDaysRolling(),
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: sloTwo.revision }),
- ],
- });
-
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).not.toThrow();
- });
-
- it('throws nothing in case of timeslices source SLOs', () => {
- const sloOne = createSLOWithTimeslicesBudgetingMethod();
- const sloTwo = createSLOWithTimeslicesBudgetingMethod();
- const compositeSlo = createCompositeSLO({
- budgetingMethod: 'timeslices',
- objective: {
- target: 0.98,
- timesliceTarget: 0.95,
- timesliceWindow: twoMinute(),
- },
- timeWindow: sevenDaysRolling(),
- sources: [
- createWeightedAverageSource({ id: sloOne.id, revision: sloOne.revision }),
- createWeightedAverageSource({ id: sloTwo.id, revision: sloTwo.revision }),
- ],
- });
-
- expect(() => validateCompositeSLO(compositeSlo, [sloOne, sloTwo])).not.toThrow();
- });
- });
-});
diff --git a/x-pack/plugins/observability/server/domain/services/composite_slo/validate_composite_slo.ts b/x-pack/plugins/observability/server/domain/services/composite_slo/validate_composite_slo.ts
deleted file mode 100644
index dd6edaf746c73..0000000000000
--- a/x-pack/plugins/observability/server/domain/services/composite_slo/validate_composite_slo.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { calendarAlignedTimeWindowSchema, rollingTimeWindowSchema } from '@kbn/slo-schema';
-import { IllegalArgumentError } from '../../../errors';
-import { SLO } from '../../models';
-import { CompositeSLO } from '../../models/composite_slo';
-
-export function validateCompositeSLO(compositeSlo: CompositeSLO, sloList: SLO[]) {
- assertNumberOfSourceSlo(compositeSlo);
- assertMatchingSloList(compositeSlo, sloList);
- assertSameBudgetingMethod(compositeSlo, sloList);
- assertSameTimeWindow(compositeSlo, sloList);
-}
-
-function assertNumberOfSourceSlo(compositeSlo: CompositeSLO) {
- if (compositeSlo.sources.length < 2 || compositeSlo.sources.length > 30) {
- throw new IllegalArgumentError('A composite SLO must contain between 2 and 30 source SLOs.');
- }
-}
-
-function assertMatchingSloList(compositeSlo: CompositeSLO, sloList: SLO[]) {
- const everySourceSloMatches = compositeSlo.sources.every((sourceSlo) =>
- sloList.find((slo) => sourceSlo.id === slo.id && sourceSlo.revision === slo.revision)
- );
-
- if (!everySourceSloMatches) {
- throw new IllegalArgumentError(
- 'One or many source SLOs are not matching the specified id and revision.'
- );
- }
-}
-
-function assertSameBudgetingMethod(compositeSlo: CompositeSLO, sloList: SLO[]) {
- const haveSameBudgetingMethod = sloList.every(
- (slo) => slo.budgetingMethod === compositeSlo.budgetingMethod
- );
-
- if (compositeSlo.budgetingMethod === 'timeslices') {
- if (compositeSlo.objective.timesliceWindow === undefined) {
- throw new IllegalArgumentError(
- 'Invalid timeslices objective. A timeslice window must be set and equal to all source SLO.'
- );
- }
- const haveSameTimesliceWindow = sloList.every((slo) =>
- slo.objective.timesliceWindow?.isEqual(compositeSlo.objective.timesliceWindow!)
- );
- if (!haveSameTimesliceWindow) {
- throw new IllegalArgumentError(
- 'Invalid budgeting method. Every source SLO must use the same timeslice window.'
- );
- }
- }
-
- if (!haveSameBudgetingMethod) {
- throw new IllegalArgumentError(
- 'Invalid budgeting method. Every source SLO must use the same budgeting method as the composite.'
- );
- }
-}
-
-function assertSameTimeWindow(compositeSlo: CompositeSLO, sloList: SLO[]) {
- let haveSameTimeWindow = false;
- if (rollingTimeWindowSchema.is(compositeSlo.timeWindow)) {
- haveSameTimeWindow = sloList.every(
- (slo) =>
- slo.timeWindow.duration.isEqual(compositeSlo.timeWindow.duration) &&
- rollingTimeWindowSchema.is(slo.timeWindow)
- );
- }
-
- if (calendarAlignedTimeWindowSchema.is(compositeSlo.timeWindow)) {
- haveSameTimeWindow = sloList.every(
- (slo) =>
- slo.timeWindow.duration.isEqual(compositeSlo.timeWindow.duration) &&
- calendarAlignedTimeWindowSchema.is(slo.timeWindow)
- );
- }
-
- if (!haveSameTimeWindow) {
- throw new IllegalArgumentError(
- 'Invalid time window. Every source SLO must use the same time window as the composite.'
- );
- }
-}
diff --git a/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts b/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts
index 77ada8cd9c233..3aaaffe180b6a 100644
--- a/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts
+++ b/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts
@@ -5,13 +5,9 @@
* 2.0.
*/
-import { CompositeSLO, ErrorBudget, SLO, Status } from '../models';
+import { ErrorBudget, SLO, Status } from '../models';
-export function computeSummaryStatus(
- slo: SLO | CompositeSLO,
- sliValue: number,
- errorBudget: ErrorBudget
-): Status {
+export function computeSummaryStatus(slo: SLO, sliValue: number, errorBudget: ErrorBudget): Status {
if (sliValue === -1) {
return 'NO_DATA';
}
diff --git a/x-pack/plugins/observability/server/errors/errors.ts b/x-pack/plugins/observability/server/errors/errors.ts
index cbecb88d9ce05..eaec36e66d08b 100644
--- a/x-pack/plugins/observability/server/errors/errors.ts
+++ b/x-pack/plugins/observability/server/errors/errors.ts
@@ -17,9 +17,6 @@ export class ObservabilityError extends Error {
export class SLONotFound extends ObservabilityError {}
export class SLOIdConflict extends ObservabilityError {}
-export class CompositeSLONotFound extends ObservabilityError {}
-export class CompositeSLOIdConflict extends ObservabilityError {}
-
export class InvalidQueryError extends ObservabilityError {}
export class InternalQueryError extends ObservabilityError {}
export class NotSupportedError extends ObservabilityError {}
diff --git a/x-pack/plugins/observability/server/errors/handler.ts b/x-pack/plugins/observability/server/errors/handler.ts
index 2898e53624832..c10f1d98c083e 100644
--- a/x-pack/plugins/observability/server/errors/handler.ts
+++ b/x-pack/plugins/observability/server/errors/handler.ts
@@ -5,21 +5,14 @@
* 2.0.
*/
-import {
- CompositeSLOIdConflict,
- CompositeSLONotFound,
- ObservabilityError,
- SecurityException,
- SLOIdConflict,
- SLONotFound,
-} from './errors';
+import { ObservabilityError, SecurityException, SLOIdConflict, SLONotFound } from './errors';
export function getHTTPResponseCode(error: ObservabilityError): number {
- if (error instanceof SLONotFound || error instanceof CompositeSLONotFound) {
+ if (error instanceof SLONotFound) {
return 404;
}
- if (error instanceof SLOIdConflict || error instanceof CompositeSLOIdConflict) {
+ if (error instanceof SLOIdConflict) {
return 409;
}
diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts
index e2e6bc768918d..e05740ea9785c 100644
--- a/x-pack/plugins/observability/server/index.ts
+++ b/x-pack/plugins/observability/server/index.ts
@@ -57,9 +57,6 @@ const configSchema = schema.object({
groupByPageSize: schema.number({ defaultValue: 10_000 }),
}),
enabled: schema.boolean({ defaultValue: true }),
- compositeSlo: schema.object({
- enabled: schema.boolean({ defaultValue: false }),
- }),
});
export const config: PluginConfigDescriptor = {
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts
index cc63f2cd688d3..719eddbd0e646 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts
@@ -14,7 +14,6 @@ import { TimeUnitChar } from '../../../../common';
export enum InfraRuleType {
MetricThreshold = 'metrics.alert.threshold',
InventoryThreshold = 'metrics.alert.inventory.threshold',
- Anomaly = 'metrics.alert.anomaly',
}
export enum AlertStates {
diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts
index 9d35ced6cb648..b4a81fb11e850 100644
--- a/x-pack/plugins/observability/server/plugin.ts
+++ b/x-pack/plugins/observability/server/plugin.ts
@@ -43,7 +43,7 @@ import { registerSloUsageCollector } from './lib/collectors/register';
import { registerRuleTypes } from './lib/rules/register_rule_types';
import { getObservabilityServerRouteRepository } from './routes/get_global_observability_server_route_repository';
import { registerRoutes } from './routes/register_routes';
-import { compositeSlo, slo, SO_COMPOSITE_SLO_TYPE, SO_SLO_TYPE } from './saved_objects';
+import { slo, SO_SLO_TYPE } from './saved_objects';
import { threshold } from './saved_objects/threshold';
import {
DefaultResourceInstaller,
@@ -182,9 +182,7 @@ export class ObservabilityPlugin implements Plugin {
const { ruleDataService } = plugins.ruleRegistry;
- const savedObjectTypes = config.compositeSlo.enabled
- ? [SO_SLO_TYPE, SO_COMPOSITE_SLO_TYPE]
- : [SO_SLO_TYPE];
+ const savedObjectTypes = [SO_SLO_TYPE];
plugins.features.registerKibanaFeature({
id: sloFeatureId,
name: i18n.translate('xpack.observability.featureRegistry.linkSloTitle', {
@@ -236,9 +234,6 @@ export class ObservabilityPlugin implements Plugin {
});
core.savedObjects.registerType(slo);
- if (config.compositeSlo.enabled) {
- core.savedObjects.registerType(compositeSlo);
- }
core.savedObjects.registerType(threshold);
registerRuleTypes(
diff --git a/x-pack/plugins/observability/server/routes/composite_slo/route.ts b/x-pack/plugins/observability/server/routes/composite_slo/route.ts
deleted file mode 100644
index e1259e3f64ce2..0000000000000
--- a/x-pack/plugins/observability/server/routes/composite_slo/route.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { badRequest } from '@hapi/boom';
-import {
- createCompositeSLOParamsSchema,
- deleteCompositeSLOParamsSchema,
- findCompositeSLOParamsSchema,
- getCompositeSLOParamsSchema,
- updateCompositeSLOParamsSchema,
-} from '@kbn/slo-schema';
-
-import {
- CreateCompositeSLO,
- DefaultSummaryClient,
- DeleteCompositeSLO,
- FindCompositeSLO,
- KibanaSavedObjectsCompositeSLORepository,
- UpdateCompositeSLO,
-} from '../../services/composite_slo';
-import { GetCompositeSLO } from '../../services/composite_slo/get_composite_slo';
-import { KibanaSavedObjectsSLORepository } from '../../services/slo';
-import { ObservabilityRequestHandlerContext } from '../../types';
-import { createObservabilityServerRoute } from '../create_observability_server_route';
-
-const assertLicenseAtLeastPlatinum = async (context: ObservabilityRequestHandlerContext) => {
- const { license } = await context.licensing;
- if (!license.hasAtLeast('platinum')) {
- throw badRequest('Platinum license or higher is needed to make use of this feature.');
- }
-};
-
-const createCompositeSLORoute = createObservabilityServerRoute({
- endpoint: 'POST /api/observability/composite_slos 2023-05-24',
- options: {
- tags: ['access:slo_write'],
- },
- params: createCompositeSLOParamsSchema,
- handler: async ({ context, params }) => {
- await assertLicenseAtLeastPlatinum(context);
-
- const soClient = (await context.core).savedObjects.client;
- const compositeSloRepository = new KibanaSavedObjectsCompositeSLORepository(soClient);
- const sloRepository = new KibanaSavedObjectsSLORepository(soClient);
- const createCompositeSLO = new CreateCompositeSLO(compositeSloRepository, sloRepository);
-
- const response = await createCompositeSLO.execute(params.body);
-
- return response;
- },
-});
-
-const updateCompositeSLORoute = createObservabilityServerRoute({
- endpoint: 'PUT /api/observability/composite_slos/{id} 2023-05-24',
- options: {
- tags: ['access:slo_write'],
- },
- params: updateCompositeSLOParamsSchema,
- handler: async ({ context, params }) => {
- await assertLicenseAtLeastPlatinum(context);
-
- const soClient = (await context.core).savedObjects.client;
- const compositeSloRepository = new KibanaSavedObjectsCompositeSLORepository(soClient);
- const sloRepository = new KibanaSavedObjectsSLORepository(soClient);
- const updateCompositeSLO = new UpdateCompositeSLO(compositeSloRepository, sloRepository);
-
- const response = await updateCompositeSLO.execute(params.path.id, params.body);
-
- return response;
- },
-});
-
-const deleteCompositeSLORoute = createObservabilityServerRoute({
- endpoint: 'DELETE /api/observability/composite_slos/{id} 2023-05-24',
- options: {
- tags: ['access:slo_write'],
- },
- params: deleteCompositeSLOParamsSchema,
- handler: async ({ context, params }) => {
- await assertLicenseAtLeastPlatinum(context);
-
- const soClient = (await context.core).savedObjects.client;
- const compositeSloRepository = new KibanaSavedObjectsCompositeSLORepository(soClient);
- const deleteCompositeSLO = new DeleteCompositeSLO(compositeSloRepository);
-
- await deleteCompositeSLO.execute(params.path.id);
- },
-});
-
-const getCompositeSLORoute = createObservabilityServerRoute({
- endpoint: 'GET /api/observability/composite_slos/{id} 2023-05-24',
- options: {
- tags: ['access:slo_read'],
- },
- params: getCompositeSLOParamsSchema,
- handler: async ({ context, params }) => {
- await assertLicenseAtLeastPlatinum(context);
-
- const soClient = (await context.core).savedObjects.client;
- const esClient = (await context.core).elasticsearch.client.asCurrentUser;
-
- const compositeSloRepository = new KibanaSavedObjectsCompositeSLORepository(soClient);
- const summaryClient = new DefaultSummaryClient(esClient);
- const getCompositeSlo = new GetCompositeSLO(compositeSloRepository, summaryClient);
-
- const response = await getCompositeSlo.execute(params.path.id);
-
- return response;
- },
-});
-
-const findCompositeSLORoute = createObservabilityServerRoute({
- endpoint: 'GET /api/observability/composite_slos 2023-05-24',
- options: {
- tags: ['access:slo_read'],
- },
- params: findCompositeSLOParamsSchema,
- handler: async ({ context, params }) => {
- await assertLicenseAtLeastPlatinum(context);
-
- const soClient = (await context.core).savedObjects.client;
- const esClient = (await context.core).elasticsearch.client.asCurrentUser;
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClient);
- const summaryClient = new DefaultSummaryClient(esClient);
- const findCompositeSlo = new FindCompositeSLO(repository, summaryClient);
-
- const response = await findCompositeSlo.execute(params?.query ?? {});
-
- return response;
- },
-});
-
-export const compositeSloRouteRepository = {
- ...createCompositeSLORoute,
- ...updateCompositeSLORoute,
- ...deleteCompositeSLORoute,
- ...getCompositeSLORoute,
- ...findCompositeSLORoute,
-};
diff --git a/x-pack/plugins/observability/server/routes/get_global_observability_server_route_repository.ts b/x-pack/plugins/observability/server/routes/get_global_observability_server_route_repository.ts
index 53f9ffcb750db..d6a71120bb37b 100644
--- a/x-pack/plugins/observability/server/routes/get_global_observability_server_route_repository.ts
+++ b/x-pack/plugins/observability/server/routes/get_global_observability_server_route_repository.ts
@@ -6,17 +6,13 @@
*/
import { ObservabilityConfig } from '..';
-import { compositeSloRouteRepository } from './composite_slo/route';
import { rulesRouteRepository } from './rules/route';
import { sloRouteRepository } from './slo/route';
export function getObservabilityServerRouteRepository(config: ObservabilityConfig) {
- const isCompositeSloFeatureEnabled = config.compositeSlo.enabled;
-
const repository = {
...rulesRouteRepository,
...sloRouteRepository,
- ...(isCompositeSloFeatureEnabled ? compositeSloRouteRepository : {}),
};
return repository;
}
diff --git a/x-pack/plugins/observability/server/saved_objects/composite_slo.ts b/x-pack/plugins/observability/server/saved_objects/composite_slo.ts
deleted file mode 100644
index 3261cb4b64dc4..0000000000000
--- a/x-pack/plugins/observability/server/saved_objects/composite_slo.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { SavedObjectsType } from '@kbn/core-saved-objects-server';
-import { SavedObject } from '@kbn/core/server';
-
-import { StoredCompositeSLO } from '../domain/models/composite_slo';
-
-export const SO_COMPOSITE_SLO_TYPE = 'composite-slo';
-
-export const compositeSlo: SavedObjectsType = {
- name: SO_COMPOSITE_SLO_TYPE,
- hidden: false,
- namespaceType: 'multiple-isolated',
- mappings: {
- dynamic: false,
- properties: {
- id: { type: 'keyword' },
- name: { type: 'text' },
- budgetingMethod: { type: 'keyword' },
- compositeMethod: { type: 'keyword' },
- sources: {
- properties: {
- id: { type: 'keyword' },
- revision: { type: 'integer' },
- },
- },
- tags: { type: 'keyword' },
- },
- },
- management: {
- displayName: 'Composite SLO',
- importableAndExportable: true,
- getTitle(compositeSloSavedObject: SavedObject) {
- return `Composite SLO: [${compositeSloSavedObject.attributes.name}]`;
- },
- },
-};
diff --git a/x-pack/plugins/observability/server/saved_objects/index.ts b/x-pack/plugins/observability/server/saved_objects/index.ts
index 2e2a36f68e30d..6e4c8b66f7521 100644
--- a/x-pack/plugins/observability/server/saved_objects/index.ts
+++ b/x-pack/plugins/observability/server/saved_objects/index.ts
@@ -6,4 +6,3 @@
*/
export { slo, SO_SLO_TYPE } from './slo';
-export { compositeSlo, SO_COMPOSITE_SLO_TYPE } from './composite_slo';
diff --git a/x-pack/plugins/observability/server/services/composite_slo/__snapshots__/summary_client.test.ts.snap b/x-pack/plugins/observability/server/services/composite_slo/__snapshots__/summary_client.test.ts.snap
deleted file mode 100644
index ec4d29cba2f10..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/__snapshots__/summary_client.test.ts.snap
+++ /dev/null
@@ -1,185 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`SummaryClient fetchSummary with a rolling and occurrences composite SLO returns the summary 1`] = `
-Object {
- "errorBudget": Object {
- "consumed": 1.666667,
- "initial": 0.03,
- "isEstimated": false,
- "remaining": -0.666667,
- },
- "sliValue": 0.95,
- "status": "VIOLATED",
-}
-`;
-
-exports[`SummaryClient fetchSummary with a rolling and occurrences composite SLO returns the summary 2`] = `
-Array [
- Object {
- "index": ".slo-observability.sli-v2*",
- },
- Object {
- "aggs": Object {
- "bySloId": Object {
- "aggs": Object {
- "good": Object {
- "sum": Object {
- "field": "slo.numerator",
- },
- },
- "total": Object {
- "sum": Object {
- "field": "slo.denominator",
- },
- },
- },
- "terms": Object {
- "field": "slo.id",
- },
- },
- },
- "query": Object {
- "bool": Object {
- "filter": Array [
- Object {
- "range": Object {
- "@timestamp": Object {
- "gte": "2023-05-22T10:15:00.000Z",
- "lt": "2023-05-29T10:15:00.000Z",
- },
- },
- },
- ],
- "minimum_should_match": 1,
- "should": Array [
- Object {
- "bool": Object {
- "must": Array [
- Object {
- "term": Object {
- "slo.id": "slo-1",
- },
- },
- Object {
- "term": Object {
- "slo.revision": 1,
- },
- },
- ],
- },
- },
- Object {
- "bool": Object {
- "must": Array [
- Object {
- "term": Object {
- "slo.id": "slo-2",
- },
- },
- Object {
- "term": Object {
- "slo.revision": 2,
- },
- },
- ],
- },
- },
- ],
- },
- },
- "size": 0,
- },
-]
-`;
-
-exports[`SummaryClient with rolling and timeslices SLO returns the summary 1`] = `
-Object {
- "errorBudget": Object {
- "consumed": 1.666667,
- "initial": 0.03,
- "isEstimated": false,
- "remaining": -0.666667,
- },
- "sliValue": 0.95,
- "status": "VIOLATED",
-}
-`;
-
-exports[`SummaryClient with rolling and timeslices SLO returns the summary 2`] = `
-Array [
- Object {
- "index": ".slo-observability.sli-v2*",
- },
- Object {
- "aggs": Object {
- "bySloId": Object {
- "aggs": Object {
- "good": Object {
- "sum": Object {
- "field": "slo.isGoodSlice",
- },
- },
- "total": Object {
- "value_count": Object {
- "field": "slo.isGoodSlice",
- },
- },
- },
- "terms": Object {
- "field": "slo.id",
- },
- },
- },
- "query": Object {
- "bool": Object {
- "filter": Array [
- Object {
- "range": Object {
- "@timestamp": Object {
- "gte": "2023-05-22T10:15:00.000Z",
- "lt": "2023-05-29T10:15:00.000Z",
- },
- },
- },
- ],
- "minimum_should_match": 1,
- "should": Array [
- Object {
- "bool": Object {
- "must": Array [
- Object {
- "term": Object {
- "slo.id": "slo-1",
- },
- },
- Object {
- "term": Object {
- "slo.revision": 1,
- },
- },
- ],
- },
- },
- Object {
- "bool": Object {
- "must": Array [
- Object {
- "term": Object {
- "slo.id": "slo-2",
- },
- },
- Object {
- "term": Object {
- "slo.revision": 2,
- },
- },
- ],
- },
- },
- ],
- },
- },
- "size": 0,
- },
-]
-`;
diff --git a/x-pack/plugins/observability/server/services/composite_slo/composite_slo_repository.test.ts b/x-pack/plugins/observability/server/services/composite_slo/composite_slo_repository.test.ts
deleted file mode 100644
index e1d234018c547..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/composite_slo_repository.test.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { SavedObjectsClientContract, SavedObjectsFindResponse } from '@kbn/core/server';
-import { savedObjectsClientMock } from '@kbn/core/server/mocks';
-import { compositeSloSchema } from '@kbn/slo-schema';
-
-import { CompositeSLO, StoredCompositeSLO } from '../../domain/models';
-import { CompositeSLOIdConflict, CompositeSLONotFound } from '../../errors';
-import { SO_COMPOSITE_SLO_TYPE } from '../../saved_objects';
-import { KibanaSavedObjectsCompositeSLORepository } from './composite_slo_repository';
-import { aStoredCompositeSLO, createCompositeSLO } from './fixtures/composite_slo';
-
-function createFindResponse(
- compositeSloList: CompositeSLO[]
-): SavedObjectsFindResponse {
- return {
- page: 1,
- per_page: 25,
- total: compositeSloList.length,
- saved_objects: compositeSloList.map((compositeSlo) => ({
- id: compositeSlo.id,
- attributes: compositeSloSchema.encode(compositeSlo),
- type: SO_COMPOSITE_SLO_TYPE,
- references: [],
- score: 1,
- })),
- };
-}
-
-describe('KibanaSavedObjectsCompositeSLORepository', () => {
- let soClientMock: jest.Mocked;
-
- beforeEach(() => {
- soClientMock = savedObjectsClientMock.create();
- });
-
- describe('saving a composite SLO', () => {
- it('saves the new composite SLO', async () => {
- const compositeSlo = createCompositeSLO({ id: 'my-composite-id' });
- soClientMock.find.mockResolvedValueOnce(createFindResponse([]));
- soClientMock.create.mockResolvedValueOnce(aStoredCompositeSLO(compositeSlo));
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClientMock);
-
- const savedCompositeSlo = await repository.save(compositeSlo);
-
- expect(savedCompositeSlo).toEqual(compositeSlo);
- expect(soClientMock.find).toHaveBeenCalledWith({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `composite-slo.attributes.id:(${compositeSlo.id})`,
- });
- expect(soClientMock.create).toHaveBeenCalledWith(
- SO_COMPOSITE_SLO_TYPE,
- compositeSloSchema.encode(compositeSlo),
- {
- id: undefined,
- overwrite: true,
- }
- );
- });
-
- it('throws when the Composite SLO id already exists and "throwOnConflict" is true', async () => {
- const compositeSlo = createCompositeSLO({ id: 'my-composite-id' });
- soClientMock.find.mockResolvedValueOnce(createFindResponse([compositeSlo]));
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClientMock);
-
- await expect(repository.save(compositeSlo, { throwOnConflict: true })).rejects.toThrowError(
- new CompositeSLOIdConflict(`Composite SLO [${compositeSlo.id}] already exists`)
- );
- expect(soClientMock.find).toHaveBeenCalledWith({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `composite-slo.attributes.id:(${compositeSlo.id})`,
- });
- });
-
- it('updates the existing composite SLO', async () => {
- const compositeSlo = createCompositeSLO({ id: 'my-composite-id' });
- soClientMock.find.mockResolvedValueOnce(createFindResponse([compositeSlo]));
- soClientMock.create.mockResolvedValueOnce(aStoredCompositeSLO(compositeSlo));
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClientMock);
-
- const savedCompositeSLO = await repository.save(compositeSlo);
-
- expect(savedCompositeSLO).toEqual(compositeSlo);
- expect(soClientMock.find).toHaveBeenCalledWith({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `composite-slo.attributes.id:(${compositeSlo.id})`,
- });
- expect(soClientMock.create).toHaveBeenCalledWith(
- SO_COMPOSITE_SLO_TYPE,
- compositeSloSchema.encode(compositeSlo),
- {
- id: 'my-composite-id',
- overwrite: true,
- }
- );
- });
- });
-
- describe('deleting a composite SLO', () => {
- it('throws when not found', async () => {
- soClientMock.find.mockResolvedValueOnce(createFindResponse([]));
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClientMock);
-
- await expect(repository.deleteById('inexistant-slo-id')).rejects.toThrowError(
- new CompositeSLONotFound('Composite SLO [inexistant-slo-id] not found')
- );
- });
-
- it('deletes a composite SLO', async () => {
- const compositeSlo = createCompositeSLO({ id: 'my-composite-id' });
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClientMock);
- soClientMock.find.mockResolvedValueOnce(createFindResponse([compositeSlo]));
-
- await repository.deleteById(compositeSlo.id);
-
- expect(soClientMock.find).toHaveBeenCalledWith({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `composite-slo.attributes.id:(${compositeSlo.id})`,
- });
- expect(soClientMock.delete).toHaveBeenCalledWith(SO_COMPOSITE_SLO_TYPE, compositeSlo.id);
- });
- });
-
- describe('finding a composite SLO', () => {
- it('finds an existing composite SLO', async () => {
- const compositeSlo = createCompositeSLO();
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClientMock);
- soClientMock.find.mockResolvedValueOnce(createFindResponse([compositeSlo]));
-
- const foundSLO = await repository.findById(compositeSlo.id);
- expect(foundSLO).toEqual(compositeSlo);
- expect(soClientMock.find).toHaveBeenCalledWith({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `composite-slo.attributes.id:(${compositeSlo.id})`,
- });
- });
-
- it('throws when the composite SLO does not exist', async () => {
- const repository = new KibanaSavedObjectsCompositeSLORepository(soClientMock);
- soClientMock.find.mockResolvedValueOnce(createFindResponse([]));
-
- await expect(repository.findById('inexistant-id')).rejects.toThrowError(
- new CompositeSLONotFound('Composite SLO [inexistant-id] not found')
- );
-
- expect(soClientMock.find).toHaveBeenCalledWith({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: 'composite-slo.attributes.id:(inexistant-id)',
- });
- });
- });
-});
diff --git a/x-pack/plugins/observability/server/services/composite_slo/composite_slo_repository.ts b/x-pack/plugins/observability/server/services/composite_slo/composite_slo_repository.ts
deleted file mode 100644
index e11e0ecd8f071..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/composite_slo_repository.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
-import { compositeSloSchema } from '@kbn/slo-schema';
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import * as t from 'io-ts';
-
-import { CompositeSLO, StoredCompositeSLO } from '../../domain/models/composite_slo';
-import { CompositeSLOIdConflict, CompositeSLONotFound } from '../../errors';
-import { SO_COMPOSITE_SLO_TYPE } from '../../saved_objects';
-
-export interface CompositeSLORepository {
- save(compositeSlo: CompositeSLO, options?: { throwOnConflict: boolean }): Promise;
- deleteById(id: string): Promise;
- findById(id: string): Promise;
- find(criteria: Criteria, sort: Sort, pagination: Pagination): Promise>;
-}
-
-export interface Criteria {
- name?: string;
-}
-
-export interface Pagination {
- page: number;
- perPage: number;
-}
-
-export const SortDirection = {
- Asc: 'Asc',
- Desc: 'Desc',
-} as const;
-
-type ObjectValues = T[keyof T];
-type SortDirection = ObjectValues;
-
-export const SortField = {
- CreationTime: 'CreationTime',
- IndicatorType: 'IndicatorType',
-};
-
-type SortField = ObjectValues;
-
-export interface Sort {
- field: SortField;
- direction: SortDirection;
-}
-
-export interface Paginated {
- page: number;
- perPage: number;
- total: number;
- results: T[];
-}
-
-const SAVED_OBJECT_ATTRIBUTES_PATH = 'composite-slo.attributes';
-
-export class KibanaSavedObjectsCompositeSLORepository implements CompositeSLORepository {
- constructor(private soClient: SavedObjectsClientContract) {}
-
- async save(
- compositeSlo: CompositeSLO,
- options = { throwOnConflict: false }
- ): Promise {
- let existingSavedObjectId;
- const findResponse = await this.soClient.find({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `${SAVED_OBJECT_ATTRIBUTES_PATH}.id:(${compositeSlo.id})`,
- });
-
- if (findResponse.total === 1) {
- if (options.throwOnConflict) {
- throw new CompositeSLOIdConflict(`Composite SLO [${compositeSlo.id}] already exists`);
- }
-
- existingSavedObjectId = findResponse.saved_objects[0].id;
- }
-
- const createResponse = await this.soClient.create(
- SO_COMPOSITE_SLO_TYPE,
- toStoredCompositeSLO(compositeSlo),
- {
- id: existingSavedObjectId,
- overwrite: true,
- }
- );
-
- return toCompositeSLO(createResponse.attributes);
- }
-
- async deleteById(id: string): Promise {
- const response = await this.soClient.find({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `${SAVED_OBJECT_ATTRIBUTES_PATH}.id:(${id})`,
- });
-
- if (response.total === 0) {
- throw new CompositeSLONotFound(`Composite SLO [${id}] not found`);
- }
-
- await this.soClient.delete(SO_COMPOSITE_SLO_TYPE, response.saved_objects[0].id);
- }
-
- async findById(id: string): Promise {
- const response = await this.soClient.find({
- type: SO_COMPOSITE_SLO_TYPE,
- page: 1,
- perPage: 1,
- filter: `${SAVED_OBJECT_ATTRIBUTES_PATH}.id:(${id})`,
- });
-
- if (response.total === 0) {
- throw new CompositeSLONotFound(`Composite SLO [${id}] not found`);
- }
-
- return toCompositeSLO(response.saved_objects[0].attributes);
- }
-
- async find(
- criteria: Criteria,
- sort: Sort,
- pagination: Pagination
- ): Promise> {
- const { search, searchFields } = buildSearch(criteria);
- const { sortField, sortOrder } = buildSortQuery(sort);
- const response = await this.soClient.find({
- type: SO_COMPOSITE_SLO_TYPE,
- page: pagination.page,
- perPage: pagination.perPage,
- search,
- searchFields,
- sortField,
- sortOrder,
- });
-
- return {
- total: response.total,
- page: response.page,
- perPage: response.per_page,
- results: response.saved_objects.map((so) => toCompositeSLO(so.attributes)),
- };
- }
-}
-
-function toStoredCompositeSLO(compositeSlo: CompositeSLO): StoredCompositeSLO {
- return compositeSloSchema.encode(compositeSlo);
-}
-
-function toCompositeSLO(storedCompositeSlo: StoredCompositeSLO): CompositeSLO {
- return pipe(
- compositeSloSchema.decode(storedCompositeSlo),
- fold(() => {
- throw new Error(`Invalid stored composite SLO [${storedCompositeSlo.id}]`);
- }, t.identity)
- );
-}
-
-function buildSearch(criteria: Criteria): {
- search: string | undefined;
- searchFields: string[] | undefined;
-} {
- if (!criteria.name) {
- return { search: undefined, searchFields: undefined };
- }
-
- return { search: addWildcardsIfAbsent(criteria.name), searchFields: ['name'] };
-}
-
-function buildSortQuery(sort: Sort): { sortField: string; sortOrder: 'asc' | 'desc' } {
- let sortField: string;
- switch (sort.field) {
- case SortField.CreationTime:
- default:
- sortField = 'created_at';
- break;
- }
-
- return {
- sortField,
- sortOrder: sort.direction === SortDirection.Desc ? 'desc' : 'asc',
- };
-}
-
-const WILDCARD_CHAR = '*';
-function addWildcardsIfAbsent(value: string): string {
- let updatedValue = value;
- if (updatedValue.substring(0, 1) !== WILDCARD_CHAR) {
- updatedValue = `${WILDCARD_CHAR}${updatedValue}`;
- }
-
- if (value.substring(value.length - 1) !== WILDCARD_CHAR) {
- updatedValue = `${updatedValue}${WILDCARD_CHAR}`;
- }
-
- return updatedValue;
-}
diff --git a/x-pack/plugins/observability/server/services/composite_slo/create_composite_slo.ts b/x-pack/plugins/observability/server/services/composite_slo/create_composite_slo.ts
deleted file mode 100644
index 2e420a53798a6..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/create_composite_slo.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- CreateCompositeSLOParams,
- CreateCompositeSLOResponse,
- CreateSLOResponse,
-} from '@kbn/slo-schema';
-import { v1 as uuidv1 } from 'uuid';
-
-import { CompositeSLO } from '../../domain/models/composite_slo';
-import { validateCompositeSLO } from '../../domain/services/composite_slo';
-import { SLORepository } from '../slo/slo_repository';
-import { CompositeSLORepository } from './composite_slo_repository';
-
-export class CreateCompositeSLO {
- constructor(
- private compositeSloRepository: CompositeSLORepository,
- private sloRepository: SLORepository
- ) {}
-
- public async execute(params: CreateCompositeSLOParams): Promise {
- const compositeSlo = toCompositeSLO(params);
- const sloList = await this.sloRepository.findAllByIds(
- compositeSlo.sources.map((slo) => slo.id)
- );
- validateCompositeSLO(compositeSlo, sloList);
-
- await this.compositeSloRepository.save(compositeSlo, { throwOnConflict: true });
-
- return toResponse(compositeSlo);
- }
-}
-
-function toCompositeSLO(params: CreateCompositeSLOParams): CompositeSLO {
- const now = new Date();
- return {
- ...params,
- id: params.id ?? uuidv1(),
- tags: params.tags ?? [],
- createdAt: now,
- updatedAt: now,
- };
-}
-
-function toResponse(compositeSlo: CompositeSLO): CreateCompositeSLOResponse {
- return {
- id: compositeSlo.id,
- };
-}
diff --git a/x-pack/plugins/observability/server/services/composite_slo/delete_composite_slo.ts b/x-pack/plugins/observability/server/services/composite_slo/delete_composite_slo.ts
deleted file mode 100644
index c795db2391cfa..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/delete_composite_slo.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { CompositeSLOId } from '../../domain/models';
-import { CompositeSLORepository } from './composite_slo_repository';
-
-export class DeleteCompositeSLO {
- constructor(private compositeSloRepository: CompositeSLORepository) {}
-
- public async execute(compositeSloId: CompositeSLOId): Promise {
- await this.compositeSloRepository.deleteById(compositeSloId);
- }
-}
diff --git a/x-pack/plugins/observability/server/services/composite_slo/find_composite_slo.ts b/x-pack/plugins/observability/server/services/composite_slo/find_composite_slo.ts
deleted file mode 100644
index 79bdb734614b1..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/find_composite_slo.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- FindCompositeSLOParams,
- FindCompositeSLOResponse,
- findCompositeSLOResponseSchema,
-} from '@kbn/slo-schema';
-import { CompositeSLO } from '../../domain/models';
-import {
- CompositeSLORepository,
- Paginated,
- Pagination,
- Sort,
- SortDirection,
- SortField,
- Criteria,
-} from './composite_slo_repository';
-import { SummaryClient } from './summary_client';
-
-const DEFAULT_PAGE = 1;
-const DEFAULT_PER_PAGE = 25;
-
-export class FindCompositeSLO {
- constructor(private repository: CompositeSLORepository, private summaryClient: SummaryClient) {}
-
- public async execute(params: FindCompositeSLOParams): Promise {
- const pagination: Pagination = toPagination(params);
- const criteria: Criteria = toCriteria(params);
- const sort: Sort = toSort(params);
-
- const { results: compositeSloList, ...resultMeta }: Paginated =
- await this.repository.find(criteria, sort, pagination);
- const summaryByCompositeSlo = await this.summaryClient.fetchSummary(compositeSloList);
-
- return findCompositeSLOResponseSchema.encode({
- page: resultMeta.page,
- perPage: resultMeta.perPage,
- total: resultMeta.total,
- results: compositeSloList.map((compositeSlo) => ({
- ...compositeSlo,
- summary: summaryByCompositeSlo[compositeSlo.id],
- })),
- });
- }
-}
-
-function toCriteria(params: FindCompositeSLOParams): Criteria {
- return { name: params.name };
-}
-
-function toPagination(params: FindCompositeSLOParams): Pagination {
- const page = Number(params.page);
- const perPage = Number(params.perPage);
-
- return {
- page: !isNaN(page) && page >= 1 ? page : DEFAULT_PAGE,
- perPage: !isNaN(perPage) && perPage >= 1 ? perPage : DEFAULT_PER_PAGE,
- };
-}
-
-function toSort(params: FindCompositeSLOParams): Sort {
- return {
- field: SortField.CreationTime,
- direction: params.sortDirection === 'desc' ? SortDirection.Desc : SortDirection.Asc,
- };
-}
diff --git a/x-pack/plugins/observability/server/services/composite_slo/fixtures/composite_slo.ts b/x-pack/plugins/observability/server/services/composite_slo/fixtures/composite_slo.ts
deleted file mode 100644
index bba107e29d689..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/fixtures/composite_slo.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { cloneDeep } from 'lodash';
-import { v1 as uuidv1 } from 'uuid';
-import { SavedObject } from '@kbn/core-saved-objects-server';
-
-import { compositeSloSchema } from '@kbn/slo-schema';
-import { SO_COMPOSITE_SLO_TYPE } from '../../../saved_objects';
-import { CompositeSLO, StoredCompositeSLO, WeightedAverageSource } from '../../../domain/models';
-import { sevenDaysRolling } from '../../slo/fixtures/time_window';
-
-export const createWeightedAverageSource = (
- params: Partial = {}
-): WeightedAverageSource => {
- return cloneDeep({
- id: uuidv1(),
- revision: 1,
- weight: 1,
- ...params,
- });
-};
-
-const defaultCompositeSLO: Omit = {
- name: 'some composite slo',
- timeWindow: sevenDaysRolling(),
- budgetingMethod: 'occurrences',
- objective: {
- target: 0.95,
- },
- compositeMethod: 'weightedAverage',
- sources: [createWeightedAverageSource(), createWeightedAverageSource()],
- tags: ['critical', 'k8s'],
-};
-
-export const createCompositeSLO = (params: Partial = {}): CompositeSLO => {
- const now = new Date();
- return cloneDeep({
- ...defaultCompositeSLO,
- id: uuidv1(),
- createdAt: now,
- updatedAt: now,
- ...params,
- });
-};
-
-export const aStoredCompositeSLO = (
- compositeSlo: CompositeSLO
-): SavedObject => {
- return {
- id: uuidv1(),
- attributes: compositeSloSchema.encode(compositeSlo),
- type: SO_COMPOSITE_SLO_TYPE,
- references: [],
- };
-};
diff --git a/x-pack/plugins/observability/server/services/composite_slo/get_composite_slo.ts b/x-pack/plugins/observability/server/services/composite_slo/get_composite_slo.ts
deleted file mode 100644
index 8a0170cbfa080..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/get_composite_slo.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { compositeSLOResponseSchema, GetCompositeSLOResponse } from '@kbn/slo-schema';
-import { CompositeSLO, CompositeSLOId, Summary } from '../../domain/models';
-import { CompositeSLORepository } from './composite_slo_repository';
-import { SummaryClient } from './summary_client';
-
-export class GetCompositeSLO {
- constructor(
- private compositeSloRepository: CompositeSLORepository,
- private summaryClient: SummaryClient
- ) {}
-
- public async execute(compositeSloId: CompositeSLOId): Promise {
- const compositeSlo = await this.compositeSloRepository.findById(compositeSloId);
- const summaryByCompositeSlo = await this.summaryClient.fetchSummary([compositeSlo]);
-
- return toResponse(compositeSlo, summaryByCompositeSlo[compositeSlo.id]);
- }
-}
-
-function toResponse(compositeSlo: CompositeSLO, summary: Summary): GetCompositeSLOResponse {
- return {
- ...compositeSLOResponseSchema.encode(compositeSlo),
- summary,
- };
-}
diff --git a/x-pack/plugins/observability/server/services/composite_slo/index.ts b/x-pack/plugins/observability/server/services/composite_slo/index.ts
deleted file mode 100644
index e9f8320ea833e..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export * from './composite_slo_repository';
-export * from './create_composite_slo';
-export * from './delete_composite_slo';
-export * from './find_composite_slo';
-export * from './summary_client';
-export * from './update_composite_slo';
diff --git a/x-pack/plugins/observability/server/services/composite_slo/summary_client.test.ts b/x-pack/plugins/observability/server/services/composite_slo/summary_client.test.ts
deleted file mode 100644
index 813183c6576d6..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/summary_client.test.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ElasticsearchClientMock, elasticsearchServiceMock } from '@kbn/core/server/mocks';
-import { CompositeSLO, Duration, DurationUnit } from '../../domain/models';
-import { sevenDaysRolling } from '../slo/fixtures/time_window';
-import { createCompositeSLO } from './fixtures/composite_slo';
-import { DefaultSummaryClient } from './summary_client';
-
-const commonEsResponse = {
- took: 100,
- timed_out: false,
- _shards: {
- total: 0,
- successful: 0,
- skipped: 0,
- failed: 0,
- },
- hits: {
- hits: [],
- },
-};
-
-const createEsResponse = (compositeSlo: CompositeSLO) => ({
- ...commonEsResponse,
- responses: [
- {
- ...commonEsResponse,
- aggregations: {
- bySloId: {
- buckets: compositeSlo.sources.map((source) => ({
- key: source.id,
- good: { value: 95 },
- total: { value: 100 },
- })),
- },
- },
- },
- ],
-});
-
-describe('SummaryClient', () => {
- let esClientMock: ElasticsearchClientMock;
-
- beforeEach(() => {
- esClientMock = elasticsearchServiceMock.createElasticsearchClient();
- jest.useFakeTimers().setSystemTime(new Date('2023-05-29T10:15:00.000Z'));
- });
-
- describe('fetchSummary', () => {
- describe('with a rolling and occurrences composite SLO', () => {
- it('returns the summary', async () => {
- const compositeSlo = createCompositeSLO({
- objective: { target: 0.97 },
- timeWindow: sevenDaysRolling(),
- sources: [
- { id: 'slo-1', revision: 1, weight: 2 },
- { id: 'slo-2', revision: 2, weight: 1 },
- ],
- });
- esClientMock.msearch.mockResolvedValueOnce(createEsResponse(compositeSlo));
- const summaryClient = new DefaultSummaryClient(esClientMock);
-
- const result = await summaryClient.fetchSummary([compositeSlo]);
-
- expect(result[compositeSlo.id]).toMatchSnapshot();
- // @ts-ignore
- expect(esClientMock.msearch.mock.calls[0][0].searches).toMatchSnapshot();
- });
- });
- });
-
- describe('with rolling and timeslices SLO', () => {
- it('returns the summary', async () => {
- const compositeSlo = createCompositeSLO({
- budgetingMethod: 'timeslices',
- objective: {
- target: 0.97,
- timesliceTarget: 0.9,
- timesliceWindow: new Duration(10, DurationUnit.Minute),
- },
- sources: [
- { id: 'slo-1', revision: 1, weight: 2 },
- { id: 'slo-2', revision: 2, weight: 1 },
- ],
- timeWindow: sevenDaysRolling(),
- });
- esClientMock.msearch.mockResolvedValueOnce(createEsResponse(compositeSlo));
- const summaryClient = new DefaultSummaryClient(esClientMock);
-
- const result = await summaryClient.fetchSummary([compositeSlo]);
-
- expect(result[compositeSlo.id]).toMatchSnapshot();
- // @ts-ignore searches not typed properly
- expect(esClientMock.msearch.mock.calls[0][0].searches).toMatchSnapshot();
- });
- });
-});
diff --git a/x-pack/plugins/observability/server/services/composite_slo/summary_client.ts b/x-pack/plugins/observability/server/services/composite_slo/summary_client.ts
deleted file mode 100644
index 275e70b6db300..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/summary_client.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { MsearchMultisearchBody } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
-import { ElasticsearchClient } from '@kbn/core/server';
-import {
- calendarAlignedTimeWindowSchema,
- Duration,
- occurrencesBudgetingMethodSchema,
- timeslicesBudgetingMethodSchema,
- toMomentUnitOfTime,
-} from '@kbn/slo-schema';
-import moment from 'moment';
-import { SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants';
-import { CompositeSLO, CompositeSLOId, DateRange, Summary } from '../../domain/models';
-import { computeSLI, computeSummaryStatus, toErrorBudget } from '../../domain/services';
-import { toDateRange } from '../../domain/services/date_range';
-import { toHighPrecision } from '../../utils/number';
-
-export interface SummaryClient {
- fetchSummary(compositeSloList: CompositeSLO[]): Promise>;
-}
-
-export class DefaultSummaryClient implements SummaryClient {
- constructor(private esClient: ElasticsearchClient) {}
-
- async fetchSummary(compositeSloList: CompositeSLO[]): Promise> {
- const dateRangeByCompositeSlo = compositeSloList.reduce>(
- (acc, compositeSlo) => {
- acc[compositeSlo.id] = toDateRange(compositeSlo.timeWindow);
- return acc;
- },
- {}
- );
- const searches = compositeSloList.flatMap((compositeSlo) => [
- { index: SLO_DESTINATION_INDEX_PATTERN },
- generateSearchQuery(compositeSlo, dateRangeByCompositeSlo[compositeSlo.id]),
- ]);
-
- const summaryByCompositeSlo: Record = {};
- if (searches.length === 0) {
- return summaryByCompositeSlo;
- }
-
- const result = await this.esClient.msearch({ searches });
-
- for (let i = 0; i < result.responses.length; i++) {
- const compositeSlo = compositeSloList[i];
-
- // @ts-ignore
- const { aggregations = {} } = result.responses[i];
- const buckets = aggregations?.bySloId?.buckets ?? [];
-
- if (
- calendarAlignedTimeWindowSchema.is(compositeSlo.timeWindow) &&
- timeslicesBudgetingMethodSchema.is(compositeSlo.budgetingMethod)
- ) {
- let sliValue = 0;
- let totalWeights = 0;
- let maxSloTotalSlices = 0;
- for (const bucket of buckets) {
- const sourceSloId = bucket.key;
- const sourceSloGoodSlices = bucket.good.value;
- const sourceSloTotalSlices = bucket.total.value;
- maxSloTotalSlices =
- sourceSloTotalSlices > maxSloTotalSlices ? sourceSloTotalSlices : maxSloTotalSlices;
- const sourceSloSliValue = computeSLI(sourceSloGoodSlices, sourceSloTotalSlices);
- const sourceSloWeight = compositeSlo.sources.find(
- (source) => source.id === sourceSloId
- )!.weight; // used to build the query, therefore exists
-
- totalWeights += sourceSloWeight;
- sliValue += sourceSloSliValue < 0 ? 0 : sourceSloWeight * sourceSloSliValue;
- }
- sliValue /= totalWeights === 0 ? 1 : totalWeights;
-
- const totalSlicesInCalendar = computeTotalSlicesFromDateRange(
- dateRangeByCompositeSlo[compositeSlo.id],
- compositeSlo.objective.timesliceWindow!
- );
- const initialErrorBudget = 1 - compositeSlo.objective.target;
- const errorBudgetConsumed =
- ((1 - sliValue) / initialErrorBudget) * (maxSloTotalSlices / totalSlicesInCalendar);
-
- const errorBudget = toErrorBudget(initialErrorBudget, errorBudgetConsumed);
- summaryByCompositeSlo[compositeSlo.id] = {
- sliValue: toHighPrecision(sliValue),
- errorBudget,
- status: computeSummaryStatus(compositeSlo, sliValue, errorBudget),
- };
- } else {
- let sliValue = 0;
- let totalWeights = 0;
- for (const bucket of buckets) {
- const sourceSloId = bucket.key;
- const sourceSloGood = bucket.good.value;
- const sourceSloTotal = bucket.total.value;
- const sourceSloSliValue = computeSLI(sourceSloGood, sourceSloTotal);
- const sourceSloWeight = compositeSlo.sources.find(
- (source) => source.id === sourceSloId
- )!.weight; // used to build the query, therefore exists
-
- totalWeights += sourceSloWeight;
- sliValue += sourceSloSliValue < 0 ? 0 : sourceSloWeight * sourceSloSliValue;
- }
- sliValue /= totalWeights === 0 ? 1 : totalWeights;
-
- const initialErrorBudget = 1 - compositeSlo.objective.target;
- const errorBudgetConsumed = (1 - sliValue) / initialErrorBudget;
- const errorBudget = toErrorBudget(
- initialErrorBudget,
- errorBudgetConsumed,
- calendarAlignedTimeWindowSchema.is(compositeSlo.timeWindow)
- );
- summaryByCompositeSlo[compositeSlo.id] = {
- sliValue: toHighPrecision(sliValue),
- errorBudget,
- status: computeSummaryStatus(compositeSlo, sliValue, errorBudget),
- };
- }
- }
-
- return summaryByCompositeSlo;
- }
-}
-
-function generateSearchQuery(
- compositeSlo: CompositeSLO,
- dateRange: DateRange
-): MsearchMultisearchBody {
- return {
- size: 0,
- query: {
- bool: {
- filter: [
- {
- range: {
- '@timestamp': { gte: dateRange.from.toISOString(), lt: dateRange.to.toISOString() },
- },
- },
- ],
- should: compositeSlo.sources.map((source) => ({
- bool: {
- must: [
- { term: { 'slo.id': source.id } },
- { term: { 'slo.revision': source.revision } },
- ],
- },
- })),
- minimum_should_match: 1,
- },
- },
- ...(occurrencesBudgetingMethodSchema.is(compositeSlo.budgetingMethod) && {
- aggs: {
- bySloId: {
- terms: {
- field: 'slo.id',
- },
- aggs: {
- good: { sum: { field: 'slo.numerator' } },
- total: { sum: { field: 'slo.denominator' } },
- },
- },
- },
- }),
- ...(timeslicesBudgetingMethodSchema.is(compositeSlo.budgetingMethod) && {
- aggs: {
- bySloId: {
- terms: {
- field: 'slo.id',
- },
- aggs: {
- good: { sum: { field: 'slo.isGoodSlice' } },
- total: { value_count: { field: 'slo.isGoodSlice' } },
- },
- },
- },
- }),
- };
-}
-
-function computeTotalSlicesFromDateRange(dateRange: DateRange, timesliceWindow: Duration) {
- const dateRangeDurationInUnit = moment(dateRange.to).diff(
- dateRange.from,
- toMomentUnitOfTime(timesliceWindow.unit)
- );
- return Math.ceil(dateRangeDurationInUnit / timesliceWindow!.value);
-}
diff --git a/x-pack/plugins/observability/server/services/composite_slo/update_composite_slo.ts b/x-pack/plugins/observability/server/services/composite_slo/update_composite_slo.ts
deleted file mode 100644
index 49725acdd927c..0000000000000
--- a/x-pack/plugins/observability/server/services/composite_slo/update_composite_slo.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- UpdateCompositeSLOParams,
- UpdateCompositeSLOResponse,
- updateCompositeSLOResponseSchema,
-} from '@kbn/slo-schema';
-import { CompositeSLO, CompositeSLOId } from '../../domain/models';
-import { validateCompositeSLO } from '../../domain/services/composite_slo';
-import { SLORepository } from '../slo';
-import { CompositeSLORepository } from './composite_slo_repository';
-
-export class UpdateCompositeSLO {
- constructor(
- private compositeSloRepository: CompositeSLORepository,
- private sloRepository: SLORepository
- ) {}
-
- public async execute(
- compositeSloId: CompositeSLOId,
- params: UpdateCompositeSLOParams
- ): Promise {
- const originalCompositeSlo = await this.compositeSloRepository.findById(compositeSloId);
-
- const updatedCompositeSlo: CompositeSLO = Object.assign({}, originalCompositeSlo, params, {
- updatedAt: new Date(),
- });
- const sloList = await this.sloRepository.findAllByIds(
- updatedCompositeSlo.sources.map((slo) => slo.id)
- );
- validateCompositeSLO(updatedCompositeSlo, sloList);
-
- await this.compositeSloRepository.save(updatedCompositeSlo);
-
- return toResponse(updatedCompositeSlo);
- }
-}
-
-function toResponse(compositeSlo: CompositeSLO): UpdateCompositeSLOResponse {
- return updateCompositeSLOResponseSchema.encode(compositeSlo);
-}
diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json
index a3d803b89bb97..d5123d9b5c86b 100644
--- a/x-pack/plugins/observability/tsconfig.json
+++ b/x-pack/plugins/observability/tsconfig.json
@@ -90,7 +90,8 @@
"@kbn/deeplinks-observability",
"@kbn/core-application-common",
"@kbn/react-kibana-mount",
- "@kbn/react-kibana-context-theme"
+ "@kbn/react-kibana-context-theme",
+ "@kbn/shared-ux-link-redirect-app"
],
"exclude": [
"target/**/*"
diff --git a/x-pack/plugins/observability_onboarding/public/application/app.tsx b/x-pack/plugins/observability_onboarding/public/application/app.tsx
index 93a62f5f378e3..fc791436e3887 100644
--- a/x-pack/plugins/observability_onboarding/public/application/app.tsx
+++ b/x-pack/plugins/observability_onboarding/public/application/app.tsx
@@ -24,7 +24,6 @@ import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { euiDarkVars, euiLightVars } from '@kbn/ui-theme';
import React from 'react';
import ReactDOM from 'react-dom';
-import { RouteComponentProps, RouteProps } from 'react-router-dom';
import { ConfigSchema } from '..';
import { customLogsRoutes } from '../components/app/custom_logs';
import { systemLogsRoutes } from '../components/app/system_logs';
@@ -37,16 +36,6 @@ import { baseRoutes, routes } from '../routes';
import { CustomLogs } from '../routes/templates/custom_logs';
import { SystemLogs } from '../routes/templates/system_logs';
-export type BreadcrumbTitle<
- T extends { [K in keyof T]?: string | undefined } = {}
-> = string | ((props: RouteComponentProps) => string) | null;
-
-export interface RouteDefinition<
- T extends { [K in keyof T]?: string | undefined } = any
-> extends RouteProps {
- breadcrumb: BreadcrumbTitle;
-}
-
export const onBoardingTitle = i18n.translate(
'xpack.observability_onboarding.breadcrumbs.onboarding',
{
@@ -157,6 +146,8 @@ export function ObservabilityOnboardingAppRoot({
const i18nCore = core.i18n;
const plugins = { ...deps };
+ const renderFeedbackLinkAsPortal = !config.serverless.enabled;
+
return (
-
-
-
+ {renderFeedbackLinkAsPortal && (
+
+
+
+ )}
diff --git a/x-pack/plugins/observability_onboarding/public/plugin.ts b/x-pack/plugins/observability_onboarding/public/plugin.ts
index 05806059b12e3..1a4982eab0859 100644
--- a/x-pack/plugins/observability_onboarding/public/plugin.ts
+++ b/x-pack/plugins/observability_onboarding/public/plugin.ts
@@ -28,6 +28,7 @@ import type { ObservabilityOnboardingConfig } from '../server';
import { PLUGIN_ID } from '../common';
import { ObservabilityOnboardingLocatorDefinition } from './locators/onboarding_locator/locator_definition';
import { ObservabilityOnboardingPluginLocators } from './locators';
+import { ConfigSchema } from '.';
export type ObservabilityOnboardingPluginSetup = void;
export type ObservabilityOnboardingPluginStart = void;
@@ -44,6 +45,14 @@ export interface ObservabilityOnboardingPluginStartDeps {
observability: ObservabilityPublicStart;
}
+export interface ObservabilityOnboardingPluginContextValue {
+ core: CoreStart;
+ plugins: ObservabilityOnboardingPluginSetupDeps;
+ data: DataPublicPluginStart;
+ observability: ObservabilityPublicStart;
+ config: ConfigSchema;
+}
+
export class ObservabilityOnboardingPlugin
implements
Plugin<
diff --git a/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx b/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx
index c2a18238943d2..411eb2b8e79e1 100644
--- a/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx
+++ b/x-pack/plugins/observability_onboarding/public/routes/templates/custom_logs.tsx
@@ -9,6 +9,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
import React, { ComponentType, useRef, useState } from 'react';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+import { ObservabilityOnboardingPluginContextValue } from '../../plugin';
import { breadcrumbsApp } from '../../application/app';
import { Provider as WizardProvider } from '../../components/app/custom_logs';
import {
@@ -16,6 +18,7 @@ import {
FilmstripTransition,
TransitionState,
} from '../../components/shared/filmstrip_transition';
+import { ObservabilityOnboardingHeaderActionMenu } from '../../components/app/header_action_menu';
interface Props {
children: React.ReactNode;
@@ -43,6 +46,12 @@ function AnimatedTransitionsWizard({ children }: Props) {
const [title, setTitle] = useState();
const TransitionComponent = useRef(() => null);
+ const {
+ services: { config },
+ } = useKibana();
+
+ const isServerless = config.serverless.enabled;
+
function onChangeStep({
direction,
stepTitle,
@@ -68,21 +77,33 @@ function AnimatedTransitionsWizard({ children }: Props) {