From 4b9487183b5237a6f5dd04cd337045d53ab3ee67 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 6 Jul 2021 10:46:51 +0200 Subject: [PATCH] Add tracing to scripts (#9486) --- .../trace/ha-trace-blueprint-config.ts} | 16 +- .../trace/ha-trace-config.ts} | 16 +- .../trace/ha-trace-logbook.ts} | 19 +- .../trace/ha-trace-path-details.ts} | 36 +- .../trace/ha-trace-timeline.ts} | 22 +- src/components/trace/hat-graph-node.ts | 8 +- src/components/trace/hat-graph.ts | 10 +- src/components/trace/hat-logbook-note.ts | 6 +- src/components/trace/hat-script-graph.ts | 142 +++-- .../trace/trace-tab-styles.ts} | 0 src/data/script.ts | 5 + src/data/trace.ts | 38 +- .../{trace => }/ha-automation-trace.ts | 62 ++- .../config/automation/ha-config-automation.ts | 2 +- src/panels/config/script/ha-config-script.ts | 6 +- src/panels/config/script/ha-script-editor.ts | 11 +- src/panels/config/script/ha-script-picker.ts | 16 + src/panels/config/script/ha-script-trace.ts | 502 ++++++++++++++++++ src/translations/en.json | 4 +- 19 files changed, 768 insertions(+), 153 deletions(-) rename src/{panels/config/automation/trace/ha-automation-trace-blueprint-config.ts => components/trace/ha-trace-blueprint-config.ts} (50%) rename src/{panels/config/automation/trace/ha-automation-trace-config.ts => components/trace/ha-trace-config.ts} (55%) rename src/{panels/config/automation/trace/ha-automation-trace-logbook.ts => components/trace/ha-trace-logbook.ts} (66%) rename src/{panels/config/automation/trace/ha-automation-trace-path-details.ts => components/trace/ha-trace-path-details.ts} (87%) rename src/{panels/config/automation/trace/ha-automation-trace-timeline.ts => components/trace/ha-trace-timeline.ts} (55%) rename src/{panels/config/automation/trace/styles.ts => components/trace/trace-tab-styles.ts} (100%) rename src/panels/config/automation/{trace => }/ha-automation-trace.ts (89%) create mode 100644 src/panels/config/script/ha-script-trace.ts diff --git a/src/panels/config/automation/trace/ha-automation-trace-blueprint-config.ts b/src/components/trace/ha-trace-blueprint-config.ts similarity index 50% rename from src/panels/config/automation/trace/ha-automation-trace-blueprint-config.ts rename to src/components/trace/ha-trace-blueprint-config.ts index bc3117114b..64e4bbc810 100644 --- a/src/panels/config/automation/trace/ha-automation-trace-blueprint-config.ts +++ b/src/components/trace/ha-trace-blueprint-config.ts @@ -1,16 +1,16 @@ import { dump } from "js-yaml"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; -import "../../../../components/ha-code-editor"; -import "../../../../components/ha-icon-button"; -import { AutomationTraceExtended } from "../../../../data/trace"; -import { HomeAssistant } from "../../../../types"; +import "../ha-code-editor"; +import "../ha-icon-button"; +import { TraceExtended } from "../../data/trace"; +import { HomeAssistant } from "../../types"; -@customElement("ha-automation-trace-blueprint-config") -export class HaAutomationTraceBlueprintConfig extends LitElement { +@customElement("ha-trace-blueprint-config") +export class HaTraceBlueprintConfig extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trace!: AutomationTraceExtended; + @property({ attribute: false }) public trace!: TraceExtended; protected render(): TemplateResult { return html` @@ -24,6 +24,6 @@ export class HaAutomationTraceBlueprintConfig extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-automation-trace-blueprint-config": HaAutomationTraceBlueprintConfig; + "ha-trace-blueprint-config": HaTraceBlueprintConfig; } } diff --git a/src/panels/config/automation/trace/ha-automation-trace-config.ts b/src/components/trace/ha-trace-config.ts similarity index 55% rename from src/panels/config/automation/trace/ha-automation-trace-config.ts rename to src/components/trace/ha-trace-config.ts index c97bb47f8b..237a051110 100644 --- a/src/panels/config/automation/trace/ha-automation-trace-config.ts +++ b/src/components/trace/ha-trace-config.ts @@ -1,16 +1,16 @@ import { dump } from "js-yaml"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; -import "../../../../components/ha-code-editor"; -import "../../../../components/ha-icon-button"; -import { AutomationTraceExtended } from "../../../../data/trace"; -import { HomeAssistant } from "../../../../types"; +import "../ha-code-editor"; +import "../ha-icon-button"; +import { TraceExtended } from "../../data/trace"; +import { HomeAssistant } from "../../types"; -@customElement("ha-automation-trace-config") -export class HaAutomationTraceConfig extends LitElement { +@customElement("ha-trace-config") +export class HaTraceConfig extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trace!: AutomationTraceExtended; + @property({ attribute: false }) public trace!: TraceExtended; protected render(): TemplateResult { return html` @@ -28,6 +28,6 @@ export class HaAutomationTraceConfig extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-automation-trace-config": HaAutomationTraceConfig; + "ha-trace-config": HaTraceConfig; } } diff --git a/src/panels/config/automation/trace/ha-automation-trace-logbook.ts b/src/components/trace/ha-trace-logbook.ts similarity index 66% rename from src/panels/config/automation/trace/ha-automation-trace-logbook.ts rename to src/components/trace/ha-trace-logbook.ts index 675d355662..2f4da4de66 100644 --- a/src/panels/config/automation/trace/ha-automation-trace-logbook.ts +++ b/src/components/trace/ha-trace-logbook.ts @@ -1,16 +1,19 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; -import "../../../../components/trace/hat-logbook-note"; -import type { LogbookEntry } from "../../../../data/logbook"; -import type { HomeAssistant } from "../../../../types"; -import "../../../logbook/ha-logbook"; +import { LogbookEntry } from "../../data/logbook"; +import { HomeAssistant } from "../../types"; +import "./hat-logbook-note"; +import "../../panels/logbook/ha-logbook"; +import { TraceExtended } from "../../data/trace"; -@customElement("ha-automation-trace-logbook") -export class HaAutomationTraceLogbook extends LitElement { +@customElement("ha-trace-logbook") +export class HaTraceLogbook extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean, reflect: true }) public narrow!: boolean; + @property({ attribute: false }) public trace!: TraceExtended; + @property({ attribute: false }) public logbookEntries!: LogbookEntry[]; protected render(): TemplateResult { @@ -22,7 +25,7 @@ export class HaAutomationTraceLogbook extends LitElement { .entries=${this.logbookEntries} .narrow=${this.narrow} > - + ` : html`
No Logbook entries found for this step. @@ -42,6 +45,6 @@ export class HaAutomationTraceLogbook extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-automation-trace-logbook": HaAutomationTraceLogbook; + "ha-trace-logbook": HaTraceLogbook; } } diff --git a/src/panels/config/automation/trace/ha-automation-trace-path-details.ts b/src/components/trace/ha-trace-path-details.ts similarity index 87% rename from src/panels/config/automation/trace/ha-automation-trace-path-details.ts rename to src/components/trace/ha-trace-path-details.ts index dc0412b149..45134c532a 100644 --- a/src/panels/config/automation/trace/ha-automation-trace-path-details.ts +++ b/src/components/trace/ha-trace-path-details.ts @@ -2,33 +2,33 @@ import { dump } from "js-yaml"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { formatDateTimeWithSeconds } from "../../../../common/datetime/format_date_time"; -import "../../../../components/ha-code-editor"; -import "../../../../components/ha-icon-button"; -import type { NodeInfo } from "../../../../components/trace/hat-graph"; -import "../../../../components/trace/hat-logbook-note"; -import { LogbookEntry } from "../../../../data/logbook"; +import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time"; +import "../ha-code-editor"; +import "../ha-icon-button"; +import type { NodeInfo } from "./hat-graph"; +import "./hat-logbook-note"; +import { LogbookEntry } from "../../data/logbook"; import { ActionTraceStep, - AutomationTraceExtended, ChooseActionTraceStep, getDataFromPath, -} from "../../../../data/trace"; -import { HomeAssistant } from "../../../../types"; -import "../../../logbook/ha-logbook"; -import { traceTabStyles } from "./styles"; + TraceExtended, +} from "../../data/trace"; +import "../../panels/logbook/ha-logbook"; +import { traceTabStyles } from "./trace-tab-styles"; +import { HomeAssistant } from "../../types"; -@customElement("ha-automation-trace-path-details") -export class HaAutomationTracePathDetails extends LitElement { +@customElement("ha-trace-path-details") +export class HaTracePathDetails extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean, reflect: true }) public narrow!: boolean; - @property() private selected!: NodeInfo; + @property({ attribute: false }) public trace!: TraceExtended; - @property() public trace!: AutomationTraceExtended; + @property({ attribute: false }) public logbookEntries!: LogbookEntry[]; - @property() public logbookEntries!: LogbookEntry[]; + @property({ attribute: false }) public selected!: NodeInfo; @property() renderedNodes: Record = {}; @@ -230,7 +230,7 @@ export class HaAutomationTracePathDetails extends LitElement { .entries=${entries} .narrow=${this.narrow} > - + ` : html`
No Logbook entries found for this step. @@ -267,6 +267,6 @@ export class HaAutomationTracePathDetails extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-automation-trace-path-details": HaAutomationTracePathDetails; + "ha-trace-path-details": HaTracePathDetails; } } diff --git a/src/panels/config/automation/trace/ha-automation-trace-timeline.ts b/src/components/trace/ha-trace-timeline.ts similarity index 55% rename from src/panels/config/automation/trace/ha-automation-trace-timeline.ts rename to src/components/trace/ha-trace-timeline.ts index d379dc6934..2680254f93 100644 --- a/src/panels/config/automation/trace/ha-automation-trace-timeline.ts +++ b/src/components/trace/ha-trace-timeline.ts @@ -1,17 +1,17 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; -import type { NodeInfo } from "../../../../components/trace/hat-graph"; -import "../../../../components/trace/hat-logbook-note"; -import "../../../../components/trace/hat-trace-timeline"; -import type { LogbookEntry } from "../../../../data/logbook"; -import type { AutomationTraceExtended } from "../../../../data/trace"; -import type { HomeAssistant } from "../../../../types"; +import type { NodeInfo } from "./hat-graph"; +import "./hat-logbook-note"; +import "./hat-trace-timeline"; +import type { LogbookEntry } from "../../data/logbook"; +import type { TraceExtended } from "../../data/trace"; +import type { HomeAssistant } from "../../types"; -@customElement("ha-automation-trace-timeline") -export class HaAutomationTraceTimeline extends LitElement { +@customElement("ha-trace-timeline") +export class HaTraceTimeline extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) public trace!: AutomationTraceExtended; + @property({ attribute: false }) public trace!: TraceExtended; @property({ attribute: false }) public logbookEntries!: LogbookEntry[]; @@ -27,7 +27,7 @@ export class HaAutomationTraceTimeline extends LitElement { allowPick > - + `; } @@ -45,6 +45,6 @@ export class HaAutomationTraceTimeline extends LitElement { declare global { interface HTMLElementTagNameMap { - "ha-automation-trace-timeline": HaAutomationTraceTimeline; + "ha-trace-timeline": HaTraceTimeline; } } diff --git a/src/components/trace/hat-graph-node.ts b/src/components/trace/hat-graph-node.ts index 5b0ba7ca64..bb418dc23e 100644 --- a/src/components/trace/hat-graph-node.ts +++ b/src/components/trace/hat-graph-node.ts @@ -8,7 +8,7 @@ export class HatGraphNode extends LitElement { @property({ reflect: true, type: Boolean }) disabled?: boolean; - @property({ reflect: true, type: Boolean }) graphstart?: boolean; + @property({ reflect: true, type: Boolean }) graphStart?: boolean; @property({ reflect: true, type: Boolean }) nofocus?: boolean; @@ -21,20 +21,20 @@ export class HatGraphNode extends LitElement { } render() { - const height = NODE_SIZE + (this.graphstart ? 2 : SPACING + 1); + const height = NODE_SIZE + (this.graphStart ? 2 : SPACING + 1); const width = SPACING + NODE_SIZE; return svg` ${ - this.graphstart + this.graphStart ? `` : svg` - ${this.branching + ${this.branching && branches.some((branch) => !branch.start) ? svg` -
+
key in node) || "other"; - const nodeEl = NODE_TYPES[type].bind(this)(node, path); + const nodeEl = NODE_TYPES[type].bind(this)(node, path, graphStart); this.renderedNodes[path] = { config: node, path }; if (this.trace && path in this.trace.trace) { this.trackedNodes[path] = this.renderedNodes[path]; @@ -423,35 +460,47 @@ class HatScriptGraph extends LitElement { const paths = Object.keys(this.trackedNodes); const manual_triggered = this.trace && "trigger" in this.trace.trace; let track_path = manual_triggered ? undefined : [0]; - const trigger_nodes = ensureArray(this.trace.config.trigger).map( - (trigger, i) => { - if (this.trace && `trigger/${i}` in this.trace.trace) { - track_path = [i]; - } - return this.render_trigger(trigger, i); - } - ); + const trigger_nodes = + "trigger" in this.trace.config + ? ensureArray(this.trace.config.trigger).map((trigger, i) => { + if (this.trace && `trigger/${i}` in this.trace.trace) { + track_path = [i]; + } + return this.render_trigger(trigger, i); + }) + : undefined; try { return html`
- - ${trigger_nodes} - - - ${ensureArray(this.trace.config.condition)?.map((condition, i) => - this.render_condition(condition!, i) - )} - - ${ensureArray(this.trace.config.action).map((action, i) => - this.render_node(action, `action/${i}`) - )} + ${trigger_nodes + ? html` + ${trigger_nodes} + ` + : ""} + ${"condition" in this.trace.config + ? html` + ${ensureArray( + this.trace.config.condition + )?.map((condition, i) => this.render_condition(condition!, i))} + ` + : ""} + ${"action" in this.trace.config + ? html`${ensureArray(this.trace.config.action).map((action, i) => + this.render_node(action, `action/${i}`) + )}` + : ""} + ${"sequence" in this.trace.config + ? html`${ensureArray(this.trace.config.sequence).map((action, i) => + this.render_node(action, `sequence/${i}`, i === 0) + )}` + : ""}
; + context: Context; + error?: string; +} + +export interface AutomationTrace extends BaseTrace { + domain: "automation"; trigger: string; } -export interface AutomationTraceExtended extends AutomationTrace { - trace: Record; - context: Context; +export interface AutomationTraceExtended + extends AutomationTrace, + BaseTraceExtended { config: ManualAutomationConfig; blueprint_inputs?: BlueprintAutomationConfig; - error?: string; } +export interface ScriptTrace extends BaseTrace { + domain: "script"; +} + +export interface ScriptTraceExtended extends ScriptTrace, BaseTraceExtended { + config: ScriptConfig; + blueprint_inputs?: BlueprintScriptConfig; +} + +export type TraceExtended = AutomationTraceExtended | ScriptTraceExtended; + interface TraceTypes { automation: { short: AutomationTrace; extended: AutomationTraceExtended; }; + script: { + short: ScriptTrace; + extended: ScriptTraceExtended; + }; } export const loadTrace = ( @@ -141,7 +165,7 @@ export const loadTraceContexts = ( }); export const getDataFromPath = ( - config: ManualAutomationConfig, + config: TraceExtended["config"], path: string ): any => { const parts = path.split("/").reverse(); diff --git a/src/panels/config/automation/trace/ha-automation-trace.ts b/src/panels/config/automation/ha-automation-trace.ts similarity index 89% rename from src/panels/config/automation/trace/ha-automation-trace.ts rename to src/panels/config/automation/ha-automation-trace.ts index fefffacb80..91ef868d31 100644 --- a/src/panels/config/automation/trace/ha-automation-trace.ts +++ b/src/panels/config/automation/ha-automation-trace.ts @@ -9,31 +9,28 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { repeat } from "lit/directives/repeat"; -import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; -import { formatDateTimeWithSeconds } from "../../../../common/datetime/format_date_time"; -import type { NodeInfo } from "../../../../components/trace/hat-graph"; -import "../../../../components/trace/hat-script-graph"; -import { AutomationEntity } from "../../../../data/automation"; -import { - getLogbookDataForContext, - LogbookEntry, -} from "../../../../data/logbook"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time"; +import type { NodeInfo } from "../../../components/trace/hat-graph"; +import "../../../components/trace/hat-script-graph"; +import { AutomationEntity } from "../../../data/automation"; +import { getLogbookDataForContext, LogbookEntry } from "../../../data/logbook"; import { AutomationTrace, AutomationTraceExtended, loadTrace, loadTraces, -} from "../../../../data/trace"; -import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box"; -import { haStyle } from "../../../../resources/styles"; -import { HomeAssistant, Route } from "../../../../types"; -import { configSections } from "../../ha-panel-config"; -import "./ha-automation-trace-blueprint-config"; -import "./ha-automation-trace-config"; -import "./ha-automation-trace-logbook"; -import "./ha-automation-trace-path-details"; -import "./ha-automation-trace-timeline"; -import { traceTabStyles } from "./styles"; +} from "../../../data/trace"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant, Route } from "../../../types"; +import { configSections } from "../ha-panel-config"; +import "../../../components/trace/ha-trace-blueprint-config"; +import "../../../components/trace/ha-trace-config"; +import "../../../components/trace/ha-trace-logbook"; +import "../../../components/trace/ha-trace-path-details"; +import "../../../components/trace/ha-trace-timeline"; +import { traceTabStyles } from "../../../components/trace/trace-tab-styles"; @customElement("ha-automation-trace") export class HaAutomationTrace extends LitElement { @@ -209,7 +206,7 @@ export class HaAutomationTrace extends LitElement { @click=${this._showTab} > Blueprint Config -
+ ` : ""}
@@ -219,46 +216,47 @@ export class HaAutomationTrace extends LitElement { ? "" : this._view === "details" ? html` - + .renderedNodes=${renderedNodes!} + > ` : this._view === "config" ? html` - + > ` : this._view === "logbook" ? html` - + > ` : this._view === "blueprint" ? html` - + > ` : html` - + > `}
diff --git a/src/panels/config/automation/ha-config-automation.ts b/src/panels/config/automation/ha-config-automation.ts index 8a61fabf57..a4fa5d38ab 100644 --- a/src/panels/config/automation/ha-config-automation.ts +++ b/src/panels/config/automation/ha-config-automation.ts @@ -51,7 +51,7 @@ class HaConfigAutomation extends HassRouterPage { }, trace: { tag: "ha-automation-trace", - load: () => import("./trace/ha-automation-trace"), + load: () => import("./ha-automation-trace"), }, }, }; diff --git a/src/panels/config/script/ha-config-script.ts b/src/panels/config/script/ha-config-script.ts index f1c11a2b84..4b8f9aefa8 100644 --- a/src/panels/config/script/ha-config-script.ts +++ b/src/panels/config/script/ha-config-script.ts @@ -42,6 +42,10 @@ class HaConfigScript extends HassRouterPage { edit: { tag: "ha-script-editor", }, + trace: { + tag: "ha-script-trace", + load: () => import("./ha-script-trace"), + }, }, }; @@ -81,7 +85,7 @@ class HaConfigScript extends HassRouterPage { if ( (!changedProps || changedProps.has("route")) && - this._currentPage === "edit" + this._currentPage !== "dashboard" ) { pageEl.creatingNew = undefined; const scriptEntityId = this.routeTail.path.substr(1); diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index dc7abce376..2b22bd69ef 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -297,7 +297,16 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
- + + + ${this.hass.localize( + "ui.panel.config.script.editor.show_trace" + )} + + `, }; + columns.trace = { + title: "", + type: "icon-button", + template: (_info, script: any) => html` + + + + + + `, + }; columns.edit = { title: "", type: "icon-button", diff --git a/src/panels/config/script/ha-script-trace.ts b/src/panels/config/script/ha-script-trace.ts new file mode 100644 index 0000000000..786dfb9d9e --- /dev/null +++ b/src/panels/config/script/ha-script-trace.ts @@ -0,0 +1,502 @@ +import { + mdiDownload, + mdiPencil, + mdiRayEndArrow, + mdiRayStartArrow, + mdiRefresh, +} from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { repeat } from "lit/directives/repeat"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time"; +import type { NodeInfo } from "../../../components/trace/hat-graph"; +import "../../../components/trace/hat-script-graph"; +import { getLogbookDataForContext, LogbookEntry } from "../../../data/logbook"; +import { ScriptEntity } from "../../../data/script"; +import { + loadTrace, + loadTraces, + ScriptTrace, + ScriptTraceExtended, +} from "../../../data/trace"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant, Route } from "../../../types"; +import { traceTabStyles } from "../../../components/trace/trace-tab-styles"; +import { configSections } from "../ha-panel-config"; +import "../../../components/trace/ha-trace-blueprint-config"; +import "../../../components/trace/ha-trace-config"; +import "../../../components/trace/ha-trace-logbook"; +import "../../../components/trace/ha-trace-path-details"; +import "../../../components/trace/ha-trace-timeline"; + +@customElement("ha-script-trace") +export class HaScriptTrace extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public scriptEntityId!: string; + + @property({ attribute: false }) public scripts!: ScriptEntity[]; + + @property({ type: Boolean }) public isWide?: boolean; + + @property({ type: Boolean, reflect: true }) public narrow!: boolean; + + @property({ attribute: false }) public route!: Route; + + @state() private _traces?: ScriptTrace[]; + + @state() private _runId?: string; + + @state() private _selected?: NodeInfo; + + @state() private _trace?: ScriptTraceExtended; + + @state() private _logbookEntries?: LogbookEntry[]; + + @state() private _view: + | "details" + | "config" + | "timeline" + | "logbook" + | "blueprint" = "details"; + + protected render(): TemplateResult { + const stateObj = this.scriptEntityId + ? this.hass.states[this.scriptEntityId] + : undefined; + + const graph = this.shadowRoot!.querySelector("hat-script-graph"); + const trackedNodes = graph?.trackedNodes; + const renderedNodes = graph?.renderedNodes; + + const title = stateObj?.attributes.friendly_name || this.scriptEntityId; + + let devButtons: TemplateResult | string = ""; + if (__DEV__) { + devButtons = html`
+ + +
`; + } + + const actionButtons = html` + this._loadTraces()}> + + + + + + `; + + return html` + ${devButtons} + + ${this.narrow + ? html` ${title} +
${actionButtons}
` + : ""} +
+ ${!this.narrow + ? html`
+ ${title} + + + + + +
` + : ""} + ${this._traces && this._traces.length > 0 + ? html` +
+ + + + + + + +
+ ` + : ""} + ${!this.narrow ? html`
${actionButtons}
` : ""} +
+ + ${this._traces === undefined + ? html`
Loading…
` + : this._traces.length === 0 + ? html`
No traces found
` + : this._trace === undefined + ? "" + : html` +
+
+ +
+ +
+
+ ${[ + ["details", "Step Details"], + ["timeline", "Trace Timeline"], + ["logbook", "Related logbook entries"], + ["config", "Script Config"], + ].map( + ([view, label]) => html` + + ` + )} + ${this._trace.blueprint_inputs + ? html` + + ` + : ""} +
+ ${this._selected === undefined || + this._logbookEntries === undefined || + trackedNodes === undefined + ? "" + : this._view === "details" + ? html` + + ` + : this._view === "config" + ? html` + + ` + : this._view === "logbook" + ? html` + + ` + : this._view === "blueprint" + ? html` + + ` + : html` + + `} +
+
+ `} +
+ `; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + + if (!this.scriptEntityId) { + return; + } + + const params = new URLSearchParams(location.search); + this._loadTraces(params.get("run_id") || undefined); + } + + protected updated(changedProps) { + super.updated(changedProps); + + // Only reset if automationId has changed and we had one before. + if (changedProps.get("scriptEntityId")) { + this._traces = undefined; + this._runId = undefined; + this._trace = undefined; + this._logbookEntries = undefined; + if (this.scriptEntityId) { + this._loadTraces(); + } + } + + if (changedProps.has("_runId") && this._runId) { + this._trace = undefined; + this._logbookEntries = undefined; + this.shadowRoot!.querySelector("select")!.value = this._runId; + this._loadTrace(); + } + } + + private _pickOlderTrace() { + const curIndex = this._traces!.findIndex((tr) => tr.run_id === this._runId); + this._runId = this._traces![curIndex + 1].run_id; + this._selected = undefined; + } + + private _pickNewerTrace() { + const curIndex = this._traces!.findIndex((tr) => tr.run_id === this._runId); + this._runId = this._traces![curIndex - 1].run_id; + this._selected = undefined; + } + + private _pickTrace(ev) { + this._runId = ev.target.value; + this._selected = undefined; + } + + private _pickNode(ev) { + this._selected = ev.detail; + } + + private async _loadTraces(runId?: string) { + this._traces = await loadTraces( + this.hass, + "script", + this.scriptEntityId.split(".")[1] + ); + // Newest will be on top. + this._traces.reverse(); + + if (runId) { + this._runId = runId; + } + + // Check if current run ID still exists + if ( + this._runId && + !this._traces.some((trace) => trace.run_id === this._runId) + ) { + this._runId = undefined; + this._selected = undefined; + + // If we came here from a trace passed into the url, clear it. + if (runId) { + const params = new URLSearchParams(location.search); + params.delete("run_id"); + history.replaceState( + null, + "", + `${location.pathname}?${params.toString()}` + ); + } + + await showAlertDialog(this, { + text: "Chosen trace is no longer available", + }); + } + + // 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 trace = await loadTrace( + this.hass, + "script", + this.scriptEntityId.split(".")[1], + this._runId! + ); + this._logbookEntries = isComponentLoaded(this.hass, "logbook") + ? await getLogbookDataForContext( + this.hass, + trace.timestamp.start, + trace.context.id + ) + : []; + + this._trace = trace; + } + + private _downloadTrace() { + const aEl = document.createElement("a"); + aEl.download = `trace ${this.scriptEntityId} ${ + this._trace!.timestamp.start + }.json`; + aEl.href = `data:application/json;charset=utf-8,${encodeURI( + JSON.stringify( + { + trace: this._trace, + logbookEntries: this._logbookEntries, + }, + undefined, + 2 + ) + )}`; + aEl.click(); + } + + private _importTrace() { + const traceText = prompt("Enter downloaded trace"); + if (!traceText) { + return; + } + localStorage.devTrace = traceText; + this._loadLocalTrace(traceText); + } + + private _loadLocalStorageTrace() { + if (localStorage.devTrace) { + this._loadLocalTrace(localStorage.devTrace); + } + } + + private _loadLocalTrace(traceText: string) { + const traceInfo = JSON.parse(traceText); + this._trace = traceInfo.trace; + this._logbookEntries = traceInfo.logbookEntries; + } + + private _showTab(ev) { + this._view = (ev.target as any).view; + } + + private _timelinePathPicked(ev) { + const path = ev.detail.value; + const nodes = this.shadowRoot!.querySelector("hat-script-graph")! + .trackedNodes; + if (nodes[path]) { + this._selected = nodes[path]; + } + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + traceTabStyles, + css` + .toolbar { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 20px; + height: var(--header-height); + padding: 0 16px; + background-color: var(--primary-background-color); + font-weight: 400; + color: var(--app-header-text-color, white); + border-bottom: var(--app-header-border-bottom, none); + box-sizing: border-box; + } + + .toolbar > * { + display: flex; + align-items: center; + } + + :host([narrow]) .toolbar > * { + display: contents; + } + + .main { + height: calc(100% - 56px); + display: flex; + background-color: var(--card-background-color); + } + + :host([narrow]) .main { + height: auto; + flex-direction: column; + } + + .container { + padding: 16px; + } + + .graph { + border-right: 1px solid var(--divider-color); + overflow-x: auto; + max-width: 50%; + } + :host([narrow]) .graph { + max-width: 100%; + } + + .info { + flex: 1; + background-color: var(--card-background-color); + } + + .linkButton { + color: var(--primary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-script-trace": HaScriptTrace; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index c556a89876..d7a4efd15d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -303,7 +303,7 @@ "entries_not_found": "No logbook entries found.", "by": "by", "by_service": "by service", - "show_trace": "Show trace", + "show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]", "retrieval_error": "Error during logbook entry retrieval", "messages": { "was_away": "was detected away", @@ -1640,6 +1640,7 @@ "show_info": "Show info about script", "run_script": "Run script", "edit_script": "Edit script", + "dev_script": "Debug script", "headers": { "name": "Name" }, @@ -1653,6 +1654,7 @@ "id_already_exists_save_error": "You can't save this script because the ID is not unique, pick another ID or leave it blank to automatically generate one.", "id_already_exists": "This ID already exists", "introduction": "Use scripts to run a sequence of actions.", + "show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]", "header": "Script: {name}", "default_name": "New Script", "modes": {