diff --git a/frontend/src/container/CloudIntegrationPage/ServicesSection/ConfigureServiceModal.tsx b/frontend/src/container/CloudIntegrationPage/ServicesSection/ConfigureServiceModal.tsx index 9ad11137df..e3da1991ec 100644 --- a/frontend/src/container/CloudIntegrationPage/ServicesSection/ConfigureServiceModal.tsx +++ b/frontend/src/container/CloudIntegrationPage/ServicesSection/ConfigureServiceModal.tsx @@ -8,12 +8,14 @@ import { SupportedSignals, } from 'container/CloudIntegrationPage/ServicesSection/types'; import { useUpdateServiceConfig } from 'hooks/integration/aws/useUpdateServiceConfig'; +import { isEqual } from 'lodash-es'; import { useCallback, useMemo, useState } from 'react'; import { useQueryClient } from 'react-query'; import logEvent from '../../../api/common/logEvent'; +import S3BucketsSelector from './S3BucketsSelector'; -interface IConfigureServiceModalProps { +export interface IConfigureServiceModalProps { isOpen: boolean; onClose: () => void; serviceName: string; @@ -36,18 +38,34 @@ function ConfigureServiceModal({ const [isLoading, setIsLoading] = useState(false); // Track current form values - const initialValues = { - metrics: initialConfig?.metrics?.enabled || false, - logs: initialConfig?.logs?.enabled || false, - }; + const initialValues = useMemo( + () => ({ + metrics: initialConfig?.metrics?.enabled || false, + logs: initialConfig?.logs?.enabled || false, + s3Buckets: initialConfig?.logs?.s3_buckets || {}, + }), + [initialConfig], + ); const [currentValues, setCurrentValues] = useState(initialValues); const isSaveDisabled = useMemo( () => // disable only if current values are same as the initial config currentValues.metrics === initialValues.metrics && - currentValues.logs === initialValues.logs, - [currentValues, initialValues.metrics, initialValues.logs], + currentValues.logs === initialValues.logs && + isEqual(currentValues.s3Buckets, initialValues.s3Buckets), + [currentValues, initialValues], + ); + + const handleS3BucketsChange = useCallback( + (bucketsByRegion: Record) => { + setCurrentValues((prev) => ({ + ...prev, + s3Buckets: bucketsByRegion, + })); + form.setFieldsValue({ s3Buckets: bucketsByRegion }); + }, + [form], ); const { @@ -70,6 +88,7 @@ function ConfigureServiceModal({ config: { logs: { enabled: values.logs, + s3_buckets: values.s3Buckets, }, metrics: { enabled: values.metrics, @@ -144,6 +163,7 @@ function ConfigureServiceModal({ initialValues={{ metrics: initialConfig?.metrics?.enabled || false, logs: initialConfig?.logs?.enabled || false, + s3Buckets: initialConfig?.logs?.s3_buckets || {}, }} >
@@ -174,27 +194,38 @@ function ConfigureServiceModal({ )} {supportedSignals.logs && ( - -
- { - setCurrentValues((prev) => ({ ...prev, logs: checked })); - form.setFieldsValue({ logs: checked }); - }} - /> - - Log Collection - -
-
- To ingest logs from your AWS services, you must complete several steps -
-
+ <> + +
+ { + setCurrentValues((prev) => ({ ...prev, logs: checked })); + form.setFieldsValue({ logs: checked }); + }} + /> + + Log Collection + +
+
+ To ingest logs from your AWS services, you must complete several steps +
+
+ + {currentValues.logs && serviceId === 's3sync' && ( + + + + )} + )}
diff --git a/frontend/src/container/CloudIntegrationPage/ServicesSection/S3BucketsSelector.tsx b/frontend/src/container/CloudIntegrationPage/ServicesSection/S3BucketsSelector.tsx new file mode 100644 index 0000000000..11a82ca87f --- /dev/null +++ b/frontend/src/container/CloudIntegrationPage/ServicesSection/S3BucketsSelector.tsx @@ -0,0 +1,123 @@ +import { Form, Select, Skeleton, Typography } from 'antd'; +import { useAwsAccounts } from 'hooks/integration/aws/useAwsAccounts'; +import useUrlQuery from 'hooks/useUrlQuery'; +import { useCallback, useMemo, useState } from 'react'; + +const { Title } = Typography; + +interface S3BucketsSelectorProps { + onChange?: (bucketsByRegion: Record) => void; + initialBucketsByRegion?: Record; +} + +/** + * Component for selecting S3 buckets by AWS region + * Displays a multi-select input for each region in the active AWS account + */ +function S3BucketsSelector({ + onChange, + initialBucketsByRegion = {}, +}: S3BucketsSelectorProps): JSX.Element { + const cloudAccountId = useUrlQuery().get('cloudAccountId'); + const { data: accounts, isLoading } = useAwsAccounts(); + const [bucketsByRegion, setBucketsByRegion] = useState< + Record + >(initialBucketsByRegion); + + // Find the active AWS account based on the URL query parameter + const activeAccount = useMemo( + () => + accounts?.find((account) => account.cloud_account_id === cloudAccountId), + [accounts, cloudAccountId], + ); + + // Get all regions to display (union of account regions and initialBucketsByRegion regions) + const allRegions = useMemo(() => { + if (!activeAccount) return []; + + // Get unique regions from both sources + const initialRegions = Object.keys(initialBucketsByRegion); + const accountRegions = activeAccount.config.regions; + + // Create a Set to get unique values + const uniqueRegions = new Set([...accountRegions, ...initialRegions]); + + return Array.from(uniqueRegions); + }, [activeAccount, initialBucketsByRegion]); + + // Check if a region is disabled (not in account's regions) + const isRegionDisabled = useCallback( + (region: string) => !activeAccount?.config.regions.includes(region), + [activeAccount], + ); + + // Handle changes to bucket selections for a specific region + const handleRegionBucketsChange = useCallback( + (region: string, buckets: string[]): void => { + setBucketsByRegion((prevBuckets) => { + const updatedBuckets = { ...prevBuckets }; + + if (buckets.length === 0) { + // Remove empty bucket arrays + delete updatedBuckets[region]; + } else { + updatedBuckets[region] = buckets; + } + + // Notify parent component of changes + onChange?.(updatedBuckets); + return updatedBuckets; + }); + }, + [onChange], + ); + + // Show loading state while fetching account data + if (isLoading || !activeAccount) { + return ; + } + + return ( +
+ Select S3 Buckets by Region + + {allRegions.map((region) => { + const disabled = isRegionDisabled(region); + + return ( + +