diff --git a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_auth_type_selector.tsx b/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_auth_type_selector.tsx index e652d1d0cf076..161c6bad3dc77 100644 --- a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_auth_type_selector.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_auth_type_selector.tsx @@ -48,16 +48,21 @@ const OPTIONS = [ interface AwsAuthTypeSelectorProps { selectedAuthType: AwsAuthType; + showIdentityFederation?: boolean; onChange: (authType: AwsAuthType) => void; } export const AwsAuthTypeSelector: React.FC = ({ selectedAuthType, + showIdentityFederation = true, onChange, }) => { + const options = showIdentityFederation + ? OPTIONS + : OPTIONS.filter((o) => o.value !== 'identity_federation'); return ( onChange(e.target.value as AwsAuthType)} aria-label={i18n.translate('xpack.fleet.awsConnectSetup.authType.selectorAriaLabel', { diff --git a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_identity_federation_setup.tsx b/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_identity_federation_setup.tsx index 30af47e241872..95c9ddafa3cf8 100644 --- a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_identity_federation_setup.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/aws_identity_federation_setup.tsx @@ -15,6 +15,7 @@ import { EuiFlexItem, EuiFormRow, EuiLink, + EuiSkeletonText, EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -57,7 +58,7 @@ export const AwsIdentityFederationSetup: React.FC { - const { data: cloudConnectors = [] } = useGetCloudConnectors({ + const { data: cloudConnectors = [], isLoading: isLoadingConnectors } = useGetCloudConnectors({ cloudProvider: 'aws', accountType, packageName, @@ -123,12 +124,16 @@ export const AwsIdentityFederationSetup: React.FC { + if (isLoadingConnectors) { + return ; + } + + const handleTabClick = (tab: { id: string }) => { setSelectedTabId(tab.id); if (tab.id === TABS.NEW_CONNECTION) { setSelectedConnectorId(undefined); } - }, []); + }; const tabs: CloudConnectorTab[] = [ { diff --git a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/index.tsx b/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/index.tsx index 87e4be52dac53..ee74d6371fd4b 100644 --- a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/index.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_connect_setup/index.tsx @@ -30,6 +30,7 @@ export interface AwsConnectSetupProps { initialConnectorId?: string; initialStaticKeys?: Partial; initialTemporaryKeys?: Partial; + showIdentityFederation?: boolean; onNext?: () => void; onConnectorIdChange?: (connectorId: string | undefined) => void; onStaticKeysChange?: (keys: AwsStaticKeyCredentials | undefined) => void; @@ -53,6 +54,7 @@ export const AwsConnectSetup: React.FC = ({ initialConnectorId, initialStaticKeys, initialTemporaryKeys, + showIdentityFederation = true, onNext, onConnectorIdChange, onStaticKeysChange, @@ -63,7 +65,9 @@ export const AwsConnectSetup: React.FC = ({ ? 'temporary_keys' : initialStaticKeys ? 'static_keys' - : 'identity_federation' + : showIdentityFederation + ? 'identity_federation' + : 'static_keys' ); const [isFormReady, setIsFormReady] = useState(false); @@ -91,7 +95,11 @@ export const AwsConnectSetup: React.FC = ({

- + {authType === 'identity_federation' && ( { }); it('has only valid delivery method values', () => { - entry.deliveryMethods.forEach((method) => { + entry.deliveryMethods.forEach(({ method }) => { expect(VALID_DELIVERY_METHODS).toContain(method); }); }); + it('has exactly one preferred delivery method', () => { + const preferred = entry.deliveryMethods.filter((dm) => dm.preferred === true); + expect(preferred).toHaveLength(1); + }); + it('has a non-empty packageName', () => { expect(entry.packageName).toBeTruthy(); }); diff --git a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_cloud_connector/aws_services_matrix.ts b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/aws_service_matrix.ts similarity index 81% rename from x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_cloud_connector/aws_services_matrix.ts rename to x-pack/platform/plugins/shared/ingest_hub/public/onboarding/aws_service_matrix.ts index 0ad12ac064419..07e0a14c3c779 100644 --- a/x-pack/platform/plugins/shared/fleet/public/components/cloud_connector/aws_cloud_connector/aws_services_matrix.ts +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/aws_service_matrix.ts @@ -31,13 +31,20 @@ export type ServiceCategory = | 'Serverless & Compute' | 'Storage'; +export interface DeliveryMethodEntry { + method: DeliveryMethod; + /** When true, this is the mechanism used by default in the onboarding deployment step. + * Exactly one entry per service should be preferred. */ + preferred?: boolean; +} + export interface AwsServiceMatrixEntry { /** Data stream identifier, matching packages//data_stream/ */ id: string; name: string; category: ServiceCategory; signalType: SignalType; - deliveryMethods: DeliveryMethod[]; + deliveryMethods: DeliveryMethodEntry[]; /** Authentication types available per delivery method. Populated once IF rollout status is confirmed. */ authTypes?: AuthType[]; /** Whether OIDC-based IAM role assumption is supported. Populated once Security team confirms per-service status. */ @@ -61,7 +68,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS API Gateway', category: 'Serverless & Compute', signalType: 'logs', - deliveryMethods: ['cloud_forwarder'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -73,7 +80,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS API Gateway', category: 'Serverless & Compute', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -85,7 +92,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Lambda', category: 'Serverless & Compute', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -97,7 +104,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Lambda', category: 'Serverless & Compute', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws-cloudwatch'], requiredConfig: ['log_group_arn', 'region_name'], packageName: 'aws', @@ -111,7 +118,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS EC2', category: 'Infrastructure', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -123,7 +130,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS EC2', category: 'Infrastructure', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -135,7 +142,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS ECS', category: 'Infrastructure', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -147,7 +154,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS EMR', category: 'Infrastructure', signalType: 'logs', - deliveryMethods: ['agentless', 'cloud_forwarder'], + deliveryMethods: [{ method: 'agentless', preferred: true }, { method: 'cloud_forwarder' }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -159,7 +166,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS EMR', category: 'Infrastructure', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -173,7 +180,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Health', category: 'Monitoring', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -185,7 +192,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS CloudWatch Logs', category: 'Monitoring', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws-cloudwatch'], requiredConfig: ['log_group_arn', 'region_name'], packageName: 'aws', @@ -197,7 +204,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS CloudWatch Metrics', category: 'Monitoring', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions', 'metrics'], packageName: 'aws', @@ -211,7 +218,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Billing', category: 'Cost & Billing', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: [], packageName: 'aws', @@ -223,7 +230,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Usage', category: 'Cost & Billing', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -237,7 +244,11 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS CloudTrail', category: 'Security', signalType: 'logs', - deliveryMethods: ['agentless', 'cloud_forwarder', 'firehose'], + deliveryMethods: [ + { method: 'agentless', preferred: true }, + { method: 'cloud_forwarder' }, + { method: 'firehose' }, + ], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -249,7 +260,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Config', category: 'Security', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['cel'], requiredConfig: ['aws_region'], packageName: 'aws', @@ -261,19 +272,20 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS GuardDuty', category: 'Security', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws-s3', 'httpjson'], requiredConfig: ['aws_region', 'detector_id', 'bucket_arn', 'region'], packageName: 'aws', defaultEnabled: true, showInUI: true, + identityFederationSupported: true, }, { id: 'inspector', name: 'AWS Inspector', category: 'Security', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['httpjson'], requiredConfig: ['aws_region'], packageName: 'aws', @@ -285,7 +297,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Network Firewall', category: 'Security', signalType: 'logs', - deliveryMethods: ['cloud_forwarder', 'firehose'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }, { method: 'firehose' }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -297,7 +309,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Network Firewall', category: 'Security', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -309,7 +321,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Security Hub', category: 'Security', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['httpjson'], requiredConfig: ['aws_region'], packageName: 'aws', @@ -321,7 +333,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Security Hub (Full Posture / CSPM)', category: 'Security', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['httpjson'], requiredConfig: ['aws_region'], packageName: 'aws', @@ -333,7 +345,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Security Hub (Insights)', category: 'Security', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['httpjson'], requiredConfig: ['aws_region'], packageName: 'aws', @@ -345,7 +357,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS WAF', category: 'Security', signalType: 'logs', - deliveryMethods: ['cloud_forwarder', 'firehose'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }, { method: 'firehose' }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -359,7 +371,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS CloudFront', category: 'Networking / CDN', signalType: 'logs', - deliveryMethods: ['cloud_forwarder', 'firehose'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }, { method: 'firehose' }], inputs: ['aws-s3'], requiredConfig: ['bucket_arn', 'region'], packageName: 'aws', @@ -371,7 +383,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS ELB', category: 'Networking', signalType: 'logs', - deliveryMethods: ['cloud_forwarder', 'firehose'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }, { method: 'firehose' }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -383,7 +395,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS ELB', category: 'Networking', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -395,7 +407,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS NAT Gateway', category: 'Networking', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -407,7 +419,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Route 53 Public DNS', category: 'Networking', signalType: 'logs', - deliveryMethods: ['cloud_forwarder', 'firehose'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }, { method: 'firehose' }], inputs: ['aws-cloudwatch'], requiredConfig: ['log_group_arn', 'region_name'], packageName: 'aws', @@ -419,7 +431,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Route 53 Resolver', category: 'Networking', signalType: 'logs', - deliveryMethods: ['agentless', 'cloud_forwarder'], + deliveryMethods: [{ method: 'agentless', preferred: true }, { method: 'cloud_forwarder' }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -431,7 +443,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Transit Gateway', category: 'Networking', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -443,7 +455,11 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS VPC Flow', category: 'Security / Networking', signalType: 'logs', - deliveryMethods: ['agentless', 'cloud_forwarder', 'firehose'], + deliveryMethods: [ + { method: 'agentless', preferred: true }, + { method: 'cloud_forwarder' }, + { method: 'firehose' }, + ], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws', @@ -455,7 +471,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS VPN', category: 'Networking', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -469,7 +485,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS EBS', category: 'Storage', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -481,7 +497,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS S3 (Storage metrics)', category: 'Storage', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -493,7 +509,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS S3 (Request metrics)', category: 'Storage', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -505,7 +521,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS S3 (Access logs)', category: 'Storage', signalType: 'logs', - deliveryMethods: ['cloud_forwarder', 'firehose'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }, { method: 'firehose' }], inputs: ['aws-s3'], requiredConfig: ['bucket_arn', 'region'], packageName: 'aws', @@ -517,7 +533,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS S3 Storage Lens', category: 'Storage', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -531,7 +547,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS DynamoDB', category: 'Databases', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -543,7 +559,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS RDS', category: 'Databases', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -555,7 +571,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Redshift', category: 'Databases', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -569,7 +585,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS MSK (Kafka)', category: 'Messaging', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -581,7 +597,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Kinesis', category: 'Messaging', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -593,7 +609,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS SNS', category: 'Messaging', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -605,7 +621,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS SQS', category: 'Messaging', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws', @@ -619,7 +635,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Bedrock (Guardrails)', category: 'AI / ML', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws_bedrock', @@ -631,7 +647,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Bedrock (Invocation)', category: 'AI / ML', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws_bedrock', @@ -643,7 +659,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Bedrock (Runtime)', category: 'AI / ML', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['aws/metrics'], requiredConfig: ['regions'], packageName: 'aws_bedrock', @@ -656,7 +672,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Bedrock AgentCore', category: 'AI / ML', signalType: 'logs', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: [], requiredConfig: [], packageName: 'aws_bedrock_agentcore', @@ -670,7 +686,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Fargate', category: 'Infrastructure', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: ['awsfargate/metrics'], requiredConfig: ['regions'], packageName: 'awsfargate', @@ -685,7 +701,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS MQ', category: 'Messaging', signalType: 'metrics', - deliveryMethods: ['agentless'], + deliveryMethods: [{ method: 'agentless', preferred: true }], inputs: [], requiredConfig: [], packageName: 'aws_mq', @@ -699,7 +715,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS CloudTrail (OTel)', category: 'Security', signalType: 'logs', - deliveryMethods: ['cloud_forwarder'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }], inputs: [], requiredConfig: [], packageName: 'aws_cloudtrail_otel', @@ -712,7 +728,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS VPC Flow (OTel)', category: 'Security / Networking', signalType: 'logs', - deliveryMethods: ['cloud_forwarder'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }], inputs: [], requiredConfig: [], packageName: 'aws_vpcflow_otel', @@ -725,7 +741,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS WAF (OTel)', category: 'Security', signalType: 'logs', - deliveryMethods: ['cloud_forwarder'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }], inputs: [], requiredConfig: [], packageName: 'aws_waf_otel', @@ -740,7 +756,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Logs (Generic)', category: 'Monitoring', signalType: 'logs', - deliveryMethods: ['cloud_forwarder'], + deliveryMethods: [{ method: 'cloud_forwarder', preferred: true }], inputs: ['aws-s3', 'aws-cloudwatch'], requiredConfig: ['bucket_arn', 'log_group_arn', 'region', 'region_name'], packageName: 'aws_logs', @@ -754,7 +770,7 @@ export const AWS_SERVICES_MATRIX: AwsServiceMatrixEntry[] = [ name: 'AWS Firehose (Receiver)', category: 'Monitoring', signalType: 'logs', - deliveryMethods: ['firehose'], + deliveryMethods: [{ method: 'firehose', preferred: true }], inputs: [], requiredConfig: [], packageName: 'awsfirehose', diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/index.ts b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/index.ts index 49ae14e67423c..e7beeba79fd70 100644 --- a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/index.ts +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/index.ts @@ -6,4 +6,3 @@ */ export { registerOnboardingApp } from './register_onboarding_app'; -export { renderOnboardingApp } from './onboarding_app'; diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/onboarding_flow_context.tsx b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/onboarding_flow_context.tsx index 5c1473c5835df..94bf7d05c4201 100644 --- a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/onboarding_flow_context.tsx +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/onboarding_flow_context.tsx @@ -9,6 +9,8 @@ import React, { createContext, useContext, useCallback, useState } from 'react'; import useSessionStorage from 'react-use/lib/useSessionStorage'; import type { AwsStaticKeyCredentials, AwsTemporaryKeyCredentials } from '@kbn/fleet-plugin/public'; +import { AWS_SERVICES_MATRIX } from './aws_service_matrix'; + export interface ConnectStepState { connectorId?: string; staticKeys?: AwsStaticKeyCredentials; @@ -22,11 +24,25 @@ interface PersistedConnectStep { accessKeyId?: string; } +export interface ServicesStepState { + selectedServiceIds: string[]; +} + +interface PersistedServicesStep { + selectedServiceIds: string[]; +} + +const DEFAULT_SELECTED_IDS = AWS_SERVICES_MATRIX.filter((s) => s.showInUI && s.defaultEnabled).map( + (s) => s.id +); + interface OnboardingFlowState { connectStep: ConnectStepState; setConnectorId: (id: string | undefined) => void; setStaticKeys: (keys: AwsStaticKeyCredentials | undefined) => void; setTemporaryKeys: (keys: AwsTemporaryKeyCredentials | undefined) => void; + servicesStep: ServicesStepState; + setSelectedServiceIds: (ids: string[]) => void; } const OnboardingFlowContext = createContext(undefined); @@ -37,6 +53,11 @@ export function OnboardingFlowProvider({ children }: { children: React.ReactNode {} ); + const [persistedServices, setPersistedServices] = useSessionStorage( + 'onboarding.aws.servicesStep', + { selectedServiceIds: DEFAULT_SELECTED_IDS } + ); + // Sensitive fields (secret_access_key, session_token) live in memory only. // access_key_id is restored from session storage; passwords start empty on page refresh. const [staticKeys, setStaticKeysState] = useState(() => @@ -92,15 +113,33 @@ export function OnboardingFlowProvider({ children }: { children: React.ReactNode [setPersistedConnectStep] ); + const setSelectedServiceIds = useCallback( + (ids: string[]) => { + setPersistedServices({ ...persistedServices, selectedServiceIds: ids }); + }, + [persistedServices, setPersistedServices] + ); + const connectStep: ConnectStepState = { connectorId: persistedConnectStep?.connectorId, staticKeys, temporaryKeys, }; + const servicesStep: ServicesStepState = { + selectedServiceIds: persistedServices?.selectedServiceIds ?? DEFAULT_SELECTED_IDS, + }; + return ( {children} diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/connect_step.tsx b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/connect_step.tsx index 95f9719437ebc..dedd8a75c5b54 100644 --- a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/connect_step.tsx +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/connect_step.tsx @@ -5,22 +5,34 @@ * 2.0. */ -import React, { Suspense } from 'react'; +import React, { Suspense, useMemo } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { CoreStart } from '@kbn/core/public'; import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { CloudSetupForCloudConnector } from '@kbn/fleet-plugin/public'; import { LazyAwsConnectSetup } from '@kbn/fleet-plugin/public'; +import { AWS_SERVICES_MATRIX } from '../aws_service_matrix'; import { useOnboardingFlow } from '../onboarding_flow_context'; +const SERVICE_MAP = new Map(AWS_SERVICES_MATRIX.map((s) => [s.id, s])); + interface ConnectStepProps { onNext: () => void; } export function ConnectStep({ onNext }: ConnectStepProps) { const { services } = useKibana(); - const { connectStep, setConnectorId, setStaticKeys, setTemporaryKeys } = useOnboardingFlow(); + const { connectStep, setConnectorId, setStaticKeys, setTemporaryKeys, servicesStep } = + useOnboardingFlow(); + const { selectedServiceIds } = servicesStep; + + const showIdentityFederation = useMemo(() => { + if (selectedServiceIds.length === 0) return true; + return selectedServiceIds.every( + (id) => SERVICE_MAP.get(id)?.identityFederationSupported === true + ); + }, [selectedServiceIds]); return (
@@ -30,6 +42,7 @@ export function ConnectStep({ onNext }: ConnectStepProps) { initialConnectorId={connectStep.connectorId} initialStaticKeys={connectStep.staticKeys} initialTemporaryKeys={connectStep.temporaryKeys} + showIdentityFederation={showIdentityFederation} onNext={onNext} onConnectorIdChange={setConnectorId} onStaticKeysChange={setStaticKeys} diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step.tsx b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step.tsx deleted file mode 100644 index 93275dbea1d89..0000000000000 --- a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; - -export function ServicesStep() { - return ( - Services} - body={

Services step content will go here.

} - /> - ); -} diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/delivery_method_badge.tsx b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/delivery_method_badge.tsx new file mode 100644 index 0000000000000..09e981c9353e2 --- /dev/null +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/delivery_method_badge.tsx @@ -0,0 +1,42 @@ +/* + * 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 from 'react'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { DeliveryMethod } from '../../aws_service_matrix'; + +const LABELS: Record = { + agentless: i18n.translate('xpack.ingestHub.servicesStep.deliveryMethod.agentless', { + defaultMessage: 'Agentless', + }), + firehose: i18n.translate('xpack.ingestHub.servicesStep.deliveryMethod.firehose', { + defaultMessage: 'Firehose', + }), + cloud_forwarder: i18n.translate('xpack.ingestHub.servicesStep.deliveryMethod.cloudForwarder', { + defaultMessage: 'ECF', + }), +}; + +const TOOLTIPS: Partial> = { + cloud_forwarder: i18n.translate( + 'xpack.ingestHub.servicesStep.deliveryMethod.cloudForwarderTooltip', + { defaultMessage: 'EDOT Cloud Forwarder' } + ), +}; + +interface DeliveryMethodBadgeProps { + method: DeliveryMethod; + preferred?: boolean; +} + +export const DeliveryMethodBadge: React.FC = ({ method, preferred }) => { + const tooltip = TOOLTIPS[method]; + const badge = {LABELS[method]}; + return tooltip ? {badge} : badge; +}; diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/index.tsx b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/index.tsx new file mode 100644 index 0000000000000..54bdf803c058a --- /dev/null +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/index.tsx @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo, useState } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiButtonGroup, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexGrid, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { AWS_SERVICES_MATRIX } from '../../aws_service_matrix'; +import type { SignalType } from '../../aws_service_matrix'; +import { useOnboardingFlow } from '../../onboarding_flow_context'; +import { ServiceRow } from './service_row'; + +interface ServicesStepProps { + onNext: () => void; +} + +type SignalFilter = SignalType | 'all'; + +const SIGNAL_FILTER_OPTIONS = [ + { + id: 'all' as SignalFilter, + label: i18n.translate('xpack.ingestHub.servicesStep.filter.all', { defaultMessage: 'All' }), + }, + { + id: 'logs' as SignalFilter, + label: i18n.translate('xpack.ingestHub.servicesStep.filter.logs', { defaultMessage: 'Logs' }), + }, + { + id: 'metrics' as SignalFilter, + label: i18n.translate('xpack.ingestHub.servicesStep.filter.metrics', { + defaultMessage: 'Metrics', + }), + }, +]; + +export function ServicesStep({ onNext }: ServicesStepProps) { + const { servicesStep, setSelectedServiceIds } = useOnboardingFlow(); + const { selectedServiceIds } = servicesStep; + + const [signalFilter, setSignalFilter] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); + + const filteredServices = useMemo(() => { + const q = searchQuery.trim().toLowerCase(); + return AWS_SERVICES_MATRIX.filter( + (s) => + s.showInUI && + (signalFilter === 'all' || s.signalType === signalFilter) && + (q === '' || s.name.toLowerCase().includes(q)) + ); + }, [signalFilter, searchQuery]); + + const selectedSet = useMemo(() => new Set(selectedServiceIds), [selectedServiceIds]); + + const isReady = useMemo(() => { + return selectedServiceIds.length > 0; + }, [selectedServiceIds]); + + const handleToggle = useCallback( + (serviceId: string, checked: boolean) => { + const next = checked + ? [...new Set([...selectedServiceIds, serviceId])] + : selectedServiceIds.filter((id) => id !== serviceId); + setSelectedServiceIds(next); + }, + [selectedServiceIds, setSelectedServiceIds] + ); + + const handleSelectAll = useCallback(() => { + const filteredIdSet = new Set(filteredServices.map((s) => s.id)); + const existing = selectedServiceIds.filter((id) => !filteredIdSet.has(id)); + setSelectedServiceIds([...existing, ...filteredIdSet]); + }, [filteredServices, selectedServiceIds, setSelectedServiceIds]); + + const handleDeselectAll = useCallback(() => { + const filteredIds = new Set(filteredServices.map((s) => s.id)); + setSelectedServiceIds(selectedServiceIds.filter((id) => !filteredIds.has(id))); + }, [filteredServices, selectedServiceIds, setSelectedServiceIds]); + + const handleNext = useCallback(() => { + if (!isReady) { + return; + } + onNext(); + }, [isReady, onNext]); + + return ( +
+ + + setSearchQuery(e.target.value)} + placeholder={i18n.translate('xpack.ingestHub.servicesStep.searchPlaceholder', { + defaultMessage: 'Search services', + })} + data-test-subj="servicesStep-searchBox" + /> + + + setSignalFilter(id as SignalFilter)} + buttonSize="compressed" + data-test-subj="servicesStep-signalFilter" + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {filteredServices.map((service) => ( + + + + ))} + + + + + + + + + + + +
+ ); +} diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/service_row.tsx b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/service_row.tsx new file mode 100644 index 0000000000000..81c6907041360 --- /dev/null +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/service_row.tsx @@ -0,0 +1,54 @@ +/* + * 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 from 'react'; +import { EuiCheckableCard, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; + +import type { AwsServiceMatrixEntry } from '../../aws_service_matrix'; +import { SignalTypeBadge } from './signal_type_badge'; +import { DeliveryMethodBadge } from './delivery_method_badge'; + +interface ServiceRowProps { + service: AwsServiceMatrixEntry; + isSelected: boolean; + onToggle: (id: string, checked: boolean) => void; +} + +export const ServiceRow: React.FC = ({ service, isSelected, onToggle }) => { + return ( +
+ + + + {service.name} + + + + + + + + {service.deliveryMethods.map((entry) => ( + + + + ))} + + + + } + checkableType="checkbox" + checked={isSelected} + onChange={(e) => onToggle(service.id, e.target.checked)} + data-test-subj={`servicesStep-toggle-${service.id}`} + /> +
+ ); +}; diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/signal_type_badge.tsx b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/signal_type_badge.tsx new file mode 100644 index 0000000000000..69d250f04d5e9 --- /dev/null +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/step_components/services_step/signal_type_badge.tsx @@ -0,0 +1,34 @@ +/* + * 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 from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { SignalType } from '../../aws_service_matrix'; + +const LABELS: Record = { + logs: i18n.translate('xpack.ingestHub.servicesStep.signalType.logs', { + defaultMessage: 'Logs', + }), + metrics: i18n.translate('xpack.ingestHub.servicesStep.signalType.metrics', { + defaultMessage: 'Metrics', + }), +}; + +const COLORS: Record = { + logs: 'hollow', + metrics: 'hollow', +}; + +interface SignalTypeBadgeProps { + signalType: SignalType; +} + +export const SignalTypeBadge: React.FC = ({ signalType }) => ( + {LABELS[signalType]} +); diff --git a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/steps.ts b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/steps.ts index e2afcac2c3014..b0f2d12988062 100644 --- a/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/steps.ts +++ b/x-pack/platform/plugins/shared/ingest_hub/public/onboarding/steps.ts @@ -12,18 +12,18 @@ export interface OnboardingStepConfig { } export const ONBOARDING_STEPS: OnboardingStepConfig[] = [ - { - id: 'connect', - title: i18n.translate('xpack.ingestHub.onboarding.steps.connect.title', { - defaultMessage: 'Connect', - }), - }, { id: 'services', title: i18n.translate('xpack.ingestHub.onboarding.steps.services.title', { defaultMessage: 'Services', }), }, + { + id: 'connect', + title: i18n.translate('xpack.ingestHub.onboarding.steps.connect.title', { + defaultMessage: 'Connect', + }), + }, { id: 'name-and-scope', title: i18n.translate('xpack.ingestHub.onboarding.steps.nameAndScope.title', { diff --git a/x-pack/platform/plugins/shared/ingest_hub/test/scout/ui/tests/onboarding_services_step.spec.ts b/x-pack/platform/plugins/shared/ingest_hub/test/scout/ui/tests/onboarding_services_step.spec.ts new file mode 100644 index 0000000000000..7e0c077b393cb --- /dev/null +++ b/x-pack/platform/plugins/shared/ingest_hub/test/scout/ui/tests/onboarding_services_step.spec.ts @@ -0,0 +1,110 @@ +/* + * 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 { tags } from '@kbn/scout'; +import { expect } from '@kbn/scout/ui'; +import { test } from '../fixtures'; + +// cloudwatch_logs and cloudwatch_metrics have defaultEnabled: false; all others are enabled by default. +const TOTAL_SERVICES = 14; +const DEFAULT_SELECTED_COUNT = 12; + +test.describe('Onboarding services step', { tag: tags.stateful.classic }, () => { + test.beforeAll(async ({ apiServices }) => { + await apiServices.core.settings({ + 'feature_flags.overrides': { + 'ingestHub.onboardingEnabled': 'true', + }, + }); + }); + + test.afterAll(async ({ apiServices }) => { + await apiServices.core.settings({ + 'feature_flags.overrides': { + 'ingestHub.onboardingEnabled': 'false', + }, + }); + }); + + test('renders all services with matrix-driven defaults', async ({ browserAuth, page }) => { + await browserAuth.loginAsAdmin(); + await page.gotoApp('onboarding/aws#services'); + await expect(page.testSubj.locator('onboardingStep-services')).toBeVisible(); + + const rows = page.locator('[data-test-subj^="servicesStep-serviceRow-"]'); + await expect(rows).toHaveCount(TOTAL_SERVICES); + + // cloudwatch_logs and cloudwatch_metrics are off by default + await expect(page.testSubj.locator('servicesStep-toggle-cloudwatch_logs')).not.toBeChecked(); + await expect(page.testSubj.locator('servicesStep-toggle-cloudwatch_metrics')).not.toBeChecked(); + + // a defaultEnabled service is checked + await expect(page.testSubj.locator('servicesStep-toggle-guardduty')).toBeChecked(); + + // selected count text reflects defaults + await expect(page.getByText(`${DEFAULT_SELECTED_COUNT} services selected`)).toBeVisible(); + }); + + test('deselect and reselect a service', async ({ browserAuth, page }) => { + await browserAuth.loginAsAdmin(); + await page.gotoApp('onboarding/aws#services'); + await expect(page.testSubj.locator('onboardingStep-services')).toBeVisible(); + + // deselect guardduty + await page.testSubj.locator('servicesStep-toggle-guardduty').click(); + await expect(page.testSubj.locator('servicesStep-toggle-guardduty')).not.toBeChecked(); + await expect(page.getByText(`${DEFAULT_SELECTED_COUNT - 1} services selected`)).toBeVisible(); + + // reselect it + await page.testSubj.locator('servicesStep-toggle-guardduty').click(); + await expect(page.testSubj.locator('servicesStep-toggle-guardduty')).toBeChecked(); + await expect(page.getByText(`${DEFAULT_SELECTED_COUNT} services selected`)).toBeVisible(); + }); + + test('Next is disabled when no services are selected', async ({ browserAuth, page }) => { + await browserAuth.loginAsAdmin(); + await page.gotoApp('onboarding/aws#services'); + await expect(page.testSubj.locator('onboardingStep-services')).toBeVisible(); + + // Next is enabled while services are selected (defaults have selections) + await expect(page.testSubj.locator('servicesStep-nextButton')).toBeEnabled(); + + // deselect all services via "Deselect all" + await page.testSubj.locator('servicesStep-deselectAllButton').click(); + await expect(page.getByText('0 services selected')).toBeVisible(); + + // Next must be disabled with nothing selected + await expect(page.testSubj.locator('servicesStep-nextButton')).toBeDisabled(); + + // re-selecting any service re-enables Next + await page.testSubj.locator(`servicesStep-toggle-cloudtrail`).click(); + await expect(page.testSubj.locator('servicesStep-nextButton')).toBeEnabled(); + }); + + test('signal-type filter shows only matching services', async ({ browserAuth, page }) => { + await browserAuth.loginAsAdmin(); + await page.gotoApp('onboarding/aws#services'); + await expect(page.testSubj.locator('onboardingStep-services')).toBeVisible(); + + // switch to Logs filter + await page.testSubj.locator('servicesStep-signalFilter').getByText('Logs').click(); + + const rows = page.locator('[data-test-subj^="servicesStep-serviceRow-"]'); + // only log-signal services are shown; metrics rows are hidden + const count = await rows.count(); + expect(count).toBeLessThan(TOTAL_SERVICES); + + // a metrics-only row must not be visible + await expect(page.testSubj.locator('servicesStep-serviceRow-dynamodb')).toBeHidden(); + + // switch to Metrics + await page.testSubj.locator('servicesStep-signalFilter').getByText('Metrics').click(); + await expect(page.testSubj.locator('servicesStep-serviceRow-dynamodb')).toBeVisible(); + // a logs-only row must not be visible + await expect(page.testSubj.locator('servicesStep-serviceRow-guardduty')).toBeHidden(); + }); +}); diff --git a/x-pack/platform/plugins/shared/ingest_hub/tsconfig.json b/x-pack/platform/plugins/shared/ingest_hub/tsconfig.json index b18ddd1f2b5ac..0ed5c8e4fe9ae 100644 --- a/x-pack/platform/plugins/shared/ingest_hub/tsconfig.json +++ b/x-pack/platform/plugins/shared/ingest_hub/tsconfig.json @@ -22,6 +22,7 @@ "@kbn/scout", "@kbn/kibana-react-plugin", "@kbn/react-query", - "@kbn/fleet-plugin" + "@kbn/fleet-plugin", + "@kbn/i18n-react" ] }