mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-17 22:01:56 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a75c91941e |
@@ -0,0 +1,75 @@
|
||||
import type { DemoTrace } from "./types";
|
||||
|
||||
export const notTriggeredTrace: DemoTrace = {
|
||||
trace: {
|
||||
last_step: "trigger/0",
|
||||
run_id: "788767ce152d3d4475134bf1107986d4",
|
||||
state: "stopped",
|
||||
script_execution: "not_triggered",
|
||||
not_triggered: true,
|
||||
timestamp: {
|
||||
start: "2021-03-25T04:36:51.223337+00:00",
|
||||
finish: "2021-03-25T04:36:51.223341+00:00",
|
||||
},
|
||||
// Not-triggered traces have no trigger description.
|
||||
trigger: null,
|
||||
domain: "automation",
|
||||
item_id: "1781703842452",
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
timestamp: "2021-03-25T04:36:51.223340+00:00",
|
||||
changed_variables: {
|
||||
trigger: {
|
||||
id: "0",
|
||||
idx: "0",
|
||||
alias: null,
|
||||
platform: "light.turned_on",
|
||||
},
|
||||
},
|
||||
result: {
|
||||
reason: "new_state_not_a_match",
|
||||
data: {
|
||||
entity_id: "light.bed_light",
|
||||
to_state: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
config: {
|
||||
id: "1781703842452",
|
||||
alias: "Light Turned On Notification",
|
||||
description: "Send a notification when a specific light is turned on.",
|
||||
triggers: [
|
||||
{
|
||||
trigger: "light.turned_on",
|
||||
target: {
|
||||
floor_id: "test",
|
||||
},
|
||||
options: {
|
||||
for: "00:00:00",
|
||||
behavior: "each",
|
||||
},
|
||||
},
|
||||
],
|
||||
conditions: [],
|
||||
actions: [
|
||||
{
|
||||
action: "notify.notify",
|
||||
data: {
|
||||
message: "A light was turned on.",
|
||||
},
|
||||
},
|
||||
],
|
||||
mode: "single",
|
||||
},
|
||||
context: {
|
||||
id: "01KVAX7CG7XBDYGJYAGA4XJHGX",
|
||||
parent_id: "01KVAX7CG631JRX4H3JS5JJ11Q",
|
||||
user_id: null,
|
||||
},
|
||||
},
|
||||
logbookEntries: [],
|
||||
};
|
||||
@@ -24,6 +24,33 @@ const traces: DemoTrace[] = [
|
||||
error: 'Variable "beer" cannot be None',
|
||||
}),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "cancelled" }),
|
||||
mockDemoTrace({
|
||||
state: "stopped",
|
||||
script_execution: "not_triggered",
|
||||
not_triggered: true,
|
||||
// Not-triggered traces have no trigger description.
|
||||
trigger: null,
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
changed_variables: {
|
||||
trigger: {
|
||||
id: "0",
|
||||
idx: "0",
|
||||
alias: null,
|
||||
platform: "light.turned_on",
|
||||
},
|
||||
},
|
||||
result: {
|
||||
reason: "new_state_not_a_match",
|
||||
data: { entity_id: "light.bed_light", to_state: "off" },
|
||||
},
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-automation-trace-timeline")
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/trace/ha-trace-path-details";
|
||||
import type { HatScriptGraph } from "../../../../src/components/trace/hat-script-graph";
|
||||
import "../../../../src/components/trace/hat-script-graph";
|
||||
import "../../../../src/components/trace/hat-trace-timeline";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { basicTrace } from "../../data/traces/basic_trace";
|
||||
import { motionLightTrace } from "../../data/traces/motion-light-trace";
|
||||
import { notTriggeredTrace } from "../../data/traces/not-triggered-trace";
|
||||
import type { DemoTrace } from "../../data/traces/types";
|
||||
|
||||
const traces: DemoTrace[] = [basicTrace, motionLightTrace];
|
||||
const traces: DemoTrace[] = [basicTrace, motionLightTrace, notTriggeredTrace];
|
||||
|
||||
@customElement("demo-automation-trace")
|
||||
export class DemoAutomationTrace extends LitElement {
|
||||
@@ -20,18 +23,25 @@ export class DemoAutomationTrace extends LitElement {
|
||||
|
||||
@state() private _selected = {};
|
||||
|
||||
@queryAll("hat-script-graph") private _graphs!: NodeListOf<HatScriptGraph>;
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
${traces.map(
|
||||
(trace, idx) => html`
|
||||
${traces.map((trace, idx) => {
|
||||
const graph = this._graphs?.[idx];
|
||||
const selectedPath = this._selected[idx];
|
||||
const selectedNode = selectedPath
|
||||
? graph?.renderedNodes[selectedPath]
|
||||
: undefined;
|
||||
return html`
|
||||
<ha-card .header=${trace.trace.config.alias}>
|
||||
<div class="card-content">
|
||||
<hat-script-graph
|
||||
.trace=${trace.trace}
|
||||
.selected=${this._selected[idx]}
|
||||
.selected=${selectedPath}
|
||||
@graph-node-selected=${this._handleGraphNodeSelected}
|
||||
.sampleIdx=${idx}
|
||||
></hat-script-graph>
|
||||
@@ -40,15 +50,25 @@ export class DemoAutomationTrace extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.trace=${trace.trace}
|
||||
.logbookEntries=${trace.logbookEntries}
|
||||
.selectedPath=${this._selected[idx]}
|
||||
.selectedPath=${selectedPath}
|
||||
@value-changed=${this._handleTimelineValueChanged}
|
||||
.sampleIdx=${idx}
|
||||
></hat-trace-timeline>
|
||||
${selectedNode && graph
|
||||
? html`<ha-trace-path-details
|
||||
.hass=${this.hass}
|
||||
.trace=${trace.trace}
|
||||
.selected=${selectedNode}
|
||||
.logbookEntries=${trace.logbookEntries}
|
||||
.trackedNodes=${graph.trackedNodes}
|
||||
.renderedNodes=${graph.renderedNodes}
|
||||
></ha-trace-path-details>`
|
||||
: nothing}
|
||||
<button @click=${() => console.log(trace)}>Log trace</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiCheckCircleOutline,
|
||||
mdiChevronDown,
|
||||
mdiCircleOffOutline,
|
||||
mdiHelpCircleOutline,
|
||||
mdiProgressClock,
|
||||
mdiProgressWrench,
|
||||
@@ -84,6 +85,11 @@ class HaTracePicker extends LitElement {
|
||||
"ui.panel.config.automation.trace.picker.debugged"
|
||||
);
|
||||
item.icon_path = mdiProgressWrench;
|
||||
} else if (trace.not_triggered) {
|
||||
item.secondary = this.hass.localize(
|
||||
"ui.panel.config.automation.trace.picker.not_triggered"
|
||||
);
|
||||
item.icon_path = mdiCircleOffOutline;
|
||||
} else if (trace.script_execution === "finished") {
|
||||
item.secondary = this.hass.localize(
|
||||
"ui.panel.config.automation.trace.picker.finished",
|
||||
|
||||
@@ -17,9 +17,10 @@ import type {
|
||||
ChooseActionTraceStep,
|
||||
TraceExtended,
|
||||
} from "../../data/trace";
|
||||
import { getDataFromPath } from "../../data/trace";
|
||||
import { getDataFromPath, isTriggerPath } from "../../data/trace";
|
||||
import "../../panels/logbook/ha-logbook-renderer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-alert";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-tab-group";
|
||||
@@ -63,7 +64,7 @@ export class HaTracePathDetails extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="padded-box trace-info">
|
||||
${this._renderSelectedTraceInfo()}
|
||||
${this._renderNotTriggeredNotice()} ${this._renderSelectedTraceInfo()}
|
||||
</div>
|
||||
|
||||
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
|
||||
@@ -89,6 +90,22 @@ export class HaTracePathDetails extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderNotTriggeredNotice() {
|
||||
if (
|
||||
!this.trace.not_triggered ||
|
||||
!this.selected?.path ||
|
||||
!isTriggerPath(this.selected.path) ||
|
||||
!(this.selected.path in this.trace.trace)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<ha-alert alert-type="info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.automation.trace.path.not_triggered"
|
||||
)}
|
||||
</ha-alert>`;
|
||||
}
|
||||
|
||||
private _renderSelectedTraceInfo() {
|
||||
const paths = this.trace.trace;
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ export class HatGraphNode extends LitElement {
|
||||
@property({ attribute: "not-enabled", reflect: true, type: Boolean })
|
||||
notEnabled = false;
|
||||
|
||||
@property({ attribute: "not-triggered", reflect: true, type: Boolean })
|
||||
notTriggered = false;
|
||||
|
||||
@property({ attribute: "graph-start", reflect: true, type: Boolean })
|
||||
graphStart = false;
|
||||
|
||||
@@ -127,6 +130,9 @@ export class HatGraphNode extends LitElement {
|
||||
--stroke-clr: var(--hover-clr);
|
||||
--icon-clr: var(--default-icon-clr);
|
||||
}
|
||||
:host([not-triggered]) circle {
|
||||
stroke-dasharray: 4 3;
|
||||
}
|
||||
:host([not-enabled]) circle {
|
||||
--stroke-clr: var(--disabled-clr);
|
||||
}
|
||||
|
||||
@@ -90,21 +90,27 @@ export class HatScriptGraph extends LitElement {
|
||||
|
||||
private _renderTrigger(config: Trigger, i: number) {
|
||||
const path = `trigger/${i}`;
|
||||
const track = this.trace && path in this.trace.trace;
|
||||
const tracked = this.trace && path in this.trace.trace;
|
||||
// A not-triggered trace records the trigger that evaluated a change but
|
||||
// decided not to fire. It is still selectable (to view the reason), but
|
||||
// must not be shown as the path that ran.
|
||||
const notTriggered = !!(tracked && this.trace.not_triggered);
|
||||
const track = tracked && !notTriggered;
|
||||
this.renderedNodes[path] = { config, path, type: "trigger" };
|
||||
if (track) {
|
||||
if (tracked) {
|
||||
this.trackedNodes[path] = this.renderedNodes[path];
|
||||
}
|
||||
return html`
|
||||
<hat-graph-node
|
||||
graph-start
|
||||
?track=${track}
|
||||
?not-triggered=${notTriggered}
|
||||
@focus=${this._selectNode(config, path, "trigger")}
|
||||
?active=${this.selected === path}
|
||||
.iconPath=${mdiAsterisk}
|
||||
.notEnabled=${"enabled" in config && config.enabled === false}
|
||||
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||
tabindex=${track ? "0" : "-1"}
|
||||
tabindex=${tracked ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCircle,
|
||||
mdiCircleOffOutline,
|
||||
mdiCircleOutline,
|
||||
mdiProgressClock,
|
||||
mdiProgressWrench,
|
||||
@@ -323,6 +324,23 @@ class ActionRenderer {
|
||||
}
|
||||
|
||||
private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number {
|
||||
if (this.trace.not_triggered) {
|
||||
this._renderEntry(
|
||||
triggerStep.path,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.evaluated_not_triggered",
|
||||
{
|
||||
time: formatDateTimeWithSeconds(
|
||||
new Date(triggerStep.timestamp),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
),
|
||||
}
|
||||
),
|
||||
mdiCircleOffOutline
|
||||
);
|
||||
return index + 1;
|
||||
}
|
||||
this._renderEntry(
|
||||
triggerStep.path,
|
||||
this.hass.localize(
|
||||
@@ -725,6 +743,16 @@ export class HaAutomationTracer extends LitElement {
|
||||
),
|
||||
icon: mdiProgressWrench,
|
||||
};
|
||||
} else if (this.trace.not_triggered) {
|
||||
entry = {
|
||||
description: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.not_triggered",
|
||||
{
|
||||
time: renderFinishedAt(),
|
||||
}
|
||||
),
|
||||
icon: mdiCircleOffOutline,
|
||||
};
|
||||
} else if (this.trace.script_execution === "finished") {
|
||||
entry = {
|
||||
description: this.hass.localize(
|
||||
|
||||
+4
-1
@@ -184,8 +184,11 @@ export const createHistoricState = (
|
||||
// translate the bare description here.
|
||||
export const localizeTriggerSource = (
|
||||
localize: LocalizeFunc,
|
||||
source: string
|
||||
source: string | null
|
||||
) => {
|
||||
if (!source) {
|
||||
return "";
|
||||
}
|
||||
for (const key of Object.keys(triggerPhrases) as TriggerPhraseKey[]) {
|
||||
const phrase = triggerPhrases[key];
|
||||
if (source.startsWith(phrase)) {
|
||||
|
||||
+17
-4
@@ -18,12 +18,17 @@ interface BaseTraceStep {
|
||||
export interface TriggerTraceStep extends BaseTraceStep {
|
||||
changed_variables: {
|
||||
trigger: {
|
||||
alias?: string;
|
||||
description: string;
|
||||
alias?: string | null;
|
||||
// Absent on not-triggered traces, which have no trigger description.
|
||||
description?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
};
|
||||
// Present on not-triggered traces: a machine-readable reason code explaining
|
||||
// why the trigger evaluated a relevant change but decided not to fire, plus
|
||||
// optional diagnostic context.
|
||||
result?: { reason: string; data?: Record<string, unknown> };
|
||||
}
|
||||
|
||||
export interface ConditionTraceStep extends BaseTraceStep {
|
||||
@@ -61,6 +66,7 @@ export interface ChooseChoiceActionTraceStep extends BaseTraceStep {
|
||||
|
||||
export type ActionTraceStep =
|
||||
| BaseTraceStep
|
||||
| TriggerTraceStep
|
||||
| ConditionTraceStep
|
||||
| CallServiceActionTraceStep
|
||||
| ChooseActionTraceStep
|
||||
@@ -73,6 +79,9 @@ interface BaseTrace {
|
||||
last_step: string | null;
|
||||
run_id: string;
|
||||
state: "running" | "stopped" | "debugged";
|
||||
// True for traces recording that a trigger evaluated a relevant change but
|
||||
// did not fire. These are counted separately from actual runs.
|
||||
not_triggered?: boolean;
|
||||
timestamp: {
|
||||
start: string;
|
||||
finish: string | null;
|
||||
@@ -93,7 +102,10 @@ interface BaseTrace {
|
||||
| "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";
|
||||
| "cancelled"
|
||||
// No action was executed because a trigger evaluated a relevant change but
|
||||
// decided not to fire; the reason is in the trigger step of the trace
|
||||
| "not_triggered";
|
||||
}
|
||||
|
||||
interface BaseTraceExtended {
|
||||
@@ -103,7 +115,8 @@ interface BaseTraceExtended {
|
||||
|
||||
export interface AutomationTrace extends BaseTrace {
|
||||
domain: "automation";
|
||||
trigger: string;
|
||||
// `null` for not-triggered traces, which have no trigger description.
|
||||
trigger: string | null;
|
||||
}
|
||||
|
||||
export interface AutomationTraceExtended
|
||||
|
||||
@@ -320,6 +320,9 @@ export class HaAutomationTrace extends LitElement {
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
this.hass.loadBackendTranslation("triggers");
|
||||
this.hass.loadBackendTranslation("conditions");
|
||||
|
||||
if (!this.automationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5893,6 +5893,7 @@
|
||||
"template_errors": "Template errors:",
|
||||
"result": "Result:",
|
||||
"step_not_executed": "This step was not executed.",
|
||||
"not_triggered": "This trigger evaluated a relevant change but did not trigger the automation. The reason is shown in the result below.",
|
||||
"no_logbook_entries": "No activity found for this step.",
|
||||
"no_variables_changed": "No variables changed",
|
||||
"unable_to_find_config": "Unable to find config"
|
||||
@@ -5916,6 +5917,8 @@
|
||||
"stopped_failed_max_runs": "Stopped because maximum number of parallel runs reached at {time} (runtime: {executiontime} seconds)",
|
||||
"stopped_error": "Stopped because an error was encountered at {time} (runtime: {executiontime} seconds)",
|
||||
"stopped_unknown_reason": "Stopped because of unknown reason {reason} at {time} (runtime: {executiontime} seconds)",
|
||||
"evaluated_not_triggered": "Evaluated a change at {time}",
|
||||
"not_triggered": "Did not trigger at {time}. Select the trigger step details to see why.",
|
||||
"disabled": "(disabled)",
|
||||
"triggered_by": "{triggeredBy, select, \n alias {{alias} triggered}\n other {Triggered} \n} {triggeredPath, select, \n trigger {by the {trigger}}\n other {manually} \n} at {time}",
|
||||
"path_error": "Unable to extract path {path}. Download trace and report as bug.",
|
||||
@@ -5932,7 +5935,8 @@
|
||||
"stopped_failed_single": "Stopped because only a single execution is allowed",
|
||||
"stopped_failed_max_runs": "Stopped because maximum number of parallel runs reached",
|
||||
"stopped_error": "Stopped on error: {error} (runtime: {executiontime}s)",
|
||||
"stopped_unknown_reason": "Stopped for an unknown reason (runtime: {executiontime}s)"
|
||||
"stopped_unknown_reason": "Stopped for an unknown reason (runtime: {executiontime}s)",
|
||||
"not_triggered": "Did not trigger"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user