Skip to content
Merged

m/p #47

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ All notable changes to the API and the widget are documented here. The format fo

### Added

- Feedback page redesign: screenshot first with the sender's message as a sticky, hideable card; technical details on a second tab with copy buttons for every value and log entry and "Copy report" as plain text; console errors highlighted and counted on the tab.
- Feedback page redesign: screenshot first with the sender's message as a sticky, hideable card; technical details on a second tab with copy buttons for every value and log entry and "Copy report" as plain text; console errors highlighted. Repeated console entries and failed requests are listed one by one (at most 500 per log), each with its own copy button and in "Copy all", instead of a single row with ×N; no count badge on the tab.
- Feedback pages behind signed, expiring links with all details, console and network log and the screenshot (inline and download).
- `feedback:link {id}` creates team links that can complete and reopen the entry (`--by` is logged, `--read-only`, `--expires`).
- The API returns a read-only share link after sending (`FEEDBACK_SHARE_LINK_MINUTES`, `0` turns it off); the widget shows it on the thank you screen with a copy button, `after_send` receives it as `url`.
Expand Down
24 changes: 15 additions & 9 deletions app/Http/Controllers/FeedbackPageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
*/
final class FeedbackPageController
{
/** Upper bound of rows per log once repeated entries are spread out. */
private const MAX_ROWS = 500;

public function __invoke(Request $request, FeedbackEntry $entry): Response
{
$expires = Carbon::createFromTimestamp($request->integer('expires'));
Expand All @@ -27,8 +30,8 @@ public function __invoke(Request $request, FeedbackEntry $entry): Response
$shot = $entry->screenshot()->select(['entry_id', 'mime', 'width', 'height', 'bytes'])->first();
$nonce = Str::random(32);
$environment = self::environment($entry);
$console = self::rows($entry->console_log, ['type', 'message', 'count', 'source', 'line', 'column', 'stack']);
$network = self::rows($entry->network_log, ['method', 'url', 'status', 'duration', 'count']);
$console = self::rows($entry->console_log, ['type', 'message', 'source', 'line', 'column', 'stack']);
$network = self::rows($entry->network_log, ['method', 'url', 'status', 'duration']);
$customData = $entry->custom_data === null || $entry->custom_data === [] ? null : (string) json_encode($entry->custom_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

return response()->view('feedback.show', [
Expand Down Expand Up @@ -133,21 +136,20 @@ private static function report(FeedbackEntry $entry, array $environment, array $
public static function consoleLine(array $row): string
{
$where = $row['source'] === '' ? '' : ' ('.$row['source'].($row['line'] === '' ? '' : ':'.$row['line']).($row['column'] === '' ? '' : ':'.$row['column']).')';
$times = (int) $row['count'] > 1 ? " ×{$row['count']}" : '';

return "[{$row['type']}] {$row['message']}{$where}{$times}".($row['stack'] === '' ? '' : "\n{$row['stack']}");
return "[{$row['type']}] {$row['message']}{$where}".($row['stack'] === '' ? '' : "\n{$row['stack']}");
}

/** @param array<string, string> $row */
public static function networkLine(array $row): string
{
$times = (int) $row['count'] > 1 ? " ×{$row['count']}" : '';

return trim("{$row['method']} {$row['url']} → {$row['status']}".($row['duration'] === '' ? '' : " ({$row['duration']} ms)").$times);
return trim("{$row['method']} {$row['url']} → {$row['status']}".($row['duration'] === '' ? '' : " ({$row['duration']} ms)"));
}

/**
* Logs come from the browser: every cell becomes a plain string.
* Logs come from the browser: every cell becomes a plain string. The
* widget folds identical consecutive entries into one with a count, the
* page lists every occurrence (at most MAX_ROWS per log).
*
* @param array<mixed>|null $log
* @param list<string> $keys
Expand All @@ -169,7 +171,11 @@ private static function rows(?array $log, array $keys): array
$row[$key] = is_scalar($value) ? (string) $value : '';
}

$rows[] = $row;
$times = max(1, is_numeric($item['count'] ?? null) ? (int) $item['count'] : 1);

for ($i = 0; $i < $times && count($rows) < self::MAX_ROWS; $i++) {
$rows[] = $row;
}
}

return $rows;
Expand Down
14 changes: 5 additions & 9 deletions resources/views/feedback/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
[role=tab][aria-selected=true] { color: var(--ink); }
[role=tab][aria-selected=true]::after { content: ""; position: absolute; left: 10px; right: 10px; bottom: -1px; height: 2px; border-radius: 2px; background: var(--ink); }
.count { font-size: 12px; padding: 1px 7px; border-radius: 999px; background: var(--sunken); color: var(--mute); }
.count.err { background: var(--err-bg); color: var(--err); }

main { max-width: 1400px; margin: 0 auto; padding: 20px; }
[role=tabpanel][hidden] { display: none; }
Expand Down Expand Up @@ -179,11 +178,6 @@
<button role="tab" id="tab-feedback" aria-controls="panel-feedback" aria-selected="true" type="button">Feedback</button>
<button role="tab" id="tab-technical" aria-controls="panel-technical" aria-selected="false" tabindex="-1" type="button">
Technical
@if ($consoleErrors > 0)
<span class="count err">{{ $consoleErrors }} {{ $consoleErrors === 1 ? 'error' : 'errors' }}</span>
@elseif ($console !== [])
<span class="count">{{ count($console) }}</span>
@endif
</button>
</div>
</header>
Expand Down Expand Up @@ -268,8 +262,8 @@
<li class="{{ in_array($row['type'], ['error', 'warn'], true) ? $row['type'] : '' }}">
<span class="level {{ $row['type'] }}">{{ $row['type'] !== '' ? $row['type'] : 'log' }}</span>
<div class="entry"><span class="entry-text">{{ $row['message'] }}</span>
@if ($row['source'] !== '' || (int) $row['count'] > 1)
<span class="entry-meta">{{ $row['source'] }}{{ $row['line'] !== '' ? ':'.$row['line'] : '' }}{{ $row['column'] !== '' ? ':'.$row['column'] : '' }}{{ (int) $row['count'] > 1 ? ' · ×'.$row['count'] : '' }}</span>
@if ($row['source'] !== '')
<span class="entry-meta">{{ $row['source'] }}{{ $row['line'] !== '' ? ':'.$row['line'] : '' }}{{ $row['column'] !== '' ? ':'.$row['column'] : '' }}</span>
@endif
@if ($row['stack'] !== '')
<details><summary>Stack trace</summary><pre>{{ $row['stack'] }}</pre></details>
Expand Down Expand Up @@ -297,7 +291,9 @@
<li>
<span class="level s{{ substr($row['status'], 0, 1) }}">{{ $row['status'] !== '' && $row['status'] !== '0' ? $row['status'] : 'ERR' }}</span>
<div class="entry"><span class="entry-text">{{ $row['method'] }} {{ $row['url'] }}</span>
<span class="entry-meta">{{ $row['duration'] !== '' ? $row['duration'].' ms' : '' }}{{ (int) $row['count'] > 1 ? ' · ×'.$row['count'] : '' }}</span>
@if ($row['duration'] !== '')
<span class="entry-meta">{{ $row['duration'] }} ms</span>
@endif
</div>
<button class="icon-btn" type="button" data-copy="{{ \App\Http\Controllers\FeedbackPageController::networkLine($row) }}" aria-label="Copy request" title="Copy"><svg><use href="#i-copy"/></svg></button>
</li>
Expand Down
2 changes: 1 addition & 1 deletion tests/Browser/FeedbackPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function sentFeedback(FeedbackProject $project): FeedbackEntry
->assertSee('Die Schaltfläche «Speichern» reagiert nicht.')
->assertSee('Anna Muster')
->assertSee('anna@beispiel.ch')
->assertSee('1 console error')
->assertSee('2 console errors')
->assertVisible('img[alt^="Screenshot of"]')
->assertNoJavaScriptErrors();

Expand Down
10 changes: 7 additions & 3 deletions tests/Feature/FeedbackLinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,13 @@ function entryWithShot(FeedbackProject $project, array $attributes = []): Feedba
->assertSee('https://www.beispiel.ch/zwei')
->assertSee('<dt>Viewport</dt>', false)
->assertSee('role="tab" id="tab-technical"', false)
->assertSee('1 error')
->assertDontSee('<span class="count err">', false)
->assertSee('2 console errors')
->assertSee('Console <span class="count">2</span>', false)
->assertSee('Copy report')
->assertSee('data-copy="[error] Boom ×2"', false)
->assertSee('data-copy="[error] Boom"', false)
->assertSee('data-copy="[error] Boom'."\n".'[error] Boom"', false)
->assertDontSee('×2')
->assertSee('POST https://api.beispiel.ch/save → 500 (120 ms)')
->assertSee('1541 × 947')
->assertSee('Europe/Zurich')
Expand All @@ -119,7 +123,7 @@ function entryWithShot(FeedbackProject $project, array $attributes = []): Feedba
->toContain("Feedback {$entry->id} (Alpha)")
->toContain('Die Schaltfläche «Speichern» reagiert nicht.')
->toContain('Viewport: 1541 × 947')
->toContain("Console:\n[error] Boom ×2")
->toContain("Console:\n[error] Boom\n[error] Boom\n")
->toContain("Network:\nPOST https://api.beispiel.ch/save → 500 (120 ms)")
->toContain('"plan": "pro"');
});
Expand Down
Loading