Change Data Entry Flow loading step description logic + cleanups (#9558)

* Change Data Entry Flow loading step description logic + cleanups

Fixes #6251

* Lint

* Address comment
This commit is contained in:
Bram Kragten 2021-07-15 21:07:25 +02:00 committed by GitHub
parent cc81239b9d
commit 1206e2d75f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 177 additions and 76 deletions

View File

@ -1,3 +1,4 @@
import { Connection } from "home-assistant-js-websocket";
import { HaFormSchema } from "../components/ha-form/ha-form";
import { ConfigEntry } from "./config_entries";
@ -74,3 +75,12 @@ export type DataEntryFlowStep =
| DataEntryFlowStepCreateEntry
| DataEntryFlowStepAbort
| DataEntryFlowStepProgress;
export const subscribeDataEntryFlowProgressed = (
conn: Connection,
callback: (ev: DataEntryFlowProgressedEvent) => void
) =>
conn.subscribeEvents<DataEntryFlowProgressedEvent>(
callback,
"data_entry_flow_progressed"
);

View File

@ -10,7 +10,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/ha-circular-progress";
import "../../components/ha-dialog";
@ -22,10 +22,10 @@ import {
subscribeAreaRegistry,
} from "../../data/area_registry";
import { fetchConfigFlowInProgress } from "../../data/config_flow";
import type {
import {
DataEntryFlowProgress,
DataEntryFlowProgressedEvent,
DataEntryFlowStep,
subscribeDataEntryFlowProgressed,
} from "../../data/data_entry_flow";
import {
DeviceRegistryEntry,
@ -34,7 +34,10 @@ import {
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { showAlertDialog } from "../generic/show-dialog-box";
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
import {
DataEntryFlowDialogParams,
LoadingReason,
} from "./show-dialog-data-entry-flow";
import "./step-flow-abort";
import "./step-flow-create-entry";
import "./step-flow-external";
@ -46,13 +49,19 @@ import "./step-flow-progress";
let instance = 0;
interface FlowUpdateEvent {
step?: DataEntryFlowStep;
stepPromise?: Promise<DataEntryFlowStep>;
}
declare global {
// for fire event
interface HASSDomEvents {
"flow-update": {
step?: DataEntryFlowStep;
stepPromise?: Promise<DataEntryFlowStep>;
};
"flow-update": FlowUpdateEvent;
}
// for add event listener
interface HTMLElementEventMap {
"flow-update": HASSDomEvent<FlowUpdateEvent>;
}
}
@ -62,7 +71,7 @@ class DataEntryFlowDialog extends LitElement {
@state() private _params?: DataEntryFlowDialogParams;
@state() private _loading = true;
@state() private _loading?: LoadingReason;
private _instance = instance;
@ -86,6 +95,8 @@ class DataEntryFlowDialog extends LitElement {
private _unsubDevices?: UnsubscribeFunc;
private _unsubDataEntryFlowProgressed?: Promise<UnsubscribeFunc>;
public async showDialog(params: DataEntryFlowDialogParams): Promise<void> {
this._params = params;
this._instance = instance++;
@ -96,7 +107,7 @@ class DataEntryFlowDialog extends LitElement {
}
if (params.continueFlowId) {
this._loading = true;
this._loading = "loading_flow";
const curInstance = this._instance;
let step: DataEntryFlowStep;
try {
@ -124,7 +135,7 @@ class DataEntryFlowDialog extends LitElement {
}
this._processStep(step);
this._loading = false;
this._loading = undefined;
return;
}
@ -136,14 +147,13 @@ class DataEntryFlowDialog extends LitElement {
// We only load the handlers once
if (this._handlers === undefined) {
this._loading = true;
this._loading = "loading_handlers";
try {
this._handlers = await params.flowConfig.getFlowHandlers(this.hass);
} finally {
this._loading = false;
this._loading = undefined;
}
}
await this.updateComplete;
}
public closeDialog() {
@ -178,6 +188,12 @@ class DataEntryFlowDialog extends LitElement {
this._unsubDevices();
this._unsubDevices = undefined;
}
if (this._unsubDataEntryFlowProgressed) {
this._unsubDataEntryFlowProgressed.then((unsub) => {
unsub();
});
this._unsubDataEntryFlowProgressed = undefined;
}
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -201,9 +217,11 @@ class DataEntryFlowDialog extends LitElement {
this._handler === undefined)
? html`
<step-flow-loading
.label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.loading_first_time"
)}
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
.loadingReason=${this._loading || "loading_handlers"}
.handler=${this._handler}
.step=${this._step}
></step-flow-loading>
`
: this._step === undefined
@ -269,7 +287,13 @@ class DataEntryFlowDialog extends LitElement {
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html` <step-flow-loading></step-flow-loading> `
html`
<step-flow-loading
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
loadingReason="loading_devices_areas"
></step-flow-loading>
`
: html`
<step-flow-create-entry
.flowConfig=${this._params.flowConfig}
@ -287,31 +311,22 @@ class DataEntryFlowDialog extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.hass.connection.subscribeEvents<DataEntryFlowProgressedEvent>(
async (ev) => {
if (ev.data.flow_id !== this._step?.flow_id) {
return;
}
const step = await this._params!.flowConfig.fetchFlow(
this.hass,
this._step?.flow_id
);
this._processStep(step);
},
"data_entry_flow_progressed"
);
this.addEventListener("flow-update", (ev) => {
const { step, stepPromise } = (ev as any).detail;
const { step, stepPromise } = ev.detail;
this._processStep(step || stepPromise);
});
}
protected updated(changedProps: PropertyValues) {
if (
changedProps.has("_step") &&
this._step &&
this._step.type === "create_entry"
) {
public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (!changedProps.has("_step") || !this._step) {
return;
}
if (["external", "progress"].includes(this._step.type)) {
// external and progress step will send update event from the backend, so we should subscribe to them
this._subscribeDataEntryFlowProgressed();
}
if (this._step.type === "create_entry") {
if (this._step.result && this._params!.flowConfig.loadDevicesAndAreas) {
this._fetchDevices(this._step.result.entry_id);
this._fetchAreas();
@ -340,13 +355,16 @@ class DataEntryFlowDialog extends LitElement {
}
private async _checkFlowsInProgress(handler: string) {
this._loading = true;
this._loading = "loading_handlers";
this._handler = handler;
const flowsInProgress = (
await fetchConfigFlowInProgress(this.hass.connection)
).filter((flow) => flow.handler === handler);
if (!flowsInProgress.length) {
// No flows in progress, create a new flow
this._loading = "loading_flow";
let step: DataEntryFlowStep;
try {
step = await this._params!.flowConfig.createFlow(this.hass, handler);
@ -362,14 +380,15 @@ class DataEntryFlowDialog extends LitElement {
),
});
return;
} finally {
this._handler = undefined;
}
this._processStep(step);
} else {
this._step = null;
this._handler = handler;
this._flowsInProgress = flowsInProgress;
}
this._loading = false;
this._loading = undefined;
}
private _handlerPicked(ev) {
@ -380,11 +399,11 @@ class DataEntryFlowDialog extends LitElement {
step: DataEntryFlowStep | undefined | Promise<DataEntryFlowStep>
): Promise<void> {
if (step instanceof Promise) {
this._loading = true;
this._loading = "loading_step";
try {
this._step = await step;
} finally {
this._loading = false;
this._loading = undefined;
}
return;
}
@ -398,6 +417,23 @@ class DataEntryFlowDialog extends LitElement {
this._step = step;
}
private _subscribeDataEntryFlowProgressed() {
if (this._unsubDataEntryFlowProgressed) {
return;
}
this._unsubDataEntryFlowProgressed = subscribeDataEntryFlowProgressed(
this.hass.connection,
async (ev) => {
if (ev.data.flow_id !== this._step?.flow_id) {
return;
}
this._processStep(
this._params!.flowConfig.fetchFlow(this.hass, this._step?.flow_id)
);
}
);
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,

View File

@ -178,4 +178,22 @@ export const showConfigFlowDialog = (
`
: "";
},
renderLoadingDescription(hass, reason, handler, step) {
if (!["loading_flow", "loading_step"].includes(reason)) {
return "";
}
const domain = step?.handler || handler;
return hass.localize(
`ui.panel.config.integrations.config_flow.loading.${reason}`,
{
integration: domain
? domainToName(hass.localize, domain)
: // when we are continuing a config flow, we only know the ID and not the domain
hass.localize(
"ui.panel.config.integrations.config_flow.loading.fallback_title"
),
}
);
},
});

View File

@ -79,8 +79,21 @@ export interface FlowConfig {
hass: HomeAssistant,
step: DataEntryFlowStepProgress
): TemplateResult | "";
renderLoadingDescription(
hass: HomeAssistant,
loadingReason: LoadingReason,
handler?: string,
step?: DataEntryFlowStep | null
): string;
}
export type LoadingReason =
| "loading_handlers"
| "loading_flow"
| "loading_step"
| "loading_devices_areas";
export interface DataEntryFlowDialogParams {
startFlowHandler?: string;
continueFlowId?: string;

View File

@ -1,5 +1,6 @@
import { html } from "lit";
import { ConfigEntry } from "../../data/config_entries";
import { domainToName } from "../../data/integration";
import {
createOptionsFlow,
deleteOptionsFlow,
@ -132,5 +133,14 @@ export const showOptionsFlowDialog = (
`
: "";
},
renderLoadingDescription(hass, reason) {
return (
hass.localize(`component.${configEntry.domain}.options.loading`) ||
hass.localize(`ui.dialogs.options_flow.loading.${reason}`, {
integration: domainToName(hass.localize, configEntry.domain),
})
);
},
}
);

View File

@ -9,13 +9,11 @@ import { configFlowContentStyles } from "./styles";
@customElement("step-flow-abort")
class StepFlowAbort extends LitElement {
public flowConfig!: FlowConfig;
@property({ attribute: false }) public flowConfig!: FlowConfig;
@property()
public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property()
private step!: DataEntryFlowStepAbort;
@property({ attribute: false }) public step!: DataEntryFlowStepAbort;
protected render(): TemplateResult {
return html`

View File

@ -18,16 +18,13 @@ import { configFlowContentStyles } from "./styles";
@customElement("step-flow-create-entry")
class StepFlowCreateEntry extends LitElement {
public flowConfig!: FlowConfig;
@property({ attribute: false }) public flowConfig!: FlowConfig;
@property()
public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property()
public step!: DataEntryFlowStepCreateEntry;
@property({ attribute: false }) public step!: DataEntryFlowStepCreateEntry;
@property()
public devices!: DeviceRegistryEntry[];
@property({ attribute: false }) public devices!: DeviceRegistryEntry[];
protected render(): TemplateResult {
const localize = this.hass.localize;

View File

@ -8,13 +8,11 @@ import { configFlowContentStyles } from "./styles";
@customElement("step-flow-external")
class StepFlowExternal extends LitElement {
public flowConfig!: FlowConfig;
@property({ attribute: false }) public flowConfig!: FlowConfig;
@property()
public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property()
private step!: DataEntryFlowStepExternal;
@property({ attribute: false }) public step!: DataEntryFlowStepExternal;
protected render(): TemplateResult {
const localize = this.hass.localize;

View File

@ -8,7 +8,7 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-circular-progress";
import "../../components/ha-form/ha-form";
@ -21,22 +21,17 @@ import { configFlowContentStyles } from "./styles";
@customElement("step-flow-form")
class StepFlowForm extends LitElement {
public flowConfig!: FlowConfig;
@property({ attribute: false }) public flowConfig!: FlowConfig;
@property()
public step!: DataEntryFlowStepForm;
@property({ attribute: false }) public step!: DataEntryFlowStepForm;
@property()
public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property()
private _loading = false;
@state() private _loading = false;
@property()
private _stepData?: Record<string, any>;
@state() private _stepData?: Record<string, any>;
@property()
private _errorMsg?: string;
@state() private _errorMsg?: string;
protected render(): TemplateResult {
const step = this.step;

View File

@ -1,15 +1,32 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../components/ha-circular-progress";
import { DataEntryFlowStep } from "../../data/data_entry_flow";
import { HomeAssistant } from "../../types";
import { FlowConfig, LoadingReason } from "./show-dialog-data-entry-flow";
@customElement("step-flow-loading")
class StepFlowLoading extends LitElement {
@property() public label?: string;
@property({ attribute: false }) public flowConfig!: FlowConfig;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public loadingReason!: LoadingReason;
@property() public handler?: string;
@property({ attribute: false }) public step?: DataEntryFlowStep | null;
protected render(): TemplateResult {
const description = this.flowConfig.renderLoadingDescription(
this.hass,
this.loadingReason,
this.handler,
this.step
);
return html`
<div class="init-spinner">
${this.label ? html` <div>${this.label}</div> ` : ""}
${description ? html`<div>${description}</div>` : ""}
<ha-circular-progress active></ha-circular-progress>
</div>
`;

View File

@ -9,13 +9,14 @@ import { configFlowContentStyles } from "./styles";
@customElement("step-flow-progress")
class StepFlowProgress extends LitElement {
@property({ attribute: false })
public flowConfig!: FlowConfig;
@property({ attribute: false })
public hass!: HomeAssistant;
@property({ attribute: false })
private step!: DataEntryFlowStepProgress;
public step!: DataEntryFlowStepProgress;
protected render(): TemplateResult {
return html`

View File

@ -753,6 +753,10 @@
"form": {
"header": "Options"
},
"loading": {
"loading_flow": "Please wait while the options for {integration} are being initialized",
"loading_step": "[%key:ui::panel::config::integrations::config_flow::loading::loading_step%]"
},
"success": {
"description": "Options successfully saved."
}
@ -2224,7 +2228,11 @@
"title": "We discovered these, want to set them up?",
"new_flow": "No, set up an other instance of {integration}"
},
"loading_first_time": "Please wait while the integration is being installed",
"loading": {
"loading_flow": "Please wait while {integration} is being setup",
"loading_step": "Loading next step for {integration}",
"fallback_title": "the integration"
},
"error": "Error",
"could_not_load": "Config flow could not be loaded",
"not_loaded": "The integration could not be loaded, try to restart Home Assistant."