Skip to content

Commit 0802366

Browse files
committed
WIP: fix client log links in test report/ui
1 parent f5a315d commit 0802366

File tree

7 files changed

+456
-67
lines changed

7 files changed

+456
-67
lines changed

cmd/hiveview/assets/lib/app-suite.js

Lines changed: 132 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,12 @@ function showSuiteData(data, suiteID) {
197197
{
198198
title: 'Logs',
199199
name: 'logs',
200-
data: 'clientInfo',
200+
data: null, // Use null to allow access to the full row data
201201
width: '20%',
202202
responsivePriority: 1,
203-
render: function (clientInfo, type, row) {
204-
return formatClientLogsList(data, row.testIndex, clientInfo);
203+
render: function (_, type, row) {
204+
// Pass both clientInfo and the row data to allow showing logs even if clientInfo is null
205+
return formatClientLogsList(data, row.testIndex, row.clientInfo);
205206
}
206207
},
207208
],
@@ -317,21 +318,112 @@ function deselectTest(row, closeDetails) {
317318
history.replaceState(null, null, '#');
318319
}
319320

320-
function testHasClients(testData) {
321-
return testData.clientInfo && Object.getOwnPropertyNames(testData.clientInfo).length > 0;
321+
function testHasClients(testData, suiteData) {
322+
// Check if the test has any associated clients directly
323+
if (testData.clientInfo && Object.getOwnPropertyNames(testData.clientInfo).length > 0) {
324+
return true;
325+
}
326+
327+
// Check for clientLogs in the test result (indicates shared client usage)
328+
if (testData.summaryResult && testData.summaryResult.clientLogs) {
329+
return Object.getOwnPropertyNames(testData.summaryResult.clientLogs).length > 0;
330+
}
331+
332+
// Check for shared clients at the suite level
333+
if (suiteData && suiteData.sharedClients && Object.getOwnPropertyNames(suiteData.sharedClients).length > 0) {
334+
return true;
335+
}
336+
337+
return false;
322338
}
323339

324340
// formatClientLogsList turns the clientInfo part of a test into a list of links.
325341
function formatClientLogsList(suiteData, testIndex, clientInfo) {
326342
let links = [];
327-
for (let instanceID in clientInfo) {
328-
let instanceInfo = clientInfo[instanceID];
329-
let logfile = routes.resultsRoot + instanceInfo.logFile;
330-
let url = routes.clientLog(suiteData.suiteID, suiteData.name, testIndex, logfile);
331-
let link = html.makeLink(url, instanceInfo.name);
332-
link.classList.add('log-link');
333-
links.push(link.outerHTML);
343+
let testData = suiteData.testCases[testIndex];
344+
345+
// Process regular clients from clientInfo
346+
if (clientInfo) {
347+
for (let instanceID in clientInfo) {
348+
let instanceInfo = clientInfo[instanceID];
349+
if (instanceInfo.logFile) {
350+
// For regular (non-shared) clients or for shared clients with client segments,
351+
// we use the logFile directly from clientInfo - this should point to either
352+
// the full log file or to the extracted segment file in shared_clients/
353+
let logfile = routes.resultsRoot + instanceInfo.logFile;
354+
let url = routes.clientLog(suiteData.suiteID, suiteData.name, testIndex, logfile);
355+
356+
// Add (shared) indicator to shared client names
357+
let name = instanceInfo.name;
358+
if (instanceInfo.isShared || instanceInfo.logFile.includes('shared_clients/')) {
359+
name += ' (shared)';
360+
}
361+
362+
let link = html.makeLink(url, name);
363+
link.classList.add('log-link');
364+
if (instanceInfo.isShared || instanceInfo.logFile.includes('shared_clients/')) {
365+
link.classList.add('shared-client-link');
366+
}
367+
links.push(link.outerHTML);
368+
}
369+
}
334370
}
371+
372+
// Check for shared client log segments
373+
if (testData && testData.summaryResult && testData.summaryResult.clientLogs) {
374+
for (let clientID in testData.summaryResult.clientLogs) {
375+
// Skip if we already have a log link for this client
376+
let alreadyHasLink = false;
377+
if (clientInfo) {
378+
for (let instanceID in clientInfo) {
379+
if (instanceID === clientID) {
380+
alreadyHasLink = true;
381+
break;
382+
}
383+
}
384+
}
385+
if (alreadyHasLink) continue;
386+
387+
// Find the shared client in the suite
388+
if (suiteData.sharedClients && suiteData.sharedClients[clientID]) {
389+
let sharedClient = suiteData.sharedClients[clientID];
390+
391+
// Look for the log segment file in the shared_clients directory
392+
// The pattern is: shared_clients/timestamp-clientName-clientID-testIndex.log
393+
let dirPath = 'shared_clients/';
394+
let pattern = `-${sharedClient.name}-${clientID}-test${testIndex}.log`;
395+
396+
// We would ideally search for matching files here, but since we can't
397+
// we'll just list the available client, linking to its original logfile
398+
// When clicking, the UI should dynamically try to locate the correct segment
399+
// We need to look for the segment file in the shared_clients directory
400+
// The pattern is now: shared_clients/timestamp-clientName-clientID-testIndex.log
401+
// But we can't search for it directly here, so we'll use the pattern to construct
402+
// a best guess of the filename based on our updated naming convention
403+
let logfile = routes.resultsRoot + sharedClient.logFile;
404+
let url = routes.clientLog(suiteData.suiteID, suiteData.name, testIndex, logfile);
405+
let link = html.makeLink(url, sharedClient.name + ' (shared)');
406+
link.classList.add('log-link');
407+
link.classList.add('shared-client-link');
408+
links.push(link.outerHTML);
409+
}
410+
}
411+
}
412+
413+
// If we still don't have any links but there are shared clients in the suite,
414+
// add links to the shared clients
415+
if (links.length === 0 && suiteData.sharedClients) {
416+
for (let instanceID in suiteData.sharedClients) {
417+
let instanceInfo = suiteData.sharedClients[instanceID];
418+
let logfile = routes.resultsRoot + instanceInfo.logFile;
419+
let url = routes.clientLog(suiteData.suiteID, suiteData.name, testIndex, logfile);
420+
let link = html.makeLink(url, instanceInfo.name + ' (shared)');
421+
link.classList.add('log-link');
422+
link.classList.add('shared-client-link');
423+
links.push(link.outerHTML);
424+
}
425+
}
426+
335427
return links.join(', ');
336428
}
337429

@@ -360,7 +452,7 @@ function formatTestDetails(suiteData, row) {
360452
p.innerHTML = formatTestStatus(d.summaryResult);
361453
container.appendChild(p);
362454
}
363-
if (!row.column('logs:name').responsiveHidden() && testHasClients(d)) {
455+
if (!row.column('logs:name').responsiveHidden() && testHasClients(d, suiteData)) {
364456
let p = document.createElement('p');
365457
p.innerHTML = '<b>Clients:</b> ' + formatClientLogsList(suiteData, d.testIndex, d.clientInfo);
366458
container.appendChild(p);
@@ -480,6 +572,33 @@ function formatTestLog(suiteData, testIndex, logData, container) {
480572
}
481573

482574
container.appendChild(output);
575+
576+
// Show client log segments if available
577+
if (d.summaryResult.clientLogs && Object.keys(d.summaryResult.clientLogs).length > 0) {
578+
let p = document.createElement('p');
579+
p.innerHTML = '<b>Client Log Segments:</b>';
580+
container.appendChild(p);
581+
582+
let logSegmentsDiv = document.createElement('div');
583+
logSegmentsDiv.classList.add('client-log-segments');
584+
585+
for (let clientID in d.summaryResult.clientLogs) {
586+
let segment = d.summaryResult.clientLogs[clientID];
587+
let clientName = clientID;
588+
589+
// Try to find a friendly name for the client
590+
if (suiteData.sharedClients && suiteData.sharedClients[clientID]) {
591+
clientName = suiteData.sharedClients[clientID].name || clientID;
592+
}
593+
594+
let segmentInfo = document.createElement('div');
595+
segmentInfo.classList.add('client-log-segment');
596+
segmentInfo.innerHTML = `<b>${clientName}:</b> Lines ${segment.start} to ${segment.end}`;
597+
logSegmentsDiv.appendChild(segmentInfo);
598+
}
599+
600+
container.appendChild(logSegmentsDiv);
601+
}
483602
}
484603

485604
function formatTestDetailLines(lines) {

cmd/hiveview/assets/lib/app-viewer.js

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,16 @@ $(document).ready(function () {
3333
showError('Invalid parameters! Missing \'suitefile\' or \'testid\' in URL.');
3434
return;
3535
}
36-
fetchTestLog(routes.resultsRoot + suiteFile, testIndex, line);
36+
// Pass clientID parameter if available (for shared client logs)
37+
let clientID = queryParam('clientid');
38+
fetchTestLog(routes.resultsRoot + suiteFile, testIndex, line, clientID);
3739
return;
3840
}
3941

4042
// Check for file name.
4143
let file = queryParam('file');
4244
if (file) {
45+
// Show the file directly - either a regular client log or a segment file
4346
$('#fileload').val(file);
4447
showText('Loading file...');
4548
fetchFile(file, line);
@@ -78,10 +81,10 @@ function setHL(num, scroll) {
7881
function showLinkBack(suiteID, suiteName, testID) {
7982
var text, url;
8083
if (testID) {
81-
text = 'Back to test ' + testID + ' in suite ‘' + suiteName + '';
84+
text = 'Back to test ' + testID + ' in suite \'' + suiteName + '\'';
8285
url = routes.testInSuite(suiteID, suiteName, testID);
8386
} else {
84-
text = 'Back to test suite ‘' + suiteName + '';
87+
text = 'Back to test suite \'' + suiteName + '\'';
8588
url = routes.suite(suiteID, suiteName);
8689
}
8790
$('#link-back').html(makeLink(url, text));
@@ -185,7 +188,7 @@ async function fetchFile(url, line /* optional jump to line */ ) {
185188
}
186189

187190
// fetchTestLog loads the suite file and displays the output of a test.
188-
async function fetchTestLog(suiteFile, testIndex, line) {
191+
async function fetchTestLog(suiteFile, testIndex, line, clientID) {
189192
let data;
190193
try {
191194
data = await load(suiteFile, 'json');
@@ -201,6 +204,8 @@ async function fetchTestLog(suiteFile, testIndex, line) {
201204
let test = data.testCases[testIndex];
202205
let name = test.name;
203206
let logtext;
207+
208+
// First check for regular test output
204209
if (test.summaryResult.details) {
205210
logtext = test.summaryResult.details;
206211
} else if (test.summaryResult.log) {
@@ -216,9 +221,73 @@ async function fetchTestLog(suiteFile, testIndex, line) {
216221
showError('Loading test log failed.', err);
217222
return;
218223
}
224+
}
225+
// Check for client logs
226+
else if (test.summaryResult.clientLogs) {
227+
// Use clientID passed to the function or from the URL parameters
228+
clientID = clientID || queryParam('clientid');
229+
if (clientID && test.summaryResult.clientLogs[clientID]) {
230+
// Get the client log segment
231+
let segment = test.summaryResult.clientLogs[clientID];
232+
233+
// Look for shared client info
234+
let clientInfo;
235+
if (test.clientInfo && test.clientInfo[clientID]) {
236+
clientInfo = test.clientInfo[clientID];
237+
} else if (data.sharedClients && data.sharedClients[clientID]) {
238+
clientInfo = data.sharedClients[clientID];
239+
}
240+
241+
if (clientInfo && clientInfo.logFile) {
242+
try {
243+
// Load the full client log file
244+
let url = routes.resultsRoot + clientInfo.logFile;
245+
showRawLink(url, 'raw client log');
246+
247+
// Load the entire file and extract the segment
248+
let fullLog = await load(url, 'text');
249+
250+
// Extract the segment based on byte offsets
251+
logtext = fullLog.substring(segment.start, segment.end);
252+
253+
// Add header to indicate this is a segment
254+
logtext = `--- Shared client log segment for ${clientInfo.name} (test ${testIndex}) ---\n` +
255+
`--- From position ${segment.start} to ${segment.end} ---\n\n` +
256+
logtext;
257+
} catch(err) {
258+
showError('Loading client log segment failed.', err);
259+
return;
260+
}
261+
} else {
262+
showError('Client information not found for: ' + clientID);
263+
return;
264+
}
265+
} else {
266+
// If clientID wasn't specified but clientLogs exist, show a list of available logs
267+
logtext = "This test used shared clients. Please select a client log to view:\n\n";
268+
269+
for (let id in test.summaryResult.clientLogs) {
270+
let clientName = id;
271+
if (data.sharedClients && data.sharedClients[id]) {
272+
clientName = data.sharedClients[id].name || id;
273+
} else if (test.clientInfo && test.clientInfo[id]) {
274+
clientName = test.clientInfo[id].name || id;
275+
}
276+
277+
logtext += `Client: ${clientName} (${id})\n`;
278+
279+
// Create a URL to view this specific client's log
280+
let params = new URLSearchParams(window.location.search);
281+
params.set('clientid', id);
282+
let clientLogUrl = window.location.pathname + '?' + params.toString();
283+
logtext += `View log: ${clientLogUrl}\n\n`;
284+
}
285+
}
219286
} else {
220287
showError('test has no details/log');
288+
return;
221289
}
290+
222291
showTitle('Test:', name);
223292
showText(logtext);
224293
setHL(line, true);
@@ -243,4 +312,4 @@ Prism.languages.log = {
243312
'boolean': /\b(?:true|false)\b/,
244313
'ip': /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/,
245314
'important': /\[[A-Z]+\]/
246-
};
315+
};

cmd/hiveview/assets/lib/app.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,26 @@ td.ellipsis {
254254
font-weight: bold;
255255
}
256256

257+
/* Styles for shared client links */
258+
.shared-client-link {
259+
font-style: italic;
260+
color: var(--bs-primary);
261+
}
262+
263+
/* Styles for client log segments */
264+
.client-log-segments {
265+
margin-top: 0.5rem;
266+
padding-left: 1rem;
267+
}
268+
269+
.client-log-segment {
270+
margin-bottom: 0.5rem;
271+
padding: 0.3rem 0.5rem;
272+
background: var(--bs-dark-bg-subtle);
273+
border-radius: 4px;
274+
font-size: 0.9em;
275+
}
276+
257277
.dataTable tr {
258278
scroll-margin: 8em 0 0 0;
259279
}

cmd/hiveview/assets/lib/routes.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@ export function testLog(suiteID, suiteName, testIndex) {
2020
return 'viewer.html?' + params.toString();
2121
}
2222

23-
export function clientLog(suiteID, suiteName, testIndex, file) {
23+
export function clientLog(suiteID, suiteName, testIndex, file, clientID) {
2424
let params = new URLSearchParams({
2525
'suiteid': suiteID,
2626
'suitename': suiteName,
2727
'testid': testIndex,
2828
'file': file,
2929
});
30+
31+
// If clientID is provided, add it to parameters for shared client logs
32+
if (clientID) {
33+
params.append('clientid', clientID);
34+
}
35+
3036
return 'viewer.html?' + params.toString();
3137
}
3238

0 commit comments

Comments
 (0)