Add trace details foundation (#8716)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paulus Schoutsen 2021-03-25 11:58:29 -07:00 committed by GitHub
parent ee38c419de
commit 40cf4c8d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 10 deletions

View File

@ -12,9 +12,11 @@ import { buttonLinkStyle } from "../../resources/styles";
import "../ha-svg-icon"; import "../ha-svg-icon";
@customElement("ha-timeline") @customElement("ha-timeline")
class HaTimeline extends LitElement { export class HaTimeline extends LitElement {
@property({ type: Boolean, reflect: true }) public label = false; @property({ type: Boolean, reflect: true }) public label = false;
@property({ type: Boolean, reflect: true }) public raised = false;
@property({ type: Boolean }) public lastItem = false; @property({ type: Boolean }) public lastItem = false;
@property({ type: String }) public icon?: string; @property({ type: String }) public icon?: string;
@ -86,6 +88,10 @@ class HaTimeline extends LitElement {
--timeline-ball-color, --timeline-ball-color,
var(--timeline-color, var(--secondary-text-color)) var(--timeline-color, var(--secondary-text-color))
); );
border-radius: 50%;
}
:host([raised]) ha-svg-icon {
transform: scale(1.3);
} }
.line { .line {
flex: 1; flex: 1;

View File

@ -3,6 +3,7 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
@ -15,6 +16,7 @@ import {
} from "../../data/trace"; } from "../../data/trace";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "./ha-timeline"; import "./ha-timeline";
import type { HaTimeline } from "./ha-timeline";
import { import {
mdiCheckCircleOutline, mdiCheckCircleOutline,
mdiCircle, mdiCircle,
@ -30,6 +32,7 @@ import {
getActionType, getActionType,
} from "../../data/script"; } from "../../data/script";
import relativeTime from "../../common/datetime/relative_time"; import relativeTime from "../../common/datetime/relative_time";
import { fireEvent } from "../../common/dom/fire_event";
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2; const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
@ -242,7 +245,7 @@ class ActionRenderer {
const isTopLevel = path.split("/").length === 2; const isTopLevel = path.split("/").length === 2;
if (!isTopLevel && !actionType) { if (!isTopLevel && !actionType) {
this._renderEntry(path.replace(/\//g, " ")); this._renderEntry(path, path.replace(/\//g, " "));
return index + 1; return index + 1;
} }
@ -254,7 +257,7 @@ class ActionRenderer {
return this._handleChoose(index); return this._handleChoose(index);
} }
this._renderEntry(data.alias || actionType); this._renderEntry(path, data.alias || actionType);
return index + 1; return index + 1;
} }
@ -273,7 +276,8 @@ class ActionRenderer {
// +3: 'sequence' // +3: 'sequence'
// +4: executed sequence // +4: executed sequence
const startLevel = this.keys[index].split("/").length - 1; const choosePath = this.keys[index];
const startLevel = choosePath.split("/").length - 1;
const chooseTrace = this._getItem(index)[0] as ChooseActionTrace; const chooseTrace = this._getItem(index)[0] as ChooseActionTrace;
const defaultExecuted = chooseTrace.result.choice === "default"; const defaultExecuted = chooseTrace.result.choice === "default";
@ -283,14 +287,14 @@ class ActionRenderer {
const name = chooseConfig.alias || "Choose"; const name = chooseConfig.alias || "Choose";
if (defaultExecuted) { if (defaultExecuted) {
this._renderEntry(`${name}: Default action executed`); this._renderEntry(choosePath, `${name}: Default action executed`);
} else { } else {
const choiceConfig = this._getDataFromPath( const choiceConfig = this._getDataFromPath(
`${this.keys[index]}/choose/${chooseTrace.result.choice}` `${this.keys[index]}/choose/${chooseTrace.result.choice}`
) as ChooseActionChoice; ) as ChooseActionChoice;
const choiceName = const choiceName =
choiceConfig.alias || `Choice ${chooseTrace.result.choice}`; choiceConfig.alias || `Choice ${chooseTrace.result.choice}`;
this._renderEntry(`${name}: ${choiceName} executed`); this._renderEntry(choosePath, `${name}: ${choiceName} executed`);
} }
let i; let i;
@ -328,9 +332,9 @@ class ActionRenderer {
return i; return i;
} }
private _renderEntry(description: string) { private _renderEntry(path: string, description: string) {
this.entries.push(html` this.entries.push(html`
<ha-timeline .icon=${mdiRecordCircleOutline}> <ha-timeline .icon=${mdiRecordCircleOutline} data-path=${path}>
${description} ${description}
</ha-timeline> </ha-timeline>
`); `);
@ -345,9 +349,11 @@ class ActionRenderer {
export class HaAutomationTracer extends LitElement { export class HaAutomationTracer extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) private trace?: AutomationTraceExtended; @property({ attribute: false }) public trace?: AutomationTraceExtended;
@property({ attribute: false }) private logbookEntries?: LogbookEntry[]; @property({ attribute: false }) public logbookEntries?: LogbookEntry[];
@internalProperty() private _selectedPath?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.trace) { if (!this.trace) {
@ -374,6 +380,7 @@ export class HaAutomationTracer extends LitElement {
.icon=${value[0].result.result .icon=${value[0].result.result
? mdiCheckCircleOutline ? mdiCheckCircleOutline
: mdiStopCircleOutline} : mdiStopCircleOutline}
data-path=${path}
> >
${getDataFromPath(this.trace!.config, path).alias || ${getDataFromPath(this.trace!.config, path).alias ||
pathToName(path)} pathToName(path)}
@ -442,12 +449,53 @@ export class HaAutomationTracer extends LitElement {
return html`${entries}`; return html`${entries}`;
} }
protected updated(props) {
super.updated(props);
// Pick first path when we load a new trace.
if (props.has("trace")) {
const element = this.shadowRoot!.querySelector<HaTimeline>(
"ha-timeline[data-path]"
);
if (element) {
fireEvent(this, "value-changed", { value: element.dataset.path });
this._selectedPath = element.dataset.path;
}
}
this.shadowRoot!.querySelectorAll<HaTimeline>(
"ha-timeline[data-path]"
).forEach((el) => {
el.style.setProperty(
"--timeline-ball-color",
this._selectedPath === el.dataset.path ? "var(--primary-color)" : null
);
if (el.dataset.upgraded) {
return;
}
el.dataset.upgraded = "1";
el.addEventListener("click", () => {
this._selectedPath = el.dataset.path;
fireEvent(this, "value-changed", { value: el.dataset.path });
});
el.addEventListener("mouseover", () => {
el.raised = true;
});
el.addEventListener("mouseout", () => {
el.raised = false;
});
});
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
css` css`
ha-timeline[lastItem].condition { ha-timeline[lastItem].condition {
--timeline-ball-color: var(--error-color); --timeline-ball-color: var(--error-color);
} }
ha-timeline[data-path] {
cursor: pointer;
}
`, `,
]; ];
} }

View File

@ -1,3 +1,4 @@
import { safeDump } from "js-yaml";
import { import {
css, css,
CSSResult, CSSResult,
@ -12,6 +13,7 @@ import { AutomationEntity } from "../../../../data/automation";
import { import {
AutomationTrace, AutomationTrace,
AutomationTraceExtended, AutomationTraceExtended,
getDataFromPath,
loadTrace, loadTrace,
loadTraces, loadTraces,
} from "../../../../data/trace"; } from "../../../../data/trace";
@ -48,6 +50,8 @@ export class HaAutomationTrace extends LitElement {
@internalProperty() private _runId?: string; @internalProperty() private _runId?: string;
@internalProperty() private _path?: string;
@internalProperty() private _trace?: AutomationTraceExtended; @internalProperty() private _trace?: AutomationTraceExtended;
@internalProperty() private _logbookEntries?: LogbookEntry[]; @internalProperty() private _logbookEntries?: LogbookEntry[];
@ -107,10 +111,31 @@ export class HaAutomationTrace extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.trace=${this._trace} .trace=${this._trace}
.logbookEntries=${this._logbookEntries} .logbookEntries=${this._logbookEntries}
@value-changed=${this._pickPath}
></hat-trace> ></hat-trace>
`} `}
</div> </div>
</ha-card> </ha-card>
${!this._path || !this._trace
? ""
: html`
<div class="details">
<ha-card header="Config">
<pre class="config card-content">
${safeDump(getDataFromPath(this._trace.config, this._path))}</pre
>
</ha-card>
<ha-card header="Trace">
<pre class="trace card-content">
${safeDump(
(this._path.split("/")[0] === "condition"
? this._trace.condition_trace
: this._trace.action_trace)[this._path]
)}</pre
>
</ha-card>
</div>
`}
</hass-tabs-subpage> </hass-tabs-subpage>
`; `;
} }
@ -162,6 +187,11 @@ export class HaAutomationTrace extends LitElement {
private _pickTrace(ev) { private _pickTrace(ev) {
this._runId = ev.target.value; this._runId = ev.target.value;
this._path = undefined;
}
private _pickPath(ev) {
this._path = ev.detail.value;
} }
private async _loadTraces(runId?: string) { private async _loadTraces(runId?: string) {
@ -179,6 +209,7 @@ export class HaAutomationTrace extends LitElement {
!this._traces.some((trace) => trace.run_id === this._runId) !this._traces.some((trace) => trace.run_id === this._runId)
) { ) {
this._runId = undefined; this._runId = undefined;
this._path = undefined;
// If we came here from a trace passed into the url, clear it. // If we came here from a trace passed into the url, clear it.
if (runId) { if (runId) {
@ -254,6 +285,17 @@ export class HaAutomationTrace extends LitElement {
top: 8px; top: 8px;
right: 8px; right: 8px;
} }
.details {
display: flex;
margin: 0 16px;
}
.details > * {
flex: 1 1 0px;
}
.details > *:first-child {
margin-right: 16px;
}
`, `,
]; ];
} }