Remove other event fields ignored by server (#4888)
Update the set of ignored fields for event restores. Most important inclusion is the `iCalUId_v2` field which will cause failures if it's not removed. --- #### Does this PR need a docs update or release note? - [x] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Test Plan - [x] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
5aa9515d67
commit
5669619a8d
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Handle the case where an email cannot be retrieved from Exchange due to an `ErrorInvalidRecipients` error. In
|
||||
this case, Corso will skip over the item but report this in the backup summary.
|
||||
- Guarantee Exchange email restoration when restoring multiple attachments. Some previous restores were failing with `ErrorItemNotFound`.
|
||||
- Avoid Graph SDK `Requests must contain extension changes exclusively.` errors by removing server-populated field from restored event items.
|
||||
|
||||
## [v0.17.0] (beta) - 2023-12-11
|
||||
|
||||
|
||||
@ -58,6 +58,28 @@ func toMessage(orig models.Messageable) models.Messageable {
|
||||
return CloneMessageableFields(orig, message)
|
||||
}
|
||||
|
||||
// eventUnsupportedAdditionalData lists the set of additionalData keys that are
|
||||
// not needed for backup completion and may cause errors in Graph API when
|
||||
// restoring items.
|
||||
var eventUnsupportedAdditionalData = []string{
|
||||
// Will cause errors about needing to put extension data in their own requests
|
||||
// if not removed.
|
||||
"iCalUId_v2",
|
||||
// Appears to be duplicate of the iCalUId.
|
||||
"uid",
|
||||
// Navigation links from the calendar to the event itself.
|
||||
"calendar@odata.associationLink",
|
||||
"calendar@odata.navigationLink",
|
||||
// Seems like info about the request that generated the data response.
|
||||
"@odata.context",
|
||||
// Appears to be similar to the change tag.
|
||||
"@odata.etag",
|
||||
// Remove exceptions for recurring events. These will be present in objects
|
||||
// once we start using the API that is currently in beta.
|
||||
"cancelledOccurrences",
|
||||
"exceptionOccurrences",
|
||||
}
|
||||
|
||||
// ToEventSimplified transforms an event to simplified restore format
|
||||
// To overcome some of the MS Graph API challenges, the event object is modified in the following ways:
|
||||
// - Instead of adding attendees and generating spurious notifications,
|
||||
@ -79,6 +101,18 @@ func toEventSimplified(orig models.Eventable) models.Eventable {
|
||||
orig.SetWebLink(nil)
|
||||
orig.SetICalUId(nil)
|
||||
orig.SetId(nil)
|
||||
orig.SetOdataType(nil)
|
||||
orig.SetChangeKey(nil)
|
||||
|
||||
// Additional fields that don't have API support but are ignored by the
|
||||
// server.
|
||||
additionalData := orig.GetAdditionalData()
|
||||
|
||||
for _, key := range eventUnsupportedAdditionalData {
|
||||
delete(additionalData, key)
|
||||
}
|
||||
|
||||
orig.SetAdditionalData(additionalData)
|
||||
|
||||
// Sanitize recurrence timezone.
|
||||
if orig.GetRecurrence() != nil {
|
||||
@ -88,14 +122,6 @@ func toEventSimplified(orig models.Eventable) models.Eventable {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove exceptions for recurring events
|
||||
// These will be present in objects once we start using the API
|
||||
// that is currently in beta
|
||||
additionalData := orig.GetAdditionalData()
|
||||
delete(additionalData, "cancelledOccurrences")
|
||||
delete(additionalData, "exceptionOccurrences")
|
||||
orig.SetAdditionalData(additionalData)
|
||||
|
||||
return orig
|
||||
}
|
||||
|
||||
|
||||
@ -58,6 +58,32 @@ func (suite *TransformUnitTest) TestToEventSimplified_attendees() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *TransformUnitTest) TestToEventSimplified_noAdditionalRemovedFields() {
|
||||
t := suite.T()
|
||||
|
||||
ctx, flush := tester.NewContext(t)
|
||||
defer flush()
|
||||
|
||||
bytes := exchMock.EventWithRemovedFields("M365 Event Support Test")
|
||||
event, err := api.BytesToEventable(bytes)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
newEvent := toEventSimplified(event)
|
||||
|
||||
serializedBytes, err := api.Client{}.Events().Serialize(
|
||||
ctx,
|
||||
newEvent,
|
||||
"",
|
||||
"")
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
serializedString := string(serializedBytes)
|
||||
|
||||
for _, key := range eventUnsupportedAdditionalData {
|
||||
assert.NotContains(t, serializedString, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *TransformUnitTest) TestToEventSimplified_recurrence() {
|
||||
var (
|
||||
t = suite.T()
|
||||
|
||||
@ -95,6 +95,84 @@ var (
|
||||
"attendees":%s
|
||||
}`
|
||||
|
||||
eventWithRemovedFieldsTmpl = `{
|
||||
"iCalUId":"abce",
|
||||
"iCalUId_v2":"fghi",
|
||||
"uid":"fghi",
|
||||
"calendar@odata.associationLink":"some-link",
|
||||
"calendar@odata.navigationLink":"some-link",
|
||||
"@odata.context":"some-data",
|
||||
"@odata.etag":"foo",
|
||||
"categories":[],
|
||||
"changeKey":"0hATW1CAfUS+njw3hdxSGAAAJIxNug==",
|
||||
"createdDateTime":"2022-03-28T03:42:03Z",
|
||||
"lastModifiedDateTime":"2022-05-26T19:25:58Z",
|
||||
"allowNewTimeProposals":true,
|
||||
"body":{
|
||||
"content":"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body>` +
|
||||
`<p>%s</p></body></html>",
|
||||
"contentType":"html"
|
||||
},
|
||||
"bodyPreview":"%s",
|
||||
"end":{
|
||||
"dateTime":"%s",
|
||||
"timeZone":"UTC"
|
||||
},
|
||||
"hideAttendees":false,
|
||||
"importance":"normal",
|
||||
"isAllDay":false,
|
||||
"isCancelled":false,
|
||||
"isDraft":false,
|
||||
"isOnlineMeeting":false,
|
||||
"isOrganizer":false,
|
||||
"isReminderOn":true,
|
||||
"location":{
|
||||
"displayName":"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)",
|
||||
"locationType":"default",
|
||||
"uniqueId":"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)",
|
||||
"uniqueIdType":"private"
|
||||
},
|
||||
"locations":[
|
||||
{
|
||||
"displayName":"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)",
|
||||
"locationType":"default",
|
||||
"uniqueId":"",
|
||||
"uniqueIdType":"unknown"
|
||||
}
|
||||
],
|
||||
"onlineMeetingProvider":"unknown",
|
||||
"organizer":{
|
||||
"emailAddress":{
|
||||
"address":"%s",
|
||||
"name":"Anu Pierson"
|
||||
}
|
||||
},
|
||||
%s
|
||||
"originalEndTimeZone":"UTC",
|
||||
"originalStartTimeZone":"UTC",
|
||||
"reminderMinutesBeforeStart":15,
|
||||
"responseRequested":true,
|
||||
"responseStatus":{
|
||||
"response":"notResponded",
|
||||
"time":"0001-01-01T00:00:00Z"
|
||||
},
|
||||
"sensitivity":"normal",
|
||||
"showAs":"tentative",
|
||||
"start":{
|
||||
"dateTime":"%s",
|
||||
"timeZone":"UTC"
|
||||
},
|
||||
"subject":"%s",
|
||||
"type":"%s",
|
||||
"hasAttachments":%v,
|
||||
%s
|
||||
"webLink":"https://outlook.office365.com/owa/?itemid=AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAAAAAG76AAA%%3D&exvsurl=1&path=/calendar/item",
|
||||
"recurrence":%s,
|
||||
%s
|
||||
%s
|
||||
"attendees":%s
|
||||
}`
|
||||
|
||||
defaultEventBody = "This meeting is to review the latest Tailspin Toys project proposal.<br>\\r\\nBut why not eat some sushi while we’re at it? :)"
|
||||
defaultEventBodyPreview = "This meeting is to review the latest Tailspin Toys project proposal.\\r\\nBut why not eat some sushi while we’re at it? :)"
|
||||
defaultEventOrganizer = "foobar@8qzvrj.onmicrosoft.com"
|
||||
@ -395,6 +473,17 @@ func EventWith(
|
||||
organizer, subject, body, bodyPreview,
|
||||
originalStartDate, startDateTime, endDateTime, recurrence, attendees,
|
||||
attachments, cancelledOccurrences, exceptionOccurrences string,
|
||||
) []byte {
|
||||
return eventWith(
|
||||
eventTmpl, organizer, subject, body, bodyPreview,
|
||||
originalStartDate, startDateTime, endDateTime, recurrence, attendees,
|
||||
attachments, cancelledOccurrences, exceptionOccurrences)
|
||||
}
|
||||
|
||||
func eventWith(
|
||||
tmpl, organizer, subject, body, bodyPreview,
|
||||
originalStartDate, startDateTime, endDateTime, recurrence, attendees,
|
||||
attachments, cancelledOccurrences, exceptionOccurrences string,
|
||||
) []byte {
|
||||
hasAttachments := len(attachments) > 0
|
||||
startDateTime = strings.TrimSuffix(startDateTime, "Z")
|
||||
@ -414,7 +503,7 @@ func EventWith(
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf(
|
||||
eventTmpl,
|
||||
tmpl,
|
||||
body,
|
||||
bodyPreview,
|
||||
endDateTime,
|
||||
@ -430,3 +519,40 @@ func EventWith(
|
||||
exceptionOccurrences,
|
||||
attendees))
|
||||
}
|
||||
|
||||
func EventWithRemovedFields(subject string) []byte {
|
||||
var (
|
||||
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||
atTime = dttm.Format(at)
|
||||
timeSlice = strings.Split(atTime, "T")
|
||||
newTime = dttm.Format(tomorrow.AddDate(0, 0, 1))
|
||||
originalStartDate = dttm.FormatTo(at, dttm.TabularOutput)
|
||||
nextYear = tomorrow.AddDate(1, 0, 0)
|
||||
)
|
||||
|
||||
recurrence := string(fmt.Sprintf(
|
||||
recurrenceTmpl,
|
||||
strconv.Itoa(int(at.Month())),
|
||||
strconv.Itoa(at.Day()),
|
||||
timeSlice[0],
|
||||
`"UTC"`))
|
||||
|
||||
cancelledInstances := []string{fmt.Sprintf(cancelledOccurrenceInstanceFormat, dttm.FormatTo(nextYear, dttm.DateOnly))}
|
||||
cancelledOccurrences := fmt.Sprintf(cancelledOccurrencesFormat, strings.Join(cancelledInstances, ","))
|
||||
|
||||
exceptionEvent := EventWith(
|
||||
defaultEventOrganizer, subject+"(modified)",
|
||||
defaultEventBody, defaultEventBodyPreview,
|
||||
fmt.Sprintf(originalStartDateFormat, originalStartDate),
|
||||
newTime, newTime, NoRecurrence, attendeesTmpl,
|
||||
NoAttachments, NoCancelledOccurrences, NoExceptionOccurrences)
|
||||
exceptionOccurrences := fmt.Sprintf(exceptionOccurrencesFormat, exceptionEvent)
|
||||
|
||||
return eventWith(
|
||||
eventWithRemovedFieldsTmpl,
|
||||
defaultEventOrganizer, subject,
|
||||
defaultEventBody, defaultEventBodyPreview,
|
||||
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
||||
defaultEventAttachments, cancelledOccurrences, exceptionOccurrences)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user