FEATURE: site setting to include expired events on calendar views (#499)

* FEATURE: site setting to include expired events on calendar views

Adds a new site setting to include expired events on calendar views (Upcoming Events and Category Calendar).

Past events are displayed with the configured color (from the category or from the "Map events to color" site setting) as text and border colors instead of the background.
This commit is contained in:
Renato Atilio 2023-12-12 19:10:54 -03:00 committed by GitHub
parent f69229a88d
commit becfe9b2ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 88 additions and 31 deletions

View File

@ -119,7 +119,7 @@ module DiscoursePostEvent
private
def filtered_events_params
params.permit(:post_id, :category_id, :include_subcategories)
params.permit(:post_id, :category_id, :include_subcategories, :include_expired)
end
end
end

View File

@ -3,10 +3,10 @@ import DiscoursePostEventAdapter from "./discourse-post-event-adapter";
export default DiscoursePostEventAdapter.extend({
pathFor(store, type, findArgs) {
let path =
const path =
this.basePath(store, type, findArgs) +
underscore(store.pluralize(this.apiNameFor(type)));
return this.appendQueryParams(path, findArgs) + ".json";
return this.appendQueryParams(`${path}.json`, findArgs);
},
apiNameFor() {

View File

@ -127,6 +127,12 @@ export default Component.extend({
`#${this.site.categoriesById[category_id]?.color}`;
}
let borderColor, textColor;
if (moment(ends_at || starts_at).isBefore(moment())) {
borderColor = textColor = backgroundColor;
backgroundColor = undefined;
}
this._calendar.addEvent({
title: formatEventName(event),
start: starts_at,
@ -134,6 +140,8 @@ export default Component.extend({
allDay: !isNotFullDayEvent(moment(starts_at), moment(ends_at)),
url: getURL(`/t/-/${post.topic.id}/${post.post_number}`),
backgroundColor,
borderColor,
textColor,
});
});

View File

@ -177,9 +177,16 @@ function initializeDiscourseCalendar(api) {
},
}
);
const loadEvents = ajax(
`/discourse-post-event/events.json?category_id=${browsedCategory.id}&include_subcategories=true`
);
const params = {
category_id: browsedCategory.id,
include_subcategories: true,
};
if (siteSettings.include_expired_events_on_calendar) {
params.include_expired = true;
}
const loadEvents = ajax(`/discourse-post-event/events.json`, {
data: params,
});
Promise.all([loadEvents]).then((results) => {
const events = results[0];
@ -210,6 +217,12 @@ function initializeDiscourseCalendar(api) {
`#${site.categoriesById[category_id]?.color}`;
}
let borderColor, textColor;
if (moment(ends_at || starts_at).isBefore(moment())) {
borderColor = textColor = backgroundColor;
backgroundColor = undefined;
}
fullCalendar.addEvent({
title: formatEventName(event),
start: starts_at,
@ -217,6 +230,8 @@ function initializeDiscourseCalendar(api) {
allDay: !isNotFullDayEvent(moment(starts_at), moment(ends_at)),
url: getURL(`/t/-/${post.topic.id}/${post.post_number}`),
backgroundColor,
borderColor,
textColor,
});
});

View File

@ -10,6 +10,9 @@ export default Route.extend({
}),
model(params) {
if (this.siteSettings.include_expired_events_on_calendar) {
params.include_expired = true;
}
return this.store.findAll("discourse-post-event-event", params);
},
});

View File

@ -56,6 +56,7 @@ en:
disable_resorting_on_categories_enabled: "Allow categories to disable the ability for users to sort on the event category."
calendar_automatic_holidays_enabled: "Automatically set holiday status based on a users region (note: you can disable specific automatic holidays in plugin settings)"
sidebar_show_upcoming_events: "Show upcoming events link in the sidebar under 'More'."
include_expired_events_on_calendar: "Include past/expired events on Category Calendar and Upcoming Events views."
discourse_calendar:
invite_user_notification: "%{username} invited you to: %{description}"
calendar_must_be_in_first_post: "Calendar tag can only be used in first post of a topic."

View File

@ -110,3 +110,6 @@ discourse_calendar:
client: true
default: "[]"
json_schema: DiscourseCalendar::SiteSettings::MapEventTagColorsJsonSchema
include_expired_events_on_calendar:
default: false
client: true

View File

@ -7,33 +7,29 @@ module DiscoursePostEvent
topics = listable_topics(guardian)
pms = private_messages(user)
dates_join = <<~SQL
LEFT JOIN (
SELECT
finished_at,
event_id,
starts_at,
ROW_NUMBER() OVER (PARTITION BY event_id ORDER BY finished_at DESC NULLS FIRST) as row_num
FROM discourse_calendar_post_event_dates
) dcped ON dcped.event_id = discourse_post_event_events.id AND dcped.row_num = 1
SQL
events =
DiscoursePostEvent::Event
.select("discourse_post_event_events.*, dcped.starts_at")
.joins(post: :topic)
.merge(Post.secured(guardian))
.merge(topics.or(pms).distinct)
.joins(
"LEFT JOIN discourse_calendar_post_event_dates dcped ON dcped.event_id = discourse_post_event_events.id",
)
.joins(dates_join)
.order("dcped.starts_at ASC")
if params[:expired]
# The second part below makes the query ignore events that have non-expired event-dates
events =
events.where(
"dcped.finished_at IS NOT NULL AND (dcped.ends_at IS NOT NULL AND dcped.ends_at < ?)",
Time.now,
).where(
"discourse_post_event_events.id NOT IN (SELECT DISTINCT event_id FROM discourse_calendar_post_event_dates WHERE event_id = discourse_post_event_events.id AND finished_at IS NULL)",
)
else
events =
events.where(
"dcped.finished_at IS NULL AND (dcped.ends_at IS NULL OR dcped.ends_at > ?)",
Time.now,
)
end
include_expired = params[:include_expired].to_s == "true"
events = events.where("dcped.finished_at IS NULL") unless include_expired
events = events.where(id: Array(params[:post_id])) if params[:post_id]

View File

@ -122,8 +122,12 @@ describe DiscoursePostEvent::EventFinder do
end
it "returns correct events" do
expect(finder.search(current_user, { expired: false })).to eq([current_event, future_event])
expect(finder.search(current_user, { expired: true })).to eq([older_event, old_event])
expect(finder.search(current_user, { include_expired: false })).to eq(
[current_event, future_event],
)
expect(finder.search(current_user, { include_expired: true })).to eq(
[older_event, old_event, current_event, future_event],
)
end
context "when a past event has been edited to be in the future" do
@ -138,10 +142,12 @@ describe DiscoursePostEvent::EventFinder do
end
it "returns correct events" do
expect(finder.search(current_user, { expired: false })).to eq(
expect(finder.search(current_user, { include_expired: false })).to eq(
[current_event, future_event],
)
expect(finder.search(current_user, { expired: true })).to eq([older_event, old_event])
expect(finder.search(current_user, { include_expired: true })).to eq(
[older_event, old_event, current_event, future_event],
)
end
end
@ -157,10 +163,12 @@ describe DiscoursePostEvent::EventFinder do
end
it "returns correct events" do
expect(finder.search(current_user, { expired: false })).to eq(
expect(finder.search(current_user, { include_expired: false })).to eq(
[current_event, future_event],
)
expect(finder.search(current_user, { expired: true })).to eq([older_event, old_event])
expect(finder.search(current_user, { include_expired: true })).to eq(
[older_event, current_event, future_event, old_event],
)
end
end
end

View File

@ -227,6 +227,14 @@ module DiscoursePostEvent
Fabricate(:post, post_number: 1, topic: Fabricate(:topic, category: subcategory)),
)
end
fab!(:event_3) do
Fabricate(
:event,
post: Fabricate(:post, post_number: 1, topic: Fabricate(:topic, category: category)),
original_starts_at: 10.days.ago,
original_ends_at: 9.days.ago,
)
end
it "can filter the event by category" do
get "/discourse-post-event/events.json?category_id=#{category.id}"
@ -267,6 +275,21 @@ module DiscoursePostEvent
"is_standalone",
)
end
it "includes expired events when param provided" do
get "/discourse-post-event/events.json?category_id=#{category.id}&include_subcategories=true&include_expired=true"
expect(response.status).to eq(200)
events = response.parsed_body["events"]
expect(events.length).to eq(3)
expect(events).to match_array(
[
hash_including("id" => event_1.id),
hash_including("id" => event_2.id),
hash_including("id" => event_3.id),
],
)
end
end
end
end