Skip to content

Commit

Permalink
Timezone information in booking confirmation email (#367)
Browse files Browse the repository at this point in the history
* πŸ”¨ fix case sensitive filename calls πŸ™ˆ

* βž• show timezone for attendee in confirmation email

* πŸ”¨ extend schedule test with attendee timezone

* πŸ”¨ Make attendee timezone mandatory

* πŸ”¨ Fix appointment tests
  • Loading branch information
devmount authored Apr 18, 2024
1 parent ee5ef96 commit 836f0f2
Show file tree
Hide file tree
Showing 11 changed files with 51 additions and 17 deletions.
1 change: 1 addition & 0 deletions backend/src/appointment/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class Attendee(Base):
id = Column(Integer, primary_key=True, index=True)
email = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=255), index=True)
name = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=255), index=True)
timezone = Column(String(255), index=True)

slots = relationship("Slot", cascade="all,delete", back_populates="attendee")

Expand Down
1 change: 1 addition & 0 deletions backend/src/appointment/database/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
class AttendeeBase(BaseModel):
email: str
name: str | None = None
timezone: str


class Attendee(AttendeeBase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""add attendee timezone
Revision ID: 89e1197d980d
Revises: fadd0d1ef438
Create Date: 2024-04-18 08:23:55.660065
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '89e1197d980d'
down_revision = 'fadd0d1ef438'
branch_labels = None
depends_on = None


def upgrade() -> None:
op.add_column('attendees', sa.Column('timezone', sa.String(255), index=True))


def downgrade() -> None:
op.drop_column('attendees', 'timezone')
15 changes: 12 additions & 3 deletions backend/src/appointment/routes/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,12 @@ def request_schedule_availability_slot(
# human readable date in subscribers timezone
# TODO: handle locale date representation
date = slot.start.replace(tzinfo=timezone.utc).astimezone(ZoneInfo(subscriber.timezone)).strftime("%c")
date = f"{date}, {slot.duration} minutes"
date = f"{date}, {slot.duration} minutes ({subscriber.timezone})"

# human readable date in attendee timezone
# TODO: handle locale date representation
attendee_date = slot.start.replace(tzinfo=timezone.utc).astimezone(ZoneInfo(slot.attendee.timezone)).strftime("%c")
attendee_date = f"{attendee_date}, {slot.duration} minutes ({slot.attendee.timezone})"

# Create a pending appointment
attendee_name = slot.attendee.name if slot.attendee.name is not None else slot.attendee.email
Expand All @@ -246,7 +251,7 @@ def request_schedule_availability_slot(
background_tasks.add_task(send_confirmation_email, url=url, attendee=attendee, date=date, to=subscriber.email)

# Sending pending email to attendee
background_tasks.add_task(send_pending_email, owner=subscriber, date=date, to=slot.attendee.email)
background_tasks.add_task(send_pending_email, owner=subscriber, date=attendee_date, to=slot.attendee.email)

# Mini version of slot, so we can grab the newly created slot id for tests
return schemas.SlotOut(
Expand Down Expand Up @@ -416,5 +421,9 @@ def decide_on_schedule_availability_slot(

return schemas.AvailabilitySlotAttendee(
slot=schemas.SlotBase(start=slot.start, duration=slot.duration),
attendee=schemas.AttendeeBase(email=slot.attendee.email, name=slot.attendee.name)
attendee=schemas.AttendeeBase(
email=slot.attendee.email,
name=slot.attendee.name,
timezone=slot.attendee.timezone
)
)
6 changes: 3 additions & 3 deletions backend/test/integration/test_appointment.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def test_attendee_selects_slot_of_unavailable_appointment(self, with_db, with_cl

response = with_client.put(
f"/apmt/public/{generated_appointment.slug}",
json={"slot_id": generated_appointment.slots[-1].id, "attendee": {"email": "a", "name": "b"}},
json={"slot_id": generated_appointment.slots[-1].id, "attendee": {"email": "a", "name": "b", "timezone": "c"}},
)
assert response.status_code == 403, response.text

Expand All @@ -347,7 +347,7 @@ def test_attendee_selects_slot_of_missing_appointment(self, with_client, make_ap

response = with_client.put(
f"/apmt/public/{generated_appointment}",
json={"slot_id": generated_appointment.slots[0].id, "attendee": {"email": "a", "name": "b"}},
json={"slot_id": generated_appointment.slots[0].id, "attendee": {"email": "a", "name": "b", "timezone": "c"}},
)
assert response.status_code == 404, response.text

Expand All @@ -356,7 +356,7 @@ def test_attendee_selects_missing_slot_of_existing_appointment(self, with_client

response = with_client.put(
f"/apmt/public/{generated_appointment.id}",
json={"slot_id": generated_appointment.slots[0].id + 1, "attendee": {"email": "a", "name": "b"}},
json={"slot_id": generated_appointment.slots[0].id + 1, "attendee": {"email": "a", "name": "b", "timezone": "c"}},
)
assert response.status_code == 404, response.text

Expand Down
3 changes: 2 additions & 1 deletion backend/test/integration/test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,8 @@ def bust_cached_events(self, all_calendars = False):
),
attendee=schemas.AttendeeBase(
email='[email protected]',
name='Greg'
name='Greg',
timezone='Europe/Berlin'
)
).model_dump(mode='json')

Expand Down
2 changes: 1 addition & 1 deletion backend/test/unit/test_mailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_confirm(self, faker, with_l10n):
deny_url = 'https://example.org/no'
fake_email = '[email protected]'
now = datetime.datetime.now()
attendee = schemas.AttendeeBase(email=faker.email(), name=faker.name())
attendee = schemas.AttendeeBase(email=faker.email(), name=faker.name(), timezone='Europe/Berlin')

mailer = ConfirmationMail(confirm_url, deny_url, attendee, now, to=fake_email)
assert mailer.html()
Expand Down
10 changes: 4 additions & 6 deletions frontend/src/components/BookingModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@
</template>

<script setup>
import {
inject, computed, reactive, ref, onMounted,
} from 'vue';
import { inject, computed, reactive, ref, onMounted } from 'vue';
import { timeFormat } from '@/utils';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
Expand Down Expand Up @@ -111,15 +109,14 @@ const props = defineProps({
// Store
const bookingModalStore = useBookingModalStore();
const {
open, state, stateData, isLoading, hasErrors, isFinished, isEditable,
} = storeToRefs(bookingModalStore);
const { open, state, stateData, isLoading, hasErrors, isFinished, isEditable } = storeToRefs(bookingModalStore);
// Refs
const attendee = reactive({
name: '',
email: '',
timezone: dj.tz.guess(),
});
const bookingForm = ref();
Expand All @@ -145,6 +142,7 @@ onMounted(() => {
if (user.exists()) {
attendee.name = user.data.name;
attendee.email = user.data.email;
attendee.timezone = user.data.timezone;
}
});
</script>
2 changes: 1 addition & 1 deletion frontend/src/elements/PrimaryButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import ToolTip from '@/elements/Tooltip';
import ToolTip from '@/elements/ToolTip';
// icons
import { IconClipboardCheck, IconCopy } from '@tabler/icons-vue';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/elements/SecondaryButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import ToolTip from '@/elements/Tooltip';
import ToolTip from '@/elements/ToolTip';
// icons
import { IconClipboardCheck, IconCopy } from '@tabler/icons-vue';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/elements/TextButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import ToolTip from '@/elements/Tooltip';
import ToolTip from '@/elements/ToolTip';
// icons
import { IconCopy, IconClipboardCheck } from '@tabler/icons-vue';
Expand Down

0 comments on commit 836f0f2

Please sign in to comment.