Skip to content

Commit cdd7cf1

Browse files
authored
Modify Compute Optimiser actions to allow partial results (#2222)
Fixes OPS-4065 ## Additional Notes <img width="583" height="766" alt="Screenshot 2026-04-15 at 5 38 59 PM" src="https://github.com/user-attachments/assets/4e24c566-e773-4afb-9355-bf618bb05bf7" /> <img width="583" height="766" alt="Screenshot 2026-04-15 at 5 38 52 PM" src="https://github.com/user-attachments/assets/d20bc42f-5ff1-4dfd-b087-3dd65ddd643b" /> <img width="583" height="766" alt="Screenshot 2026-04-15 at 5 38 45 PM" src="https://github.com/user-attachments/assets/6e056235-cc0b-490f-8377-a36db2e4359b" />
1 parent 2619cee commit cdd7cf1

12 files changed

Lines changed: 737 additions & 37 deletions

packages/blocks/aws-compute-optimizer/src/lib/actions/ebs-get-recommendations-action.ts

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import {
1717
} from '@openops/common';
1818
import {
1919
getEbsRecommendationsForARNs,
20+
getEbsRecommendationsForARNsAllowPartial,
2021
getEbsRecommendationsForRegions,
22+
getEbsRecommendationsForRegionsAllowPartial,
2123
} from '../common/compute-optimizer-ebs-client';
2224

2325
export const ebsGetRecommendationsAction = createAction({
@@ -72,6 +74,13 @@ export const ebsGetRecommendationsAction = createAction({
7274
return props;
7375
},
7476
}),
77+
allowPartialResults: Property.Checkbox({
78+
displayName: 'Allow Partial Results',
79+
description:
80+
'When enabled, the step returns partial results if the operation fails in some selected regions.',
81+
required: false,
82+
defaultValue: false,
83+
}),
7584
},
7685
async run(context) {
7786
try {
@@ -81,10 +90,34 @@ export const ebsGetRecommendationsAction = createAction({
8190
const findingType = getFindingType(context);
8291

8392
const resourceArns = context.propsValue.filterProperty['resourceARNs'];
93+
const partial = context.propsValue.allowPartialResults === true;
8494

8595
if (resourceArns) {
8696
const arns = convertToARNArrayWithValidation(resourceArns);
8797
const groupedARNs = groupARNsByAccount(arns);
98+
99+
if (partial) {
100+
const partialOutcomes = await Promise.all(
101+
Object.keys(groupedARNs).map(async (accountId) => {
102+
const arnsForAccount = groupedARNs[accountId];
103+
const credentials = await getCredentialsForAccount(
104+
context.auth,
105+
accountId,
106+
);
107+
return getEbsRecommendationsForARNsAllowPartial(
108+
credentials,
109+
findingType,
110+
arnsForAccount,
111+
);
112+
}),
113+
);
114+
115+
return {
116+
results: partialOutcomes.flatMap((o) => o.results),
117+
failedRegions: partialOutcomes.flatMap((o) => o.failedRegions),
118+
};
119+
}
120+
88121
const promises = [];
89122

90123
for (const accountId in groupedARNs) {
@@ -114,13 +147,27 @@ export const ebsGetRecommendationsAction = createAction({
114147
context.auth,
115148
accounts,
116149
);
117-
const promises = credentials.map((credentials) => {
118-
return getEbsRecommendationsForRegions(
119-
credentials,
120-
findingType,
121-
regions,
150+
151+
if (partial) {
152+
const partialOutcomes = await Promise.all(
153+
credentials.map((creds) =>
154+
getEbsRecommendationsForRegionsAllowPartial(
155+
creds,
156+
findingType,
157+
regions,
158+
),
159+
),
122160
);
123-
});
161+
162+
return {
163+
results: partialOutcomes.flatMap((o) => o.results),
164+
failedRegions: partialOutcomes.flatMap((o) => o.failedRegions),
165+
};
166+
}
167+
168+
const promises = credentials.map((creds) =>
169+
getEbsRecommendationsForRegions(creds, findingType, regions),
170+
);
124171

125172
const recommendations = await Promise.all(promises);
126173

packages/blocks/aws-compute-optimizer/src/lib/actions/ec2-get-recommendations-action.ts

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import {
1717
} from '@openops/common';
1818
import {
1919
getEC2RecommendationsForARNs,
20+
getEC2RecommendationsForARNsAllowPartial,
2021
getEC2RecommendationsForRegions,
22+
getEC2RecommendationsForRegionsAllowPartial,
2123
} from '../common/compute-optimizer-ec2-client';
2224

2325
export const ec2GetRecommendationsAction = createAction({
@@ -75,6 +77,13 @@ export const ec2GetRecommendationsAction = createAction({
7577
return props;
7678
},
7779
}),
80+
allowPartialResults: Property.Checkbox({
81+
displayName: 'Allow Partial Results',
82+
description:
83+
'When enabled, the step returns partial results if the operation fails in some selected regions.',
84+
required: false,
85+
defaultValue: false,
86+
}),
7887
},
7988
async run(context) {
8089
try {
@@ -83,10 +92,34 @@ export const ec2GetRecommendationsAction = createAction({
8392
| undefined;
8493
const findingType = getFindingType(context);
8594
const resourceArns = context.propsValue.filterProperty['resourceARNs'];
95+
const partial = context.propsValue.allowPartialResults === true;
8696

8797
if (resourceArns) {
8898
const arns = convertToARNArrayWithValidation(resourceArns);
8999
const groupedARNs = groupARNsByAccount(arns);
100+
101+
if (partial) {
102+
const partialOutcomes = await Promise.all(
103+
Object.keys(groupedARNs).map(async (accountId) => {
104+
const arnsForAccount = groupedARNs[accountId];
105+
const credentials = await getCredentialsForAccount(
106+
context.auth,
107+
accountId,
108+
);
109+
return getEC2RecommendationsForARNsAllowPartial(
110+
credentials,
111+
findingType,
112+
arnsForAccount,
113+
);
114+
}),
115+
);
116+
117+
return {
118+
results: partialOutcomes.flatMap((o) => o.results),
119+
failedRegions: partialOutcomes.flatMap((o) => o.failedRegions),
120+
};
121+
}
122+
90123
const promises = [];
91124

92125
for (const accountId in groupedARNs) {
@@ -116,13 +149,27 @@ export const ec2GetRecommendationsAction = createAction({
116149
context.auth,
117150
accounts,
118151
);
119-
const promises = credentials.map((credentials) => {
120-
return getEC2RecommendationsForRegions(
121-
credentials,
122-
findingType,
123-
regions,
152+
153+
if (partial) {
154+
const partialOutcomes = await Promise.all(
155+
credentials.map((creds) =>
156+
getEC2RecommendationsForRegionsAllowPartial(
157+
creds,
158+
findingType,
159+
regions,
160+
),
161+
),
124162
);
125-
});
163+
164+
return {
165+
results: partialOutcomes.flatMap((o) => o.results),
166+
failedRegions: partialOutcomes.flatMap((o) => o.failedRegions),
167+
};
168+
}
169+
170+
const promises = credentials.map((creds) =>
171+
getEC2RecommendationsForRegions(creds, findingType, regions),
172+
);
126173

127174
const recommendations = await Promise.all(promises);
128175
return recommendations.flat();

packages/blocks/aws-compute-optimizer/src/lib/actions/get-recommendations-summary-action.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
import { createAction } from '@openops/blocks-framework';
1+
import { createAction, Property } from '@openops/blocks-framework';
22
import {
33
amazonAuth,
44
convertToRegionsArrayWithValidation,
55
getAwsAccountsMultiSelectDropdown,
66
getCredentialsListFromAuth,
77
regionsStaticMultiSelectDropdown,
88
} from '@openops/common';
9-
import { getRecommendationSummaries } from '../common/compute-optimizer-client';
9+
import {
10+
getRecommendationSummaries,
11+
getRecommendationSummariesAllowPartial,
12+
} from '../common/compute-optimizer-client';
1013

1114
export const getRecommendationsSummaryAction = createAction({
1215
auth: amazonAuth,
@@ -17,6 +20,13 @@ export const getRecommendationsSummaryAction = createAction({
1720
props: {
1821
accounts: getAwsAccountsMultiSelectDropdown().accounts,
1922
regions: regionsStaticMultiSelectDropdown(true).regions,
23+
allowPartialResults: Property.Checkbox({
24+
displayName: 'Allow Partial Results',
25+
description:
26+
'When enabled, the step returns partial results if the operation fails in some selected regions.',
27+
required: false,
28+
defaultValue: false,
29+
}),
2030
},
2131
async run(context) {
2232
try {
@@ -26,14 +36,28 @@ export const getRecommendationsSummaryAction = createAction({
2636
const regions = convertToRegionsArrayWithValidation(
2737
context.propsValue.regions,
2838
);
39+
const partial = context.propsValue.allowPartialResults === true;
2940
const credentials = await getCredentialsListFromAuth(
3041
context.auth,
3142
accounts,
3243
);
3344

34-
const promises = credentials.map((credentials) => {
35-
return getRecommendationSummaries(credentials, regions);
36-
});
45+
if (partial) {
46+
const partialOutcomes = await Promise.all(
47+
credentials.map((creds) =>
48+
getRecommendationSummariesAllowPartial(creds, regions),
49+
),
50+
);
51+
52+
return {
53+
results: partialOutcomes.flatMap((o) => o.results),
54+
failedRegions: partialOutcomes.flatMap((o) => o.failedRegions),
55+
};
56+
}
57+
58+
const promises = credentials.map((creds) =>
59+
getRecommendationSummaries(creds, regions),
60+
);
3761

3862
const recommendations = await Promise.all(promises);
3963

packages/blocks/aws-compute-optimizer/src/lib/common/compute-optimizer-client.ts

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ import {
33
GetRecommendationSummariesCommand,
44
RecommendationSummary,
55
} from '@aws-sdk/client-compute-optimizer';
6-
import { getAwsClient, makeAwsRequest } from '@openops/common';
6+
import {
7+
formatAwsError,
8+
getAccountId,
9+
getAwsClient,
10+
makeAwsRequest,
11+
type FailedRegion,
12+
type PartialResult,
13+
} from '@openops/common';
714

815
export async function getRecommendationSummaries(
916
credentials: any,
@@ -12,21 +19,65 @@ export async function getRecommendationSummaries(
1219
const results: RecommendationSummary[] = [];
1320

1421
for (const region of regions) {
15-
const client = getComputeOptimizerClient(credentials, region);
16-
const command = new GetRecommendationSummariesCommand({
17-
nextToken: '',
18-
});
19-
const regionalResults = await makeAwsRequest(client, command);
20-
21-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22-
for (const result of regionalResults as any) {
23-
const recommendationSummaries = result.recommendationSummaries?.map(
24-
(item: any) => ({ ...item, region }),
25-
);
26-
27-
if (recommendationSummaries) {
28-
results.push(...recommendationSummaries);
29-
}
22+
const regionalResults = await fetchRecommendationSummariesForRegion(
23+
credentials,
24+
region,
25+
);
26+
results.push(...regionalResults);
27+
}
28+
29+
return results;
30+
}
31+
32+
export async function getRecommendationSummariesAllowPartial(
33+
credentials: any,
34+
regions: string[],
35+
): Promise<PartialResult<RecommendationSummary>> {
36+
const accountId = await getAccountId(credentials, regions[0]);
37+
const settled = await Promise.allSettled(
38+
regions.map((region) =>
39+
fetchRecommendationSummariesForRegion(credentials, region),
40+
),
41+
);
42+
43+
const results: RecommendationSummary[] = [];
44+
const failedRegions: FailedRegion[] = [];
45+
46+
settled.forEach((outcome, index) => {
47+
const region = regions[index];
48+
if (outcome.status === 'fulfilled') {
49+
results.push(...outcome.value);
50+
} else {
51+
failedRegions.push({
52+
region,
53+
accountId,
54+
error: formatAwsError(outcome.reason),
55+
});
56+
}
57+
});
58+
59+
return { results, failedRegions };
60+
}
61+
62+
async function fetchRecommendationSummariesForRegion(
63+
credentials: any,
64+
region: string,
65+
): Promise<RecommendationSummary[]> {
66+
const results: RecommendationSummary[] = [];
67+
const client = getComputeOptimizerClient(credentials, region);
68+
const command = new GetRecommendationSummariesCommand({
69+
nextToken: '',
70+
});
71+
const regionalResults = await makeAwsRequest(client, command);
72+
73+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
74+
for (const result of regionalResults as any) {
75+
const recommendationSummaries = result.recommendationSummaries?.map(
76+
(item: any) => ({ ...item, region }),
77+
);
78+
79+
if (recommendationSummaries) {
80+
results.push(...recommendationSummaries);
3081
}
3182
}
3283

0 commit comments

Comments
 (0)