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 2 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
18 changes: 18 additions & 0 deletions src/data/blueprint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HomeAssistant } from "../types";
import { ManualAutomationConfig } from "./automation";
import { Selector } from "./selector";

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

export interface BlueprintSubstituteResult {
substituted_config: ManualAutomationConfig;
}

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

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

export const substituteBlueprint = (
hass: HomeAssistant,
domain: BlueprintDomain,
path: string,
input: Record<string, any>
) =>
hass.callWS<BlueprintSubstituteResult>({
type: "blueprint/substitute",
domain,
path,
input,
});
69 changes: 66 additions & 3 deletions src/panels/config/automation/blueprint-automation-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import "@material/mwc-button/mwc-button";
import { HassEntity } from "home-assistant-js-websocket";
import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { navigate } from "../../../common/navigate";
import { nextRender } from "../../../common/util/render-status";
import "../../../components/ha-alert";
import { BlueprintAutomationConfig } from "../../../data/automation";
import { fetchBlueprints } from "../../../data/blueprint";
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
import "../../../components/ha-markdown";
piitaya marked this conversation as resolved.
Show resolved Hide resolved
import {
BlueprintAutomationConfig,
normalizeAutomationConfig,
showAutomationEditor,
} from "../../../data/automation";
import { fetchBlueprints, substituteBlueprint } from "../../../data/blueprint";
import { showConfirmationDialog } from "../../lovelace/custom-card-helpers";
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
import "./manual-automation-editor";

@customElement("blueprint-automation-editor")
export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
Expand Down Expand Up @@ -65,6 +73,61 @@ export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
entity_id: this.stateObj.entity_id,
});
}

public async substituteBlueprint(): Promise<void> {
try {
const substitute = await substituteBlueprint(
this.hass,
"automation",
this.config.use_blueprint.path,
this.config.use_blueprint.input || {}
);

const config = normalizeAutomationConfig(substitute.substituted_config);

const convert = await showConfirmationDialog(this, {
title: "Take control of blueprint automation",
text: html`<manual-automation-editor
.hass=${this.hass}
disabled
.config=${config}
></manual-automation-editor>`,
confirmText: "Create automation",
});

if (convert) {
if (this.dirty) {
const confirmed = await showConfirmationDialog(this, {
title: "Unsaved changes",
text: "You have unsaved changes. Do you want to continue and lose these changes?",
confirmText: "Continue",
});
if (!confirmed) {
return;
}
}
while (
location.pathname === "/config/automation/edit/new" &&
history.length > 1
) {
history.back();
// eslint-disable-next-line no-await-in-loop
await nextRender();
}
if (location.pathname === "/config/automation/edit/new") {
navigate("/config/automation");
await nextRender();
}
showAutomationEditor({
alias: this.config.alias,
description: `${this.config.description ? this.config.description : this._blueprint && "metadata" in this._blueprint ? this._blueprint.metadata.description : ""}${this._blueprint && "metadata" in this._blueprint ? `(Originated from blueprint ${this._blueprint?.metadata.name})` : ""}`,
...substitute.substituted_config,
});
}
} catch (err: any) {
alert(`Failed to substitute blueprint: ${err.message}`);
}
}
piitaya marked this conversation as resolved.
Show resolved Hide resolved
}
declare global {
interface HTMLElementTagNameMap {
Expand Down
40 changes: 24 additions & 16 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 @@ -44,6 +45,7 @@ import {
fetchAutomationFileConfig,
getAutomationEditorInitData,
getAutomationStateConfig,
normalizeAutomationConfig,
piitaya marked this conversation as resolved.
Show resolved Hide resolved
saveAutomationConfig,
showAutomationEditor,
triggerAutomationActions,
Expand Down Expand Up @@ -217,7 +219,16 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
></ha-svg-icon>
</ha-list-item>
`
: nothing}
: html`<ha-list-item
graphic="icon"
@click=${this._substituteBlueprint}
.disabled=${this._readOnly}
>
${this.hass.localize(
"ui.panel.config.automation.editor.subtitute_blueprint"
)}
<ha-svg-icon slot="graphic" .path=${mdiFileEdit}></ha-svg-icon>
</ha-list-item>`}
piitaya marked this conversation as resolved.
Show resolved Hide resolved

<ha-list-item
.disabled=${!this._readOnly && !this.automationId}
Expand Down Expand Up @@ -331,6 +342,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
.stateObj=${stateObj}
.config=${this._config}
.disabled=${Boolean(this._readOnly)}
.dirty=${this._dirty}
@value-changed=${this._valueChanged}
@duplicate=${this._duplicate}
></blueprint-automation-editor>
Expand All @@ -343,6 +355,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
.stateObj=${stateObj}
.config=${this._config}
.disabled=${Boolean(this._readOnly)}
.readOnly=${Boolean(this._readOnly)}
.dirty=${this._dirty}
@value-changed=${this._valueChanged}
@duplicate=${this._duplicate}
></manual-automation-editor>
Expand Down Expand Up @@ -432,7 +446,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 +455,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 +511,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 +519,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 +640,12 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
}
};

private _substituteBlueprint() {
this.renderRoot
.querySelector("blueprint-automation-editor")
?.substituteBlueprint();
}
piitaya marked this conversation as resolved.
Show resolved Hide resolved

private async _duplicate() {
const result = this._readOnly
? await showConfirmationDialog(this, {
Expand Down
4 changes: 3 additions & 1 deletion src/panels/config/automation/manual-automation-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ export class HaManualAutomationEditor extends LitElement {

@property({ type: Boolean }) public disabled = false;

@property({ type: Boolean }) public readOnly = false;

@property({ attribute: false }) public config!: ManualAutomationConfig;

@property({ attribute: false }) public stateObj?: HassEntity;

protected render() {
return html`
${this.disabled
${this.readOnly
? html`<ha-alert alert-type="warning">
${this.hass.localize("ui.panel.config.automation.editor.read_only")}
<mwc-button slot="action" @click=${this._duplicate}>
Expand Down
2 changes: 2 additions & 0 deletions src/panels/config/blueprint/blueprint-generic-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export abstract class HaBlueprintGenericEditor extends LitElement {

@property({ type: Boolean }) public disabled = false;

@property({ type: Boolean }) public dirty = false;
piitaya marked this conversation as resolved.
Show resolved Hide resolved

@property({ type: Boolean, reflect: true }) public narrow = false;

@state() protected _blueprints?: Blueprints;
Expand Down
1 change: 1 addition & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2759,6 +2759,7 @@
"unavailable": "Automation is unavailable",
"migrate": "Migrate",
"duplicate": "[%key:ui::common::duplicate%]",
"subtitute_blueprint": "Take control of automation",
piitaya marked this conversation as resolved.
Show resolved Hide resolved
"run": "[%key:ui::panel::config::automation::editor::actions::run%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"show_trace": "Traces",
Expand Down
Loading