Use manfiests to render doc urls (#5549)

* Use manfiests to render doc urls

* Update UI
This commit is contained in:
Paulus Schoutsen 2020-04-15 13:36:25 -07:00 committed by GitHub
parent ff81536463
commit 66f33ad497
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 143 additions and 50 deletions

View File

@ -1,10 +1,32 @@
import { LocalizeFunc } from "../common/translations/localize"; import { LocalizeFunc } from "../common/translations/localize";
import { HomeAssistant } from "../types";
export const integrationDocsUrl = (domain: string) => export interface IntegrationManifest {
`https://www.home-assistant.io/integrations/${domain}`; is_built_in: boolean;
domain: string;
name: string;
config_flow: boolean;
documentation: string;
dependencies?: string[];
after_dependencies?: string[];
codeowners?: string[];
requirements?: string[];
ssdp?: Array<{ manufacturer?: string; modelName?: string; st?: string }>;
zeroconf?: string[];
homekit?: { models: string[] };
quality_scale?: string;
}
export const integrationIssuesUrl = (domain: string) => export const integrationIssuesUrl = (domain: string) =>
`https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`; `https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`;
export const domainToName = (localize: LocalizeFunc, domain: string) => export const domainToName = (localize: LocalizeFunc, domain: string) =>
localize(`domain.${domain}`) || domain; localize(`domain.${domain}`) || domain;
export const fetchIntegrationManifests = (hass: HomeAssistant) =>
hass.callWS<IntegrationManifest[]>({ type: "manifest/list" });
export const fetchIntegrationManifest = (
hass: HomeAssistant,
integration: string
) => hass.callWS<IntegrationManifest>({ type: "manifest/get", integration });

View File

@ -19,4 +19,6 @@ export const fetchSystemLog = (hass: HomeAssistant) =>
export const getLoggedErrorIntegration = (item: LoggedError) => export const getLoggedErrorIntegration = (item: LoggedError) =>
item.name.startsWith("homeassistant.components.") item.name.startsWith("homeassistant.components.")
? item.name.split(".")[2] ? item.name.split(".")[2]
: item.name.startsWith("custom_components.")
? item.name.split(".")[1]
: undefined; : undefined;

View File

@ -9,8 +9,9 @@ import {
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { import {
integrationDocsUrl,
integrationIssuesUrl, integrationIssuesUrl,
IntegrationManifest,
fetchIntegrationManifests,
} from "../../../data/integration"; } from "../../../data/integration";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
@ -18,46 +19,67 @@ import { HomeAssistant } from "../../../types";
class IntegrationsCard extends LitElement { class IntegrationsCard extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() private _manifests?: { [domain: string]: IntegrationManifest };
private _sortedIntegrations = memoizeOne((components: string[]) => { private _sortedIntegrations = memoizeOne((components: string[]) => {
return components.filter((comp) => !comp.includes(".")).sort(); return components.filter((comp) => !comp.includes(".")).sort();
}); });
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._fetchManifests();
}
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-card header="Integrations"> <ha-card header="Integrations">
<table class="card-content"> <table class="card-content">
<tbody> <tbody>
${this._sortedIntegrations(this.hass!.config.components).map( ${this._sortedIntegrations(this.hass!.config.components).map(
(domain) => html` (domain) => {
<tr> const manifest = this._manifests && this._manifests[domain];
<td> return html`
<img <tr>
loading="lazy" <td>
src="https://brands.home-assistant.io/_/${domain}/icon.png" <img
referrerpolicy="no-referrer" loading="lazy"
/> src="https://brands.home-assistant.io/_/${domain}/icon.png"
</td> referrerpolicy="no-referrer"
<td>${domain}</td> />
<td> </td>
<a <td class="name">
href=${integrationDocsUrl(domain)} ${manifest?.name}<br />
target="_blank" <span class="domain">${domain}</span>
rel="noreferrer" </td>
> ${!manifest
Documentation ? ""
</a> : html`
</td> <td>
<td> <a
<a href=${manifest.documentation}
href=${integrationIssuesUrl(domain)} target="_blank"
target="_blank" rel="noreferrer"
rel="noreferrer" >
> Documentation
Issues </a>
</a> </td>
</td> ${!manifest.is_built_in
</tr> ? ""
` : html`
<td>
<a
href=${integrationIssuesUrl(domain)}
target="_blank"
rel="noreferrer"
>
Issues
</a>
</td>
`}
`}
</tr>
`;
}
)} )}
</tbody> </tbody>
</table> </table>
@ -65,19 +87,32 @@ class IntegrationsCard extends LitElement {
`; `;
} }
private async _fetchManifests() {
const manifests = {};
for (const manifest of await fetchIntegrationManifests(this.hass)) {
manifests[manifest.domain] = manifest;
}
this._manifests = manifests;
}
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
td { td {
line-height: 2em;
padding: 0 8px; padding: 0 8px;
} }
td:first-child { td:first-child {
padding-left: 0; padding-left: 0;
} }
td.name {
padding: 8px;
}
.domain {
color: var(--secondary-text-color);
}
img { img {
display: block; display: block;
max-height: 24px; max-height: 40px;
max-width: 24px; max-width: 40px;
} }
a { a {
color: var(--primary-color); color: var(--primary-color);

View File

@ -10,8 +10,9 @@ import {
import "../../../components/dialog/ha-paper-dialog"; import "../../../components/dialog/ha-paper-dialog";
import { import {
domainToName, domainToName,
integrationDocsUrl,
integrationIssuesUrl, integrationIssuesUrl,
IntegrationManifest,
fetchIntegrationManifest,
} from "../../../data/integration"; } from "../../../data/integration";
import { getLoggedErrorIntegration } from "../../../data/system_log"; import { getLoggedErrorIntegration } from "../../../data/system_log";
import { PolymerChangedEvent } from "../../../polymer-types"; import { PolymerChangedEvent } from "../../../polymer-types";
@ -25,11 +26,25 @@ class DialogSystemLogDetail extends LitElement {
@property() private _params?: SystemLogDetailDialogParams; @property() private _params?: SystemLogDetailDialogParams;
@property() private _manifest?: IntegrationManifest;
public async showDialog(params: SystemLogDetailDialogParams): Promise<void> { public async showDialog(params: SystemLogDetailDialogParams): Promise<void> {
this._params = params; this._params = params;
this._manifest = undefined;
await this.updateComplete; await this.updateComplete;
} }
protected updated(changedProps) {
super.updated(changedProps);
if (!changedProps.has("_params") || !this._params) {
return;
}
const integration = getLoggedErrorIntegration(this._params.item);
if (integration) {
this._fetchManifest(integration);
}
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._params) { if (!this._params) {
return html``; return html``;
@ -58,19 +73,30 @@ class DialogSystemLogDetail extends LitElement {
${integration ${integration
? html` ? html`
<br /> <br />
Integration: ${domainToName(this.hass.localize, integration)} Integration:
(<a ${this._manifest
href=${integrationDocsUrl(integration)} ? this._manifest.name
target="_blank" : domainToName(this.hass.localize, integration)}
rel="noreferrer" ${!this._manifest ||
>documentation</a // Can happen with custom integrations
>, !this._manifest.documentation
<a ? ""
href=${integrationIssuesUrl(integration)} : html`
target="_blank" (<a
rel="noreferrer" href=${this._manifest.documentation}
>issues</a target="_blank"
>) rel="noreferrer"
>documentation</a
>${!this._manifest.is_built_in
? ""
: html`,
<a
href=${integrationIssuesUrl(integration)}
target="_blank"
rel="noreferrer"
>issues</a
>`})
`}
` `
: ""} : ""}
<br /> <br />
@ -100,6 +126,14 @@ class DialogSystemLogDetail extends LitElement {
`; `;
} }
private async _fetchManifest(integration: string) {
try {
this._manifest = await fetchIntegrationManifest(this.hass, integration);
} catch (err) {
// Ignore if loading manifest fails. Probably bad JSON in manifest
}
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void { private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) { if (!(ev.detail as any).value) {
this._params = undefined; this._params = undefined;