8000 feature #42195 [WebProfilerBundle] Redesigned the log section (javier… · symfony/symfony@c1c973c · GitHub
[go: up one dir, main page]

Skip to content

Commit c1c973c

Browse files
committed
feature #42195 [WebProfilerBundle] Redesigned the log section (javiereguiluz)
This PR was squashed before being merged into the 5.4 branch. Discussion ---------- [WebProfilerBundle] Redesigned the log section | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #42155 | License | MIT | Doc PR | - This PR is not fully polished, but it's ready for review, both code/design and UX/usability. The main missing feature is dark mode styles. ### Before The logs were divided in sections (that's the main issue reported in #42155). If the app had errors, we displayed errors section first: ![before-error](https://user-images.githubusercontent.com/73419/126186615-8407abe3-e54f-41e7-bf43-f1aaf09ebdf3.png) If there are no errors but there are deprecations, we display that section: ![before-deprecation](https://user-images.githubusercontent.com/73419/126186625-3e5915eb-a824-4497-8f95-aeaf81d003cf.png) Abut filtering data, it depends on each section. For example, some allow to filter by log channel: ![before-filter-channel](https://user-images.githubusercontent.com/73419/126186644-49f039c8-d7ce-4790-abb1-c311a57111e7.gif) And others allow to filter by priority: ![before-filter-priority](https://user-images.githubusercontent.com/73419/126186656-2447dc54-91eb-4b64-bd5d-c35e4aaedaaa.gif) ### After This PR proposes to display a single table with all the logs (and separate the "container compilation" logs because they are a bit different than the others): ![after-logs](https://user-images.githubusercontent.com/73419/126186675-d9df2990-0e3c-491a-b331-4c022b64adbe.png) **Question**: log timestamps now display milliseconds. Do you like this or is it enough with seconds? Deprecations and errors are now more highlighted on each row (this needs a bit more polishing): ![after-deprecation-error](https://user-images.githubusercontent.com/73419/126186699-220055ef-1d57-4e5c-95dc-791238dc22b3.png) Filters are now super dynamic and always available: ![after-filters](https://user-images.githubusercontent.com/73419/126186731-d147e642-d7ab-4047-845c-3943187a1f00.gif) Container logs are still available at the bottom of the page: ![after-container](https://user-images.githubusercontent.com/73419/126186743-02ced909-2871-4491-b764-46194f89287a.gif) So, what do you think? Commits ------- bcb3329 [WebProfilerBundle] Redesigned the log section
2 parents 4c11c62 + bcb3329 commit c1c973c

File tree

7 files changed

+567
-271
lines changed

7 files changed

+567
-271
lines changed

src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,17 @@ public function __invoke(array $record)
3232
{
3333
$hash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
3434

35+
$timestamp = $timestampRfc3339 = false;
36+
if ($record['datetime'] instanceof \DateTimeInterface) {
37+
$timestamp = $record['datetime']->getTimestamp();
38+
$timestampRfc3339 = $record['datetime']->format(\DateTimeInterface::RFC3339_EXTENDED);
39+
} elseif (false !== $timestamp = strtotime($record['datetime'])) {
40+
$timestampRfc3339 = (new \DateTimeImmutable($record['datetime']))->format(\DateTimeInterface::RFC3339_EXTENDED);
41+
}
42+
3543
$this->records[$hash][] = [
36-
'timestamp' => $record['datetime'] instanceof \DateTimeInterface ? $record['datetime']->getTimestamp() : strtotime($record['datetime']),
44+
'timestamp' => $timestamp,
45+
'timestamp_rfc3339' => $timestampRfc3339,
3746
'message' => $record['message'],
3847
'priority' => $record['level'],
3948
'priorityName' => $record['level_name'],

src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,30 @@ public function providerDatetimeFormatTests(): array
4343
];
4444
}
4545

46+
/**
47+
* @dataProvider providerDatetimeRfc3339FormatTests
48+
*/
49+
public function testDatetimeRfc3339Format(array $record, $expectedTimestamp)
50+
{
51+
$processor = new DebugProcessor();
52+
$processor($record);
53+
54+
$records = $processor->getLogs();
55+
self::assertCount(1, $records);
56+
self::assertSame($expectedTimestamp, $records[0]['timestamp_rfc3339']);
57+
}
58+
59+
public function providerDatetimeRfc3339FormatTests(): array
60+
{
61+
$record = $this->getRecord();
62+
63+
return [
64+
[array_merge($record, ['datetime' => new \DateTime('2019-01-01T00:01:00+00:00')]), '2019-01-01T00:01:00.000+00:00'],
65+
[array_merge($record, ['datetime' => '2019-01-01T00:01:00+00:00']), '2019-01-01T00:01:00.000+00:00'],
66+
[array_merge($record, ['datetime' => 'foo']), false],
67+
];
68+
}
69+
4670
public function testDebugProcessor()
4771
{
4872
$processor = new DebugProcessor();

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig

Lines changed: 182 additions & 175 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Loading

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig

Lines changed: 66 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -709,104 +709,78 @@ if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') {
709709
}
710710
},
711711
712-
createFilters: function() {
713-
document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
714-
var filters = filter.closest('[data-filters]'),
715-
type = 'choice',
716-
name = filter.dataset.filter,
717-
ucName = name.charAt(0).toUpperCase()+name.slice(1),
718-
list = document.createElement('ul'),
719-
values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
720-
labels = {},
721-
defaults = null,
722-
indexed = {},
723-
processed = {};
724-
if (typeof values === 'string') {
725-
type = 'level';
726-
labels = values.split(',');
727-
values = values.toLowerCase().split(',');
728-
defaults = values.length - 1;
729-
}
730-
addClass(list, 'filter-list');
731-
addClass(list, 'filter-list-'+type);
732-
values.forEach(function (value, i) {
733-
if (value instanceof HTMLElement) {
734-
value = value.dataset['filter'+ucName];
735-
}
736-
if (value in processed) {
737-
return;
738-
}
739-
var option = document.createElement('li'),
740-
label = i in labels ? labels[i] : value,
741-
active = false,
742-
matches;
743-
if ('' === label) {
744-
option.innerHTML = '<em>(none)</em>';
745-
} else {
746-
option.innerText = label;
747-
}
748-
option.dataset.filter = value;
749-
option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
750-
indexed[value] = i;
751-
list.appendChild(option);
752-
addEventListener(option, 'click', function () {
753-
if ('choice' === type) {
754-
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
755-
if (option.dataset.filter === row.dataset['filter'+ucName]) {
756-
toggleClass(row, 'filter-hidden-'+name);
757-
}
758-
});
759-
toggleClass(option, 'active');
760-
} else if ('level' === type) {
761-
if (i === this.parentNode.querySelectorAll('.active').length - 1) {
762-
return;
763-
}
764-
this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
765-
if (j <= i) {
766-
addClass(currentOption, 'active');
767-
if (i === j) {
768-
addClass(currentOption, 'last-active');
769-
} else {
770-
removeClass(currentOption, 'last-active');
771-
}
772-
} else {
773-
removeClass(currentOption, 'active');
774-
removeClass(currentOption, 'last-active');
775-
}
776-
});
777-
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
778-
if (i < indexed[row.dataset['filter'+ucName]]) {
779-
addClass(row, 'filter-hidden-'+name);
780-
} else {
781-
removeClass(row, 'filter-hidden-'+name);
782-
}
783-
});
784-
}
712+
initializeLogsTable: function() {
713+
Sfjs.updateLogsTable();
714+
715+
document.querySelectorAll('.log-filter input').forEach((input) => {
716+
input.addEventListener('change', () => { Sfjs.updateLogsTable(); });
717+
});
718+
719+
document.querySelectorAll('.filter-select-all-or-none a').forEach((link) => {
720+
link.addEventListener('click', () => {
721+
const selectAll = link.classList.contains('select-all');
722+
link.closest('.log-filter-content').querySelectorAll('input').forEach((input) => {
723+
input.checked = selectAll;
785724
});
786-
if ('choice' === type) {
787-
active = null === defaults || 0 <= defaults.indexOf(value);
788-
} else if ('level' === type) {
789-
active = i <= defaults;
790-
if (active && i === defaults) {
791-
addClass(option, 'last-active');
792-
}
793-
}
794-
if (active) {
795-
addClass(option, 'active');
796-
} else {
797-
filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
798-
toggleClass(row, 'filter-hidden-'+name);
799-
});
725+
726+
Sfjs.updateLogsTable();
727+
});
728+
});
729+
730+
document.body.addEventListener('click', (event) => {
731+
document.querySelectorAll('details.log-filter').forEach((filterElement) => {
732+
if (!filterElement.contains(event.target) && filterElement.open) {
733+
filterElement.open = false;
800734
}
801-
processed[value] = true;
802735
});
736+
});
737+
},
738+
739+
updateLogsTable: function() {
740+
const selectedType = document.querySelector('#log-filter-type input:checked').value;
741+
const priorities = document.querySelectorAll('#log-filter-priority input');
742+
const selectedPriorities = Array.from(priorities).filter((input) => input.checked).map((input) => input.value);
743+
const channels = document.querySelectorAll('#log-filter-channel input');
744+
const selectedChannels = Array.from(channels).filter((input) => input.checked).map((input) => input.value);
745+
746+
const logs = document.querySelector('table.logs');
747+
if (null === logs) {
748+
return;
749+
}
750+
751+
// hide rows that don't match the current filters
752+
let numVisibleRows = 0;
753+
logs.querySelectorAll('tbody tr').forEach((row) => {
754+
if ('all' !== selectedType && selectedType !== row.getAttribute('data-type')) {
755+
row.style.display = 'none';
756+
return;
757+
}
803758
804-
if (1 < list.childNodes.length) {
805-
filter.appendChild(list);
806-
filter.dataset.filtered = '';
759+
if (false === selectedPriorities.includes(row.getAttribute('data-priority'))) {
760+
row.style.display = 'none';
761+
return;
807762
}
763+
764+
if ('' !== row.getAttribute('data-channel') && false === selectedChannels.includes(row.getAttribute('data-channel'))) {
765+
row.style.display = 'none';
766+
return;
767+
}
768+
769+
row.style.display = 'table-row';
770+
numVisibleRows++;
808771
});
809-
}
772+
773+
document.querySelector('table.logs').style.display = 0 === numVisibleRows ? 'none' : 'table';
774+
document.querySelector('.no-logs-message').style.display = 0 === numVisibleRows ? 'block' : 'none';
775+
776+
// update the selected totals of all filters
777+
document.querySelector('#log-filter-priority .filter-active-num').innerText = (priorities.length === selectedPriorities.length) ? 'All' : selectedPriorities.length;
778+
document.querySelector('#log-filter-channel .filter-active-num').innerText = (channels.length === selectedChannels.length) ? 'All' : selectedChannels.length;
779+
780+
// update the currently selected "log type" tab
781+
document.querySelectorAll('#log-filter-type li').forEach((tab) => tab.classList.remove('active'));
782+
document.querySelector(`#log-filter-type input[value="${selectedType}"]`).parentElement.classList.add('active');
783+
},
810784
};
811785
})();
812786

0 commit comments

Comments
 (0)
0