diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx
index 156981207e225..98759cd5ceb1d 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx
@@ -1348,5 +1348,175 @@ describe('PackagePolicyInputPanel', () => {
expect(lastCall?.streams?.[0]?.vars).not.toHaveProperty(USE_APM_VAR_NAME);
});
});
+
+ it('hoists the non-GA release badge up to the input header for input-type packages', async () => {
+ const betaOtelStreams: RegistryStreamWithDataStream[] = [
+ {
+ ...otelPackageInputStreams[0],
+ data_stream: {
+ ...otelPackageInputStreams[0].data_stream,
+ release: 'beta',
+ },
+ },
+ ];
+ const otelPolicyInput = {
+ id: 'input-1',
+ type: OTEL_COLLECTOR_INPUT_TYPE,
+ policy_template: 'otel_template',
+ enabled: true,
+ streams: [
+ {
+ id: 'otel-stream-1',
+ data_stream: { type: 'logs', dataset: 'my_otel.data' },
+ enabled: true,
+ vars: {},
+ },
+ ],
+ } as NewPackagePolicyInput;
+
+ renderResult = testRenderer.render(
+
+ );
+
+ await waitFor(() => {
+ expect(renderResult.getByText('Beta')).toBeInTheDocument();
+ // Stream-level toggle should not render for input-type packages,
+ // which is why the badge is hoisted up to the input header instead.
+ expect(renderResult.queryByTestId('streamOptions.switch')).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('Non-GA release badge hoisting', () => {
+ beforeEach(() => {
+ jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({
+ enableVarGroups: true,
+ } as any);
+ useAgentlessMock.mockReturnValue({
+ isAgentlessEnabled: false,
+ isAgentlessDefault: false,
+ isAgentlessAgentPolicy: jest.fn(),
+ getAgentlessStatusForPackage: jest
+ .fn()
+ .mockReturnValue({ isAgentless: false, isDefaultDeploymentMode: false }),
+ isServerless: false,
+ isCloud: false,
+ });
+ });
+
+ const singleBetaStream: RegistryStreamWithDataStream[] = [
+ {
+ input: 'logfile',
+ title: 'Stream 1',
+ template_path: 'stream.yml.hbs',
+ vars: [
+ {
+ name: 'paths',
+ type: 'text',
+ title: 'Paths',
+ multi: false,
+ required: false,
+ show_user: true,
+ },
+ ],
+ description: 'Test stream',
+ data_stream: {
+ ...mockPackageInputStreams[0].data_stream,
+ release: 'beta',
+ },
+ },
+ ];
+
+ const singleStreamPolicyInput = {
+ ...packagePolicyInput,
+ streams: [packagePolicyInput.streams[0]],
+ } as NewPackagePolicyInput;
+
+ it('hoists the release badge to the input header when there is a single stream', async () => {
+ renderResult = testRenderer.render(
+
+ );
+ await waitFor(() => {
+ expect(renderResult.getByText('Beta')).toBeInTheDocument();
+ // Single-stream rows don't render their own toggle, so the badge
+ // would otherwise float alone - here we expect it at the input header.
+ expect(renderResult.queryByTestId('streamOptions.switch')).not.toBeInTheDocument();
+ });
+ });
+
+ it('renders the release badge at the stream row for multi-stream integrations', async () => {
+ const multiStreamsWithBeta: RegistryStreamWithDataStream[] = [
+ {
+ input: 'logfile',
+ title: 'Stream 1',
+ template_path: 'stream.yml.hbs',
+ vars: [
+ {
+ name: 'paths',
+ type: 'text',
+ title: 'Paths',
+ multi: false,
+ required: false,
+ show_user: true,
+ },
+ ],
+ description: 'Test stream 1',
+ data_stream: {
+ ...mockPackageInputStreams[0].data_stream,
+ release: 'beta',
+ },
+ },
+ {
+ input: 'logfile',
+ title: 'Stream 2',
+ template_path: 'stream.yml.hbs',
+ vars: [
+ {
+ name: 'paths',
+ type: 'text',
+ title: 'Paths',
+ multi: false,
+ required: false,
+ show_user: true,
+ },
+ ],
+ description: 'Test stream 2',
+ data_stream: {
+ ...mockPackageInputStreams[1].data_stream,
+ },
+ },
+ ];
+
+ renderResult = testRenderer.render(
+
+ );
+ await waitFor(() => {
+ expect(renderResult.getByText('Beta')).toBeInTheDocument();
+ // Multi-stream integrations show per-stream toggles, so the badge
+ // stays at the stream row - no need to hoist.
+ expect(renderResult.getAllByTestId('streamOptions.switch').length).toBeGreaterThan(0);
+ });
+ });
});
});
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx
index 59c2ec345cd4f..968b9cf505f12 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState, Fragment, memo, useMemo, useCallback } from 'react';
+import React, { useState, memo, useMemo, useCallback } from 'react';
import ReactMarkdown from 'react-markdown';
import styled from 'styled-components';
import { FormattedMessage } from '@kbn/i18n-react';
@@ -40,7 +40,9 @@ import {
DATA_STREAM_USE_APM_VAR,
shouldIncludeUseAPMVar,
hasDynamicSignalTypes,
+ mapPackageReleaseToIntegrationCardRelease,
} from '../../../../../../../../../common/services';
+import { InlineReleaseBadge } from '../../../../../../components';
import {
DATA_STREAM_TYPE_VAR_NAME,
USE_APM_VAR_NAME,
@@ -234,11 +236,6 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
const errorCount = inputValidationResults && countValidationErrors(inputValidationResults);
const hasErrors = forceShowErrors && errorCount;
- const hasInputStreams = useMemo(
- () => packageInputStreams.length > 0,
- [packageInputStreams.length]
- );
-
const inputStreams = useMemo(
() =>
packageInputStreams
@@ -254,6 +251,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
.filter((stream) => Boolean(stream.packagePolicyInputStream)),
[packageInputStreamShouldBeVisible, packageInputStreams, packagePolicyInput.streams]
);
+ const hasInputStreams = useMemo(() => inputStreams.length > 0, [inputStreams.length]);
const showTopLevelDescription = inputStreams.length === 1;
const dynamicSignalTypes = useMemo(
@@ -345,6 +343,27 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
defaultMessage: 'This input is deprecated.',
});
+ // Whether the individual stream rows will render their own toggle switch.
+ // When they won't (input-type package or single visible stream), we hoist
+ // the non-GA release badge up to the input header so it doesn't float alone.
+ const hasStreamToggle = packageInfo.type !== 'input' && inputStreams.length > 1;
+
+ const inputReleaseBadge = useMemo(() => {
+ if (hasStreamToggle) return null;
+ const preReleaseStream = inputStreams.find(
+ ({ packageInputStream }) =>
+ packageInputStream.data_stream.release && packageInputStream.data_stream.release !== 'ga'
+ );
+ if (!preReleaseStream?.packageInputStream.data_stream.release) return null;
+ return (
+
+ );
+ }, [hasStreamToggle, inputStreams]);
+
// Check if any vars or streams in this input are deprecated
const hasDeprecatedFeatures = useMemo(() => {
const inputVarsDeprecated = (packageInput.vars || []).some((v) => !!v.deprecated);
@@ -382,6 +401,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
+ {inputReleaseBadge && {inputReleaseBadge}}
{migrationTooltip}
@@ -407,6 +427,9 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
+ {inputReleaseBadge && (
+ {inputReleaseBadge}
+ )}
{migrationTooltip}
{isDeprecatedInput && (
@@ -505,11 +528,16 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
- {/* Header rule break */}
- {isShowingStreams ? : null}
+ {/* Spacing if we are showing rest of content */}
+ {isShowingStreams &&
+ hasInputStreams &&
+ ((packageInput.vars && packageInput.vars.length) || !shouldConsolidateAdvancedSections) ? (
+
+ ) : null}
+
{/* Input level policy */}
{isShowingStreams && packageInput.vars && packageInput.vars.length ? (
-
+ <>
) : (
-
+
)}
-
+ >
) : null}
{/* Per-stream policy */}
- {isShowingStreams && !shouldConsolidateAdvancedSections ? (
+ {isShowingStreams && hasInputStreams && !shouldConsolidateAdvancedSections ? (
{inputStreams.map(({ packageInputStream, packagePolicyInputStream }, index) => {
return (
@@ -546,7 +574,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
data-test-subj="PackagePolicy.InputStreamConfig"
packageInfo={packageInfo}
packageInputStream={packageInputStream}
- totalStreams={inputStreams.length}
+ hasStreamToggle={hasStreamToggle}
packagePolicyInputStream={packagePolicyInputStream!}
inputPolicyTemplate={packagePolicyInput.policy_template}
showDescriptionColumn={!isSingleInputAndStreams}
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.test.tsx
index 2b02760ec79bc..74e44299a349c 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.test.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.test.tsx
@@ -223,7 +223,7 @@ describe('PackagePolicyInputStreamConfig', () => {
updatePackagePolicyInputStream={mockUpdatePackagePolicyInputStream}
inputStreamValidationResults={{ vars: {} }}
forceShowErrors={false}
- totalStreams={2}
+ hasStreamToggle={true}
inputPolicyTemplate={inputPolicyTemplate}
/>
);
@@ -297,7 +297,7 @@ describe('PackagePolicyInputStreamConfig', () => {
updatePackagePolicyInputStream={mockUpdatePackagePolicyInputStream}
inputStreamValidationResults={{ vars: {} }}
forceShowErrors={false}
- totalStreams={2}
+ hasStreamToggle={true}
/>
);
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx
index 5f8a69a7407ed..78323d8e65f5c 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx
@@ -84,7 +84,7 @@ interface Props {
forceShowErrors?: boolean;
isEditPage?: boolean;
isUpgrade?: boolean;
- totalStreams?: number;
+ hasStreamToggle?: boolean;
showDescriptionColumn?: boolean;
varGroupSelections?: Record;
/** Parent input's `policy_template`; required for correct composable multi-template matching. */
@@ -101,7 +101,7 @@ export const PackagePolicyInputStreamConfig = memo(
forceShowErrors,
isEditPage,
isUpgrade,
- totalStreams,
+ hasStreamToggle = true,
showDescriptionColumn = true,
varGroupSelections = {},
inputPolicyTemplate,
@@ -126,7 +126,6 @@ export const PackagePolicyInputStreamConfig = memo(
!!packagePolicyInputStream.id &&
packagePolicyInputStream.id === defaultDataStreamId;
const isPackagePolicyEdit = !!packagePolicyId;
- const shouldShowStreamsToggles = totalStreams ? totalStreams > 1 : true;
const customDatasetVar = packagePolicyInputStream.vars?.[DATASET_VAR_NAME];
const customDatasetVarValue = customDatasetVar?.value?.dataset || customDatasetVar?.value;
@@ -303,113 +302,115 @@ export const PackagePolicyInputStreamConfig = memo(
data-test-subj={`streamOptions.inputStreams.${packageInputStream.data_stream.dataset}`}
>
-
-
-
-
-
- {packageInfo.type !== 'input' && shouldShowStreamsToggles && (
-
-
+ {showDescriptionColumn || hasStreamToggle || hasRequiredVarGroupErrors ? (
+
+
+
+
+ {hasStreamToggle && (
+ <>
+
- {
- const enabled = e.target.checked;
- updatePackagePolicyInputStream({
- enabled,
- });
- }}
- />
+
+
+ {
+ const enabled = e.target.checked;
+ updatePackagePolicyInputStream({
+ enabled,
+ });
+ }}
+ />
+
+ {showStreamDeprecationIcon && (
+
+
+
+
+
+ )}
+ {isUpgrade &&
+ packagePolicyInputStream.migrate_from &&
+ !showStreamDeprecationIcon && (
+
+ )}
+
- {showStreamDeprecationIcon && (
+ {packageInputStream.data_stream.release &&
+ packageInputStream.data_stream.release !== 'ga' ? (
-
-
-
-
- )}
- {isUpgrade &&
- packagePolicyInputStream.migrate_from &&
- !showStreamDeprecationIcon && (
-
- )}
+
+ ) : null}
-
+ {packageInputStream.description ? (
+ <>
+
+
+ {packageInputStream.description}
+
+ >
+ ) : null}
+ >
)}
- {packageInputStream.data_stream.release &&
- packageInputStream.data_stream.release !== 'ga' ? (
-
-
-
- ) : null}
-
- {packageInfo.type !== 'input' &&
- packageInputStream.description &&
- shouldShowStreamsToggles ? (
- <>
-
-
- {packageInputStream.description}
-
- >
- ) : null}
- {hasRequiredVarGroupErrors && (
- <>
-
-
-
+ {hasRequiredVarGroupErrors && (
+ <>
+
+
+
+
+ }
+ >
+
+ {Object.entries(inputStreamValidationResults?.required_vars || {}).map(
+ ([groupName, vars]) => {
+ return (
+ <>
+ {groupName}
+
+ {vars.map(({ name }) => (
+ - {name}
+ ))}
+
+ >
+ );
+ }
+ )}
- }
- >
-
- {Object.entries(inputStreamValidationResults?.required_vars || {}).map(
- ([groupName, vars]) => {
- return (
- <>
- {groupName}
-
- {vars.map(({ name }) => (
- - {name}
- ))}
-
- >
- );
- }
- )}
-
-
- >
- )}
-
-
-
+
+ >
+ )}
+
+
+
+ ) : null}
{/* Stream-level Var Group Selectors */}
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/add_integration_flyout_configure_header.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/add_integration_flyout_configure_header.tsx
index cfe10a3102806..f23715748136a 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/add_integration_flyout_configure_header.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/add_integration_flyout_configure_header.tsx
@@ -6,7 +6,8 @@
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { EuiLink, EuiSpacer, EuiText, useEuiTheme } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { PackageIcon } from '../../../../../components';
@@ -24,61 +25,52 @@ export const AddIntegrationFlyoutConfigureHeader: React.FC = ({
pkgLabel,
integration,
}) => {
+ const theme = useEuiTheme();
return (
<>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {pkgLabel}
+
+
+
+
-
-
- {pkgLabel}
-
-
-
-
-
-
- ),
- }}
- />
-
-
-
-
-
-
+
+ ),
+ }}
+ />
+
>
);
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
index 28464d9490322..e96058745b7d4 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
@@ -102,6 +102,7 @@ import { useAgentless } from './hooks/setup_technology';
export const StepsWithLessPadding = styled(EuiSteps)`
.euiStep__content {
+ padding-top: ${(props) => props.theme.eui.euiSizeXS};
padding-bottom: ${(props) => props.theme.eui.euiSizeM};
}
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/add_integration_flyout.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/add_integration_flyout.tsx
index 8bd5864a29aa6..5e1522ca266a3 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/add_integration_flyout.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/add_integration_flyout.tsx
@@ -147,7 +147,7 @@ export const AddIntegrationFlyout: React.FunctionComponent<{
-
+