From 40cf4c8d3210b22330e2b054f9ef477ece871220 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Mar 2021 11:58:29 -0700 Subject: [PATCH] Add trace details foundation (#8716) Co-authored-by: Bram Kragten --- src/components/trace/ha-timeline.ts | 8 ++- src/components/trace/hat-trace.ts | 66 ++++++++++++++++--- .../automation/trace/ha-automation-trace.ts | 42 ++++++++++++ 3 files changed, 106 insertions(+), 10 deletions(-) diff --git a/src/components/trace/ha-timeline.ts b/src/components/trace/ha-timeline.ts index 3135775a2c..d9572c4a67 100644 --- a/src/components/trace/ha-timeline.ts +++ b/src/components/trace/ha-timeline.ts @@ -12,9 +12,11 @@ import { buttonLinkStyle } from "../../resources/styles"; import "../ha-svg-icon"; @customElement("ha-timeline") -class HaTimeline extends LitElement { +export class HaTimeline extends LitElement { @property({ type: Boolean, reflect: true }) public label = false; + @property({ type: Boolean, reflect: true }) public raised = false; + @property({ type: Boolean }) public lastItem = false; @property({ type: String }) public icon?: string; @@ -86,6 +88,10 @@ class HaTimeline extends LitElement { --timeline-ball-color, var(--timeline-color, var(--secondary-text-color)) ); + border-radius: 50%; + } + :host([raised]) ha-svg-icon { + transform: scale(1.3); } .line { flex: 1; diff --git a/src/components/trace/hat-trace.ts b/src/components/trace/hat-trace.ts index 213526d8cf..8abd33b9d2 100644 --- a/src/components/trace/hat-trace.ts +++ b/src/components/trace/hat-trace.ts @@ -3,6 +3,7 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, TemplateResult, @@ -15,6 +16,7 @@ import { } from "../../data/trace"; import { HomeAssistant } from "../../types"; import "./ha-timeline"; +import type { HaTimeline } from "./ha-timeline"; import { mdiCheckCircleOutline, mdiCircle, @@ -30,6 +32,7 @@ import { getActionType, } from "../../data/script"; import relativeTime from "../../common/datetime/relative_time"; +import { fireEvent } from "../../common/dom/fire_event"; const LOGBOOK_ENTRIES_BEFORE_FOLD = 2; @@ -242,7 +245,7 @@ class ActionRenderer { const isTopLevel = path.split("/").length === 2; if (!isTopLevel && !actionType) { - this._renderEntry(path.replace(/\//g, " ")); + this._renderEntry(path, path.replace(/\//g, " ")); return index + 1; } @@ -254,7 +257,7 @@ class ActionRenderer { return this._handleChoose(index); } - this._renderEntry(data.alias || actionType); + this._renderEntry(path, data.alias || actionType); return index + 1; } @@ -273,7 +276,8 @@ class ActionRenderer { // +3: 'sequence' // +4: executed sequence - const startLevel = this.keys[index].split("/").length - 1; + const choosePath = this.keys[index]; + const startLevel = choosePath.split("/").length - 1; const chooseTrace = this._getItem(index)[0] as ChooseActionTrace; const defaultExecuted = chooseTrace.result.choice === "default"; @@ -283,14 +287,14 @@ class ActionRenderer { const name = chooseConfig.alias || "Choose"; if (defaultExecuted) { - this._renderEntry(`${name}: Default action executed`); + this._renderEntry(choosePath, `${name}: Default action executed`); } else { const choiceConfig = this._getDataFromPath( `${this.keys[index]}/choose/${chooseTrace.result.choice}` ) as ChooseActionChoice; const choiceName = choiceConfig.alias || `Choice ${chooseTrace.result.choice}`; - this._renderEntry(`${name}: ${choiceName} executed`); + this._renderEntry(choosePath, `${name}: ${choiceName} executed`); } let i; @@ -328,9 +332,9 @@ class ActionRenderer { return i; } - private _renderEntry(description: string) { + private _renderEntry(path: string, description: string) { this.entries.push(html` - + ${description} `); @@ -345,9 +349,11 @@ class ActionRenderer { export class HaAutomationTracer extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) private trace?: AutomationTraceExtended; + @property({ attribute: false }) public trace?: AutomationTraceExtended; - @property({ attribute: false }) private logbookEntries?: LogbookEntry[]; + @property({ attribute: false }) public logbookEntries?: LogbookEntry[]; + + @internalProperty() private _selectedPath?: string; protected render(): TemplateResult { if (!this.trace) { @@ -374,6 +380,7 @@ export class HaAutomationTracer extends LitElement { .icon=${value[0].result.result ? mdiCheckCircleOutline : mdiStopCircleOutline} + data-path=${path} > ${getDataFromPath(this.trace!.config, path).alias || pathToName(path)} @@ -442,12 +449,53 @@ export class HaAutomationTracer extends LitElement { return html`${entries}`; } + protected updated(props) { + super.updated(props); + + // Pick first path when we load a new trace. + if (props.has("trace")) { + const element = this.shadowRoot!.querySelector( + "ha-timeline[data-path]" + ); + if (element) { + fireEvent(this, "value-changed", { value: element.dataset.path }); + this._selectedPath = element.dataset.path; + } + } + + this.shadowRoot!.querySelectorAll( + "ha-timeline[data-path]" + ).forEach((el) => { + el.style.setProperty( + "--timeline-ball-color", + this._selectedPath === el.dataset.path ? "var(--primary-color)" : null + ); + if (el.dataset.upgraded) { + return; + } + el.dataset.upgraded = "1"; + el.addEventListener("click", () => { + this._selectedPath = el.dataset.path; + fireEvent(this, "value-changed", { value: el.dataset.path }); + }); + el.addEventListener("mouseover", () => { + el.raised = true; + }); + el.addEventListener("mouseout", () => { + el.raised = false; + }); + }); + } + static get styles(): CSSResult[] { return [ css` ha-timeline[lastItem].condition { --timeline-ball-color: var(--error-color); } + ha-timeline[data-path] { + cursor: pointer; + } `, ]; } diff --git a/src/panels/config/automation/trace/ha-automation-trace.ts b/src/panels/config/automation/trace/ha-automation-trace.ts index f1ab98ad14..5f936c3e8d 100644 --- a/src/panels/config/automation/trace/ha-automation-trace.ts +++ b/src/panels/config/automation/trace/ha-automation-trace.ts @@ -1,3 +1,4 @@ +import { safeDump } from "js-yaml"; import { css, CSSResult, @@ -12,6 +13,7 @@ import { AutomationEntity } from "../../../../data/automation"; import { AutomationTrace, AutomationTraceExtended, + getDataFromPath, loadTrace, loadTraces, } from "../../../../data/trace"; @@ -48,6 +50,8 @@ export class HaAutomationTrace extends LitElement { @internalProperty() private _runId?: string; + @internalProperty() private _path?: string; + @internalProperty() private _trace?: AutomationTraceExtended; @internalProperty() private _logbookEntries?: LogbookEntry[]; @@ -107,10 +111,31 @@ export class HaAutomationTrace extends LitElement { .hass=${this.hass} .trace=${this._trace} .logbookEntries=${this._logbookEntries} + @value-changed=${this._pickPath} > `} + ${!this._path || !this._trace + ? "" + : html` +
+ +
+${safeDump(getDataFromPath(this._trace.config, this._path))}
+
+ +
+${safeDump(
+                      (this._path.split("/")[0] === "condition"
+                        ? this._trace.condition_trace
+                        : this._trace.action_trace)[this._path]
+                    )}
+
+
+ `} `; } @@ -162,6 +187,11 @@ export class HaAutomationTrace extends LitElement { private _pickTrace(ev) { this._runId = ev.target.value; + this._path = undefined; + } + + private _pickPath(ev) { + this._path = ev.detail.value; } private async _loadTraces(runId?: string) { @@ -179,6 +209,7 @@ export class HaAutomationTrace extends LitElement { !this._traces.some((trace) => trace.run_id === this._runId) ) { this._runId = undefined; + this._path = undefined; // If we came here from a trace passed into the url, clear it. if (runId) { @@ -254,6 +285,17 @@ export class HaAutomationTrace extends LitElement { top: 8px; right: 8px; } + + .details { + display: flex; + margin: 0 16px; + } + .details > * { + flex: 1 1 0px; + } + .details > *:first-child { + margin-right: 16px; + } `, ]; }