Adjust traces to latest API (#8656)

This commit is contained in:
Paulus Schoutsen 2021-03-16 10:43:30 -07:00 committed by GitHub
parent f34dfde925
commit 3c75eb96f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 193 additions and 244 deletions

View File

@ -15,6 +15,7 @@ export const basicTrace: DemoTrace = {
action_trace: {
"action/0": [
{
path: "action/0",
timestamp: "2021-03-12T21:38:48.054395+00:00",
changed_variables: {
trigger: {
@ -60,6 +61,7 @@ export const basicTrace: DemoTrace = {
condition_trace: {
"condition/0": [
{
path: "condition/0",
timestamp: "2021-03-12T21:38:48.050783+00:00",
changed_variables: {
trigger: {

View File

@ -1,187 +0,0 @@
import { DemoTrace } from "./types";
export const deviceTriggerEventTrace: DemoTrace = {
trace: {
last_action: "action/0",
last_condition: null,
run_id: "3",
state: "stopped",
timestamp: {
start: "2021-03-13T10:30:30.058878+00:00",
finish: "2021-03-13T10:30:30.205801+00:00",
},
trigger: "event 'lutron_caseta_button_event'",
unique_id: "1578616228911",
action_trace: {
"action/0": [
{
timestamp: "2021-03-13T10:30:30.059607+00:00",
changed_variables: {
trigger: {
platform: "device",
event: {
event_type: "lutron_caseta_button_event",
data: {
serial: 47850540,
type: "Pico3ButtonRaiseLower",
button_number: 4,
device_name: "Right Light Pico",
area_name: "Master Bed",
action: "press",
},
origin: "LOCAL",
time_fired: "2021-03-13T10:30:30.053185+00:00",
context: {
id: "e5387dff0c615c67e8fa43bf9d5d72ca",
parent_id: null,
user_id: null,
},
},
description: "event 'lutron_caseta_button_event'",
},
context: {
id: "1c7d0dd26e031960e0ccbf0d9e0d8a16",
parent_id: "e5387dff0c615c67e8fa43bf9d5d72ca",
user_id: null,
},
},
},
],
},
condition_trace: {},
config: {
id: "1578616228911",
alias: "Turn Off Master Bed Lights from Picos",
description: "",
trigger: [
{
platform: "device",
device_id: "36fd7cb4103ad0ce927e26a7ee44fa3a",
domain: "lutron_caseta",
type: "press",
subtype: "off",
},
{
platform: "device",
device_id: "392111b5a9a362db57e2c49ec68b7a40",
domain: "lutron_caseta",
type: "press",
subtype: "off",
},
],
condition: [],
action: [
{
entity_id: "light.master_bed_lights",
service: "light.turn_off",
},
],
mode: "single",
},
context: {
id: "1c7d0dd26e031960e0ccbf0d9e0d8a16",
parent_id: "e5387dff0c615c67e8fa43bf9d5d72ca",
user_id: null,
},
variables: {
trigger: {
platform: "device",
event: {
event_type: "lutron_caseta_button_event",
data: {
serial: 47850540,
type: "Pico3ButtonRaiseLower",
button_number: 4,
device_name: "Right Light Pico",
area_name: "Master Bed",
action: "press",
},
origin: "LOCAL",
time_fired: "2021-03-13T10:30:30.053185+00:00",
context: {
id: "e5387dff0c615c67e8fa43bf9d5d72ca",
parent_id: null,
user_id: null,
},
},
description: "event 'lutron_caseta_button_event'",
},
},
},
logbookEntries: [
{
name: "Turn Off Master Bed Lights from Picos",
message: "has been triggered by event 'lutron_caseta_button_event'",
source: "event 'lutron_caseta_button_event'",
entity_id: "automation.turn_off_master_bed_lights_from_picos",
when: "2021-03-13T10:30:30.059052+00:00",
domain: "automation",
},
{
when: "2021-03-13T10:30:30.200532+00:00",
name: "Master Bed Lights",
state: "off",
entity_id: "light.master_bed_lights",
context_entity_id: "automation.turn_off_master_bed_lights_from_picos",
context_entity_id_name: "Turn Off Master Bed Lights from Picos",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Turn Off Master Bed Lights from Picos",
},
{
when: "2021-03-13T10:30:30.200532+00:00",
name: "Master Bed Lights",
state: "off",
entity_id: "light.master_bed_lights",
context_entity_id: "automation.turn_off_master_bed_lights_from_picos",
context_entity_id_name: "Turn Off Master Bed Lights from Picos",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Turn Off Master Bed Lights from Picos",
},
{
when: "2021-03-13T10:30:30.200532+00:00",
name: "Master Bed Lights",
state: "off",
entity_id: "light.master_bed_lights",
context_entity_id: "automation.turn_off_master_bed_lights_from_picos",
context_entity_id_name: "Turn Off Master Bed Lights from Picos",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Turn Off Master Bed Lights from Picos",
},
{
when: "2021-03-13T10:30:30.200532+00:00",
name: "Master Bed Lights",
state: "off",
entity_id: "light.master_bed_lights",
context_entity_id: "automation.turn_off_master_bed_lights_from_picos",
context_entity_id_name: "Turn Off Master Bed Lights from Picos",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Turn Off Master Bed Lights from Picos",
},
{
when: "2021-03-13T10:30:30.200532+00:00",
name: "Master Bed Lights",
state: "off",
entity_id: "light.master_bed_lights",
context_entity_id: "automation.turn_off_master_bed_lights_from_picos",
context_entity_id_name: "Turn Off Master Bed Lights from Picos",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Turn Off Master Bed Lights from Picos",
},
{
when: "2021-03-13T10:30:30.200532+00:00",
name: "Master Bed Lights",
state: "off",
entity_id: "light.master_bed_lights",
context_entity_id: "automation.turn_off_master_bed_lights_from_picos",
context_entity_id_name: "Turn Off Master Bed Lights from Picos",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Turn Off Master Bed Lights from Picos",
},
],
};

View File

@ -15,6 +15,7 @@ export const motionLightTrace: DemoTrace = {
action_trace: {
"action/0": [
{
path: "action/0",
timestamp: "2021-03-14T06:07:01.771038+00:00",
changed_variables: {
trigger: {
@ -64,12 +65,11 @@ export const motionLightTrace: DemoTrace = {
},
],
"action/1": [
{
timestamp: "2021-03-14T06:07:01.875316+00:00",
},
{ path: "action/1", timestamp: "2021-03-14T06:07:01.875316+00:00" },
],
"action/2": [
{
path: "action/2",
timestamp: "2021-03-14T06:07:53.195013+00:00",
changed_variables: {
wait: {
@ -118,6 +118,7 @@ export const motionLightTrace: DemoTrace = {
],
"action/3": [
{
path: "action/3",
timestamp: "2021-03-14T06:07:53.196014+00:00",
},
],

View File

@ -13,13 +13,8 @@ import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace";
import { deviceTriggerEventTrace } from "../data/traces/device_trigger_event_trace";
const traces: DemoTrace[] = [
basicTrace,
motionLightTrace,
deviceTriggerEventTrace,
];
const traces: DemoTrace[] = [basicTrace, motionLightTrace];
@customElement("demo-automation-trace")
export class DemoAutomationTrace extends LitElement {

View File

@ -11,7 +11,7 @@ import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_tim
import {
ActionTrace,
AutomationTraceExtended,
getConfigFromPath,
getDataFromPath,
} from "../../data/automation_debug";
import { HomeAssistant } from "../../types";
import "./ha-timeline";
@ -24,7 +24,7 @@ import {
mdiStopCircleOutline,
} from "@mdi/js";
import { LogbookEntry } from "../../data/logbook";
import { Action, describeAction } from "../../data/script";
import { describeAction } from "../../data/script";
import relativeTime from "../../common/datetime/relative_time";
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
@ -68,7 +68,7 @@ export class HaAutomationTracer extends LitElement {
? mdiCheckCircleOutline
: mdiStopCircleOutline}
>
${getConfigFromPath(this.trace!.config, path).alias ||
${getDataFromPath(this.trace!.config, path).alias ||
pathToName(path)}
${value[0].result.result ? "passed" : "failed"}
</ha-timeline>
@ -77,7 +77,7 @@ export class HaAutomationTracer extends LitElement {
}
if (this.trace.action_trace && this.logbookEntries) {
const actionTraces = Object.entries(this.trace.action_trace);
const actionTraces = Object.values(this.trace.action_trace);
let logbookIndex = 0;
let actionTraceIndex = 0;
@ -123,7 +123,7 @@ export class HaAutomationTracer extends LitElement {
// Find next item time-wise.
const logbookItem = this.logbookEntries[logbookIndex];
const actionTrace = actionTraces[actionTraceIndex];
const actionTimestamp = new Date(actionTrace[1][0].timestamp);
const actionTimestamp = new Date(actionTrace[0].timestamp);
if (new Date(logbookItem.when) > actionTimestamp) {
actionTraceIndex++;
@ -133,7 +133,7 @@ export class HaAutomationTracer extends LitElement {
groupedLogbookItems = [];
}
maybeRenderTime(actionTimestamp);
entries.push(this._renderActionTrace(...actionTrace));
entries.push(this._renderActionTrace(actionTrace));
} else {
logbookIndex++;
groupedLogbookItems.push(logbookItem);
@ -152,8 +152,8 @@ export class HaAutomationTracer extends LitElement {
while (actionTraceIndex < actionTraces.length) {
const trace = actionTraces[actionTraceIndex];
maybeRenderTime(new Date(trace[1][0].timestamp));
entries.push(this._renderActionTrace(...trace));
maybeRenderTime(new Date(trace[0].timestamp));
entries.push(this._renderActionTrace(trace));
actionTraceIndex++;
}
}
@ -221,11 +221,25 @@ export class HaAutomationTracer extends LitElement {
`;
}
private _renderActionTrace(path: string, _value: ActionTrace[]) {
const action = getConfigFromPath(this.trace!.config, path) as Action;
private _renderActionTrace(value: ActionTrace[]) {
const path = value[0].path;
let data;
try {
data = getDataFromPath(this.trace!.config, path);
} catch (err) {
return html`Unable to extract path ${path}. Download trace and report as
bug`;
}
const description =
// Top-level we know it's an action
path.split("/").length === 2
? data.alias || describeAction(data, this.hass.localize)
: path.replace(/\//g, " ");
return html`
<ha-timeline .icon=${mdiRecordCircleOutline}>
${action.alias || describeAction(action, this.hass.localize)}
${description}
</ha-timeline>
`;
}

View File

@ -38,6 +38,7 @@ export interface ManualAutomationConfig {
| "info"
| "debug"
| "notset";
variables?: Record<string, unknown>;
}
export interface BlueprintAutomationConfig extends ManualAutomationConfig {
@ -55,7 +56,7 @@ export interface StateTrigger {
entity_id: string;
attribute?: string;
from?: string | number;
to?: string | number;
to?: string | string[] | number;
for?: string | number | ForDict;
}

View File

@ -1,6 +1,5 @@
import { HomeAssistant, Context } from "../types";
import { AutomationConfig, Condition } from "./automation";
import { Action } from "./script";
import { AutomationConfig } from "./automation";
interface TraceVariables extends Record<string, unknown> {
trigger: {
@ -10,6 +9,7 @@ interface TraceVariables extends Record<string, unknown> {
}
interface BaseTrace {
path: string;
timestamp: string;
changed_variables?: Record<string, unknown>;
}
@ -18,7 +18,18 @@ export interface ConditionTrace extends BaseTrace {
result: { result: boolean };
}
export type ActionTrace = BaseTrace;
export interface ChooseActionTrace extends BaseTrace {
result: { choice: number };
}
export interface ChooseChoiceActionTrace extends BaseTrace {
result: { result: boolean };
}
export type ActionTrace =
| BaseTrace
| ChooseActionTrace
| ChooseChoiceActionTrace;
export interface AutomationTrace {
last_action: string | null;
@ -53,16 +64,40 @@ export const loadAutomationTrace = (
});
export const loadAutomationTraces = (
hass: HomeAssistant
hass: HomeAssistant,
automation_id?: string
): Promise<AutomationTrace[]> =>
hass.callWS({
type: "automation/trace/list",
automation_id,
});
export const getConfigFromPath = <T extends Condition | Action>(
export const getDataFromPath = (
config: AutomationConfig,
path: string
): T => {
const parts = path.split("/");
return config[parts[0]][Number(parts[1])];
): any => {
const parts = path.split("/").reverse();
let result: any = config;
while (parts.length) {
const raw = parts.pop()!;
const asNumber = Number(raw);
if (isNaN(asNumber)) {
result = result[raw];
continue;
}
if (Array.isArray(result)) {
result = result[asNumber];
continue;
}
if (asNumber !== 0) {
throw new Error("If config is not an array, can only return index 0");
}
}
return result;
};

View File

@ -118,6 +118,36 @@ class HaAutomationPicker extends LitElement {
></ha-icon-button>
`,
};
columns.trace = {
title: "",
type: "icon-button",
template: (_info, automation: any) => html`
<a
href=${ifDefined(
automation.attributes.id
? `/config/automation/trace/${automation.attributes.id}`
: undefined
)}
>
<ha-icon-button
icon="hass:hammer"
.disabled=${!automation.attributes.id}
title="${this.hass.localize(
"ui.panel.config.automation.picker.dev_automation"
)}"
></ha-icon-button>
</a>
${!automation.attributes.id
? html`
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.automation.picker.dev_only_editable"
)}
</paper-tooltip>
`
: ""}
`,
};
columns.edit = {
title: "",
type: "icon-button",

View File

@ -10,6 +10,7 @@ import {
} from "lit-element";
import { AutomationEntity } from "../../../../data/automation";
import {
AutomationTrace,
AutomationTraceExtended,
loadAutomationTrace,
loadAutomationTraces,
@ -23,6 +24,9 @@ import {
getLogbookDataForContext,
LogbookEntry,
} from "../../../../data/logbook";
import { formatDateTimeWithSeconds } from "../../../../common/datetime/format_date_time";
import { repeat } from "lit-html/directives/repeat";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
@customElement("ha-automation-trace")
export class HaAutomationTrace extends LitElement {
@ -40,6 +44,10 @@ export class HaAutomationTrace extends LitElement {
@internalProperty() private _entityId?: string;
@internalProperty() private _traces?: AutomationTrace[];
@internalProperty() private _runId?: string;
@internalProperty() private _trace?: AutomationTraceExtended;
@internalProperty() private _logbookEntries?: LogbookEntry[];
@ -63,24 +71,45 @@ export class HaAutomationTrace extends LitElement {
}`}
>
<div class="actions">
<button @click=${this._loadTrace}>
Load last trace
${this._traces && this._traces.length > 0
? html`
<select .value=${this._runId} @change=${this._pickTrace}>
${repeat(
this._traces,
(trace) => trace.run_id,
(trace) =>
html`<option value=${trace.run_id}
>${formatDateTimeWithSeconds(
new Date(trace.timestamp.start),
this.hass.language
)}</option
>`
)}
</select>
`
: ""}
<button @click=${this._loadTraces}>
Refresh
</button>
<button @click=${this._downloadTrace}>
Download
</button>
</div>
${this._trace
? html`
<div class="card-content">
<div class="card-content">
${this._traces === undefined
? "Loading…"
: this._traces.length === 0
? "No traces found"
: this._trace === undefined
? "Loading…"
: html`
<hat-trace
.hass=${this.hass}
.trace=${this._trace}
.logbookEntries=${this._logbookEntries}
></hat-trace>
</div>
`
: ""}
`}
</div>
</ha-card>
</hass-tabs-subpage>
`;
@ -90,10 +119,22 @@ export class HaAutomationTrace extends LitElement {
super.updated(changedProps);
if (changedProps.has("automationId")) {
this._traces = undefined;
this._entityId = undefined;
this._runId = undefined;
this._trace = undefined;
this._logbookEntries = undefined;
this._loadTrace();
if (this.automationId) {
this._loadTraces();
}
}
if (changedProps.has("_runId") && this._runId) {
this._trace = undefined;
this._logbookEntries = undefined;
if (this._runId) {
this._loadTrace();
}
}
if (
@ -101,24 +142,44 @@ export class HaAutomationTrace extends LitElement {
this.automationId &&
!this._entityId
) {
this._setEntityId();
const automation = this.automations.find(
(entity: AutomationEntity) => entity.attributes.id === this.automationId
);
this._entityId = automation?.entity_id;
}
}
private _pickTrace(ev) {
this._runId = ev.target.value;
}
private async _loadTraces() {
this._traces = await loadAutomationTraces(this.hass, this.automationId);
// Newest will be on top.
this._traces.reverse();
// Check if current run ID still exists
if (
this._runId &&
!this._traces.some((trace) => trace.run_id === this._runId)
) {
await showAlertDialog(this, {
text: "Chosen trace is no longer available",
});
this._runId = undefined;
}
// See if we can set a default runID
if (!this._runId && this._traces.length > 0) {
this._runId = this._traces[0].run_id;
}
}
private async _loadTrace() {
const traces = await loadAutomationTraces(this.hass);
const automationTraces = traces[this.automationId];
if (!automationTraces || automationTraces.length === 0) {
// TODO no trace found.
alert("NO traces found");
return;
}
const trace = await loadAutomationTrace(
this.hass,
this.automationId,
automationTraces[automationTraces.length - 1].run_id
this._runId!
);
this._logbookEntries = await getLogbookDataForContext(
this.hass,
@ -129,20 +190,15 @@ export class HaAutomationTrace extends LitElement {
this._trace = trace;
}
private _setEntityId() {
const automation = this.automations.find(
(entity: AutomationEntity) => entity.attributes.id === this.automationId
);
this._entityId = automation?.entity_id;
}
private _backTapped(): void {
history.back();
}
private _downloadTrace() {
const aEl = document.createElement("a");
aEl.download = `trace-${this._entityId}.json`;
aEl.download = `trace ${this._entityId} ${
this._trace!.timestamp.start
}.json`;
aEl.href = `data:application/json;charset=utf-8,${encodeURI(
JSON.stringify(
{

View File

@ -1180,7 +1180,9 @@
"no_automations": "We couldnt find any editable automations",
"add_automation": "Add automation",
"only_editable": "Only automations defined in automations.yaml are editable.",
"dev_only_editable": "Only automations defined in automations.yaml are debuggable.",
"edit_automation": "Edit automation",
"dev_automation": "Debug automation",
"show_info_automation": "Show info about automation",
"delete_automation": "Delete automation",
"delete_confirm": "Are you sure you want to delete this automation?",