FEATURE: regional holidays

FEATURE: regional holidays

diff --git a/.gitignore b/.gitignore
index a6b49d8..6913e99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 .rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml
 .DS_Store
+gems/
diff --git a/assets/javascripts/initializers/discourse-calendar.js.es6 b/assets/javascripts/initializers/discourse-calendar.js.es6
index 54c56ab..420954d 100644
--- a/assets/javascripts/initializers/discourse-calendar.js.es6
+++ b/assets/javascripts/initializers/discourse-calendar.js.es6
@@ -2,6 +2,7 @@ import loadScript from "discourse/lib/load-script";
 import { withPluginApi } from "discourse/lib/plugin-api";
 import { minimumOffset } from "discourse/lib/offset-calculator";
 import { ajax } from "discourse/lib/ajax";
+import { showPopover, hidePopover } from "discourse/lib/d-popover";
 
 // https://stackoverflow.com/a/16348977
 /* eslint-disable */
@@ -190,10 +191,21 @@ function initializeDiscourseCalendar(api) {
       );
     }
 
-    calendar.setOption("eventClick", calEvent => {
-      const postNumber = calEvent.event.extendedProps.postNumber;
+    calendar.setOption("eventClick", ({ event }) => {
+      const { postNumber } = event.extendedProps;
+      if (!postNumber) return;
       _topicController = _topicController || api.container.lookup("controller:topic");
-      _topicController.send("jumpToPost", postNumber)
+      _topicController.send("jumpToPost", postNumber);
+    });
+
+    calendar.setOption("eventMouseEnter", ({ event, jsEvent }) => {
+      const { htmlContent } = event.extendedProps;
+      if (!htmlContent) return;
+      showPopover(jsEvent, { htmlContent })
+    });
+
+    calendar.setOption("eventMouseLeave", ({ jsEvent }) => {
+      hidePopover(jsEvent);
     });
   }
 
@@ -218,6 +230,7 @@ function initializeDiscourseCalendar(api) {
         Discourse.SiteSettings.holiday_calendar_topic_id,
         10
       );
+
       const excerpt = detail.message.split("\n").filter(e => e);
       if (excerpt.length && (post.topic_id && holidayCalendarTopicId !== post.topic_id)) {
         event.title = excerpt[0];
@@ -226,9 +239,13 @@ function initializeDiscourseCalendar(api) {
         event.backgroundColor = stringToHexColor(detail.username);
       }
 
-      event.extendedProps = {
-        postNumber: parseInt(detail.post_number, 10)
-      };
+      event.extendedProps = { htmlContent: detail.message };
+
+      if (detail.post_number) {
+        event.extendedProps.postNumber = parseInt(detail.post_number, 10);
+      } else {
+        event.classNames = ["holiday"];
+      }
 
       calendar.addEvent(event);
     });
diff --git a/assets/stylesheets/common/discourse-calendar.scss b/assets/stylesheets/common/discourse-calendar.scss
index 0a4c2c6..cb9214c 100644
--- a/assets/stylesheets/common/discourse-calendar.scss
+++ b/assets/stylesheets/common/discourse-calendar.scss
@@ -83,3 +83,7 @@
 .mention .d-icon {
   margin-left: 0.5em;
 }
+
+a.holiday {
+  cursor: default;
+}
diff --git a/jobs/scheduled/check_next_regional_holidays.rb b/jobs/scheduled/check_next_regional_holidays.rb
new file mode 100644
index 0000000..ab269b4
--- /dev/null
+++ b/jobs/scheduled/check_next_regional_holidays.rb
@@ -0,0 +1,42 @@
+module Jobs
+  class ::DiscourseCalendar::CheckNextRegionalHolidays < Jobs::Scheduled
+    every 12.hours
+
+    def execute(args)
+      return unless SiteSetting.calendar_enabled
+      return unless topic_id = SiteSetting.holiday_calendar_topic_id
+      return unless op = Post.find_by(topic_id: topic_id, post_number: 1)
+
+      require "holidays" unless defined?(Holidays)
+
+      user_ids = []
+      users_in_region = {}
+
+      UserCustomField.where(name: ::DiscourseCalendar::REGION_CUSTOM_FIELD).pluck(:user_id, :value).each do |user_id, region|
+        user_ids << user_id
+        users_in_region[region] ||= []
+        users_in_region[region] << user_id
+      end
+
+      usernames = User.where(id: user_ids).pluck(:id, :username_lower).to_h
+
+      regional_holidays = op.custom_fields[::DiscourseCalendar::CALENDAR_HOLIDAYS_CUSTOM_FIELD] || []
+
+      users_in_region.keys.each do |region|
+        next if !(next_holiday = Holidays.next_holidays(1, [region]).first)
+        next if next_holiday[:date] > 1.month.from_now
+
+        date = next_holiday[:date].to_s
+
+        users_in_region[region].each do |user_id|
+          if !regional_holidays.find { |r, _, d, u| r == region && d == date && u == usernames[user_id] }
+            regional_holidays << [region, next_holiday[:name], date, usernames[user_id]]
+          end
+        end
+      end
+
+      op.custom_fields[::DiscourseCalendar::CALENDAR_HOLIDAYS_CUSTOM_FIELD] = regional_holidays
+      op.save_custom_fields(true)
+    end
+  end
+end
diff --git a/jobs/scheduled/ensure_expired_event_destruction.rb b/jobs/scheduled/ensure_expired_event_destruction.rb
index 84ec6e7..b7975e4 100644
--- a/jobs/scheduled/ensure_expired_event_destruction.rb
+++ b/jobs/scheduled/ensure_expired_event_destruction.rb
@@ -3,6 +3,8 @@ module Jobs
     every 10.minutes
 
     def execute(args)
+      return unless SiteSetting.calendar_enabled
+
       delay = SiteSetting.delete_expired_event_posts_after
 
       return if delay < 0
diff --git a/jobs/scheduled/update_holiday_usernames.rb b/jobs/scheduled/update_holiday_usernames.rb
index d24976c..89fbec3 100644
--- a/jobs/scheduled/update_holiday_usernames.rb
+++ b/jobs/scheduled/update_holiday_usernames.rb
@@ -5,29 +5,53 @@ module Jobs
     PLUGIN_NAME ||= "calendar".freeze
 
     def execute(args)
+      return unless SiteSetting.calendar_enabled
       return unless topic_id = SiteSetting.holiday_calendar_topic_id
       return unless op = Post.find_by(topic_id: topic_id, post_number: 1)
-      return unless details = op.custom_fields[::DiscourseCalendar::CALENDAR_DETAILS_CUSTOM_FIELD]
 
-      user_ids = []
-      users_on_holiday = []
+      events = []
 
-      details.values.each do |_, from, to, username|
+      if details = op.custom_fields[::DiscourseCalendar::CALENDAR_DETAILS_CUSTOM_FIELD]
+        details.values.each do |_, from, to, username|
+          events << [from, to, username]
+        end
+      end
+
+      if holidays = op.custom_fields[::DiscourseCalendar::CALENDAR_HOLIDAYS_CUSTOM_FIELD]
+        holidays.each do |_, _, date, username|
+          events << [date, nil, username]
+        end
+      end
+
+      usernames = []
+
+      events.each do |from, to, username|
         from_time = Time.parse(from)
         to_time   = to ? Time.parse(to) : from_time + 24.hours
+        usernames << username if from_time < Time.zone.now && Time.zone.now < to_time
+      end
 
-        if from_time < Time.zone.now && Time.zone.now < to_time
-          users_on_holiday << username
+      usernames.uniq!
+      usernames.compact!
 
-          user = User.find_by(username_lower: username)
-          user.custom_fields[::DiscourseCalendar::HOLIDAY_CUSTOM_FIELD] = "t"
-          user.save_custom_fields(true)
-          user_ids << user.id
-        end
+      PluginStore.set(PLUGIN_NAME, DiscourseCalendar::USERS_ON_HOLIDAY_KEY, usernames)
+
+      cf_name  = ::DiscourseCalendar::HOLIDAY_CUSTOM_FIELD
+      user_ids = User.where(username_lower: usernames).pluck(:id).compact
+
+      if user_ids.present?
+        DB.exec <<~SQL
+          INSERT INTO user_custom_fields (user_id, name, value, created_at, updated_at)
+          VALUES #{user_ids.map { |id| "(#{id}, '#{cf_name}', 't', now(), now())" }.join(",")}
+          ON CONFLICT DO NOTHING
+        SQL
       end
 
-      PluginStore.set(PLUGIN_NAME, DiscourseCalendar::USERS_ON_HOLIDAY_KEY, users_on_holiday)
-      UserCustomField.where(name: ::DiscourseCalendar::HOLIDAY_CUSTOM_FIELD).where.not(user_id: user_ids).destroy_all
+      DB.exec <<~SQL, cf_name, user_ids
+        DELETE FROM user_custom_fields
+         WHERE name = ?
+           AND user_id NOT IN (?)
+      SQL
     end
   end
 end
diff --git a/plugin.rb b/plugin.rb
index c210ee5..3cffd83 100644
--- a/plugin.rb

[... diff too long, it was truncated ...]

GitHub sha: a8a560cc

1 Like

Are there any tests we can add here?

1 Like

FIX: ensure only active users can be on holiday