Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take convert of blueprint automation and script #21151

Merged
merged 6 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/data/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,22 @@ export const saveAutomationConfig = (
config: AutomationConfig
) => hass.callApi<void>("POST", `config/automation/config/${id}`, config);

export const normalizeAutomationConfig = <
T extends Partial<AutomationConfig> | AutomationConfig,
>(
config: T
): T => {
// Normalize data: ensure trigger, action and condition are lists
// Happens when people copy paste their automations into the config
for (const key of ["trigger", "condition", "action"]) {
const value = config[key];
if (value && !Array.isArray(value)) {
config[key] = [value];
}
}
return config;
};

export const showAutomationEditor = (data?: Partial<AutomationConfig>) => {
initialAutomationEditorData = data;
navigate("/config/automation/edit/new");
Expand Down
22 changes: 22 additions & 0 deletions src/data/blueprint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { HomeAssistant } from "../types";
import { ManualAutomationConfig } from "./automation";
import { ManualScriptConfig } from "./script";
import { Selector } from "./selector";

export type BlueprintDomain = "automation" | "script";
Expand Down Expand Up @@ -42,6 +44,11 @@ export interface BlueprintImportResult {
validation_errors: string[] | null;
}

export interface BlueprintSubstituteResults {
automation: { substituted_config: ManualAutomationConfig };
script: { substituted_config: ManualScriptConfig };
}

export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) =>
hass.callWS<Blueprints>({ type: "blueprint/list", domain });

Expand Down Expand Up @@ -91,3 +98,18 @@ export const getBlueprintSourceType = (
}
return "community";
};

export const substituteBlueprint = <
T extends BlueprintDomain = BlueprintDomain,
>(
hass: HomeAssistant,
domain: T,
path: string,
input: Record<string, any>
) =>
hass.callWS<BlueprintSubstituteResults[T]>({
type: "blueprint/substitute",
domain,
path,
input,
});
piitaya marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { HassEntity } from "home-assistant-js-websocket";
import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-alert";
import "../../../components/ha-markdown";
piitaya marked this conversation as resolved.
Show resolved Hide resolved
import { BlueprintAutomationConfig } from "../../../data/automation";
import { fetchBlueprints } from "../../../data/blueprint";
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
import "../../../components/ha-markdown";

@customElement("blueprint-automation-editor")
export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
Expand Down
79 changes: 64 additions & 15 deletions src/panels/config/automation/ha-automation-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
mdiDebugStepOver,
mdiDelete,
mdiDotsVertical,
mdiFileEdit,
piitaya marked this conversation as resolved.
Show resolved Hide resolved
mdiInformationOutline,
mdiPlay,
mdiPlayCircleOutline,
Expand Down Expand Up @@ -40,10 +41,12 @@ import "../../../components/ha-yaml-editor";
import {
AutomationConfig,
AutomationEntity,
BlueprintAutomationConfig,
deleteAutomation,
fetchAutomationFileConfig,
getAutomationEditorInitData,
getAutomationStateConfig,
normalizeAutomationConfig,
piitaya marked this conversation as resolved.
Show resolved Hide resolved
piitaya marked this conversation as resolved.
Show resolved Hide resolved
saveAutomationConfig,
showAutomationEditor,
triggerAutomationActions,
Expand All @@ -65,6 +68,7 @@ import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-a
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
import "./blueprint-automation-editor";
import "./manual-automation-editor";
import { substituteBlueprint } from "../../../data/blueprint";

declare global {
interface HTMLElementTagNameMap {
Expand Down Expand Up @@ -235,6 +239,24 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
></ha-svg-icon>
</ha-list-item>

${useBlueprint
? html`
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly || this._mode === "yaml"}
>
${this.hass.localize(
"ui.panel.config.automation.editor.take_control"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}

<li divider role="separator"></li>

<ha-list-item graphic="icon" @click=${this._switchUiMode}>
Expand Down Expand Up @@ -432,7 +454,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
}
this._config = {
...baseConfig,
...initData,
...(initData ? normalizeAutomationConfig(initData) : initData),
piitaya marked this conversation as resolved.
Show resolved Hide resolved
} as AutomationConfig;
this._entityId = undefined;
this._readOnly = false;
Expand All @@ -441,7 +463,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {

if (changedProps.has("entityId") && this.entityId) {
getAutomationStateConfig(this.hass, this.entityId).then((c) => {
this._config = this._normalizeConfig(c.config);
this._config = normalizeAutomationConfig(c.config);
this._checkValidation();
});
this._entityId = this.entityId;
Expand Down Expand Up @@ -497,18 +519,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
);
}

private _normalizeConfig(config: AutomationConfig): AutomationConfig {
// Normalize data: ensure trigger, action and condition are lists
// Happens when people copy paste their automations into the config
for (const key of ["trigger", "condition", "action"]) {
const value = config[key];
if (value && !Array.isArray(value)) {
config[key] = [value];
}
}
return config;
}

private async _loadConfig() {
try {
const config = await fetchAutomationFileConfig(
Expand All @@ -517,7 +527,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
);
this._dirty = false;
this._readOnly = false;
this._config = this._normalizeConfig(config);
this._config = normalizeAutomationConfig(config);
piitaya marked this conversation as resolved.
Show resolved Hide resolved
this._checkValidation();
} catch (err: any) {
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
Expand Down Expand Up @@ -638,6 +648,45 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
}
};

private async _takeControl() {
const config = this._config as BlueprintAutomationConfig;

const confirmation = await showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.automation.editor.take_control_confirmation.title"
),
text: this.hass!.localize(
"ui.panel.config.automation.editor.take_control_confirmation.text"
),
confirmText: this.hass!.localize(
"ui.panel.config.automation.editor.take_control_confirmation.action"
),
});

if (!confirmation) return;

try {
const result = await substituteBlueprint(
this.hass,
"automation",
config.use_blueprint.path,
config.use_blueprint.input || {}
);

const newConfig = {
...normalizeAutomationConfig(result.substituted_config),
alias: config.alias,
description: config.description,
};

this._config = newConfig;
this._dirty = true;
this._errors = undefined;
} catch (err: any) {
this._errors = err.message;
}
}
piitaya marked this conversation as resolved.
Show resolved Hide resolved

private async _duplicate() {
const result = this._readOnly
? await showConfirmationDialog(this, {
Expand Down
4 changes: 2 additions & 2 deletions src/panels/config/script/blueprint-script-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import "@material/mwc-button/mwc-button";
import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-alert";
import { BlueprintScriptConfig } from "../../../data/script";
import "../../../components/ha-markdown";
import { fetchBlueprints } from "../../../data/blueprint";
import { BlueprintScriptConfig } from "../../../data/script";
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
import "../../../components/ha-markdown";

@customElement("blueprint-script-editor")
export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {
Expand Down
60 changes: 60 additions & 0 deletions src/panels/config/script/ha-script-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
mdiDebugStepOver,
mdiDelete,
mdiDotsVertical,
mdiFileEdit,
mdiFormTextbox,
mdiInformationOutline,
mdiPlay,
Expand Down Expand Up @@ -40,6 +41,7 @@ import { validateConfig } from "../../../data/config";
import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import {
BlueprintScriptConfig,
ScriptConfig,
deleteScript,
fetchScriptFileConfig,
Expand All @@ -61,6 +63,7 @@ import { showAutomationRenameDialog } from "../automation/automation-rename-dial
import "./blueprint-script-editor";
import "./manual-script-editor";
import type { HaManualScriptEditor } from "./manual-script-editor";
import { substituteBlueprint } from "../../../data/blueprint";

export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
Expand Down Expand Up @@ -228,6 +231,24 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
></ha-svg-icon>
</ha-list-item>

${useBlueprint
? html`
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly || this._mode === "yaml"}
>
${this.hass.localize(
"ui.panel.config.script.editor.take_control"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}

<li divider role="separator"></li>

<ha-list-item graphic="icon" @click=${this._switchUiMode}>
Expand Down Expand Up @@ -601,6 +622,45 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
};

private async _takeControl() {
const config = this._config as BlueprintScriptConfig;

const confirmation = await showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.script.editor.take_control_confirmation.title"
),
text: this.hass!.localize(
"ui.panel.config.script.editor.take_control_confirmation.text"
),
confirmText: this.hass!.localize(
"ui.panel.config.script.editor.take_control_confirmation.action"
),
});

if (!confirmation) return;

try {
const result = await substituteBlueprint(
this.hass,
"script",
config.use_blueprint.path,
config.use_blueprint.input || {}
);

const newConfig = {
...this._normalizeConfig(result.substituted_config),
alias: config.alias,
description: config.description,
};

this._config = newConfig;
this._dirty = true;
this._errors = undefined;
} catch (err: any) {
this._errors = err.message;
}
}
piitaya marked this conversation as resolved.
Show resolved Hide resolved

private async _duplicate() {
const result = this._readOnly
? await showConfirmationDialog(this, {
Expand Down
12 changes: 12 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2759,6 +2759,12 @@
"unavailable": "Automation is unavailable",
"migrate": "Migrate",
"duplicate": "[%key:ui::common::duplicate%]",
"take_control": "Take control",
"take_control_confirmation": {
"title": "Take control of automation?",
"text": "This automation is using a blueprint. By taking control, your automation will be converted into a regular automation using triggers, conditions and actions. You will be able to edit it directly and you won't be able to convert it back to a blueprint.",
"action": "Take control"
},
"run": "[%key:ui::panel::config::automation::editor::actions::run%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"show_trace": "Traces",
Expand Down Expand Up @@ -3629,6 +3635,12 @@
"show_info": "[%key:ui::panel::config::automation::editor::show_info%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"change_mode": "[%key:ui::panel::config::automation::editor::change_mode%]",
"take_control": "[%key:ui::panel::config::automation::editor::take_control%]",
"take_control_confirmation": {
"title": "Take control of script?",
"text": "This script is using a blueprint. By taking control, your script will be converted into a regular automation using actions. You will be able to edit it directly and you won't be able to convert it back to a blueprint.",
"action": "[%key:ui::panel::config::automation::editor::take_control_confirmation::action%]"
},
"read_only": "This script cannot be edited from the UI, because it is not stored in the ''scripts.yaml'' file.",
"unavailable": "Script is unavailable",
"migrate": "Migrate",
Expand Down
Loading