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-66, mm-12, mm-47] - resolves all three issues #85

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ The meeting settings for each channel can be configured in the Channel Header Dr

Meeting settings include:

- Schedule Day: Day of the week when the meeting is scheduled.
- Hashtag Format: The format of the hashtag for the meeting date. The date format is based on [Go date and time formatting](https://yourbasic.org/golang/format-parse-string-time-date-example/#standard-time-and-date-formats).
The date format must be wrapped in double Braces ( {{ }} ).
A default is generated from the first 15 characters of the channel's name with the short name of the month and day (i.e. Dev-{{ Jan02 }}).
- Schedule Day: Day of the week when the meeting is being scheduled.
- Hashtag Format: The format of the hashtag for the meeting date.
A default hashtag is generated from the first 15 characters of the channel's name with the short name of the month and day (i.e. Dev_{{ Jan 2 }}).

#### Slash Commands to manage the meeting agenda:

Expand Down
Binary file modified assets/settingsDialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
153 changes: 143 additions & 10 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"fmt"
"regexp"
"sort"
"strings"
"time"

Expand All @@ -16,6 +18,13 @@ const (
wsEventList = "list"
)

// ParsedMeetingMessage is meeting message after being parsed
type ParsedMeetingMessage struct {
date string
number string // TODO we don't need it right now
textMessage string
}

const helpCommandText = "###### Mattermost Agenda Plugin - Slash Command Help\n" +
"The Agenda plugin lets you queue up meeting topics for channel discussion at a later time. When your meeting happens, you can click on the Hashtag to see all agenda items in the RHS. \n" +
"To configure the agenda for this channel, click on the Channel Name in Mattermost to access the channel options menu and select `Agenda Settings`" +
Expand Down Expand Up @@ -49,6 +58,9 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo
case "queue":
return p.executeCommandQueue(args), nil

case "requeue":
return p.executeCommandReQueue(args), nil

case "setting":
return p.executeCommandSetting(args), nil

Expand All @@ -69,7 +81,7 @@ func (p *Plugin) executeCommandList(args *model.CommandArgs) *model.CommandRespo
weekday = int(parsedWeekday)
}

hashtag, err := p.GenerateHashtag(args.ChannelId, nextWeek, weekday)
hashtag, err := p.GenerateHashtag(args.ChannelId, nextWeek, weekday, false, time.Now().Weekday())
if err != nil {
return responsef("Error calculating hashtags")
}
Expand Down Expand Up @@ -132,6 +144,11 @@ func (p *Plugin) executeCommandQueue(args *model.CommandArgs) *model.CommandResp
return responsef("Missing parameters for queue command")
}

meeting, err := p.GetMeeting(args.ChannelId)
if err != nil {
return responsef("Can't find the meeting")
}

nextWeek := false
weekday := -1
message := strings.Join(split[2:], " ")
Expand All @@ -147,31 +164,147 @@ func (p *Plugin) executeCommandQueue(args *model.CommandArgs) *model.CommandResp
message = strings.Join(split[3:], " ")
}

hashtag, error := p.GenerateHashtag(args.ChannelId, nextWeek, weekday)
hashtag, error := p.GenerateHashtag(args.ChannelId, nextWeek, weekday, false, time.Now().Weekday())
if error != nil {
return responsef("Error calculating hashtags. Check the meeting settings for this channel.")
}

searchResults, appErr := p.API.SearchPostsInTeamForUser(args.TeamId, args.UserId, model.SearchParameter{Terms: &hashtag})
numQueueItems, itemErr := calculateQueItemNumberAndUpdateOldItems(meeting, args, p, hashtag)
if itemErr != nil {
return itemErr
}

_, appErr := p.API.CreatePost(&model.Post{
UserId: args.UserId,
ChannelId: args.ChannelId,
RootId: args.RootId,
Message: fmt.Sprintf("#### %v %v) %v", hashtag, numQueueItems, message),
})
if appErr != nil {
return responsef("Error calculating list number")
return responsef("Error creating post: %s", appErr.Message)
}

return &model.CommandResponse{}
}

func calculateQueItemNumberAndUpdateOldItems(meeting *Meeting, args *model.CommandArgs, p *Plugin, hashtag string) (int, *model.CommandResponse) {
searchResults, appErr := p.API.SearchPostsInTeamForUser(args.TeamId, args.UserId, model.SearchParameter{Terms: &hashtag})
if appErr != nil {
return 0, responsef("Error calculating list number")
}

counter := 1

var sortedPosts []*model.Post
// TODO we won't need to do this once we fix https://github.com/mattermost/mattermost-server/issues/11006
for _, post := range searchResults.PostList.Posts {
sortedPosts = append(sortedPosts, post)
}

sort.Slice(sortedPosts, func(i, j int) bool {
return sortedPosts[i].CreateAt < sortedPosts[j].CreateAt
})

for _, post := range sortedPosts {
_, parsedMessage, _ := parseMeetingPost(meeting, post)
_, findErr := p.API.UpdatePost(&model.Post{
Id: post.Id,
UserId: args.UserId,
ChannelId: args.ChannelId,
RootId: args.RootId,
Message: fmt.Sprintf("#### %v %v) %v", hashtag, counter, parsedMessage.textMessage),
})
counter++
if findErr != nil {
return 0, responsef("Error updating post: %s", findErr.Message)
}
}
return counter, nil
}

func (p *Plugin) executeCommandReQueue(args *model.CommandArgs) *model.CommandResponse {
split := strings.Fields(args.Command)

if len(split) <= 2 {
return responsef("Missing parameters for requeue command")
}

meeting, err := p.GetMeeting(args.ChannelId)
if err != nil {
return responsef("Can't find the meeting")
}

postList := *searchResults.PostList
numQueueItems := len(postList.Posts)
oldPostID := split[2]
postToBeReQueued, _ := p.API.GetPost(oldPostID)
hashtagDateFormat, parsedMeetingMessage, errors := parseMeetingPost(meeting, postToBeReQueued)
if errors != nil {
return errors
}

originalPostDate := strings.ReplaceAll(strings.TrimSpace(parsedMeetingMessage.date), "_", " ") // reverse what we do to make it a valid hashtag
originalPostMessage := strings.TrimSpace(parsedMeetingMessage.textMessage)

today := time.Now()
local, _ := time.LoadLocation("Local")
formattedDate, _ := time.ParseInLocation(hashtagDateFormat, originalPostDate, local)
if formattedDate.Year() == 0 {
thisYear := today.Year()
formattedDate = formattedDate.AddDate(thisYear, 0, 0)
}

_, appErr = p.API.CreatePost(&model.Post{
if today.Year() <= formattedDate.Year() && today.YearDay() < formattedDate.YearDay() {
return responsef("We don't support re-queuing future items, only available for present and past items.")
}

hashtag, error := p.GenerateHashtag(args.ChannelId, false, -1, true, formattedDate.Weekday())
if error != nil {
return responsef("Error calculating hashtags. Check the meeting settings for this channel.")
}

numQueueItems, itemErr := calculateQueItemNumberAndUpdateOldItems(meeting, args, p, hashtag)
if itemErr != nil {
return itemErr
}

_, appErr := p.API.UpdatePost(&model.Post{
Id: oldPostID,
UserId: args.UserId,
ChannelId: args.ChannelId,
RootId: args.RootId,
Message: fmt.Sprintf("#### %v %v) %v", hashtag, numQueueItems+1, message),
Message: fmt.Sprintf("#### %v %v) %v", hashtag, numQueueItems, originalPostMessage),
})
if appErr != nil {
return responsef("Error creating post: %s", appErr.Message)
return responsef("Error updating post: %s", appErr.Message)
}
return &model.CommandResponse{Text: fmt.Sprintf("Item has been Re-queued to %v", hashtag), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}

return &model.CommandResponse{}
func parseMeetingPost(meeting *Meeting, post *model.Post) (string, ParsedMeetingMessage, *model.CommandResponse) {
var (
prefix string
hashtagDateFormat string
)
if matchGroups := meetingDateFormatRegex.FindStringSubmatch(meeting.HashtagFormat); len(matchGroups) == 4 {
prefix = matchGroups[1]
hashtagDateFormat = strings.TrimSpace(matchGroups[2])
} else {
return "", ParsedMeetingMessage{}, responsef("Error 267")
}

var (
messageRegexFormat = regexp.MustCompile(fmt.Sprintf(`(?m)^#### #%s(?P<date>.*) ([0-9]+)\) (?P<message>.*)?$`, prefix))
)

matchGroups := messageRegexFormat.FindStringSubmatch(post.Message)
if len(matchGroups) == 4 {
parsedMeetingMessage := ParsedMeetingMessage{
date: matchGroups[1],
number: matchGroups[2],
textMessage: matchGroups[3],
}
return hashtagDateFormat, parsedMeetingMessage, nil
}
return hashtagDateFormat, ParsedMeetingMessage{}, responsef("Please ensure correct message format!")
}

func (p *Plugin) executeCommandHelp(args *model.CommandArgs) *model.CommandResponse {
Expand Down
27 changes: 20 additions & 7 deletions server/meeting.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
type Meeting struct {
ChannelID string `json:"channelId"`
Schedule []time.Weekday `json:"schedule"`
HashtagFormat string `json:"hashtagFormat"` // Default: {ChannelName}-Jan02
HashtagFormat string `json:"hashtagFormat"` // Default: {ChannelName}-Jan-2
}

// GetMeeting returns a meeting
Expand All @@ -37,9 +37,10 @@ func (p *Plugin) GetMeeting(channelID string) (*Meeting, error) {
if err != nil {
return nil, err
}
paddedChannelName := strings.ReplaceAll(channel.Name, "-", "_")
meeting = &Meeting{
Schedule: []time.Weekday{time.Thursday},
HashtagFormat: strings.Join([]string{fmt.Sprintf("%.15s", channel.Name), "{{ Jan02 }}"}, "-"),
HashtagFormat: strings.Join([]string{fmt.Sprintf("%.15s", paddedChannelName), "{{ Jan 2 }}"}, "_"),
ChannelID: channelID,
}
}
Expand All @@ -62,7 +63,7 @@ func (p *Plugin) SaveMeeting(meeting *Meeting) error {
}

// GenerateHashtag returns a meeting hashtag
func (p *Plugin) GenerateHashtag(channelID string, nextWeek bool, weekday int) (string, error) {
func (p *Plugin) GenerateHashtag(channelID string, nextWeek bool, weekday int, requeue bool, assignedDay time.Weekday) (string, error) {
meeting, err := p.GetMeeting(channelID)
if err != nil {
return "", err
Expand All @@ -75,10 +76,20 @@ func (p *Plugin) GenerateHashtag(channelID string, nextWeek bool, weekday int) (
return "", err
}
} else {
// Get date for the list of days of the week
if meetingDate, err = nextWeekdayDateInWeek(meeting.Schedule, nextWeek); err != nil {
return "", err
// user didn't provide any specific date, Get date for the list of days of the week
if !requeue {
if meetingDate, err = nextWeekdayDateInWeek(meeting.Schedule, nextWeek); err != nil {
return "", err
}
} else {
if len(meeting.Schedule) == 1 && meeting.Schedule[0] == assignedDay { // if this day is the only day selected in settings
nextWeek = true
}
if meetingDate, err = nextWeekdayDateInWeekSkippingDay(meeting.Schedule, nextWeek, assignedDay); err != nil {
return "", err
}
}
//---- requeue Logic
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this line?

Copy link
Contributor

Choose a reason for hiding this comment

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

sure

}

var hashtag string
Expand All @@ -92,8 +103,10 @@ func (p *Plugin) GenerateHashtag(channelID string, nextWeek bool, weekday int) (
prefix = matchGroups[1]
hashtagFormat = strings.TrimSpace(matchGroups[2])
postfix = matchGroups[3]
formattedDate := meetingDate.Format(hashtagFormat)
formattedDate = strings.ReplaceAll(formattedDate, " ", "_")

hashtag = fmt.Sprintf("#%s%v%s", prefix, meetingDate.Format(hashtagFormat), postfix)
hashtag = fmt.Sprintf("#%s%v%s", prefix, formattedDate, postfix)
} else {
hashtag = fmt.Sprintf("#%s", meeting.HashtagFormat)
}
Expand Down
24 changes: 12 additions & 12 deletions server/meeting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
meeting: &Meeting{
ChannelID: "QA",
Schedule: []time.Weekday{time.Wednesday},
HashtagFormat: "{{Jan02}}",
HashtagFormat: "{{Jan 2}}",
}},
want: "#" + assertNextWeekdayDate(time.Wednesday, true).Format("Jan02"),
want: "#" + strings.ReplaceAll(assertNextWeekdayDate(time.Wednesday, true).Format("Jan 2"), " ", "_"),
wantErr: false,
},
{
Expand All @@ -67,9 +67,9 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
meeting: &Meeting{
ChannelID: "QA Backend",
Schedule: []time.Weekday{time.Monday},
HashtagFormat: "QA-{{January 02 2006}}",
HashtagFormat: "QA_{{January 02 2006}}",
}},
want: "#QA-" + assertNextWeekdayDate(time.Monday, true).Format("January 02 2006"),
want: "#QA_" + strings.ReplaceAll(assertNextWeekdayDate(time.Monday, true).Format("January 02 2006"), " ", "_"),
wantErr: false,
},
{
Expand All @@ -81,7 +81,7 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
Schedule: []time.Weekday{time.Monday},
HashtagFormat: "{{January 02 2006}}.vue",
}},
want: "#" + assertNextWeekdayDate(time.Monday, false).Format("January 02 2006") + ".vue",
want: "#" + strings.ReplaceAll(assertNextWeekdayDate(time.Monday, false).Format("January 02 2006"), " ", "_") + ".vue",
wantErr: false,
},
{
Expand All @@ -93,7 +93,7 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
Schedule: []time.Weekday{time.Monday},
HashtagFormat: "React {{January 02 2006}} Born",
}},
want: "#React " + assertNextWeekdayDate(time.Monday, false).Format("January 02 2006") + " Born",
want: "#React " + strings.ReplaceAll(assertNextWeekdayDate(time.Monday, false).Format("January 02 2006"), " ", "_") + " Born",
wantErr: false,
},
{
Expand All @@ -105,7 +105,7 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
Schedule: []time.Weekday{time.Monday},
HashtagFormat: "January 02 2006 {{January 02 2006}} January 02 2006",
}},
want: "#January 02 2006 " + assertNextWeekdayDate(time.Monday, false).Format("January 02 2006") + " January 02 2006",
want: "#January 02 2006 " + strings.ReplaceAll(assertNextWeekdayDate(time.Monday, false).Format("January 02 2006"), " ", "_") + " January 02 2006",
wantErr: false,
},
{
Expand All @@ -117,7 +117,7 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
Schedule: []time.Weekday{time.Monday},
HashtagFormat: "{{ January 02 2006 }}",
}},
want: "#" + assertNextWeekdayDate(time.Monday, false).Format("January 02 2006"),
want: "#" + strings.ReplaceAll(assertNextWeekdayDate(time.Monday, false).Format("January 02 2006"), " ", "_"),
wantErr: false,
},
{
Expand All @@ -129,7 +129,7 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
Schedule: []time.Weekday{time.Monday},
HashtagFormat: "{{ Mon Jan _2 }}",
}},
want: "#" + assertNextWeekdayDate(time.Monday, false).Format("Mon Jan _2"),
want: "#" + strings.ReplaceAll(assertNextWeekdayDate(time.Monday, false).Format("Mon Jan _2"), " ", "_"),
wantErr: false,
},
}
Expand All @@ -138,7 +138,7 @@ func TestPlugin_GenerateHashtag(t *testing.T) {
jsonMeeting, err := json.Marshal(tt.args.meeting)
tAssert.Nil(err)
api.On("KVGet", tt.args.meeting.ChannelID).Return(jsonMeeting, nil)
got, err := mPlugin.GenerateHashtag(tt.args.meeting.ChannelID, tt.args.nextWeek, -1)
got, err := mPlugin.GenerateHashtag(tt.args.meeting.ChannelID, tt.args.nextWeek, -1, false, time.Now().Weekday()) // last parameter doesn't matter unless requeue
if (err != nil) != tt.wantErr {
t.Errorf("GenerateHashtag() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -176,7 +176,7 @@ func TestPlugin_GetMeeting(t *testing.T) {
},
want: &Meeting{
Schedule: []time.Weekday{time.Thursday},
HashtagFormat: "Short-{{ Jan02 }}",
HashtagFormat: "Short_{{ Jan 2 }}",
ChannelID: "#short.name.channel",
},
wantErr: false,
Expand All @@ -190,7 +190,7 @@ func TestPlugin_GetMeeting(t *testing.T) {
},
want: &Meeting{
Schedule: []time.Weekday{time.Thursday},
HashtagFormat: "Very Long Chann-{{ Jan02 }}",
HashtagFormat: "Very Long Chann_{{ Jan 2 }}",
ChannelID: "#long.name.channel",
},
wantErr: false,
Expand Down
2 changes: 1 addition & 1 deletion server/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestServeHTTP(t *testing.T) {
defaultMeeting := &Meeting{
ChannelID: "myChannelId",
Schedule: []time.Weekday{time.Thursday},
HashtagFormat: "Jan02",
HashtagFormat: "Jan-2",
}

jsonMeeting, err := json.Marshal(defaultMeeting)
Expand Down
Loading