cognite-neat 1.0.6__py3-none-any.whl → 1.0.7__py3-none-any.whl
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.
- cognite/neat/_data_model/deployer/data_classes.py +1 -1
- cognite/neat/_session/_html/static/deployment.css +173 -0
- cognite/neat/_session/_html/static/deployment.js +36 -5
- cognite/neat/_session/_html/templates/deployment.html +8 -3
- cognite/neat/_session/_result/__init__.py +3 -0
- cognite/neat/_session/_result/_deployment/__init__.py +0 -0
- cognite/neat/_session/_result/_deployment/_physical/__init__.py +0 -0
- cognite/neat/_session/_result/_deployment/_physical/_changes.py +195 -0
- cognite/neat/_session/_result/_deployment/_physical/_statistics.py +180 -0
- cognite/neat/_session/_result/_deployment/_physical/serializer.py +35 -0
- cognite/neat/_session/_result/_result.py +31 -0
- cognite/neat/_version.py +1 -1
- {cognite_neat-1.0.6.dist-info → cognite_neat-1.0.7.dist-info}/METADATA +42 -42
- {cognite_neat-1.0.6.dist-info → cognite_neat-1.0.7.dist-info}/RECORD +44 -39
- cognite_neat-1.0.7.dist-info/WHEEL +4 -0
- cognite/neat/_session/_result.py +0 -236
- cognite_neat-1.0.6.dist-info/WHEEL +0 -4
- cognite_neat-1.0.6.dist-info/licenses/LICENSE +0 -201
|
@@ -132,6 +132,16 @@
|
|
|
132
132
|
color: #9ca3af;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
.change-failed {
|
|
136
|
+
background: #fee2e2;
|
|
137
|
+
color: #991b1b;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.dark-mode .change-failed {
|
|
141
|
+
background: #7f1d1d;
|
|
142
|
+
color: #fca5a5;
|
|
143
|
+
}
|
|
144
|
+
|
|
135
145
|
.severity-badge {
|
|
136
146
|
padding: 4px 10px;
|
|
137
147
|
border-radius: 4px;
|
|
@@ -286,6 +296,16 @@
|
|
|
286
296
|
color: white;
|
|
287
297
|
}
|
|
288
298
|
|
|
299
|
+
.status-recovered {
|
|
300
|
+
background: #10b981;
|
|
301
|
+
color: white;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.status-recovery_failed {
|
|
305
|
+
background: #dc2626;
|
|
306
|
+
color: white;
|
|
307
|
+
}
|
|
308
|
+
|
|
289
309
|
.dark-mode .status-success {
|
|
290
310
|
background: #059669;
|
|
291
311
|
}
|
|
@@ -300,4 +320,157 @@
|
|
|
300
320
|
|
|
301
321
|
.dark-mode .status-pending {
|
|
302
322
|
background: #2563eb;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.dark-mode .status-recovered {
|
|
326
|
+
background: #059669;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.dark-mode .status-recovery_failed {
|
|
330
|
+
background: #b91c1c;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
/* Error message handling */
|
|
335
|
+
|
|
336
|
+
.error-message-box {
|
|
337
|
+
background: #fee2e2;
|
|
338
|
+
border: 2px solid #dc2626;
|
|
339
|
+
border-left: 6px solid #dc2626;
|
|
340
|
+
border-radius: 8px;
|
|
341
|
+
padding: 16px;
|
|
342
|
+
margin-top: 12px;
|
|
343
|
+
animation: slideIn 0.3s ease-out;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.dark-mode .error-message-box {
|
|
347
|
+
background: #7f1d1d;
|
|
348
|
+
border-color: #ef4444;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.error-message-header {
|
|
352
|
+
display: flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
gap: 10px;
|
|
355
|
+
margin-bottom: 8px;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.error-icon {
|
|
359
|
+
font-size: 24px;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.error-title {
|
|
363
|
+
font-weight: 700;
|
|
364
|
+
font-size: 14px;
|
|
365
|
+
color: #991b1b;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.dark-mode .error-title {
|
|
369
|
+
color: #fca5a5;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.error-message-content {
|
|
373
|
+
color: #7f1d1d;
|
|
374
|
+
font-size: 13px;
|
|
375
|
+
line-height: 1.6;
|
|
376
|
+
padding-left: 34px;
|
|
377
|
+
font-family: 'Monaco', 'Courier New', monospace;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.dark-mode .error-message-content {
|
|
381
|
+
color: #fca5a5;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.change-item.failed-change {
|
|
385
|
+
border-left: 4px solid #dc2626;
|
|
386
|
+
background: rgba(254, 226, 226, 0.1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.dark-mode .change-item.failed-change {
|
|
390
|
+
border-left-color: #ef4444;
|
|
391
|
+
background: rgba(127, 29, 29, 0.2);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.info-message-box {
|
|
395
|
+
border-radius: 6px;
|
|
396
|
+
padding: 12px 16px;
|
|
397
|
+
margin-top: 12px;
|
|
398
|
+
font-size: 13px;
|
|
399
|
+
line-height: 1.6;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.info-message-content {
|
|
403
|
+
color: inherit;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.info-message-create {
|
|
407
|
+
background: #d1fae5;
|
|
408
|
+
border-left: 4px solid #065f46;
|
|
409
|
+
color: #065f46;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.info-message-update {
|
|
413
|
+
background: #dbeafe;
|
|
414
|
+
border-left: 4px solid #1e40af;
|
|
415
|
+
color: #1e40af;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.info-message-delete {
|
|
419
|
+
background: #fee2e2;
|
|
420
|
+
border-left: 4px solid #991b1b;
|
|
421
|
+
color: #991b1b;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.info-message-skip {
|
|
425
|
+
background: #fef3c7;
|
|
426
|
+
border-left: 4px solid #92400e;
|
|
427
|
+
color: #92400e;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.info-message-unchanged {
|
|
431
|
+
background: #f3f4f6;
|
|
432
|
+
border-left: 4px solid #4b5563;
|
|
433
|
+
color: #4b5563;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.dark-mode .info-message-create {
|
|
437
|
+
background: #064e3b;
|
|
438
|
+
border-left-color: #6ee7b7;
|
|
439
|
+
color: #6ee7b7;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.dark-mode .info-message-update {
|
|
443
|
+
background: #1e3a8a;
|
|
444
|
+
border-left-color: #93c5fd;
|
|
445
|
+
color: #93c5fd;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.dark-mode .info-message-delete {
|
|
449
|
+
background: #7f1d1d;
|
|
450
|
+
border-left-color: #fca5a5;
|
|
451
|
+
color: #fca5a5;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.dark-mode .info-message-skip {
|
|
455
|
+
background: #78350f;
|
|
456
|
+
border-left-color: #fde68a;
|
|
457
|
+
color: #fde68a;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.dark-mode .info-message-unchanged {
|
|
461
|
+
background: #374151;
|
|
462
|
+
border-left-color: #9ca3af;
|
|
463
|
+
color: #9ca3af;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@keyframes slideIn {
|
|
468
|
+
from {
|
|
469
|
+
opacity: 0;
|
|
470
|
+
transform: translateY(-10px);
|
|
471
|
+
}
|
|
472
|
+
to {
|
|
473
|
+
opacity: 1;
|
|
474
|
+
transform: translateY(0);
|
|
475
|
+
}
|
|
303
476
|
}
|
|
@@ -17,7 +17,9 @@ const STATUS_CONFIG = {
|
|
|
17
17
|
success: { icon: '✅', text: 'Success' },
|
|
18
18
|
failure: { icon: '❌', text: 'Failure' },
|
|
19
19
|
partial: { icon: '⚠️', text: 'Partial Success' },
|
|
20
|
-
pending: { icon: '⏳', text: 'Pending (Dry Run)' }
|
|
20
|
+
pending: { icon: '⏳', text: 'Pending (Dry Run)' },
|
|
21
|
+
recovered: { icon: '🔄', text: 'Recovered' },
|
|
22
|
+
recovery_failed: { icon: '💔', text: 'Recovery Failed' }
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
// Initialize status badge
|
|
@@ -26,7 +28,7 @@ function initializeStatus() {
|
|
|
26
28
|
const statusIcon = document.getElementById('statusIcon-' + uniqueId);
|
|
27
29
|
const statusText = document.getElementById('statusText-' + uniqueId);
|
|
28
30
|
|
|
29
|
-
const statusConfig = STATUS_CONFIG[
|
|
31
|
+
const statusConfig = STATUS_CONFIG[deploymentStatus] || STATUS_CONFIG.pending;
|
|
30
32
|
statusIcon.textContent = statusConfig.icon;
|
|
31
33
|
statusText.textContent = statusConfig.text;
|
|
32
34
|
}
|
|
@@ -43,6 +45,31 @@ function updateTheme() {
|
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
function renderChangeMessage(change) {
|
|
49
|
+
if (!change.message) return '';
|
|
50
|
+
|
|
51
|
+
// Using textContent to set the message prevents XSS vulnerabilities
|
|
52
|
+
// by automatically escaping HTML characters.
|
|
53
|
+
const messageHolder = document.createElement('div');
|
|
54
|
+
messageHolder.textContent = change.message;
|
|
55
|
+
const escapedMessage = messageHolder.innerHTML;
|
|
56
|
+
|
|
57
|
+
if (change.change_type === 'failed') {
|
|
58
|
+
return `
|
|
59
|
+
<div class="error-message-box">
|
|
60
|
+
<div class="error-message-header">
|
|
61
|
+
<span class="error-icon">❌</span>
|
|
62
|
+
<span class="error-title">Deployment Failed</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="error-message-content">${escapedMessage}</div>
|
|
65
|
+
</div>`;
|
|
66
|
+
}
|
|
67
|
+
return `
|
|
68
|
+
<div class="info-message-box info-message-${change.change_type}">
|
|
69
|
+
<div class="info-message-content">${escapedMessage}</div>
|
|
70
|
+
</div>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
updateTheme();
|
|
47
74
|
initializeStatus();
|
|
48
75
|
|
|
@@ -76,13 +103,14 @@ function renderChanges() {
|
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
listContainer.innerHTML = filtered.map(change => `
|
|
79
|
-
<div class="change-item">
|
|
106
|
+
<div class="change-item ${change.change_type === 'failed' ? 'failed-change' : ''}">
|
|
80
107
|
<div class="change-header">
|
|
81
108
|
<span class="endpoint-badge endpoint-${change.endpoint}">${change.endpoint}</span>
|
|
82
109
|
<span class="change-type-badge change-${change.change_type}">${change.change_type}</span>
|
|
83
110
|
<span class="severity-badge severity-${change.severity}">${change.severity}</span>
|
|
84
111
|
</div>
|
|
85
112
|
<div class="resource-id">${change.resource_id}</div>
|
|
113
|
+
|
|
86
114
|
${change.changes.length > 0 ? `
|
|
87
115
|
<div class="field-changes">
|
|
88
116
|
${change.changes.map(fc => `
|
|
@@ -93,6 +121,9 @@ function renderChanges() {
|
|
|
93
121
|
`).join('')}
|
|
94
122
|
</div>
|
|
95
123
|
` : ''}
|
|
124
|
+
|
|
125
|
+
${renderChangeMessage(change)}
|
|
126
|
+
|
|
96
127
|
</div>
|
|
97
128
|
`).join('');
|
|
98
129
|
}
|
|
@@ -130,8 +161,8 @@ document.getElementById('searchInput-' + uniqueId).addEventListener('input', fun
|
|
|
130
161
|
window['exportDeployment_' + uniqueId] = function() {
|
|
131
162
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
132
163
|
const report = {
|
|
133
|
-
status:
|
|
134
|
-
is_dry_run:
|
|
164
|
+
status: deploymentStatus,
|
|
165
|
+
is_dry_run: is_dry_run === 'True',
|
|
135
166
|
timestamp: timestamp,
|
|
136
167
|
statistics: stats,
|
|
137
168
|
changes: changes,
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
<div class="stat-item active" data-filter="all">
|
|
21
21
|
<span class="stat-number">{{total_changes}}</span> Total Changes
|
|
22
22
|
</div>
|
|
23
|
+
<div class="stat-item" data-filter="failed">
|
|
24
|
+
<span class="stat-number">{{failed}}</span> Failed
|
|
25
|
+
</div>
|
|
23
26
|
<div class="stat-item" data-filter="create">
|
|
24
27
|
<span class="stat-number">{{created}}</span> Created
|
|
25
28
|
</div>
|
|
@@ -29,12 +32,12 @@
|
|
|
29
32
|
<div class="stat-item" data-filter="delete">
|
|
30
33
|
<span class="stat-number">{{deleted}}</span> Deleted
|
|
31
34
|
</div>
|
|
32
|
-
<div class="stat-item" data-filter="skip">
|
|
33
|
-
<span class="stat-number">{{skipped}}</span> Skipped
|
|
34
|
-
</div>
|
|
35
35
|
<div class="stat-item" data-filter="unchanged">
|
|
36
36
|
<span class="stat-number">{{unchanged}}</span> Unchanged
|
|
37
37
|
</div>
|
|
38
|
+
<div class="stat-item" data-filter="skip">
|
|
39
|
+
<span class="stat-number">{{skipped}}</span> Skipped
|
|
40
|
+
</div>
|
|
38
41
|
<button class="export-btn" onclick="exportDeployment_{{unique_id}}()">Export Report</button>
|
|
39
42
|
</div>
|
|
40
43
|
</div>
|
|
@@ -70,6 +73,8 @@
|
|
|
70
73
|
const changes = {{CHANGES_JSON}};
|
|
71
74
|
const stats = {{STATS_JSON}};
|
|
72
75
|
const uniqueId = '{{unique_id}}';
|
|
76
|
+
const is_dry_run = '{{is_dry_run}}';
|
|
77
|
+
const deploymentStatus = '{{status}}';
|
|
73
78
|
{{SCRIPTS}}
|
|
74
79
|
})();
|
|
75
80
|
</script>
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from cognite.neat._data_model.deployer.data_classes import (
|
|
7
|
+
AddedField,
|
|
8
|
+
AppliedChanges,
|
|
9
|
+
ChangedField,
|
|
10
|
+
ChangeResult,
|
|
11
|
+
DeploymentResult,
|
|
12
|
+
FieldChange,
|
|
13
|
+
FieldChanges,
|
|
14
|
+
RemovedField,
|
|
15
|
+
ResourceChange,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SerializedFieldChange(BaseModel):
|
|
20
|
+
"""Serialized field change for JSON output."""
|
|
21
|
+
|
|
22
|
+
field_path: str
|
|
23
|
+
severity: str
|
|
24
|
+
description: str
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_field_change(cls, field_change: FieldChange) -> list["SerializedFieldChange"]:
|
|
28
|
+
"""Serialize a field change, handling nested FieldChanges recursively.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
field_change: The field change to serialize.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of serialized field changes (may be multiple if nested).
|
|
35
|
+
"""
|
|
36
|
+
serialized_changes: list[SerializedFieldChange] = []
|
|
37
|
+
|
|
38
|
+
if isinstance(field_change, FieldChanges):
|
|
39
|
+
# Recursively handle nested changes
|
|
40
|
+
for change in field_change.changes:
|
|
41
|
+
serialized_changes.extend(cls.from_field_change(change))
|
|
42
|
+
else:
|
|
43
|
+
# Base case: single field change
|
|
44
|
+
serialized_changes.append(cls._from_single_field_change(field_change))
|
|
45
|
+
|
|
46
|
+
return serialized_changes
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _from_single_field_change(cls, field_change: FieldChange) -> "SerializedFieldChange":
|
|
50
|
+
"""Serialize a single non-nested field change.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
field_change: The single field change to serialize.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Serialized field change.
|
|
57
|
+
"""
|
|
58
|
+
return cls(
|
|
59
|
+
field_path=field_change.field_path,
|
|
60
|
+
severity=field_change.severity.name,
|
|
61
|
+
description=field_change.description
|
|
62
|
+
if isinstance(field_change, AddedField | RemovedField | ChangedField)
|
|
63
|
+
else "Field changed",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SerializedResourceChange(BaseModel):
|
|
68
|
+
"""Serialized resource change for JSON output."""
|
|
69
|
+
|
|
70
|
+
id: int
|
|
71
|
+
endpoint: str
|
|
72
|
+
change_type: str
|
|
73
|
+
severity: str
|
|
74
|
+
resource_id: str
|
|
75
|
+
message: str | None = None
|
|
76
|
+
changes: list[SerializedFieldChange] = Field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_resource_change(
|
|
80
|
+
cls, resource: ResourceChange, endpoint: str, change_id: int
|
|
81
|
+
) -> "SerializedResourceChange":
|
|
82
|
+
"""Serialize a single resource change.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
resource: The resource change to serialize.
|
|
86
|
+
endpoint: The endpoint type.
|
|
87
|
+
change_id: Unique ID for this change.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Serialized resource change.
|
|
91
|
+
"""
|
|
92
|
+
changes: list[SerializedFieldChange] = []
|
|
93
|
+
|
|
94
|
+
for change in resource.changes:
|
|
95
|
+
changes.extend(SerializedFieldChange.from_field_change(change))
|
|
96
|
+
|
|
97
|
+
return cls(
|
|
98
|
+
id=change_id,
|
|
99
|
+
endpoint=endpoint,
|
|
100
|
+
change_type=resource.change_type,
|
|
101
|
+
severity=resource.severity.name,
|
|
102
|
+
resource_id=str(resource.resource_id),
|
|
103
|
+
changes=changes,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_change_result(cls, change_id: int, response: ChangeResult) -> "SerializedResourceChange":
|
|
108
|
+
"""Serialize from a change result (actual deployment).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
change_id: Unique ID for this change.
|
|
112
|
+
response: The change result from deployment.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Serialized resource change with deployment status.
|
|
116
|
+
"""
|
|
117
|
+
serialized_resource_change = cls.from_resource_change(
|
|
118
|
+
resource=response.change,
|
|
119
|
+
endpoint=response.endpoint,
|
|
120
|
+
change_id=change_id,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
serialized_resource_change.message = response.message
|
|
124
|
+
if not response.is_success:
|
|
125
|
+
serialized_resource_change.change_type = "failed"
|
|
126
|
+
|
|
127
|
+
return serialized_resource_change
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class SerializedChanges(BaseModel):
|
|
131
|
+
"""Container for all serialized changes."""
|
|
132
|
+
|
|
133
|
+
changes: list[SerializedResourceChange] = Field(default_factory=list)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_deployment_result(cls, result: DeploymentResult) -> "SerializedChanges":
|
|
137
|
+
"""Create SerializedChanges from a DeploymentResult.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
result: The deployment result to serialize changes from.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
SerializedChanges instance with all changes.
|
|
144
|
+
"""
|
|
145
|
+
serialized = cls()
|
|
146
|
+
|
|
147
|
+
if not result.responses:
|
|
148
|
+
serialized._add_from_dry_run(result)
|
|
149
|
+
else:
|
|
150
|
+
serialized._add_from_applied_changes(result.responses)
|
|
151
|
+
|
|
152
|
+
return serialized
|
|
153
|
+
|
|
154
|
+
def _add_from_dry_run(self, result: DeploymentResult) -> None:
|
|
155
|
+
"""Add changes from dry run deployment.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
result: The deployment result in dry run mode.
|
|
159
|
+
"""
|
|
160
|
+
# Iterate over each endpoint plan
|
|
161
|
+
for endpoint_plan in result.plan:
|
|
162
|
+
# Then per resource in the endpoint
|
|
163
|
+
for resource in endpoint_plan.resources:
|
|
164
|
+
# Then serialize individual resource change
|
|
165
|
+
serialized_resource_change = SerializedResourceChange.from_resource_change(
|
|
166
|
+
resource=resource,
|
|
167
|
+
endpoint=endpoint_plan.endpoint,
|
|
168
|
+
change_id=len(self.changes),
|
|
169
|
+
)
|
|
170
|
+
self.changes.append(serialized_resource_change)
|
|
171
|
+
|
|
172
|
+
def _add_from_applied_changes(self, applied_changes: AppliedChanges) -> None:
|
|
173
|
+
"""Add changes from actual deployment.
|
|
174
|
+
Args:
|
|
175
|
+
result: The deployment result from actual deployment.
|
|
176
|
+
"""
|
|
177
|
+
for response in itertools.chain(
|
|
178
|
+
applied_changes.created,
|
|
179
|
+
applied_changes.merged_updated,
|
|
180
|
+
applied_changes.deletions,
|
|
181
|
+
applied_changes.unchanged,
|
|
182
|
+
applied_changes.skipped,
|
|
183
|
+
):
|
|
184
|
+
self.changes.append(SerializedResourceChange.from_change_result(len(self.changes), response))
|
|
185
|
+
|
|
186
|
+
def model_dump_json_flat(self, **kwargs: Any) -> str:
|
|
187
|
+
"""Dump changes as JSON array without the wrapper key.
|
|
188
|
+
Returns:
|
|
189
|
+
JSON string of the changes array.
|
|
190
|
+
"""
|
|
191
|
+
if not self.changes:
|
|
192
|
+
return "[]"
|
|
193
|
+
|
|
194
|
+
iterator = (change.model_dump_json(**kwargs) for change in self.changes)
|
|
195
|
+
return f"[{','.join(iterator)}]"
|