Migrate base automation config to plurals (#22053)

* Migrate base automation config to plurals

* revert

* Update hat-script-graph.ts

* Make traces work with both new and old config

* Adjust validateConfig
This commit is contained in:
Bram Kragten 2024-09-24 20:03:53 +02:00 committed by GitHub
parent 1bbf45d35e
commit cbce6f633f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 99 additions and 45 deletions

View File

@ -217,20 +217,20 @@ export const basicTrace: DemoTrace = {
id: "1615419646544", id: "1615419646544",
alias: "Ensure Party mode", alias: "Ensure Party mode",
description: "", description: "",
trigger: [ triggers: [
{ {
platform: "state", platform: "state",
entity_id: "input_boolean.toggle_1", entity_id: "input_boolean.toggle_1",
}, },
], ],
condition: [ conditions: [
{ {
condition: "template", condition: "template",
alias: "Test if Paulus is home", alias: "Test if Paulus is home",
value_template: "{{ true }}", value_template: "{{ true }}",
}, },
], ],
action: [ actions: [
{ {
action: "input_boolean.toggle", action: "input_boolean.toggle",
target: { target: {

View File

@ -31,8 +31,8 @@ export const mockDemoTrace = (
], ],
}, },
config: { config: {
trigger: [], triggers: [],
action: [], actions: [],
}, },
context: { context: {
id: "abcd", id: "abcd",

View File

@ -133,7 +133,7 @@ export const motionLightTrace: DemoTrace = {
config: { config: {
mode: "restart", mode: "restart",
max_exceeded: "silent", max_exceeded: "silent",
trigger: [ triggers: [
{ {
platform: "state", platform: "state",
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use", entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
@ -141,7 +141,7 @@ export const motionLightTrace: DemoTrace = {
to: "on", to: "on",
}, },
], ],
action: [ actions: [
{ {
action: "light.turn_on", action: "light.turn_on",
target: { target: {

View File

@ -121,7 +121,7 @@ const ACTIONS = [
]; ];
const initialAction: Action = { const initialAction: Action = {
service: "light.turn_on", action: "light.turn_on",
target: { target: {
entity_id: "light.kitchen", entity_id: "light.kitchen",
}, },

View File

@ -569,10 +569,15 @@ export class HatScriptGraph extends LitElement {
} }
protected render() { protected render() {
const triggerKey = "triggers" in this.trace.config ? "triggers" : "trigger";
const conditionKey =
"conditions" in this.trace.config ? "conditions" : "condition";
const actionKey = "actions" in this.trace.config ? "actions" : "action";
const paths = Object.keys(this.trackedNodes); const paths = Object.keys(this.trackedNodes);
const trigger_nodes = const trigger_nodes =
"trigger" in this.trace.config triggerKey in this.trace.config
? flattenTriggers(ensureArray(this.trace.config.trigger)).map( ? flattenTriggers(ensureArray(this.trace.config[triggerKey])).map(
(trigger, i) => this.render_trigger(trigger, i) (trigger, i) => this.render_trigger(trigger, i)
) )
: undefined; : undefined;
@ -584,14 +589,14 @@ export class HatScriptGraph extends LitElement {
${trigger_nodes} ${trigger_nodes}
</hat-graph-branch>` </hat-graph-branch>`
: ""} : ""}
${"condition" in this.trace.config ${conditionKey in this.trace.config
? html`${ensureArray(this.trace.config.condition)?.map( ? html`${ensureArray(this.trace.config[conditionKey])?.map(
(condition, i) => this.render_condition(condition, i) (condition, i) => this.render_condition(condition, i)
)}` )}`
: ""} : ""}
${"action" in this.trace.config ${actionKey in this.trace.config
? html`${ensureArray(this.trace.config.action).map((action, i) => ? html`${ensureArray(this.trace.config[actionKey]).map(
this.render_action_node(action, `action/${i}`) (action, i) => this.render_action_node(action, `action/${i}`)
)}` )}`
: ""} : ""}
${"sequence" in this.trace.config ${"sequence" in this.trace.config

View File

@ -27,8 +27,14 @@ export interface ManualAutomationConfig {
id?: string; id?: string;
alias?: string; alias?: string;
description?: string; description?: string;
trigger: Trigger | Trigger[]; triggers: Trigger | Trigger[];
/** @deprecated Use `triggers` instead */
trigger?: Trigger | Trigger[];
conditions?: Condition | Condition[];
/** @deprecated Use `conditions` instead */
condition?: Condition | Condition[]; condition?: Condition | Condition[];
actions: Action | Action[];
/** @deprecated Use `actions` instead */
action?: Action | Action[]; action?: Action | Action[];
mode?: (typeof MODES)[number]; mode?: (typeof MODES)[number];
max?: number; max?: number;
@ -362,22 +368,50 @@ export const normalizeAutomationConfig = <
>( >(
config: T config: T
): T => { ): T => {
config = migrateAutomationConfig(config);
// Normalize data: ensure triggers, actions and conditions are lists // Normalize data: ensure triggers, actions and conditions are lists
// Happens when people copy paste their automations into the config // Happens when people copy paste their automations into the config
for (const key of ["trigger", "condition", "action"]) { for (const key of ["triggers", "conditions", "actions"]) {
const value = config[key]; const value = config[key];
if (value && !Array.isArray(value)) { if (value && !Array.isArray(value)) {
config[key] = [value]; config[key] = [value];
} }
} }
if (config.action) { if (config.actions) {
config.action = migrateAutomationAction(config.action); config.actions = migrateAutomationAction(config.actions);
} }
return config; return config;
}; };
export const migrateAutomationConfig = <
T extends Partial<AutomationConfig> | AutomationConfig,
>(
config: T
) => {
if ("trigger" in config) {
if (!("triggers" in config)) {
config.triggers = config.trigger;
}
delete config.trigger;
}
if ("condition" in config) {
if (!("conditions" in config)) {
config.conditions = config.condition;
}
delete config.condition;
}
if ("action" in config) {
if (!("actions" in config)) {
config.actions = config.action;
}
delete config.action;
}
return config;
};
export const flattenTriggers = ( export const flattenTriggers = (
triggers: undefined | (Trigger | TriggerList)[] triggers: undefined | (Trigger | TriggerList)[]
): Trigger[] => { ): Trigger[] => {

View File

@ -10,7 +10,7 @@ interface InvalidConfig {
error: string; error: string;
} }
type ValidKeys = "trigger" | "action" | "condition"; type ValidKeys = "triggers" | "actions" | "conditions";
export const validateConfig = < export const validateConfig = <
T extends Partial<{ [key in ValidKeys]: unknown }>, T extends Partial<{ [key in ValidKeys]: unknown }>,

View File

@ -404,7 +404,7 @@ export const getActionType = (action: Action): ActionType => {
if ("set_conversation_response" in action) { if ("set_conversation_response" in action) {
return "set_conversation_response"; return "set_conversation_response";
} }
if ("action" in action) { if ("action" in action || "service" in action) {
if ("metadata" in action) { if ("metadata" in action) {
if (is(action, activateSceneActionStruct)) { if (is(action, activateSceneActionStruct)) {
return "activate_scene"; return "activate_scene";

View File

@ -187,10 +187,21 @@ export const getDataFromPath = (
const asNumber = Number(raw); const asNumber = Number(raw);
if (isNaN(asNumber)) { if (isNaN(asNumber)) {
const tempResult = result[raw]; let tempResult = result[raw];
if (!tempResult && raw === "sequence") { if (!tempResult && raw === "sequence") {
continue; continue;
} }
if (!tempResult && raw === "trigger") {
tempResult = result.triggers;
}
if (!tempResult && raw === "condition") {
tempResult = result.conditions;
}
if (!tempResult && raw === "action") {
tempResult = result.actions;
}
if (raw === "trigger") { if (raw === "trigger") {
result = flattenTriggers(tempResult); result = flattenTriggers(tempResult);
} else { } else {

View File

@ -510,15 +510,15 @@ export default class HaAutomationActionRow extends LitElement {
private async _runAction() { private async _runAction() {
const validated = await validateConfig(this.hass, { const validated = await validateConfig(this.hass, {
action: this.action, actions: this.action,
}); });
if (!validated.action.valid) { if (!validated.actions.valid) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.automation.editor.actions.invalid_action" "ui.panel.config.automation.editor.actions.invalid_action"
), ),
text: validated.action.error, text: validated.actions.error,
}); });
return; return;
} }

View File

@ -431,7 +431,7 @@ export default class HaAutomationConditionRow extends LitElement {
try { try {
const validateResult = await validateConfig(this.hass, { const validateResult = await validateConfig(this.hass, {
condition, conditions: condition,
}); });
// Abort if condition changed. // Abort if condition changed.
@ -440,12 +440,12 @@ export default class HaAutomationConditionRow extends LitElement {
return; return;
} }
if (!validateResult.condition.valid) { if (!validateResult.conditions.valid) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.automation.editor.conditions.invalid_condition" "ui.panel.config.automation.editor.conditions.invalid_condition"
), ),
text: validateResult.condition.error, text: validateResult.conditions.error,
}); });
this._testing = false; this._testing = false;
return; return;

View File

@ -46,6 +46,7 @@ import {
fetchAutomationFileConfig, fetchAutomationFileConfig,
getAutomationEditorInitData, getAutomationEditorInitData,
getAutomationStateConfig, getAutomationStateConfig,
migrateAutomationConfig,
normalizeAutomationConfig, normalizeAutomationConfig,
saveAutomationConfig, saveAutomationConfig,
showAutomationEditor, showAutomationEditor,
@ -520,9 +521,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
return; return;
} }
const validation = await validateConfig(this.hass, { const validation = await validateConfig(this.hass, {
trigger: this._config.trigger, triggers: this._config.triggers,
condition: this._config.condition, conditions: this._config.conditions,
action: this._config.action, actions: this._config.actions,
}); });
this._validationErrors = ( this._validationErrors = (
Object.entries(validation) as Entries<typeof validation> Object.entries(validation) as Entries<typeof validation>
@ -530,7 +531,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
value.valid value.valid
? "" ? ""
: html`${this.hass.localize( : html`${this.hass.localize(
`ui.panel.config.automation.editor.${key}s.name` `ui.panel.config.automation.editor.${key}.name`
)}: )}:
${value.error}<br />` ${value.error}<br />`
); );
@ -637,7 +638,10 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
if (!ev.detail.isValid) { if (!ev.detail.isValid) {
return; return;
} }
this._config = { id: this._config?.id, ...ev.detail.value }; this._config = {
id: this._config?.id,
...migrateAutomationConfig(ev.detail.value),
};
this._errors = undefined; this._errors = undefined;
this._dirty = true; this._dirty = true;
} }

View File

@ -89,7 +89,7 @@ export class HaManualAutomationEditor extends LitElement {
<ha-automation-trigger <ha-automation-trigger
role="region" role="region"
aria-labelledby="triggers-heading" aria-labelledby="triggers-heading"
.triggers=${this.config.trigger || []} .triggers=${this.config.triggers || []}
.path=${["trigger"]} .path=${["trigger"]}
@value-changed=${this._triggerChanged} @value-changed=${this._triggerChanged}
@item-moved=${this._itemMoved} @item-moved=${this._itemMoved}
@ -119,7 +119,7 @@ export class HaManualAutomationEditor extends LitElement {
></ha-icon-button> ></ha-icon-button>
</a> </a>
</div> </div>
${!ensureArray(this.config.condition)?.length ${!ensureArray(this.config.conditions)?.length
? html`<p> ? html`<p>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.conditions.description", "ui.panel.config.automation.editor.conditions.description",
@ -131,7 +131,7 @@ export class HaManualAutomationEditor extends LitElement {
<ha-automation-condition <ha-automation-condition
role="region" role="region"
aria-labelledby="conditions-heading" aria-labelledby="conditions-heading"
.conditions=${this.config.condition || []} .conditions=${this.config.conditions || []}
.path=${["condition"]} .path=${["condition"]}
@value-changed=${this._conditionChanged} @value-changed=${this._conditionChanged}
@item-moved=${this._itemMoved} @item-moved=${this._itemMoved}
@ -160,7 +160,7 @@ export class HaManualAutomationEditor extends LitElement {
</a> </a>
</div> </div>
</div> </div>
${!ensureArray(this.config.action)?.length ${!ensureArray(this.config.actions)?.length
? html`<p> ? html`<p>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.description" "ui.panel.config.automation.editor.actions.description"
@ -171,7 +171,7 @@ export class HaManualAutomationEditor extends LitElement {
<ha-automation-action <ha-automation-action
role="region" role="region"
aria-labelledby="actions-heading" aria-labelledby="actions-heading"
.actions=${this.config.action} .actions=${this.config.actions || []}
.path=${["action"]} .path=${["action"]}
@value-changed=${this._actionChanged} @value-changed=${this._actionChanged}
@item-moved=${this._itemMoved} @item-moved=${this._itemMoved}
@ -185,7 +185,7 @@ export class HaManualAutomationEditor extends LitElement {
private _triggerChanged(ev: CustomEvent): void { private _triggerChanged(ev: CustomEvent): void {
ev.stopPropagation(); ev.stopPropagation();
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {
value: { ...this.config!, trigger: ev.detail.value as Trigger[] }, value: { ...this.config!, triggers: ev.detail.value as Trigger[] },
}); });
} }
@ -194,7 +194,7 @@ export class HaManualAutomationEditor extends LitElement {
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {
value: { value: {
...this.config!, ...this.config!,
condition: ev.detail.value as Condition[], conditions: ev.detail.value as Condition[],
}, },
}); });
} }
@ -202,7 +202,7 @@ export class HaManualAutomationEditor extends LitElement {
private _actionChanged(ev: CustomEvent): void { private _actionChanged(ev: CustomEvent): void {
ev.stopPropagation(); ev.stopPropagation();
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {
value: { ...this.config!, action: ev.detail.value as Action[] }, value: { ...this.config!, actions: ev.detail.value as Action[] },
}); });
} }

View File

@ -436,11 +436,11 @@ export default class HaAutomationTriggerRow extends LitElement {
} }
const validateResult = await validateConfig(this.hass, { const validateResult = await validateConfig(this.hass, {
trigger, triggers: trigger,
}); });
// Don't do anything if trigger not valid or if trigger changed. // Don't do anything if trigger not valid or if trigger changed.
if (!validateResult.trigger.valid || this.trigger !== trigger) { if (!validateResult.triggers.valid || this.trigger !== trigger) {
return; return;
} }

View File

@ -467,7 +467,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
return; return;
} }
const validation = await validateConfig(this.hass, { const validation = await validateConfig(this.hass, {
action: this._config.sequence, actions: this._config.sequence,
}); });
this._validationErrors = ( this._validationErrors = (
Object.entries(validation) as Entries<typeof validation> Object.entries(validation) as Entries<typeof validation>
@ -475,7 +475,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
value.valid value.valid
? "" ? ""
: html`${this.hass.localize( : html`${this.hass.localize(
`ui.panel.config.automation.editor.${key}s.name` `ui.panel.config.automation.editor.${key}.name`
)}: )}:
${value.error}<br />` ${value.error}<br />`
); );