From 9bcefe356fba379c27089145204f0550f8347230 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 23 Jul 2024 15:44:42 +0200 Subject: [PATCH 1/9] [Index Management] Fix failed tests on mki (#187963) --- .../index_management/common/types/indices.ts | 1 + .../index_management/svl_templates.api.ts | 9 ++- .../index_management/svl_templates.helpers.ts | 4 +- .../index_management/index_templates.ts | 36 ++++++------ .../common/index_management/settings.ts | 56 +++---------------- 5 files changed, 35 insertions(+), 71 deletions(-) diff --git a/x-pack/plugins/index_management/common/types/indices.ts b/x-pack/plugins/index_management/common/types/indices.ts index 678d5c854470d4..0e8a8725a56cd4 100644 --- a/x-pack/plugins/index_management/common/types/indices.ts +++ b/x-pack/plugins/index_management/common/types/indices.ts @@ -11,6 +11,7 @@ interface IndexModule { number_of_shards: number | string; codec: string; routing_partition_size: number; + refresh_interval: string; load_fixed_bitset_filters_eagerly: boolean; shard: { check_on_startup: boolean | 'checksum'; diff --git a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.api.ts b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.api.ts index 10a95efbcd9f82..5960bfdd87e8e9 100644 --- a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.api.ts +++ b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.api.ts @@ -7,7 +7,7 @@ import { TemplateDeserialized, TemplateSerialized } from '@kbn/index-management-plugin/common'; import { API_BASE_PATH } from './constants'; -import { InternalRequestHeader, RoleCredentials } from '../../../shared/services'; +import { RoleCredentials } from '../../../shared/services'; import { FtrProviderContext } from '../../ftr_provider_context'; export function SvlTemplatesApi({ getService }: FtrProviderContext) { @@ -16,8 +16,11 @@ export function SvlTemplatesApi({ getService }: FtrProviderContext) { let templatesCreated: Array<{ name: string; isLegacy?: boolean }> = []; - const getAllTemplates = (internalReqHeader: InternalRequestHeader, roleAuthc: RoleCredentials) => - supertestWithoutAuth.get(`${API_BASE_PATH}/index_templates`).set(roleAuthc.apiKeyHeader); + const getAllTemplates = (roleAuthc: RoleCredentials) => + supertestWithoutAuth + .get(`${API_BASE_PATH}/index_templates`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader); const getOneTemplate = (name: string, isLegacy: boolean = false) => supertestWithoutAuth.get(`${API_BASE_PATH}/index_templates/${name}?legacy=${isLegacy}`); diff --git a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts index 16e453e10edf5a..0f6006fd914700 100644 --- a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts +++ b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts @@ -10,9 +10,7 @@ import { INDEX_PATTERNS } from './constants'; import { FtrProviderContext } from '../../ftr_provider_context'; const templateMock = { - settings: { - number_of_shards: 1, - }, + settings: {}, mappings: { properties: { host_name: { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts index 90f38c9e9801b1..1e0dff8bef632a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts @@ -27,8 +27,7 @@ export default function ({ getService }: FtrProviderContext) { const getRandomString: () => string = () => randomness.string({ casing: 'lower', alpha: true }); - // see details: https://github.com/elastic/kibana/issues/187368 - describe.skip('Index templates', function () { + describe('Index templates', function () { before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); @@ -136,6 +135,7 @@ export default function ({ getService }: FtrProviderContext) { undefined, false ); + const { status } = await svlTemplatesApi.createTemplate(payload, roleAuthc); expect(status).to.eql(200); }); @@ -214,12 +214,13 @@ export default function ({ getService }: FtrProviderContext) { const { status } = await svlTemplatesApi.createTemplate(indexTemplate, roleAuthc); expect(status).to.eql(200); - let { body: catTemplateResponse } = await svlTemplatesHelpers.catTemplate(templateName); - + const { body: templates } = await svlTemplatesApi.getAllTemplates(roleAuthc); const { name, version } = indexTemplate; expect( - catTemplateResponse.find(({ name: catTemplateName }) => catTemplateName === name)?.version - ).to.equal(version?.toString()); + templates.templates.find( + ({ name: catTemplateName }: { name: string }) => catTemplateName === name + )?.version + ).to.equal(version); // Update template with new version const updatedVersion = 2; @@ -230,11 +231,13 @@ export default function ({ getService }: FtrProviderContext) { ); expect(updateStatus).to.eql(200); - ({ body: catTemplateResponse } = await svlTemplatesHelpers.catTemplate(templateName)); + const { body: templates2 } = await svlTemplatesApi.getAllTemplates(roleAuthc); expect( - catTemplateResponse.find(({ name: catTemplateName }) => catTemplateName === name)?.version - ).to.equal(updatedVersion.toString()); + templates2.templates.find( + ({ name: catTemplateName }: { name: string }) => catTemplateName === name + )?.version + ).to.equal(updatedVersion); }); it('should parse the ES error and return the cause', async () => { @@ -267,7 +270,8 @@ export default function ({ getService }: FtrProviderContext) { templateName, roleAuthc ); - expect(updateStatus).to.eql(404); + + expect(updateStatus).to.eql(400); expect(body.attributes).an('object'); // one of the item of the cause array should point to our script @@ -292,10 +296,10 @@ export default function ({ getService }: FtrProviderContext) { if (createStatus !== 200) throw new Error(`Error creating template: ${createStatus} ${createBody.message}`); - const { body: catTemplateResponse } = await svlTemplatesHelpers.catTemplate(templateName); + const { body: allTemplates } = await svlTemplatesApi.getAllTemplates(roleAuthc); expect( - catTemplateResponse.find((template) => template.name === payload.name)?.name + allTemplates.templates.find(({ name }: { name: string }) => name === payload.name)?.name ).to.equal(templateName); const { status: deleteStatus, body: deleteBody } = await svlTemplatesApi.deleteTemplates( @@ -308,11 +312,11 @@ export default function ({ getService }: FtrProviderContext) { expect(deleteBody.errors).to.be.empty(); expect(deleteBody.templatesDeleted[0]).to.equal(templateName); - const { body: catTemplateResponse2 } = await svlTemplatesHelpers.catTemplate(templateName); + const { body: allTemplates2 } = await svlTemplatesApi.getAllTemplates(roleAuthc); - expect(catTemplateResponse2.find((template) => template.name === payload.name)).to.equal( - undefined - ); + expect( + allTemplates2.templates.find(({ name }: { name: string }) => name === payload.name) + ).to.equal(undefined); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/settings.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/settings.ts index 62756e70d753b9..b54393da6f6f70 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/settings.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/settings.ts @@ -17,8 +17,7 @@ export default function ({ getService }: FtrProviderContext) { const svlIndicesHelpers = getService('svlIndicesHelpers'); let roleAuthc: RoleCredentials; - // see details: https://github.com/elastic/kibana/issues/187369 - describe.skip('settings', function () { + describe('settings', function () { before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); }); @@ -34,60 +33,16 @@ export default function ({ getService }: FtrProviderContext) { const { status, body } = await svlSettingsApi.getIndexSettings(index, roleAuthc); svlCommonApi.assertResponseStatusCode(200, status, body); - // Verify we fetch the corret index settings - expect(body.settings.index.provided_name).to.be(index); - const expectedSettings = [ - 'max_inner_result_window', - 'unassigned', - 'max_terms_count', 'lifecycle', - 'routing_partition_size', - 'max_docvalue_fields_search', 'merge', - 'max_refresh_listeners', - 'max_regex_length', - 'load_fixed_bitset_filters_eagerly', - 'number_of_routing_shards', - 'write', - 'verified_before_close', 'mapping', - 'source_only', - 'soft_deletes', - 'max_script_fields', 'query', - 'format', - 'frozen', 'sort', - 'priority', 'codec', - 'max_rescore_window', - 'analyze', - 'gc_deletes', - 'max_ngram_diff', - 'translog', - 'auto_expand_replicas', - 'requests', - 'data_path', - 'highlight', - 'routing', - 'search', - 'fielddata', 'default_pipeline', - 'max_slices_per_scroll', - 'shard', - 'xpack', - 'percolator', - 'allocation', 'refresh_interval', - 'indexing', - 'compound_format', 'blocks', - 'max_result_window', - 'store', - 'queries', - 'warmer', - 'max_shingle_diff', 'query_string', ]; @@ -105,17 +60,20 @@ export default function ({ getService }: FtrProviderContext) { const index = await svlIndicesHelpers.createIndex(); const { body: body1 } = await svlSettingsApi.getIndexSettings(index, roleAuthc); - expect(body1.settings.index.number_of_replicas).to.be('1'); + + // There are no settings by default + expect(body1.settings?.index?.number_of_replicas).to.be(undefined); const settings = { index: { - number_of_replicas: 2, + refresh_interval: '7s', }, }; await svlSettingsApi.updateIndexSettings(index, settings, roleAuthc); const { body: body2 } = await svlSettingsApi.getIndexSettings(index, roleAuthc); - expect(body2.settings.index.number_of_replicas).to.be('2'); + + expect(body2.settings.index.refresh_interval).to.be('7s'); }); }); } From dc3d9600daa0e5fab6686f2c130cf1e67dd4450c Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 23 Jul 2024 15:45:28 +0200 Subject: [PATCH 2/9] [Index Management] Support rollover of datastreams from component templates linked to managed/packaged index templates (#187733) --- .../common/types/component_templates.ts | 8 ++ .../component_template_edit.test.tsx | 91 +++++++++++++++++-- .../component_template_form.helpers.ts | 1 + .../helpers/http_requests.ts | 13 +++ .../component_template_create.tsx | 39 +++++++- .../component_template_edit.tsx | 15 ++- .../component_template_form.tsx | 15 +++ .../steps/step_review.tsx | 14 +-- .../steps/step_review_container.tsx | 11 ++- .../components/component_templates/lib/api.ts | 21 +++++ .../component_templates/shared_imports.ts | 1 + .../routes/api/component_templates/index.ts | 6 +- .../register_datastream_route.ts | 38 ++++++++ 13 files changed, 248 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/index_management/common/types/component_templates.ts b/x-pack/plugins/index_management/common/types/component_templates.ts index be285170f668e9..a84e1a3fe6eb08 100644 --- a/x-pack/plugins/index_management/common/types/component_templates.ts +++ b/x-pack/plugins/index_management/common/types/component_templates.ts @@ -49,3 +49,11 @@ export interface ComponentTemplateListItem { export interface ComponentTemplateDatastreams { data_streams: string[]; } + +export interface ComponentTemplateMeta { + managed: boolean; + managed_by: string; + package: { + name: string; + }; +} diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx index 555e4030b92c12..1929be89aa3399 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx @@ -78,7 +78,6 @@ describe('', () => { template: { settings: { number_of_shards: 1 }, }, - _kbnMeta: { usedBy: [], isManaged: false }, }; beforeEach(async () => { @@ -166,27 +165,30 @@ describe('', () => { }), }) ); - // Mapping rollout modal should not be opened if the component template is not managed by Fleet + // Mapping rollout modal should not be opened if the component template is not managed expect(coreStart.overlays.openModal).not.toBeCalled(); }); }); - describe('managed by fleet', () => { + describe('can rollover linked datastreams', () => { const DATASTREAM_NAME = 'logs-test-default'; + const CUSTOM_COMPONENT_TEMPLATE = 'comp-1@custom'; + const ENCODED_CUSTOM_COMPONENT_TEMPLATE = encodeURIComponent(CUSTOM_COMPONENT_TEMPLATE); + beforeEach(async () => { httpRequestsMockHelpers.setLoadComponentTemplateResponse( - COMPONENT_TEMPLATE_TO_EDIT.name, + ENCODED_CUSTOM_COMPONENT_TEMPLATE, Object.assign({}, COMPONENT_TEMPLATE_TO_EDIT, { - _meta: { managed_by: 'fleet' }, + name: CUSTOM_COMPONENT_TEMPLATE, }) ); - httpRequestsMockHelpers.setGetComponentTemplateDatastream(COMPONENT_TEMPLATE_TO_EDIT.name, { + httpRequestsMockHelpers.setGetComponentTemplateDatastream(ENCODED_CUSTOM_COMPONENT_TEMPLATE, { data_streams: [DATASTREAM_NAME], }); await act(async () => { - testBed = await setup(httpSetup); + testBed = await setup(httpSetup, '@custom'); }); testBed.component.update(); @@ -221,7 +223,7 @@ describe('', () => { component.update(); expect(httpSetup.put).toHaveBeenLastCalledWith( - `${API_BASE_PATH}/component_templates/${COMPONENT_TEMPLATE_TO_EDIT.name}`, + `${API_BASE_PATH}/component_templates/${ENCODED_CUSTOM_COMPONENT_TEMPLATE}`, expect.anything() ); expect(httpSetup.post).toHaveBeenLastCalledWith( @@ -259,7 +261,7 @@ describe('', () => { component.update(); expect(httpSetup.put).toHaveBeenLastCalledWith( - `${API_BASE_PATH}/component_templates/${COMPONENT_TEMPLATE_TO_EDIT.name}`, + `${API_BASE_PATH}/component_templates/${ENCODED_CUSTOM_COMPONENT_TEMPLATE}`, expect.anything() ); expect(httpSetup.post).toHaveBeenLastCalledWith( @@ -269,5 +271,76 @@ describe('', () => { expect(coreStart.overlays.openModal).not.toBeCalled(); }); + + it('should show mappings rollover modal on save if referenced index template is managed and packaged', async () => { + httpRequestsMockHelpers.setLoadComponentTemplateResponse( + COMPONENT_TEMPLATE_TO_EDIT.name, + Object.assign({}, COMPONENT_TEMPLATE_TO_EDIT, { + _meta: {}, + }) + ); + + httpRequestsMockHelpers.setGetComponentTemplateDatastream(COMPONENT_TEMPLATE_TO_EDIT.name, { + data_streams: [DATASTREAM_NAME], + }); + + httpRequestsMockHelpers.setLoadReferencedIndexTemplateMetaResponse( + COMPONENT_TEMPLATE_TO_EDIT.name, + { + package: { + name: 'security', + }, + managed_by: 'security', + managed: true, + } + ); + + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + + httpRequestsMockHelpers.setPostDatastreamMappingsFromTemplate( + DATASTREAM_NAME, + {}, + { message: 'Bad request', statusCode: 400 } + ); + const { exists, actions, component, form, coreStart } = testBed; + + await act(async () => { + form.setInputValue('versionField.input', '1'); + }); + + await act(async () => { + actions.clickNextButton(); + }); + + component.update(); + + await actions.completeStepSettings(); + await actions.completeStepMappings(); + await actions.completeStepAliases(); + + // Make sure the list of affected mappings is shown + expect(exists('affectedMappingsList')).toBe(true); + + await act(async () => { + actions.clickNextButton(); + }); + + component.update(); + + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/component_templates/${COMPONENT_TEMPLATE_TO_EDIT.name}`, + expect.anything() + ); + expect(httpSetup.post).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/data_streams/${DATASTREAM_NAME}/mappings_from_template`, + expect.anything() + ); + + expect(coreStart.overlays.openModal).toBeCalled(); + }); }); }); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts index 03aff69a548af1..d8b2452ce9bee2 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts @@ -189,4 +189,5 @@ export type ComponentTemplateFormTestSubjects = | 'aliasesEditor' | 'mappingsEditor' | 'settingsEditor' + | 'affectedMappingsList' | 'versionField.input'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts index 8ce2a34db397bd..cc3124fb7a19f4 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts @@ -76,6 +76,18 @@ const registerHttpRequestMockHelpers = ( error?: ResponseError ) => mockResponse('GET', `${API_BASE_PATH}/component_templates/${templateId}`, response, error); + const setLoadReferencedIndexTemplateMetaResponse = ( + templateId: string, + response?: HttpResponse, + error?: ResponseError + ) => + mockResponse( + 'GET', + `${API_BASE_PATH}/component_templates/${templateId}/referenced_index_template_meta`, + response, + error + ); + const setDeleteComponentTemplateResponse = ( templateId: string, response?: HttpResponse, @@ -100,6 +112,7 @@ const registerHttpRequestMockHelpers = ( return { setLoadComponentTemplatesResponse, + setLoadReferencedIndexTemplateMetaResponse, setDeleteComponentTemplateResponse, setLoadComponentTemplateResponse, setCreateComponentTemplateResponse, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx index 736f4410b92377..09c2edb834445d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx @@ -17,7 +17,6 @@ import { useComponentTemplatesContext } from '../../component_templates_context' import { ComponentTemplateForm } from '../component_template_form'; import { useStepFromQueryString } from '../use_step_from_query_string'; import { useDatastreamsRollover } from '../component_template_datastreams_rollover/use_datastreams_rollover'; -import { MANAGED_BY_FLEET } from '../../constants'; interface Props { /** @@ -33,13 +32,38 @@ export const ComponentTemplateCreate: React.FunctionComponent(false); const [saveError, setSaveError] = useState(null); const redirectTo = useRedirectPath(history); + const [currentStep, setCurrentStep] = useState('logistics'); + const [componentName, setComponentName] = useState(); + const [canRollover, setCanRollover] = useState(false); + const { api } = useComponentTemplatesContext(); const { activeStep: defaultActiveStep, updateStep } = useStepFromQueryString(history); const locationSearchParams = useMemo(() => { return new URLSearchParams(history.location.search); }, [history.location.search]); + // Effect for computing if we should allow the user to rollover attached datastreams + useEffect(() => { + async function computeCanRollover() { + // When the current step is not logistics, we have an available component template + // name that we can use to query the referenced index template. + if (currentStep !== 'logistics') { + // If the component template is referenced by an index template that is part of + // a package and is managed we can allow the user to roll it over if possible. + const { data: refIndexTemplate } = await api.getReferencedIndexTemplateMeta( + componentName as string + ); + + setCanRollover(Boolean(refIndexTemplate?.managed_by && refIndexTemplate?.package)); + } + + setCanRollover(false); + } + + computeCanRollover(); + }, [api, currentStep, componentName, setCanRollover]); + const defaultValue = useMemo(() => { if (sourceComponentTemplate) { return sourceComponentTemplate; @@ -59,7 +83,6 @@ export const ComponentTemplateCreate: React.FunctionComponent { @@ -76,7 +99,11 @@ export const ComponentTemplateCreate: React.FunctionComponent { + setCurrentStep(step); + updateStep(step); + }} defaultValue={defaultValue} onSave={onSave} isSaving={isSaving} saveError={saveError} + setComponentName={setComponentName} clearSaveError={clearSaveError} /> diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx index 8944973a0d4805..61402135c7fa0d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx @@ -21,7 +21,6 @@ import { } from '../../shared_imports'; import { ComponentTemplateForm } from '../component_template_form'; import { useRedirectPath } from '../../../../hooks/redirect_path'; -import { MANAGED_BY_FLEET } from '../../constants'; import { useStepFromQueryString } from '../use_step_from_query_string'; import { useDatastreamsRollover } from '../component_template_datastreams_rollover/use_datastreams_rollover'; @@ -48,6 +47,13 @@ export const ComponentTemplateEdit: React.FunctionComponent dataStreamResponse?.data_streams ?? [], [dataStreamResponse]); + // If the component template is referenced by an index template that is part of + // a package and is managed we can allow the user to roll it over if possible. + const { data: refIndexTemplate } = api.useLoadReferencedIndexTemplateMeta(decodedName); + const canRollover = useMemo( + () => Boolean(refIndexTemplate?.managed_by && refIndexTemplate?.package), + [refIndexTemplate] + ); const { showDatastreamRolloverModal } = useDatastreamsRollover(); @@ -68,9 +74,13 @@ export const ComponentTemplateEdit: React.FunctionComponent void; clearSaveError: () => void; + setComponentName?: (name: string) => void; isSaving: boolean; saveError: any; defaultValue?: ComponentTemplateDeserialized; @@ -45,6 +46,7 @@ interface Props { defaultActiveWizardSection?: WizardSection; onStepChange?: (stepId: string) => void; dataStreams?: string[]; + canRollover?: boolean; } const wizardSections: { [id: string]: { id: WizardSection; label: string } } = { @@ -90,7 +92,9 @@ export const ComponentTemplateForm = ({ isManaged: false, }, }, + setComponentName, dataStreams, + canRollover, isEditing, isSaving, saveError, @@ -237,6 +241,16 @@ export const ComponentTemplateForm = ({ texts={i18nTexts} defaultActiveStep={defaultActiveStepIndex} onStepChange={onStepChange} + onChange={(attrs) => { + // Let the parent component know the name of the component template in the + // form has changed, so that it can re-compute the canRollover prop. + // This is needed for determinating if the user should see a rollover + // attached datastreams modal or not. + const data = attrs.getData(); + if (setComponentName) { + setComponentName(data.logistics.name); + } + }} > diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx index c2e9a37cab4085..6a421c50bcd323 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx @@ -27,7 +27,6 @@ import { serializers, serializeComponentTemplate, } from '../../../shared_imports'; -import { MANAGED_BY_FLEET } from '../../../constants'; import { getLifecycleValue } from '../../../../../lib/data_streams'; const INFINITE_AS_ICON = true; @@ -52,10 +51,11 @@ const getDescriptionText = (data: any) => { interface Props { componentTemplate: ComponentTemplateDeserialized; dataStreams?: string[]; + canRollover?: boolean; } export const StepReview: React.FunctionComponent = React.memo( - ({ dataStreams, componentTemplate }) => { + ({ dataStreams, canRollover, componentTemplate }) => { const { name } = componentTemplate; const serializedComponentTemplate = serializeComponentTemplate( @@ -70,8 +70,8 @@ export const StepReview: React.FunctionComponent = React.memo( version: serializedVersion, } = serializedComponentTemplate; - const isFleetDatastreamsVisible = - Boolean(dataStreams?.length) && componentTemplate._meta?.managed_by === MANAGED_BY_FLEET; + const areDatastreamsVisible = + Boolean(dataStreams?.length) && (componentTemplate.name.endsWith('@custom') || canRollover); const SummaryTab = () => (
@@ -138,8 +138,8 @@ export const StepReview: React.FunctionComponent = React.memo( - {isFleetDatastreamsVisible && dataStreams && ( - + {areDatastreamsVisible && dataStreams && ( + {/* Datastream mappings */} = React.memo( {request} - {isFleetDatastreamsVisible && ( + {areDatastreamsVisible && ( <> diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review_container.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review_container.tsx index b2f2fbd0d57fdd..8f6c077617308c 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review_container.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review_container.tsx @@ -14,16 +14,23 @@ import { StepReview } from './step_review'; interface Props { getComponentTemplateData: (wizardContent: WizardContent) => ComponentTemplateDeserialized; dataStreams?: string[]; + canRollover?: boolean; } export const StepReviewContainer = React.memo( - ({ getComponentTemplateData, dataStreams }: Props) => { + ({ getComponentTemplateData, dataStreams, canRollover }: Props) => { const { getData } = Forms.useMultiContentContext(); const wizardContent = getData(); // Build the final template object, providing the wizard content data const componentTemplate = getComponentTemplateData(wizardContent); - return ; + return ( + + ); } ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts index ca6f8f242d288a..f743ce5da777d8 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts @@ -11,6 +11,7 @@ import { ComponentTemplateDeserialized, ComponentTemplateSerialized, ComponentTemplateDatastreams, + ComponentTemplateMeta, } from '../shared_imports'; import { UIM_COMPONENT_TEMPLATE_DELETE_MANY, @@ -109,13 +110,33 @@ export const getApi = ( }); } + function useLoadReferencedIndexTemplateMeta(name: string) { + return useRequest({ + path: `${apiBasePath}/component_templates/${encodeURIComponent( + name + )}/referenced_index_template_meta`, + method: 'get', + }); + } + + async function getReferencedIndexTemplateMeta(name: string) { + return sendRequest({ + path: `${apiBasePath}/component_templates/${encodeURIComponent( + name + )}/referenced_index_template_meta`, + method: 'get', + }); + } + return { useLoadComponentTemplates, deleteComponentTemplates, useLoadComponentTemplate, createComponentTemplate, updateComponentTemplate, + useLoadReferencedIndexTemplateMeta, useLoadComponentTemplatesDatastream, + getReferencedIndexTemplateMeta, getComponentTemplateDatastreams, postDataStreamRollover, postDataStreamMappingsFromTemplate, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts index d70ed32e2cdf82..05bdd8e2f3068d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts @@ -68,6 +68,7 @@ export type { ComponentTemplateDeserialized, ComponentTemplateListItem, ComponentTemplateDatastreams, + ComponentTemplateMeta, } from '../../../../common'; export { serializeComponentTemplate } from '../../../../common/lib'; diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/index.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/index.ts index 9a3e22068a7f3a..535d6c9352a73f 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/index.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/index.ts @@ -12,13 +12,17 @@ import { registerCreateRoute } from './register_create_route'; import { registerUpdateRoute } from './register_update_route'; import { registerDeleteRoute } from './register_delete_route'; import { registerPrivilegesRoute } from './register_privileges_route'; -import { registerGetDatastreams } from './register_datastream_route'; +import { + registerGetDatastreams, + registerReferencedIndexTemplateMeta, +} from './register_datastream_route'; export function registerComponentTemplateRoutes(dependencies: RouteDependencies) { registerGetAllRoute(dependencies); registerCreateRoute(dependencies); registerUpdateRoute(dependencies); registerGetDatastreams(dependencies); + registerReferencedIndexTemplateMeta(dependencies); registerDeleteRoute(dependencies); registerPrivilegesRoute(dependencies); } diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/register_datastream_route.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/register_datastream_route.ts index 8a7f1917a42181..8474c0d4c96600 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/register_datastream_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/register_datastream_route.ts @@ -77,3 +77,41 @@ export const registerGetDatastreams = ({ } ); }; + +export const registerReferencedIndexTemplateMeta = ({ + router, + lib: { handleEsError }, +}: RouteDependencies): void => { + router.get( + { + path: addBasePath('/component_templates/{name}/referenced_index_template_meta'), + validate: { + params: paramsSchema, + }, + }, + async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const { name } = request.params; + + try { + const { index_templates: indexTemplates } = + await client.asCurrentUser.indices.getIndexTemplate(); + const result = indexTemplates.filter((indexTemplate) => + indexTemplate.index_template?.composed_of?.includes(name) + ); + + // We should always match against the first result which should yield + // the index template we are after. + if (result[0]) { + return response.ok({ + body: result[0].index_template._meta, + }); + } + + return response.notFound(); + } catch (error) { + return handleEsError({ error, response }); + } + } + ); +}; From 457f08bb37016a71f0cbd21f0e51bf3283760982 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Tue, 23 Jul 2024 08:05:47 -0600 Subject: [PATCH 3/9] [Embeddable Rebuild] [Controls] Fix control state on edit (#188784) ## Summary This PR fixes control editing so that, when the control type is changed, extra state from the old type gets removed. Prior to this, controls were keeping unrelated state - for example, switching from a range slider to a search control would result in a search control with the "step" property. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../data_controls/data_control_editor.tsx | 6 +++--- .../data_controls/initialize_data_control.tsx | 2 +- .../get_range_slider_control_factory.test.tsx | 4 ++-- .../range_slider/get_range_slider_control_factory.tsx | 8 +++----- .../search_control/get_search_control_factory.tsx | 10 ++++------ .../public/react_controls/data_controls/types.ts | 2 +- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/examples/controls_example/public/react_controls/data_controls/data_control_editor.tsx b/examples/controls_example/public/react_controls/data_controls/data_control_editor.tsx index 5e0079342a6dfa..6776a24e1874dd 100644 --- a/examples/controls_example/public/react_controls/data_controls/data_control_editor.tsx +++ b/examples/controls_example/public/react_controls/data_controls/data_control_editor.tsx @@ -147,6 +147,7 @@ export const DataControlEditor = (false); + /** TODO: Make `editorConfig` work when refactoring the `ControlGroupRenderer` */ // const editorConfig = controlGroup.getEditorConfig(); @@ -193,7 +194,6 @@ export const DataControlEditor = setEditorState({ ...editorState, ...newState })} setControlEditorValid={setControlEditorValid} /> ); - }, [fieldRegistry, selectedControlType, editorState, initialState]); + }, [fieldRegistry, selectedControlType, editorState]); return ( <> diff --git a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.tsx b/examples/controls_example/public/react_controls/data_controls/initialize_data_control.tsx index 2f5babd94951dd..31cb979e5ba395 100644 --- a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.tsx +++ b/examples/controls_example/public/react_controls/data_controls/initialize_data_control.tsx @@ -139,7 +139,7 @@ export const initializeDataControl = ( ); } else { // replace the control with a new one of the updated type - controlGroup.replacePanel(controlId, { panelType: newType, initialState }); + controlGroup.replacePanel(controlId, { panelType: newType, initialState: newState }); } }, initialState: { diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx index 73aa593c0ce565..23ff4c4d206349 100644 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx @@ -248,7 +248,7 @@ describe('RangesliderControlApi', () => { const CustomSettings = factory.CustomOptionsComponent!; const component = render( @@ -263,7 +263,7 @@ describe('RangesliderControlApi', () => { const CustomSettings = factory.CustomOptionsComponent!; const component = render( diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx index ed882873f73a78..b6ba29b7d0095c 100644 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo } from 'react'; import deepEqual from 'react-fast-compare'; import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; @@ -38,9 +38,8 @@ export const getRangesliderControlFactory = ( isFieldCompatible: (field) => { return field.aggregatable && field.type === 'number'; }, - CustomOptionsComponent: ({ initialState, updateState, setControlEditorValid }) => { - const [step, setStep] = useState(initialState.step ?? 1); - + CustomOptionsComponent: ({ currentState, updateState, setControlEditorValid }) => { + const step = currentState.step ?? 1; return ( <> @@ -48,7 +47,6 @@ export const getRangesliderControlFactory = ( value={step} onChange={(event) => { const newStep = event.target.valueAsNumber; - setStep(newStep); updateState({ step: newStep }); setControlEditorValid(newStep > 0); }} diff --git a/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx b/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx index 1fde5a4ef6ca76..9997fd4e6d64e1 100644 --- a/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx +++ b/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import deepEqual from 'react-fast-compare'; import { BehaviorSubject, combineLatest, debounceTime, distinctUntilChanged } from 'rxjs'; @@ -65,17 +65,15 @@ export const getSearchControlFactory = ({ (field.spec.esTypes ?? []).includes('text') ); }, - CustomOptionsComponent: ({ initialState, updateState }) => { - const [searchTechnique, setSearchTechnique] = useState(initialState.searchTechnique); - + CustomOptionsComponent: ({ currentState, updateState }) => { + const searchTechnique = currentState.searchTechnique ?? DEFAULT_SEARCH_TECHNIQUE; return ( { const newSearchTechnique = id as SearchControlTechniques; - setSearchTechnique(newSearchTechnique); updateState({ searchTechnique: newSearchTechnique }); }} /> diff --git a/examples/controls_example/public/react_controls/data_controls/types.ts b/examples/controls_example/public/react_controls/data_controls/types.ts index a4cf9cab9fc53b..b3379889f4223a 100644 --- a/examples/controls_example/public/react_controls/data_controls/types.ts +++ b/examples/controls_example/public/react_controls/data_controls/types.ts @@ -30,7 +30,7 @@ export interface DataControlFactory< > extends ControlFactory { isFieldCompatible: (field: DataViewField) => boolean; CustomOptionsComponent?: React.FC<{ - initialState: Omit; + currentState: Partial; updateState: (newState: Partial) => void; setControlEditorValid: (valid: boolean) => void; }>; From 2cc03329e43d74f68101dbd514818854fd280887 Mon Sep 17 00:00:00 2001 From: dkirchan <55240027+dkirchan@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:45:39 +0300 Subject: [PATCH 4/9] [Security] Fixed key issue in pipeline for quality gate (#188952) A " was missed in a key in the pipeline for the rule management tests for Security Quality Gate. --- .../mki_quality_gate/mki_quality_gate_rule_management.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml index 5134d96f043c85..affdaca9cb539a 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml @@ -22,7 +22,7 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management:prebuilt_rules - label: "Cypress MKI - Rule Management - Prebuilt Rules + label: "Cypress MKI - Rule Management - Prebuilt Rules" key: test_rule_management_prebuilt_rules env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" From 5d9d92b88e92546f664d8d7c5ff78e06ec20b55b Mon Sep 17 00:00:00 2001 From: Katerina Date: Tue, 23 Jul 2024 18:01:40 +0300 Subject: [PATCH 5/9] [APM][ECO] Promote new experience when no apm data found (#188867) closes https://github.com/elastic/observability-dev/issues/3737 ## Summary - When FF is disabled it shows the existing no data page - The no data config is very limited in the template, thus we had to go against the guidelines and create a custom no data page for the new experience. - The user needs to have permissions to enable EEM, othewise same modal appears with slightly different copy Additionally, the PR includes - Small refactoring in the enablement component in order to share it - Add short link that was misseed https://github.com/user-attachments/assets/5d3bbe83-682a-47a1-a9af-770f1ca42876 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../routing/templates/apm_main_template.tsx | 31 ++++- .../templates/custom_no_data_template.tsx | 124 ++++++++++++++++++ .../shared/entity_enablement/index.tsx | 107 ++++++++------- .../entity_enablement/unauthorized_modal.tsx | 18 ++- .../observability_solution/apm/tsconfig.json | 4 +- .../page_template/page_template.tsx | 4 +- 6 files changed, 232 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/observability_solution/apm/public/components/routing/templates/custom_no_data_template.tsx diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx index 29a0bb4eae1ec2..892d17490e3c0d 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx @@ -11,6 +11,7 @@ import { entityCentricExperience } from '@kbn/observability-plugin/common'; import { ObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; import React, { useContext } from 'react'; +import { i18n } from '@kbn/i18n'; import { useLocation } from 'react-router-dom'; import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public'; import { useEntityManagerEnablementContext } from '../../../context/entity_manager_context/use_entity_manager_enablement_context'; @@ -26,6 +27,8 @@ import { ApmEnvironmentFilter } from '../../shared/environment_filter'; import { getNoDataConfig } from './no_data_config'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { EntityEnablement } from '../../shared/entity_enablement'; +import { CustomNoDataTemplate } from './custom_no_data_template'; +import { ServiceInventoryView } from '../../../context/entity_manager_context/entity_manager_context'; // Paths that must skip the no data screen const bypassNoDataScreenPaths = ['/settings', '/diagnostics']; @@ -76,7 +79,8 @@ export function ApmMainTemplate({ entityCentricExperience, false ); - const { isEntityCentricExperienceViewEnabled } = useEntityManagerEnablementContext(); + const { isEntityCentricExperienceViewEnabled, serviceInventoryViewLocalStorageSetting } = + useEntityManagerEnablementContext(); const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate; @@ -114,6 +118,10 @@ export function ApmMainTemplate({ const hasApmData = !!data?.hasData; const hasApmIntegrations = !!fleetApmPoliciesData?.hasApmPolicies; + const showCustomEmptyState = + !hasApmData && + isEntityCentricExperienceSettingEnabled && + serviceInventoryViewLocalStorageSetting === ServiceInventoryView.classic; const noDataConfig = getNoDataConfig({ basePath, @@ -160,9 +168,16 @@ export function ApmMainTemplate({ ); - const pageTemplate = ( + const pageTemplate = showCustomEmptyState ? ( + + ) : ( + ) : null} {showServiceGroupsNav && selectedNavButton && ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/custom_no_data_template.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/custom_no_data_template.tsx new file mode 100644 index 00000000000000..677bbdad6f78ab --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/custom_no_data_template.tsx @@ -0,0 +1,124 @@ +/* + * 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, + EuiFlexItem, + EuiSpacer, + EuiTextColor, + EuiText, + EuiButton, + EuiPageTemplate, + EuiCard, + EuiImage, + EuiScreenReaderOnly, +} from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { KibanaSolutionAvatar } from '@kbn/shared-ux-avatar-solution'; +import { NoDataConfig } from '@kbn/shared-ux-page-no-data-config-types'; +import { ApmPluginStartDeps } from '../../../plugin'; +import { EntityEnablement } from '../../shared/entity_enablement'; + +export function CustomNoDataTemplate({ + isPageDataLoaded, + noDataConfig, +}: { + isPageDataLoaded: boolean; + noDataConfig?: NoDataConfig; +}) { + const { services } = useKibana(); + const { http, observabilityShared } = services; + const basePath = http?.basePath.get(); + + const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate; + const imageUrl = `${basePath}/plugins/kibanaReact/assets/elastic_agent_card.svg`; + + return ( + + + + + + +

+ {i18n.translate('xpack.apm.customEmtpyState.title', { + defaultMessage: 'Detect and resolve problems with your application', + })} +

+ +

+ {i18n.translate('xpack.apm.customEmtpyState.description', { + defaultMessage: + 'Start collecting data for your applications and services so you can detect and resolve problems faster.', + })} +

+
+
+ + + + {i18n.translate('xpack.apm.customEmtpyState.title.reader', { + defaultMessage: 'Add APM data', + })} + + + } + description={i18n.translate('xpack.apm.customEmtpyState.card.description', { + defaultMessage: + 'Use APM agents to collect APM data. We make it easy with agents for many popular languages.', + })} + footer={ + + + + {noDataConfig?.action.elasticAgent.title} + + + +

+ +

+
+
+
+ } + image={ + + } + /> +
+
+
+ ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx index 06fd44e2950fec..daf588fbc5b41a 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx @@ -28,7 +28,7 @@ import { FeedbackModal } from './feedback_modal'; import { ServiceInventoryView } from '../../../context/entity_manager_context/entity_manager_context'; import { Unauthorized } from './unauthorized_modal'; -export function EntityEnablement() { +export function EntityEnablement({ label, tooltip }: { label: string; tooltip?: string }) { const [isFeedbackModalVisible, setsIsFeedbackModalVisible] = useState(false); const [isUnauthorizedModalVisible, setsIsUnauthorizedModalVisible] = useState(false); @@ -37,6 +37,7 @@ export function EntityEnablement() { } = useKibana(); const { + isEntityManagerEnabled, isEnablementPending, refetch, setServiceInventoryViewLocalStorageSetting, @@ -52,6 +53,11 @@ export function EntityEnablement() { }; const handleEnablement = async () => { + if (isEntityManagerEnabled) { + setServiceInventoryViewLocalStorageSetting(ServiceInventoryView.entity); + return; + } + setIsLoading(true); try { const response = await entityManager.entityClient.enableManagedEntityDiscovery(); @@ -82,11 +88,15 @@ export function EntityEnablement() { ) : ( - {isLoading ? : } + {isLoading ? ( + + ) : ( + + )} @@ -94,54 +104,54 @@ export function EntityEnablement() { ? i18n.translate('xpack.apm.eemEnablement.enabled.', { defaultMessage: 'Viewing our new experience', }) - : i18n.translate('xpack.apm.eemEnablement.tryItButton.', { - defaultMessage: 'Try our new experience!', - })} + : label} - - - } - isOpen={isPopoverOpen} - closePopover={togglePopover} - anchorPosition="downLeft" - > -
- -

- {i18n.translate('xpack.apm.entityEnablement.content', { - defaultMessage: - 'Our new experience combines both APM-instrumented services with services detected from logs in a single service inventory.', - })} -

-
-
- - - - {i18n.translate('xpack.apm.entityEnablement.footer', { - defaultMessage: 'Learn more', + {tooltip && ( + + - - -
-
+ /> + } + isOpen={isPopoverOpen} + closePopover={togglePopover} + anchorPosition="downLeft" + > +
+ +

+ {i18n.translate('xpack.apm.entityEnablement.content', { + defaultMessage: + 'Our new experience combines both APM-instrumented services with services detected from logs in a single service inventory.', + })} +

+
+
+ + + + {i18n.translate('xpack.apm.entityEnablement.footer', { + defaultMessage: 'Learn more', + })} + + + + +
+ )} {isEntityCentricExperienceViewEnabled && ( @@ -158,6 +168,7 @@ export function EntityEnablement() { setsIsUnauthorizedModalVisible(false)} + label={label} /> ); diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/unauthorized_modal.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/unauthorized_modal.tsx index 8dd3682b61ff32..acf50474a08af4 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/unauthorized_modal.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/unauthorized_modal.tsx @@ -22,6 +22,7 @@ import { EuiFlexItem, EuiIcon, EuiImage, + EuiLink, EuiPanel, EuiSpacer, EuiText, @@ -32,9 +33,11 @@ import { useKibanaUrl } from '../../../hooks/use_kibana_url'; export function Unauthorized({ isUnauthorizedModalVisible = false, onClose, + label, }: { isUnauthorizedModalVisible?: boolean; onClose: () => void; + label: string; }) { const servicesInventory = useKibanaUrl('/plugins/apm/assets/services_inventory.png'); @@ -54,6 +57,18 @@ export function Unauthorized({ })} } + cancelButtonText={ + + {i18n.translate('xpack.apm.unauthorized.linkLinkLabel', { + defaultMessage: 'See how to enable EEM', + })} + + } defaultFocusedButton="confirm" > @@ -83,7 +98,8 @@ export function Unauthorized({

{i18n.translate('xpack.apm.unauthorised.body', { defaultMessage: - 'To see services detected from logs and APM-instrumented services in our new service inventory, please ask an administrator to visit this page and try our new experience. ', + 'To see services detected from logs and APM-instrumented services in our new service inventory, please ask an administrator to visit this page and {label}. ', + values: { label: label.toLowerCase() }, })}

diff --git a/x-pack/plugins/observability_solution/apm/tsconfig.json b/x-pack/plugins/observability_solution/apm/tsconfig.json index c081fce2e783b7..371119d697f9fa 100644 --- a/x-pack/plugins/observability_solution/apm/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm/tsconfig.json @@ -123,7 +123,9 @@ "@kbn/test-jest-helpers", "@kbn/security-plugin-types-common", "@kbn/entityManager-plugin", - "@kbn/react-hooks" + "@kbn/react-hooks", + "@kbn/shared-ux-avatar-solution", + "@kbn/shared-ux-page-no-data-config-types" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/observability_shared/public/components/page_template/page_template.tsx b/x-pack/plugins/observability_solution/observability_shared/public/components/page_template/page_template.tsx index f0daadda636eda..f485da7047a508 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/components/page_template/page_template.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/components/page_template/page_template.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiSideNavItemType, EuiPageSectionProps } from '@elastic/eui'; +import { EuiSideNavItemType, EuiPageSectionProps, EuiEmptyPromptProps } from '@elastic/eui'; import { _EuiPageBottomBarProps } from '@elastic/eui/src/components/page_template/bottom_bar/page_bottom_bar'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; @@ -39,7 +39,7 @@ export type WrappedPageTemplateProps = Pick< bottomBar?: React.ReactNode; bottomBarProps?: _EuiPageBottomBarProps; topSearchBar?: React.ReactNode; -}; +} & Pick; export interface NavigationEntry { // the label of the menu entry, should be translated From 14770214fd4bdf6e436660163ee07d8f682146c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Tue, 23 Jul 2024 16:06:20 +0100 Subject: [PATCH 6/9] [Spaces] Add warning for changes that impact other users (#188728) --- .../edit_space/manage_space_page.tsx | 90 ++++++++++++++----- .../create_edit_space.ts | 4 + 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx index 86d1c138843212..7594778062857f 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -8,6 +8,7 @@ import { EuiButton, EuiButtonEmpty, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiPageHeader, @@ -63,6 +64,8 @@ interface State { features: KibanaFeature[]; originalSpace?: Partial; showAlteringActiveSpaceDialog: boolean; + haveDisabledFeaturesChanged: boolean; + hasSolutionViewChanged: boolean; isLoading: boolean; saveInProgress: boolean; formError?: { @@ -74,7 +77,6 @@ interface State { export class ManageSpacePage extends Component { private readonly validator: SpaceValidator; - private initialSpaceState: State['space'] | null = null; constructor(props: Props) { super(props); @@ -88,6 +90,8 @@ export class ManageSpacePage extends Component { }, features: [], isSolutionNavEnabled: false, + haveDisabledFeaturesChanged: false, + hasSolutionViewChanged: false, }; } @@ -118,7 +122,32 @@ export class ManageSpacePage extends Component { }); } - public async componentDidUpdate(previousProps: Props) { + public async componentDidUpdate(previousProps: Props, prevState: State) { + const { originalSpace, space } = this.state; + + if (originalSpace && space) { + let haveDisabledFeaturesChanged = prevState.haveDisabledFeaturesChanged; + if (prevState.space.disabledFeatures !== space.disabledFeatures) { + haveDisabledFeaturesChanged = + space.disabledFeatures?.length !== originalSpace.disabledFeatures?.length || + difference(space.disabledFeatures, originalSpace.disabledFeatures ?? []).length > 0; + } + const hasSolutionViewChanged = + originalSpace.solution !== undefined + ? space.solution !== originalSpace.solution + : !!space.solution && space.solution !== 'classic'; + + if ( + prevState.haveDisabledFeaturesChanged !== haveDisabledFeaturesChanged || + prevState.hasSolutionViewChanged !== hasSolutionViewChanged + ) { + this.setState({ + haveDisabledFeaturesChanged, + hasSolutionViewChanged, + }); + } + } + if (this.props.spaceId !== previousProps.spaceId && this.props.spaceId) { await this.loadSpace(this.props.spaceId, Promise.resolve(this.state.features)); } @@ -190,6 +219,8 @@ export class ManageSpacePage extends Component { + {this.getChangeImpactWarning()} + {this.getFormButtons()} {showAlteringActiveSpaceDialog && ( @@ -221,6 +252,31 @@ export class ManageSpacePage extends Component { ); }; + public getChangeImpactWarning = () => { + if (!this.editingExistingSpace()) return null; + const { haveDisabledFeaturesChanged, hasSolutionViewChanged } = this.state; + if (!haveDisabledFeaturesChanged && !hasSolutionViewChanged) return null; + + return ( + <> + + + + + + ); + }; + public getFormButtons = () => { const createSpaceText = i18n.translate( 'xpack.spaces.management.manageSpacePage.createSpaceButton', @@ -244,6 +300,7 @@ export class ManageSpacePage extends Component { ); const saveText = this.editingExistingSpace() ? updateSpaceText : createSpaceText; + return ( @@ -296,6 +353,7 @@ export class ManageSpacePage extends Component { const originalSpace: Space = this.state.originalSpace as Space; const space: Space = this.state.space as Space; + const { haveDisabledFeaturesChanged, hasSolutionViewChanged } = this.state; const result = this.validator.validateForSave(space); if (result.isInvalid) { this.setState({ @@ -311,12 +369,6 @@ export class ManageSpacePage extends Component { spacesManager.getActiveSpace().then((activeSpace) => { const editingActiveSpace = activeSpace.id === originalSpace.id; - const haveDisabledFeaturesChanged = - space.disabledFeatures.length !== originalSpace.disabledFeatures.length || - difference(space.disabledFeatures, originalSpace.disabledFeatures).length > 0; - const hasSolutionViewChanged = - this.state.space.solution !== this.initialSpaceState?.solution; - if (editingActiveSpace && (haveDisabledFeaturesChanged || hasSolutionViewChanged)) { this.setState({ showAlteringActiveSpaceDialog: true, @@ -344,19 +396,17 @@ export class ManageSpacePage extends Component { onLoadSpace(space); } - this.initialSpaceState = { - ...space, - avatarType: space.imageUrl ? 'image' : 'initials', - initials: space.initials || getSpaceInitials(space), - color: space.color || getSpaceColor(space), - customIdentifier: false, - customAvatarInitials: - !!space.initials && getSpaceInitials({ name: space.name }) !== space.initials, - customAvatarColor: !!space.color && getSpaceColor({ name: space.name }) !== space.color, - }; - this.setState({ - space: { ...this.initialSpaceState }, + space: { + ...space, + avatarType: space.imageUrl ? 'image' : 'initials', + initials: space.initials || getSpaceInitials(space), + color: space.color || getSpaceColor(space), + customIdentifier: false, + customAvatarInitials: + !!space.initials && getSpaceInitials({ name: space.name }) !== space.initials, + customAvatarColor: !!space.color && getSpaceColor({ name: space.name }) !== space.color, + }, features, originalSpace: space, isLoading: false, diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts index b6b5eff6ef5f28..3f00dda32c8781 100644 --- a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts +++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts @@ -57,7 +57,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { shouldUseHashForSubUrl: false, }); + + await testSubjects.missingOrFail('userImpactWarning'); await PageObjects.spaceSelector.changeSolutionView('classic'); + await testSubjects.existOrFail('userImpactWarning'); // Warn that the change will impact other users + await PageObjects.spaceSelector.clickSaveSpaceCreation(); await PageObjects.spaceSelector.confirmModal(); From df9e95a087d23a0554a774a8fb59e53a6fb73021 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:14:36 +0200 Subject: [PATCH 7/9] [Fleet] Fix copy agent policy, missed bump revision (#188935) ## Summary Closes https://github.com/elastic/kibana/issues/188929 It looks like the copy agent functionality was not working well when the agent policy has integration policies or tamper protection enabled. The revision was not bumped, and the resulting documents in `.fleet-policies` were incorrect. To verify: - run local kibana with fleet-server 8.15 - create agent policy and add endpoint integration - copy the policy - verify that the copied policy is on `revision:2` and there is one document in `.fleet-policies` with `revision:2` and `coordinator_idx:1` and the `data.inputs` field has `endpoint` in it - enable tamper protection on the original agent policy - copy again and verify the same is true (`revision:2`, etc.) image ``` // no tamper GET .fleet-policies/_search {"query": { "bool": { "must": [ {"match": { "coordinator_idx": 1 }}, {"match": { "revision_idx": 2 }}, {"match": { "policy_id": "ae7c5e99-d79a-4364-9209-1c3da5789cd8" }} ] } }} "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 4.4079456, "hits": [ { "_index": ".fleet-policies-7", "_id": "W7iD35ABUS1gQZsO-P6F", "_score": 4.4079456, "_source": { "coordinator_idx": 1, "data": { "agent": { "download": { "sourceURI": "https://artifacts.elastic.co/downloads/" }, }, "fleet": { "hosts": [ "https://192.168.178.216:8220" ] }, "id": "ae7c5e99-d79a-4364-9209-1c3da5789cd8", "inputs": [ { "manifest_version": "1.0.0", "schema_version": "v1" }, "data_stream": { "namespace": "default" }, "id": "16c83e6a-b764-459d-847d-024697603269", "integration_config": { "endpointConfig": { "preset": "EDRComplete" }, "type": "endpoint" }, "meta": { "package": { "name": "endpoint", "version": "8.15.0" } }, "name": "endpoint (copy)", // tamper GET .fleet-policies/_search {"query": { "bool": { "must": [ {"match": { "coordinator_idx": 1 }}, {"match": { "revision_idx": 2 }}, {"match": { "policy_id": "d3dae391-a68e-4d0d-b8cd-d09f431c8a52" }} ] } }} "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 4.4079456, "hits": [ { "_index": ".fleet-policies-7", "_id": "8LmF35ABUS1gQZsOEwrC", "_score": 4.4079456, "_source": { "coordinator_idx": 1, "data": {atures": {}, "protection": { "enabled": true, } }, "fleet": { "hosts": [ "https://192.168.178.216:8220" ] }, "id": "d3dae391-a68e-4d0d-b8cd-d09f431c8a52", "inputs": [ { "id": "cbf33dcf-289a-4124-b6a9-50e988247307", "integration_config": { "endpointConfig": { "preset": "EDRComplete" }, "type": "endpoint" }, "meta": { "package": { "name": "endpoint", "version": "8.15.0" } }, "name": "endpoint (copy 2)", "package_policy_id": "cbf33dcf-289a-4124-b6a9-50e988247307", ``` ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- x-pack/plugins/fleet/server/services/agent_policy.ts | 12 +++++++++++- .../apis/agent_policy/agent_policy.ts | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 94983e4929883a..36a62af378c318 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -744,13 +744,23 @@ class AgentPolicyService { ); } + const policyNeedsBump = baseAgentPolicy.package_policies || baseAgentPolicy.is_protected; + + // bump revision if agent policy is updated after creation + if (policyNeedsBump) { + await this.bumpRevision(soClient, esClient, newAgentPolicy.id, { + user: options?.user, + }); + } else { + await this.deployPolicy(soClient, newAgentPolicy.id); + } + // Get updated agent policy with package policies and adjusted tamper protection const updatedAgentPolicy = await this.get(soClient, newAgentPolicy.id, true); if (!updatedAgentPolicy) { throw new AgentPolicyNotFoundError('Copied agent policy not found'); } - await this.deployPolicy(soClient, newAgentPolicy.id); logger.debug(`Completed copy of agent policy ${id}`); return updatedAgentPolicy; } diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index 634d1921d1612e..261e210a979cd7 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -526,7 +526,7 @@ export default function (providerContext: FtrProviderContext) { is_managed: false, namespace: 'default', monitoring_enabled: ['logs', 'metrics'], - revision: 1, + revision: 2, schema_version: FLEET_AGENT_POLICIES_SCHEMA_VERSION, updated_by: 'elastic', package_policies: [], @@ -650,6 +650,7 @@ export default function (providerContext: FtrProviderContext) { .expect(200); expect(newPolicy.is_protected).to.eql(true); + expect(newPolicy.revision).to.eql(2); }); it('should increment package policy copy names', async () => { From a9db60091e3850328ccb0d31a60eea8f5974e856 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:17:59 -0400 Subject: [PATCH 8/9] [Security Solution] Multi-line string fields diff algorithm test plan (#188323) ## Summary Related ticket: https://github.com/elastic/kibana/issues/180159 Adds test plan for diff algorithm for arrays of scalar values implemented in https://github.com/elastic/kibana/pull/188022 ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Georgii Gorbachev Co-authored-by: Juan Pablo Djeredjian --- .../upgrade_review_algorithms.md | 92 ++++++++++++++----- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md index fad01b9698b9be..e73b976d0b44e1 100644 --- a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md +++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md @@ -24,6 +24,8 @@ Status: `in progress`. - [**Scenario: `ABB` - Rule field is any type**](#scenario-abb---rule-field-is-any-type) - [Rule field has an update and a custom value that are NOT the same - `ABC`](#rule-field-has-an-update-and-a-custom-value-that-are-not-the-same---abc) - [**Scenario: `ABC` - Rule field is a number or single line string**](#scenario-abc---rule-field-is-a-number-or-single-line-string) + - [**Scenario: `ABC` - Rule field is a mergable multi line string**](#scenario-abc---rule-field-is-a-mergable-multi-line-string) + - [**Scenario: `ABC` - Rule field is a non-mergable multi line string**](#scenario-abc---rule-field-is-a-non-mergable-multi-line-string) - [**Scenario: `ABC` - Rule field is an array of scalar values**](#scenario-abc---rule-field-is-an-array-of-scalar-values) - [Rule field has an update and a custom value that are the same and the rule base version doesn't exist - `-AA`](#rule-field-has-an-update-and-a-custom-value-that-are-the-same-and-the-rule-base-version-doesnt-exist----aa) - [**Scenario: `-AA` - Rule field is any type**](#scenario--aa---rule-field-is-any-type) @@ -61,7 +63,7 @@ Status: `in progress`. #### **Scenario: `AAA` - Rule field is any type** -**Automation**: 3 integration tests with mock rules + a set of unit tests for each algorithm +**Automation**: 4 integration tests with mock rules + a set of unit tests for each algorithm ```Gherkin Given field is not customized by the user (current version == base version) @@ -71,10 +73,11 @@ And field should not be returned from the `upgrade/_review` API end And field should not be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "A" | "A" | "A" | -| number | risk_score | 1 | 1 | 1 | 1 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "A" | "A" | "A" | +| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | +| number | risk_score | 1 | 1 | 1 | 1 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | ``` ### Rule field doesn't have an update but has a custom value - `ABA` @@ -91,10 +94,11 @@ And field should be returned from the `upgrade/_review` API endpoin And field should be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "B" | "A" | "B" | -| number | risk_score | 1 | 2 | 1 | 2 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "three"] | ["one", "two", "four"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "B" | "A" | "B" | +| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | +| number | risk_score | 1 | 2 | 1 | 2 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "three"] | ["one", "two", "four"] | ``` ### Rule field has an update and doesn't have a custom value - `AAB` @@ -111,10 +115,11 @@ And field should be returned from the `upgrade/_review` API endpoin And field should be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "A" | "B" | "B" | -| number | risk_score | 1 | 1 | 2 | 2 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "A" | "B" | "B" | +| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | +| number | risk_score | 1 | 1 | 2 | 2 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | ``` ### Rule field has an update and a custom value that are the same - `ABB` @@ -132,10 +137,11 @@ And field should be returned from the `upgrade/_review` API endpoin And field should be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "B" | "B" | "B" | -| number | risk_score | 1 | 2 | 2 | 2 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | ["one", "two", "four"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "B" | "B" | "B" | +| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | +| number | risk_score | 1 | 2 | 2 | 2 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | ["one", "two", "four"] | ``` ### Rule field has an update and a custom value that are NOT the same - `ABC` @@ -158,6 +164,42 @@ Examples: | number | risk_score | 1 | 2 | 3 | 2 | ``` +#### **Scenario: `ABC` - Rule field is a mergable multi line string** + +**Automation**: 2 integration tests with mock rules + a set of unit tests for the algorithms + +```Gherkin +Given field is customized by the user (current version != base version) +And field is updated by Elastic in this upgrade (target version != base version) +And customized field is different than the Elastic update in this upgrade (current version != target version) +And the 3-way diff of fields are determined to be mergable +Then for field the diff algorithm should output a merged version as the merged one with a solvable conflict +And field should be returned from the `upgrade/_review` API endpoint +And field should be shown in the upgrade preview UI + +Examples: +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My description.\nThis is a second line, now longer." | "My GREAT description.\nThis is a second line, now longer." | +``` + +#### **Scenario: `ABC` - Rule field is a non-mergable multi line string** + +**Automation**: 2 integration tests with mock rules + a set of unit tests for the algorithms + +```Gherkin +Given field is customized by the user (current version != base version) +And field is updated by Elastic in this upgrade (target version != base version) +And customized field is different than the Elastic update in this upgrade (current version != target version) +And the 3-way diff of fields are determined to be unmergable +Then for field the diff algorithm should output the current version as the merged one with a non-solvable conflict +And field should be returned from the `upgrade/_review` API endpoint +And field should be shown in the upgrade preview UI + +Examples: +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a third line." | "My EXCELLENT description.\nThis is a fourth." | "My GREAT description.\nThis is a third line." | +``` + #### **Scenario: `ABC` - Rule field is an array of scalar values** **Automation**: 5 integration tests with mock rules + a set of unit tests for the algorithm @@ -198,10 +240,11 @@ And field should not be returned from the `upgrade/_review` API end And field should not be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | N/A | "A" | "A" | "A" | -| number | risk_score | N/A | 1 | 1 | 1 | -| array of scalars | tags | N/A | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | N/A | "A" | "A" | "A" | +| multi line string | description | N/A | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | +| number | risk_score | N/A | 1 | 1 | 1 | +| array of scalars | tags | N/A | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | ``` ### Rule field has an update and a custom value that are NOT the same and the rule base version doesn't exist - `-BC` @@ -219,9 +262,10 @@ And field should be returned from the `upgrade/_review` API endpoin And field should be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | N/A | "B" | "C" | "C" | -| number | risk_score | N/A | 2 | 3 | 3 | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | N/A | "B" | "C" | "C" | +| multi line string | description | N/A | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | +| number | risk_score | N/A | 2 | 3 | 3 | ``` #### **Scenario: `-BC` - Rule field is an array of scalar values** From 2ced9416d3b87ffbe247974724420e357d667035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:21:08 +0100 Subject: [PATCH 9/9] [Profiling] Fix TopN function total CPU value (#188848) closes https://github.com/elastic/kibana/issues/188511 Screenshot 2024-07-22 at 13 59 28 Screenshot 2024-07-22 at 14 55 50 15 --- .../cypress/e2e/profiling_views/functions.cy.ts | 14 +++++++------- .../public/components/topn_functions/utils.test.ts | 2 +- .../public/components/topn_functions/utils.ts | 13 ++++++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts index 3fc0c535639ee2..4c42069188b3a7 100644 --- a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts +++ b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts @@ -36,8 +36,8 @@ describe('Functions page', () => { const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]'; cy.get(firstRowSelector).eq(1).contains('1'); cy.get(firstRowSelector).eq(2).contains('vmlinux'); - cy.get(firstRowSelector).eq(3).contains('0.16%'); - cy.get(firstRowSelector).eq(4).contains('0.16%'); + cy.get(firstRowSelector).eq(3).contains('5.46%'); + cy.get(firstRowSelector).eq(4).contains('5.46%'); cy.get(firstRowSelector).eq(5).contains('3.97 lbs / 1.8 kg'); cy.get(firstRowSelector).eq(6).contains('$17.37'); cy.get(firstRowSelector).eq(7).contains('28'); @@ -56,8 +56,8 @@ describe('Functions page', () => { { parentKey: 'informationRows', key: 'executable', value: 'vmlinux' }, { parentKey: 'informationRows', key: 'function', value: 'N/A' }, { parentKey: 'informationRows', key: 'sourceFile', value: 'N/A' }, - { parentKey: 'impactEstimates', key: 'totalCPU', value: '0.16%' }, - { parentKey: 'impactEstimates', key: 'selfCPU', value: '0.16%' }, + { parentKey: 'impactEstimates', key: 'totalCPU', value: '5.46%' }, + { parentKey: 'impactEstimates', key: 'selfCPU', value: '5.46%' }, { parentKey: 'impactEstimates', key: 'samples', value: '28' }, { parentKey: 'impactEstimates', key: 'selfSamples', value: '28' }, { parentKey: 'impactEstimates', key: 'coreSeconds', value: '1.4 seconds' }, @@ -118,7 +118,7 @@ describe('Functions page', () => { columnIndex: 3, highRank: 1, lowRank: 389, - highValue: '0.16%', + highValue: '5.46%', lowValue: '0.00%', }, { @@ -126,8 +126,8 @@ describe('Functions page', () => { columnIndex: 4, highRank: 693, lowRank: 44, - highValue: '1.80%', - lowValue: '0.01%', + highValue: '60.43%', + lowValue: '0.19%', }, { columnKey: 'annualizedCo2', diff --git a/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.test.ts b/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.test.ts index c7598f55acb0ba..9142427790450a 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.test.ts +++ b/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.test.ts @@ -46,7 +46,7 @@ describe('Top N functions: Utils', () => { expect(getTotalCount()).toEqual(0); }); it('returns value from topNFunctions', () => { - expect(getTotalCount({ TotalCount: 10 } as TopNFunctions)).toEqual(10); + expect(getTotalCount({ selfCPU: 10 } as TopNFunctions)).toEqual(10); }); }); }); diff --git a/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.ts b/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.ts index 74f63f1dd56892..6cb68bd54f1534 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.ts +++ b/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/utils.ts @@ -32,7 +32,7 @@ export function scaleValue({ value, scaleFactor = 1 }: { value: number; scaleFac return value * scaleFactor; } -export const getTotalCount = (topNFunctions?: TopNFunctions) => topNFunctions?.TotalCount ?? 0; +export const getTotalCount = (topNFunctions?: TopNFunctions) => topNFunctions?.selfCPU ?? 0; export interface IFunctionRow { id: string; @@ -95,15 +95,15 @@ export function getFunctionsRows({ scaleFactor: baselineScaleFactor, }); - const totalCPUPerc = (topN.CountInclusive / topNFunctions.TotalCount) * 100; - const selfCPUPerc = (topN.CountExclusive / topNFunctions.TotalCount) * 100; + const totalCPUPerc = (topN.CountInclusive / topNFunctions.selfCPU) * 100; + const selfCPUPerc = (topN.CountExclusive / topNFunctions.selfCPU) * 100; const impactEstimates = totalSeconds > 0 ? calculateImpactEstimates({ countExclusive: topN.CountExclusive, countInclusive: topN.CountInclusive, - totalSamples: topNFunctions.TotalCount, + totalSamples: topNFunctions.selfCPU, totalSeconds, }) : undefined; @@ -124,10 +124,9 @@ export function getFunctionsRows({ selfCPU: comparisonRow.CountExclusive, totalCPU: comparisonRow.CountInclusive, selfCPUPerc: - selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100, + selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.selfCPU) * 100, totalCPUPerc: - totalCPUPerc - - (comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100, + totalCPUPerc - (comparisonRow.CountInclusive / comparisonTopNFunctions.selfCPU) * 100, selfAnnualCO2kgs: comparisonRow.selfAnnualCO2kgs, selfAnnualCostUSD: comparisonRow.selfAnnualCostUSD, totalAnnualCO2kgs: comparisonRow.totalAnnualCO2kgs,