From f8e8b5ad18baecf073a7a557a7b88f5c89578e0c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 17 May 2021 11:19:47 -0700 Subject: [PATCH] Trace fixes (#9192) * Show sub-conditions * Better show errors in timeline * Add rendered nodes * Fix some rendering issues with sub-conditions in timeline * Improve condition rendering --- src/components/trace/hat-script-graph.ts | 69 +++++++++++-------- src/components/trace/hat-trace-timeline.ts | 21 ++++-- src/data/automation_i18n.ts | 4 +- src/data/device_automation.ts | 2 +- .../trace/ha-automation-trace-path-details.ts | 12 ++-- .../automation/trace/ha-automation-trace.ts | 12 ++-- 6 files changed, 71 insertions(+), 49 deletions(-) diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index cecb0feb28..7b0755ee4e 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -63,6 +63,8 @@ class HatScriptGraph extends LitElement { @property({ attribute: false }) public selected; + @property() renderedNodes: Record = {}; + @property() trackedNodes: Record = {}; private selectNode(config, path) { @@ -74,8 +76,9 @@ class HatScriptGraph extends LitElement { private render_trigger(config: Trigger, i: number) { const path = `trigger/${i}`; const tracked = this.trace && path in this.trace.trace; + this.renderedNodes[path] = { config, path }; if (tracked) { - this.trackedNodes[path] = { config, path }; + this.trackedNodes[path] = this.renderedNodes[path]; } return html` @@ -411,8 +418,9 @@ class HatScriptGraph extends LitElement { const type = Object.keys(NODE_TYPES).find((key) => key in node) || "other"; const nodeEl = NODE_TYPES[type].bind(this)(node, path); + this.renderedNodes[path] = { config: node, path }; if (this.trace && path in this.trace.trace) { - this.trackedNodes[path] = { config: node, path }; + this.trackedNodes[path] = this.renderedNodes[path]; } return nodeEl; } @@ -483,6 +491,7 @@ class HatScriptGraph extends LitElement { protected update(changedProps: PropertyValues) { if (changedProps.has("trace")) { + this.renderedNodes = {}; this.trackedNodes = {}; } super.update(changedProps); @@ -493,7 +502,7 @@ class HatScriptGraph extends LitElement { // Select first node if new trace loaded but no selection given. if (changedProps.has("trace")) { - const tracked = this.getTrackedNodes(); + const tracked = this.trackedNodes; const paths = Object.keys(tracked); // If trace changed and we have no or an invalid selection, select first option. @@ -509,42 +518,44 @@ class HatScriptGraph extends LitElement { if (this.trace) { const sortKeys = Object.keys(this.trace.trace); - const keys = Object.keys(this.trackedNodes).sort( + const keys = Object.keys(this.renderedNodes).sort( (a, b) => sortKeys.indexOf(a) - sortKeys.indexOf(b) ); - const sortedTrackedNodes = keys.reduce((obj, key) => { - obj[key] = this.trackedNodes[key]; - return obj; - }, {}); + const sortedTrackedNodes = {}; + const sortedRenderedNodes = {}; + for (const key of keys) { + sortedRenderedNodes[key] = this.renderedNodes[key]; + if (key in this.trackedNodes) { + sortedTrackedNodes[key] = this.trackedNodes[key]; + } + } + this.renderedNodes = sortedRenderedNodes; this.trackedNodes = sortedTrackedNodes; } } } - public getTrackedNodes() { - return this.trackedNodes; - } - public previousTrackedNode() { - const tracked = this.getTrackedNodes(); - const nodes = Object.keys(tracked); - - for (let i = nodes.indexOf(this.selected) - 1; i >= 0; i--) { - if (tracked[nodes[i]]) { - fireEvent(this, "graph-node-selected", tracked[nodes[i]]); - break; - } + const nodes = Object.keys(this.trackedNodes); + const prevIndex = nodes.indexOf(this.selected) - 1; + if (prevIndex >= 0) { + fireEvent( + this, + "graph-node-selected", + this.trackedNodes[nodes[prevIndex]] + ); } } public nextTrackedNode() { - const tracked = this.getTrackedNodes(); - const nodes = Object.keys(tracked); - for (let i = nodes.indexOf(this.selected) + 1; i < nodes.length; i++) { - if (tracked[nodes[i]]) { - fireEvent(this, "graph-node-selected", tracked[nodes[i]]); - break; - } + const nodes = Object.keys(this.trackedNodes); + const nextIndex = nodes.indexOf(this.selected) + 1; + if (nextIndex < nodes.length) { + fireEvent( + this, + "graph-node-selected", + this.trackedNodes[nodes[nextIndex]] + ); } } diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index ea6746322e..67c4eb1f04 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -245,13 +245,15 @@ class ActionRenderer { try { data = getDataFromPath(this.trace.config, path); } catch (err) { - this.entries.push( - html`Unable to extract path ${path}. Download trace and report as bug` + this._renderEntry( + path, + `Unable to extract path ${path}. Download trace and report as bug` ); return index + 1; } - const isTopLevel = path.split("/").length === 2; + const parts = path.split("/"); + const isTopLevel = parts.length === 2; if (!isTopLevel && !actionType) { this._renderEntry(path, path.replace(/\//g, " ")); @@ -267,7 +269,16 @@ class ActionRenderer { } this._renderEntry(path, describeAction(this.hass, data, actionType)); - return index + 1; + + let i = index + 1; + + for (; i < this.keys.length; i++) { + if (this.keys[i].split("/").length === parts.length) { + break; + } + } + + return i; } private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number { @@ -303,7 +314,7 @@ class ActionRenderer { // +4: executed sequence const choosePath = this.keys[index]; - const startLevel = choosePath.split("/").length - 1; + const startLevel = choosePath.split("/").length; const chooseTrace = this._getItem(index)[0] as ChooseActionTraceStep; const defaultExecuted = chooseTrace.result?.choice === "default"; diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 7b0caf842f..8dc2746556 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -7,8 +7,8 @@ export const describeCondition = (condition: Condition) => { if (condition.alias) { return condition.alias; } - if (condition.condition === "template") { - return "Test a template"; + if (["or", "and", "not"].includes(condition.condition)) { + return `multiple conditions using "${condition.condition}"`; } return `${condition.condition} condition`; }; diff --git a/src/data/device_automation.ts b/src/data/device_automation.ts index 339bf61a16..4392fa529e 100644 --- a/src/data/device_automation.ts +++ b/src/data/device_automation.ts @@ -17,7 +17,7 @@ export interface DeviceAction extends DeviceAutomation { } export interface DeviceCondition extends DeviceAutomation { - condition: string; + condition: "device"; } export interface DeviceTrigger extends DeviceAutomation { diff --git a/src/panels/config/automation/trace/ha-automation-trace-path-details.ts b/src/panels/config/automation/trace/ha-automation-trace-path-details.ts index a7c3754c4d..4afaa2abc5 100644 --- a/src/panels/config/automation/trace/ha-automation-trace-path-details.ts +++ b/src/panels/config/automation/trace/ha-automation-trace-path-details.ts @@ -38,6 +38,8 @@ export class HaAutomationTracePathDetails extends LitElement { @property() public logbookEntries!: LogbookEntry[]; + @property() renderedNodes: Record = {}; + @property() public trackedNodes!: Record; @state() private _view: "config" | "changed_variables" | "logbook" = "config"; @@ -99,12 +101,12 @@ export class HaAutomationTracePathDetails extends LitElement { const parts: TemplateResult[][] = []; let active = false; - const childConditionsPrefix = `${this.selected.path}/conditions/`; for (const curPath of Object.keys(this.trace.trace)) { - // Include all child conditions too + // Include all trace results until the next rendered node. + // Rendered nodes also include non-chosen choose paths. if (active) { - if (!curPath.startsWith(childConditionsPrefix)) { + if (curPath in this.renderedNodes) { break; } } else if (curPath === this.selected.path) { @@ -129,9 +131,7 @@ export class HaAutomationTracePathDetails extends LitElement { return html` ${curPath === this.selected.path ? "" - : html`

- Condition ${curPath.substr(childConditionsPrefix.length)} -

`} + : html`

${curPath.substr(this.selected.path.length + 1)}

`} ${data.length === 1 ? "" : html`

Iteration ${idx + 1}

`} Executed: ${formatDateTimeWithSeconds( diff --git a/src/panels/config/automation/trace/ha-automation-trace.ts b/src/panels/config/automation/trace/ha-automation-trace.ts index 9985d1a496..2f0afc5fee 100644 --- a/src/panels/config/automation/trace/ha-automation-trace.ts +++ b/src/panels/config/automation/trace/ha-automation-trace.ts @@ -81,9 +81,9 @@ export class HaAutomationTrace extends LitElement { ? this.hass.states[this._entityId] : undefined; - const trackedNodes = this.shadowRoot!.querySelector( - "hat-script-graph" - )?.getTrackedNodes(); + const graph = this.shadowRoot!.querySelector("hat-script-graph"); + const trackedNodes = graph?.trackedNodes; + const renderedNodes = graph?.renderedNodes; const title = stateObj?.attributes.friendly_name || this._entityId; @@ -234,6 +234,7 @@ export class HaAutomationTrace extends LitElement { .selected=${this._selected} .logbookEntries=${this._logbookEntries} .trackedNodes=${trackedNodes} + .renderedNodes=${renderedNodes} > ` : this._view === "config" @@ -442,9 +443,8 @@ export class HaAutomationTrace extends LitElement { private _timelinePathPicked(ev) { const path = ev.detail.value; - const nodes = this.shadowRoot!.querySelector( - "hat-script-graph" - )!.getTrackedNodes(); + const nodes = this.shadowRoot!.querySelector("hat-script-graph")! + .trackedNodes; if (nodes[path]) { this._selected = nodes[path]; }