diff --git a/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts b/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts index 9cdbe6b3a97cc..cce1269585cbb 100644 --- a/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts @@ -174,10 +174,10 @@ describe('Add Integration - Real API', () => { setupIntegrations(); cy.getBySel(getIntegrationCategories('aws')).click(); cy.getBySel(INTEGRATIONS_SEARCHBAR.BADGE).contains('AWS').should('exist'); - cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length', 29); + cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length.greaterThan', 29); cy.getBySel(INTEGRATIONS_SEARCHBAR.INPUT).clear().type('Cloud'); - cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length', 3); + cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length.greaterThan', 3); cy.getBySel(INTEGRATIONS_SEARCHBAR.REMOVE_BADGE_BUTTON).click(); cy.getBySel(INTEGRATIONS_SEARCHBAR.BADGE).should('not.exist'); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx new file mode 100644 index 0000000000000..4588ebc4912b3 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx @@ -0,0 +1,66 @@ +/* + * 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, { useEffect, useState } from 'react'; +import { EuiComboBox } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const DatasetComboBox: React.FC<{ + value: any; + onChange: (newValue: any) => void; + datasets: string[]; +}> = ({ value, onChange, datasets }) => { + const datasetOptions = datasets.map((dataset: string) => ({ label: dataset })) ?? []; + const defaultOption = 'generic'; + const [selectedOptions, setSelectedOptions] = useState>([ + { + label: value ?? defaultOption, + }, + ]); + + useEffect(() => { + if (!value) onChange(defaultOption); + }, [value, defaultOption, onChange]); + + const onDatasetChange = (newSelectedOptions: Array<{ label: string }>) => { + setSelectedOptions(newSelectedOptions); + onChange(newSelectedOptions[0]?.label); + }; + + const onCreateOption = (searchValue: string = '') => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + if (!normalizedSearchValue) { + return; + } + const newOption = { + label: searchValue, + }; + setSelectedOptions([newOption]); + onChange(searchValue); + }; + + return ( + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.test.ts new file mode 100644 index 0000000000000..521c5e08dc2c1 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.test.ts @@ -0,0 +1,30 @@ +/* + * 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 { orderDatasets } from './order_datasets'; + +describe('orderDatasets', () => { + it('should move datasets up that match name', () => { + const datasets = orderDatasets( + ['system.memory', 'elastic_agent', 'elastic_agent.filebeat', 'system.cpu'], + 'elastic_agent' + ); + + expect(datasets).toEqual([ + 'elastic_agent', + 'elastic_agent.filebeat', + 'system.cpu', + 'system.memory', + ]); + }); + + it('should order alphabetically if name does not match', () => { + const datasets = orderDatasets(['system.memory', 'elastic_agent'], 'nginx'); + + expect(datasets).toEqual(['elastic_agent', 'system.memory']); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.ts new file mode 100644 index 0000000000000..8262af7064142 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.ts @@ -0,0 +1,16 @@ +/* + * 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 { partition } from 'lodash'; + +export function orderDatasets(datasetList: string[], name: string): string[] { + const [relevantDatasets, otherDatasets] = partition(datasetList.sort(), (record) => + record.startsWith(name) + ); + const datasets = relevantDatasets.concat(otherDatasets); + return datasets; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx index 184deb98abd63..893d0494a8b8a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx @@ -8,6 +8,7 @@ import React, { useState, Fragment, memo, useMemo, useEffect, useRef, useCallback } from 'react'; import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; +import { uniq } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFlexGrid, @@ -22,6 +23,8 @@ import { } from '@elastic/eui'; import { useRouteMatch } from 'react-router-dom'; +import { useGetDataStreams } from '../../../../../../../../hooks'; + import { mapPackageReleaseToIntegrationCardRelease } from '../../../../../../../../services/package_prerelease'; import { getRegistryDataStreamAssetBaseName } from '../../../../../../../../../common/services'; @@ -41,6 +44,7 @@ import { PackagePolicyEditorDatastreamMappings } from '../../datastream_mappings import { PackagePolicyInputVarField } from './package_policy_input_var_field'; import { useDataStreamId } from './hooks'; +import { orderDatasets } from './order_datasets'; const ScrollAnchor = styled.div` display: none; @@ -175,6 +179,11 @@ export const PackagePolicyInputStreamConfig = memo( }); }; + const { data: dataStreamsData } = useGetDataStreams(); + const datasetList = + uniq(dataStreamsData?.data_streams.map((dataStream) => dataStream.dataset)) ?? []; + const datasets = orderDatasets(datasetList, packageInfo.name); + return ( <> @@ -252,6 +261,8 @@ export const PackagePolicyInputStreamConfig = memo( }} errors={inputStreamValidationResults?.vars![varName]} forceShowErrors={forceShowErrors} + packageType={packageInfo.type} + datasets={datasets} /> ); @@ -311,6 +322,8 @@ export const PackagePolicyInputStreamConfig = memo( }} errors={inputStreamValidationResults?.vars![varName]} forceShowErrors={forceShowErrors} + packageType={packageInfo.type} + datasets={datasets} /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx index 812d5fc2233de..e314fb2c79ca6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx @@ -25,6 +25,7 @@ import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import type { RegistryVarsEntry } from '../../../../../../types'; import { MultiTextInput } from './multi_text_input'; +import { DatasetComboBox } from './dataset_combo'; const FixedHeightDiv = styled.div` height: 300px; @@ -37,127 +38,144 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{ errors?: string[] | null; forceShowErrors?: boolean; frozen?: boolean; -}> = memo(({ varDef, value, onChange, errors: varErrors, forceShowErrors, frozen }) => { - const [isDirty, setIsDirty] = useState(false); - const { multi, required, type, title, name, description } = varDef; - const isInvalid = (isDirty || forceShowErrors) && !!varErrors; - const errors = isInvalid ? varErrors : null; - const fieldLabel = title || name; + packageType?: string; + datasets?: string[]; +}> = memo( + ({ + varDef, + value, + onChange, + errors: varErrors, + forceShowErrors, + frozen, + packageType, + datasets = [], + }) => { + const [isDirty, setIsDirty] = useState(false); + const { multi, required, type, title, name, description } = varDef; + const isInvalid = (isDirty || forceShowErrors) && !!varErrors; + const errors = isInvalid ? varErrors : null; + const fieldLabel = title || name; - const field = useMemo(() => { - if (multi) { - return ( - setIsDirty(true)} - isDisabled={frozen} - /> - ); - } - switch (type) { - case 'textarea': + const field = useMemo(() => { + if (multi) { return ( - onChange(e.target.value)} + setIsDirty(true)} - disabled={frozen} - resize="vertical" + isDisabled={frozen} /> ); - case 'yaml': - return frozen ? ( - -
{value}
-
- ) : ( - - - - ); - case 'bool': - return ( - onChange(e.target.checked)} - onBlur={() => setIsDirty(true)} - disabled={frozen} - /> - ); - case 'password': - return ( - onChange(e.target.value)} - onBlur={() => setIsDirty(true)} - disabled={frozen} - /> - ); - default: - return ( - onChange(e.target.value)} - onBlur={() => setIsDirty(true)} - disabled={frozen} - /> - ); - } - }, [isInvalid, multi, onChange, type, value, fieldLabel, frozen]); - - // Boolean cannot be optional by default set to false - const isOptional = useMemo(() => type !== 'bool' && !required, [required, type]); + } - return ( - - ; + } + switch (type) { + case 'textarea': + return ( + onChange(e.target.value)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + resize="vertical" /> - - ) : null + ); + case 'yaml': + return frozen ? ( + +
{value}
+
+ ) : ( + + + + ); + case 'bool': + return ( + onChange(e.target.checked)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + /> + ); + case 'password': + return ( + onChange(e.target.value)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + /> + ); + default: + return ( + onChange(e.target.value)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + /> + ); } - helpText={description && } - > - {field} -
- ); -}); + }, [isInvalid, multi, onChange, type, value, fieldLabel, frozen, datasets, name, packageType]); + + // Boolean cannot be optional by default set to false + const isOptional = useMemo(() => type !== 'bool' && !required, [required, type]); + + return ( + + + + ) : null + } + helpText={description && } + > + {field} + + ); + } +);