Skip to content

Commit 038165c

Browse files
authored
FLPATH-2732 | project filtering (#1771)
1 parent c17b6d0 commit 038165c

10 files changed

Lines changed: 1218 additions & 87 deletions

File tree

workspaces/redhat-resource-optimization/plugins/redhat-resource-optimization-common/src/clients/optimizations/OptimizationsClient.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,112 @@ export class OptimizationsClient implements OptimizationsApi {
190190
return response;
191191
}
192192

193+
/**
194+
* Search OpenShift projects
195+
* @param search - Search term to filter projects
196+
*/
197+
public async searchOpenShiftProjects(
198+
search: string = '',
199+
): Promise<
200+
TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>
201+
> {
202+
const baseUrl = await this.discoveryApi.getBaseUrl('proxy');
203+
const searchParam = search ? `?search=${encodeURIComponent(search)}` : '';
204+
const url = `${baseUrl}/cost-management/v1/resource-types/openshift-projects/${searchParam}`;
205+
206+
return await this.fetchResourceType(url);
207+
}
208+
209+
/**
210+
* Search OpenShift clusters
211+
* @param search - Search term to filter clusters
212+
*/
213+
public async searchOpenShiftClusters(
214+
search: string = '',
215+
): Promise<
216+
TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>
217+
> {
218+
const baseUrl = await this.discoveryApi.getBaseUrl('proxy');
219+
const searchParam = search ? `?search=${encodeURIComponent(search)}` : '';
220+
const url = `${baseUrl}/cost-management/v1/resource-types/openshift-clusters/${searchParam}`;
221+
222+
return await this.fetchResourceType(url);
223+
}
224+
225+
/**
226+
* Search OpenShift nodes
227+
* @param search - Search term to filter nodes
228+
*/
229+
public async searchOpenShiftNodes(
230+
search: string = '',
231+
): Promise<
232+
TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>
233+
> {
234+
const baseUrl = await this.discoveryApi.getBaseUrl('proxy');
235+
const searchParam = search ? `?search=${encodeURIComponent(search)}` : '';
236+
const url = `${baseUrl}/cost-management/v1/resource-types/openshift-nodes/${searchParam}`;
237+
238+
return await this.fetchResourceType(url);
239+
}
240+
241+
private async fetchResourceType(
242+
url: string,
243+
): Promise<
244+
TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>
245+
> {
246+
// Get access permission
247+
const accessAPIResponse = await this.getAccess();
248+
249+
if (accessAPIResponse.decision === AuthorizeResult.DENY) {
250+
throw new UnauthorizedError();
251+
}
252+
253+
// Get or refresh token
254+
if (!this.token) {
255+
const { accessToken } = await this.getNewToken();
256+
this.token = accessToken;
257+
}
258+
259+
// Call the API via backend proxy
260+
let response = await this.fetchApi.fetch(url, {
261+
headers: {
262+
'Content-Type': 'application/json',
263+
Authorization: `Bearer ${this.token}`,
264+
},
265+
method: 'GET',
266+
});
267+
268+
// Handle 401 errors by refreshing token and retrying
269+
if (!response.ok && response.status === 401) {
270+
const { accessToken } = await this.getNewToken();
271+
this.token = accessToken;
272+
273+
response = await this.fetchApi.fetch(url, {
274+
headers: {
275+
'Content-Type': 'application/json',
276+
Authorization: `Bearer ${this.token}`,
277+
},
278+
method: 'GET',
279+
});
280+
}
281+
282+
if (!response.ok) {
283+
throw new Error(response.statusText);
284+
}
285+
286+
return {
287+
...response,
288+
json: async () => {
289+
const data = await response.json();
290+
return data as {
291+
data: Array<{ value: string }>;
292+
meta?: any;
293+
links?: any;
294+
};
295+
},
296+
};
297+
}
298+
193299
private async getAccess(): Promise<GetAccessResponse> {
194300
const baseUrl = await this.discoveryApi.getBaseUrl(`${pluginId}`);
195301
const response = await this.fetchApi.fetch(`${baseUrl}/access`);

workspaces/redhat-resource-optimization/plugins/redhat-resource-optimization-common/src/clients/optimizations/types.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,29 @@
1515
*/
1616

1717
import { DefaultApiClient } from '../../generated/apis/DefaultApi.client';
18+
import type { TypedResponse } from '../../generated/apis/DefaultApi.client';
1819

1920
/** @public */
2021
export type OptimizationsApi = Omit<
2122
InstanceType<typeof DefaultApiClient>,
2223
'fetchApi' | 'discoveryApi'
23-
>;
24+
> & {
25+
searchOpenShiftProjects(
26+
search?: string,
27+
): Promise<
28+
TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>
29+
>;
30+
searchOpenShiftClusters(
31+
search?: string,
32+
): Promise<
33+
TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>
34+
>;
35+
searchOpenShiftNodes(
36+
search?: string,
37+
): Promise<
38+
TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>
39+
>;
40+
};
2441

2542
/** @public */
2643
export type GetCostManagementRequest = Parameters<

workspaces/redhat-resource-optimization/plugins/redhat-resource-optimization-common/src/clients/types/cost-management.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,40 @@
1414
* limitations under the License.
1515
*/
1616

17+
/** @public */
18+
export type CurrencyCode =
19+
| 'USD'
20+
| 'EUR'
21+
| 'GBP'
22+
| 'JPY'
23+
| 'AUD'
24+
| 'CAD'
25+
| 'CHF'
26+
| 'CNY'
27+
| 'INR'
28+
| 'MXN'
29+
| 'NZD'
30+
| 'SEK'
31+
| 'SGD'
32+
| 'HKD'
33+
| 'TWD'
34+
| 'THB'
35+
| 'RUB'
36+
| 'BRL'
37+
| 'ZAR'
38+
| 'PLN'
39+
| 'KRW'
40+
| 'TRY'
41+
| 'IDR'
42+
| 'MYR'
43+
| 'PHP'
44+
| 'VND'
45+
| 'HUF'
46+
| 'CZK'
47+
| 'NOK'
48+
| 'DKK'
49+
| 'NGN';
50+
1751
/** @public */
1852
export interface CostValue {
1953
value: number;
@@ -40,7 +74,10 @@ export interface DistributedCost extends BasicCost {
4074
/** @public */
4175
export interface ProjectValue {
4276
date: string;
43-
project: string;
77+
project?: string;
78+
cluster?: string;
79+
node?: string;
80+
tag?: string;
4481
cost_group: number | string;
4582
classification: string;
4683
source_uuid: string[];
@@ -54,14 +91,36 @@ export interface ProjectValue {
5491

5592
/** @public */
5693
export interface Project {
57-
project: string;
94+
project?: string;
95+
values: ProjectValue[];
96+
}
97+
98+
/** @public */
99+
export interface Cluster {
100+
cluster: string;
101+
values: ProjectValue[];
102+
}
103+
104+
/** @public */
105+
export interface Node {
106+
node: string;
107+
values: ProjectValue[];
108+
}
109+
110+
/** @public */
111+
export interface Tag {
112+
tag: string;
58113
values: ProjectValue[];
59114
}
60115

61116
/** @public */
62117
export interface DateData {
63118
date: string;
64-
projects: Project[];
119+
projects?: Project[];
120+
clusters?: Cluster[];
121+
nodes?: Node[];
122+
tags?: Tag[];
123+
[key: string]: unknown;
65124
}
66125

67126
/** @public */
@@ -109,7 +168,7 @@ export interface CostManagementReport {
109168
/** @public */
110169
export interface GetCostManagementRequest {
111170
query: {
112-
currency?: 'USD' | 'EUR' | 'GBP';
171+
currency?: CurrencyCode;
113172
delta?: string;
114173
'filter[limit]'?: number;
115174
'filter[offset]'?: number;

workspaces/redhat-resource-optimization/plugins/redhat-resource-optimization-common/src/generated/apis/DefaultApi.client.ts

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import * as parser from 'uri-template';
2525

2626
import { RecommendationBoxPlots } from '../models/RecommendationBoxPlots.model';
2727
import { RecommendationList } from '../models/RecommendationList.model';
28-
import type { CostManagementReport } from '../../clients/types/cost-management';
28+
import type {
29+
CostManagementReport,
30+
CurrencyCode,
31+
} from '../../clients/types/cost-management';
2932

3033
/**
3134
* Wraps the Response type to convey a type on the json call.
@@ -158,7 +161,7 @@ export class DefaultApiClient {
158161

159162
/**
160163
* Get cost management report for OpenShift projects
161-
* @param currency - Currency preference (USD, EUR, GBP)
164+
* @param currency - Currency preference (USD, EUR, GBP, JPY, AUD, CAD, CHF, CNY, INR, MXN, NZD, SEK, SGD, HKD, TWD, THB, RUB, BRL, ZAR, PLN, KRW, TRY, IDR, MYR, PHP, VND, HUF, CZK, NOK, DKK, NGN)
162165
* @param delta - Delta calculation method
163166
* @param filter - Filter parameters
164167
* @param group_by - Group by parameters
@@ -167,17 +170,22 @@ export class DefaultApiClient {
167170
public async getCostManagementReport(
168171
request: {
169172
query: {
170-
currency?: 'USD' | 'EUR' | 'GBP';
173+
currency?: CurrencyCode;
171174
delta?: string;
172175
'filter[limit]'?: number;
173176
'filter[offset]'?: number;
174177
'filter[resolution]'?: 'daily' | 'monthly';
175178
'filter[time_scope_units]'?: 'day' | 'month';
176179
'filter[time_scope_value]'?: number;
177180
'group_by[project]'?: '*' | string;
181+
'group_by[cluster]'?: '*' | string;
182+
'group_by[node]'?: '*' | string;
183+
'group_by[tag]'?: '*' | string;
184+
'order_by[cost]'?: 'asc' | 'desc';
178185
'order_by[distributed_cost]'?: 'asc' | 'desc';
179186
'order_by[markup_cost]'?: 'asc' | 'desc';
180187
'order_by[raw_cost]'?: 'asc' | 'desc';
188+
[key: string]: string | number | undefined;
181189
};
182190
},
183191
options?: RequestOptions,
@@ -224,30 +232,47 @@ export class DefaultApiClient {
224232
String(request.query['filter[time_scope_value]']),
225233
);
226234
}
227-
if (request.query['group_by[project]']) {
228-
queryParams.append(
229-
'group_by[project]',
230-
request.query['group_by[project]'],
231-
);
232-
}
233-
if (request.query['order_by[distributed_cost]']) {
234-
queryParams.append(
235-
'order_by[distributed_cost]',
236-
request.query['order_by[distributed_cost]'],
237-
);
238-
}
239-
if (request.query['order_by[markup_cost]']) {
240-
queryParams.append(
241-
'order_by[markup_cost]',
242-
request.query['order_by[markup_cost]'],
243-
);
244-
}
245-
if (request.query['order_by[raw_cost]']) {
246-
queryParams.append(
247-
'order_by[raw_cost]',
248-
request.query['order_by[raw_cost]'],
249-
);
250-
}
235+
// Handle dynamic group_by parameters (project, cluster, node, tag, etc.)
236+
Object.keys(request.query).forEach(key => {
237+
if (key.startsWith('group_by[') && key.endsWith(']')) {
238+
const value = request.query[key as keyof typeof request.query];
239+
if (value) {
240+
queryParams.append(key, String(value));
241+
}
242+
}
243+
});
244+
245+
// Handle dynamic order_by parameters (cost, distributed_cost, raw_cost, etc.)
246+
Object.keys(request.query).forEach(key => {
247+
if (key.startsWith('order_by[') && key.endsWith(']')) {
248+
const value = request.query[key as keyof typeof request.query];
249+
if (value) {
250+
queryParams.append(key, String(value));
251+
}
252+
}
253+
});
254+
255+
// Handle dynamic filter parameters (filter[project], filter[cluster], filter[node], etc.)
256+
// Skip the ones already handled explicitly above (limit, offset, resolution, time_scope_units, time_scope_value)
257+
const handledFilterKeys = [
258+
'filter[limit]',
259+
'filter[offset]',
260+
'filter[resolution]',
261+
'filter[time_scope_units]',
262+
'filter[time_scope_value]',
263+
];
264+
Object.keys(request.query).forEach(key => {
265+
if (
266+
key.startsWith('filter[') &&
267+
key.endsWith(']') &&
268+
!handledFilterKeys.includes(key)
269+
) {
270+
const value = request.query[key as keyof typeof request.query];
271+
if (value) {
272+
queryParams.append(key, String(value));
273+
}
274+
}
275+
});
251276

252277
const queryString = queryParams.toString();
253278
const url = `${baseUrl}${uri}${queryString ? `?${queryString}` : ''}`;

0 commit comments

Comments
 (0)