FIX: recurring events not displayed on /upcoming-events (#676)

When we introduced the DiscoursePostEventEvent model and the API service, the recurrent events stopped working as their logic still relied on the old naming convention, upcoming_dates.

This PR changes the addRecurrentEvents logic to work with and to create DiscoursePostEventEvents for each of the upcomingDates, and changes the logic on the Category calendar to also work with the DiscoursePostEventEvent instance instead of the plain object, as well as re-using the API service to fetch the events.

The function passed to onPageChange was also changed to async/await with some minor changes for clarity.
This commit is contained in:
Renato Atilio 2025-01-08 08:23:24 -03:00 committed by GitHub
parent 764fd67b5e
commit 3c833c99e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 134 additions and 112 deletions

View File

@ -47,7 +47,7 @@ function initializeDiscourseCalendar(api) {
selector = `.topic-list:not(.shared-drafts) .${outletName}-outlet`;
}
api.onPageChange((url) => {
api.onPageChange(async (url) => {
const categoryCalendarNode = document.querySelector(
`${selector}.category-calendar`
);
@ -98,28 +98,27 @@ function initializeDiscourseCalendar(api) {
categoryCalendarNode.innerHTML =
'<div class="calendar"><div class="spinner medium"></div></div>';
loadFullCalendar().then(() => {
const options = [`postId=${postId}`];
await loadFullCalendar();
const optionals = ["weekends", "tzPicker", "defaultView"];
optionals.forEach((optional) => {
if (isPresent(categorySetting[optional])) {
options.push(
`${optional}=${escapeExpression(categorySetting[optional])}`
);
}
});
const options = [`postId=${postId}`];
const rawCalendar = `[calendar ${options.join(" ")}]\n[/calendar]`;
const cookRaw = cook(rawCalendar);
const loadPost = ajax(`/posts/${postId}.json`);
Promise.all([cookRaw, loadPost]).then((results) => {
const cooked = results[0];
const post = results[1];
categoryCalendarNode.innerHTML = cooked.toString();
render($(".calendar"), post);
});
const optionals = ["weekends", "tzPicker", "defaultView"];
optionals.forEach((optional) => {
if (isPresent(categorySetting[optional])) {
options.push(
`${optional}=${escapeExpression(categorySetting[optional])}`
);
}
});
const rawCalendar = `[calendar ${options.join(" ")}]\n[/calendar]`;
const cookRaw = cook(rawCalendar);
const loadPost = ajax(`/posts/${postId}.json`);
const [cooked, post] = await Promise.all([cookRaw, loadPost]);
categoryCalendarNode.innerHTML = cooked.toString();
render($(".calendar"), post);
} else {
if (!categoryEventNode) {
return;
@ -131,97 +130,91 @@ function initializeDiscourseCalendar(api) {
);
if (foundCategory) {
loadFullCalendar().then(() => {
let fullCalendar = new window.FullCalendar.Calendar(
categoryEventNode,
{
...fullCalendarDefaultOptions(),
eventPositioned: (info) => {
if (siteSettings.events_max_rows === 0) {
return;
}
let fcContent = info.el.querySelector(".fc-content");
let computedStyle = window.getComputedStyle(fcContent);
let lineHeight = parseInt(computedStyle.lineHeight, 10);
if (lineHeight === 0) {
lineHeight = 20;
}
let maxHeight = lineHeight * siteSettings.events_max_rows;
if (fcContent) {
fcContent.style.maxHeight = `${maxHeight}px`;
}
let fcTitle = info.el.querySelector(".fc-title");
if (fcTitle) {
fcTitle.style.overflow = "hidden";
fcTitle.style.whiteSpace = "pre-wrap";
}
fullCalendar.updateSize();
},
await loadFullCalendar();
let fullCalendar = new window.FullCalendar.Calendar(categoryEventNode, {
...fullCalendarDefaultOptions(),
eventPositioned: (info) => {
if (siteSettings.events_max_rows === 0) {
return;
}
);
const params = {
category_id: browsedCategory.id,
include_subcategories: true,
};
if (siteSettings.include_expired_events_on_calendar) {
params.include_expired = true;
let fcContent = info.el.querySelector(".fc-content");
let computedStyle = window.getComputedStyle(fcContent);
let lineHeight = parseInt(computedStyle.lineHeight, 10);
if (lineHeight === 0) {
lineHeight = 20;
}
let maxHeight = lineHeight * siteSettings.events_max_rows;
if (fcContent) {
fcContent.style.maxHeight = `${maxHeight}px`;
}
let fcTitle = info.el.querySelector(".fc-title");
if (fcTitle) {
fcTitle.style.overflow = "hidden";
fcTitle.style.whiteSpace = "pre-wrap";
}
fullCalendar.updateSize();
},
});
const params = {
category_id: browsedCategory.id,
include_subcategories: true,
};
if (siteSettings.include_expired_events_on_calendar) {
params.include_expired = true;
}
const tagsColorsMap = JSON.parse(siteSettings.map_events_to_color);
const discoursePostEventApiService = api.container.lookup(
"service:discourse-post-event-api"
);
const events = await discoursePostEventApiService.events(params);
addRecurrentEvents(events).forEach((event) => {
const { startsAt, endsAt, post, categoryId } = event;
let backgroundColor;
if (post.topic.tags) {
const tagColorEntry = tagsColorsMap.find(
(entry) =>
entry.type === "tag" && post.topic.tags.includes(entry.slug)
);
backgroundColor = tagColorEntry ? tagColorEntry.color : null;
}
const loadEvents = ajax(`/discourse-post-event/events`, {
data: params,
});
const tagsColorsMap = JSON.parse(siteSettings.map_events_to_color);
if (!backgroundColor) {
const categoryColorFromMap = tagsColorsMap.find(
(entry) =>
entry.type === "category" &&
entry.slug === post.topic.category_slug
)?.color;
backgroundColor =
categoryColorFromMap ||
`#${Category.findById(categoryId)?.color}`;
}
Promise.all([loadEvents]).then((results) => {
const [{ events }] = results;
let classNames;
if (moment(endsAt || startsAt).isBefore(moment())) {
classNames = "fc-past-event";
}
addRecurrentEvents(events).forEach((event) => {
const { starts_at, ends_at, post, category_id } = event;
let backgroundColor;
if (post.topic.tags) {
const tagColorEntry = tagsColorsMap.find(
(entry) =>
entry.type === "tag" && post.topic.tags.includes(entry.slug)
);
backgroundColor = tagColorEntry ? tagColorEntry.color : null;
}
if (!backgroundColor) {
const categoryColorFromMap = tagsColorsMap.find(
(entry) =>
entry.type === "category" &&
entry.slug === post.topic.category_slug
)?.color;
backgroundColor =
categoryColorFromMap ||
`#${Category.findById(category_id)?.color}`;
}
let classNames;
if (moment(ends_at || starts_at).isBefore(moment())) {
classNames = "fc-past-event";
}
fullCalendar.addEvent({
title: formatEventName(event),
start: starts_at,
end: ends_at || starts_at,
allDay: !isNotFullDayEvent(moment(starts_at), moment(ends_at)),
url: getURL(`/t/-/${post.topic.id}/${post.post_number}`),
backgroundColor,
classNames,
});
});
fullCalendar.render();
fullCalendar.addEvent({
title: formatEventName(event),
start: startsAt,
end: endsAt || startsAt,
allDay: !isNotFullDayEvent(moment(startsAt), moment(endsAt)),
url: getURL(`/t/-/${post.topic.id}/${post.post_number}`),
backgroundColor,
classNames,
});
});
fullCalendar.render();
}
}
});

View File

@ -1,12 +1,17 @@
import DiscoursePostEventEvent from "../models/discourse-post-event-event";
export default function addRecurrentEvents(events) {
return events.flatMap((event) => {
const upcomingEvents =
event.upcoming_dates?.map((upcomingDate) => ({
...event,
starts_at: upcomingDate.starts_at,
ends_at: upcomingDate.ends_at,
upcoming_dates: [],
})) || [];
event.upcomingDates?.map((upcomingDate) =>
DiscoursePostEventEvent.create({
name: event.name,
post: event.post,
category_id: event.categoryId,
starts_at: upcomingDate.starts_at,
ends_at: upcomingDate.ends_at,
})
) || [];
return [event, ...upcomingEvents];
});

View File

@ -1,10 +1,11 @@
import { visit } from "@ember/test-helpers";
import { test } from "qunit";
import { tomorrow } from "discourse/lib/time-utils";
import { tomorrow, twoDays } from "discourse/lib/time-utils";
import {
acceptance,
exists,
query,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) {
@ -38,7 +39,7 @@ acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) {
events: [
{
id: 67501,
starts_at: tomorrow(),
starts_at: tomorrow().add(1, "hour"),
ends_at: null,
timezone: "Asia/Calcutta",
post: {
@ -51,6 +52,12 @@ acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) {
},
},
name: "Awesome Event",
upcoming_dates: [
{
starts_at: twoDays().format("YYYY-MM-DDT15:14:00.000Z"),
ends_at: twoDays().format("YYYY-MM-DDT16:14:00.000Z"),
},
],
category_id: 1,
},
{
@ -101,4 +108,21 @@ acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) {
"Event item uses the proper color from category 2"
);
});
test("upcoming events calendar shows recurrent events", async (assert) => {
await visit("/upcoming-events");
const [, first, second] = queryAll(".fc-event .fc-title");
assert.equal(first.textContent, "Awesome Event");
assert.equal(second.textContent, "Awesome Event");
const firstCell = first.closest("td");
const secondCell = second.closest("td");
assert.notEqual(
firstCell,
secondCell,
"events should be in different days"
);
});
});