diff --git a/CHANGELOG.md b/CHANGELOG.md index b45093e..9d47991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## Unreleased * Local profiler: - * Filter by section kind in sections breakdown + * Filter sections by kind in sections breakdown + * Filter sections by file name ## 1.11.1 (2026-03-16) diff --git a/lib/rorvswild/local/javascript/local.js b/lib/rorvswild/local/javascript/local.js index 2a0e291..28dc0f9 100644 --- a/lib/rorvswild/local/javascript/local.js +++ b/lib/rorvswild/local/javascript/local.js @@ -13,14 +13,14 @@ RorVsWild.Local = function(container) { RorVsWild.Local.prototype.getRequests = function(callback) { this.getJson("/rorvswild/requests.json", function(data) { - this.requests = data.map(function(attributes) { return new RorVsWild.Local.Request(attributes) }) + this.requests = data.map(function(attributes) { return new RorVsWild.Local.Execution(attributes) }) callback() }.bind(this)) } RorVsWild.Local.prototype.getJobs = function(callback) { this.getJson("/rorvswild/jobs.json", function(data) { - this.jobs = data.map(function(attributes) { return new RorVsWild.Local.Request(attributes) }) + this.jobs = data.map(function(attributes) { return new RorVsWild.Local.Execution(attributes) }) callback() }.bind(this)) } @@ -54,7 +54,6 @@ RorVsWild.Local.prototype.getJson = function(path, callback) { RorVsWild.Local.prototype.render = function(view) { this.view = view Barber.render("RorVsWild.Local", this, this.root) - Prism.highlightAllUnder(this.root) } RorVsWild.Local.prototype.renderBody = function() { @@ -70,59 +69,20 @@ RorVsWild.Local.prototype.toggle = function(event) { this.active ? this.collapse() : this.expand(event) } -RorVsWild.Local.prototype.toggleCommand = function(event) { - document.querySelector(event.currentTarget.dataset.target).classList.toggle("is-open") -} - -RorVsWild.Local.prototype.resetLowImpactSections = function() { - var button = document.querySelector("[data-rorvswild-local-toggle-low-impact]") - document.querySelectorAll("[data-rorvswild-local-low-impact]").forEach(function(section) { - section.classList.add("is-hidden") - }) - if (button) button.textContent = "↓ View low impact sections" -} - -RorVsWild.Local.prototype.toggleLowImpactSections = function(event) { - var sections = document.querySelectorAll("[data-rorvswild-local-low-impact]") - var isHidden = sections[0].classList.contains("is-hidden") - - if (isHidden) { - sections.forEach(function(section) { section.classList.remove("is-hidden") }) - event.currentTarget.textContent = "↑ Hide low impact sections" - } else { - this.resetLowImpactSections() - } -} - -RorVsWild.Local.prototype.filterByKind = function(event) { - var li = event.currentTarget - var kind = li.dataset.kind - var isActive = li.classList.contains("is-active") - var toggleButton = document.querySelector("[data-rorvswild-local-toggle-low-impact]") - - document.querySelectorAll("[data-rorvswild-local-breakdown-item]").forEach(function(el) { - el.classList.remove("is-active") - }) - - var sections = document.querySelectorAll("[data-rorvswild-local-section]") - - if (isActive) { - this.resetLowImpactSections() - sections.forEach(function(section) { - if (!section.hasAttribute("data-rorvswild-local-low-impact")) section.classList.remove("is-hidden") - }) - if (toggleButton) toggleButton.style.display = "" - } else { - li.classList.add("is-active") - sections.forEach(function(section) { - if (section.dataset.kind === kind) { - section.classList.remove("is-hidden") - } else { - section.classList.add("is-hidden") - } - }) - if (toggleButton) toggleButton.style.display = "none" +RorVsWild.Local.highlightMatches = function(text, query) { + if (query.length < 1) + return Mustache.escape(text) + var lower = text.toLowerCase() + var result = "", start = 0, index + while ((index = lower.indexOf(query, start)) !== -1) { + result += Mustache.escape(text.slice(start, index)) + result += '' + result += Mustache.escape(text.slice(index, index + query.length)) + result += "" + start = index + query.length } + result += Mustache.escape(text.slice(start)) + return result } RorVsWild.Local.prototype.expand = function() { @@ -165,11 +125,17 @@ RorVsWild.Local.formatDateTime = function(date) { return [date.getDate(), months[date.getMonth()], hours + ":" + minutes].join(" ") } +RorVsWild.Local.prototype.goToExecutionDetail = function(execution) { + this.currentExecution = execution + execution.resetSectionFilters() + execution.loadSections() + this.render("Execution") + execution.renderSections() +} + RorVsWild.Local.prototype.goToRequestDetail = function(event) { - this.root.dataset.tab = "requests" var uuid = event.currentTarget.dataset.uuid - this.currentRequest = this.requests.find(function(req) { return req.uuid == uuid }) - this.render("RequestDetail") + this.goToExecutionDetail(this.requests.find(function(req) { return req.uuid == uuid })) } RorVsWild.Local.prototype.goToRequestIndex = function(event) { @@ -184,8 +150,7 @@ RorVsWild.Local.prototype.goToJobIndex = function(event) { RorVsWild.Local.prototype.goToJobDetail = function(event) { var uuid = event.currentTarget.dataset.uuid - this.currentJob = this.jobs.find(function(job) { return job.uuid == uuid }) - this.render("JobDetail") + this.goToExecutionDetail(this.jobs.find(function(job) { return job.uuid == uuid })) } RorVsWild.Local.prototype.goToErrors = function(event) { @@ -197,6 +162,7 @@ RorVsWild.Local.prototype.goToErrorDetail = function(event) { var uuid = event.currentTarget.dataset.uuid this.currentError = this.errors.find(function(err) { return err.uuid == uuid }) this.render("ErrorDetail") + Prism.highlightAllUnder(this.root) } RorVsWild.Local.prototype.containerClass = function() { @@ -226,24 +192,48 @@ RorVsWild.Local.nextId = function() { return RorVsWild.Local.lastId += 1 } -RorVsWild.Local.Request = function(data) { +RorVsWild.Local.Execution = function(data) { this.data = data this.uuid = data.uuid this.name = data.name this.path = data.path this.queuedAt = RorVsWild.Local.formatDateTime(new Date(data.queued_at)) + this.sections = [] } -RorVsWild.Local.Request.prototype.runtime = function() { +RorVsWild.Local.Execution.prototype.runtime = function() { return RorVsWild.Local.relevantRounding(this.data.runtime) } -RorVsWild.Local.Request.prototype.sections = function() { - return this.data.sections.map(function(section) { +RorVsWild.Local.Execution.prototype.resetSectionFilters = function() { + this.sectionKindFilter = null + this.sectionQueryFilter = "" + this.lowImpactExpanded = false +} + +RorVsWild.Local.Execution.prototype.isFilteringSections = function() { + return this.sectionKindFilter != null || this.sectionQueryFilter.length > 0 +} + +RorVsWild.Local.Execution.prototype.filterSections = function() { + var kind = this.sectionKindFilter + var query = this.sectionQueryFilter + var filtering = this.isFilteringSections() + + return this.sections.filter(function(section) { + return (!kind || section.kind === kind) + && (query.length < 1 || section.location.toLowerCase().indexOf(query) !== -1) + && (filtering || this.lowImpactExpanded || !section.isLowImpact) + }.bind(this)) +} + +RorVsWild.Local.Execution.prototype.loadSections = function() { + this.sections = this.data.sections.map(function(section) { var runtime = (section.total_runtime - section.children_runtime) var impactValue = runtime * 100 / this.data.runtime return { id: RorVsWild.Local.nextId(), + isOpen: false, impact: RorVsWild.Local.formatImpact(impactValue), isLowImpact: impactValue < 1, language: RorVsWild.Local.kindToLanguage(section.kind), @@ -258,27 +248,92 @@ RorVsWild.Local.Request.prototype.sections = function() { kind: section.kind.substring(0, 7), file: section.file, line: section.line, - isLineRelevant: section.line > 0, - location: section.kind == "view" ? section.file : section.file + ":" + section.line, + location: section.file + (section.line > 0 ? ":" + section.line : ""), locationUrl: RorVsWild.Local.pathToUrl(this.data.environment.cwd, section.file, section.line), isAsync: RorVsWild.Local.relevantRounding(section.async_runtime) > 0, } }.bind(this)).sort(function(a, b) { return b.selfRuntime - a.selfRuntime }) } -RorVsWild.Local.Request.prototype.hasLowImpactSections = function() { - return this.sections().some(function(section) { return section.isLowImpact }) -} +RorVsWild.Local.Execution.prototype.sectionsImpactPerKind = function() { + if (!this.sections.length) return [] -RorVsWild.Local.Request.prototype.sectionsImpactPerKind = function() { var total = 0 - var perKind = this.sections().reduce(function(object, section) { + var perKind = this.sections.reduce(function(object, section) { object[section.kind] = (object[section.kind] || 0) + section.runtime total += section.runtime return object }, {}) return Object.entries(perKind).sort(function(a, b) { return b[1] - a[1] }) - .map(function(item) { return {kind: item[0], impact: Math.round((item[1] / total * 100) * 10) / 10} }) + .map(function(item) { + return { + kind: item[0], + impact: Math.round((item[1] / total * 100) * 10) / 10, + isActive: item[0] === this.sectionKindFilter + } + }.bind(this)) +} + +RorVsWild.Local.Execution.prototype.showLowImpactToggle = function() { + return !this.isFilteringSections() && this.sections.some(function(section) { return section.isLowImpact }) +} + +RorVsWild.Local.Execution.prototype.lowImpactToggleLabel = function() { + return this.lowImpactExpanded ? "↑ Hide low impact sections" : "↓ View low impact sections" +} + +RorVsWild.Local.Execution.prototype.renderSectionHeader = function() { + var container = document.querySelector("#rorvswild-local-sections-header") + if (!container) return + + Barber.render("RorVsWild.Local.Sections.Header", this, container) +} + +RorVsWild.Local.Execution.prototype.renderSectionList = function() { + (this.filteredSections = this.filterSections()).forEach(function(section) { + section.locationHtml = RorVsWild.Local.highlightMatches(section.location, this.sectionQueryFilter) + }.bind(this)) + + var container = document.querySelector("#rorvswild-local-sections-list") + Barber.render("RorVsWild.Local.Sections.List", this, container) + + this.filteredSections.forEach(function(section) { + section.isOpen && Prism.highlightAllUnder(container.querySelector("#section-" + section.id)) + }) +} + +RorVsWild.Local.Execution.prototype.renderSections = function() { + this.renderSectionHeader() + this.renderSectionList() +} + +RorVsWild.Local.Execution.prototype.toggleSection = function(event) { + var id = parseInt(event.currentTarget.dataset.sectionId, 10) + var section = this.sections.find(function(section) { return section.id === id }) + if (!section) return + + section.isOpen = !section.isOpen + var element = event.currentTarget.closest(".rorvswild-local-panel__details__section") + element.classList.toggle("is-open", section.isOpen) + if (section.isOpen) + Prism.highlightAllUnder(element) +} + +RorVsWild.Local.Execution.prototype.toggleLowImpactSections = function() { + this.lowImpactExpanded = !this.lowImpactExpanded + this.renderSectionList() +} + +RorVsWild.Local.Execution.prototype.filterSectionsByText = function(event) { + this.sectionQueryFilter = event.currentTarget.value.trim().toLowerCase() + this.renderSectionList() +} + +RorVsWild.Local.Execution.prototype.filterSectionsByKind = function(event) { + var kind = event.currentTarget.dataset.kind + this.sectionKindFilter = this.sectionKindFilter === kind ? null : kind + this.renderSectionHeader() + this.renderSectionList() } RorVsWild.Local.Error = function(data) { diff --git a/lib/rorvswild/local/local.html.erb b/lib/rorvswild/local/local.html.erb index d382ee3..8b5965f 100644 --- a/lib/rorvswild/local/local.html.erb +++ b/lib/rorvswild/local/local.html.erb @@ -82,7 +82,7 @@

No jobs

{{/jobs}} {{#jobs}} -
+
{{name}}
{{path}}
@@ -106,8 +106,8 @@
- - - - - - + +