Skip to content

Commit

Permalink
Show yaml setup integrations in the UI (#21447)
Browse files Browse the repository at this point in the history
* Show yaml setup integrations in the UI

* Update en.json

* Move config entry logic to memoize function
  • Loading branch information
bramkragten authored Jul 22, 2024
1 parent bbb6487 commit d96ddf9
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 46 deletions.
73 changes: 61 additions & 12 deletions src/components/ha-related-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
mdiSofa,
} from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
Expand All @@ -20,7 +20,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { Blueprints, fetchBlueprints } from "../data/blueprint";
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
import { findRelated, ItemType, RelatedResult } from "../data/search";
import { ItemType, RelatedResult, findRelated } from "../data/search";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
import { brandsUrl } from "../util/brands-url";
Expand Down Expand Up @@ -109,6 +109,26 @@ export class HaRelatedItems extends LitElement {
)
);

private _getConfigEntries = memoizeOne(
(
relatedConfigEntries: string[] | undefined,
entries: ConfigEntry[] | undefined
) => {
const configEntries =
relatedConfigEntries && entries
? relatedConfigEntries.map((entryId) =>
entries!.find((configEntry) => configEntry.entry_id === entryId)
)
: undefined;

const configEntryDomains = new Set(
configEntries?.map((entry) => entry?.domain)
);

return { configEntries, configEntryDomains };
}
);

protected render() {
if (!this._related) {
return nothing;
Expand All @@ -128,22 +148,25 @@ export class HaRelatedItems extends LitElement {
</mwc-list>
`;
}

const { configEntries, configEntryDomains } = this._getConfigEntries(
this._related.config_entry,
this._entries
);

return html`
${this._related.config_entry && this._entries
${configEntries || this._related.integration
? html`<h3>
${this.hass.localize("ui.components.related-items.integration")}
</h3>
<mwc-list
>${this._related.config_entry.map((relatedConfigEntryId) => {
const entry: ConfigEntry | undefined = this._entries!.find(
(configEntry) => configEntry.entry_id === relatedConfigEntryId
);
>${configEntries?.map((entry) => {
if (!entry) {
return nothing;
}
return html`
<a
href=${`/config/integrations/integration/${entry.domain}#config_entry=${relatedConfigEntryId}`}
href=${`/config/integrations/integration/${entry.domain}#config_entry=${entry.entry_id}`}
@click=${this._navigateAwayClose}
>
<ha-list-item hasMeta graphic="icon">
Expand All @@ -164,8 +187,34 @@ export class HaRelatedItems extends LitElement {
</ha-list-item>
</a>
`;
})}</mwc-list
>`
})}
${this._related.integration
?.filter((integration) => !configEntryDomains.has(integration))
.map(
(integration) =>
html`<a
href=${`/config/integrations/integration/${integration}`}
@click=${this._navigateAwayClose}
>
<ha-list-item hasMeta graphic="icon">
<img
.src=${brandsUrl({
domain: integration,
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${integration}
slot="graphic"
/>
${this.hass.localize(`component.${integration}.title`)}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
</a>`
)}
</mwc-list>`
: nothing}
${this._related.device
? html`<h3>
Expand Down
6 changes: 5 additions & 1 deletion src/data/repairs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export const fetchRepairsIssues = (conn: Connection) =>
type: "repairs/list_issues",
});

export const fetchRepairsIssueData = (conn: Connection, domain, issue_id) =>
export const fetchRepairsIssueData = (
conn: Connection,
domain: string,
issue_id: string
) =>
conn.sendMessagePromise<{ issue_data: { string: any } }>({
type: "repairs/get_issue_data",
domain,
Expand Down
1 change: 1 addition & 0 deletions src/data/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface RelatedResult {
device?: string[];
entity?: string[];
group?: string[];
integration?: string[];
scene?: string[];
script?: string[];
script_blueprint?: string[];
Expand Down
28 changes: 25 additions & 3 deletions src/panels/config/entities/ha-config-entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,30 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
)
.map((entry) => entry.entry_id);

const filteredEntitiesByDomain = new Set<string>();

const entitySources = this._entitySources || {};

const entitiesByDomain = {};

for (const [entity, source] of Object.entries(entitySources)) {
if (!(source.domain in entitiesByDomain)) {
entitiesByDomain[source.domain] = [];
}
entitiesByDomain[source.domain].push(entity);
}

for (const val of filter.value) {
if (val in entitiesByDomain) {
entitiesByDomain[val].forEach((item) =>
filteredEntitiesByDomain.add(item)
);
}
}

filteredEntities = filteredEntities.filter(
(entity) =>
filteredEntitiesByDomain.has(entity.entity_id) ||
(filter.value as string[]).includes(entity.platform) ||
(entity.config_entry_id &&
entryIds.includes(entity.config_entry_id))
Expand Down Expand Up @@ -951,6 +973,9 @@ ${
}

protected firstUpdated() {
fetchEntitySourcesWithCache(this.hass).then((sources) => {
this._entitySources = sources;
});
this._setFiltersFromUrl();
if (Object.keys(this._filters).length) {
return;
Expand All @@ -961,9 +986,6 @@ ${
items: undefined,
},
};
fetchEntitySourcesWithCache(this.hass).then((sources) => {
this._entitySources = sources;
});
}

private _setFiltersFromUrl() {
Expand Down
64 changes: 58 additions & 6 deletions src/panels/config/integrations/ha-config-integration-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import { documentationUrl } from "../../../util/documentation-url";
import { fileDownload } from "../../../util/file_download";
import { DataEntryFlowProgressExtended } from "./ha-config-integrations";
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";

@customElement("ha-config-integration-page")
class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
Expand Down Expand Up @@ -140,6 +141,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
window.location.hash.substring(1)
);

@state() private _domainEntities: Record<string, string[]> = {};

private _configPanel = memoizeOne(
(domain: string, panels: HomeAssistant["panels"]): string | undefined =>
Object.values(panels).find(
Expand Down Expand Up @@ -185,7 +188,23 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
this._extraConfigEntries = undefined;
this._fetchManifest();
this._fetchDiagnostics();
this._fetchEntitySources();
}
}

private async _fetchEntitySources() {
const entitySources = await fetchEntitySourcesWithCache(this.hass);

const entitiesByDomain = {};

for (const [entity, source] of Object.entries(entitySources)) {
if (!(source.domain in entitiesByDomain)) {
entitiesByDomain[source.domain] = [];
}
entitiesByDomain[source.domain].push(entity);
}

this._domainEntities = entitiesByDomain;
}

protected updated(changed: PropertyValues) {
Expand Down Expand Up @@ -245,6 +264,22 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {

const devices = this._getDevices(configEntries, this.hass.devices);
const entities = this._getEntities(configEntries, this._entities);
let numberOfEntities = entities.length;

if (
this.domain in this._domainEntities &&
numberOfEntities !== this._domainEntities[this.domain].length
) {
if (!numberOfEntities) {
numberOfEntities = this._domainEntities[this.domain].length;
} else {
const entityIds = new Set(entities.map((entity) => entity.entity_id));
for (const entityId of this._domainEntities[this.domain]) {
entityIds.add(entityId);
}
numberOfEntities = entityIds.size;
}
}

const services = !devices.some((device) => device.entry_type !== "service");

Expand Down Expand Up @@ -320,7 +355,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
</ha-list-item>
</a>`
: ""}
${entities.length > 0
${numberOfEntities > 0
? html`<a
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
>
Expand All @@ -331,7 +366,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
></ha-svg-icon>
${this.hass.localize(
`ui.panel.config.integrations.config_entry.entities`,
{ count: entities.length }
{ count: numberOfEntities }
)}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
Expand Down Expand Up @@ -503,9 +538,15 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
</h1>
${normalEntries.length === 0
? html`<div class="card-content no-entries">
${this.hass.localize(
"ui.panel.config.integrations.integration_page.no_entries"
)}
${this.hass.config.components.find(
(comp) => comp.split(".")[0] === this.domain
)
? this.hass.localize(
"ui.panel.config.integrations.integration_page.yaml_entry"
)
: this.hass.localize(
"ui.panel.config.integrations.integration_page.no_entries"
)}
</div>`
: nothing}
<ha-list-new>
Expand Down Expand Up @@ -683,7 +724,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {

const configPanel = this._configPanel(item.domain, this.hass.panels);

return html` <ha-list-item-new
return html`<ha-list-item-new
class=${classMap({
config_entry: true,
"state-not-loaded": item!.state === "not_loaded",
Expand Down Expand Up @@ -1323,6 +1364,17 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
}

private async _addIntegration() {
if (!this._manifest?.config_flow) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.yaml_only_title"
),
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.yaml_only"
),
});
return;
}
if (this._manifest?.single_config_entry) {
const entries = this._domainConfigEntries(
this.domain,
Expand Down
Loading

0 comments on commit d96ddf9

Please sign in to comment.