mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-09 18:36:35 +00:00
Render script execution state (#8789)
This commit is contained in:
parent
b6f59d3c98
commit
401064d3c8
@ -2,8 +2,7 @@ import { DemoTrace } from "./types";
|
||||
|
||||
export const basicTrace: DemoTrace = {
|
||||
trace: {
|
||||
last_action: "action/2",
|
||||
last_condition: "condition/0",
|
||||
last_step: "action/2",
|
||||
run_id: "0",
|
||||
state: "stopped",
|
||||
timestamp: {
|
||||
@ -14,6 +13,12 @@ export const basicTrace: DemoTrace = {
|
||||
domain: "automation",
|
||||
item_id: "1615419646544",
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
},
|
||||
],
|
||||
"condition/0": [
|
||||
{
|
||||
path: "condition/0",
|
||||
@ -284,45 +289,7 @@ export const basicTrace: DemoTrace = {
|
||||
parent_id: "664d6d261450a9ecea6738e97269a149",
|
||||
user_id: null,
|
||||
},
|
||||
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",
|
||||
},
|
||||
},
|
||||
script_execution: "finished",
|
||||
},
|
||||
logbookEntries: [
|
||||
{
|
||||
|
44
gallery/src/data/traces/mock-demo-trace.ts
Normal file
44
gallery/src/data/traces/mock-demo-trace.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { LogbookEntry } from "../../../../src/data/logbook";
|
||||
import { AutomationTraceExtended } from "../../../../src/data/trace";
|
||||
import { DemoTrace } from "./types";
|
||||
|
||||
export const mockDemoTrace = (
|
||||
tracePartial: Partial<AutomationTraceExtended>,
|
||||
logbookEntries?: LogbookEntry[]
|
||||
): DemoTrace => ({
|
||||
trace: {
|
||||
last_step: "",
|
||||
run_id: "0",
|
||||
state: "stopped",
|
||||
timestamp: {
|
||||
start: "2021-03-25T04:36:51.223693+00:00",
|
||||
finish: "2021-03-25T04:36:51.266132+00:00",
|
||||
},
|
||||
trigger: "mocked trigger",
|
||||
domain: "automation",
|
||||
item_id: "1615419646544",
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
changed_variables: {
|
||||
trigger: {
|
||||
description: "mocked trigger",
|
||||
},
|
||||
},
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
},
|
||||
],
|
||||
},
|
||||
config: {
|
||||
trigger: [],
|
||||
action: [],
|
||||
},
|
||||
context: {
|
||||
id: "abcd",
|
||||
},
|
||||
script_execution: "finished",
|
||||
...tracePartial,
|
||||
},
|
||||
logbookEntries: logbookEntries || [],
|
||||
});
|
@ -2,8 +2,7 @@ import { DemoTrace } from "./types";
|
||||
|
||||
export const motionLightTrace: DemoTrace = {
|
||||
trace: {
|
||||
last_action: "action/3",
|
||||
last_condition: null,
|
||||
last_step: "action/3",
|
||||
run_id: "1",
|
||||
state: "stopped",
|
||||
timestamp: {
|
||||
@ -14,6 +13,12 @@ export const motionLightTrace: DemoTrace = {
|
||||
domain: "automation",
|
||||
item_id: "1614732497392",
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
},
|
||||
],
|
||||
"action/0": [
|
||||
{
|
||||
path: "action/0",
|
||||
@ -171,45 +176,7 @@ export const motionLightTrace: DemoTrace = {
|
||||
parent_id: "e22ddfd5f11dc4aad9a52fc10dab613b",
|
||||
user_id: null,
|
||||
},
|
||||
variables: {
|
||||
trigger: {
|
||||
platform: "state",
|
||||
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
from_state: {
|
||||
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
state: "off",
|
||||
attributes: {
|
||||
friendly_name: "Paulus’s MacBook Pro Camera In Use",
|
||||
icon: "mdi:camera-off",
|
||||
},
|
||||
last_changed: "2021-03-14T06:06:29.235325+00:00",
|
||||
last_updated: "2021-03-14T06:06:29.235325+00:00",
|
||||
context: {
|
||||
id: "ad4864c5ce957c38a07b50378eeb245d",
|
||||
parent_id: null,
|
||||
user_id: null,
|
||||
},
|
||||
},
|
||||
to_state: {
|
||||
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
state: "on",
|
||||
attributes: {
|
||||
friendly_name: "Paulus’s MacBook Pro Camera In Use",
|
||||
icon: "mdi:camera",
|
||||
},
|
||||
last_changed: "2021-03-14T06:07:01.762009+00:00",
|
||||
last_updated: "2021-03-14T06:07:01.762009+00:00",
|
||||
context: {
|
||||
id: "e22ddfd5f11dc4aad9a52fc10dab613b",
|
||||
parent_id: null,
|
||||
user_id: null,
|
||||
},
|
||||
},
|
||||
for: null,
|
||||
attribute: null,
|
||||
description: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
},
|
||||
},
|
||||
script_execution: "finished",
|
||||
},
|
||||
logbookEntries: [
|
||||
{
|
||||
|
87
gallery/src/demos/demo-automation-trace-timeline.ts
Normal file
87
gallery/src/demos/demo-automation-trace-timeline.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/trace/hat-script-graph";
|
||||
import "../../../src/components/trace/hat-trace-timeline";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { mockDemoTrace } from "../data/traces/mock-demo-trace";
|
||||
import { DemoTrace } from "../data/traces/types";
|
||||
|
||||
const traces: DemoTrace[] = [
|
||||
mockDemoTrace({ state: "running" }),
|
||||
mockDemoTrace({ state: "debugged" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "failed_condition" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "failed_single" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "failed_max_runs" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "finished" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "aborted" }),
|
||||
mockDemoTrace({
|
||||
state: "stopped",
|
||||
script_execution: "error",
|
||||
error: 'Variable "beer" cannot be None',
|
||||
}),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "cancelled" }),
|
||||
];
|
||||
|
||||
@customElement("demo-automation-trace-timeline")
|
||||
export class DemoAutomationTraceTimeline extends LitElement {
|
||||
@property({ attribute: false }) hass?: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${traces.map(
|
||||
(trace) => html`
|
||||
<ha-card .header=${trace.trace.config.alias}>
|
||||
<div class="card-content">
|
||||
<hat-trace-timeline
|
||||
.hass=${this.hass}
|
||||
.trace=${trace.trace}
|
||||
.logbookEntries=${trace.logbookEntries}
|
||||
></hat-trace-timeline>
|
||||
<button @click=${() => console.log(trace)}>Log trace</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
}
|
||||
button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-automation-trace-timeline": DemoAutomationTraceTimeline;
|
||||
}
|
||||
}
|
@ -20,9 +20,11 @@ import { HomeAssistant } from "../../types";
|
||||
import "./ha-timeline";
|
||||
import type { HaTimeline } from "./ha-timeline";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCircle,
|
||||
mdiCircleOutline,
|
||||
mdiPauseCircleOutline,
|
||||
mdiProgressClock,
|
||||
mdiProgressWrench,
|
||||
mdiRecordCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { LogbookEntry } from "../../data/logbook";
|
||||
@ -34,6 +36,7 @@ import {
|
||||
import relativeTime from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { describeAction } from "../../data/script_i18n";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
|
||||
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
|
||||
|
||||
@ -273,7 +276,7 @@ class ActionRenderer {
|
||||
`Triggered ${
|
||||
triggerStep.path === "trigger"
|
||||
? "manually"
|
||||
: `by the ${triggerStep.changed_variables.trigger.description}`
|
||||
: `by the ${this.trace.trigger}`
|
||||
} at
|
||||
${formatDateTimeWithSeconds(
|
||||
new Date(triggerStep.timestamp),
|
||||
@ -421,29 +424,92 @@ export class HaAutomationTracer extends LitElement {
|
||||
|
||||
logbookRenderer.flush();
|
||||
|
||||
// Render footer
|
||||
const renderFinishedAt = () =>
|
||||
formatDateTimeWithSeconds(
|
||||
new Date(this.trace!.timestamp.finish!),
|
||||
this.hass.locale
|
||||
);
|
||||
const renderRuntime = () => `(runtime:
|
||||
${(
|
||||
(new Date(this.trace!.timestamp.finish!).getTime() -
|
||||
new Date(this.trace!.timestamp.start).getTime()) /
|
||||
1000
|
||||
).toFixed(2)}
|
||||
seconds)`;
|
||||
|
||||
let entry: {
|
||||
description: TemplateResult | string;
|
||||
icon: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
if (this.trace.state === "running") {
|
||||
entry = {
|
||||
description: "Still running",
|
||||
icon: mdiProgressClock,
|
||||
};
|
||||
} else if (this.trace.state === "debugged") {
|
||||
entry = {
|
||||
description: "Debugged",
|
||||
icon: mdiProgressWrench,
|
||||
};
|
||||
} else if (this.trace.script_execution === "finished") {
|
||||
entry = {
|
||||
description: `Finished at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
icon: mdiCircle,
|
||||
};
|
||||
} else if (this.trace.script_execution === "aborted") {
|
||||
entry = {
|
||||
description: `Aborted at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
icon: mdiAlertCircle,
|
||||
};
|
||||
} else if (this.trace.script_execution === "cancelled") {
|
||||
entry = {
|
||||
description: `Cancelled at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
icon: mdiAlertCircle,
|
||||
};
|
||||
} else {
|
||||
let reason: string;
|
||||
let isError = false;
|
||||
let extra: TemplateResult | undefined;
|
||||
|
||||
switch (this.trace.script_execution) {
|
||||
case "failed_condition":
|
||||
reason = "a condition failed";
|
||||
break;
|
||||
case "failed_single":
|
||||
reason = "only a single execution is allowed";
|
||||
break;
|
||||
case "failed_max_runs":
|
||||
reason = "maximum number of parallel runs reached";
|
||||
break;
|
||||
case "error":
|
||||
reason = "an error was encountered";
|
||||
isError = true;
|
||||
extra = html`<br /><br />${this.trace.error!}`;
|
||||
break;
|
||||
default:
|
||||
reason = `of unknown reason "${this.trace.script_execution}"`;
|
||||
isError = true;
|
||||
}
|
||||
|
||||
entry = {
|
||||
description: html`Stopped because ${reason} at ${renderFinishedAt()}
|
||||
${renderRuntime()}${extra || ""}`,
|
||||
icon: mdiAlertCircle,
|
||||
className: isError ? "error" : undefined,
|
||||
};
|
||||
}
|
||||
// null means it was stopped by a condition
|
||||
if (this.trace.last_action !== null) {
|
||||
if (entry) {
|
||||
entries.push(html`
|
||||
<ha-timeline
|
||||
lastItem
|
||||
.icon=${this.trace.timestamp.finish
|
||||
? mdiCircle
|
||||
: mdiPauseCircleOutline}
|
||||
.icon=${entry.icon}
|
||||
class=${ifDefined(entry.className)}
|
||||
>
|
||||
${this.trace.timestamp.finish
|
||||
? html`Finished at
|
||||
${formatDateTimeWithSeconds(
|
||||
new Date(this.trace.timestamp.finish),
|
||||
this.hass.locale
|
||||
)}
|
||||
(runtime:
|
||||
${(
|
||||
(new Date(this.trace.timestamp.finish!).getTime() -
|
||||
new Date(this.trace.timestamp.start).getTime()) /
|
||||
1000
|
||||
).toFixed(2)}
|
||||
seconds)`
|
||||
: "Still running"}
|
||||
${entry.description}
|
||||
</ha-timeline>
|
||||
`);
|
||||
}
|
||||
@ -506,6 +572,10 @@ export class HaAutomationTracer extends LitElement {
|
||||
ha-timeline[data-path] {
|
||||
cursor: pointer;
|
||||
}
|
||||
.error {
|
||||
--timeline-ball-color: var(--error-color);
|
||||
color: var(--error-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -54,22 +54,40 @@ export type ActionTraceStep =
|
||||
export interface AutomationTrace {
|
||||
domain: string;
|
||||
item_id: string;
|
||||
last_action: string | null;
|
||||
last_condition: string | null;
|
||||
last_step: string | null;
|
||||
run_id: string;
|
||||
state: "running" | "stopped" | "debugged";
|
||||
timestamp: {
|
||||
start: string;
|
||||
finish: string | null;
|
||||
};
|
||||
trigger: unknown;
|
||||
script_execution:
|
||||
| // The script was not executed because the automation's condition failed
|
||||
"failed_condition"
|
||||
// The script was not executed because the run mode is single
|
||||
| "failed_single"
|
||||
// The script was not executed because max parallel runs would be exceeded
|
||||
| "failed_max_runs"
|
||||
// All script steps finished:
|
||||
| "finished"
|
||||
// Script execution stopped by the script itself because a condition fails, wait_for_trigger timeouts etc:
|
||||
| "aborted"
|
||||
// Details about failing condition, timeout etc. is in the last element of the trace
|
||||
// Script execution stops because of an unexpected exception:
|
||||
| "error"
|
||||
// The exception is in the trace itself or in the last element of the trace
|
||||
// Script execution stopped by async_stop called on the script run because home assistant is shutting down, script mode is SCRIPT_MODE_RESTART etc:
|
||||
| "cancelled"
|
||||
| string;
|
||||
// Automation only, should become it's own type when we support script in frontend
|
||||
trigger: string;
|
||||
}
|
||||
|
||||
export interface AutomationTraceExtended extends AutomationTrace {
|
||||
trace: Record<string, ActionTraceStep[]>;
|
||||
context: Context;
|
||||
variables: Record<string, unknown>;
|
||||
config: AutomationConfig;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface TraceTypes {
|
||||
|
@ -1926,7 +1926,7 @@
|
||||
"@codemirror/state" "^0.18.0"
|
||||
"@codemirror/view" "^0.18.0"
|
||||
|
||||
"@codemirror/highlight@^0.18.0":
|
||||
"@codemirror/highlight@^0.18.0", "@codemirror/highlight@^0.18.1":
|
||||
version "0.18.3"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/highlight/-/highlight-0.18.3.tgz#50e268630f113c322a2dc97c9f68d71934fffcb0"
|
||||
integrity sha512-NmRmkmWl8ht6Y6Y39ghov84AMPCqhUPIH9fmILs2NaWxZFZf4jGCTzrULnmREGsTie+26+LbKUncIU+PBu1Qng==
|
||||
|
Loading…
x
Reference in New Issue
Block a user