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 all 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
2 changes: 1 addition & 1 deletion build/custom.mk
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ GO_BUILD_FLAGS = -ldflags '$(LDFLAGS)'
mock:
ifneq ($(HAS_SERVER),)
go install github.com/golang/mock/[email protected]
mockgen -destination calendar/jobs/mock_cluster/mock_cluster.go github.com/mattermost/mattermost-plugin-api/cluster JobPluginAPI
mockgen -destination calendar/jobs/mock_cluster/mock_cluster.go github.com/mattermost/mattermost/server/public/pluginapi/cluster JobPluginAPI
mockgen -destination calendar/engine/mock_engine/mock_engine.go $(REPOSITORY_URL)/calendar/engine Engine
mockgen -destination calendar/engine/mock_welcomer/mock_welcomer.go -package mock_welcomer $(REPOSITORY_URL)/calendar/engine Welcomer
mockgen -destination calendar/engine/mock_plugin_api/mock_plugin_api.go -package mock_plugin_api $(REPOSITORY_URL)/calendar/engine PluginAPI
Expand Down
153 changes: 135 additions & 18 deletions calendar/engine/availability.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package engine

import (
"fmt"
"sort"
"time"

"github.com/mattermost/mattermost/server/public/model"
Expand Down Expand Up @@ -109,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.UpdateStatus || user.Settings.ReceiveReminders) {
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 @@ -153,6 +154,14 @@ func (m *mscalendar) retrieveUsersToSync(userIndex store.UserIndex, syncJobSumma
return users, calendarViews, fmt.Errorf("no calendar views found")
}

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

return users, calendarViews, nil
}

Expand Down Expand Up @@ -230,7 +239,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.UpdateStatus {
if u.IsConfiguredForStatusUpdates() || u.IsConfiguredForCustomStatusUpdates() {
toUpdate = append(toUpdate, u)
}
}
Expand Down Expand Up @@ -278,38 +287,112 @@ func (m *mscalendar) setUserStatuses(users []*store.User, calendarViews []*remot
continue
}

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

var err error
res, isStatusChanged, err = m.setStatusFromCalendarView(user, status, view)
if err != nil {
if numberOfLogs < logTruncateLimit {
m.Logger.Warnf("Error setting user %s status. err=%v", user.MattermostUserID, err)
} else if numberOfLogs == logTruncateLimit {
m.Logger.Warnf(logTruncateMsg)
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)
} else if numberOfLogs == logTruncateLimit {
m.Logger.Warnf(logTruncateMsg)
}
numberOfLogs++
numberOfUserErrorInStatusChange++
}
if isStatusChanged {
numberOfUserStatusChange++
}
numberOfLogs++
numberOfUserErrorInStatusChange++
}
if isStatusChanged {
numberOfUserStatusChange++

if user.IsConfiguredForCustomStatusUpdates() {
res, isStatusChanged, err = m.setCustomStatusFromCalendarView(user, events)
if err != nil {
if numberOfLogs < logTruncateLimit {
m.Logger.Warnf("Error setting user %s custom status. err=%v", user.MattermostUserID, err)
} else if numberOfLogs == logTruncateLimit {
m.Logger.Warnf(logTruncateMsg)
}
numberOfLogs++
numberOfUserErrorInStatusChange++
}

// 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 == store.NotSetStatusOption {
numberOfUserStatusChange++
}
}
}

if res != "" {
return res, numberOfUserStatusChange, numberOfUserErrorInStatusChange, nil
}

return utils.JSONBlock(calendarViews), numberOfUserStatusChange, numberOfUserErrorInStatusChange, nil
}

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

if len(events) == 0 {
if user.IsCustomStatusSet {
if err := m.PluginAPI.RemoveMattermostUserCustomStatus(user.MattermostUserID); err != nil {
m.Logger.Warnf("Error removing user %s custom status. err=%v", user.MattermostUserID, err)
}

if err := m.Store.StoreUserCustomStatusUpdates(user.MattermostUserID, false); err != nil {
return "", isStatusChanged, err
}
}

return "No event present to set 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 has a custom status set, ignoring custom status change", isStatusChanged, nil
}

if appErr := m.PluginAPI.UpdateMattermostUserCustomStatus(user.MattermostUserID, &model.CustomStatus{
Emoji: "calendar",
Text: "In a meeting",
ExpiresAt: events[0].End.Time(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is an event at events[1] not important here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Updated the flow now

Duration: "date_and_time",
}); appErr != nil {
return "", isStatusChanged, appErr
}

isStatusChanged = true
if err := m.Store.StoreUserCustomStatusUpdates(user.MattermostUserID, true); err != nil {
return "", isStatusChanged, err
}

return "", isStatusChanged, nil
}

func (m *mscalendar) setStatusFromCalendarView(user *store.User, status *model.Status, events []*remote.Event) (string, bool, error) {
isStatusChanged := false
currentStatus := status.Status
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)
busyStatus := model.StatusDnd
if user.Settings.ReceiveNotificationsDuringMeeting {
if user.Settings.UpdateStatusFromOptions == store.AwayStatusOption {
busyStatus = model.StatusAway
}

Expand Down Expand Up @@ -424,7 +507,7 @@ func (m *mscalendar) setStatusOrAskUser(user *store.User, currentStatus *model.S

if !isFree {
toSet = model.StatusDnd
if user.Settings.ReceiveNotificationsDuringMeeting {
if user.Settings.UpdateStatusFromOptions == store.AwayStatusOption {
toSet = model.StatusAway
}
if !user.Settings.GetConfirmation {
Expand Down Expand Up @@ -563,12 +646,46 @@ 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" {
// 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
}

// 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 events
}

idx := 0
for i := 1; i < len(events); i++ {
if areEventsMergeable(events[idx], events[i]) {
events[idx].End = events[i].End
} else {
idx++
events[idx] = events[i]
}
}

return events[0 : idx+1]
}

/*
areEventsMergeable function checks if two events can be merged into a single event.
There are two conditions that are being checked in the function:
- If two events overlap, the end time of event1 will be
greater than or equal to event2 and we can merge those events into a single event.
For e.g.- event1: 1:01–1:04, event2: 1:03–1:05. Final event: 1:01–1:05.
- If the difference between event1 end time and event1 start time isor equal to StatusSyncJobInterval and the difference between event2 start time and event1 end time is less than or equal to StatusSyncJobInterval. This is done to merge those events that occur within the time span of StatusSyncJobInterval.
For e.g.- event1: 1:01–1:02, event2: 1:03–1:05, StatusSyncJobInterval: 5 mins. Final event: 1:01–1:05.
This is done to avoid skipping of event2 as both events are fetched together in a single API call when the job runs every 5 minutes.
*/
func areEventsMergeable(event1, event2 *remote.Event) bool {
return (event1.End.Time().UnixMicro() >= event2.Start.Time().UnixMicro()) || (event1.End.Time().Sub(event1.Start.Time()) <= StatusSyncJobInterval && event2.Start.Time().Sub(event1.End.Time()) <= StatusSyncJobInterval)
}
Loading