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",
domain: "automation",
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": [
{
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: {
id: "1615419646544",
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",
domain: "automation",
item_id: "1614732497392",
action_trace: {
trace: {
"action/0": [
{
path: "action/0",
@ -124,7 +124,6 @@ export const motionLightTrace: DemoTrace = {
},
],
},
condition_trace: {},
config: {
mode: "restart",
max_exceeded: "silent",

View File

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

View File

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

View File

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

View File

@ -10,9 +10,9 @@ import {
TemplateResult,
} from "lit-element";
import {
ActionTrace,
ActionTraceStep,
AutomationTraceExtended,
ChooseActionTrace,
ChooseActionTraceStep,
getDataFromPath,
} from "../../../../data/trace";
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() {
const paths = this._getPaths();
const paths = this.trace.trace;
if (!this.selected?.path) {
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") {
const parentTraceInfo = paths[
pathParts.slice(0, pathParts.length - 1).join("/")
] as ChooseActionTrace[];
] as ChooseActionTraceStep[];
if (parentTraceInfo && parentTraceInfo[0]?.result?.choice === "default") {
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.";
}
const data: ActionTrace[] = paths[this.selected.path];
const data: ActionTraceStep[] = paths[this.selected.path];
return data.map((trace, idx) => {
const {
@ -146,16 +140,11 @@ export class HaAutomationTracePathDetails extends LitElement {
}
private _renderChangedVars() {
const paths = this._getPaths();
const data: ActionTrace[] = paths[this.selected.path];
const paths = this.trace.trace;
const data: ActionTraceStep[] = paths[this.selected.path];
return html`
<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(
(trace, idx) => html`
${idx > 0 ? html`<p>Iteration ${idx + 1}</p>` : ""}
@ -171,15 +160,9 @@ ${safeDump(trace.changed_variables).trimRight()}</pre
}
private _renderLogbook() {
const paths = {
...this.trace.condition_trace,
...this.trace.action_trace,
};
const paths = this.trace.trace;
const startTrace = paths[this.selected.path];
const trackedPaths = Object.keys(this.trackedNodes);
const index = trackedPaths.indexOf(this.selected.path);
if (index === -1) {

View File

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