Add analytics integration (#8695)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
This commit is contained in:
Joakim Sørensen 2021-03-29 09:47:04 +02:00 committed by GitHub
parent 62f685bac2
commit 742028b691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 555 additions and 24 deletions

View File

@ -8,6 +8,7 @@ import {
property,
TemplateResult,
} from "lit-element";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
@ -150,30 +151,32 @@ class HassioSupervisorInfo extends LitElement {
</ha-settings-row>
${this.supervisor.supervisor.supported
? html` <ha-settings-row three-line>
<span slot="heading">
${this.supervisor.localize(
"system.supervisor.share_diagnostics"
)}
</span>
<div slot="description" class="diagnostics-description">
${this.supervisor.localize(
"system.supervisor.share_diagnostics_description"
)}
<button
class="link"
.title=${this.supervisor.localize("common.show_more")}
@click=${this._diagnosticsInformationDialog}
>
${this.supervisor.localize("common.learn_more")}
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisor.supervisor.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
? !atLeastVersion(this.hass.config.version, 2021, 4)
? html` <ha-settings-row three-line>
<span slot="heading">
${this.supervisor.localize(
"system.supervisor.share_diagnostics"
)}
</span>
<div slot="description" class="diagnostics-description">
${this.supervisor.localize(
"system.supervisor.share_diagnostics_description"
)}
<button
class="link"
.title=${this.supervisor.localize("common.show_more")}
@click=${this._diagnosticsInformationDialog}
>
${this.supervisor.localize("common.learn_more")}
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisor.supervisor.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
: ""
: html`<div class="error">
${this.supervisor.localize(
"system.supervisor.unsupported_title"

View File

@ -0,0 +1,188 @@
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event";
import { Analytics, AnalyticsPreferences } from "../data/analytics";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
import { documentationUrl } from "../util/documentation-url";
import "./ha-checkbox";
import type { HaCheckbox } from "./ha-checkbox";
import "./ha-settings-row";
const ADDITIONAL_PREFERENCES = ["usage", "statistics"];
declare global {
interface HASSDomEvents {
"analytics-preferences-changed": { preferences: AnalyticsPreferences };
}
}
@customElement("ha-analytics")
export class HaAnalytics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public analytics!: Analytics;
protected render(): TemplateResult {
if (!this.analytics.huuid) {
return html``;
}
const enabled = this.analytics.preferences.base;
return html`
<p>
${this.hass.localize(
"ui.panel.config.core.section.core.analytics.instance_id",
"huuid",
this.analytics.huuid
)}
</p>
<ha-settings-row>
<span slot="prefix">
<ha-checkbox
@change=${this._handleRowCheckboxClick}
.checked=${enabled}
.preference=${"base"}
>
</ha-checkbox>
</span>
<span slot="heading">
${this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.base.title`
)}
</span>
<span slot="description">
${this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.base.description`
)}
</span>
</ha-settings-row>
${ADDITIONAL_PREFERENCES.map(
(preference) =>
html`<ha-settings-row>
<span slot="prefix">
<ha-checkbox
@change=${this._handleRowCheckboxClick}
.checked=${this.analytics.preferences[preference]}
.preference=${preference}
.disabled=${!enabled}
>
</ha-checkbox>
${!enabled
? html`<paper-tooltip animation-delay="0" position="right"
>${this.hass.localize(
"ui.panel.config.core.section.core.analytics.needs_base"
)}
</paper-tooltip>`
: ""}
</span>
<span slot="heading">
${preference === "usage"
? isComponentLoaded(this.hass, "hassio")
? this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.usage_supervisor.title`
)
: this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.usage.title`
)
: this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.${preference}.title`
)}
</span>
<span slot="description">
${preference === "usage"
? isComponentLoaded(this.hass, "hassio")
? this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.usage_supervisor.description`
)
: this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.usage.description`
)
: this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.${preference}.description`
)}
</span>
</ha-settings-row>`
)}
<ha-settings-row>
<span slot="prefix">
<ha-checkbox
@change=${this._handleRowCheckboxClick}
.checked=${this.analytics.preferences.diagnostics}
.preference=${"diagnostics"}
>
</ha-checkbox>
</span>
<span slot="heading">
${this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.diagnostics.title`
)}
</span>
<span slot="description">
${this.hass.localize(
`ui.panel.config.core.section.core.analytics.preference.diagnostics.description`
)}
</span>
</ha-settings-row>
<p>
${this.hass.localize(
"ui.panel.config.core.section.core.analytics.documentation",
"link",
html`<a
.href=${documentationUrl(this.hass, "/integrations/analytics/")}
>
${documentationUrl(this.hass, "/integrations/analytics/")}
</a>`
)}
</p>
`;
}
private _handleRowCheckboxClick(ev: Event) {
const checkbox = ev.currentTarget as HaCheckbox;
const preference = (checkbox as any).preference;
const preferences = { ...this.analytics.preferences };
if (checkbox.checked) {
if (preferences[preference]) {
return;
}
preferences[preference] = true;
} else {
preferences[preference] = false;
}
fireEvent(this, "analytics-preferences-changed", { preferences });
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.error {
color: var(--error-color);
}
ha-settings-row {
padding: 0;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-analytics": HaAnalytics;
}
}

27
src/data/analytics.ts Normal file
View File

@ -0,0 +1,27 @@
import { HomeAssistant } from "../types";
export interface AnalyticsPreferences {
base?: boolean;
diagnostics?: boolean;
usage?: boolean;
statistics?: boolean;
}
export interface Analytics {
preferences: AnalyticsPreferences;
huuid: string;
}
export const getAnalyticsDetails = (hass: HomeAssistant) =>
hass.callWS<Analytics>({
type: "analytics",
});
export const setAnalyticsPreferences = (
hass: HomeAssistant,
preferences: AnalyticsPreferences
) =>
hass.callWS<AnalyticsPreferences>({
type: "analytics/preferences",
preferences,
});

View File

@ -12,10 +12,14 @@ export interface OnboardingIntegrationStepResponse {
auth_code: string;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface OnboardingAnalyticsStepResponse {}
export interface OnboardingResponses {
user: OnboardingUserStepResponse;
core_config: OnboardingCoreConfigStepResponse;
integration: OnboardingIntegrationStepResponse;
analytics: OnboardingAnalyticsStepResponse;
}
export type ValidOnboardingStep = keyof OnboardingResponses;
@ -49,6 +53,9 @@ export const onboardCoreConfigStep = (hass: HomeAssistant) =>
"onboarding/core_config"
);
export const onboardAnalyticsStep = (hass: HomeAssistant) =>
hass.callApi<OnboardingAnalyticsStepResponse>("POST", "onboarding/analytics");
export const onboardIntegrationStep = (
hass: HomeAssistant,
params: { client_id: string; redirect_uri: string }

View File

@ -31,6 +31,7 @@ import { HomeAssistant } from "../types";
import { registerServiceWorker } from "../util/register-service-worker";
import "./onboarding-create-user";
import "./onboarding-loading";
import "./onboarding-analytics";
type OnboardingEvent =
| {
@ -43,6 +44,9 @@ type OnboardingEvent =
}
| {
type: "integration";
}
| {
type: "analytics";
};
declare global {
@ -102,6 +106,15 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
></onboarding-core-config>
`;
}
if (step.step === "analytics") {
return html`
<onboarding-analytics
.hass=${this.hass}
.localize=${this.localize}
></onboarding-analytics>
`;
}
if (step.step === "integration") {
return html`
<onboarding-integrations

View File

@ -0,0 +1,124 @@
import "@material/mwc-button/mwc-button";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-analytics";
import {
Analytics,
getAnalyticsDetails,
setAnalyticsPreferences,
} from "../data/analytics";
import { onboardAnalyticsStep } from "../data/onboarding";
import type { HomeAssistant } from "../types";
@customElement("onboarding-analytics")
class OnboardingAnalytics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize!: LocalizeFunc;
@internalProperty() private _error?: string;
@internalProperty() private _analyticsDetails?: Analytics;
protected render(): TemplateResult {
if (!this._analyticsDetails?.huuid) {
return html``;
}
return html`
<p>
${this.localize(
"ui.panel.page-onboarding.analytics.intro",
"link",
html`<a href="https://analytics.home-assistant.io" target="_blank"
>https://analytics.home-assistant.io</a
>`
)}
</p>
<ha-analytics
@analytics-preferences-changed=${this._preferencesChanged}
.hass=${this.hass}
.analytics=${this._analyticsDetails}
>
</ha-analytics>
${this._error ? html`<div class="error">${this._error}</div>` : ""}
<div class="footer">
<mwc-button @click=${this._save}>
${this.localize("ui.panel.page-onboarding.analytics.finish")}
</mwc-button>
</div>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("keypress", (ev) => {
if (ev.keyCode === 13) {
this._save(ev);
}
});
this._load();
}
private _preferencesChanged(event: CustomEvent): void {
this._analyticsDetails = {
...this._analyticsDetails!,
preferences: event.detail.preferences,
};
}
private async _save(ev) {
ev.preventDefault();
try {
await setAnalyticsPreferences(
this.hass,
this._analyticsDetails!.preferences
);
await onboardAnalyticsStep(this.hass);
fireEvent(this, "onboarding-step", {
type: "analytics",
});
} catch (err) {
alert(`Failed to save: ${err.message}`);
}
}
private async _load() {
this._error = undefined;
try {
this._analyticsDetails = await getAnalyticsDetails(this.hass);
} catch (err) {
this._error = err.message || err;
}
}
static get styles(): CSSResult {
return css`
.error {
color: var(--error-color);
}
.footer {
margin-top: 16px;
text-align: right;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"onboarding-analytics": OnboardingAnalytics;
}
}

View File

@ -0,0 +1,132 @@
import "@material/mwc-button/mwc-button";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-analytics";
import "../../../components/ha-card";
import "../../../components/ha-checkbox";
import "../../../components/ha-settings-row";
import {
Analytics,
getAnalyticsDetails,
setAnalyticsPreferences,
} from "../../../data/analytics";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
@customElement("ha-config-analytics")
class ConfigAnalytics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _analyticsDetails?: Analytics;
@internalProperty() private _error?: string;
protected render(): TemplateResult {
if (
!isComponentLoaded(this.hass, "analytics") ||
!this.hass.user?.is_owner ||
!this._analyticsDetails?.huuid
) {
return html``;
}
return html`
<ha-card
.header=${this.hass.localize(
"ui.panel.config.core.section.core.analytics.header"
)}
>
<div class="card-content">
${this._error ? html`<div class="error">${this._error}</div>` : ""}
<p>
${this.hass.localize(
"ui.panel.config.core.section.core.analytics.introduction",
"link",
html`<a href="https://analytics.home-assistant.io" target="_blank"
>https://analytics.home-assistant.io</a
>`
)}
</p>
<ha-analytics
@analytics-preferences-changed=${this._preferencesChanged}
.hass=${this.hass}
.analytics=${this._analyticsDetails}
></ha-analytics>
</div>
<div class="card-actions">
<mwc-button @click=${this._save}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.save_button"
)}
</mwc-button>
</div>
</ha-card>
`;
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
if (isComponentLoaded(this.hass, "analytics")) {
this._load();
}
}
private async _load() {
this._error = undefined;
try {
this._analyticsDetails = await getAnalyticsDetails(this.hass);
} catch (err) {
this._error = err.message || err;
}
}
private async _save() {
this._error = undefined;
try {
await setAnalyticsPreferences(
this.hass,
this._analyticsDetails?.preferences || {}
);
} catch (err) {
this._error = err.message || err;
}
}
private _preferencesChanged(event: CustomEvent): void {
this._analyticsDetails = {
...this._analyticsDetails!,
preferences: event.detail.preferences,
};
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.error {
color: var(--error-color);
}
ha-settings-row {
padding: 0;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-analytics": ConfigAnalytics;
}
}

View File

@ -8,6 +8,7 @@ import "../../../components/ha-card";
import LocalizeMixin from "../../../mixins/localize-mixin";
import "../../../styles/polymer-ha-style";
import "../ha-config-section";
import "./ha-config-analytics";
import "./ha-config-core-form";
import "./ha-config-name-form";
import "./ha-config-url-form";
@ -29,6 +30,7 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
<ha-config-url-form hass="[[hass]]"></ha-config-url-form>
<ha-config-analytics hass="[[hass]]"></ha-config-analytics>
</ha-config-section>
`;
}

View File

@ -977,6 +977,35 @@
"save_button": "Save",
"external_url": "External URL",
"internal_url": "Internal URL"
},
"analytics": {
"header": "Analytics",
"introduction": "Share analytics from your instance. This data will be publiclly available at {link}",
"instance_id": "Instance ID: {huuid}",
"needs_base": "You need to enable base analytics for this option to be available",
"preference": {
"base": {
"title": "Basic analytics",
"description": "This includes the instance ID, the version and the installation type"
},
"diagnostics": {
"title": "Diagnostics",
"description": "Share crash reports and diagnostic information"
},
"usage": {
"title": "Used integrations",
"description": "This includes the names of your integrations"
},
"usage_supervisor": {
"title": "Used integrations and add-ons",
"description": "This includes the names and capabilities of your integrations and add-ons"
},
"statistics": {
"title": "Usage statistics",
"description": "This includes a count of elements in your installation, for a full list look at the documentation"
}
},
"documentation": "Before you enable this make sure you visit the analytics documentation page {link} to understand what you are sending and how it's stored."
}
}
}
@ -3420,6 +3449,8 @@
},
"page-onboarding": {
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
"next": "Next",
"finish": "Finish",
"user": {
"intro": "Let's get started by creating a user account.",
"required_field": "Required",
@ -3449,6 +3480,10 @@
"more_integrations": "More",
"finish": "Finish"
},
"analytics": {
"intro": "Share analytics from your instance. This data will be publiclly available at {link}",
"finish": "Next"
},
"restore": {
"description": "Alternatively you can restore from a previous snapshot.",
"in_progress": "Restore in progress",