Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { LitElement, css, customElement, html, property } from "@umbraco-cms/backoffice/external/lit";
import { LitElement, css, customElement, html, property, type TemplateResult } from "@umbraco-cms/backoffice/external/lit";
import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
import type { AnalyticsBreakdown, AnalyticsDimension, AnalyticsEventsReport, AnalyticsFlagsReport } from "../api/types.gen.js";
import { breakdownMetricTotal, topBreakdownRows } from "./breakdown-rows.js";
import { selectedCardDimension, type AcquisitionView, type DashboardCard } from "./dashboard-cards.js";
import type { AnalyticsFilter, AudienceDimension, DashboardMetric, UtmDimension } from "./dashboard-url-state.js";
import type { AnalyticsEventFilter, AnalyticsFilter, AnalyticsFlagFilter, AudienceDimension, DashboardMetric, UtmDimension } from "./dashboard-url-state.js";
import { topEventRows } from "./event-rows.js";
import "./breakdown-table.element.js";
import "./event-table.element.js";
import "./flag-card.element.js";
import "./flag-table.element.js";
import { isInitialLoading, stateData, type AsyncState } from "./async-state.js";
import type { ReportTabGroup } from "./report-tabs.js";

Expand All @@ -18,8 +18,9 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
@property({ attribute: false }) breakdowns: Partial<Record<AnalyticsDimension, AsyncState<AnalyticsBreakdown>>> = {};
@property({ attribute: false }) events: AsyncState<AnalyticsEventsReport> = { status: "loading" };
@property({ attribute: false }) flags: AsyncState<AnalyticsFlagsReport> = { status: "loading" };
@property({ attribute: false }) selectedFlag?: AsyncState<AnalyticsFlagsReport>;
@property({ attribute: false }) filters: AnalyticsFilter[] = [];
@property({ attribute: false }) eventFilter?: AnalyticsEventFilter;
@property({ attribute: false }) flagFilter?: AnalyticsFlagFilter;
@property() metric: DashboardMetric = "visitors";
@property() audienceDimension: AudienceDimension = "DeviceType";
@property() acquisitionView: AcquisitionView = "referrers";
Expand Down Expand Up @@ -66,20 +67,49 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
};
}

#renderCardFrame({
busy,
content,
layout = "report",
wide = false,
hint,
footer,
}: {
busy: boolean;
content: TemplateResult;
layout?: "report" | "empty";
wide?: boolean;
hint?: TemplateResult;
footer: {
visible: boolean;
content?: TemplateResult;
};
}) {
return html`
<uui-box class=${`breakdown-card${wide ? " wide" : ""}`} aria-busy=${busy ? "true" : "false"}>
<div class=${`breakdown-card-layout${layout === "empty" ? " empty-card-layout" : ""}`}>
${content}
${hint}
${footer.visible ? html`<footer class="breakdown-footer">${footer.content}</footer>` : ""}
</div>
</uui-box>
`;
}

#renderBreakdownFooter(
loading: boolean,
unavailable: string | undefined,
hasRows: boolean,
selected: ReturnType<typeof selectedCardDimension>,
) {
): TemplateResult | undefined {
if (!loading && hasRows && !unavailable) {
return html`
<uui-button class="view-all" compact look="default" label=${`View all ${selected.headline}`} @click=${() => this.#dispatch("view-breakdown", selected)}>View all</uui-button>
`;
}
return !loading && unavailable
? html`<uui-button look="secondary" label=${`Retry ${selected.headline} report`} @click=${() => this.#dispatch("retry-reports")}>Retry</uui-button>`
: "";
: undefined;
}

#renderCard(card: DashboardCard) {
Expand All @@ -93,10 +123,10 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
const planLimited = card.kind === "tabbed-breakdown" && card.planLimited;
const linkValues = selected.dimension === "RequestPath" || selected.dimension === "Route";
const headingTabs = card.kind === "tabbed-breakdown" ? this.#tabsForCard(card) : undefined;
return html`
<uui-box class=${`breakdown-card ${card.span === "wide" ? "wide" : ""}`} aria-busy=${state?.status === "loading" ? "true" : "false"}>
<div class="breakdown-card-layout">
<web-analytics-breakdown-table
return this.#renderCardFrame({
busy: state?.status === "loading",
wide: card.span === "wide",
content: html`<web-analytics-breakdown-table
.headline=${selected.headline}
.dimension=${selected.dimension}
.metric=${this.metric}
Expand All @@ -110,14 +140,13 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
.headingTabs=${headingTabs}
@heading-tab-change=${(event: CustomEvent<{ value: AnalyticsDimension }>) => card.kind === "tabbed-breakdown" && this.#dispatch(card.id === "audience" ? "audience-change" : "utm-change", { dimension: event.detail.value })}
.unavailable=${unavailable}>
</web-analytics-breakdown-table>
${planLimited && unavailable ? html`<p class="hint breakdown-hint">UTM reporting availability depends on your analytics plan and reporting window.</p>` : ""}
<footer class="breakdown-footer">
${this.#renderBreakdownFooter(loading, unavailable, rows.length > 0, selected)}
</footer>
</div>
</uui-box>
`;
</web-analytics-breakdown-table>`,
hint: planLimited && unavailable ? html`<p class="hint breakdown-hint">UTM reporting availability depends on your analytics plan and reporting window.</p>` : undefined,
footer: {
visible: true,
content: this.#renderBreakdownFooter(loading, unavailable, rows.length > 0, selected),
},
});
}

#renderAcquisitionCard(referrerCard: DashboardCard, utmCard?: Extract<DashboardCard, { kind: "tabbed-breakdown" }>) {
Expand All @@ -132,10 +161,10 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
const rows = topBreakdownRows(allRows, 10);
const total = breakdownMetricTotal(allRows, this.metric);
const unavailable = report?.status === "error" ? report.message : undefined;
return html`
<uui-box class="breakdown-card wide" aria-busy=${report?.status === "loading" ? "true" : "false"}>
<div class="breakdown-card-layout">
<web-analytics-breakdown-table
return this.#renderCardFrame({
busy: report?.status === "loading",
wide: true,
content: html`<web-analytics-breakdown-table
.headline=${selected.headline}
.dimension=${selected.dimension}
.metric=${this.metric}
Expand All @@ -150,28 +179,58 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
.unavailable=${unavailable}
@heading-tab-change=${(event: CustomEvent<{ value: AcquisitionView }>) => this.#dispatch("acquisition-change", { view: event.detail.value })}
@subheading-tab-change=${(event: CustomEvent<{ value: UtmDimension }>) => this.#dispatch("utm-change", { dimension: event.detail.value })}></web-analytics-breakdown-table>
<footer class="breakdown-footer">
${this.#renderBreakdownFooter(loading, unavailable, rows.length > 0, selected)}
</footer>
</div>
</uui-box>
`;
`,
footer: {
visible: true,
content: this.#renderBreakdownFooter(loading, unavailable, rows.length > 0, selected),
},
});
}

#renderEvents() {
const loading = isInitialLoading(this.events);
const rows = topEventRows(stateData(this.events)?.rows ?? [], 10);
const allRows = stateData(this.events)?.rows ?? [];
const rows = this.eventFilter
? allRows.filter(({ eventName }) => eventName === this.eventFilter?.eventName)
: topEventRows(allRows, 10);
const empty = !loading && rows.length === 0;
return html`
<uui-box class="breakdown-card feature-card" aria-busy=${this.events.status === "loading" ? "true" : "false"}>
<div class=${`breakdown-card-layout${empty ? " empty-card-layout" : ""}`}>
<web-analytics-event-table .rows=${rows} .filters=${this.filters} .loading=${loading} .detailsEnabled=${this.supportsEventDetails} .filteringEnabled=${this.supportsGlobalEventFiltering}></web-analytics-event-table>
${empty ? "" : html`<footer class="breakdown-footer">
${!loading && rows.length ? html`<uui-button class="view-all" compact look="default" label="View all events" @click=${() => this.#dispatch("view-events")}>View all</uui-button>` : ""}
</footer>`}
</div>
</uui-box>
`;
return this.#renderCardFrame({
busy: this.events.status === "loading",
layout: empty ? "empty" : "report",
content: html`<web-analytics-event-table .rows=${rows} .filters=${this.filters} .eventFilter=${this.eventFilter} .loading=${loading} .detailsEnabled=${this.supportsEventDetails} .filteringEnabled=${this.supportsGlobalEventFiltering}></web-analytics-event-table>`,
footer: {
visible: !empty,
content: !loading && rows.length
? html`<uui-button class="view-all" compact look="default" label="View all events" @click=${() => this.#dispatch("view-events")}>View all</uui-button>`
: undefined,
},
});
}

#renderFlags() {
const loading = isInitialLoading(this.flags);
const allRows = stateData(this.flags)?.rows ?? [];
const rows = this.flagFilter
? allRows.filter(({ value }) => value === this.flagFilter?.flagKey)
: allRows;
const empty = !loading && rows.length === 0;
return this.#renderCardFrame({
busy: this.flags.status === "loading",
layout: empty ? "empty" : "report",
content: html`
<web-analytics-flag-table
.rows=${rows}
.loading=${loading}
.flagFilter=${this.flagFilter}
mode="overview"></web-analytics-flag-table>
`,
footer: {
visible: !empty,
content: !loading && rows.length
? html`<uui-button class="view-all" compact look="default" label="View all flags" @click=${() => this.#dispatch("view-flags")}>View all</uui-button>`
: undefined,
},
});
}

render() {
Expand All @@ -190,9 +249,7 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
</section>
${this.supportsEvents || this.supportsFlags ? html`<section class="grid feature-grid" aria-label="Optional analytics reports">
${this.supportsEvents ? this.#renderEvents() : ""}
${this.supportsFlags ? html`<uui-box class="breakdown-card flags-card feature-card" aria-busy=${this.flags.status === "loading" ? "true" : "false"}>
<web-analytics-flag-card .report=${this.flags} .selected=${this.selectedFlag}></web-analytics-flag-card>
</uui-box>` : ""}
${this.supportsFlags ? this.#renderFlags() : ""}
</section>` : ""}
`;
}
Expand All @@ -204,7 +261,6 @@ export class WebAnalyticsBreakdownGridElement extends UmbElementMixin(LitElement
.feature-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 28rem), 1fr)); }
.detail-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); }
.breakdown-card { --uui-box-default-padding: 0; min-inline-size: 0; overflow: hidden; position: relative; }
.flags-card { --uui-box-default-padding: 0; }
.breakdown-card-layout { box-sizing: border-box; min-block-size: 100%; padding-bottom: var(--uui-size-layout-3); }
.empty-card-layout { block-size: 100%; padding-bottom: 0; }
.breakdown-footer { align-items: center; background: color-mix(in srgb, var(--uui-color-surface-alt) 9%, var(--uui-color-surface)); border-top: 1px solid var(--uui-color-border); bottom: 0; box-sizing: border-box; display: flex; justify-content: flex-end; left: 0; min-block-size: var(--uui-size-layout-3); padding: 0 var(--uui-size-space-4); position: absolute; right: 0; }
Expand Down
Loading