Skip to content

Commit b0720f5

Browse files
devmountrwood-mozjakobcornell
authored
Alert messages with title and details (#880)
* ➕ Make alert box component style scoped and detail togglable * 👕 Fix linter warnings * 🔨 Fix optional component prop * 🔨 Migrate alert-box instances * ➕ Implement message reasons * ➕ Implement message reasons for components using ftue store props * ➕ Implement message reasons for components using ftue store props * 👕 Fix linter warnings * ➕ Implement google caldav error detection * 🔨 Implement alert messaging instead of simple strings * 👕 Fix linter warnings * Catch up from main (#891) * Enable E2E tests to run against local dev env and stage env (at branch/PR level) (#870) * Enable E2E tests to run against local dev env and against stage at the branch/PR level * Post-review updates * Fix initialization of profile metrics (#877) * 🔨 Fix initialization of profile metrics * 🔨 Fix linting warning * 🔨 Call profile updates in parallel * 🔨 Move lang var to commented metric collection * Fix grammatical mistake in frontend English messages (#882) --------- Co-authored-by: Rob Wood <rwood@thunderbird.net> Co-authored-by: Jakob Cornell <jakob+gpg@jcornell.net> * 👕 Fix linter warnings * 🔨 Fix missing import * 🔨 Fix persistent invalidation messages * 🔨 Move Google domains to defines * 🔨 Use alert type instead of simple string * ➕ Integration test for Google CalDAV * 🔨 Fix Google CalDAV test * 💚 Use alert property directly --------- Co-authored-by: Rob Wood <rwood@thunderbird.net> Co-authored-by: Jakob Cornell <jakob+gpg@jcornell.net>
1 parent b5c89c1 commit b0720f5

31 files changed

+461
-219
lines changed

backend/src/appointment/defines.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@
2222

2323
# CalDAV doesn't provide colours afaik
2424
DEFAULT_CALENDAR_COLOUR = '#c276c5'
25+
26+
# List of Google CalDAV domains
27+
GOOGLE_CALDAV_DOMAINS = ['googleusercontent.com', 'google.com', 'gmail.com']

backend/src/appointment/exceptions/calendar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class EventNotDeletedException(Exception):
1212

1313
class FreeBusyTimeException(Exception):
1414
"""Generic error with the free busy time api"""
15+
1516
pass
1617

1718

@@ -20,4 +21,3 @@ class TestConnectionFailed(Exception):
2021

2122
def __init__(self, reason: str | None = None):
2223
self.reason = reason
23-

backend/src/appointment/exceptions/validation.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,21 @@ class APIException(HTTPException):
1010
id_code = 'UNKNOWN'
1111
status_code = 500
1212
message_key = None
13+
reason = None
1314

14-
def __init__(self, **kwargs):
15+
def __init__(self, reason: str|None = None, **kwargs):
1516
message_key = kwargs.pop('message_key', False)
1617
if message_key is not False:
1718
self.message_key = message_key
1819

20+
if reason:
21+
self.reason = reason
22+
1923
super().__init__(
2024
status_code=self.status_code,
2125
detail={
2226
'id': self.id_code,
27+
'reason': self.get_reason(),
2328
'message': self.get_msg(),
2429
'status': self.status_code,
2530
},
@@ -32,6 +37,8 @@ def get_msg(self):
3237

3338
return l10n('unknown-error')
3439

40+
def get_reason(self):
41+
return self.reason
3542

3643
class InvalidPermissionLevelException(APIException):
3744
"""Raise when the subscribers permission level is too low for the action"""
@@ -205,19 +212,9 @@ def get_msg(self):
205212
class RemoteCalendarConnectionError(APIException):
206213
id_code = 'REMOTE_CALENDAR_CONNECTION_ERROR'
207214
status_code = 400
208-
reason = None
209-
210-
def __init__(self, reason: str|None = None, **kwargs):
211-
if reason:
212-
self.reason = reason
213-
super().__init__(**kwargs)
214215

215216
def get_msg(self):
216-
reason = self.reason
217-
if not self.reason:
218-
reason = l10n('unknown-error-short')
219-
220-
return l10n('remote-calendar-connection-error', {'reason': reason})
217+
return l10n('remote-calendar-connection-error')
221218

222219

223220
class EventCouldNotBeAccepted(APIException):
@@ -332,3 +329,16 @@ class APIRateLimitExceeded(APIException):
332329

333330
def get_msg(self):
334331
return l10n('rate-limit-exceeded')
332+
333+
334+
class GoogleCaldavNotSupported(APIException):
335+
"""Is raised when an attempt to access the Google CalDAV API was detected"""
336+
337+
id_code = 'GOOGLE_CALDAV_NOT_SUPPORTED'
338+
status_code = 400
339+
340+
def get_msg(self):
341+
return l10n('google-caldav-not-supported')
342+
343+
def get_reason(self):
344+
return l10n('google-caldav-not-supported-details')

backend/src/appointment/l10n/de/main.ftl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,12 @@ not-in-allow-list = Deine E-Mail-Adresse ist nicht in der Liste erlaubter Adress
4646
4747
schedule-not-active = Der Zeitplan wurde abgeschaltet. Bitte für weitere Informationen den Eigentümer des Zeitplans kontaktieren.
4848
49-
remote-calendar-connection-error = Der angebundene Kalender konnte nicht erreicht werden: {$reason}.
50-
remote-calendar-connection-error = The remote calendar could not be reached due to {$reason}.
49+
remote-calendar-connection-error = Es konnte keine Verbindung hergestellt werden.
5150
52-
Bitte die Verbindungsinformationen überprüfen und noch einmal versuchen.
53-
54-
# Possible entries for $reason,
55-
remote-calendar-reason-doesnt-support-caldav = Der Kalender bietet keine CalDAV-Unterstützung
56-
remote-calendar-reason-doesnt-support-auth = Der Kalender unterstützt keine Authentifizierung
57-
remote-calendar-reason-unauthorized = Es gibt ein Problem mit Benutzername oder Passwort
51+
# Possible error reasons
52+
remote-calendar-reason-doesnt-support-caldav = Der Kalender bietet keine CalDAV-Unterstützung.
53+
remote-calendar-reason-doesnt-support-auth = Der Kalender unterstützt keine der verfügbaren Authentifizierungsmethoden
54+
remote-calendar-reason-unauthorized = Bitte überprüfe Benutzername und Passwort und versuche es nochmal.
5855
5956
event-could-not-be-accepted = Es ist ein Fehler bei der Annahme der Buchungsdaten aufgetreten. Bitte später noch einmal versuchen.
6057
event-could-not-be-deleted = Es ist ein Fehler beim Entfernen des vorläufigen Termins aufgetreten. Bitte später noch einmal versuchen.
@@ -67,6 +64,7 @@ subscriber-self-delete = Die Löschung des eigenen Benutzerkontos ist hier nicht
6764
6865
rate-limit-exceeded = Zu viele Anfragen in zu kurzem Zeitraum. Bitte später noch einmal versuchen.
6966
67+
# This is for the frontend, don't variable-ify!
7068
error-minimum-value = {field} sollte wenigstens {value} sein.
7169
7270
## Authentication Exceptions
@@ -91,6 +89,8 @@ google-auth-expired = Die Google-Authentifizierungssitzung ist abgelaufen, bitte
9189
google-sync-fail = Bei der Synchronisierung von Kalendern ist ein Fehler aufgetreten. Bitte später noch einmal versuchen.
9290
google-only-one = Es kann nur ein einziges Google-Konto verbunden sein.
9391
google-connect-to-continue = Zum Fortfahren muss mindestens ein Google-Konto verbunden werden.
92+
google-caldav-not-supported = Google CalDAV wird nicht unterstützt.
93+
google-caldav-not-supported-details = Bitte 'Google Kalender verbinden' benutzen.
9494
9595
## Frontend Facing Strings
9696

backend/src/appointment/l10n/en/main.ftl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,12 @@ not-in-allow-list = Your email is not in the allow list.
4646
4747
schedule-not-active = The schedule has been turned off. Please contact the schedule owner for more information.
4848
49-
remote-calendar-connection-error = The remote calendar could not be reached due to {$reason}.
49+
remote-calendar-connection-error = We couldn't connect to your calendar.
5050
51-
Please verify your connection information and try again.
52-
53-
# Possible entries for $reason,
54-
remote-calendar-reason-doesnt-support-caldav = the remote calendar does not support CalDAV
55-
remote-calendar-reason-doesnt-support-auth = the remote calendar does not support authentication
56-
remote-calendar-reason-unauthorized = an issue with your username or password
51+
# Possible error reasons
52+
remote-calendar-reason-doesnt-support-caldav = The remote calendar does not support CalDAV.
53+
remote-calendar-reason-doesnt-support-auth = The server does not support any of the available authentication methods.
54+
remote-calendar-reason-unauthorized = Please check your username and password, then try again.
5755
5856
event-could-not-be-accepted = There was an error accepting the booking details. Please try again later.
5957
event-could-not-be-deleted = There was an error removing the hold event. Please try again later.
@@ -91,6 +89,8 @@ google-auth-expired = Google authentication session expired, please try again.
9189
google-sync-fail = An error occurred while syncing calendars. Please try again later.
9290
google-only-one = You can only have one Google account connected.
9391
google-connect-to-continue = You must connect at least one Google account to continue.
92+
google-caldav-not-supported = Google CalDAV is not supported.
93+
google-caldav-not-supported-details = Please use the 'Connect Google Calendar' option to link your calendar.
9494
9595
## Frontend Facing Strings
9696

backend/src/appointment/routes/caldav.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
from appointment.dependencies.database import get_db, get_redis
1515
from appointment.exceptions.calendar import TestConnectionFailed
1616
from appointment.exceptions.misc import UnexpectedBehaviourWarning
17-
from appointment.exceptions.validation import RemoteCalendarConnectionError
17+
from appointment.exceptions.validation import RemoteCalendarConnectionError, GoogleCaldavNotSupported
1818
from appointment.l10n import l10n
19+
from appointment.defines import GOOGLE_CALDAV_DOMAINS
1920

2021
router = APIRouter()
2122

@@ -37,6 +38,12 @@ def caldav_autodiscover_auth(
3738

3839
dns_lookup_cache_key = f'dns:{utils.encrypt(connection.url)}'
3940

41+
# Check for an attempt to use Google CalDAV API
42+
# which we don't support because we use their API directly
43+
basename = urlparse(connection.url).netloc
44+
if (any([(g in basename) for g in GOOGLE_CALDAV_DOMAINS])):
45+
raise GoogleCaldavNotSupported()
46+
4047
lookup_branch = None
4148
lookup_url = None
4249
if redis_client:

backend/test/integration/test_calendar.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from appointment.database.models import CalendarProvider
66
from appointment.controller.calendar import CalDavConnector, GoogleConnector
77
from appointment.database import schemas, models
8+
from appointment.defines import GOOGLE_CALDAV_DOMAINS
89

910
from sqlalchemy import select
1011

@@ -384,6 +385,20 @@ def test_create_first_caldav_calendar(self, with_client):
384385
assert 'user' not in data
385386
assert 'password' not in data
386387

388+
def test_create_google_caldav_calendar(self, with_client):
389+
response = with_client.post(
390+
'/caldav/auth',
391+
json={
392+
'url': 'https://' + GOOGLE_CALDAV_DOMAINS[0],
393+
'user': os.getenv('CALDAV_TEST_USER'),
394+
'password': os.getenv('CALDAV_TEST_PASS'),
395+
},
396+
headers=auth_headers,
397+
)
398+
assert response.status_code == 400, response.text
399+
data = response.json()
400+
assert data['detail']['id'] == 'GOOGLE_CALDAV_NOT_SUPPORTED'
401+
387402
def test_update_existing_caldav_calendar_with_password(self, with_client, with_db, make_caldav_calendar):
388403
generated_calendar = make_caldav_calendar()
389404

frontend/src/components/DataTable.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface Props {
2424
dataList: TableDataRow[], // List of data to be displayed
2525
filters?: TableFilter[], // List of filters to be displayed
2626
loading: boolean, // Displays a loading spinner
27-
showPagination: boolean;
27+
showPagination?: boolean;
2828
}
2929
3030
const props = withDefaults(defineProps<Props>(), {

frontend/src/components/FTUE/CalDavProvider.vue

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<script setup lang="ts">
22
import { useI18n } from 'vue-i18n';
33
import {
4-
computed,
5-
inject, ref,
4+
computed, inject, ref,
65
} from 'vue';
76
import { callKey } from '@/keys';
87
import PrimaryButton from '@/tbpro/elements/PrimaryButton.vue';
98
import TextInput from '@/tbpro/elements/TextInput.vue';
10-
import { CalendarListResponse, PydanticException } from '@/models';
9+
import {
10+
CalendarListResponse, PydanticException,
11+
} from '@/models';
1112
import { clearFormErrors, handleFormError } from '@/utils';
1213
import SecondaryButton from '@/tbpro/elements/SecondaryButton.vue';
1314
@@ -70,10 +71,11 @@ const onSubmit = async () => {
7071
if (!error.value) {
7172
emits('next');
7273
} else {
73-
const err = handleFormError(t, formRef, data?.value as PydanticException);
74-
if (err) {
74+
const err = data?.value as PydanticException;
75+
const errorMessage = handleFormError(t, formRef, err);
76+
if (errorMessage) {
7577
// Emit a form-level error event if there's a problem here
76-
emits('error', err);
78+
emits('error', errorMessage);
7779
}
7880
}
7981
};

frontend/src/components/FTUE/CalendarProvider.vue

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
import { callKey } from '@/keys';
66
import { useFTUEStore } from '@/stores/ftue-store';
77
import { storeToRefs } from 'pinia';
8+
import { Alert } from '@/models';
89
import GoogleOauthProvider from '@/components/FTUE/GoogleOauthProvider.vue';
910
import CalDavProvider from '@/components/FTUE/CalDavProvider.vue';
1011
@@ -32,17 +33,32 @@ const onSwitch = () => {
3233
}
3334
};
3435
35-
const onError = (err: string) => {
36-
errorMessage.value = err;
36+
const onError = (alert: Alert) => {
37+
errorMessage.value = alert;
3738
};
3839
3940
</script>
4041

4142
<template>
4243
<div class="content">
4344
<div class="provider-view">
44-
<google-oauth-provider @next="onNext" @previous="onPrevious" @switch="onSwitch" :showPrevious="true" :showSwitch="true" v-if="provider === 'google'"></google-oauth-provider>
45-
<cal-dav-provider @next="onNext" @previous="onPrevious" @switch="onSwitch" @error="onError" :showPrevious="true" :showSwitch="true" v-else-if="provider === 'caldav'"></cal-dav-provider>
45+
<google-oauth-provider
46+
v-if="provider === 'google'"
47+
@next="onNext"
48+
@previous="onPrevious"
49+
@switch="onSwitch"
50+
:showPrevious="true"
51+
:showSwitch="true"
52+
/>
53+
<cal-dav-provider
54+
v-else-if="provider === 'caldav'"
55+
@next="onNext"
56+
@previous="onPrevious"
57+
@switch="onSwitch"
58+
@error="onError"
59+
:showPrevious="true"
60+
:showSwitch="true"
61+
/>
4662
</div>
4763
</div>
4864
</template>

0 commit comments

Comments
 (0)