Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-228] Add feature to update custom status and status of the user during meeting #359

Merged
merged 23 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed87b58
[MM-188] Add feature to update custom status and status of the user d…
ayusht2810 Feb 6, 2024
b696601
[MM-188] Add test case for custom status change
ayusht2810 Feb 6, 2024
d1c271b
Fix test cases
raghavaggarwal2308 Mar 4, 2024
a9c4531
[MM-188] Review fixes: Update constant name, add some comments and re…
ayusht2810 Feb 16, 2024
10d6649
[MM-252] Review fixes
raghavaggarwal2308 Feb 27, 2024
3c2ed2b
[MM-250] Review fixes
raghavaggarwal2308 Feb 28, 2024
56441cc
fix link error
raghavaggarwal2308 Mar 4, 2024
8fd8412
Fix failing test
raghavaggarwal2308 Mar 4, 2024
642b212
[MM-256] Added test cases for back-to-back event and non overlapping …
raghavaggarwal2308 Mar 4, 2024
59f5951
Merge branch 'master' into MM-188-fix
fmartingr May 21, 2024
85fe326
Merge branch 'master' into MM-188-fix
raghavaggarwal2308 Jun 11, 2024
ef5152e
Merge branch 'master' into MM-188-fix
hanzei Jun 26, 2024
67c24e7
Merge branch 'master' into MM-188-fix
raghavaggarwal2308 Jul 2, 2024
dbd3e5d
[MM-188] Fix review fixes: Use legacy settings, update and add new te…
ayusht2810 Jul 4, 2024
14eddb0
[MM-188] Update case to filter events on attendee as well
ayusht2810 Jul 4, 2024
8f503b1
Merge branch 'master' into MM-188-fix
ayusht2810 Jul 4, 2024
0ad2bb8
Merge branch 'master' into MM-188-fix
wiggin77 Jul 4, 2024
03b1d25
Merge branch 'master' into MM-188-fix
raghavaggarwal2308 Jul 10, 2024
5b08060
[MM-188] Add comments to the code
ayusht2810 Jul 10, 2024
c7441e0
Merge branch 'master' into MM-188-fix
wiggin77 Aug 1, 2024
f287370
Merge branch 'master' into MM-188-fix
raghavaggarwal2308 Aug 5, 2024
b851b21
Merge branch 'master' into MM-188-fix
raghavaggarwal2308 Aug 8, 2024
716ebcc
Merge branch 'master' of github.com:mattermost/mattermost-plugin-msca…
raghavaggarwal2308 Aug 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 38 additions & 48 deletions calendar/engine/availability.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (m *mscalendar) retrieveUsersToSync(userIndex store.UserIndex, syncJobSumma
}

// If user does not have the proper features enabled, just go to the next one
if !(user.Settings.UpdateStatusFromOptions != NotSetStatusOption || user.Settings.ReceiveReminders || user.Settings.SetCustomStatus) {
if !(user.IsConfiguredForStatusUpdates() || user.IsConfiguredForCustomStatusUpdates() || user.Settings.ReceiveReminders) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would expect these methods to be defined on user.Settings, though I don't think it matters. This current way allows for less typing throughout the usages of the method

continue
}

Expand Down Expand Up @@ -154,6 +154,13 @@ func (m *mscalendar) retrieveUsersToSync(userIndex store.UserIndex, syncJobSumma
return users, calendarViews, fmt.Errorf("no calendar views found")
}

for _, view := range calendarViews {
ayusht2810 marked this conversation as resolved.
Show resolved Hide resolved
event := view.Events
ayusht2810 marked this conversation as resolved.
Show resolved Hide resolved
sort.Slice(event, func(i, j int) bool {
return event[i].Start.Time().UnixMicro() < event[j].Start.Time().UnixMicro()
})
}

return users, calendarViews, nil
}

Expand Down Expand Up @@ -231,7 +238,7 @@ func (m *mscalendar) setUserStatuses(users []*store.User, calendarViews []*remot
numberOfLogs, numberOfUserStatusChange, numberOfUserErrorInStatusChange := 0, 0, 0
toUpdate := []*store.User{}
for _, u := range users {
if u.Settings.UpdateStatusFromOptions != NotSetStatusOption || u.Settings.SetCustomStatus {
if u.IsConfiguredForStatusUpdates() || u.IsConfiguredForCustomStatusUpdates() {
toUpdate = append(toUpdate, u)
}
}
Expand Down Expand Up @@ -279,9 +286,12 @@ func (m *mscalendar) setUserStatuses(users []*store.User, calendarViews []*remot
continue
}

events := filterBusyAndAttendeeEvents(view.Events)
events = getMergedEvents(events)

var err error
if user.Settings.UpdateStatusFromOptions != NotSetStatusOption {
res, isStatusChanged, err = m.setStatusFromCalendarView(user, status, view)
if user.IsConfiguredForStatusUpdates() {
res, isStatusChanged, err = m.setStatusFromCalendarView(user, status, events)
if err != nil {
if numberOfLogs < logTruncateLimit {
m.Logger.Warnf("Error setting user %s status. err=%v", user.MattermostUserID, err)
Expand All @@ -296,11 +306,11 @@ func (m *mscalendar) setUserStatuses(users []*store.User, calendarViews []*remot
}
}

if user.Settings.SetCustomStatus {
res, isStatusChanged, err = m.setCustomStatusFromCalendarView(user, view)
if user.IsConfiguredForCustomStatusUpdates() {
res, isStatusChanged, err = m.setCustomStatusFromCalendarView(user, events)
if err != nil {
if numberOfLogs < logTruncateLimit {
m.Logger.Warnf("Error setting user %s status. err=%v", user.MattermostUserID, err)
m.Logger.Warnf("Error setting user %s custom status. err=%v", user.MattermostUserID, err)
} else if numberOfLogs == logTruncateLimit {
m.Logger.Warnf(logTruncateMsg)
}
Expand All @@ -309,7 +319,7 @@ func (m *mscalendar) setUserStatuses(users []*store.User, calendarViews []*remot
}

// Increment count only when we have not updated the status of the user from the options to have status change count per user.
if isStatusChanged && user.Settings.UpdateStatusFromOptions == NotSetStatusOption {
if isStatusChanged && user.Settings.UpdateStatusFromOptions == store.NotSetStatusOption {
numberOfUserStatusChange++
}
}
Expand All @@ -322,13 +332,12 @@ func (m *mscalendar) setUserStatuses(users []*store.User, calendarViews []*remot
return utils.JSONBlock(calendarViews), numberOfUserStatusChange, numberOfUserErrorInStatusChange, nil
}

func (m *mscalendar) setCustomStatusFromCalendarView(user *store.User, res *remote.ViewCalendarResponse) (string, bool, error) {
func (m *mscalendar) setCustomStatusFromCalendarView(user *store.User, events []*remote.Event) (string, bool, error) {
isStatusChanged := false
if !user.Settings.SetCustomStatus {
if !user.IsConfiguredForCustomStatusUpdates() {
return "User doesn't want to set custom status", isStatusChanged, nil
}

events := filterBusyEvents(res.Events)
if len(events) == 0 {
if user.IsCustomStatusSet {
if err := m.PluginAPI.RemoveMattermostUserCustomStatus(user.MattermostUserID); err != nil {
Expand All @@ -343,23 +352,14 @@ func (m *mscalendar) setCustomStatusFromCalendarView(user *store.User, res *remo
return "No event present to set custom status", isStatusChanged, nil
}

if checkOverlappingEvents(events) {
return "Overlapping events, not setting a custom status", isStatusChanged, nil
}

// Not setting custom status for events without attendees since those are unlikely to be meetings.
if len(events[0].Attendees) < 1 {
return "No attendee present, not setting custom status", isStatusChanged, nil
}

currentUser, err := m.PluginAPI.GetMattermostUser(user.MattermostUserID)
if err != nil {
return "", isStatusChanged, err
}

currentCustomStatus := currentUser.GetCustomStatus()
if currentCustomStatus != nil && !user.IsCustomStatusSet {
return "User already have a custom status set, ignoring custom status change", isStatusChanged, nil
return "User already has a custom status set, ignoring custom status change", isStatusChanged, nil
}

if appErr := m.PluginAPI.UpdateMattermostUserCustomStatus(user.MattermostUserID, &model.CustomStatus{
Expand All @@ -379,25 +379,19 @@ func (m *mscalendar) setCustomStatusFromCalendarView(user *store.User, res *remo
return "", isStatusChanged, nil
}

func (m *mscalendar) setStatusFromCalendarView(user *store.User, status *model.Status, res *remote.ViewCalendarResponse) (string, bool, error) {
func (m *mscalendar) setStatusFromCalendarView(user *store.User, status *model.Status, events []*remote.Event) (string, bool, error) {
isStatusChanged := false
currentStatus := status.Status
if user.Settings.UpdateStatusFromOptions == "" {
if !user.IsConfiguredForStatusUpdates() {
return "No value set from options to update status", isStatusChanged, nil
}

if currentStatus == model.StatusOffline && !user.Settings.GetConfirmation {
return "User offline and does not want status change confirmations. No status change", isStatusChanged, nil
}

events := filterBusyEvents(res.Events)

sort.Slice(events, func(i, j int) bool {
return events[i].Start.Time().UnixMicro() < events[j].Start.Time().UnixMicro()
})

busyStatus := model.StatusDnd
if user.Settings.UpdateStatusFromOptions == AwayStatusOption {
if user.Settings.UpdateStatusFromOptions == store.AwayStatusOption {
busyStatus = model.StatusAway
}

Expand Down Expand Up @@ -426,15 +420,6 @@ func (m *mscalendar) setStatusFromCalendarView(user *store.User, status *model.S
return message, isStatusChanged, nil
}

if checkOverlappingEvents(events) {
return "Overlapping events, not updating status", isStatusChanged, nil
}

// Not setting custom status for events without attendees since those are unlikely to be meetings.
if len(events[0].Attendees) < 1 {
return "No attendee present, not updating status", isStatusChanged, nil
}

remoteHashes := []string{}
for _, e := range events {
if e.IsCancelled {
Expand Down Expand Up @@ -521,7 +506,7 @@ func (m *mscalendar) setStatusOrAskUser(user *store.User, currentStatus *model.S

if !isFree {
toSet = model.StatusDnd
if user.Settings.UpdateStatusFromOptions == AwayStatusOption {
if user.Settings.UpdateStatusFromOptions == store.AwayStatusOption {
toSet = model.StatusAway
}
if !user.Settings.GetConfirmation {
Expand Down Expand Up @@ -660,27 +645,32 @@ func (m *mscalendar) notifyUpcomingEvents(mattermostUserID string, events []*rem
}
}

func filterBusyEvents(events []*remote.Event) []*remote.Event {
func filterBusyAndAttendeeEvents(events []*remote.Event) []*remote.Event {
result := []*remote.Event{}
for _, e := range events {
if e.ShowAs == "busy" && !e.IsCancelled {
// Not setting custom status for events without attendees since those are unlikely to be meetings.
if e.ShowAs == "busy" && !e.IsCancelled && len(e.Attendees) >= 1 {
result = append(result, e)
}
}
return result
}

// Function to check for overlapping events.
func checkOverlappingEvents(events []*remote.Event) bool {
// getMergedEvents accepts a sorted array of events, and returns events after merging them, if overlapping or if the meeting duration is less than StatusSyncJobInterval.
func getMergedEvents(events []*remote.Event) []*remote.Event {
if len(events) <= 1 {
return false
return events
}

idx := 0
for i := 1; i < len(events); i++ {
if events[i-1].End.Time().UnixMicro() > events[i].Start.Time().UnixMicro() {
return true
if (events[idx].End.Time().UnixMicro() >= events[i].Start.Time().UnixMicro()) || (events[idx].End.Time().Sub(events[idx].Start.Time()) <= StatusSyncJobInterval && events[i].Start.Time().Sub(events[idx].End.Time()) <= StatusSyncJobInterval) {
events[idx].End = events[i].End
} else {
idx++
events[idx] = events[i]
}
}

return false
return events[0 : idx+1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to put the body of this loop in its own function, and have it unit tested to show/document how it works

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickmister Added a comment to it. I don't think we need to separately test the function as it is already covered by its parent function.

}
Loading