@electrolux-oss/plugin-infrawallet-backend 0.1.7 → 0.1.9
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/config.d.ts +37 -0
- package/dist/index.cjs.js +495 -24
- package/dist/index.cjs.js.map +1 -1
- package/migrations/20240807123438_wallet-and-metric-configurations.js +49 -0
- package/mock/mock_response.json +10654 -0
- package/package.json +4 -1
- package/seeds/default_category_mappings.js +9 -0
- package/seeds/default_wallet.js +12 -0
package/config.d.ts
CHANGED
|
@@ -41,6 +41,43 @@ export interface Config {
|
|
|
41
41
|
tags?: string[];
|
|
42
42
|
},
|
|
43
43
|
];
|
|
44
|
+
mock?: [
|
|
45
|
+
{
|
|
46
|
+
name: string;
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
};
|
|
50
|
+
metricProviders?: {
|
|
51
|
+
datadog?: [
|
|
52
|
+
{
|
|
53
|
+
name: string;
|
|
54
|
+
/**
|
|
55
|
+
* @visibility secret
|
|
56
|
+
*/
|
|
57
|
+
apiKey: string;
|
|
58
|
+
/**
|
|
59
|
+
* @visibility secret
|
|
60
|
+
*/
|
|
61
|
+
applicationKey: string;
|
|
62
|
+
ddSite: string;
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
grafanaCloud?: [
|
|
66
|
+
{
|
|
67
|
+
name: string;
|
|
68
|
+
url: string;
|
|
69
|
+
datasourceUid: string;
|
|
70
|
+
/**
|
|
71
|
+
* @visibility secret
|
|
72
|
+
*/
|
|
73
|
+
token: string;
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
mock?: [
|
|
77
|
+
{
|
|
78
|
+
name: string;
|
|
79
|
+
},
|
|
80
|
+
];
|
|
44
81
|
};
|
|
45
82
|
};
|
|
46
83
|
};
|
package/dist/index.cjs.js
CHANGED
|
@@ -14,39 +14,86 @@ var armCostmanagement = require('@azure/arm-costmanagement');
|
|
|
14
14
|
var coreRestPipeline = require('@azure/core-rest-pipeline');
|
|
15
15
|
var identity = require('@azure/identity');
|
|
16
16
|
var bigquery = require('@google-cloud/bigquery');
|
|
17
|
+
var datadogApiClient = require('@datadog/datadog-api-client');
|
|
18
|
+
var fetch = require('node-fetch');
|
|
19
|
+
var fs = require('fs');
|
|
20
|
+
var upath = require('upath');
|
|
17
21
|
|
|
18
22
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
19
23
|
|
|
24
|
+
function _interopNamespaceCompat(e) {
|
|
25
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
26
|
+
var n = Object.create(null);
|
|
27
|
+
if (e) {
|
|
28
|
+
Object.keys(e).forEach(function (k) {
|
|
29
|
+
if (k !== 'default') {
|
|
30
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
31
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get: function () { return e[k]; }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
n.default = e;
|
|
39
|
+
return Object.freeze(n);
|
|
40
|
+
}
|
|
41
|
+
|
|
20
42
|
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
21
43
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
22
44
|
var moment__default = /*#__PURE__*/_interopDefaultCompat(moment);
|
|
45
|
+
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
46
|
+
var upath__namespace = /*#__PURE__*/_interopNamespaceCompat(upath);
|
|
47
|
+
|
|
48
|
+
async function getWallet(database, walletName) {
|
|
49
|
+
const client = await database.getClient();
|
|
50
|
+
const result = await client("wallets").where("name", walletName).first();
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
async function getWalletMetricSettings(database, walletName) {
|
|
54
|
+
const client = await database.getClient();
|
|
55
|
+
const metricSettings = await client.select("business_metrics.*").from("business_metrics").where("wallets.name", walletName).join("wallets", "business_metrics.wallet_id", "=", "wallets.id");
|
|
56
|
+
return metricSettings;
|
|
57
|
+
}
|
|
58
|
+
async function updateOrInsertWalletMetricSetting(database, walletSetting) {
|
|
59
|
+
const client = await database.getClient();
|
|
60
|
+
const result = await client("business_metrics").insert(walletSetting).onConflict("id").merge();
|
|
61
|
+
if (result[0] > 0) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
async function deleteWalletMetricSetting(database, walletSetting) {
|
|
67
|
+
const client = await database.getClient();
|
|
68
|
+
const result = await client("business_metrics").where("id", walletSetting.id).del();
|
|
69
|
+
if (result > 0) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
23
74
|
|
|
24
75
|
async function getCategoryMappings(database, provider) {
|
|
25
76
|
const result = {};
|
|
26
77
|
const client = await database.getClient();
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
} else {
|
|
34
|
-
mapping.cloud_service_names.forEach((service) => {
|
|
35
|
-
result[service] = mapping.category;
|
|
36
|
-
});
|
|
78
|
+
const defaultMappings = await client.where({ provider: provider.toLowerCase() }).select().from("category_mappings_default");
|
|
79
|
+
defaultMappings.forEach((mapping) => {
|
|
80
|
+
let services = mapping.cloud_service_names;
|
|
81
|
+
if (typeof services === "string") {
|
|
82
|
+
services = JSON.parse(services);
|
|
37
83
|
}
|
|
84
|
+
services.forEach((service) => {
|
|
85
|
+
result[service] = mapping.category;
|
|
86
|
+
});
|
|
38
87
|
});
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
} else {
|
|
46
|
-
mapping.cloud_service_names.forEach((service) => {
|
|
47
|
-
result[service] = mapping.category;
|
|
48
|
-
});
|
|
88
|
+
const overrideMappings = await client.where({ provider }).select().from("category_mappings_override");
|
|
89
|
+
overrideMappings.forEach((mapping) => {
|
|
90
|
+
let services = mapping.cloud_service_names;
|
|
91
|
+
if (typeof services === "string") {
|
|
92
|
+
services = JSON.parse(services);
|
|
49
93
|
}
|
|
94
|
+
services.forEach((service) => {
|
|
95
|
+
result[service] = mapping.category;
|
|
96
|
+
});
|
|
50
97
|
});
|
|
51
98
|
return result;
|
|
52
99
|
}
|
|
@@ -54,6 +101,14 @@ function getCategoryByServiceName(serviceName, categoryMappings) {
|
|
|
54
101
|
if (serviceName in categoryMappings) {
|
|
55
102
|
return categoryMappings[serviceName];
|
|
56
103
|
}
|
|
104
|
+
for (const service in categoryMappings) {
|
|
105
|
+
if (Object.hasOwn(categoryMappings, service)) {
|
|
106
|
+
const regex = new RegExp(service);
|
|
107
|
+
if (regex.test(serviceName)) {
|
|
108
|
+
return categoryMappings[service];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
57
112
|
return "Uncategorized";
|
|
58
113
|
}
|
|
59
114
|
async function getReportsFromCache(cache, provider, configKey, query) {
|
|
@@ -69,6 +124,20 @@ async function getReportsFromCache(cache, provider, configKey, query) {
|
|
|
69
124
|
const cachedCosts = await cache.get(cacheKey);
|
|
70
125
|
return cachedCosts;
|
|
71
126
|
}
|
|
127
|
+
async function getMetricsFromCache(cache, provider, configKey, query) {
|
|
128
|
+
const cacheKey = [
|
|
129
|
+
provider,
|
|
130
|
+
configKey,
|
|
131
|
+
query.name,
|
|
132
|
+
query.query,
|
|
133
|
+
query.granularity,
|
|
134
|
+
query.startTime,
|
|
135
|
+
query.endTime
|
|
136
|
+
].join("_");
|
|
137
|
+
const crypto = require("crypto");
|
|
138
|
+
const cachedMetrics = await cache.get(crypto.createHash("md5").update(cacheKey).digest("hex"));
|
|
139
|
+
return cachedMetrics;
|
|
140
|
+
}
|
|
72
141
|
async function setReportsToCache(cache, reports, provider, configKey, query, ttl) {
|
|
73
142
|
const cacheKey = [
|
|
74
143
|
provider,
|
|
@@ -83,6 +152,21 @@ async function setReportsToCache(cache, reports, provider, configKey, query, ttl
|
|
|
83
152
|
ttl: ttl
|
|
84
153
|
});
|
|
85
154
|
}
|
|
155
|
+
async function setMetricsToCache(cache, metrics, provider, configKey, query, ttl) {
|
|
156
|
+
const cacheKey = [
|
|
157
|
+
provider,
|
|
158
|
+
configKey,
|
|
159
|
+
query.name,
|
|
160
|
+
query.query,
|
|
161
|
+
query.granularity,
|
|
162
|
+
query.startTime,
|
|
163
|
+
query.endTime
|
|
164
|
+
].join("_");
|
|
165
|
+
const crypto = require("crypto");
|
|
166
|
+
await cache.set(crypto.createHash("md5").update(cacheKey).digest("hex"), metrics, {
|
|
167
|
+
ttl: ttl
|
|
168
|
+
});
|
|
169
|
+
}
|
|
86
170
|
|
|
87
171
|
class InfraWalletClient {
|
|
88
172
|
constructor(providerName, config, database, cache, logger) {
|
|
@@ -541,10 +625,305 @@ class GCPClient extends InfraWalletClient {
|
|
|
541
625
|
}
|
|
542
626
|
}
|
|
543
627
|
|
|
544
|
-
|
|
628
|
+
class MetricProvider {
|
|
629
|
+
constructor(providerName, config, database, cache, logger) {
|
|
630
|
+
this.providerName = providerName;
|
|
631
|
+
this.config = config;
|
|
632
|
+
this.database = database;
|
|
633
|
+
this.cache = cache;
|
|
634
|
+
this.logger = logger;
|
|
635
|
+
}
|
|
636
|
+
async getMetrics(query) {
|
|
637
|
+
const conf = this.config.getOptionalConfigArray(
|
|
638
|
+
`backend.infraWallet.metricProviders.${this.providerName.toLowerCase()}`
|
|
639
|
+
);
|
|
640
|
+
if (!conf) {
|
|
641
|
+
return { metrics: [], errors: [] };
|
|
642
|
+
}
|
|
643
|
+
const promises = [];
|
|
644
|
+
const results = [];
|
|
645
|
+
const errors = [];
|
|
646
|
+
for (const c of conf) {
|
|
647
|
+
const configName = c.getString("name");
|
|
648
|
+
const client = await this.initProviderClient(c);
|
|
649
|
+
const dbClient = await this.database.getClient();
|
|
650
|
+
const metricSettings = await dbClient.where({
|
|
651
|
+
"wallets.name": query.walletName,
|
|
652
|
+
"business_metrics.metric_provider": this.providerName.toLowerCase(),
|
|
653
|
+
"business_metrics.config_name": configName
|
|
654
|
+
}).select("business_metrics.*").from("business_metrics").join("wallets", "business_metrics.wallet_id", "=", "wallets.id");
|
|
655
|
+
for (const metric of metricSettings || []) {
|
|
656
|
+
const promise = (async () => {
|
|
657
|
+
try {
|
|
658
|
+
const fullQuery = {
|
|
659
|
+
name: metric.metric_name,
|
|
660
|
+
query: metric.query,
|
|
661
|
+
...query
|
|
662
|
+
};
|
|
663
|
+
const cachedMetrics = await getMetricsFromCache(this.cache, this.providerName, configName, fullQuery);
|
|
664
|
+
if (cachedMetrics) {
|
|
665
|
+
this.logger.debug(`${this.providerName}/${configName}/${fullQuery.name} metrics from cache`);
|
|
666
|
+
cachedMetrics.map((m) => {
|
|
667
|
+
results.push(m);
|
|
668
|
+
});
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const metricResponse = await this.fetchMetrics(c, client, fullQuery);
|
|
672
|
+
const transformedMetrics = await this.transformMetricData(c, fullQuery, metricResponse);
|
|
673
|
+
await setMetricsToCache(
|
|
674
|
+
this.cache,
|
|
675
|
+
transformedMetrics,
|
|
676
|
+
this.providerName,
|
|
677
|
+
configName,
|
|
678
|
+
fullQuery,
|
|
679
|
+
60 * 60 * 2 * 1e3
|
|
680
|
+
);
|
|
681
|
+
transformedMetrics.map((value) => {
|
|
682
|
+
results.push(value);
|
|
683
|
+
});
|
|
684
|
+
} catch (e) {
|
|
685
|
+
this.logger.error(e);
|
|
686
|
+
errors.push({
|
|
687
|
+
provider: this.providerName,
|
|
688
|
+
name: `${this.providerName}/${configName}/${metric.getString("metricName")}`,
|
|
689
|
+
error: e.message
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
})();
|
|
693
|
+
promises.push(promise);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
await Promise.all(promises);
|
|
697
|
+
return {
|
|
698
|
+
metrics: results,
|
|
699
|
+
errors
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
class DatadogProvider extends MetricProvider {
|
|
705
|
+
static create(config, database, cache, logger) {
|
|
706
|
+
return new DatadogProvider("Datadog", config, database, cache, logger);
|
|
707
|
+
}
|
|
708
|
+
async initProviderClient(config) {
|
|
709
|
+
const apiKey = config.getString("apiKey");
|
|
710
|
+
const applicationKey = config.getString("applicationKey");
|
|
711
|
+
const ddSite = config.getString("ddSite");
|
|
712
|
+
const configuration = datadogApiClient.client.createConfiguration({
|
|
713
|
+
baseServer: new datadogApiClient.client.BaseServerConfiguration(ddSite, {}),
|
|
714
|
+
authMethods: {
|
|
715
|
+
apiKeyAuth: apiKey,
|
|
716
|
+
appKeyAuth: applicationKey
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
const client = new datadogApiClient.v1.MetricsApi(configuration);
|
|
720
|
+
return client;
|
|
721
|
+
}
|
|
722
|
+
async fetchMetrics(_metricProviderConfig, client, query) {
|
|
723
|
+
var _a;
|
|
724
|
+
const params = {
|
|
725
|
+
from: parseInt(query.startTime, 10) / 1e3,
|
|
726
|
+
to: parseInt(query.endTime, 10) / 1e3,
|
|
727
|
+
query: (_a = query.query) == null ? void 0 : _a.replaceAll("IW_INTERVAL", query.granularity === "daily" ? "86400" : "2592000")
|
|
728
|
+
};
|
|
729
|
+
return client.queryMetrics(params).then((data) => {
|
|
730
|
+
if (data.status === "ok") {
|
|
731
|
+
return data;
|
|
732
|
+
}
|
|
733
|
+
throw new Error(data.error);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
async transformMetricData(_metricProviderConfig, query, metricResponse) {
|
|
737
|
+
const transformedData = [];
|
|
738
|
+
for (const series of metricResponse.series) {
|
|
739
|
+
const metricName = query.name;
|
|
740
|
+
const tagSet = series.tagSet;
|
|
741
|
+
const metric = {
|
|
742
|
+
id: `${metricName} ${tagSet.length === 0 ? "" : tagSet}`,
|
|
743
|
+
provider: this.providerName,
|
|
744
|
+
name: metricName,
|
|
745
|
+
reports: {}
|
|
746
|
+
};
|
|
747
|
+
for (const point of series.pointlist) {
|
|
748
|
+
const period = moment__default.default(point[0]).format(query.granularity === "daily" ? "YYYY-MM-DD" : "YYYY-MM");
|
|
749
|
+
const value = point[1];
|
|
750
|
+
metric.reports[period] = value;
|
|
751
|
+
}
|
|
752
|
+
transformedData.push(metric);
|
|
753
|
+
}
|
|
754
|
+
return transformedData;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
class GrafanaCloudProvider extends MetricProvider {
|
|
759
|
+
static create(config, database, cache, logger) {
|
|
760
|
+
return new GrafanaCloudProvider("GrafanaCloud", config, database, cache, logger);
|
|
761
|
+
}
|
|
762
|
+
async initProviderClient(_config) {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
async fetchMetrics(metricProviderConfig, _client, query) {
|
|
766
|
+
var _a;
|
|
767
|
+
const url = metricProviderConfig.getString("url");
|
|
768
|
+
const datasourceUid = metricProviderConfig.getString("datasourceUid");
|
|
769
|
+
const token = metricProviderConfig.getString("token");
|
|
770
|
+
const headers = {
|
|
771
|
+
"Content-Type": "application/json",
|
|
772
|
+
Authorization: `Bearer ${token}`
|
|
773
|
+
};
|
|
774
|
+
const payload = {
|
|
775
|
+
queries: [
|
|
776
|
+
{
|
|
777
|
+
datasource: {
|
|
778
|
+
uid: datasourceUid
|
|
779
|
+
},
|
|
780
|
+
expr: (_a = query.query) == null ? void 0 : _a.replaceAll("IW_INTERVAL", query.granularity === "daily" ? "1d" : "30d"),
|
|
781
|
+
refId: "A"
|
|
782
|
+
}
|
|
783
|
+
],
|
|
784
|
+
from: query.startTime,
|
|
785
|
+
to: query.endTime
|
|
786
|
+
};
|
|
787
|
+
const response = await fetch__default.default(`${url}/api/ds/query`, {
|
|
788
|
+
method: "post",
|
|
789
|
+
body: JSON.stringify(payload),
|
|
790
|
+
headers
|
|
791
|
+
});
|
|
792
|
+
const data = await response.json();
|
|
793
|
+
return data;
|
|
794
|
+
}
|
|
795
|
+
async transformMetricData(_metricProviderConfig, query, metricResponse) {
|
|
796
|
+
const transformedData = [];
|
|
797
|
+
const metricName = query.name;
|
|
798
|
+
const metric = {
|
|
799
|
+
id: metricName,
|
|
800
|
+
provider: this.providerName,
|
|
801
|
+
name: metricName,
|
|
802
|
+
reports: {}
|
|
803
|
+
};
|
|
804
|
+
const periods = metricResponse.results.A.frames[0].data.values[0];
|
|
805
|
+
const values = metricResponse.results.A.frames[0].data.values[1];
|
|
806
|
+
for (let i = 0; i < periods.length; i++) {
|
|
807
|
+
const period = moment__default.default(periods[i]).format(query.granularity === "daily" ? "YYYY-MM-DD" : "YYYY-MM");
|
|
808
|
+
const value = values[i];
|
|
809
|
+
metric.reports[period] = value;
|
|
810
|
+
}
|
|
811
|
+
transformedData.push(metric);
|
|
812
|
+
return transformedData;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
class MockProvider extends MetricProvider {
|
|
817
|
+
static create(config, database, cache, logger) {
|
|
818
|
+
return new MockProvider("Mock", config, database, cache, logger);
|
|
819
|
+
}
|
|
820
|
+
async initProviderClient(_config) {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
async fetchMetrics(_metricProviderConfig, _client, _query) {
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
async transformMetricData(_metricProviderConfig, query, _metricResponse) {
|
|
827
|
+
var _a, _b;
|
|
828
|
+
const transformedData = [];
|
|
829
|
+
const metricName = query.name;
|
|
830
|
+
let mockSettings = {};
|
|
831
|
+
try {
|
|
832
|
+
mockSettings = JSON.parse(query.query);
|
|
833
|
+
} catch (e) {
|
|
834
|
+
}
|
|
835
|
+
const minValue = (_a = mockSettings.min) != null ? _a : 0;
|
|
836
|
+
const maxValue = (_b = mockSettings.max) != null ? _b : 1e3;
|
|
837
|
+
const metric = {
|
|
838
|
+
id: metricName,
|
|
839
|
+
provider: this.providerName,
|
|
840
|
+
name: metricName,
|
|
841
|
+
reports: {}
|
|
842
|
+
};
|
|
843
|
+
let cursor = moment__default.default(parseInt(query.startTime, 10));
|
|
844
|
+
while (cursor <= moment__default.default(parseInt(query.endTime, 10))) {
|
|
845
|
+
const period = cursor.format(query.granularity === "daily" ? "YYYY-MM-DD" : "YYYY-MM");
|
|
846
|
+
metric.reports[period] = Math.floor(Math.random() * (maxValue - minValue) + minValue);
|
|
847
|
+
cursor = cursor.add(1, query.granularity === "daily" ? "days" : "months");
|
|
848
|
+
}
|
|
849
|
+
transformedData.push(metric);
|
|
850
|
+
return transformedData;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
class MockClient extends InfraWalletClient {
|
|
855
|
+
static create(config, database, cache, logger) {
|
|
856
|
+
return new MockClient("mock", config, database, cache, logger);
|
|
857
|
+
}
|
|
858
|
+
async initCloudClient(config) {
|
|
859
|
+
this.logger.debug(`MockClient.initCloudClient called with config: ${JSON.stringify(config)}`);
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
async fetchCostsFromCloud(_subAccountConfig, _client, _query) {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
async transformCostsData(_subAccountConfig, query, _costResponse, _categoryMappings) {
|
|
866
|
+
try {
|
|
867
|
+
const startD = moment__default.default.unix(Number(query.startTime) / 1e3);
|
|
868
|
+
let endD = moment__default.default.unix(Number(query.endTime) / 1e3);
|
|
869
|
+
const mockDir = backendPluginApi.resolvePackagePath("@electrolux-oss/plugin-infrawallet-backend", "mock");
|
|
870
|
+
const mockFilePath = upath__namespace.join(mockDir, "mock_response.json");
|
|
871
|
+
const data = await fs.promises.readFile(mockFilePath, "utf8");
|
|
872
|
+
const jsonData = JSON.parse(data);
|
|
873
|
+
const currentDate = moment__default.default();
|
|
874
|
+
if (endD.isAfter(currentDate)) {
|
|
875
|
+
this.logger.warn("End Date is in the future, adjusting to current date.");
|
|
876
|
+
endD = currentDate.clone();
|
|
877
|
+
endD.add(1, "day");
|
|
878
|
+
}
|
|
879
|
+
const processedData = await Promise.all(
|
|
880
|
+
jsonData.map(async (item) => {
|
|
881
|
+
item.reports = {};
|
|
882
|
+
const StartDate = moment__default.default(startD);
|
|
883
|
+
let step;
|
|
884
|
+
let dateFormat = "YYYY-MM";
|
|
885
|
+
if (query.granularity.toLowerCase() === "monthly") {
|
|
886
|
+
step = "months";
|
|
887
|
+
dateFormat = "YYYY-MM";
|
|
888
|
+
} else if (query.granularity.toLowerCase() === "daily") {
|
|
889
|
+
step = "days";
|
|
890
|
+
dateFormat = "YYYY-MM-DD";
|
|
891
|
+
}
|
|
892
|
+
while (StartDate.isBefore(endD)) {
|
|
893
|
+
const dateString = StartDate.format(dateFormat);
|
|
894
|
+
if (query.granularity.toLowerCase() === "monthly") {
|
|
895
|
+
item.reports[dateString] = this.getRandomValue(0.4 * 30, 33.3 * 30);
|
|
896
|
+
} else {
|
|
897
|
+
item.reports[dateString] = this.getRandomValue(0.4, 33.3);
|
|
898
|
+
}
|
|
899
|
+
StartDate.add(1, step);
|
|
900
|
+
}
|
|
901
|
+
return item;
|
|
902
|
+
})
|
|
903
|
+
);
|
|
904
|
+
return processedData;
|
|
905
|
+
} catch (err) {
|
|
906
|
+
this.logger.error("Error while reading a file", err);
|
|
907
|
+
throw err;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
getRandomValue(min, max) {
|
|
911
|
+
const random = Math.random();
|
|
912
|
+
const amplifiedRandom = Math.pow(random, 3);
|
|
913
|
+
return amplifiedRandom * (max - min) + min;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const COST_CLIENT_MAPPINGS = {
|
|
545
918
|
aws: AwsClient,
|
|
546
919
|
azure: AzureClient,
|
|
547
|
-
gcp: GCPClient
|
|
920
|
+
gcp: GCPClient,
|
|
921
|
+
mock: MockClient
|
|
922
|
+
};
|
|
923
|
+
const METRIC_PROVIDER_MAPPINGS = {
|
|
924
|
+
datadog: DatadogProvider,
|
|
925
|
+
grafanacloud: GrafanaCloudProvider,
|
|
926
|
+
mock: MockProvider
|
|
548
927
|
};
|
|
549
928
|
|
|
550
929
|
async function setUpDatabase(database) {
|
|
@@ -579,8 +958,8 @@ async function createRouter(options) {
|
|
|
579
958
|
const errors = [];
|
|
580
959
|
const conf = config.getConfig("backend.infraWallet.integrations");
|
|
581
960
|
conf.keys().forEach((provider) => {
|
|
582
|
-
if (provider in
|
|
583
|
-
const client =
|
|
961
|
+
if (provider in COST_CLIENT_MAPPINGS) {
|
|
962
|
+
const client = COST_CLIENT_MAPPINGS[provider].create(config, database, cache, logger);
|
|
584
963
|
const fetchCloudCosts = (async () => {
|
|
585
964
|
try {
|
|
586
965
|
const clientResponse = await client.getCostReports({
|
|
@@ -615,6 +994,98 @@ async function createRouter(options) {
|
|
|
615
994
|
response.json({ data: results, errors, status: 200 });
|
|
616
995
|
}
|
|
617
996
|
});
|
|
997
|
+
router.get("/:walletName/metrics", async (request, response) => {
|
|
998
|
+
const walletName = request.params.walletName;
|
|
999
|
+
const granularity = request.query.granularity;
|
|
1000
|
+
const startTime = request.query.startTime;
|
|
1001
|
+
const endTime = request.query.endTime;
|
|
1002
|
+
const promises = [];
|
|
1003
|
+
const results = [];
|
|
1004
|
+
const errors = [];
|
|
1005
|
+
const conf = config.getConfig("backend.infraWallet.metricProviders");
|
|
1006
|
+
conf.keys().forEach((provider) => {
|
|
1007
|
+
if (provider in METRIC_PROVIDER_MAPPINGS) {
|
|
1008
|
+
const client = METRIC_PROVIDER_MAPPINGS[provider].create(config, database, cache, logger);
|
|
1009
|
+
const fetchMetrics = (async () => {
|
|
1010
|
+
try {
|
|
1011
|
+
const metricResponse = await client.getMetrics({
|
|
1012
|
+
walletName,
|
|
1013
|
+
granularity,
|
|
1014
|
+
startTime,
|
|
1015
|
+
endTime
|
|
1016
|
+
});
|
|
1017
|
+
metricResponse.errors.forEach((e) => {
|
|
1018
|
+
errors.push(e);
|
|
1019
|
+
});
|
|
1020
|
+
metricResponse.metrics.forEach((metric) => {
|
|
1021
|
+
results.push(metric);
|
|
1022
|
+
});
|
|
1023
|
+
} catch (e) {
|
|
1024
|
+
logger.error(e);
|
|
1025
|
+
errors.push({
|
|
1026
|
+
provider: client.constructor.name,
|
|
1027
|
+
name: client.constructor.name,
|
|
1028
|
+
error: e.message
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
})();
|
|
1032
|
+
promises.push(fetchMetrics);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
await Promise.all(promises);
|
|
1036
|
+
if (errors.length > 0) {
|
|
1037
|
+
response.status(207).json({ data: results, errors, status: 207 });
|
|
1038
|
+
} else {
|
|
1039
|
+
response.json({ data: results, errors, status: 200 });
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
router.get("/:walletName", async (request, response) => {
|
|
1043
|
+
const walletName = request.params.walletName;
|
|
1044
|
+
const wallet = await getWallet(database, walletName);
|
|
1045
|
+
if (wallet === void 0) {
|
|
1046
|
+
response.status(404).json({ error: "Wallet not found", status: 404 });
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
response.json({ data: wallet, status: 200 });
|
|
1050
|
+
});
|
|
1051
|
+
router.get("/:walletName/metrics_setting", async (request, response) => {
|
|
1052
|
+
const walletName = request.params.walletName;
|
|
1053
|
+
const metricSettings = await getWalletMetricSettings(database, walletName);
|
|
1054
|
+
response.json({ data: metricSettings, status: 200 });
|
|
1055
|
+
});
|
|
1056
|
+
router.get("/metric/metric_configs", async (_request, response) => {
|
|
1057
|
+
const conf = config.getConfig("backend.infraWallet.metricProviders");
|
|
1058
|
+
const configNames = [];
|
|
1059
|
+
conf.keys().forEach((provider) => {
|
|
1060
|
+
const configs = conf.getOptionalConfigArray(provider);
|
|
1061
|
+
if (configs) {
|
|
1062
|
+
configs.forEach((c) => {
|
|
1063
|
+
configNames.push({ metric_provider: provider, config_name: c.getString("name") });
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
response.json({ data: configNames, status: 200 });
|
|
1068
|
+
});
|
|
1069
|
+
router.put("/:walletName/metrics_setting", async (request, response) => {
|
|
1070
|
+
var _a;
|
|
1071
|
+
const readOnly = (_a = config.getOptionalBoolean("infraWallet.settings.readOnly")) != null ? _a : false;
|
|
1072
|
+
if (readOnly) {
|
|
1073
|
+
response.status(403).json({ error: "API not enabled in read-only mode", status: 403 });
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const updatedMetricSetting = await updateOrInsertWalletMetricSetting(database, request.body);
|
|
1077
|
+
response.json({ updated: updatedMetricSetting, status: 200 });
|
|
1078
|
+
});
|
|
1079
|
+
router.delete("/:walletName/metrics_setting", async (request, response) => {
|
|
1080
|
+
var _a;
|
|
1081
|
+
const readOnly = (_a = config.getOptionalBoolean("infraWallet.settings.readOnly")) != null ? _a : false;
|
|
1082
|
+
if (readOnly) {
|
|
1083
|
+
response.status(403).json({ error: "API not enabled in read-only mode", status: 403 });
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const deletedMetricSetting = await deleteWalletMetricSetting(database, request.body);
|
|
1087
|
+
response.json({ deleted: deletedMetricSetting, status: 200 });
|
|
1088
|
+
});
|
|
618
1089
|
router.use(backendCommon.errorHandler());
|
|
619
1090
|
return router;
|
|
620
1091
|
}
|