Adjust for latest trace API (#8755)

This commit is contained in:
Paulus Schoutsen 2021-03-29 17:01:39 -07:00 committed by GitHub
parent 46580376dd
commit b866166425
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 196 additions and 205 deletions

View File

@ -13,7 +13,55 @@ export const basicTrace: DemoTrace = {
trigger: "state of input_boolean.toggle_1", trigger: "state of input_boolean.toggle_1",
domain: "automation", domain: "automation",
item_id: "1615419646544", item_id: "1615419646544",
action_trace: { trace: {
"condition/0": [
{
path: "condition/0",
timestamp: "2021-03-25T04:36:51.228243+00:00",
changed_variables: {
trigger: {
platform: "state",
entity_id: "input_boolean.toggle_1",
from_state: {
entity_id: "input_boolean.toggle_1",
state: "on",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-24T19:03:59.141440+00:00",
last_updated: "2021-03-24T19:03:59.141440+00:00",
context: {
id: "5d0918eb379214d07554bdab6a08bcff",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "input_boolean.toggle_1",
state: "off",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-25T04:36:51.220696+00:00",
last_updated: "2021-03-25T04:36:51.220696+00:00",
context: {
id: "664d6d261450a9ecea6738e97269a149",
parent_id: null,
user_id: "d1b4e89da01445fa8bc98e39fac477ca",
},
},
for: null,
attribute: null,
description: "state of input_boolean.toggle_1",
},
},
result: {
result: true,
},
},
],
"action/0": [ "action/0": [
{ {
path: "action/0", path: "action/0",
@ -158,56 +206,7 @@ export const basicTrace: DemoTrace = {
}, },
], ],
}, },
condition_trace: {
"condition/0": [
{
path: "condition/0",
timestamp: "2021-03-25T04:36:51.228243+00:00",
changed_variables: {
trigger: {
platform: "state",
entity_id: "input_boolean.toggle_1",
from_state: {
entity_id: "input_boolean.toggle_1",
state: "on",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-24T19:03:59.141440+00:00",
last_updated: "2021-03-24T19:03:59.141440+00:00",
context: {
id: "5d0918eb379214d07554bdab6a08bcff",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "input_boolean.toggle_1",
state: "off",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-25T04:36:51.220696+00:00",
last_updated: "2021-03-25T04:36:51.220696+00:00",
context: {
id: "664d6d261450a9ecea6738e97269a149",
parent_id: null,
user_id: "d1b4e89da01445fa8bc98e39fac477ca",
},
},
for: null,
attribute: null,
description: "state of input_boolean.toggle_1",
},
},
result: {
result: true,
},
},
],
},
config: { config: {
id: "1615419646544", id: "1615419646544",
alias: "Ensure Party mode", alias: "Ensure Party mode",

View File

@ -13,7 +13,7 @@ export const motionLightTrace: DemoTrace = {
trigger: "state of binary_sensor.pauluss_macbook_pro_camera_in_use", trigger: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
domain: "automation", domain: "automation",
item_id: "1614732497392", item_id: "1614732497392",
action_trace: { trace: {
"action/0": [ "action/0": [
{ {
path: "action/0", path: "action/0",
@ -124,7 +124,6 @@ export const motionLightTrace: DemoTrace = {
}, },
], ],
}, },
condition_trace: {},
config: { config: {
mode: "restart", mode: "restart",
max_exceeded: "silent", max_exceeded: "silent",

View File

@ -11,19 +11,18 @@ import {
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time"; import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import { import {
AutomationTraceExtended, AutomationTraceExtended,
ChooseActionTrace, ChooseActionTraceStep,
getDataFromPath, getDataFromPath,
TriggerTraceStep,
} from "../../data/trace"; } from "../../data/trace";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "./ha-timeline"; import "./ha-timeline";
import type { HaTimeline } from "./ha-timeline"; import type { HaTimeline } from "./ha-timeline";
import { import {
mdiCheckCircleOutline,
mdiCircle, mdiCircle,
mdiCircleOutline, mdiCircleOutline,
mdiPauseCircleOutline, mdiPauseCircleOutline,
mdiRecordCircleOutline, mdiRecordCircleOutline,
mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
import { LogbookEntry } from "../../data/logbook"; import { LogbookEntry } from "../../data/logbook";
import { import {
@ -36,8 +35,6 @@ import { fireEvent } from "../../common/dom/fire_event";
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2; const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
const pathToName = (path: string) => path.split("/").join(" ");
/* eslint max-classes-per-file: "off" */ /* eslint max-classes-per-file: "off" */
// Report time entry when more than this time has passed // Report time entry when more than this time has passed
@ -190,12 +187,13 @@ class ActionRenderer {
private keys: string[]; private keys: string[];
constructor( constructor(
private hass: HomeAssistant,
private entries: TemplateResult[], private entries: TemplateResult[],
private trace: AutomationTraceExtended, private trace: AutomationTraceExtended,
private logbookRenderer: LogbookRenderer, private logbookRenderer: LogbookRenderer,
private timeTracker: RenderedTimeTracker private timeTracker: RenderedTimeTracker
) { ) {
this.keys = Object.keys(trace.action_trace); this.keys = Object.keys(trace.trace);
} }
get curItem() { get curItem() {
@ -211,7 +209,7 @@ class ActionRenderer {
} }
private _getItem(index: number) { private _getItem(index: number) {
return this.trace.action_trace[this.keys[index]]; return this.trace.trace[this.keys[index]];
} }
private _renderItem( private _renderItem(
@ -219,6 +217,11 @@ class ActionRenderer {
actionType?: ReturnType<typeof getActionType> actionType?: ReturnType<typeof getActionType>
): number { ): number {
const value = this._getItem(index); const value = this._getItem(index);
if (value[0].path === "trigger") {
return this._handleTrigger(index, value[0] as TriggerTraceStep);
}
const timestamp = new Date(value[0].timestamp); const timestamp = new Date(value[0].timestamp);
// Render all logbook items that are in front of this item. // Render all logbook items that are in front of this item.
@ -262,6 +265,20 @@ class ActionRenderer {
return index + 1; return index + 1;
} }
private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number {
this._renderEntry(
"trigger",
`Triggered by the
${triggerStep.changed_variables.trigger.description} at
${formatDateTimeWithSeconds(
new Date(triggerStep.timestamp),
this.hass.locale
)}`,
mdiCircle
);
return index + 1;
}
private _handleChoose(index: number): number { private _handleChoose(index: number): number {
// startLevel: choose root config // startLevel: choose root config
@ -280,7 +297,7 @@ class ActionRenderer {
const choosePath = this.keys[index]; const choosePath = this.keys[index];
const startLevel = choosePath.split("/").length - 1; const startLevel = choosePath.split("/").length - 1;
const chooseTrace = this._getItem(index)[0] as ChooseActionTrace; const chooseTrace = this._getItem(index)[0] as ChooseActionTraceStep;
const defaultExecuted = chooseTrace.result.choice === "default"; const defaultExecuted = chooseTrace.result.choice === "default";
const chooseConfig = this._getDataFromPath( const chooseConfig = this._getDataFromPath(
this.keys[index] this.keys[index]
@ -333,9 +350,13 @@ class ActionRenderer {
return i; return i;
} }
private _renderEntry(path: string, description: string) { private _renderEntry(
path: string,
description: string,
icon = mdiRecordCircleOutline
) {
this.entries.push(html` this.entries.push(html`
<ha-timeline .icon=${mdiRecordCircleOutline} data-path=${path}> <ha-timeline .icon=${icon} data-path=${path}>
${description} ${description}
</ha-timeline> </ha-timeline>
`); `);
@ -362,66 +383,33 @@ export class HaAutomationTracer extends LitElement {
if (!this.trace) { if (!this.trace) {
return html``; return html``;
} }
const entries = [
html`
<ha-timeline .icon=${mdiCircle}>
Triggered by the ${this.trace.variables.trigger.description} at
${formatDateTimeWithSeconds(
new Date(this.trace.timestamp.start),
this.hass.locale
)}
</ha-timeline>
`,
];
if (this.trace.condition_trace) { const entries: TemplateResult[] = [];
for (const [path, value] of Object.entries(this.trace.condition_trace)) {
entries.push(html` const timeTracker = new RenderedTimeTracker(this.hass, entries, this.trace);
<ha-timeline const logbookRenderer = new LogbookRenderer(
?lastItem=${!value[0].result.result} entries,
class="condition" timeTracker,
.icon=${value[0].result.result this.logbookEntries || []
? mdiCheckCircleOutline );
: mdiStopCircleOutline} const actionRenderer = new ActionRenderer(
data-path=${path} this.hass,
> entries,
${getDataFromPath(this.trace!.config, path).alias || this.trace,
pathToName(path)} logbookRenderer,
${value[0].result.result ? "passed" : "failed"} timeTracker
</ha-timeline> );
`);
} while (actionRenderer.hasNext) {
actionRenderer.renderItem();
} }
if (this.trace.action_trace && this.logbookEntries) { while (logbookRenderer.hasNext) {
const timeTracker = new RenderedTimeTracker( logbookRenderer.maybeRenderItem();
this.hass,
entries,
this.trace
);
const logbookRenderer = new LogbookRenderer(
entries,
timeTracker,
this.logbookEntries
);
const actionRenderer = new ActionRenderer(
entries,
this.trace,
logbookRenderer,
timeTracker
);
while (actionRenderer.hasNext) {
actionRenderer.renderItem();
}
while (logbookRenderer.hasNext) {
logbookRenderer.maybeRenderItem();
}
logbookRenderer.flush();
} }
logbookRenderer.flush();
// null means it was stopped by a condition // null means it was stopped by a condition
if (this.trace.last_action !== null) { if (this.trace.last_action !== null) {
entries.push(html` entries.push(html`
@ -456,7 +444,13 @@ export class HaAutomationTracer extends LitElement {
super.updated(props); super.updated(props);
// Pick first path when we load a new trace. // Pick first path when we load a new trace.
if (this.allowPick && props.has("trace")) { if (
this.allowPick &&
props.has("trace") &&
this.trace &&
this.selectedPath &&
!(this.selectedPath in this.trace.trace)
) {
const element = this.shadowRoot!.querySelector<HaTimeline>( const element = this.shadowRoot!.querySelector<HaTimeline>(
"ha-timeline[data-path]" "ha-timeline[data-path]"
); );

View File

@ -14,16 +14,16 @@ import {
mdiCheckboxBlankOutline, mdiCheckboxBlankOutline,
mdiAsterisk, mdiAsterisk,
mdiDevices, mdiDevices,
mdiFlare,
} from "@mdi/js"; } from "@mdi/js";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { Condition } from "../../data/automation"; import { Condition } from "../../data/automation";
import { Action, ChooseAction, RepeatAction } from "../../data/script"; import { Action, ChooseAction, RepeatAction } from "../../data/script";
import { import {
ActionTrace,
AutomationTraceExtended, AutomationTraceExtended,
ChooseActionTrace, ChooseActionTraceStep,
ChooseChoiceActionTrace, ChooseChoiceActionTraceStep,
ConditionTrace, ConditionTraceStep,
} from "../../data/trace"; } from "../../data/trace";
import { NodeInfo, TreeNode } from "./hat-graph"; import { NodeInfo, TreeNode } from "./hat-graph";
@ -106,7 +106,7 @@ export class ActionHandler {
} }
_createGraph = memoizeOne((_actions, _selected, _trace) => _createGraph = memoizeOne((_actions, _selected, _trace) =>
this._renderConditions().concat( this._renderTraceHead().concat(
this.actions.map((action, idx) => this.actions.map((action, idx) =>
this._createTreeNode( this._createTreeNode(
idx, idx,
@ -118,23 +118,44 @@ export class ActionHandler {
) )
); );
_renderConditions(): TreeNode[] { _renderTraceHead(): TreeNode[] {
// action/ = default pathPrefix for trace-based actions if (this.pathPrefix !== TRACE_ACTION_PREFIX) {
if (
this.pathPrefix !== TRACE_ACTION_PREFIX ||
!this.trace?.config.condition
) {
return []; return [];
} }
return this.trace.config.condition.map((condition, idx) =>
this._createConditionNode( const triggerNodeInfo = {
"condition/", path: "trigger",
this.trace?.condition_trace, // Just all triggers for now.
idx, config: this.trace!.config.trigger,
condition, };
!this.actions.length && this.trace!.config.condition!.length === idx + 1
) const nodes: TreeNode[] = [
); {
icon: mdiFlare,
nodeInfo: triggerNodeInfo,
clickCallback: () => {
this._selectNode(triggerNodeInfo);
},
isActive: this.selected === "trigger",
isTracked: true,
},
];
if (this.trace!.config.condition) {
this.trace!.config.condition.forEach((condition, idx) =>
nodes.push(
this._createConditionNode(
"condition/",
idx,
condition,
!this.actions.length &&
this.trace!.config.condition!.length === idx + 1
)
)
);
}
return nodes;
} }
_updateAction(idx: number, action) { _updateAction(idx: number, action) {
@ -192,7 +213,7 @@ export class ActionHandler {
this._selectNode(nodeInfo); this._selectNode(nodeInfo);
}, },
isActive: path === this.selected, isActive: path === this.selected,
isTracked: this.trace && path in this.trace.action_trace, isTracked: this.trace && path in this.trace.trace,
end, end,
}; };
} }
@ -212,13 +233,7 @@ export class ActionHandler {
(idx: number, action: any, end: boolean) => TreeNode (idx: number, action: any, end: boolean) => TreeNode
> = { > = {
condition: (idx, action: Condition, end: boolean): TreeNode => condition: (idx, action: Condition, end: boolean): TreeNode =>
this._createConditionNode( this._createConditionNode(this.pathPrefix, idx, action, end),
this.pathPrefix,
this.trace?.action_trace,
idx,
action,
end
),
repeat: (idx, action: RepeatAction, end: boolean): TreeNode => { repeat: (idx, action: RepeatAction, end: boolean): TreeNode => {
let seq: Array<Action | NoAction> = action.repeat.sequence; let seq: Array<Action | NoAction> = action.repeat.sequence;
@ -227,11 +242,10 @@ export class ActionHandler {
} }
const path = `${this.pathPrefix}${idx}`; const path = `${this.pathPrefix}${idx}`;
const isTracked = this.trace && path in this.trace.action_trace; const isTracked = this.trace && path in this.trace.trace;
const repeats = const repeats =
this.trace && this.trace && this.trace.trace[`${path}/repeat/sequence/0`]?.length;
this.trace.action_trace[`${path}/repeat/sequence/0`]?.length;
const nodeInfo: NodeInfo = { const nodeInfo: NodeInfo = {
path, path,
@ -268,10 +282,10 @@ export class ActionHandler {
const choosePath = `${this.pathPrefix}${idx}`; const choosePath = `${this.pathPrefix}${idx}`;
let choice: number | "default" | undefined; let choice: number | "default" | undefined;
if (this.trace?.action_trace && choosePath in this.trace.action_trace) { if (this.trace?.trace && choosePath in this.trace.trace) {
const chooseResult = this.trace.action_trace[ const chooseResult = this.trace.trace[
choosePath choosePath
] as ChooseActionTrace[]; ] as ChooseActionTraceStep[];
choice = chooseResult[0].result.choice; choice = chooseResult[0].result.choice;
} }
@ -280,10 +294,10 @@ export class ActionHandler {
// If we have a trace, highlight the chosen track here. // If we have a trace, highlight the chosen track here.
const choicePath = `${this.pathPrefix}${idx}/choose/${choiceIdx}`; const choicePath = `${this.pathPrefix}${idx}/choose/${choiceIdx}`;
let chosen = false; let chosen = false;
if (this.trace && choicePath in this.trace.action_trace) { if (this.trace && choicePath in this.trace.trace) {
const choiceResult = this.trace.action_trace[ const choiceResult = this.trace.trace[
choicePath choicePath
] as ChooseChoiceActionTrace[]; ] as ChooseChoiceActionTraceStep[];
chosen = choiceResult[0].result.result; chosen = choiceResult[0].result.result;
} }
const choiceNodeInfo: NodeInfo = { const choiceNodeInfo: NodeInfo = {
@ -380,7 +394,6 @@ export class ActionHandler {
private _createConditionNode( private _createConditionNode(
pathPrefix: string, pathPrefix: string,
tracePaths: Record<string, ActionTrace[]> | undefined,
idx: number, idx: number,
action: Condition, action: Condition,
end: boolean end: boolean
@ -389,8 +402,8 @@ export class ActionHandler {
let result: boolean | undefined; let result: boolean | undefined;
let isTracked = false; let isTracked = false;
if (tracePaths && path in tracePaths) { if (this.trace && path in this.trace.trace) {
const conditionResult = tracePaths[path] as ConditionTrace[]; const conditionResult = this.trace.trace[path] as ConditionTraceStep[];
result = conditionResult[0].result.result; result = conditionResult[0].result.result;
isTracked = true; isTracked = true;
} }

View File

@ -1,24 +1,27 @@
import { HomeAssistant, Context } from "../types"; import { HomeAssistant, Context } from "../types";
import { AutomationConfig } from "./automation"; import { AutomationConfig } from "./automation";
interface TraceVariables extends Record<string, unknown> { interface BaseTraceStep {
trigger: {
description: string;
[key: string]: unknown;
};
}
interface BaseTrace {
path: string; path: string;
timestamp: string; timestamp: string;
changed_variables?: Record<string, unknown>; changed_variables?: Record<string, unknown>;
} }
export interface ConditionTrace extends BaseTrace { export interface TriggerTraceStep extends BaseTraceStep {
changed_variables: {
trigger: {
description: string;
[key: string]: unknown;
};
[key: string]: unknown;
};
}
export interface ConditionTraceStep extends BaseTraceStep {
result: { result: boolean }; result: { result: boolean };
} }
export interface CallServiceActionTrace extends BaseTrace { export interface CallServiceActionTraceStep extends BaseTraceStep {
result: { result: {
limit: number; limit: number;
running_script: boolean; running_script: boolean;
@ -31,19 +34,20 @@ export interface CallServiceActionTrace extends BaseTrace {
}; };
} }
export interface ChooseActionTrace extends BaseTrace { export interface ChooseActionTraceStep extends BaseTraceStep {
result: { choice: number | "default" }; result: { choice: number | "default" };
} }
export interface ChooseChoiceActionTrace extends BaseTrace { export interface ChooseChoiceActionTraceStep extends BaseTraceStep {
result: { result: boolean }; result: { result: boolean };
} }
export type ActionTrace = export type ActionTraceStep =
| BaseTrace | BaseTraceStep
| CallServiceActionTrace | ConditionTraceStep
| ChooseActionTrace | CallServiceActionTraceStep
| ChooseChoiceActionTrace; | ChooseActionTraceStep
| ChooseChoiceActionTraceStep;
export interface AutomationTrace { export interface AutomationTrace {
domain: string; domain: string;
@ -60,10 +64,9 @@ export interface AutomationTrace {
} }
export interface AutomationTraceExtended extends AutomationTrace { export interface AutomationTraceExtended extends AutomationTrace {
condition_trace: Record<string, ConditionTrace[]>; trace: Record<string, ActionTraceStep[]>;
action_trace: Record<string, ActionTrace[]>;
context: Context; context: Context;
variables: TraceVariables; variables: Record<string, unknown>;
config: AutomationConfig; config: AutomationConfig;
} }

View File

@ -10,9 +10,9 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { import {
ActionTrace, ActionTraceStep,
AutomationTraceExtended, AutomationTraceExtended,
ChooseActionTrace, ChooseActionTraceStep,
getDataFromPath, getDataFromPath,
} from "../../../../data/trace"; } from "../../../../data/trace";
import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-button";
@ -77,14 +77,8 @@ export class HaAutomationTracePathDetails extends LitElement {
`; `;
} }
private _getPaths() {
return this.selected.path.split("/")[0] === "condition"
? this.trace!.condition_trace
: this.trace!.action_trace;
}
private _renderSelectedTraceInfo() { private _renderSelectedTraceInfo() {
const paths = this._getPaths(); const paths = this.trace.trace;
if (!this.selected?.path) { if (!this.selected?.path) {
return "Select a node on the left for more information."; return "Select a node on the left for more information.";
@ -95,7 +89,7 @@ export class HaAutomationTracePathDetails extends LitElement {
if (pathParts[pathParts.length - 1] === "default") { if (pathParts[pathParts.length - 1] === "default") {
const parentTraceInfo = paths[ const parentTraceInfo = paths[
pathParts.slice(0, pathParts.length - 1).join("/") pathParts.slice(0, pathParts.length - 1).join("/")
] as ChooseActionTrace[]; ] as ChooseActionTraceStep[];
if (parentTraceInfo && parentTraceInfo[0]?.result?.choice === "default") { if (parentTraceInfo && parentTraceInfo[0]?.result?.choice === "default") {
return "The default node was executed because no choices matched."; return "The default node was executed because no choices matched.";
@ -106,7 +100,7 @@ export class HaAutomationTracePathDetails extends LitElement {
return "This node was not executed and so no further trace information is available."; return "This node was not executed and so no further trace information is available.";
} }
const data: ActionTrace[] = paths[this.selected.path]; const data: ActionTraceStep[] = paths[this.selected.path];
return data.map((trace, idx) => { return data.map((trace, idx) => {
const { const {
@ -146,16 +140,11 @@ export class HaAutomationTracePathDetails extends LitElement {
} }
private _renderChangedVars() { private _renderChangedVars() {
const paths = this._getPaths(); const paths = this.trace.trace;
const data: ActionTrace[] = paths[this.selected.path]; const data: ActionTraceStep[] = paths[this.selected.path];
return html` return html`
<div class="padded-box"> <div class="padded-box">
<p>
The following variables have changed while the step ran. If this is
the first condition or action, this will include the trigger
variables.
</p>
${data.map( ${data.map(
(trace, idx) => html` (trace, idx) => html`
${idx > 0 ? html`<p>Iteration ${idx + 1}</p>` : ""} ${idx > 0 ? html`<p>Iteration ${idx + 1}</p>` : ""}
@ -171,15 +160,9 @@ ${safeDump(trace.changed_variables).trimRight()}</pre
} }
private _renderLogbook() { private _renderLogbook() {
const paths = { const paths = this.trace.trace;
...this.trace.condition_trace,
...this.trace.action_trace,
};
const startTrace = paths[this.selected.path]; const startTrace = paths[this.selected.path];
const trackedPaths = Object.keys(this.trackedNodes); const trackedPaths = Object.keys(this.trackedNodes);
const index = trackedPaths.indexOf(this.selected.path); const index = trackedPaths.indexOf(this.selected.path);
if (index === -1) { if (index === -1) {

View File

@ -367,7 +367,7 @@ export class HaAutomationTrace extends LitElement {
const nodes = this.shadowRoot!.querySelector( const nodes = this.shadowRoot!.querySelector(
"hat-script-graph" "hat-script-graph"
)!.getTrackedNodes(); )!.getTrackedNodes();
this._selected = nodes[path].nodeInfo; this._selected = nodes[path]?.nodeInfo;
} }
static get styles(): CSSResult[] { static get styles(): CSSResult[] {