diff --git a/bumiworker/bumiworker/modules/recommendations/s3_intelligent_tiering.py b/bumiworker/bumiworker/modules/recommendations/s3_intelligent_tiering.py index 70d25410f..606df860d 100644 --- a/bumiworker/bumiworker/modules/recommendations/s3_intelligent_tiering.py +++ b/bumiworker/bumiworker/modules/recommendations/s3_intelligent_tiering.py @@ -1,6 +1,6 @@ import logging from collections import defaultdict -from concurrent.futures.thread import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Any, Dict, List, Tuple from datetime import datetime, date, timedelta @@ -205,7 +205,7 @@ def _display_name(storage_type): }) metrics = aws.get_cloud_watch_metric_data( region, metric_queries, today, - today - timedelta(days=7)) + today - timedelta(days=2)) for md in metrics.get("MetricDataResults", []): values = md.get("Values") or [] if not values: @@ -228,14 +228,21 @@ def _candidates_and_savings(self, """ buckets_data = {} result = [] - with ThreadPoolExecutor(max_workers=50) as executor: + if not region_candidates: + return result + cw_clients = { + region: aws.session.client("cloudwatch", region_name=region) + for region in region_candidates + } + max_workers = min(20, sum(len(v) for v in region_candidates.values())) + with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [] - for region, candidates in region_candidates.items(): - s3_client = aws.session.client("s3", region_name=region) - for cand in candidates: + for region, buckets in region_candidates.items(): + cloudwatch = cw_clients[region] + for bucket in buckets: futures.append(executor.submit( - aws.get_bucket_storage_info, s3_client, cand, )) - for f in futures: + aws.get_bucket_storage_info, cloudwatch, bucket, )) + for f in as_completed(futures): res = f.result() if res: buckets_data.update(res) diff --git a/ngui/server/graphql/__generated__/types/restapi.ts b/ngui/server/graphql/__generated__/types/restapi.ts index 4c67fb666..070b6f9d2 100644 --- a/ngui/server/graphql/__generated__/types/restapi.ts +++ b/ngui/server/graphql/__generated__/types/restapi.ts @@ -82,6 +82,7 @@ export type AwsAssumedRoleConfigInput = { region_name?: InputMaybe; report_name?: InputMaybe; use_edp_discount?: InputMaybe; + linked?: InputMaybe; }; export type AwsConfig = { diff --git a/ngui/server/graphql/typeDefs/restapi/restapi.ts b/ngui/server/graphql/typeDefs/restapi/restapi.ts index 5ba5a65f4..a5fea37e9 100644 --- a/ngui/server/graphql/typeDefs/restapi/restapi.ts +++ b/ngui/server/graphql/typeDefs/restapi/restapi.ts @@ -359,6 +359,7 @@ export default gql` cur_version: Int config_scheme: String report_name: String + linked: Boolean } input AzureSubscriptionConfigInput { diff --git a/ngui/ui/src/components/Button/Button.tsx b/ngui/ui/src/components/Button/Button.tsx index 7a2029d17..bdd14aa46 100644 --- a/ngui/ui/src/components/Button/Button.tsx +++ b/ngui/ui/src/components/Button/Button.tsx @@ -30,7 +30,7 @@ type ButtonText = ExclusiveUnion<{ pepega: string; }>; -type ButtonProps = MuiButtonProps & +export type ButtonProps = MuiButtonProps & ButtonText & NavProps & { dataTestId?: string; diff --git a/ngui/ui/src/components/CanvasBarChart/CanvasBarChart.tsx b/ngui/ui/src/components/CanvasBarChart/CanvasBarChart.tsx index c39d4f51a..fd7f61c06 100644 --- a/ngui/ui/src/components/CanvasBarChart/CanvasBarChart.tsx +++ b/ngui/ui/src/components/CanvasBarChart/CanvasBarChart.tsx @@ -428,6 +428,7 @@ const CanvasBarChart = ({ const ResponsiveCanvasBarChart = ({ data, + wrapperRef, keys = [], style = {}, isLoading = false, @@ -454,6 +455,7 @@ const ResponsiveCanvasBarChart = ({ style={{ height: muiTheme.spacing(height) }} + ref={wrapperRef} > {({ width: wrapperWidth, height: wrapperHeight }) => { diff --git a/ngui/ui/src/components/CloudResourceId/CloudResourceId.tsx b/ngui/ui/src/components/CloudResourceId/CloudResourceId.tsx index e9bbfc587..19ff69b8f 100644 --- a/ngui/ui/src/components/CloudResourceId/CloudResourceId.tsx +++ b/ngui/ui/src/components/CloudResourceId/CloudResourceId.tsx @@ -46,7 +46,13 @@ const CloudResourceId = (props) => { // Additional check to handle cloudResourceIdentifier having 'null' or 'undefined' substring if (separator && cloudResourceIdentifier.includes(separator)) { - const shortenedCloudResourceId = `${SHORTENED_CLOUD_RESOURCE_ID_PREFIX}${cloudResourceIdentifier.split(separator).pop()}`; + // If the path ends with "/", take last 3 segments (last segment is empty) + // Example: "path/to/resource/" -> ["to", "resource", ""] -> "to/resource/" + const shortId = cloudResourceIdentifier.endsWith(separator) + ? cloudResourceIdentifier.split(separator).slice(-3).join(separator) + : cloudResourceIdentifier.split(separator).pop(); + + const shortenedCloudResourceId = `${SHORTENED_CLOUD_RESOURCE_ID_PREFIX}${shortId}`; return ( ( <> - - - - - - + {showAdvancedOptions && ( + <> + + + + + + + + )} ); diff --git a/ngui/ui/src/components/DataSourceDetails/Properties/AwsProperties.tsx b/ngui/ui/src/components/DataSourceDetails/Properties/AwsProperties.tsx index 3c15ade2e..52507a09f 100644 --- a/ngui/ui/src/components/DataSourceDetails/Properties/AwsProperties.tsx +++ b/ngui/ui/src/components/DataSourceDetails/Properties/AwsProperties.tsx @@ -21,14 +21,18 @@ const AwsProperties = ({ accountId, config, createdAt }: AwsPropertiesProps) => const getAwsAccountTypeMessageId = () => { if (linked) { - return "linked"; + return "member"; } + return "managementStandalone"; + }; + + const getAwsAuthenticationTypeMessageId = () => { if (isAssumeRole) { return "assumedRole"; } - return "root"; + return "accessKey"; }; return ( @@ -57,6 +61,11 @@ const AwsProperties = ({ accountId, config, createdAt }: AwsPropertiesProps) => value: `p_${AWS_CNR}_value` }} /> + } + dataTestIds={{ key: "p_authentication_type_key", value: "p_authentication_type_value" }} + /> {isAssumeRole && ( (type === "icon" ? :