diff --git a/src/esn.calendar.libs/app/components/event/form/event-form.controller.js b/src/esn.calendar.libs/app/components/event/form/event-form.controller.js index 9d9b908c..e5fe0275 100644 --- a/src/esn.calendar.libs/app/components/event/form/event-form.controller.js +++ b/src/esn.calendar.libs/app/components/event/form/event-form.controller.js @@ -179,6 +179,10 @@ function CalEventFormController( _.find(calendars, 'selected'); } + // when editing an event exclude delegated calendars from the possible target calendars + // that can be used to move events into + $scope.calendars = calendars.filter(calendar => calendar.isOwner(session.user._id)); + return _getCalendarByUniqueId($scope.editedEvent.calendarUniqueId); }) .then(function(selectedCalendar) { @@ -368,7 +372,7 @@ function CalEventFormController( $scope.editedEvent.attendees = getUpdatedAttendees(); - if (!calEventUtils.hasAnyChange($scope.editedEvent, $scope.event)) { + if (!calEventUtils.hasAnyChange($scope.editedEvent, $scope.event) && !_calendarHasChanged()) { _hideModal(); return; @@ -393,6 +397,10 @@ function CalEventFormController( .then(cacheAttendees) .then(denormalizeAttendees) .then(function() { + if (!calEventUtils.hasAnyChange($scope.editedEvent, $scope.event)) { + return $q.when(true); + } + return calEventService.modifyEvent( $scope.event.path || calPathBuilder.forCalendarPath($scope.calendarHomeId, _getCalendarByUniqueId($scope.selectedCalendar.uniqueId).id), $scope.editedEvent, @@ -402,6 +410,7 @@ function CalEventFormController( { graceperiod: true, notifyFullcalendar: $state.is('calendar.main') } ); }) + .then(canPerformCalendarMove) .then(onEventCreateUpdateResponse) .finally(function() { $scope.restActive = false; @@ -668,4 +677,38 @@ function CalEventFormController( placement: 'center' }); } + + /** + * Checks if an event can be moved into another calendar + * + * @param {boolean} success - true if the previous response was successful + * + * @returns {Promise} + */ + function canPerformCalendarMove(success) { + if (!success) return $q.when(false); + + return _calendarHasChanged() ? changeCalendar() : $q.when(); + } + + /** + * moves the event to the new calendar + * + * @returns {Promise} - resolves to true if the event calendar has changed + */ + function changeCalendar() { + const destinationPath = calPathBuilder.forEventId($scope.calendarHomeId, _getCalendarByUniqueId($scope.selectedCalendar.uniqueId).id, $scope.editedEvent.uid); + const sourcePath = $scope.event.path; + + return calEventService.moveEvent(sourcePath, destinationPath); + } + + /** + * Checks if the selected calendar differs from the original event calendar + * + * @returns {Boolean} - true if the event calendar and the selected calendar are different + */ + function _calendarHasChanged() { + return _getCalendarByUniqueId($scope.selectedCalendar.uniqueId).id !== $scope.editedEvent.calendarId; + } } diff --git a/src/esn.calendar.libs/app/components/event/form/event-form.controller.spec.js b/src/esn.calendar.libs/app/components/event/form/event-form.controller.spec.js index da944c61..9d105db0 100644 --- a/src/esn.calendar.libs/app/components/event/form/event-form.controller.spec.js +++ b/src/esn.calendar.libs/app/components/event/form/event-form.controller.spec.js @@ -123,7 +123,8 @@ describe('The CalEventFormController controller', function() { sendCounter: sinon.spy(function() { return $q.when(true); }), - removeEvent: sinon.stub().returns($q.when()) + removeEvent: sinon.stub().returns($q.when()), + moveEvent: sinon.stub().returns($q.when(true)) }; calendarHomeId = 'calendarHomeId'; @@ -1223,9 +1224,6 @@ describe('The CalEventFormController controller', function() { scope.event = { title: 'oldtitle', path: '/path/to/event', - rrule: { - equals: _.constant(false) - }, etag: '123123', clone: _.constant(editedEvent) }; @@ -1258,6 +1256,7 @@ describe('The CalEventFormController controller', function() { }); calEventServiceMock.modifyEvent = sinon.stub().returns($q.when(true)); + calEventServiceMock.moveEvent = sinon.stub().returns($q.when(true)); scope.modifyEvent(); scope.$digest(); @@ -1297,6 +1296,56 @@ describe('The CalEventFormController controller', function() { expect(restoreSpy).to.not.have.been.called; expect(calOpenEventFormMock).to.have.been.calledWith(sinon.match.any, scope.editedEvent); }); + + it('should attempt to move the event to another calendar if the organizer changed it', function() { + const fakeEvent = { + start: start, + end: end, + title: 'oldtitle', + path: '/calendars/owner/id.json', + etag: '123123' + }; + + scope.event = CalendarShell.fromIncompleteShell(fakeEvent); + initController(); + + scope.editedEvent = CalendarShell.fromIncompleteShell({ ...fakeEvent, title: 'new title' }); + scope.selectedCalendar.uniqueId = '/calendars/owner/id2.json'; + scope.calendarHomeId = 'owner'; + + scope.modifyEvent(); + scope.$digest(); + + expect(calEventServiceMock.moveEvent).to.have.been.calledWith( + '/calendars/owner/id.json', + `/calendars/owner/id2/${scope.editedEvent.uid}.ics` + ); + }); + + it('should attempt to move the event to another calendar when the other fields were left intact', function() { + const fakeEvent = { + start: start, + end: end, + title: 'oldtitle', + path: '/calendars/owner/id.json', + etag: '123123' + }; + + scope.event = CalendarShell.fromIncompleteShell(fakeEvent); + initController(); + + scope.editedEvent = CalendarShell.fromIncompleteShell(fakeEvent); + scope.selectedCalendar.uniqueId = '/calendars/owner/id2.json'; + scope.calendarHomeId = 'owner'; + + scope.modifyEvent(); + scope.$digest(); + + expect(calEventServiceMock.moveEvent).to.have.been.calledWith( + '/calendars/owner/id.json', + `/calendars/owner/id2/${scope.editedEvent.uid}.ics` + ); + }); }); describe('as an attendee', function() { diff --git a/src/esn.calendar.libs/app/components/event/form/event-form.pug b/src/esn.calendar.libs/app/components/event/form/event-form.pug index 93eb6bfd..f7970ccc 100644 --- a/src/esn.calendar.libs/app/components/event/form/event-form.pug +++ b/src/esn.calendar.libs/app/components/event/form/event-form.pug @@ -46,8 +46,8 @@ form.event-form(role="form", name="form", aria-hidden="true", ng-class="{ 'reado i.mdi.mdi-calendar-multiple .fg-line md-input-container(ng-click="changeBackdropZIndex()") - md-select(ng-disabled="!isNew(editedEvent) || !canModifyEvent", ng-model="selectedCalendar.uniqueId", md-container-class="cal-select-dropdown" aria-label="calendar") - md-option(ng-value="calendar.getUniqueId()" ng-repeat="calendar in calendars | filter: (isNew(editedEvent) || canModifyEvent) ? { readOnly: false } : {}") + md-select(ng-disabled="!canModifyEvent", ng-model="selectedCalendar.uniqueId", md-container-class="cal-select-dropdown" aria-label="calendar") + md-option(ng-value="calendar.getUniqueId()" ng-repeat="calendar in calendars | filter: { readOnly: false }") cal-select-calendar-item(calendar="calendar") cal-event-date-edition(event="editedEvent", disabled='!canModifyEvent', use-24hour-format='use24hourFormat', on-date-change='onDateChange') cal-entities-autocomplete-input.cal-user-autocomplete-input( diff --git a/src/esn.calendar.libs/app/services/calendar-api.js b/src/esn.calendar.libs/app/services/calendar-api.js index 56fe8936..817b995f 100644 --- a/src/esn.calendar.libs/app/services/calendar-api.js +++ b/src/esn.calendar.libs/app/services/calendar-api.js @@ -43,7 +43,8 @@ require('./http-response-handler.js'); changeParticipation: changeParticipation, modifyPublicRights: modifyPublicRights, exportCalendar, - getSecretAddress + getSecretAddress, + moveEvent }; //////////// @@ -331,5 +332,21 @@ require('./http-response-handler.js'); return $q.reject(error); }); } + + /** + * Move an event from one calendar to another + * + * @param {String} eventPath the path of the event. + * @param {String} destinationCalendarId the calendar id + * @returns {Object} the http response. + */ + function moveEvent(originalEventPath, destinationEventPath) { + const headers = { + Destination: destinationEventPath, + Overwrite: 'F' + }; + + return calDavRequest('move', originalEventPath, headers); + } } })(angular); diff --git a/src/esn.calendar.libs/app/services/calendar-api.spec.js b/src/esn.calendar.libs/app/services/calendar-api.spec.js index c7cf4fef..62b9581a 100644 --- a/src/esn.calendar.libs/app/services/calendar-api.spec.js +++ b/src/esn.calendar.libs/app/services/calendar-api.spec.js @@ -688,4 +688,22 @@ describe('The calendar module apis', function() { }); }); + describe('The moveEvent request', function() { + it('should send a MOVE request with the correct headers', function() { + const originalEventPath = '/calendars/user/calendar1/event.json'; + const destinationEventPath = '/calendars/user/calendar2/event.json'; + const expectedHeaders = { + Destination: destinationEventPath, + Overwrite: 'F', + Authorization: 'Bearer jwt', + Accept: 'application/json, text/plain, */*' + }; + + this.$httpBackend.expect('MOVE', originalEventPath, null, expectedHeaders) + .respond(201, {}); + + this.calendarAPI.moveEvent(originalEventPath, destinationEventPath); + this.$httpBackend.flush(); + }); + }); }); diff --git a/src/esn.calendar.libs/app/services/event-service.js b/src/esn.calendar.libs/app/services/event-service.js index b21f3e11..fef02096 100644 --- a/src/esn.calendar.libs/app/services/event-service.js +++ b/src/esn.calendar.libs/app/services/event-service.js @@ -59,6 +59,7 @@ function calEventService( self.getEventByUID = getEventByUID; self.getEventFromICSUrl = getEventFromICSUrl; self.onEventCreatedOrUpdated = onEventCreatedOrUpdated; + self.moveEvent = moveEvent; //////////// @@ -575,4 +576,8 @@ function calEventService( return new CalendarShell(ICAL.Component.fromString(response.data)); }); } + + function moveEvent(source, destination) { + return calendarAPI.moveEvent(source, destination); + } } diff --git a/src/esn.calendar.libs/app/services/event-service.spec.js b/src/esn.calendar.libs/app/services/event-service.spec.js index cae27a65..de0a3c4b 100644 --- a/src/esn.calendar.libs/app/services/event-service.spec.js +++ b/src/esn.calendar.libs/app/services/event-service.spec.js @@ -2041,4 +2041,15 @@ describe('The calEventService service', function() { self.$rootScope.$digest(); }); }); + + describe('the moveEvent function', function() { + it('should call the calendarAPI.moveEvent function', function() { + const spy = sinon.stub(self.calendarAPI, 'moveEvent').returns($q.when()); + + self.calEventService.moveEvent('/path/to/event.ics', '/path/to/new/event.ics') + .then(function() { + expect(spy).to.have.been.calledWith('/path/to/event.ics', '/path/to/new/event.ics'); + }); + }); + }); });