FEATURE: adds support for timezone (recurring and non recurring) (#237)

This commit is contained in:
Joffrey JAFFEUX 2022-03-03 12:03:21 +01:00 committed by GitHub
parent c3a09e2ec5
commit 7d10944055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 106 additions and 17 deletions

View File

@ -282,6 +282,7 @@ module DiscoursePostEvent
original_ends_at: event_params[:end],
url: event_params[:url],
recurrence: event_params[:recurrence],
timezone: event_params[:timezone],
status: event_params[:status].present? ? Event.statuses[event_params[:status].to_sym] : event.status,
reminders: event_params[:reminders],
raw_invitees: event_params[:"allowed-groups"] ? event_params[:"allowed-groups"].split(',') : nil
@ -329,7 +330,12 @@ module DiscoursePostEvent
end
def calculate_next_date
return { starts_at: original_starts_at, ends_at: original_ends_at } if !original_ends_at || self.recurrence.blank? || original_starts_at > Time.current
if !original_ends_at || self.recurrence.blank? || original_starts_at > Time.current
return {
starts_at: original_starts_at,
ends_at: original_ends_at
}
end
recurrence = nil
@ -357,7 +363,7 @@ module DiscoursePostEvent
recurrence = "FREQ=WEEKLY;BYDAY=#{byday}"
end
next_starts_at = RRuleGenerator.generate(recurrence, original_starts_at)
next_starts_at = RRuleGenerator.generate(recurrence, original_starts_at, tzid: self.timezone)
difference = original_ends_at - original_starts_at
next_ends_at = next_starts_at + difference.seconds

View File

@ -8,6 +8,7 @@ module DiscoursePostEvent
attributes :watching_invitee
attributes :starts_at
attributes :ends_at
attributes :timezone
attributes :stats
attributes :status
attributes :raw_invitees

View File

@ -2,12 +2,13 @@ import RestModel from "discourse/models/rest";
import { ajax } from "discourse/lib/ajax";
const ATTRIBUTES = {
id: {},
name: {},
starts_at: {},
ends_at: {},
raw_invitees: {},
url: {},
id: null,
name: null,
starts_at: null,
ends_at: null,
raw_invitees: null,
url: null,
timezone: null,
status: {
transform(value) {
return STATUSES[value];
@ -51,7 +52,7 @@ const Event = RestModel.extend({
const attributesKeys = Object.keys(ATTRIBUTES);
attributesKeys.forEach((key) => {
const attribute = ATTRIBUTES[key];
if (attribute.transform) {
if (attribute?.transform) {
props[key] = attribute.transform(props[key]);
}
});

View File

@ -28,6 +28,15 @@
}}
{{/event-field}}
{{#event-field class="timezone" label="discourse_post_event.builder_modal.timezone.label"}}
{{timezone-input
value=model.eventModel.timezone
onChange=(action (mut model.eventModel.timezone))
class="input-xxlarge"
none="discourse_post_event.builder_modal.timezone.remove_timezone"
}}
{{/event-field}}
{{#event-field label="discourse_post_event.builder_modal.status.label"}}
<label class="radio-label">
{{radio-button

View File

@ -115,6 +115,7 @@ function _attachWidget(api, cooked, eventModel) {
eventContainer.appendChild(glueContainer);
const startsAt = moment(eventModel.starts_at);
const timezone = eventModel.timezone || "UTC";
const format = guessDateFormat(
startsAt,
eventModel.ends_at && moment(eventModel.ends_at)
@ -129,7 +130,7 @@ function _attachWidget(api, cooked, eventModel) {
.utc(eventModel.starts_at)
.format("YYYY-MM-DD")} time=${moment
.utc(eventModel.starts_at)
.format("HH:mm")} format=${format}]`
.format("HH:mm")} format=${format} timezone=${timezone}]`
);
if (eventModel.ends_at) {
@ -137,7 +138,7 @@ function _attachWidget(api, cooked, eventModel) {
dates.push(
`[date=${endsAt.format("YYYY-MM-DD")} time=${endsAt.format(
"HH:mm"
)} format=${format}]`
)} format=${format} timezone=${timezone}]`
);
}

View File

@ -19,6 +19,10 @@ export function buildParams(startsAt, endsAt, eventModel, siteSettings) {
params.url = eventModel.url;
}
if (eventModel.timezone) {
params.timezone = eventModel.timezone;
}
if (eventModel.recurrence) {
params.recurrence = eventModel.recurrence;
}

View File

@ -359,6 +359,9 @@ en:
update: "Save"
attach: "Create event"
add_reminder: "Add reminder"
timezone:
label: Timezone
remove_timezone: No timezone (UTC)
reminders:
label: "Reminders"
recurrence:

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddsTimezoneToDiscoursePostEventEvent < ActiveRecord::Migration[6.1]
def change
add_column :discourse_post_event_events, :timezone, :string
end
end

View File

@ -8,7 +8,8 @@ VALID_OPTIONS = [
:url,
:name,
:reminders,
:recurrence
:recurrence,
:timezone
]
module DiscoursePostEvent

View File

@ -75,6 +75,12 @@ module DiscoursePostEvent
end
end
if extracted_event[:timezone].present?
if !ActiveSupport::TimeZone[extracted_event[:timezone]].present?
@post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.invalid_timezone", timezone: extracted_event[:timezone]))
end
end
true
end
end

View File

@ -3,11 +3,11 @@
require 'rrule'
class RRuleGenerator
def self.generate(base_rrule, starts_at)
def self.generate(base_rrule, starts_at, tzid: 'UTC')
rrule = generate_hash(base_rrule)
rrule = set_mandatory_options(rrule, starts_at)
::RRule::Rule.new(stringify(rrule), dtstart: starts_at, exdate: [starts_at])
::RRule::Rule.new(stringify(rrule), dtstart: starts_at, exdate: [starts_at], tzid: tzid)
.between(Time.current, Time.current + 2.months)
.first
end

View File

@ -551,8 +551,8 @@ after_initialize do
fragment.css('.discourse-post-event').each do |event_node|
starts_at = event_node['data-start']
ends_at = event_node['data-end']
dates = "#{starts_at} (UTC)"
dates = "#{dates}#{ends_at} (UTC)" if ends_at
dates = "#{starts_at} (#{event_node['data-timezone'] || 'UTC'})"
dates = "#{dates}#{ends_at} (#{event_node['data-timezone'] || 'UTC'})" if ends_at
event_name = event_node['data-name'] || post.topic.title
event_node.replace <<~TXT

View File

@ -18,7 +18,7 @@ describe 'discourse_post_event_recurrence' do
it 'delete previous notifications before creating a new one for invites' do
going_user = Fabricate(:user)
Invitee.create_attendance!(going_user.id, post_event_1.id, :going)
DiscoursePostEvent::Invitee.create_attendance!(going_user.id, post_event_1.id, :going)
post_event_1.update!(recurrence: 'every_month')
post_event_1.set_next_date
@ -91,4 +91,20 @@ describe 'discourse_post_event_recurrence' do
expect(post_event_1.starts_at).to eq_time(Time.zone.parse('2020-09-14 19:00'))
end
end
context 'the event has a timezone' do
context 'every_month' do
before do
post_event_1.update!(recurrence: 'every_month', timezone: 'America/New_York')
end
it 'sets the next month at the same weekday' do
freeze_time(starts_at + 1.day)
post_event_1.set_next_date
expect(post_event_1.starts_at).to eq_time(Time.zone.parse('2020-10-08 23:00'))
end
end
end
end

View File

@ -57,6 +57,21 @@ describe PrettyText do
HTML
end
end
context 'The event has a timezone' do
let(:post_1) { create_post_with_event(user_1, 'timezone="America/New_York"') }
it 'uses the timezone' do
cooked = PrettyText.cook(post_1.raw)
expect(PrettyText.format_for_email(cooked, post_1)).to match_html(<<~HTML)
<div style='border:1px solid #dedede'>
<p><a href="#{Discourse.base_url}#{post_1.url}">#{post_1.topic.title}</a></p>
<p>2018-06-05T18:39:50.000Z (America/New_York)</p>
</div>
HTML
end
end
end
end
end

View File

@ -27,6 +27,25 @@ describe RRuleGenerator do
end
end
context 'tzid given' do
let(:sample_rrule) { 'FREQ=WEEKLY;BYDAY=MO' }
it 'it correctly computes the next date using the timezone' do
tzid = 'Europe/Paris'
time = Time.utc(2020, 1, 25, 15, 36)
freeze_time DateTime.parse('2020-02-25 15:36')
rrule = RRuleGenerator.generate(sample_rrule, time, tzid: tzid)
expect(rrule.to_s).to eq('2020-03-02 15:36:00 +0100')
freeze_time DateTime.parse('2020-09-25 15:36')
rrule = RRuleGenerator.generate(sample_rrule, time, tzid: tzid)
expect(rrule.to_s).to eq('2020-09-28 15:36:00 +0200')
end
end
context 'every day' do
let(:sample_rrule) { 'FREQ=DAILY' }