@electrolux-oss/plugin-infrawallet 0.1.9 → 0.1.11-20240925135753-e34b0d4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -25
- package/dist/api/InfraWalletApi.esm.js.map +1 -1
- package/dist/api/InfraWalletApiClient.esm.js +12 -2
- package/dist/api/InfraWalletApiClient.esm.js.map +1 -1
- package/dist/api/functions.esm.js +48 -5
- package/dist/api/functions.esm.js.map +1 -1
- package/dist/components/ColumnsChartComponent/ColumnsChartComponent.esm.js +117 -101
- package/dist/components/ColumnsChartComponent/ColumnsChartComponent.esm.js.map +1 -1
- package/dist/components/CostReportsTableComponent/CostReportsTableComponent.esm.js +190 -110
- package/dist/components/CostReportsTableComponent/CostReportsTableComponent.esm.js.map +1 -1
- package/dist/components/ErrorsAlertComponent/ErrorsAlertComponent.esm.js +3 -2
- package/dist/components/ErrorsAlertComponent/ErrorsAlertComponent.esm.js.map +1 -1
- package/dist/components/FiltersComponent/FiltersComponent.esm.js +217 -10
- package/dist/components/FiltersComponent/FiltersComponent.esm.js.map +1 -1
- package/dist/components/MetricConfigurationComponent/MetricConfigurationComponent.esm.js +7 -0
- package/dist/components/MetricConfigurationComponent/MetricConfigurationComponent.esm.js.map +1 -1
- package/dist/components/PieChartComponent/PieChartComponent.esm.js +15 -19
- package/dist/components/PieChartComponent/PieChartComponent.esm.js.map +1 -1
- package/dist/components/ProviderIcons/ProviderIcons.esm.js +322 -0
- package/dist/components/ProviderIcons/ProviderIcons.esm.js.map +1 -0
- package/dist/components/ReportsComponent/ReportsComponent.esm.js +42 -28
- package/dist/components/ReportsComponent/ReportsComponent.esm.js.map +1 -1
- package/dist/components/TopbarComponent/TopbarComponent.esm.js +1 -1
- package/dist/components/TopbarComponent/TopbarComponent.esm.js.map +1 -1
- package/package.json +5 -7
- package/dist/components/CostReportsTableComponent/TrendBarComponent.esm.js +0 -43
- package/dist/components/CostReportsTableComponent/TrendBarComponent.esm.js.map +0 -1
package/README.md
CHANGED
|
@@ -24,36 +24,49 @@ query period. Add the following configurations to your `app-config.yaml` file if
|
|
|
24
24
|
# note that infraWallet exists at the root level, it is not the same one for backend configurations
|
|
25
25
|
infraWallet:
|
|
26
26
|
settings:
|
|
27
|
-
defaultGroupBy: none # none by default, or provider, category, service, tag:<tag_key>
|
|
27
|
+
defaultGroupBy: none # none by default, or account, provider, category, service, tag:<tag_key>
|
|
28
28
|
defaultShowLastXMonths: 3 # 3 by default, or other numbers, we recommend it less than 12
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
#### Customizing the InfraWalletPage Title and Subtitle
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
query period. Add the following configurations to your `app-config.yaml` file if the default view needs to be changed.
|
|
33
|
+
By default, the `InfraWalletPage` component is configured in the `packages/app/src/App.tsx` file as follows:
|
|
35
34
|
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
```ts
|
|
36
|
+
<Route path="/infrawallet" element={<InfraWalletPage />} />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
To customize the title and subtitle of the InfraWalletPage, you can modify the route in the same file as shown below:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
<Route path="/infrawallet" element={<InfraWalletPage title="Custom title" subTitle="Custom subTitle" />} />
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
###
|
|
45
|
+
### Defining Provider Integrations
|
|
46
|
+
|
|
47
|
+
InfraWallet's configuration schema is specified in in [plugins/infrawallet-backend/config.d.ts](../infrawallet-backend/config.d.ts). To set up provider integrations, users must configure them in the `app-config.yaml` file located in the root directory.
|
|
48
|
+
|
|
49
|
+
#### AWS Integration
|
|
50
|
+
|
|
51
|
+
InfraWallet uses an IAM role to retrieve cost and usage data via the AWS Cost Explorer APIs. Before configuring InfraWallet, you must set up the necessary AWS IAM role and policy.
|
|
52
|
+
|
|
53
|
+
##### For Management Accounts
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
If you have a [management account](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_getting-started_concepts.html#management-account), this setup only needs to be done once within the management account. InfraWallet will then be able to retrieve cost data across all [member accounts](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_getting-started_concepts.html#member-account).
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
##### For Non-Management Accounts
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
If you're not using a [management account](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_getting-started_concepts.html#management-account), you'll need to create a role in each AWS account and configure trust relationships individually.
|
|
60
|
+
|
|
61
|
+
##### Required IAM Role Permissions
|
|
62
|
+
|
|
63
|
+
The IAM role must have the following permissions to access cost and usage data:
|
|
51
64
|
|
|
52
65
|
```json
|
|
53
66
|
{
|
|
54
67
|
"Statement": [
|
|
55
68
|
{
|
|
56
|
-
"Action": "ce:GetCostAndUsage",
|
|
69
|
+
"Action": ["ce:GetCostAndUsage", "ce:GetTags"],
|
|
57
70
|
"Effect": "Allow",
|
|
58
71
|
"Resource": "*",
|
|
59
72
|
"Sid": ""
|
|
@@ -63,37 +76,49 @@ For AWS, InfraWallet relies on an IAM role to fetch cost and usage data using AW
|
|
|
63
76
|
}
|
|
64
77
|
```
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
##### Configuration
|
|
80
|
+
|
|
81
|
+
Once the IAM roles and policies are in place, add the following configuration to your `app-config.yaml` file:
|
|
67
82
|
|
|
68
83
|
```yaml
|
|
69
84
|
backend:
|
|
70
85
|
infraWallet:
|
|
71
86
|
integrations:
|
|
72
87
|
aws:
|
|
73
|
-
- name: <
|
|
88
|
+
- name: <unique_name_of_this_integration>
|
|
74
89
|
accountId: '<12-digit_account_ID>' # quoted as a string
|
|
75
90
|
assumedRoleName: <name_of_the_AWS_IAM_role_to_be_assumed>
|
|
76
|
-
accessKeyId: <access_key_ID_of_AWS_IAM_user_that_assumes_the_role>
|
|
77
|
-
accessKeySecret: <access_key_secret_of_AWS_IAM_user_that_assumes_the_role>
|
|
91
|
+
accessKeyId: <access_key_ID_of_AWS_IAM_user_that_assumes_the_role> # optional, only needed when an IAM user is used to assume the role
|
|
92
|
+
accessKeySecret: <access_key_secret_of_AWS_IAM_user_that_assumes_the_role> # optional, only needed when an IAM user is used to assume the role
|
|
78
93
|
```
|
|
79
94
|
|
|
80
|
-
|
|
95
|
+
InfraWallet's AWS client is built using the AWS SDK for JavaScript. If both `accessKeyId` and `accessKeySecret` are provided in the configuration, the client will use the specified IAM user to assume the role. Otherwise, it follows the [default credential provider chain](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html#credchain).
|
|
96
|
+
|
|
97
|
+
#### Azure Integration
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
To manage Azure costs with InfraWallet, you need to register an application in Azure. Note that InfraWallet has been tested with subscription-level cost data only.
|
|
100
|
+
|
|
101
|
+
##### Steps:
|
|
102
|
+
|
|
103
|
+
1. After registering the application, navigate to the `Subscriptions` page and select the target subscription.
|
|
104
|
+
2. Go to the `Access control (IAM)` section and assign the `Cost Management Reader` role to the newly created application.
|
|
105
|
+
3. Generate a client secret for the application.
|
|
106
|
+
|
|
107
|
+
Add the following configurations to your `app-config.yaml` file:
|
|
83
108
|
|
|
84
109
|
```yaml
|
|
85
110
|
backend:
|
|
86
111
|
infraWallet:
|
|
87
112
|
integrations:
|
|
88
113
|
azure:
|
|
89
|
-
- name: <
|
|
114
|
+
- name: <unique_name_of_this_integration>
|
|
90
115
|
subscriptionId: <Azure_subscription_ID>
|
|
91
116
|
tenantId: <Azure_tenant_ID>
|
|
92
117
|
clientId: <Client_ID_of_the_created_application>
|
|
93
118
|
clientSecret: <Client_secret_of_the_created_application>
|
|
94
119
|
```
|
|
95
120
|
|
|
96
|
-
#### GCP
|
|
121
|
+
#### GCP Integration
|
|
97
122
|
|
|
98
123
|
InfraWallet relies on GCP Big Query to fetch cost data. This means that the billing data needs to be exported to a big query dataset, and a service account needs to be created for InfraWallet. The steps of exporting billing data to Big Query can be found [here](https://cloud.google.com/billing/docs/how-to/export-data-bigquery). Then, visit Google Cloud Console and navigate to the `IAM & Admin` section in the billing account. Click `Service Accounts`, and create a new service account. The service account needs to have `BigQuery Data Viewer` and `BigQuery Job User` roles. On the `Service Accounts` page, click the three dots (menu) in the `Actions` column for the newly created service account and select `Manage keys`. There click `Add key` -> `Create new key`, and use `JSON` as the format. Download the JSON key file and keep it safe.
|
|
99
124
|
|
|
@@ -104,16 +129,45 @@ backend:
|
|
|
104
129
|
infraWallet:
|
|
105
130
|
integrations:
|
|
106
131
|
gcp:
|
|
107
|
-
- name: <
|
|
132
|
+
- name: <unique_name_of_this_integration>
|
|
108
133
|
keyFilePath: <path_to_your_json_key_file> # if you run it in a k8s pod, you may need to create a secret and mount it to the pod
|
|
109
134
|
projectId: <GCP_project_that_your_big_query_dataset_belongs_to>
|
|
110
135
|
datasetId: <big_query_dataset_id>
|
|
111
136
|
tableId: <big_query_table_id>
|
|
112
137
|
```
|
|
113
138
|
|
|
139
|
+
#### Confluent Cloud Integration
|
|
140
|
+
|
|
141
|
+
To manage Confluent Cloud costs, you need to create an API key (Service account) for your Organization with the 'Cloud resource management' resource scope, you can find the documentation [here](https://docs.confluent.io/cloud/current/security/authenticate/workload-identities/service-accounts/api-keys/manage-api-keys.html#add-an-api-key). Once you have your API key details, add the following settings to `app-config.yaml`:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
backend:
|
|
145
|
+
infraWallet:
|
|
146
|
+
integrations:
|
|
147
|
+
confluent:
|
|
148
|
+
- name: <unique_name_of_this_integration>
|
|
149
|
+
apiKey: <your_api_key>
|
|
150
|
+
apiSecret: <your_api_key_secret>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### MongoDB Atlas Integration
|
|
154
|
+
|
|
155
|
+
To manage Mongo Atlas costs, you need to create an API key for your Organization with `Organization Billing Viewer` permission, you can find the documentation [here](https://www.mongodb.com/docs/atlas/configure-api-access/#std-label-about-org-api-keys). Once you have your API key details, add the following settings to `app-config.yaml`:
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
backend:
|
|
159
|
+
infraWallet:
|
|
160
|
+
integrations:
|
|
161
|
+
mongoatlas:
|
|
162
|
+
- name: <unique_name_of_this_integration>
|
|
163
|
+
orgId: <id_organization_mongo_atlas>
|
|
164
|
+
publicKey: <public_key_of_your_api_key>
|
|
165
|
+
privateKey: <private_key_of_your_api_key>
|
|
166
|
+
```
|
|
167
|
+
|
|
114
168
|
### Adjust Category Mappings if Needed
|
|
115
169
|
|
|
116
|
-
The category mappings are stored in the plugin's database. If there is no mapping found in the DB when initializing the plugin, the default mappings will be used. The default mappings can be found in the [plugins/infrawallet-backend/seeds/init.js](
|
|
170
|
+
The category mappings are stored in the plugin's database. If there is no mapping found in the DB when initializing the plugin, the default mappings will be used. The default mappings can be found in the [plugins/infrawallet-backend/seeds/init.js](../infrawallet-backend/seeds/init.js) file. You can adjust this seed file to fit your needs, or update the database directly later on.
|
|
117
171
|
|
|
118
172
|
### Install the Plugin
|
|
119
173
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfraWalletApi.esm.js","sources":["../../src/api/InfraWalletApi.ts"],"sourcesContent":["import { createApiRef } from '@backstage/core-plugin-api';\nimport {\n CostReportsResponse,\n GetWalletResponse,\n MetricConfigsResponse,\n MetricSetting,\n MetricsResponse,\n MetricsSettingResponse,\n} from './types';\n\n/** @public */\nexport const infraWalletApiRef = createApiRef<InfraWalletApi>({\n id: 'plugin.infrawallet',\n});\n\n/** @public */\nexport interface InfraWalletApi {\n getCostReports(\n filters: string,\n groups: string,\n granularity: string,\n startTime: Date,\n endTime: Date,\n ): Promise<CostReportsResponse>;\n getMetrics(walletName: string, granularity: string, startTime: Date, endTime: Date): Promise<MetricsResponse>;\n getMetricConfigs(): Promise<MetricConfigsResponse>;\n getWalletMetricsSetting(walletName: string): Promise<MetricsSettingResponse>;\n updateWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ updated: boolean; status: number }>;\n deleteWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ deleted: boolean; status: number }>;\n getWalletByName(walletName: string): Promise<GetWalletResponse>;\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"InfraWalletApi.esm.js","sources":["../../src/api/InfraWalletApi.ts"],"sourcesContent":["import { createApiRef } from '@backstage/core-plugin-api';\nimport {\n CostReportsResponse,\n GetWalletResponse,\n MetricConfigsResponse,\n MetricSetting,\n MetricsResponse,\n MetricsSettingResponse,\n Tag,\n TagResponse,\n} from './types';\n\n/** @public */\nexport const infraWalletApiRef = createApiRef<InfraWalletApi>({\n id: 'plugin.infrawallet',\n});\n\n/** @public */\nexport interface InfraWalletApi {\n getCostReports(\n filters: string,\n tags: Tag[],\n groups: string,\n granularity: string,\n startTime: Date,\n endTime: Date,\n ): Promise<CostReportsResponse>;\n getTagKeys(provider: string, startTime: Date, endTime: Date): Promise<TagResponse>;\n getTagValues(tag: Tag, startTime: Date, endTime: Date): Promise<TagResponse>;\n getMetrics(walletName: string, granularity: string, startTime: Date, endTime: Date): Promise<MetricsResponse>;\n getMetricConfigs(): Promise<MetricConfigsResponse>;\n getWalletMetricsSetting(walletName: string): Promise<MetricsSettingResponse>;\n updateWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ updated: boolean; status: number }>;\n deleteWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ deleted: boolean; status: number }>;\n getWalletByName(walletName: string): Promise<GetWalletResponse>;\n}\n"],"names":[],"mappings":";;AAaO,MAAM,oBAAoB,YAA6B,CAAA;AAAA,EAC5D,EAAI,EAAA,oBAAA;AACN,CAAC;;;;"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
|
+
import { tagsToString } from './functions.esm.js';
|
|
2
3
|
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -35,8 +36,17 @@ class InfraWalletApiClient {
|
|
|
35
36
|
}
|
|
36
37
|
return await response.json();
|
|
37
38
|
}
|
|
38
|
-
async getCostReports(filters, groups, granularity, startTime, endTime) {
|
|
39
|
-
const
|
|
39
|
+
async getCostReports(filters, tags, groups, granularity, startTime, endTime) {
|
|
40
|
+
const tagsString = tagsToString(tags);
|
|
41
|
+
const url = `api/infrawallet/reports?&filters=${filters}&tags=${tagsString}&groups=${groups}&granularity=${granularity}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;
|
|
42
|
+
return await this.request(url);
|
|
43
|
+
}
|
|
44
|
+
async getTagKeys(provider, startTime, endTime) {
|
|
45
|
+
const url = `api/infrawallet/tag-keys?provider=${provider}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;
|
|
46
|
+
return await this.request(url);
|
|
47
|
+
}
|
|
48
|
+
async getTagValues(tag, startTime, endTime) {
|
|
49
|
+
const url = `api/infrawallet/tag-values?provider=${tag.provider}&tag=${tag.key}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;
|
|
40
50
|
return await this.request(url);
|
|
41
51
|
}
|
|
42
52
|
async getWalletByName(walletName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfraWalletApiClient.esm.js","sources":["../../src/api/InfraWalletApiClient.ts"],"sourcesContent":["import { ConfigApi, IdentityApi } from '@backstage/core-plugin-api';\nimport fetch from 'node-fetch';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport {\n CostReportsResponse,\n GetWalletResponse,\n MetricConfigsResponse,\n MetricSetting,\n MetricsResponse,\n MetricsSettingResponse,\n} from './types';\n\n/** @public */\nexport class InfraWalletApiClient implements InfraWalletApi {\n private readonly identityApi: IdentityApi;\n private readonly backendUrl: string;\n\n constructor(options: { identityApi: IdentityApi; configApi: ConfigApi }) {\n this.identityApi = options.identityApi;\n this.backendUrl = options.configApi.getString('backend.baseUrl');\n }\n\n async request(path: string, method?: string, payload?: Record<string, string | undefined>) {\n const url = `${this.backendUrl}/${path}`;\n const { token: idToken } = await this.identityApi.getCredentials();\n const headers: Record<string, string> = idToken ? { Authorization: `Bearer ${idToken}` } : {};\n\n if (method !== undefined && method !== 'GET') {\n headers['Content-Type'] = 'application/json';\n }\n\n const request: any = {\n headers: headers,\n method: method ?? 'GET',\n };\n\n if (payload) {\n request.body = JSON.stringify(payload);\n }\n\n const response = await fetch(url, request);\n\n if (!response.ok) {\n const res = await response.text();\n const message = `Request failed with ${response.status} ${response.statusText}, ${res}`;\n throw new Error(message);\n }\n\n return await response.json();\n }\n\n async getCostReports(\n filters: string,\n groups: string,\n granularity: string,\n startTime: Date,\n endTime: Date,\n ): Promise<CostReportsResponse> {\n const url = `api/infrawallet/reports?&filters=${filters}&groups=${groups}&granularity=${granularity}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;\n return await this.request(url);\n }\n\n async getWalletByName(walletName: string): Promise<GetWalletResponse> {\n const url = `api/infrawallet/${walletName}`;\n return await this.request(url);\n }\n\n async getMetrics(walletName: string, granularity: string, startTime: Date, endTime: Date): Promise<MetricsResponse> {\n const url = `api/infrawallet/${walletName}/metrics?&granularity=${granularity}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;\n return await this.request(url);\n }\n\n async getMetricConfigs(): Promise<MetricConfigsResponse> {\n const url = 'api/infrawallet/metric/metric_configs';\n return await this.request(url);\n }\n\n async getWalletMetricsSetting(walletName: string): Promise<MetricsSettingResponse> {\n const url = `api/infrawallet/${walletName}/metrics_setting`;\n return await this.request(url);\n }\n async updateWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ updated: boolean; status: number }> {\n const url = `api/infrawallet/${walletName}/metrics_setting`;\n return await this.request(url, 'PUT', metricSetting);\n }\n\n async deleteWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ deleted: boolean; status: number }> {\n const url = `api/infrawallet/${walletName}/metrics_setting`;\n return await this.request(url, 'DELETE', metricSetting);\n }\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"InfraWalletApiClient.esm.js","sources":["../../src/api/InfraWalletApiClient.ts"],"sourcesContent":["import { ConfigApi, IdentityApi } from '@backstage/core-plugin-api';\nimport fetch from 'node-fetch';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport {\n CostReportsResponse,\n GetWalletResponse,\n MetricConfigsResponse,\n MetricSetting,\n MetricsResponse,\n MetricsSettingResponse,\n Tag,\n TagResponse,\n} from './types';\nimport { tagsToString } from './functions';\n\n/** @public */\nexport class InfraWalletApiClient implements InfraWalletApi {\n private readonly identityApi: IdentityApi;\n private readonly backendUrl: string;\n\n constructor(options: { identityApi: IdentityApi; configApi: ConfigApi }) {\n this.identityApi = options.identityApi;\n this.backendUrl = options.configApi.getString('backend.baseUrl');\n }\n\n async request(path: string, method?: string, payload?: Record<string, string | undefined>) {\n const url = `${this.backendUrl}/${path}`;\n const { token: idToken } = await this.identityApi.getCredentials();\n const headers: Record<string, string> = idToken ? { Authorization: `Bearer ${idToken}` } : {};\n\n if (method !== undefined && method !== 'GET') {\n headers['Content-Type'] = 'application/json';\n }\n\n const request: any = {\n headers: headers,\n method: method ?? 'GET',\n };\n\n if (payload) {\n request.body = JSON.stringify(payload);\n }\n\n const response = await fetch(url, request);\n\n if (!response.ok) {\n const res = await response.text();\n const message = `Request failed with ${response.status} ${response.statusText}, ${res}`;\n throw new Error(message);\n }\n\n return await response.json();\n }\n\n async getCostReports(\n filters: string,\n tags: Tag[],\n groups: string,\n granularity: string,\n startTime: Date,\n endTime: Date,\n ): Promise<CostReportsResponse> {\n const tagsString = tagsToString(tags);\n const url = `api/infrawallet/reports?&filters=${filters}&tags=${tagsString}&groups=${groups}&granularity=${granularity}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;\n return await this.request(url);\n }\n\n async getTagKeys(provider: string, startTime: Date, endTime: Date): Promise<TagResponse> {\n const url = `api/infrawallet/tag-keys?provider=${provider}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;\n return await this.request(url);\n }\n\n async getTagValues(tag: Tag, startTime: Date, endTime: Date): Promise<TagResponse> {\n const url = `api/infrawallet/tag-values?provider=${tag.provider}&tag=${\n tag.key\n }&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;\n return await this.request(url);\n }\n\n async getWalletByName(walletName: string): Promise<GetWalletResponse> {\n const url = `api/infrawallet/${walletName}`;\n return await this.request(url);\n }\n\n async getMetrics(walletName: string, granularity: string, startTime: Date, endTime: Date): Promise<MetricsResponse> {\n const url = `api/infrawallet/${walletName}/metrics?&granularity=${granularity}&startTime=${startTime.getTime()}&endTime=${endTime.getTime()}`;\n return await this.request(url);\n }\n\n async getMetricConfigs(): Promise<MetricConfigsResponse> {\n const url = 'api/infrawallet/metric/metric_configs';\n return await this.request(url);\n }\n\n async getWalletMetricsSetting(walletName: string): Promise<MetricsSettingResponse> {\n const url = `api/infrawallet/${walletName}/metrics_setting`;\n return await this.request(url);\n }\n async updateWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ updated: boolean; status: number }> {\n const url = `api/infrawallet/${walletName}/metrics_setting`;\n return await this.request(url, 'PUT', metricSetting);\n }\n\n async deleteWalletMetricSetting(\n walletName: string,\n metricSetting: MetricSetting,\n ): Promise<{ deleted: boolean; status: number }> {\n const url = `api/infrawallet/${walletName}/metrics_setting`;\n return await this.request(url, 'DELETE', metricSetting);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parse, subMonths, format } from 'date-fns';
|
|
1
|
+
import { parse, subMonths, format, subDays } from 'date-fns';
|
|
2
2
|
import { reduce } from 'lodash';
|
|
3
3
|
import moment from 'moment';
|
|
4
|
+
import humanFormat from 'human-format';
|
|
4
5
|
|
|
5
6
|
const mergeCostReports = (reports, threshold) => {
|
|
6
7
|
const totalCosts = [];
|
|
@@ -64,6 +65,7 @@ const aggregateCostReports = (reports, aggregatedBy) => {
|
|
|
64
65
|
if (!accumulator[keyName]) {
|
|
65
66
|
accumulator[keyName] = {
|
|
66
67
|
id: keyName,
|
|
68
|
+
provider: report.provider,
|
|
67
69
|
reports: {}
|
|
68
70
|
};
|
|
69
71
|
if (aggregatedBy !== void 0) {
|
|
@@ -91,9 +93,8 @@ const getReportKeyAndValues = (reports) => {
|
|
|
91
93
|
if (!excludedKeys.includes(key)) {
|
|
92
94
|
if (keyValueSets[key] === void 0) {
|
|
93
95
|
keyValueSets[key] = /* @__PURE__ */ new Set();
|
|
94
|
-
} else {
|
|
95
|
-
keyValueSets[key].add(report[key]);
|
|
96
96
|
}
|
|
97
|
+
keyValueSets[key].add(report[key]);
|
|
97
98
|
}
|
|
98
99
|
});
|
|
99
100
|
});
|
|
@@ -103,9 +104,38 @@ const getReportKeyAndValues = (reports) => {
|
|
|
103
104
|
});
|
|
104
105
|
return keyValues;
|
|
105
106
|
};
|
|
107
|
+
const extractProvider = (input) => {
|
|
108
|
+
let provider = void 0;
|
|
109
|
+
if (input && input.indexOf("/") !== -1) {
|
|
110
|
+
provider = input.split("/")[0];
|
|
111
|
+
}
|
|
112
|
+
return provider;
|
|
113
|
+
};
|
|
114
|
+
const extractAccountInfo = (input) => {
|
|
115
|
+
const regex = /^(.*?)\s*\(([^)]+)\)$/;
|
|
116
|
+
const match = input.match(regex);
|
|
117
|
+
if (match) {
|
|
118
|
+
const accountName = match[1];
|
|
119
|
+
const accountId = match[2];
|
|
120
|
+
return { accountName, accountId };
|
|
121
|
+
}
|
|
122
|
+
return { accountName: input };
|
|
123
|
+
};
|
|
124
|
+
function tagExists(tags, targetTag) {
|
|
125
|
+
return tags.some(
|
|
126
|
+
(tag) => tag.provider === targetTag.provider && tag.key === targetTag.key && tag.value === targetTag.value
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const tagsToString = (tags) => {
|
|
130
|
+
if (tags.length === 0) {
|
|
131
|
+
return "()";
|
|
132
|
+
}
|
|
133
|
+
const keyValuePairs = tags.map((tag) => `${tag.provider}:${tag.key}=${tag.value}`);
|
|
134
|
+
return `(${keyValuePairs.join(" OR ")})`;
|
|
135
|
+
};
|
|
106
136
|
const getAllReportTags = (reports) => {
|
|
107
137
|
const tags = /* @__PURE__ */ new Set();
|
|
108
|
-
const reservedKeys = ["id", "
|
|
138
|
+
const reservedKeys = ["id", "account", "service", "category", "provider", "reports"];
|
|
109
139
|
reports.forEach((report) => {
|
|
110
140
|
Object.keys(report).forEach((key) => {
|
|
111
141
|
if (reservedKeys.indexOf(key) === -1) {
|
|
@@ -120,6 +150,11 @@ const getPreviousMonth = (month) => {
|
|
|
120
150
|
const previousMonth = subMonths(date, 1);
|
|
121
151
|
return format(previousMonth, "yyyy-MM");
|
|
122
152
|
};
|
|
153
|
+
const getPreviousDay = (day) => {
|
|
154
|
+
const date = parse(day, "yyyy-MM-dd", /* @__PURE__ */ new Date());
|
|
155
|
+
const previousDay = subDays(date, 1);
|
|
156
|
+
return format(previousDay, "yyyy-MM-dd");
|
|
157
|
+
};
|
|
123
158
|
const getPeriodStrings = (granularity, startTime, endTime) => {
|
|
124
159
|
const result = [];
|
|
125
160
|
const current = moment(startTime);
|
|
@@ -134,6 +169,14 @@ const getPeriodStrings = (granularity, startTime, endTime) => {
|
|
|
134
169
|
}
|
|
135
170
|
return result;
|
|
136
171
|
};
|
|
172
|
+
const formatNumber = (number) => {
|
|
173
|
+
const customScale = humanFormat.Scale.create(["", "K", "M", "B"], 1e3);
|
|
174
|
+
return humanFormat(number, {
|
|
175
|
+
scale: customScale,
|
|
176
|
+
separator: "",
|
|
177
|
+
decimals: 2
|
|
178
|
+
});
|
|
179
|
+
};
|
|
137
180
|
|
|
138
|
-
export { aggregateCostReports, filterCostReports, getAllReportTags, getPeriodStrings, getPreviousMonth, getReportKeyAndValues, mergeCostReports };
|
|
181
|
+
export { aggregateCostReports, extractAccountInfo, extractProvider, filterCostReports, formatNumber, getAllReportTags, getPeriodStrings, getPreviousDay, getPreviousMonth, getReportKeyAndValues, mergeCostReports, tagExists, tagsToString };
|
|
139
182
|
//# sourceMappingURL=functions.esm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"functions.esm.js","sources":["../../src/api/functions.ts"],"sourcesContent":["import { format, parse, subMonths } from 'date-fns';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { Report, Filters } from './types';\n\nexport const mergeCostReports = (reports: Report[], threshold: number): Report[] => {\n const totalCosts: { id: string; total: number }[] = [];\n reports.forEach(report => {\n let total = 0;\n Object.values(report.reports).forEach(v => {\n total += v as number;\n });\n totalCosts.push({ id: report.id, total: total });\n });\n const sortedTotalCosts = totalCosts.sort((a, b) => b.total - a.total);\n const idsToBeKept = sortedTotalCosts.slice(0, threshold).map(v => v.id);\n\n const mergedReports = reduce(\n reports,\n (accumulator: { [key: string]: Report }, report) => {\n let keyName = 'others';\n if (idsToBeKept.includes(report.id)) {\n keyName = report.id;\n }\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n reports: {},\n };\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {},\n );\n\n return Object.values(mergedReports);\n};\n\nexport const filterCostReports = (reports: Report[], filters: Filters): Report[] => {\n const filteredReports = reports.filter(report => {\n let match = true;\n Object.keys(filters).forEach(key => {\n if (filters[key].length > 0 && !filters[key].includes(report[key] as string)) {\n match = false;\n }\n });\n return match;\n });\n\n return filteredReports;\n};\n\nexport const aggregateCostReports = (reports: Report[], aggregatedBy?: string): Report[] => {\n const aggregatedReports: { [key: string]: Report } = reduce(\n reports,\n (accumulator, report) => {\n let keyName: string = 'no value';\n if (aggregatedBy && aggregatedBy in report) {\n keyName = report[aggregatedBy] as string;\n } else if (aggregatedBy === 'none') {\n keyName = 'Total';\n }\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n reports: {},\n } as {\n id: string;\n reports: { [key: string]: number };\n [key: string]: any;\n };\n\n if (aggregatedBy !== undefined) {\n accumulator[keyName][aggregatedBy] = keyName;\n }\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {} as { [key: string]: Report },\n );\n return Object.values(aggregatedReports);\n};\n\nexport const getReportKeyAndValues = (reports: Report[]): { [key: string]: string[] } => {\n const excludedKeys = ['id', 'reports'];\n const keyValueSets: { [key: string]: Set<string> } = {};\n reports.forEach(report => {\n Object.keys(report).forEach(key => {\n if (!excludedKeys.includes(key)) {\n if (keyValueSets[key] === undefined) {\n keyValueSets[key] = new Set<string>();\n } else {\n keyValueSets[key].add(report[key] as string);\n }\n }\n });\n });\n\n const keyValues: { [key: string]: string[] } = {};\n Object.keys(keyValueSets).forEach((key: string) => {\n keyValues[key] = Array.from(keyValueSets[key]);\n });\n return keyValues;\n};\n\nexport const getAllReportTags = (reports: Report[]): string[] => {\n const tags = new Set<string>();\n const reservedKeys = ['id', 'name', 'service', 'category', 'provider', 'reports'];\n reports.forEach(report => {\n Object.keys(report).forEach(key => {\n if (reservedKeys.indexOf(key) === -1) {\n tags.add(key);\n }\n });\n });\n return Array.from(tags);\n};\n\nexport const getPreviousMonth = (month: string): string => {\n const date = parse(month, 'yyyy-MM', new Date());\n const previousMonth = subMonths(date, 1);\n return format(previousMonth, 'yyyy-MM');\n};\n\nexport const getPeriodStrings = (granularity: string, startTime: Date, endTime: Date): string[] => {\n const result: string[] = [];\n const current = moment(startTime);\n\n while (current.isSameOrBefore(endTime) && current.isSameOrBefore(moment())) {\n if (granularity === 'monthly') {\n result.push(current.format('YYYY-MM'));\n current.add(1, 'months');\n } else {\n result.push(current.format('YYYY-MM-DD'));\n current.add(1, 'days');\n }\n }\n\n return result;\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"functions.esm.js","sources":["../../src/api/functions.ts"],"sourcesContent":["import { format, parse, subDays, subMonths } from 'date-fns';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { Report, Filters, Tag } from './types';\nimport humanFormat from 'human-format';\n\nexport const mergeCostReports = (reports: Report[], threshold: number): Report[] => {\n const totalCosts: { id: string; total: number }[] = [];\n reports.forEach(report => {\n let total = 0;\n Object.values(report.reports).forEach(v => {\n total += v as number;\n });\n totalCosts.push({ id: report.id, total: total });\n });\n const sortedTotalCosts = totalCosts.sort((a, b) => b.total - a.total);\n const idsToBeKept = sortedTotalCosts.slice(0, threshold).map(v => v.id);\n\n const mergedReports = reduce(\n reports,\n (accumulator: { [key: string]: Report }, report) => {\n let keyName = 'others';\n if (idsToBeKept.includes(report.id)) {\n keyName = report.id;\n }\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n reports: {},\n };\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {},\n );\n\n return Object.values(mergedReports);\n};\n\nexport const filterCostReports = (reports: Report[], filters: Filters): Report[] => {\n const filteredReports = reports.filter(report => {\n let match = true;\n Object.keys(filters).forEach(key => {\n if (filters[key].length > 0 && !filters[key].includes(report[key] as string)) {\n match = false;\n }\n });\n return match;\n });\n\n return filteredReports;\n};\n\nexport const aggregateCostReports = (reports: Report[], aggregatedBy?: string): Report[] => {\n const aggregatedReports: { [key: string]: Report } = reduce(\n reports,\n (accumulator, report) => {\n let keyName: string = 'no value';\n if (aggregatedBy && aggregatedBy in report) {\n keyName = report[aggregatedBy] as string;\n } else if (aggregatedBy === 'none') {\n keyName = 'Total';\n }\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n provider: report.provider,\n reports: {},\n } as {\n id: string;\n reports: { [key: string]: number };\n [key: string]: any;\n };\n\n if (aggregatedBy !== undefined) {\n accumulator[keyName][aggregatedBy] = keyName;\n }\n }\n\n Object.keys(report.reports).forEach(key => {\n if (accumulator[keyName].reports[key]) {\n accumulator[keyName].reports[key] += report.reports[key];\n } else {\n accumulator[keyName].reports[key] = report.reports[key];\n }\n });\n return accumulator;\n },\n {} as { [key: string]: Report },\n );\n return Object.values(aggregatedReports);\n};\n\nexport const getReportKeyAndValues = (reports: Report[]): { [key: string]: string[] } => {\n const excludedKeys = ['id', 'reports'];\n const keyValueSets: { [key: string]: Set<string> } = {};\n reports.forEach(report => {\n Object.keys(report).forEach(key => {\n if (!excludedKeys.includes(key)) {\n if (keyValueSets[key] === undefined) {\n keyValueSets[key] = new Set<string>();\n }\n\n keyValueSets[key].add(report[key] as string);\n }\n });\n });\n\n const keyValues: { [key: string]: string[] } = {};\n Object.keys(keyValueSets).forEach((key: string) => {\n keyValues[key] = Array.from(keyValueSets[key]);\n });\n return keyValues;\n};\n\nexport const extractProvider = (input: string): string | undefined => {\n let provider = undefined;\n if (input && input.indexOf('/') !== -1) {\n provider = input.split('/')[0];\n }\n\n return provider;\n};\n\nexport const extractAccountInfo = (input: string): { accountName: string; accountId?: string } => {\n // try to match format: accountName (accountId), e.g. aws-dev (123456789012)\n const regex = /^(.*?)\\s*\\(([^)]+)\\)$/;\n const match = input.match(regex);\n\n if (match) {\n const accountName = match[1];\n const accountId = match[2];\n return { accountName: accountName, accountId: accountId };\n }\n\n return { accountName: input };\n};\n\n// check if targetTag exists in tags\nexport function tagExists(tags: Tag[], targetTag: Tag): boolean {\n return tags.some(\n tag => tag.provider === targetTag.provider && tag.key === targetTag.key && tag.value === targetTag.value,\n );\n}\n\n// convert Tag array to (provider1:key1=value1 OR provider2:key2=value2) format\nexport const tagsToString = (tags: Tag[]): string => {\n if (tags.length === 0) {\n return '()';\n }\n\n const keyValuePairs = tags.map(tag => `${tag.provider}:${tag.key}=${tag.value}`);\n return `(${keyValuePairs.join(' OR ')})`;\n};\n\nexport const getAllReportTags = (reports: Report[]): string[] => {\n const tags = new Set<string>();\n const reservedKeys = ['id', 'account', 'service', 'category', 'provider', 'reports'];\n reports.forEach(report => {\n Object.keys(report).forEach(key => {\n if (reservedKeys.indexOf(key) === -1) {\n tags.add(key);\n }\n });\n });\n return Array.from(tags);\n};\n\nexport const getPreviousMonth = (month: string): string => {\n const date = parse(month, 'yyyy-MM', new Date());\n const previousMonth = subMonths(date, 1);\n return format(previousMonth, 'yyyy-MM');\n};\n\nexport const getPreviousDay = (day: string): string => {\n const date = parse(day, 'yyyy-MM-dd', new Date());\n const previousDay = subDays(date, 1);\n return format(previousDay, 'yyyy-MM-dd');\n};\n\nexport const getPeriodStrings = (granularity: string, startTime: Date, endTime: Date): string[] => {\n const result: string[] = [];\n const current = moment(startTime);\n\n while (current.isSameOrBefore(endTime) && current.isSameOrBefore(moment())) {\n if (granularity === 'monthly') {\n result.push(current.format('YYYY-MM'));\n current.add(1, 'months');\n } else {\n result.push(current.format('YYYY-MM-DD'));\n current.add(1, 'days');\n }\n }\n\n return result;\n};\n\nexport const formatNumber = (number: number): string => {\n const customScale = humanFormat.Scale.create(['', 'K', 'M', 'B'], 1000);\n return humanFormat(number, {\n scale: customScale,\n separator: '',\n decimals: 2,\n });\n};\n"],"names":[],"mappings":"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"}
|