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
This commit is contained in:
Paulus Schoutsen 2021-05-17 11:19:47 -07:00 committed by GitHub
parent fd2728c02c
commit f8e8b5ad18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 71 additions and 49 deletions

View File

@ -63,6 +63,8 @@ class HatScriptGraph extends LitElement {
@property({ attribute: false }) public selected; @property({ attribute: false }) public selected;
@property() renderedNodes: Record<string, any> = {};
@property() trackedNodes: Record<string, any> = {}; @property() trackedNodes: Record<string, any> = {};
private selectNode(config, path) { private selectNode(config, path) {
@ -74,8 +76,9 @@ class HatScriptGraph extends LitElement {
private render_trigger(config: Trigger, i: number) { private render_trigger(config: Trigger, i: number) {
const path = `trigger/${i}`; const path = `trigger/${i}`;
const tracked = this.trace && path in this.trace.trace; const tracked = this.trace && path in this.trace.trace;
this.renderedNodes[path] = { config, path };
if (tracked) { if (tracked) {
this.trackedNodes[path] = { config, path }; this.trackedNodes[path] = this.renderedNodes[path];
} }
return html` return html`
<hat-graph-node <hat-graph-node
@ -96,8 +99,9 @@ class HatScriptGraph extends LitElement {
const trace = this.trace.trace[path] as ConditionTraceStep[] | undefined; const trace = this.trace.trace[path] as ConditionTraceStep[] | undefined;
const track_path = const track_path =
trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2; trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2;
this.renderedNodes[path] = { config, path };
if (trace) { if (trace) {
this.trackedNodes[path] = { config, path }; this.trackedNodes[path] = this.renderedNodes[path];
} }
return html` return html`
<hat-graph <hat-graph
@ -172,8 +176,11 @@ class HatScriptGraph extends LitElement {
const branch_path = `${path}/choose/${i}`; const branch_path = `${path}/choose/${i}`;
const track_this = const track_this =
trace !== undefined && trace[0].result?.choice === i; trace !== undefined && trace[0].result?.choice === i;
this.renderedNodes[branch_path] = { config, path: branch_path };
if (track_this) { if (track_this) {
this.trackedNodes[branch_path] = { config, path: branch_path }; this.trackedNodes[branch_path] = this.renderedNodes[
branch_path
];
} }
return html` return html`
<hat-graph> <hat-graph>
@ -411,8 +418,9 @@ class HatScriptGraph extends LitElement {
const type = Object.keys(NODE_TYPES).find((key) => key in node) || "other"; const type = Object.keys(NODE_TYPES).find((key) => key in node) || "other";
const nodeEl = NODE_TYPES[type].bind(this)(node, path); const nodeEl = NODE_TYPES[type].bind(this)(node, path);
this.renderedNodes[path] = { config: node, path };
if (this.trace && path in this.trace.trace) { if (this.trace && path in this.trace.trace) {
this.trackedNodes[path] = { config: node, path }; this.trackedNodes[path] = this.renderedNodes[path];
} }
return nodeEl; return nodeEl;
} }
@ -483,6 +491,7 @@ class HatScriptGraph extends LitElement {
protected update(changedProps: PropertyValues<this>) { protected update(changedProps: PropertyValues<this>) {
if (changedProps.has("trace")) { if (changedProps.has("trace")) {
this.renderedNodes = {};
this.trackedNodes = {}; this.trackedNodes = {};
} }
super.update(changedProps); super.update(changedProps);
@ -493,7 +502,7 @@ class HatScriptGraph extends LitElement {
// Select first node if new trace loaded but no selection given. // Select first node if new trace loaded but no selection given.
if (changedProps.has("trace")) { if (changedProps.has("trace")) {
const tracked = this.getTrackedNodes(); const tracked = this.trackedNodes;
const paths = Object.keys(tracked); const paths = Object.keys(tracked);
// If trace changed and we have no or an invalid selection, select first option. // 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) { if (this.trace) {
const sortKeys = Object.keys(this.trace.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) (a, b) => sortKeys.indexOf(a) - sortKeys.indexOf(b)
); );
const sortedTrackedNodes = keys.reduce((obj, key) => { const sortedTrackedNodes = {};
obj[key] = this.trackedNodes[key]; const sortedRenderedNodes = {};
return obj; 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; this.trackedNodes = sortedTrackedNodes;
} }
} }
} }
public getTrackedNodes() {
return this.trackedNodes;
}
public previousTrackedNode() { public previousTrackedNode() {
const tracked = this.getTrackedNodes(); const nodes = Object.keys(this.trackedNodes);
const nodes = Object.keys(tracked); const prevIndex = nodes.indexOf(this.selected) - 1;
if (prevIndex >= 0) {
for (let i = nodes.indexOf(this.selected) - 1; i >= 0; i--) { fireEvent(
if (tracked[nodes[i]]) { this,
fireEvent(this, "graph-node-selected", tracked[nodes[i]]); "graph-node-selected",
break; this.trackedNodes[nodes[prevIndex]]
} );
} }
} }
public nextTrackedNode() { public nextTrackedNode() {
const tracked = this.getTrackedNodes(); const nodes = Object.keys(this.trackedNodes);
const nodes = Object.keys(tracked); const nextIndex = nodes.indexOf(this.selected) + 1;
for (let i = nodes.indexOf(this.selected) + 1; i < nodes.length; i++) { if (nextIndex < nodes.length) {
if (tracked[nodes[i]]) { fireEvent(
fireEvent(this, "graph-node-selected", tracked[nodes[i]]); this,
break; "graph-node-selected",
} this.trackedNodes[nodes[nextIndex]]
);
} }
} }

View File

@ -245,13 +245,15 @@ class ActionRenderer {
try { try {
data = getDataFromPath(this.trace.config, path); data = getDataFromPath(this.trace.config, path);
} catch (err) { } catch (err) {
this.entries.push( this._renderEntry(
html`Unable to extract path ${path}. Download trace and report as bug` path,
`Unable to extract path ${path}. Download trace and report as bug`
); );
return index + 1; return index + 1;
} }
const isTopLevel = path.split("/").length === 2; const parts = path.split("/");
const isTopLevel = parts.length === 2;
if (!isTopLevel && !actionType) { if (!isTopLevel && !actionType) {
this._renderEntry(path, path.replace(/\//g, " ")); this._renderEntry(path, path.replace(/\//g, " "));
@ -267,7 +269,16 @@ class ActionRenderer {
} }
this._renderEntry(path, describeAction(this.hass, data, actionType)); 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 { private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number {
@ -303,7 +314,7 @@ class ActionRenderer {
// +4: executed sequence // +4: executed sequence
const choosePath = this.keys[index]; 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 chooseTrace = this._getItem(index)[0] as ChooseActionTraceStep;
const defaultExecuted = chooseTrace.result?.choice === "default"; const defaultExecuted = chooseTrace.result?.choice === "default";

View File

@ -7,8 +7,8 @@ export const describeCondition = (condition: Condition) => {
if (condition.alias) { if (condition.alias) {
return condition.alias; return condition.alias;
} }
if (condition.condition === "template") { if (["or", "and", "not"].includes(condition.condition)) {
return "Test a template"; return `multiple conditions using "${condition.condition}"`;
} }
return `${condition.condition} condition`; return `${condition.condition} condition`;
}; };

View File

@ -17,7 +17,7 @@ export interface DeviceAction extends DeviceAutomation {
} }
export interface DeviceCondition extends DeviceAutomation { export interface DeviceCondition extends DeviceAutomation {
condition: string; condition: "device";
} }
export interface DeviceTrigger extends DeviceAutomation { export interface DeviceTrigger extends DeviceAutomation {

View File

@ -38,6 +38,8 @@ export class HaAutomationTracePathDetails extends LitElement {
@property() public logbookEntries!: LogbookEntry[]; @property() public logbookEntries!: LogbookEntry[];
@property() renderedNodes: Record<string, any> = {};
@property() public trackedNodes!: Record<string, any>; @property() public trackedNodes!: Record<string, any>;
@state() private _view: "config" | "changed_variables" | "logbook" = "config"; @state() private _view: "config" | "changed_variables" | "logbook" = "config";
@ -99,12 +101,12 @@ export class HaAutomationTracePathDetails extends LitElement {
const parts: TemplateResult[][] = []; const parts: TemplateResult[][] = [];
let active = false; let active = false;
const childConditionsPrefix = `${this.selected.path}/conditions/`;
for (const curPath of Object.keys(this.trace.trace)) { 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 (active) {
if (!curPath.startsWith(childConditionsPrefix)) { if (curPath in this.renderedNodes) {
break; break;
} }
} else if (curPath === this.selected.path) { } else if (curPath === this.selected.path) {
@ -129,9 +131,7 @@ export class HaAutomationTracePathDetails extends LitElement {
return html` return html`
${curPath === this.selected.path ${curPath === this.selected.path
? "" ? ""
: html`<h2> : html`<h2>${curPath.substr(this.selected.path.length + 1)}</h2>`}
Condition ${curPath.substr(childConditionsPrefix.length)}
</h2>`}
${data.length === 1 ? "" : html`<h3>Iteration ${idx + 1}</h3>`} ${data.length === 1 ? "" : html`<h3>Iteration ${idx + 1}</h3>`}
Executed: Executed:
${formatDateTimeWithSeconds( ${formatDateTimeWithSeconds(

View File

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