Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/nic/fix/3461-changes_in_meeting_…
Browse files Browse the repository at this point in the history
…details_updates_other_widgets' into nic/fix/3461-changes_in_meeting_details_updates_other_widgets
  • Loading branch information
nurjinjafar committed Sep 22, 2023
2 parents b18619b + 73e6d1e commit 0d7e625
Show file tree
Hide file tree
Showing 37 changed files with 1,246 additions and 397 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-coins-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@nordeck/matrix-meetings-widget': minor
---

Support editing a single occurrence of a recurring meeting.
1 change: 1 addition & 0 deletions e2e/src/meetingReaper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ test.describe('Meeting Reaper', () => {
);

const aliceEditMeetingWidgetPage = await meeting.editMeeting();
await aliceEditMeetingWidgetPage.toggleRecurringEdit();
await aliceEditMeetingWidgetPage.setStart([2040, 10, 4], '11:00 AM');
await aliceEditMeetingWidgetPage.submit();
await aliceElementWebPage.approveWidgetIdentity();
Expand Down
16 changes: 12 additions & 4 deletions e2e/src/pages/scheduleMeetingWidgetPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class ScheduleMeetingWidgetPage {
public readonly afterMeetingCountRadio: Locator;
public readonly afterMeetingCountSpinbutton: Locator;
public readonly customRecurrenceRuleGroup: Locator;
public readonly switchRecurringEdit: Locator;

constructor(
private readonly page: Page,
Expand All @@ -37,6 +38,9 @@ export class ScheduleMeetingWidgetPage {
const dialog = this.page.getByRole('dialog', {
name: /Schedule Meeting|Edit Meeting/,
});
this.switchRecurringEdit = widget.getByRole('checkbox', {
name: 'Edit the recurring meeting series',
});
this.submitMeetingButton = dialog.getByRole('button', {
name: /Create Meeting|Save/,
});
Expand Down Expand Up @@ -87,17 +91,21 @@ export class ScheduleMeetingWidgetPage {
);
}

async toggleRecurringEdit() {
await this.switchRecurringEdit.click();
}

async toggleChatPermission() {
await this.allowMessagingCheckbox.click();
}

async addParticipant(name: string) {
await this.participantsCombobox.type(name);
await this.widget.getByRole('option', { name }).waitFor();
await this.participantsCombobox.press('ArrowDown');
await this.participantsCombobox.press('Enter');
}

async toggleChatPermission() {
await this.allowMessagingCheckbox.click();
}

async addWidget(widgetName: string) {
await this.widgetsCombobox.click();
await this.widget.getByRole('option', { name: widgetName }).click();
Expand Down
46 changes: 44 additions & 2 deletions e2e/src/recurringMeetings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ test.describe('Recurring Meetings', () => {
);

const aliceEditMeetingWidgetPage = await meeting.editMeeting();
await aliceEditMeetingWidgetPage.toggleRecurringEdit();
await aliceEditMeetingWidgetPage.setStart([2040, 10, 4], '11:00 AM');
await aliceEditMeetingWidgetPage.selectRecurrence('custom');
await aliceEditMeetingWidgetPage.selectRecurrenceFrequency('weeks');
Expand Down Expand Up @@ -187,6 +188,48 @@ test.describe('Recurring Meetings', () => {
).toBeVisible();
});

test('should edit one instance of the recurring meeting', async ({
aliceMeetingsWidgetPage,
aliceElementWebPage,
}) => {
await aliceMeetingsWidgetPage.setDateFilter([2040, 10, 1], [2040, 10, 9]);

const aliceScheduleMeetingWidgetPage =
await aliceMeetingsWidgetPage.scheduleMeeting();

await aliceScheduleMeetingWidgetPage.titleTextbox.fill('My Meeting');
await aliceScheduleMeetingWidgetPage.descriptionTextbox.fill(
'My Description',
);
await aliceScheduleMeetingWidgetPage.setStart([2040, 10, 3], '10:30 AM');
await aliceScheduleMeetingWidgetPage.selectRecurrence('daily');
await aliceScheduleMeetingWidgetPage.setEndAfterMeetingCount(5);
await aliceScheduleMeetingWidgetPage.submit();

const meeting = aliceMeetingsWidgetPage.getMeeting(
'My Meeting',
'10/03/2040',
);
await expect(meeting.meetingTimeRangeText).toHaveText(
'10:30 AM – 11:30 AM. Recurrence: Every day for 5 times',
);

const aliceEditMeetingWidgetPage = await meeting.editMeeting();
await aliceEditMeetingWidgetPage.setStart([2040, 10, 9], '10:40 AM');
await aliceEditMeetingWidgetPage.submit();
await aliceElementWebPage.approveWidgetIdentity();

await expect(
aliceMeetingsWidgetPage.getMeeting('My Meeting', '10/09/2040')
.meetingTimeRangeText,
).toHaveText('10:40 AM – 11:40 AM. Recurrence: Every day for 5 times');

await expect(
aliceMeetingsWidgetPage.getMeeting('My Meeting', '10/04/2040')
.meetingTimeRangeText,
).toHaveText('10:30 AM – 11:30 AM. Recurrence: Every day for 5 times');
});

test('should covert a recurring meeting into a single meeting', async ({
aliceMeetingsWidgetPage,
aliceElementWebPage,
Expand Down Expand Up @@ -214,6 +257,7 @@ test.describe('Recurring Meetings', () => {
);

const aliceEditMeetingWidgetPage = await meeting.editMeeting();
await aliceEditMeetingWidgetPage.toggleRecurringEdit();
await aliceEditMeetingWidgetPage.selectRecurrence('no repetition');
await aliceEditMeetingWidgetPage.submit();
await aliceElementWebPage.approveWidgetIdentity();
Expand Down Expand Up @@ -294,8 +338,6 @@ test.describe('Recurring Meetings', () => {
).toBeVisible();
});

// TODO: Edit single meeting

// TODO: Edit starting from

test('should delete recurring meeting', async ({
Expand Down
8 changes: 4 additions & 4 deletions matrix-meetings-widget/public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
"save": "Speichern",
"title": "Bearbeiten"
},
"editRecurringMessage": {
"message": "Alle Instanzen der Besprechungsserie können bearbeitet werden",
"titleOne": "Wiederkehrende Besprechungsserie bearbeiten"
},
"invitedMeetingList": {
"detail": {
"invitedBy": "Einladung von: {{name}}"
Expand Down Expand Up @@ -364,10 +368,6 @@
"hasPowerToKickUser": "Du hast nicht die Berechtigung um diesen Nutzer aus der Besprechung zu entfernen.",
"meetingAlreadyStarted": "Die Besprechung ist bereits gestartet.",
"participants": "Teilnehmer",
"recurringMeetingMessage": {
"message": "Alle Wiederholungen des Termins werden bearbeitet.",
"title": "Du bearbeitest einen Serientermin"
},
"startAt": "Startzeitpunkt",
"title": "Titel (erforderlich)",
"titleHelperText": "Ein Titel ist erforderlich",
Expand Down
8 changes: 4 additions & 4 deletions matrix-meetings-widget/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
"save": "Save",
"title": "Edit Meeting"
},
"editRecurringMessage": {
"message": "All instances of the recurring meeting are editable",
"titleOne": "Edit the recurring meeting series"
},
"invitedMeetingList": {
"detail": {
"invitedBy": "Invited By: {{name}}"
Expand Down Expand Up @@ -367,10 +371,6 @@
"hasPowerToKickUser": "You don't have the permission to remove this participant.",
"meetingAlreadyStarted": "The meeting already started.",
"participants": "Participants",
"recurringMeetingMessage": {
"message": "All instances of the recurring meeting are edited",
"title": "You are editing a recurring meeting"
},
"startAt": "Start at",
"title": "Title (required)",
"titleHelperText": "A title is required",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ describe('<MeetingNotEndedGuard/>', () => {
<MeetingNotEndedGuard
meeting={mockMeeting({
content: {
startTime: '2000-01-02T01:00:00Z',
endTime: '2000-01-02T02:00:00Z',
recurrenceId: '2000-01-02T01:00:00Z',
startTime: '2000-01-01T01:00:00Z',
endTime: '2000-01-01T02:00:00Z',
recurrenceId: undefined,
calendarEntries: [
mockCalendarEntry({
dtstart: '20000101T010000',
Expand All @@ -64,7 +64,7 @@ describe('<MeetingNotEndedGuard/>', () => {
content: {
startTime: '2000-01-02T01:00:00Z',
endTime: '2000-01-02T02:00:00Z',
recurrenceId: '2000-01-02T01:00:00Z',
recurrenceId: undefined,
calendarEntries: [
mockCalendarEntry({
dtstart: '20000101T010000',
Expand Down Expand Up @@ -115,7 +115,7 @@ describe('<MeetingNotEndedGuard/>', () => {
content: {
startTime: '2000-01-02T01:00:00Z',
endTime: '2000-01-02T02:00:00Z',
recurrenceId: '2000-01-02T01:00:00Z',
recurrenceId: undefined,
calendarEntries: [
mockCalendarEntry({
dtstart: '20000101T010000',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import React, {
ReactNode,
} from 'react';
import { useTranslation } from 'react-i18next';
import { getCalendarEnd } from '../../../lib/utils';
import { getCalendarEvent } from '../../../lib/utils';
import { isMeetingBreakOutRoom, Meeting } from '../../../reducer/meetingsApi';
import { useUpdateOnDate } from '../hooks';

Expand Down Expand Up @@ -56,13 +56,19 @@ export function MeetingNotEndedGuard({
}: MeetingNotEndedGuardProps): ReactElement {
const { t } = useTranslation();

const endTime = meeting ? getCalendarEnd(meeting.calendarEntries) : undefined;
const endTime = meeting
? getCalendarEvent(
meeting.calendarEntries,
meeting.calendarUid,
meeting?.recurrenceId,
)?.endTime
: undefined;

// Make sure this component is rerendered (and the now below re-evaluated)
// after the meeting ended.
useUpdateOnDate(endTime?.toJSDate());
useUpdateOnDate(endTime);

if (meeting && endTime && DateTime.now() >= endTime) {
if (meeting && endTime && DateTime.now() >= DateTime.fromISO(endTime)) {
if (!withMessage) {
return <React.Fragment />;
}
Expand All @@ -81,7 +87,12 @@ export function MeetingNotEndedGuard({

const isBreakOutRoom = isMeetingBreakOutRoom(meeting.type);
return (
<Alert icon={<AccessTimeIcon />} role="status" severity="info">
<Alert
icon={<AccessTimeIcon />}
role="status"
severity="info"
sx={{ mx: 1 }}
>
{isBreakOutRoom
? t(
'meetingViewEditMeeting.breakoutSessionIsOver',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('useUpdateOnDate', () => {
});

it('should rerender at date', () => {
function Component({ date }: { date: Date }) {
function Component({ date }: { date: string }) {
useUpdateOnDate(date);

return <div>{new Date(Date.now()).toISOString()}</div>;
Expand All @@ -33,7 +33,7 @@ describe('useUpdateOnDate', () => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2022-12-07T20:30:00.000Z'));

render(<Component date={new Date('2022-12-07T20:31:00.000Z')}></Component>);
render(<Component date={'2022-12-07T20:31:00.000Z'}></Component>);

expect(screen.getByText('2022-12-07T20:30:00.000Z')).toBeInTheDocument();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
* limitations under the License.
*/

import { DateTime } from 'luxon';
import { useEffect } from 'react';
import { useUpdate } from 'react-use';

/**
* Triggers to update a component after date.
* @param date
*/
export function useUpdateOnDate(date: Date | undefined): void {
export function useUpdateOnDate(date: string | undefined): void {
const update = useUpdate();

// trigger an update when the meeting ends
Expand Down Expand Up @@ -50,7 +51,7 @@ export function useUpdateOnDate(date: Date | undefined): void {
return () => {};
}

schedule(date.getTime());
schedule(+DateTime.fromISO(date));

return () => {
clearTimeout(timeoutRef);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ export function withCurrentRoomMeeting<T>(
const NewComponent = useMemo(() => withRoomIdMeeting(WrappedComponent), []);

// As the meeting is the current instance of a recurring meeting, we have
// to reevaluate the meeting everytime the end of the meeting instance is
// to reevaluate the meeting every time the end of the meeting instance is
// reached!
useUpdateOnDate(entry ? new Date(entry.endTime) : undefined);
useUpdateOnDate(entry?.endTime);

return (
<NewComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ import {
Meeting,
makeSelectRoomPermissions,
selectNordeckMeetingMetadataEventByRoomId,
selectRoomPowerLevelsEventByRoomId,
useCloseMeetingMutation,
} from '../../../reducer/meetingsApi';
import { useAppSelector } from '../../../store';
import { useAppDispatch, useAppSelector } from '../../../store';
import { ConfirmDeleteDialog } from '../../common/ConfirmDeleteDialog';
import { withoutYearDateFormat } from '../../common/DateTimePickers';
import {
Expand All @@ -47,7 +46,7 @@ import {
OpenXchangeMenuButtonItem,
getOpenXChangeExternalReference,
} from '../../common/MenuButton';
import { useEditMeeting } from '../ScheduleMeetingModal';
import { editMeetingThunk } from '../ScheduleMeetingModal';
import { ScheduledDeletionWarning } from './ScheduledDeletionWarning';

type MeetingCardMenuProps = {
Expand Down Expand Up @@ -143,20 +142,14 @@ export function MeetingCardMenu({
widgetApi,
]);

const { editMeeting } = useEditMeeting();

const isMessagingEnabled = useAppSelector((state) => {
const event = selectRoomPowerLevelsEventByRoomId(state, meeting.meetingId);
return event?.content.events_default === 0;
});

const dispatch = useAppDispatch();
const handleClickEditMeeting = useCallback(async () => {
try {
await editMeeting(meeting, isMessagingEnabled);
await dispatch(editMeetingThunk(meeting)).unwrap();
} catch {
setShowErrorDialog(true);
}
}, [editMeeting, meeting, isMessagingEnabled]);
}, [dispatch, meeting]);

if (!canUpdateMeeting || !canCloseMeeting) {
// If the menu would be empty, skip it
Expand Down
Loading

0 comments on commit 0d7e625

Please sign in to comment.