Compare commits

..
Author SHA1 Message Date
Paul Bottein 7f45689676 Migrate legacy sun and calendar offsets in the trigger editor 2026-09-10 13:46:28 +02:00
2 changed files with 118 additions and 32 deletions
@@ -0,0 +1,106 @@
import { durationDataToSeconds } from "../../common/datetime/duration_to_seconds";
import { durationValueToData } from "../../common/datetime/duration_value_to_data";
import type { HaDurationData } from "../../components/ha-duration-input";
import type { PlatformTrigger } from "../automation";
import { TRIGGER_ROW_CONFIG_KEYS } from "../automation";
import type { OffsetSelectorValue } from "../selector";
import type { TriggerDescription } from "../trigger";
const TRIGGER_KEYS: (keyof PlatformTrigger)[] = [
...TRIGGER_ROW_CONFIG_KEYS,
"trigger",
"target",
"options",
];
const isOffsetSelectorValue = (value: unknown): value is OffsetSelectorValue =>
typeof value === "object" && value !== null && "type" in value;
const absDuration = (duration: HaDurationData): HaDurationData =>
Object.fromEntries(
Object.entries(duration).map(([field, amount]) => [
field,
Math.abs(amount ?? 0),
])
);
const migrateLegacyOffsetOptions = (
options: Record<string, unknown>,
fields: TriggerDescription["fields"]
): Record<string, unknown> => {
let migrated: Record<string, unknown> | undefined;
for (const [key, field] of Object.entries(fields)) {
if (!field.selector || !("offset" in field.selector)) {
continue;
}
const typeKey = `${key}_type`;
const value = options[key] as OffsetSelectorValue["duration"] | undefined;
const hasLegacyType = typeKey in options;
if (
!hasLegacyType &&
(value === undefined || isOffsetSelectorValue(value))
) {
continue;
}
migrated ??= { ...options };
delete migrated[typeKey];
if (isOffsetSelectorValue(value)) {
continue;
}
const duration = durationValueToData(value ?? 0);
if (!duration || Object.values(duration).some((amount) => isNaN(amount))) {
continue;
}
let seconds = durationDataToSeconds(duration);
// The released trigger schema defaulted offset_type to before
if (options[typeKey] !== "after") {
seconds = -seconds;
}
migrated[key] =
seconds === 0
? { type: "none" }
: {
type: seconds < 0 ? "before" : "after",
duration: absDuration(duration),
};
}
return migrated ?? options;
};
const moveStrayKeysToOptions = (
trigger: PlatformTrigger
): PlatformTrigger | undefined => {
let migrated: PlatformTrigger | undefined;
for (const key in trigger) {
if (TRIGGER_KEYS.includes(key as keyof PlatformTrigger)) {
continue;
}
migrated ??= { ...trigger, options: { ...trigger.options } };
migrated.options![key] = trigger[key];
delete migrated[key];
}
return migrated;
};
export const migratePlatformTrigger = (
trigger: PlatformTrigger,
description?: TriggerDescription
): PlatformTrigger | undefined => {
let migrated = moveStrayKeysToOptions(trigger);
const options = (migrated ?? trigger).options;
if (options && description?.fields) {
const migratedOptions = migrateLegacyOffsetOptions(
options,
description.fields
);
if (migratedOptions !== options) {
migrated = { ...(migrated ?? trigger), options: migratedOptions };
}
}
return migrated;
};
@@ -10,7 +10,6 @@ import { getSelectorFallbackValue } from "../../../../../components/ha-form/get-
import "../../../../../components/ha-selector/ha-selector";
import "../../../../../components/ha-settings-row";
import type { PlatformTrigger } from "../../../../../data/automation";
import { TRIGGER_ROW_CONFIG_KEYS } from "../../../../../data/automation";
import type { IntegrationManifest } from "../../../../../data/integration";
import { fetchIntegrationManifest } from "../../../../../data/integration";
import type { TargetSelector } from "../../../../../data/selector";
@@ -20,6 +19,7 @@ import {
getTriggerObjectId,
type TriggerDescription,
} from "../../../../../data/trigger";
import { migratePlatformTrigger } from "../../../../../data/trigger/migrate_platform_trigger";
import type { HomeAssistant } from "../../../../../types";
import { documentationUrl } from "../../../../../util/documentation-url";
@@ -28,13 +28,6 @@ const showOptionalToggle = (field: TriggerDescription["fields"][string]) =>
!field.required &&
!("boolean" in field.selector && field.default);
const DEFAULT_KEYS: (keyof PlatformTrigger)[] = [
...TRIGGER_ROW_CONFIG_KEYS,
"trigger",
"target",
"options",
];
@customElement("ha-automation-trigger-platform")
export class HaPlatformTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -61,34 +54,21 @@ export class HaPlatformTrigger extends LitElement {
this.hass.loadBackendTranslation("triggers");
this.hass.loadBackendTranslation("selector");
}
if (
changedProperties.has("trigger") ||
changedProperties.has("description")
) {
const migrated = migratePlatformTrigger(this.trigger, this.description);
if (migrated) {
fireEvent(this, "value-changed", { value: migrated });
this.trigger = migrated;
}
}
if (!changedProperties.has("trigger")) {
return;
}
let newValue: PlatformTrigger | undefined;
for (const key in this.trigger) {
// Migrate old options to `options`
if (DEFAULT_KEYS.includes(key as keyof PlatformTrigger)) {
continue;
}
if (newValue === undefined) {
newValue = {
...this.trigger,
options: { [key]: this.trigger[key] },
};
} else {
newValue.options![key] = this.trigger[key];
}
delete newValue[key];
}
if (newValue !== undefined) {
fireEvent(this, "value-changed", {
value: newValue,
});
this.trigger = newValue;
}
const oldValue = changedProperties.get("trigger") as
undefined | this["trigger"];