diff --git a/src/data/calendar.ts b/src/data/calendar.ts index 819bbee25d..5e20205fee 100644 --- a/src/data/calendar.ts +++ b/src/data/calendar.ts @@ -56,13 +56,14 @@ export const fetchCalendarEvents = async ( start: Date, end: Date, calendars: Calendar[] -): Promise => { +): Promise<{ events: CalendarEvent[]; errors: string[] }> => { const params = encodeURI( `?start=${start.toISOString()}&end=${end.toISOString()}` ); const calEvents: CalendarEvent[] = []; - const promises: Promise[] = []; + const errors: string[] = []; + const promises: Promise[] = []; calendars.forEach((cal) => { promises.push( @@ -73,9 +74,15 @@ export const fetchCalendarEvents = async ( ); }); - const results = await Promise.all(promises); - - results.forEach((result, idx) => { + for (const [idx, promise] of promises.entries()) { + let result: CalendarEvent[]; + try { + // eslint-disable-next-line no-await-in-loop + result = await promise; + } catch (err) { + errors.push(calendars[idx].entity_id); + continue; + } const cal = calendars[idx]; result.forEach((ev) => { const eventStart = getCalendarDate(ev.start); @@ -104,9 +111,9 @@ export const fetchCalendarEvents = async ( calEvents.push(event); }); - }); + } - return calEvents; + return { events: calEvents, errors }; }; const getCalendarDate = (dateObj: any): string | undefined => { diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index 3cd139d303..90f6e3d4c4 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -97,6 +97,8 @@ export class HAFullCalendar extends LitElement { @property() public initialView: FullCalendarView = "dayGridMonth"; + @property({ attribute: false }) public error?: string = undefined; + private calendar?: Calendar; private _viewButtons?: ToggleButton[]; @@ -116,6 +118,14 @@ export class HAFullCalendar extends LitElement { return html` ${this.calendar ? html` + ${this.error + ? html`${this.error}` + : ""}
${!this.narrow ? html` @@ -380,6 +390,10 @@ export class HAFullCalendar extends LitElement { ); }); + private _clearError() { + this.error = undefined; + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -449,6 +463,11 @@ export class HAFullCalendar extends LitElement { z-index: 1; } + ha-alert { + display: block; + margin: 4px 0; + } + #calendar { flex-grow: 1; background-color: var( diff --git a/src/panels/calendar/ha-panel-calendar.ts b/src/panels/calendar/ha-panel-calendar.ts index 5dc7a28381..de3410f3a8 100644 --- a/src/panels/calendar/ha-panel-calendar.ts +++ b/src/panels/calendar/ha-panel-calendar.ts @@ -15,6 +15,7 @@ import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { LocalStorage } from "../../common/decorators/local-storage"; import { HASSDomEvent } from "../../common/dom/fire_event"; +import { computeStateName } from "../../common/entity/compute_state_name"; import "../../components/ha-card"; import "../../components/ha-icon-button"; import "../../components/ha-menu-button"; @@ -40,6 +41,8 @@ class PanelCalendar extends LitElement { @state() private _events: CalendarEvent[] = []; + @state() private _error?: string = undefined; + @LocalStorage("deSelectedCalendars", true) private _deSelectedCalendars: string[] = []; @@ -101,6 +104,7 @@ class PanelCalendar extends LitElement { .calendars=${this._calendars} .narrow=${this.narrow} .hass=${this.hass} + .error=${this._error} @view-changed=${this._handleViewChanged} >
@@ -118,9 +122,9 @@ class PanelCalendar extends LitElement { start: Date, end: Date, calendars: Calendar[] - ): Promise { + ): Promise<{ events: CalendarEvent[]; errors: string[] }> { if (!calendars.length) { - return []; + return { events: [], errors: [] }; } return fetchCalendarEvents(this.hass, start, end, calendars); @@ -135,8 +139,9 @@ class PanelCalendar extends LitElement { const checked = ev.target.checked; if (checked) { - const events = await this._fetchEvents(this._start!, this._end!, [cal]); - this._events = [...this._events, ...events]; + const result = await this._fetchEvents(this._start!, this._end!, [cal]); + this._events = [...this._events, ...result.events]; + this._handleErrors(result.errors); this._deSelectedCalendars = this._deSelectedCalendars.filter( (deCal) => deCal !== cal.entity_id ); @@ -161,19 +166,40 @@ class PanelCalendar extends LitElement { ): Promise { this._start = ev.detail.start; this._end = ev.detail.end; - this._events = await this._fetchEvents( + const result = await this._fetchEvents( this._start, this._end, this._selectedCalendars ); + this._events = result.events; + this._handleErrors(result.errors); } private async _handleRefresh(): Promise { - this._events = await this._fetchEvents( + const result = await this._fetchEvents( this._start!, this._end!, this._selectedCalendars ); + this._events = result.events; + this._handleErrors(result.errors); + } + + private _handleErrors(error_entity_ids: string[]) { + this._error = undefined; + if (error_entity_ids.length > 0) { + const nameList = error_entity_ids + .map((error_entity_id) => + this.hass!.states[error_entity_id] + ? computeStateName(this.hass!.states[error_entity_id]) + : error_entity_id + ) + .join(", "); + + this._error = `${this.hass!.localize( + "ui.components.calendar.event_retrieval_error" + )} ${nameList}`; + } } static get styles(): CSSResultGroup { diff --git a/src/panels/lovelace/cards/hui-calendar-card.ts b/src/panels/lovelace/cards/hui-calendar-card.ts index 6b551746c3..f389f9388e 100644 --- a/src/panels/lovelace/cards/hui-calendar-card.ts +++ b/src/panels/lovelace/cards/hui-calendar-card.ts @@ -10,6 +10,7 @@ import { customElement, property, state, query } from "lit/decorators"; import { getColorByIndex } from "../../../common/color/colors"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { HASSDomEvent } from "../../../common/dom/fire_event"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import { debounce } from "../../../common/util/debounce"; import "../../../components/ha-card"; import { @@ -69,6 +70,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { @state() private _veryNarrow = false; + @state() private _error?: string = undefined; + @query("ha-full-calendar", true) private _calendar?: HAFullCalendar; private _startDate?: Date; @@ -131,6 +134,7 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { .hass=${this.hass} .views=${views} .initialView=${this._config.initial_view!} + .error=${this._error} @view-changed=${this._handleViewChanged} > @@ -169,12 +173,28 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { return; } - this._events = await fetchCalendarEvents( + this._error = undefined; + const result = await fetchCalendarEvents( this.hass!, this._startDate, this._endDate, this._calendars ); + this._events = result.events; + + if (result.errors.length > 0) { + const nameList = result.errors + .map((error_entity_id) => + this.hass!.states[error_entity_id] + ? computeStateName(this.hass!.states[error_entity_id]) + : error_entity_id + ) + .join(", "); + + this._error = `${this.hass!.localize( + "ui.components.calendar.event_retrieval_error" + )} ${nameList}`; + } } private _measureCard() { diff --git a/src/translations/en.json b/src/translations/en.json index ca6749cf87..34f5efdcc2 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -634,6 +634,7 @@ "label": "Calendar", "my_calendars": "My Calendars", "today": "Today", + "event_retrieval_error": "Could not retrieve events for calendars: ", "event": { "add": "Add Event", "delete": "Delete Event",