mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Refactor trace rendering (#8693)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
4d48fc3d85
commit
5156c67226
@ -9,8 +9,8 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||||
import {
|
import {
|
||||||
ActionTrace,
|
|
||||||
AutomationTraceExtended,
|
AutomationTraceExtended,
|
||||||
|
ChooseActionTrace,
|
||||||
getDataFromPath,
|
getDataFromPath,
|
||||||
} from "../../data/automation_debug";
|
} from "../../data/automation_debug";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
@ -24,16 +24,300 @@ import {
|
|||||||
mdiStopCircleOutline,
|
mdiStopCircleOutline,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { LogbookEntry } from "../../data/logbook";
|
import { LogbookEntry } from "../../data/logbook";
|
||||||
import { describeAction } from "../../data/script";
|
import { getActionType } from "../../data/script";
|
||||||
import relativeTime from "../../common/datetime/relative_time";
|
import relativeTime from "../../common/datetime/relative_time";
|
||||||
|
|
||||||
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
|
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
|
||||||
|
|
||||||
const pathToName = (path: string) => path.split("/").join(" ");
|
const pathToName = (path: string) => path.split("/").join(" ");
|
||||||
|
|
||||||
|
/* eslint max-classes-per-file: "off" */
|
||||||
|
|
||||||
// Report time entry when more than this time has passed
|
// Report time entry when more than this time has passed
|
||||||
const SIGNIFICANT_TIME_CHANGE = 5000; // 5 seconds
|
const SIGNIFICANT_TIME_CHANGE = 5000; // 5 seconds
|
||||||
|
|
||||||
|
const isSignificantTimeChange = (a: Date, b: Date) =>
|
||||||
|
Math.abs(b.getTime() - a.getTime()) > SIGNIFICANT_TIME_CHANGE;
|
||||||
|
|
||||||
|
class RenderedTimeTracker {
|
||||||
|
private lastReportedTime: Date;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private hass: HomeAssistant,
|
||||||
|
private entries: TemplateResult[],
|
||||||
|
trace: AutomationTraceExtended
|
||||||
|
) {
|
||||||
|
this.lastReportedTime = new Date(trace.timestamp.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastReportedTime(date: Date) {
|
||||||
|
this.lastReportedTime = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTime(from: Date, to: Date): void {
|
||||||
|
this.entries.push(html`
|
||||||
|
<ha-timeline label>
|
||||||
|
${relativeTime(from, this.hass.localize, {
|
||||||
|
compareTime: to,
|
||||||
|
includeTense: false,
|
||||||
|
})}
|
||||||
|
later
|
||||||
|
</ha-timeline>
|
||||||
|
`);
|
||||||
|
this.lastReportedTime = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeRenderTime(timestamp: Date): boolean {
|
||||||
|
if (!isSignificantTimeChange(timestamp, this.lastReportedTime)) {
|
||||||
|
this.lastReportedTime = timestamp;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderTime(this.lastReportedTime, timestamp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogbookRenderer {
|
||||||
|
private curIndex: number;
|
||||||
|
|
||||||
|
private pendingItems: Array<[Date, LogbookEntry]> = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private entries: TemplateResult[],
|
||||||
|
private timeTracker: RenderedTimeTracker,
|
||||||
|
private logbookEntries: LogbookEntry[]
|
||||||
|
) {
|
||||||
|
// Skip the "automation got triggered item"
|
||||||
|
this.curIndex =
|
||||||
|
logbookEntries.length > 0 && logbookEntries[0].domain === "automation"
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get curItem() {
|
||||||
|
return this.logbookEntries[this.curIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasNext() {
|
||||||
|
return this.curIndex !== this.logbookEntries.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeRenderItem() {
|
||||||
|
const logbookEntry = this.curItem;
|
||||||
|
this.curIndex++;
|
||||||
|
const entryDate = new Date(logbookEntry.when);
|
||||||
|
|
||||||
|
if (this.pendingItems.length === 0) {
|
||||||
|
this.pendingItems.push([entryDate, logbookEntry]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousEntryDate = this.pendingItems[
|
||||||
|
this.pendingItems.length - 1
|
||||||
|
][0];
|
||||||
|
|
||||||
|
// If logbook entry is too long after the last one,
|
||||||
|
// add a time passed label
|
||||||
|
if (isSignificantTimeChange(previousEntryDate, entryDate)) {
|
||||||
|
this._renderLogbookEntries();
|
||||||
|
this.timeTracker.renderTime(previousEntryDate, entryDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pendingItems.push([entryDate, logbookEntry]);
|
||||||
|
}
|
||||||
|
|
||||||
|
flush() {
|
||||||
|
if (this.pendingItems.length > 0) {
|
||||||
|
this._renderLogbookEntries();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderLogbookEntries() {
|
||||||
|
this.timeTracker.maybeRenderTime(this.pendingItems[0][0]);
|
||||||
|
|
||||||
|
const parts: TemplateResult[] = [];
|
||||||
|
|
||||||
|
let i;
|
||||||
|
|
||||||
|
for (
|
||||||
|
i = 0;
|
||||||
|
i < Math.min(this.pendingItems.length, LOGBOOK_ENTRIES_BEFORE_FOLD);
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
parts.push(this._renderLogbookEntryHelper(this.pendingItems[i][1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let moreItems: TemplateResult[] | undefined;
|
||||||
|
|
||||||
|
// If we didn't render all items, push rest into `moreItems`
|
||||||
|
if (i < this.pendingItems.length) {
|
||||||
|
moreItems = [];
|
||||||
|
for (; i < this.pendingItems.length; i++) {
|
||||||
|
moreItems.push(this._renderLogbookEntryHelper(this.pendingItems[i][1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.entries.push(html`
|
||||||
|
<ha-timeline .icon=${mdiCircleOutline} .moreItems=${moreItems}>
|
||||||
|
${parts}
|
||||||
|
</ha-timeline>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Clear rendered items.
|
||||||
|
this.timeTracker.setLastReportedTime(
|
||||||
|
this.pendingItems[this.pendingItems.length - 1][0]
|
||||||
|
);
|
||||||
|
this.pendingItems = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderLogbookEntryHelper(entry: LogbookEntry) {
|
||||||
|
return html`${entry.name} (${entry.entity_id}) turned ${entry.state}<br />`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActionRenderer {
|
||||||
|
private curIndex = 0;
|
||||||
|
|
||||||
|
private keys: string[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private entries: TemplateResult[],
|
||||||
|
private trace: AutomationTraceExtended,
|
||||||
|
private timeTracker: RenderedTimeTracker
|
||||||
|
) {
|
||||||
|
this.keys = Object.keys(trace.action_trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
get curItem() {
|
||||||
|
return this._getItem(this.curIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasNext() {
|
||||||
|
return this.curIndex !== this.keys.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem() {
|
||||||
|
this.curIndex = this._renderItem(this.curIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getItem(index: number) {
|
||||||
|
return this.trace.action_trace[this.keys[index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderItem(
|
||||||
|
index: number,
|
||||||
|
actionType?: ReturnType<typeof getActionType>
|
||||||
|
): number {
|
||||||
|
const value = this._getItem(index);
|
||||||
|
const timestamp = new Date(value[0].timestamp);
|
||||||
|
|
||||||
|
this.timeTracker.maybeRenderTime(timestamp);
|
||||||
|
|
||||||
|
const path = value[0].path;
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = getDataFromPath(this.trace.config, path);
|
||||||
|
} catch (err) {
|
||||||
|
this.entries.push(
|
||||||
|
html`Unable to extract path ${path}. Download trace and report as bug`
|
||||||
|
);
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTopLevel = path.split("/").length === 2;
|
||||||
|
|
||||||
|
if (!isTopLevel && !actionType) {
|
||||||
|
this._renderEntry(path.replace(/\//g, " "));
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!actionType) {
|
||||||
|
actionType = getActionType(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionType === "choose") {
|
||||||
|
return this._handleChoose(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._renderEntry(data.alias || actionType);
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleChoose(index: number): number {
|
||||||
|
// startLevel: choose root config
|
||||||
|
|
||||||
|
// +1: 'default
|
||||||
|
// +2: executed sequence
|
||||||
|
|
||||||
|
// +1: 'choose'
|
||||||
|
// +2: current choice
|
||||||
|
|
||||||
|
// +3: 'conditions'
|
||||||
|
// +4: evaluated condition
|
||||||
|
|
||||||
|
// +3: 'sequence'
|
||||||
|
// +4: executed sequence
|
||||||
|
|
||||||
|
const startLevel = this.keys[index].split("/").length - 1;
|
||||||
|
|
||||||
|
const chooseTrace = this._getItem(index)[0] as ChooseActionTrace;
|
||||||
|
const defaultExecuted = chooseTrace.result.choice === "default";
|
||||||
|
|
||||||
|
if (defaultExecuted) {
|
||||||
|
this._renderEntry(`Choose: Default action executed`);
|
||||||
|
} else {
|
||||||
|
this._renderEntry(`Choose: Choice ${chooseTrace.result.choice} executed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let i;
|
||||||
|
|
||||||
|
// Skip over conditions
|
||||||
|
for (i = index + 1; i < this.keys.length; i++) {
|
||||||
|
const parts = this.keys[i].split("/");
|
||||||
|
|
||||||
|
// We're done if no more sequence in current level
|
||||||
|
if (parts.length <= startLevel) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're going to skip all conditions
|
||||||
|
if (parts[startLevel + 3] === "sequence") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render choice
|
||||||
|
for (; i < this.keys.length; i++) {
|
||||||
|
const path = this.keys[i];
|
||||||
|
const parts = path.split("/");
|
||||||
|
|
||||||
|
// We're done if no more sequence in current level
|
||||||
|
if (parts.length <= startLevel) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We know it's an action sequence, so force the type like that
|
||||||
|
// for rendering.
|
||||||
|
this._renderItem(i, getActionType(this._getDataFromPath(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderEntry(description: string) {
|
||||||
|
this.entries.push(html`
|
||||||
|
<ha-timeline .icon=${mdiRecordCircleOutline}>
|
||||||
|
${description}
|
||||||
|
</ha-timeline>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDataFromPath(path: string) {
|
||||||
|
return getDataFromPath(this.trace.config, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("hat-trace")
|
@customElement("hat-trace")
|
||||||
export class HaAutomationTracer extends LitElement {
|
export class HaAutomationTracer extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -77,84 +361,44 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.trace.action_trace && this.logbookEntries) {
|
if (this.trace.action_trace && this.logbookEntries) {
|
||||||
const actionTraces = Object.values(this.trace.action_trace);
|
const timeTracker = new RenderedTimeTracker(
|
||||||
|
this.hass,
|
||||||
let logbookIndex = 0;
|
entries,
|
||||||
let actionTraceIndex = 0;
|
this.trace
|
||||||
let lastReportedTime = new Date(this.trace.timestamp.start);
|
);
|
||||||
|
const logbookRenderer = new LogbookRenderer(
|
||||||
const maybeRenderTime = (nextItemTimestamp: Date) => {
|
entries,
|
||||||
if (
|
timeTracker,
|
||||||
nextItemTimestamp.getTime() - lastReportedTime.getTime() <
|
this.logbookEntries
|
||||||
SIGNIFICANT_TIME_CHANGE
|
);
|
||||||
) {
|
const actionRenderer = new ActionRenderer(
|
||||||
return;
|
entries,
|
||||||
}
|
this.trace,
|
||||||
|
timeTracker
|
||||||
entries.push(html`
|
);
|
||||||
<ha-timeline label>
|
|
||||||
${relativeTime(lastReportedTime, this.hass.localize, {
|
|
||||||
compareTime: nextItemTimestamp,
|
|
||||||
includeTense: false,
|
|
||||||
})}
|
|
||||||
later
|
|
||||||
</ha-timeline>
|
|
||||||
`);
|
|
||||||
lastReportedTime = nextItemTimestamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
let groupedLogbookItems: LogbookEntry[] = [];
|
|
||||||
|
|
||||||
while (
|
|
||||||
logbookIndex < this.logbookEntries.length &&
|
|
||||||
actionTraceIndex < actionTraces.length
|
|
||||||
) {
|
|
||||||
// Find next item.
|
|
||||||
|
|
||||||
// Skip the "automation got triggered item"
|
|
||||||
if (
|
|
||||||
logbookIndex === 0 &&
|
|
||||||
this.logbookEntries[0].domain === "automation"
|
|
||||||
) {
|
|
||||||
logbookIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while (logbookRenderer.hasNext && actionRenderer.hasNext) {
|
||||||
// Find next item time-wise.
|
// Find next item time-wise.
|
||||||
const logbookItem = this.logbookEntries[logbookIndex];
|
const logbookItem = logbookRenderer.curItem;
|
||||||
const actionTrace = actionTraces[actionTraceIndex];
|
const actionTrace = actionRenderer.curItem;
|
||||||
const actionTimestamp = new Date(actionTrace[0].timestamp);
|
const actionTimestamp = new Date(actionTrace[0].timestamp);
|
||||||
|
|
||||||
if (new Date(logbookItem.when) > actionTimestamp) {
|
if (new Date(logbookItem.when) > actionTimestamp) {
|
||||||
actionTraceIndex++;
|
logbookRenderer.flush();
|
||||||
if (groupedLogbookItems.length > 0) {
|
actionRenderer.renderItem();
|
||||||
maybeRenderTime(new Date(groupedLogbookItems[0].when));
|
|
||||||
entries.push(this._renderLogbookEntries(groupedLogbookItems));
|
|
||||||
groupedLogbookItems = [];
|
|
||||||
}
|
|
||||||
maybeRenderTime(actionTimestamp);
|
|
||||||
entries.push(this._renderActionTrace(actionTrace));
|
|
||||||
} else {
|
} else {
|
||||||
logbookIndex++;
|
logbookRenderer.maybeRenderItem();
|
||||||
groupedLogbookItems.push(logbookItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (logbookIndex < this.logbookEntries.length) {
|
while (logbookRenderer.hasNext) {
|
||||||
groupedLogbookItems.push(this.logbookEntries[logbookIndex]);
|
logbookRenderer.maybeRenderItem();
|
||||||
logbookIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupedLogbookItems.length > 0) {
|
logbookRenderer.flush();
|
||||||
maybeRenderTime(new Date(groupedLogbookItems[0].when));
|
|
||||||
entries.push(this._renderLogbookEntries(groupedLogbookItems));
|
|
||||||
}
|
|
||||||
|
|
||||||
while (actionTraceIndex < actionTraces.length) {
|
while (actionRenderer.hasNext) {
|
||||||
const trace = actionTraces[actionTraceIndex];
|
actionRenderer.renderItem();
|
||||||
maybeRenderTime(new Date(trace[0].timestamp));
|
|
||||||
entries.push(this._renderActionTrace(trace));
|
|
||||||
actionTraceIndex++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,62 +432,6 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
return html`${entries}`;
|
return html`${entries}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderLogbookEntryHelper(entry: LogbookEntry) {
|
|
||||||
return html`${entry.name} (${entry.entity_id}) turned ${entry.state}<br />`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderLogbookEntries(entries: LogbookEntry[]) {
|
|
||||||
const parts: TemplateResult[] = [];
|
|
||||||
|
|
||||||
let i;
|
|
||||||
|
|
||||||
for (
|
|
||||||
i = 0;
|
|
||||||
i < Math.min(entries.length, LOGBOOK_ENTRIES_BEFORE_FOLD);
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
parts.push(this._renderLogbookEntryHelper(entries[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
let moreItems: TemplateResult[] | undefined;
|
|
||||||
|
|
||||||
if (i < entries.length) {
|
|
||||||
moreItems = [];
|
|
||||||
for (i = 0; i < entries.length; i++) {
|
|
||||||
moreItems.push(this._renderLogbookEntryHelper(entries[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-timeline .icon=${mdiCircleOutline} .moreItems=${moreItems}>
|
|
||||||
${parts}
|
|
||||||
</ha-timeline>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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}>
|
|
||||||
${description}
|
|
||||||
</ha-timeline>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
|
@ -27,7 +27,7 @@ export interface CallServiceActionTrace extends BaseTrace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ChooseActionTrace extends BaseTrace {
|
export interface ChooseActionTrace extends BaseTrace {
|
||||||
result: { choice: number };
|
result: { choice: number | "default" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChooseChoiceActionTrace extends BaseTrace {
|
export interface ChooseChoiceActionTrace extends BaseTrace {
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { Condition, Trigger } from "./automation";
|
import { Condition, Trigger } from "./automation";
|
||||||
|
|
||||||
@ -165,40 +164,40 @@ export const getScriptEditorInitData = () => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const describeAction = (action: Action, _localize: LocalizeFunc) => {
|
export const getActionType = (action: Action) => {
|
||||||
// Check based on config_validation.py#determine_script_action
|
// Check based on config_validation.py#determine_script_action
|
||||||
if ("delay" in action) {
|
if ("delay" in action) {
|
||||||
return "Delay";
|
return "delay";
|
||||||
}
|
}
|
||||||
if ("wait_template" in action) {
|
if ("wait_template" in action) {
|
||||||
return "Wait";
|
return "wait_template";
|
||||||
}
|
}
|
||||||
if ("condition" in action) {
|
if ("condition" in action) {
|
||||||
return "Check condition";
|
return "check_condition";
|
||||||
}
|
}
|
||||||
if ("event" in action) {
|
if ("event" in action) {
|
||||||
return "Fire event";
|
return "fire_event";
|
||||||
}
|
}
|
||||||
if ("device_id" in action) {
|
if ("device_id" in action) {
|
||||||
return "Run Device Action";
|
return "device_action";
|
||||||
}
|
}
|
||||||
if ("scene" in action) {
|
if ("scene" in action) {
|
||||||
return "Activate a scene";
|
return "activate_scene";
|
||||||
}
|
}
|
||||||
if ("repeat" in action) {
|
if ("repeat" in action) {
|
||||||
return "Repeat an action multiple times";
|
return "repeat";
|
||||||
}
|
}
|
||||||
if ("choose" in action) {
|
if ("choose" in action) {
|
||||||
return "Choose an action";
|
return "choose";
|
||||||
}
|
}
|
||||||
if ("wait_for_trigger" in action) {
|
if ("wait_for_trigger" in action) {
|
||||||
return "Wait for a trigger";
|
return "wait_for_trigger";
|
||||||
}
|
}
|
||||||
if ("variables" in action) {
|
if ("variables" in action) {
|
||||||
return "Define variables";
|
return "variables";
|
||||||
}
|
}
|
||||||
if ("service" in action) {
|
if ("service" in action) {
|
||||||
return "Call service";
|
return "service";
|
||||||
}
|
}
|
||||||
return "Unknown action";
|
return "unknown";
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user