@vizzly-testing/cli 0.35.3-beta.2 → 0.35.3-beta.3
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/dist/api/endpoints.js +1 -46
- package/dist/api/index.js +1 -1
- package/dist/cli.js +4 -4
- package/dist/commands/api.js +3 -3
- package/dist/commands/builds.js +12 -15
- package/dist/commands/comparisons.js +7 -9
- package/dist/commands/context.js +3 -24
- package/dist/commands/review.js +122 -35
- package/dist/commands/run.js +1 -3
- package/dist/commands/status.js +35 -79
- package/dist/commands/upload.js +1 -1
- package/dist/context/local-workspace-provider.js +36 -126
- package/dist/reporter/reporter-bundle.iife.js +49 -81
- package/dist/server/handlers/tdd-handler.js +2 -7
- package/dist/server/routers/dashboard.js +5 -1
- package/dist/tdd/runtime-context.js +0 -2
- package/dist/tdd/services/comparison-service.js +4 -68
- package/dist/tdd/tdd-service.js +7 -205
- package/dist/types/index.d.ts +1 -1
- package/dist/uploader/core.js +7 -8
- package/dist/utils/visual-context-normalizers.js +86 -139
- package/package.json +1 -1
- package/skills/vizzly/SKILL.md +4 -4
- package/skills/vizzly/references/dynamic-content.md +2 -2
- package/dist/tdd/core/hotspot-coverage.js +0 -112
- package/dist/tdd/core/region-coverage.js +0 -203
- package/dist/tdd/metadata/hotspot-metadata.js +0 -93
- package/dist/tdd/metadata/region-metadata.js +0 -56
package/dist/commands/status.js
CHANGED
|
@@ -49,67 +49,38 @@ async function fetchOptionalPreviewInfo(getPreviewInfo, client, buildId) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
export function normalizeBuildStatus(buildStatus) {
|
|
52
|
-
return buildStatus.build
|
|
52
|
+
return buildStatus.build;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Read
|
|
56
|
+
* Read the processing counts from the status response.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* becoming client-authored zeroes.
|
|
61
|
-
*
|
|
62
|
-
* @param {Object} status - Canonical status bundle or legacy build record.
|
|
63
|
-
* @returns {Object|undefined} API-provided processing facts when available.
|
|
58
|
+
* @param {Object} status - Status response.
|
|
59
|
+
* @returns {Object} API-provided processing facts.
|
|
64
60
|
*/
|
|
65
61
|
export function getProcessingStatus(status = {}) {
|
|
66
|
-
|
|
67
|
-
return status.processing;
|
|
68
|
-
}
|
|
69
|
-
let build = normalizeBuildStatus(status);
|
|
70
|
-
let processing = {};
|
|
71
|
-
let fields = [['total', build.screenshot_count], ['completed', build.completed_jobs], ['failed', build.failed_jobs], ['active', build.processing_screenshots]];
|
|
72
|
-
for (let [name, value] of fields) {
|
|
73
|
-
if (value != null) {
|
|
74
|
-
processing[name] = value;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return Object.keys(processing).length > 0 ? processing : undefined;
|
|
62
|
+
return status.processing;
|
|
78
63
|
}
|
|
79
64
|
|
|
80
65
|
/**
|
|
81
66
|
* Read comparison totals without deriving them from screenshot counts.
|
|
82
67
|
*
|
|
83
|
-
* @param {Object} status -
|
|
84
|
-
* @returns {Object
|
|
68
|
+
* @param {Object} status - Status response.
|
|
69
|
+
* @returns {Object} API-provided comparison facts.
|
|
85
70
|
*/
|
|
86
71
|
export function getComparisonStatus(status = {}) {
|
|
87
|
-
|
|
88
|
-
return status.comparisons;
|
|
89
|
-
}
|
|
90
|
-
let build = normalizeBuildStatus(status);
|
|
91
|
-
let comparisons = {};
|
|
92
|
-
let fields = [['total', build.total_comparisons], ['new', build.new_comparisons], ['changed', build.changed_comparisons], ['identical', build.identical_comparisons]];
|
|
93
|
-
for (let [name, value] of fields) {
|
|
94
|
-
if (value != null) {
|
|
95
|
-
comparisons[name] = value;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return Object.keys(comparisons).length > 0 ? comparisons : undefined;
|
|
72
|
+
return status.comparisons;
|
|
99
73
|
}
|
|
100
74
|
|
|
101
75
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* Processing status stays separate: a pending review never means a screenshot
|
|
105
|
-
* is still being processed.
|
|
76
|
+
* Read the build review state without mixing it with processing status.
|
|
106
77
|
*
|
|
107
|
-
* @param {Object} status -
|
|
78
|
+
* @param {Object} status - Status response.
|
|
108
79
|
* @returns {string|null} API-provided review state when available.
|
|
109
80
|
*/
|
|
110
81
|
export function getBuildReviewState(status = {}) {
|
|
111
82
|
let build = normalizeBuildStatus(status);
|
|
112
|
-
return getVisualReviewState(build)
|
|
83
|
+
return getVisualReviewState(build);
|
|
113
84
|
}
|
|
114
85
|
|
|
115
86
|
/**
|
|
@@ -132,13 +103,10 @@ export function createStatusSuggestedCommands(build = {}) {
|
|
|
132
103
|
}
|
|
133
104
|
|
|
134
105
|
/**
|
|
135
|
-
* Preserve the established JSON fields while exposing canonical status facts.
|
|
136
|
-
*
|
|
137
106
|
* Every lifecycle, processing, comparison, and review value comes directly
|
|
138
|
-
* from the API.
|
|
139
|
-
* rather than being presented as false certainty.
|
|
107
|
+
* from the API.
|
|
140
108
|
*
|
|
141
|
-
* @param {Object} status -
|
|
109
|
+
* @param {Object} status - Status response.
|
|
142
110
|
* @param {Object|null} previewInfo - Optional preview response.
|
|
143
111
|
* @returns {Object} Machine-readable status payload.
|
|
144
112
|
*/
|
|
@@ -146,7 +114,6 @@ export function createStatusData(status, previewInfo = null) {
|
|
|
146
114
|
let build = normalizeBuildStatus(status);
|
|
147
115
|
let processing = getProcessingStatus(status);
|
|
148
116
|
let comparisons = getComparisonStatus(status);
|
|
149
|
-
let visualReview = build.visual_review || status.visualReview?.build || status.visual_review;
|
|
150
117
|
return {
|
|
151
118
|
resource: status.resource,
|
|
152
119
|
schemaVersion: status.schema_version,
|
|
@@ -161,18 +128,14 @@ export function createStatusData(status, previewInfo = null) {
|
|
|
161
128
|
branch: build.branch,
|
|
162
129
|
commit: build.commit_sha,
|
|
163
130
|
commitMessage: build.commit_message,
|
|
164
|
-
screenshotsTotal: processing
|
|
131
|
+
screenshotsTotal: processing.total,
|
|
165
132
|
processing,
|
|
166
|
-
comparisonsTotal: comparisons
|
|
133
|
+
comparisonsTotal: comparisons.total,
|
|
167
134
|
comparisons,
|
|
168
|
-
newComparisons: comparisons
|
|
169
|
-
changedComparisons: comparisons
|
|
170
|
-
identicalComparisons: comparisons
|
|
171
|
-
|
|
172
|
-
review: status.review,
|
|
173
|
-
reviewFlow: status.reviewFlow,
|
|
174
|
-
visualReview,
|
|
175
|
-
approvalStatus: build.approval_status,
|
|
135
|
+
newComparisons: comparisons.new,
|
|
136
|
+
changedComparisons: comparisons.changed,
|
|
137
|
+
identicalComparisons: comparisons.identical,
|
|
138
|
+
visual_review: build.visual_review || null,
|
|
176
139
|
executionTime: build.execution_time_ms,
|
|
177
140
|
isBaseline: build.is_baseline,
|
|
178
141
|
userAgent: build.user_agent,
|
|
@@ -203,19 +166,18 @@ export function createBuildInfo(build) {
|
|
|
203
166
|
}
|
|
204
167
|
|
|
205
168
|
/**
|
|
206
|
-
* Format API comparison outcomes
|
|
169
|
+
* Format API comparison outcomes.
|
|
207
170
|
*
|
|
208
|
-
* @param {Object} status -
|
|
171
|
+
* @param {Object} status - Status response.
|
|
209
172
|
* @param {Object} colors - Output color helpers.
|
|
210
173
|
* @returns {string} Human-readable comparison summary.
|
|
211
174
|
*/
|
|
212
175
|
export function createComparisonStats(status, colors) {
|
|
213
|
-
let
|
|
214
|
-
let comparisons = getComparisonStatus(status) || {};
|
|
176
|
+
let comparisons = getComparisonStatus(status);
|
|
215
177
|
let stats = [];
|
|
216
|
-
let newCount = comparisons.new
|
|
217
|
-
let changedCount = comparisons.changed
|
|
218
|
-
let identicalCount = comparisons.identical
|
|
178
|
+
let newCount = comparisons.new;
|
|
179
|
+
let changedCount = comparisons.changed;
|
|
180
|
+
let identicalCount = comparisons.identical;
|
|
219
181
|
if (newCount > 0) {
|
|
220
182
|
stats.push(`${colors.brand.info(newCount)} new`);
|
|
221
183
|
}
|
|
@@ -229,45 +191,39 @@ export function createComparisonStats(status, colors) {
|
|
|
229
191
|
}
|
|
230
192
|
|
|
231
193
|
/**
|
|
232
|
-
* Build the
|
|
233
|
-
*
|
|
234
|
-
* Slug routes match the current app, while the project-ID route remains as a
|
|
235
|
-
* compatibility fallback for older build responses.
|
|
194
|
+
* Build the scoped app link when the API did not return one.
|
|
236
195
|
*
|
|
237
196
|
* @param {string} baseUrl - API or app base URL.
|
|
238
197
|
* @param {Object} build - Build record.
|
|
239
|
-
* @param {Object|null} scope -
|
|
198
|
+
* @param {Object|null} scope - Organization and project details.
|
|
240
199
|
* @returns {string|null} Build URL when the response has enough identity.
|
|
241
200
|
*/
|
|
242
201
|
export function createBuildUrl(baseUrl, build, scope = null) {
|
|
243
202
|
if (!baseUrl) {
|
|
244
203
|
return null;
|
|
245
204
|
}
|
|
246
|
-
let organizationSlug = scope?.organization?.slug
|
|
247
|
-
let projectSlug = scope?.project?.slug
|
|
205
|
+
let organizationSlug = scope?.organization?.slug;
|
|
206
|
+
let projectSlug = scope?.project?.slug;
|
|
248
207
|
let appBaseUrl = getAppBaseUrl(baseUrl);
|
|
249
208
|
if (organizationSlug && projectSlug) {
|
|
250
209
|
return `${appBaseUrl}/${organizationSlug}/${projectSlug}/builds/${build.id}`;
|
|
251
210
|
}
|
|
252
|
-
if (build.project_id) {
|
|
253
|
-
return `${appBaseUrl}/projects/${build.project_id}/builds/${build.id}`;
|
|
254
|
-
}
|
|
255
211
|
return null;
|
|
256
212
|
}
|
|
257
213
|
|
|
258
214
|
/**
|
|
259
|
-
* Preserve status failure behavior while
|
|
215
|
+
* Preserve status failure behavior while using the server's conclusion.
|
|
260
216
|
*
|
|
261
217
|
* Review-required and rejected builds keep their existing successful command
|
|
262
218
|
* exit behavior. Only build or processing failures produce a failing status.
|
|
263
219
|
*
|
|
264
|
-
* @param {Object} status -
|
|
220
|
+
* @param {Object} status - Status response.
|
|
265
221
|
* @returns {boolean} Whether human status should exit non-zero.
|
|
266
222
|
*/
|
|
267
223
|
export function shouldFailStatus(status) {
|
|
268
224
|
let build = normalizeBuildStatus(status);
|
|
269
225
|
let processing = getProcessingStatus(status);
|
|
270
|
-
return build.status === 'failed' || ['build_failed', 'processing_failed'].includes(status.conclusion) ||
|
|
226
|
+
return build.status === 'failed' || ['build_failed', 'processing_failed'].includes(status.conclusion) || processing.failed > 0;
|
|
271
227
|
}
|
|
272
228
|
function writeStatusTiming({
|
|
273
229
|
build,
|
|
@@ -314,7 +270,7 @@ function writeSuggestedCommands({
|
|
|
314
270
|
/**
|
|
315
271
|
* Add optional API facts without inventing placeholders for missing metadata.
|
|
316
272
|
*
|
|
317
|
-
* @param {Object} status -
|
|
273
|
+
* @param {Object} status - Status response.
|
|
318
274
|
* @returns {Object} Available verbose fields.
|
|
319
275
|
*/
|
|
320
276
|
function createVerboseInfo(status) {
|
|
@@ -348,7 +304,7 @@ function createVerboseInfo(status) {
|
|
|
348
304
|
/**
|
|
349
305
|
* Format only the processing facts supplied by the API.
|
|
350
306
|
*
|
|
351
|
-
* @param {Object|undefined} processing -
|
|
307
|
+
* @param {Object|undefined} processing - Processing counts.
|
|
352
308
|
* @returns {string} Human-readable processing summary.
|
|
353
309
|
*/
|
|
354
310
|
function formatProcessingStatus(processing) {
|
|
@@ -387,7 +343,7 @@ function writeHumanStatus({
|
|
|
387
343
|
let colors = output.getColors();
|
|
388
344
|
let comparisonStats = createComparisonStats(status, colors);
|
|
389
345
|
let processing = getProcessingStatus(status);
|
|
390
|
-
let screenshotsTotal = processing
|
|
346
|
+
let screenshotsTotal = processing.total;
|
|
391
347
|
let processingSummary = formatProcessingStatus(processing);
|
|
392
348
|
if (screenshotsTotal != null) {
|
|
393
349
|
output.labelValue('Screenshots', String(screenshotsTotal));
|
package/dist/commands/upload.js
CHANGED
|
@@ -262,7 +262,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
262
262
|
failed: buildResult.failedComparisons || 0,
|
|
263
263
|
new: buildResult.newComparisons || 0
|
|
264
264
|
},
|
|
265
|
-
|
|
265
|
+
visual_review: buildResult.visual_review,
|
|
266
266
|
executionTimeMs
|
|
267
267
|
});
|
|
268
268
|
output.cleanup();
|
|
@@ -43,7 +43,7 @@ function mapComparisonResult(status) {
|
|
|
43
43
|
}
|
|
44
44
|
return status || 'unknown';
|
|
45
45
|
}
|
|
46
|
-
function
|
|
46
|
+
function mapReviewState(status) {
|
|
47
47
|
if (status === 'failed' || status === 'new') {
|
|
48
48
|
return 'pending';
|
|
49
49
|
}
|
|
@@ -88,50 +88,6 @@ function resolveAssetReference(assetPath, snapshot) {
|
|
|
88
88
|
}
|
|
89
89
|
return assetPath;
|
|
90
90
|
}
|
|
91
|
-
function normalizeConfirmedRegions(regions = []) {
|
|
92
|
-
return regions.map((region, index) => ({
|
|
93
|
-
id: region.id || `local-region-${index}`,
|
|
94
|
-
x1: region.x1 ?? region.x ?? null,
|
|
95
|
-
y1: region.y1 ?? region.y ?? null,
|
|
96
|
-
x2: region.x2 ?? (region.x != null && region.width != null ? region.x + region.width : null),
|
|
97
|
-
y2: region.y2 ?? (region.y != null && region.height != null ? region.y + region.height : null),
|
|
98
|
-
label: region.label || null
|
|
99
|
-
}));
|
|
100
|
-
}
|
|
101
|
-
function mergeConfirmedRegions(snapshot, comparisonName, details = {}) {
|
|
102
|
-
let workspaceRegions = snapshot.regions?.[comparisonName]?.confirmed || [];
|
|
103
|
-
let detailRegions = details.confirmedRegions || [];
|
|
104
|
-
let merged = [...workspaceRegions, ...detailRegions];
|
|
105
|
-
let seen = new Set();
|
|
106
|
-
return normalizeConfirmedRegions(merged).filter(region => {
|
|
107
|
-
let key = `${region.label || ''}:${region.x1}:${region.y1}:${region.x2}:${region.y2}`;
|
|
108
|
-
if (seen.has(key)) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
seen.add(key);
|
|
112
|
-
return true;
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
function buildHotspotAnalysis(snapshot, comparisonName, details = {}) {
|
|
116
|
-
let hotspotMetadata = snapshot.hotspots?.[comparisonName] || null;
|
|
117
|
-
let hotspotAnalysis = details.hotspotAnalysis || null;
|
|
118
|
-
if (!hotspotMetadata && !hotspotAnalysis) {
|
|
119
|
-
return {
|
|
120
|
-
regions: [],
|
|
121
|
-
total_builds_analyzed: 0,
|
|
122
|
-
confidence: 'no_data',
|
|
123
|
-
confidence_score: null,
|
|
124
|
-
data_source: 'local_workspace'
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
regions: hotspotMetadata?.regions || [],
|
|
129
|
-
total_builds_analyzed: 1,
|
|
130
|
-
confidence: hotspotAnalysis?.confidence || hotspotMetadata?.confidence || 'workspace',
|
|
131
|
-
confidence_score: hotspotAnalysis?.confidenceScore || null,
|
|
132
|
-
data_source: 'local_workspace'
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
91
|
function buildComparisonLinks(snapshot, comparisonId) {
|
|
136
92
|
if (!snapshot.serverInfo?.port) {
|
|
137
93
|
return {};
|
|
@@ -160,7 +116,7 @@ function buildBuildSnapshot(snapshot) {
|
|
|
160
116
|
branch: 'local',
|
|
161
117
|
commit_sha: null,
|
|
162
118
|
commit_message: null,
|
|
163
|
-
|
|
119
|
+
review_state: snapshot.serverInfo ? 'pending' : 'approved',
|
|
164
120
|
status: snapshot.serverInfo ? 'running' : 'completed',
|
|
165
121
|
created_at: null
|
|
166
122
|
};
|
|
@@ -181,7 +137,7 @@ function buildBaselineSnapshot(snapshot) {
|
|
|
181
137
|
branch: metadata.branch || 'local',
|
|
182
138
|
commit_sha: metadata.buildInfo?.commitSha || null,
|
|
183
139
|
commit_message: metadata.buildInfo?.commitMessage || null,
|
|
184
|
-
|
|
140
|
+
review_state: metadata.buildInfo?.visual_review?.state || 'approved',
|
|
185
141
|
status: metadata.buildInfo?.completedAt ? 'completed' : 'local',
|
|
186
142
|
created_at: metadata.createdAt || null,
|
|
187
143
|
completed_at: metadata.buildInfo?.completedAt || null
|
|
@@ -192,7 +148,7 @@ function buildBaselineSnapshot(snapshot) {
|
|
|
192
148
|
}
|
|
193
149
|
function buildReviewState(build, reviewSummary) {
|
|
194
150
|
let reasons = [];
|
|
195
|
-
if (build.
|
|
151
|
+
if (build.review_state === 'pending') {
|
|
196
152
|
reasons.push('build_pending_approval');
|
|
197
153
|
}
|
|
198
154
|
if (reviewSummary.pending > 0) {
|
|
@@ -208,12 +164,10 @@ function buildReviewState(build, reviewSummary) {
|
|
|
208
164
|
function mapLocalComparison(snapshot, comparison) {
|
|
209
165
|
let details = snapshot.comparisonDetails[comparison.id] || {};
|
|
210
166
|
let comparisonName = comparison.originalName || comparison.name;
|
|
211
|
-
let confirmedRegions = mergeConfirmedRegions(snapshot, comparisonName, details);
|
|
212
|
-
let hotspotAnalysis = buildHotspotAnalysis(snapshot, comparisonName, details);
|
|
213
167
|
let properties = comparison.properties || {};
|
|
214
168
|
let buildSnapshot = buildBuildSnapshot(snapshot);
|
|
215
169
|
let result = mapComparisonResult(comparison.status);
|
|
216
|
-
let
|
|
170
|
+
let reviewState = mapReviewState(comparison.status);
|
|
217
171
|
let baselineBuildId = snapshot.baselineMetadata?.buildId || null;
|
|
218
172
|
let diffImageUrl = resolveAssetReference(comparison.diff, snapshot);
|
|
219
173
|
let diffRegions = details.diffClusters || [];
|
|
@@ -223,8 +177,8 @@ function mapLocalComparison(snapshot, comparison) {
|
|
|
223
177
|
screenshot_name: comparisonName,
|
|
224
178
|
status: comparison.status,
|
|
225
179
|
result,
|
|
226
|
-
|
|
227
|
-
needs_review:
|
|
180
|
+
review_state: reviewState,
|
|
181
|
+
needs_review: reviewState === 'pending' && ['changed', 'new'].includes(result),
|
|
228
182
|
build_id: buildSnapshot.id,
|
|
229
183
|
build_name: buildSnapshot.name,
|
|
230
184
|
build_branch: buildSnapshot.branch,
|
|
@@ -275,14 +229,11 @@ function mapLocalComparison(snapshot, comparison) {
|
|
|
275
229
|
} : null,
|
|
276
230
|
diff_lines: null,
|
|
277
231
|
fingerprint_hash: null,
|
|
278
|
-
fingerprint_data: null
|
|
279
|
-
hotspot_analysis: hotspotAnalysis,
|
|
280
|
-
region_analysis: details.regionAnalysis || null,
|
|
281
|
-
confirmed_regions: confirmedRegions
|
|
232
|
+
fingerprint_data: null
|
|
282
233
|
}
|
|
283
234
|
};
|
|
284
235
|
}
|
|
285
|
-
function
|
|
236
|
+
function formatLocalScreenshot(screenshot, baseline) {
|
|
286
237
|
if (!screenshot) return null;
|
|
287
238
|
return {
|
|
288
239
|
id: screenshot.id,
|
|
@@ -306,8 +257,8 @@ function projectLocalScreenshot(screenshot, baseline) {
|
|
|
306
257
|
} : null
|
|
307
258
|
};
|
|
308
259
|
}
|
|
309
|
-
function
|
|
310
|
-
let
|
|
260
|
+
function formatLocalDiff(diff, includeDiffs) {
|
|
261
|
+
let formatted = {
|
|
311
262
|
percentage: diff?.percentage ?? null,
|
|
312
263
|
changed_pixels: diff?.changed_pixels ?? null,
|
|
313
264
|
total_pixels: diff?.total_pixels ?? null,
|
|
@@ -317,32 +268,32 @@ function projectLocalDiff(diff, includeDiffs) {
|
|
|
317
268
|
region_count: Array.isArray(diff?.regions) ? diff.regions.length : null
|
|
318
269
|
};
|
|
319
270
|
if (includeDiffs) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
271
|
+
formatted.regions = diff?.regions || [];
|
|
272
|
+
formatted.cluster_metadata = diff?.cluster_metadata ?? null;
|
|
273
|
+
formatted.fingerprint_data = diff?.fingerprint_data ?? null;
|
|
274
|
+
formatted.diff_lines = diff?.diff_lines ?? [];
|
|
324
275
|
}
|
|
325
|
-
return
|
|
276
|
+
return formatted;
|
|
326
277
|
}
|
|
327
|
-
function
|
|
278
|
+
function formatLocalEvidence(comparison, includeDiffs) {
|
|
328
279
|
return {
|
|
329
280
|
type: 'comparison',
|
|
330
281
|
id: comparison.id,
|
|
331
282
|
screenshot_name: comparison.screenshot_name,
|
|
332
283
|
status: comparison.status,
|
|
333
284
|
result: comparison.result,
|
|
334
|
-
|
|
285
|
+
review_state: comparison.review_state,
|
|
335
286
|
needs_review: comparison.needs_review,
|
|
336
287
|
build_id: comparison.build_id,
|
|
337
288
|
build_name: comparison.build_name,
|
|
338
289
|
build_branch: comparison.build_branch,
|
|
339
290
|
build_commit_sha: comparison.build_commit_sha,
|
|
340
291
|
build_created_at: comparison.build_created_at,
|
|
341
|
-
screenshot:
|
|
342
|
-
diff:
|
|
292
|
+
screenshot: formatLocalScreenshot(comparison.screenshot, comparison.baseline),
|
|
293
|
+
diff: formatLocalDiff(comparison.diff, includeDiffs)
|
|
343
294
|
};
|
|
344
295
|
}
|
|
345
|
-
function
|
|
296
|
+
function formatLocalFocusedComparison(comparison, includeDiffs) {
|
|
346
297
|
if (includeDiffs) return comparison;
|
|
347
298
|
let analysis = comparison.analysis || {};
|
|
348
299
|
let {
|
|
@@ -354,11 +305,11 @@ function projectLocalFocusedComparison(comparison, includeDiffs) {
|
|
|
354
305
|
} = analysis;
|
|
355
306
|
return {
|
|
356
307
|
...comparison,
|
|
357
|
-
diff:
|
|
308
|
+
diff: formatLocalDiff(comparison.diff, false),
|
|
358
309
|
analysis: summaryAnalysis
|
|
359
310
|
};
|
|
360
311
|
}
|
|
361
|
-
function
|
|
312
|
+
function formatLocalHistoryItem(comparison) {
|
|
362
313
|
return {
|
|
363
314
|
id: comparison.id,
|
|
364
315
|
screenshot_name: comparison.screenshot_name,
|
|
@@ -369,7 +320,7 @@ function projectLocalHistoryItem(comparison) {
|
|
|
369
320
|
build_name: comparison.build_name,
|
|
370
321
|
build_branch: comparison.build_branch,
|
|
371
322
|
build_created_at: comparison.build_created_at,
|
|
372
|
-
screenshot:
|
|
323
|
+
screenshot: formatLocalScreenshot(comparison.screenshot, comparison.baseline),
|
|
373
324
|
diff: {
|
|
374
325
|
percentage: comparison.diff?.percentage ?? null,
|
|
375
326
|
fingerprint_hash: comparison.diff?.fingerprint_hash ?? null,
|
|
@@ -384,43 +335,19 @@ function compactLocalCollection(items) {
|
|
|
384
335
|
details_available: Array.isArray(items) && items.length > 0
|
|
385
336
|
};
|
|
386
337
|
}
|
|
387
|
-
function createLocalDynamicRegions(comparison = null) {
|
|
388
|
-
let confirmedRegions = comparison?.analysis?.confirmed_regions;
|
|
389
|
-
let hotspotAnalysis = comparison?.analysis?.hotspot_analysis;
|
|
390
|
-
return {
|
|
391
|
-
decision: null,
|
|
392
|
-
patterns: compactLocalCollection([]),
|
|
393
|
-
confirmed_regions: compactLocalCollection(confirmedRegions),
|
|
394
|
-
exclusions: {
|
|
395
|
-
total: null,
|
|
396
|
-
included: false,
|
|
397
|
-
details_available: false
|
|
398
|
-
},
|
|
399
|
-
hotspot_analysis: hotspotAnalysis ? {
|
|
400
|
-
total_builds_analyzed: hotspotAnalysis.total_builds_analyzed ?? null,
|
|
401
|
-
confidence: hotspotAnalysis.confidence ?? null,
|
|
402
|
-
confidence_score: hotspotAnalysis.confidence_score ?? null,
|
|
403
|
-
data_source: hotspotAnalysis.data_source ?? null,
|
|
404
|
-
coverage: hotspotAnalysis.coverage ?? null,
|
|
405
|
-
confirmed_region_coverage: hotspotAnalysis.confirmed_region_coverage ?? null
|
|
406
|
-
} : null
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
338
|
function createLocalSnapshotRevision(snapshot) {
|
|
410
339
|
let revisionInput = {
|
|
411
340
|
serverInfo: snapshot.serverInfo,
|
|
412
341
|
reportData: snapshot.reportData,
|
|
413
342
|
comparisonDetails: snapshot.comparisonDetails,
|
|
414
|
-
baselineMetadata: snapshot.baselineMetadata
|
|
415
|
-
hotspotFile: snapshot.hotspotFile,
|
|
416
|
-
regionFile: snapshot.regionFile
|
|
343
|
+
baselineMetadata: snapshot.baselineMetadata
|
|
417
344
|
};
|
|
418
345
|
return createHash('sha256').update(JSON.stringify(revisionInput)).digest('base64url');
|
|
419
346
|
}
|
|
420
347
|
function buildReviewSummary(comparisons = []) {
|
|
421
|
-
let approved = comparisons.filter(comparison =>
|
|
422
|
-
let rejected = comparisons.filter(comparison =>
|
|
423
|
-
let pending = comparisons.filter(comparison =>
|
|
348
|
+
let approved = comparisons.filter(comparison => mapReviewState(comparison.status) === 'approved').length;
|
|
349
|
+
let rejected = comparisons.filter(comparison => mapReviewState(comparison.status) === 'rejected').length;
|
|
350
|
+
let pending = comparisons.filter(comparison => mapReviewState(comparison.status) === 'pending').length;
|
|
424
351
|
return {
|
|
425
352
|
total: comparisons.length,
|
|
426
353
|
pending,
|
|
@@ -457,12 +384,8 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
457
384
|
serverInfo: readJson(join(vizzlyDir, 'server.json')),
|
|
458
385
|
reportData: normalizeReportData(readJson(join(vizzlyDir, 'report-data.json')) || createEmptyReportData()),
|
|
459
386
|
comparisonDetails: readJson(join(vizzlyDir, 'comparison-details.json')) || {},
|
|
460
|
-
baselineMetadata: readJson(join(vizzlyDir, 'baselines', 'metadata.json'))
|
|
461
|
-
hotspotFile: readJson(join(vizzlyDir, 'hotspots.json')),
|
|
462
|
-
regionFile: readJson(join(vizzlyDir, 'regions.json'))
|
|
387
|
+
baselineMetadata: readJson(join(vizzlyDir, 'baselines', 'metadata.json'))
|
|
463
388
|
};
|
|
464
|
-
snapshotCache.hotspots = snapshotCache.hotspotFile?.hotspots || null;
|
|
465
|
-
snapshotCache.regions = snapshotCache.regionFile?.regions || null;
|
|
466
389
|
return snapshotCache;
|
|
467
390
|
}
|
|
468
391
|
function isAvailable(snapshot = loadSnapshot()) {
|
|
@@ -486,7 +409,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
486
409
|
return Boolean(findComparison(snapshot, target));
|
|
487
410
|
}
|
|
488
411
|
if (command === 'screenshot') {
|
|
489
|
-
return Boolean(snapshot.reportData.comparisons.some(comparison => (comparison.originalName || comparison.name) === target)
|
|
412
|
+
return Boolean(snapshot.reportData.comparisons.some(comparison => (comparison.originalName || comparison.name) === target));
|
|
490
413
|
}
|
|
491
414
|
if (command === 'review-queue') {
|
|
492
415
|
return snapshot.reportData.comparisons.length > 0;
|
|
@@ -572,7 +495,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
572
495
|
throw createLocalWorkspaceError(`Local workspace context is only available for the active local build (${resolvedBuild.id})`);
|
|
573
496
|
}
|
|
574
497
|
let mappedComparisons = snapshot.reportData.comparisons.map(comparison => mapLocalComparison(snapshot, comparison));
|
|
575
|
-
let mappedScreenshots = mappedComparisons.map(comparison =>
|
|
498
|
+
let mappedScreenshots = mappedComparisons.map(comparison => formatLocalScreenshot(comparison.screenshot, comparison.baseline));
|
|
576
499
|
let reviewSummary = buildReviewSummary(snapshot.reportData.comparisons);
|
|
577
500
|
let reviewState = buildReviewState(resolvedBuild, reviewSummary);
|
|
578
501
|
let context = {
|
|
@@ -612,7 +535,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
612
535
|
let cursorQuery = `${query.details}:${limit}`;
|
|
613
536
|
let cursor = readLocalCursor(query.cursor, 'build_context', resolvedBuild.id, cursorQuery, revision, ['evidence']);
|
|
614
537
|
let includeDiffs = query.details === 'diffs';
|
|
615
|
-
let evidence = mappedComparisons.map(comparison =>
|
|
538
|
+
let evidence = mappedComparisons.map(comparison => formatLocalEvidence(comparison, includeDiffs));
|
|
616
539
|
return {
|
|
617
540
|
resource: 'build_context',
|
|
618
541
|
source: LOCAL_CONTEXT_SOURCE,
|
|
@@ -623,14 +546,6 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
623
546
|
summary: context.summary,
|
|
624
547
|
preview: null,
|
|
625
548
|
signature_properties: [],
|
|
626
|
-
dynamic_regions: {
|
|
627
|
-
exclusions: {
|
|
628
|
-
total: null,
|
|
629
|
-
included: false,
|
|
630
|
-
details_available: false
|
|
631
|
-
},
|
|
632
|
-
item_details_included: false
|
|
633
|
-
},
|
|
634
549
|
evidence: createLocalPage(evidence, {
|
|
635
550
|
limit,
|
|
636
551
|
offset: cursor?.offset || 0,
|
|
@@ -664,9 +579,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
664
579
|
comparison: mappedComparison,
|
|
665
580
|
history: {
|
|
666
581
|
similar_by_fingerprint: [],
|
|
667
|
-
recent_by_name: history
|
|
668
|
-
hotspot_analysis: buildHotspotAnalysis(snapshot, comparisonName, snapshot.comparisonDetails[comparison.id] || {}),
|
|
669
|
-
confirmed_regions: mergeConfirmedRegions(snapshot, comparisonName, snapshot.comparisonDetails[comparison.id] || {})
|
|
582
|
+
recent_by_name: history
|
|
670
583
|
},
|
|
671
584
|
review: {
|
|
672
585
|
review_summary: {
|
|
@@ -699,8 +612,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
699
612
|
scope: context.scope,
|
|
700
613
|
build: context.build,
|
|
701
614
|
signature_properties: [],
|
|
702
|
-
comparison:
|
|
703
|
-
dynamic_regions: createLocalDynamicRegions(context.comparison),
|
|
615
|
+
comparison: formatLocalFocusedComparison(context.comparison, query.details === 'diffs'),
|
|
704
616
|
history: {
|
|
705
617
|
active_stream: activeStream || null,
|
|
706
618
|
similar_by_fingerprint: createLocalPage([], {
|
|
@@ -712,7 +624,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
712
624
|
stream: 'similar_by_fingerprint',
|
|
713
625
|
revision
|
|
714
626
|
}),
|
|
715
|
-
recent_by_name: createLocalPage(history.map(
|
|
627
|
+
recent_by_name: createLocalPage(history.map(formatLocalHistoryItem), {
|
|
716
628
|
limit,
|
|
717
629
|
offset: recentOffset,
|
|
718
630
|
resource: 'comparison_context',
|
|
@@ -735,7 +647,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
735
647
|
function getScreenshotContext(screenshotName) {
|
|
736
648
|
let snapshot = loadSnapshot();
|
|
737
649
|
let matches = snapshot.reportData.comparisons.filter(comparison => (comparison.originalName || comparison.name) === screenshotName).map(comparison => mapLocalComparison(snapshot, comparison));
|
|
738
|
-
if (matches.length === 0
|
|
650
|
+
if (matches.length === 0) {
|
|
739
651
|
throw createLocalWorkspaceError(`No local screenshot context found for "${screenshotName}"`);
|
|
740
652
|
}
|
|
741
653
|
return {
|
|
@@ -745,8 +657,6 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
745
657
|
screenshot: {
|
|
746
658
|
name: screenshotName
|
|
747
659
|
},
|
|
748
|
-
hotspot_analysis: buildHotspotAnalysis(snapshot, screenshotName),
|
|
749
|
-
confirmed_regions: mergeConfirmedRegions(snapshot, screenshotName),
|
|
750
660
|
history: {
|
|
751
661
|
recent_comparisons: matches
|
|
752
662
|
}
|