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",
alias: "Ensure Party mode",
description: "",
trigger: [
triggers: [
{
platform: "state",
entity_id: "input_boolean.toggle_1",
},
],
condition: [
conditions: [
{
condition: "template",
alias: "Test if Paulus is home",
value_template: "{{ true }}",
},
],
action: [
actions: [
{
action: "input_boolean.toggle",
target: {

View File

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

View File

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

View File

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

View File

@ -569,10 +569,15 @@ export class HatScriptGraph extends LitElement {
}
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 trigger_nodes =
"trigger" in this.trace.config
? flattenTriggers(ensureArray(this.trace.config.trigger)).map(
triggerKey in this.trace.config
? flattenTriggers(ensureArray(this.trace.config[triggerKey])).map(
(trigger, i) => this.render_trigger(trigger, i)
)
: undefined;
@ -584,14 +589,14 @@ export class HatScriptGraph extends LitElement {
${trigger_nodes}
</hat-graph-branch>`
: ""}
${"condition" in this.trace.config
? html`${ensureArray(this.trace.config.condition)?.map(
${conditionKey in this.trace.config
? html`${ensureArray(this.trace.config[conditionKey])?.map(
(condition, i) => this.render_condition(condition, i)
)}`
: ""}
${"action" in this.trace.config
? html`${ensureArray(this.trace.config.action).map((action, i) =>
this.render_action_node(action, `action/${i}`)
${actionKey in this.trace.config
? html`${ensureArray(this.trace.config[actionKey]).map(
(action, i) => this.render_action_node(action, `action/${i}`)
)}`
: ""}
${"sequence" in this.trace.config

View File

@ -27,8 +27,14 @@ export interface ManualAutomationConfig {
id?: string;
alias?: 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[];
actions: Action | Action[];
/** @deprecated Use `actions` instead */
action?: Action | Action[];
mode?: (typeof MODES)[number];
max?: number;
@ -362,22 +368,50 @@ export const normalizeAutomationConfig = <
>(
config: T
): T => {
config = migrateAutomationConfig(config);
// Normalize data: ensure triggers, actions and conditions are lists
// 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];
if (value && !Array.isArray(value)) {
config[key] = [value];
}
}
if (config.action) {
config.action = migrateAutomationAction(config.action);
if (config.actions) {
config.actions = migrateAutomationAction(config.actions);
}
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 = (
triggers: undefined | (Trigger | TriggerList)[]
): Trigger[] => {

View File

@ -10,7 +10,7 @@ interface InvalidConfig {
error: string;
}
type ValidKeys = "trigger" | "action" | "condition";
type ValidKeys = "triggers" | "actions" | "conditions";
export const validateConfig = <
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) {
return "set_conversation_response";
}
if ("action" in action) {
if ("action" in action || "service" in action) {
if ("metadata" in action) {
if (is(action, activateSceneActionStruct)) {
return "activate_scene";

View File

@ -187,10 +187,21 @@ export const getDataFromPath = (
const asNumber = Number(raw);
if (isNaN(asNumber)) {
const tempResult = result[raw];
let tempResult = result[raw];
if (!tempResult && raw === "sequence") {
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") {
result = flattenTriggers(tempResult);
} else {

View File

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

View File

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

View File

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

View File

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

View File

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