@meyicloud/meyi-cost-server 1.8.0 → 1.8.2
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/package.json
CHANGED
|
@@ -7,7 +7,6 @@ export class CostAnalysisReportRepository {
|
|
|
7
7
|
this.db = db;
|
|
8
8
|
this.schedules = `${qSchema}.cost_ai_report_schedules`;
|
|
9
9
|
this.reports = `${qSchema}.cost_ai_reports`;
|
|
10
|
-
this.incidentTickets = `${qSchema}.cost_ai_incident_tickets`;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
async getSchedule(tenantId) {
|
|
@@ -110,43 +109,6 @@ export class CostAnalysisReportRepository {
|
|
|
110
109
|
`));
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
async stageIncidentTickets(report, tickets) {
|
|
114
|
-
for (const ticket of tickets) {
|
|
115
|
-
await this.db.execute(sql`
|
|
116
|
-
INSERT INTO ${sql.raw(this.incidentTickets)}
|
|
117
|
-
(tenant_id, ticket_id, report_id, payload)
|
|
118
|
-
VALUES (${report.tenant_id}, ${ticket.ticket_id}, ${report.id}, ${JSON.stringify(ticket)}::jsonb)
|
|
119
|
-
ON CONFLICT (tenant_id, ticket_id) DO UPDATE SET
|
|
120
|
-
payload = EXCLUDED.payload, report_id = EXCLUDED.report_id, updated_at = now()
|
|
121
|
-
`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async listUnsyncedIncidentTickets(reportId) {
|
|
126
|
-
return rows(await this.db.execute(sql`
|
|
127
|
-
SELECT * FROM ${sql.raw(this.incidentTickets)}
|
|
128
|
-
WHERE report_id = ${reportId} AND sync_status IN ('PENDING', 'FAILED')
|
|
129
|
-
ORDER BY created_at ASC
|
|
130
|
-
`));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async markIncidentTicketSynced(tenantId, ticketId, incidentId) {
|
|
134
|
-
await this.db.execute(sql`
|
|
135
|
-
UPDATE ${sql.raw(this.incidentTickets)}
|
|
136
|
-
SET incident_id = ${incidentId}, sync_status = 'COMPLETED', sync_error = NULL,
|
|
137
|
-
synced_at = now(), updated_at = now()
|
|
138
|
-
WHERE tenant_id = ${tenantId} AND ticket_id = ${ticketId}
|
|
139
|
-
`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async markIncidentTicketFailed(tenantId, ticketId, error) {
|
|
143
|
-
await this.db.execute(sql`
|
|
144
|
-
UPDATE ${sql.raw(this.incidentTickets)}
|
|
145
|
-
SET sync_status = 'FAILED', sync_error = ${String(error?.message || error).slice(0, 2000)}, updated_at = now()
|
|
146
|
-
WHERE tenant_id = ${tenantId} AND ticket_id = ${ticketId}
|
|
147
|
-
`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
112
|
async setIncidentSyncState(reportId, status, error = null) {
|
|
151
113
|
await this.db.execute(sql`
|
|
152
114
|
UPDATE ${sql.raw(this.reports)}
|
|
@@ -67,18 +67,5 @@ export async function installCostAnalysisReportSchema(db, qSchema) {
|
|
|
67
67
|
await db.execute(sql.raw(`UPDATE ${qSchema}.cost_ai_reports
|
|
68
68
|
SET ticket_count = (result->>'ticketCount')::integer
|
|
69
69
|
WHERE ticket_count = 0 AND result ? 'ticketCount' AND (result->>'ticketCount') ~ '^[0-9]+$'`));
|
|
70
|
-
await db.execute(sql.raw(`
|
|
71
|
-
tenant_id text NOT NULL,
|
|
72
|
-
ticket_id text NOT NULL,
|
|
73
|
-
report_id text NOT NULL REFERENCES ${qSchema}.cost_ai_reports(id) ON DELETE CASCADE,
|
|
74
|
-
payload jsonb NOT NULL,
|
|
75
|
-
incident_id text,
|
|
76
|
-
sync_status text NOT NULL DEFAULT 'PENDING' CHECK (sync_status IN ('PENDING', 'COMPLETED', 'FAILED')),
|
|
77
|
-
sync_error text,
|
|
78
|
-
synced_at timestamptz,
|
|
79
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
80
|
-
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
81
|
-
PRIMARY KEY (tenant_id, ticket_id)
|
|
82
|
-
)`));
|
|
83
|
-
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS cost_ai_incident_tickets_report_idx ON ${qSchema}.cost_ai_incident_tickets (report_id, sync_status)`));
|
|
70
|
+
await db.execute(sql.raw(`DROP TABLE IF EXISTS ${qSchema}.cost_ai_incident_tickets`));
|
|
84
71
|
}
|
|
@@ -175,6 +175,7 @@ export class CostAnalyserService {
|
|
|
175
175
|
if (!athena.enabled) throw Object.assign(new Error("CUR discovery has not produced a queryable Athena table for this tenant"), { name: "CurDataNotReadyError", statusCode: 503 });
|
|
176
176
|
const targetRoleArn = required(customer.meta?.payerRoleArn, "customer TARGET_ROLE_ARN");
|
|
177
177
|
const targetAccountId = required(customer.meta?.managementAccountId || customer.accounts?.[0]?.id, "customer management account ID");
|
|
178
|
+
const targetAccountName = customer.accounts?.find((account) => account.id === targetAccountId)?.name || targetAccountId;
|
|
178
179
|
const prefix = `tenants/${tenantId}/reports/${reportId}`;
|
|
179
180
|
let providerSecretArn;
|
|
180
181
|
try {
|
|
@@ -189,6 +190,7 @@ export class CostAnalyserService {
|
|
|
189
190
|
REPORT_BUCKET: this.reportBucket,
|
|
190
191
|
REPORT_BUCKET_REGION: this.region,
|
|
191
192
|
TARGET_ACCOUNT_ID: targetAccountId,
|
|
193
|
+
TARGET_ACCOUNT_NAME: targetAccountName,
|
|
192
194
|
TARGET_ROLE_ARN: targetRoleArn,
|
|
193
195
|
TARGET_ROLE_EXTERNAL_ID: customer.meta?.externalId,
|
|
194
196
|
TARGET_REGIONS: this.targetRegions,
|
|
@@ -17,22 +17,15 @@ export class CostIncidentSyncService {
|
|
|
17
17
|
if (!this.incidentSink) return false;
|
|
18
18
|
try {
|
|
19
19
|
const key = this.ticketsKey(report);
|
|
20
|
-
if (!report.artifact_bucket || !key) throw new Error("
|
|
20
|
+
if (!report.artifact_bucket || !key) throw new Error("Cost ticket artifact location is missing");
|
|
21
21
|
const data = await this.artifactService.get(report.artifact_bucket, key);
|
|
22
|
-
if (!data) throw new Error("
|
|
22
|
+
if (!data) throw new Error("Cost ticket artifact is empty");
|
|
23
23
|
const parsed = JSON.parse(data.toString("utf8"));
|
|
24
24
|
const tickets = Array.isArray(parsed?.tickets) ? parsed.tickets.filter((ticket) => ticket?.ticket_id) : [];
|
|
25
|
-
await this.repository.stageIncidentTickets(report, tickets);
|
|
26
25
|
|
|
27
|
-
for (const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!result?.id) throw new Error("Incident integration did not return an incident ID");
|
|
31
|
-
await this.repository.markIncidentTicketSynced(report.tenant_id, row.ticket_id, result.id);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
await this.repository.markIncidentTicketFailed(report.tenant_id, row.ticket_id, error);
|
|
34
|
-
throw error;
|
|
35
|
-
}
|
|
26
|
+
for (const ticket of tickets) {
|
|
27
|
+
const result = await this.incidentSink({ tenantId: report.tenant_id, report, ticket });
|
|
28
|
+
if (!result?.id) throw new Error(`Incident integration did not return an incident ID for ${ticket.ticket_id}`);
|
|
36
29
|
}
|
|
37
30
|
|
|
38
31
|
await this.repository.setIncidentSyncState(report.id, "COMPLETED");
|