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

Plugin review proposals #3

Merged
merged 6 commits into from
Jan 30, 2020
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Initial development as part of [Mattermost Hackathon 2019](https://github.com/ma

#### Meeting Settings Configuration

The meeting settings for each channel can be configured in the Channel Header Dropwdown (supported in [this WebApp branch](https://github.com/mattermost/mattermost-webapp/tree/MM-19902))
The meeting settings for each channel can be configured in the Channel Header Dropdown.

![channel_header_menu](./assets/channelHeaderDropdown.png)

Expand All @@ -26,7 +26,7 @@ Meeting settings include:
```
/agenda queue [next-week] message
```
Creates a post for the user with the given `message` for the next meeting date with the configured hashtag format preceding it.
Creates a post for the user with the given `message` for the next meeting date. The configured hashtag will precede the `message`.
If `next-week` is indicated (optional), it will use the date of the meeting in the next calendar week.

![post_example](./assets/postExample.png)
Expand Down
29 changes: 14 additions & 15 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ import (
const (
commandTriggerAgenda = "agenda"

WS_EVENT_LIST = "list"
wsEventList = "list"
)

func (p *Plugin) registerCommands() error {
if err := p.API.RegisterCommand(&model.Command{

Trigger: commandTriggerAgenda,
AutoComplete: true,
AutoCompleteHint: "[command]",
Expand All @@ -32,7 +31,7 @@ func (p *Plugin) registerCommands() error {
return nil
}

// ExecuteCommand
// ExecuteCommand executes a command that has been previously registered via the RegisterCommand
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
split := strings.Fields(args.Command)
command := split[0]
Expand Down Expand Up @@ -76,16 +75,17 @@ func (p *Plugin) executeCommandList(args *model.CommandArgs) *model.CommandRespo
split := strings.Fields(args.Command)
nextWeek := len(split) > 2 && split[2] == "next-week"

hashtag, error := p.GenerateHashtag(args.ChannelId, nextWeek)
if error != nil {
hashtag, err := p.GenerateHashtag(args.ChannelId, nextWeek)
if err != nil {
return &model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
Text: fmt.Sprintf("Error calculating hashtags"),
}
}

//TODO need to understand this
p.API.PublishWebSocketEvent(
WS_EVENT_LIST,
wsEventList,
map[string]interface{}{
"hashtag": hashtag,
},
Expand Down Expand Up @@ -136,7 +136,7 @@ func (p *Plugin) executeCommandSetting(args *model.CommandArgs) *model.CommandRe
} else {
return &model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
Text: fmt.Sprintf("Unknow setting " + field),
Text: fmt.Sprintf("Unknown setting " + field),
}
}

Expand Down Expand Up @@ -171,32 +171,31 @@ func (p *Plugin) executeCommandQueue(args *model.CommandArgs) *model.CommandResp
message = strings.Join(split[3:], " ")
}

hashtag, error := p.GenerateHashtag(args.ChannelId, nextWeek)
if error != nil {
hashtag, err := p.GenerateHashtag(args.ChannelId, nextWeek)
if err != nil {
return &model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
Text: fmt.Sprintf("Error calculating hashtags"),
}
}

itemsQueued, appError := p.API.SearchPostsInTeam(args.TeamId, []*model.SearchParams{{Terms: hashtag, IsHashtag: true}})

if appError != nil {
itemsQueued, appErr := p.API.SearchPostsInTeam(args.TeamId, []*model.SearchParams{{Terms: hashtag, IsHashtag: true}})
if appErr != nil {
return &model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
Text: fmt.Sprintf("Error getting user"),
}
}

_, err := p.API.CreatePost(&model.Post{
_, appErr = p.API.CreatePost(&model.Post{
UserId: args.UserId,
ChannelId: args.ChannelId,
Message: fmt.Sprintf("#### %v %v) %v", hashtag, len(itemsQueued)+1, message),
})
if err != nil {
if appErr != nil {
return &model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
Text: fmt.Sprintf("Error creating post: " + err.Message),
Text: fmt.Sprintf("Error creating post: " + appErr.Message),
}
}

Expand Down
28 changes: 16 additions & 12 deletions server/meeting.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,60 +6,64 @@ import (
"time"
)

// Meeting represents a meeting agenda
type Meeting struct {
ChannelId string `json:"channelId"`
ChannelID string `json:"channelId"`
Schedule time.Weekday `json:"schedule"`
HashtagFormat string `json:"hashtagFormat"` //Default: Jan02
}

func (p *Plugin) GetMeeting(channelId string) (*Meeting, error) {
// GetMeeting returns a meeting
func (p *Plugin) GetMeeting(channelID string) (*Meeting, error) {

meettingBytes, appErr := p.API.KVGet(channelId)
meetingBytes, appErr := p.API.KVGet(channelID)
if appErr != nil {
return nil, appErr
}

var meeting *Meeting
if meettingBytes != nil {
if err := json.Unmarshal(meettingBytes, &meeting); err != nil {
if meetingBytes != nil {
if err := json.Unmarshal(meetingBytes, &meeting); err != nil {
return nil, err
}
} else {
//Return a default value
meeting = &Meeting{
Schedule: time.Thursday,
HashtagFormat: "Jan02",
ChannelId: channelId,
ChannelID: channelID,
}
}

return meeting, nil
}

// SaveMeeting saves a meeting
func (p *Plugin) SaveMeeting(meeting *Meeting) error {

jsonMeeting, err := json.Marshal(meeting)
if err != nil {
return err
}

if err := p.API.KVSet(meeting.ChannelId, jsonMeeting); err != nil {
return err
if appErr := p.API.KVSet(meeting.ChannelID, jsonMeeting); appErr != nil {
return appErr
}

return nil
}

func (p *Plugin) GenerateHashtag(channelId string, nextWeek bool) (string, error) {
// GenerateHashtag returns a meeting hashtag
func (p *Plugin) GenerateHashtag(channelID string, nextWeek bool) (string, error) {

meeting, err := p.GetMeeting(channelId)
meeting, err := p.GetMeeting(channelID)
if err != nil {
return "", err
}

meetingDate := nextWeekdayDate(meeting.Schedule, nextWeek)

hastag := fmt.Sprintf("#%v", meetingDate.Format(meeting.HashtagFormat))
hashtag := fmt.Sprintf("#%v", meetingDate.Format(meeting.HashtagFormat))

return hastag, nil
return hashtag, nil
}
20 changes: 10 additions & 10 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
}
}

// OnActivate
// OnActivate is invoked when the plugin is activated
func (p *Plugin) OnActivate() error {
if err := p.registerCommands(); err != nil {
return errors.Wrap(err, "failed to register commands")
Expand All @@ -61,22 +61,22 @@ func (p *Plugin) OnActivate() error {

func (p *Plugin) httpMeetingSettings(w http.ResponseWriter, r *http.Request) {

mattermostUserId := r.Header.Get("Mattermost-User-Id")
if mattermostUserId == "" {
mattermostUserID := r.Header.Get("Mattermost-User-Id")
if mattermostUserID == "" {
http.Error(w, "Not Authorized", http.StatusUnauthorized)
}

switch r.Method {
case http.MethodPost:
p.httpMeetingSaveSettings(w, r, mattermostUserId)
p.httpMeetingSaveSettings(w, r, mattermostUserID)
case http.MethodGet:
p.httpMeetingGetSettings(w, r, mattermostUserId)
p.httpMeetingGetSettings(w, r, mattermostUserID)
default:
http.Error(w, "Request: "+r.Method+" is not allowed.", http.StatusMethodNotAllowed)
}
}

func (p *Plugin) httpMeetingSaveSettings(w http.ResponseWriter, r *http.Request, mmUserId string) {
func (p *Plugin) httpMeetingSaveSettings(w http.ResponseWriter, r *http.Request, mmUserID string) {

userID := r.Header.Get("Mattermost-User-ID")
if userID == "" {
Expand Down Expand Up @@ -104,21 +104,21 @@ func (p *Plugin) httpMeetingSaveSettings(w http.ResponseWriter, r *http.Request,
w.Write([]byte("{\"status\": \"OK\"}"))
}

func (p *Plugin) httpMeetingGetSettings(w http.ResponseWriter, r *http.Request, mmUserId string) {
func (p *Plugin) httpMeetingGetSettings(w http.ResponseWriter, r *http.Request, mmUserID string) {
userID := r.Header.Get("Mattermost-User-ID")
if userID == "" {
http.Error(w, "Not authorized", http.StatusUnauthorized)
return
}

channelId, ok := r.URL.Query()["channelId"]
channelID, ok := r.URL.Query()["channelId"]

if !ok || len(channelId[0]) < 1 {
if !ok || len(channelID[0]) < 1 {
http.Error(w, "Missing channelId parameter", http.StatusBadRequest)
return
}

meeting, err := p.GetMeeting(channelId[0])
meeting, err := p.GetMeeting(channelID[0])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
Expand Down
9 changes: 5 additions & 4 deletions server/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package main

import (
"encoding/json"
"github.com/mattermost/mattermost-server/plugin/plugintest"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/mattermost/mattermost-server/plugin/plugintest"
"github.com/stretchr/testify/assert"
)

func TestServeHTTP(t *testing.T) {
Expand All @@ -22,7 +23,7 @@ func TestServeHTTP(t *testing.T) {
t.Run("get default meeting settings", func(t *testing.T) {
// Mock get default meeting
defaultMeeting := &Meeting{
ChannelId: "myChannelId",
ChannelID: "myChannelId",
Schedule: time.Thursday,
HashtagFormat: "Jan02",
}
Expand All @@ -49,7 +50,7 @@ func TestServeHTTP(t *testing.T) {
t.Run("post meeting settings", func(t *testing.T) {
// Mock set meeting
meeting := &Meeting{
ChannelId: "myChannelId",
ChannelID: "myChannelId",
Schedule: time.Tuesday,
HashtagFormat: "MyMeeting-Jan-02",
}
Expand Down
6 changes: 3 additions & 3 deletions webapp/src/components/meeting_settings/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {getMettingSettingsModalState, getMeetingSettings} from 'selectors';
import {getMeetingSettingsModalState, getMeetingSettings} from 'selectors';
import {closeMeetingSettingsModal, fetchMeetingSettings, saveMeetingSettings} from 'actions';

import MeetingSettingsModal from './meeting_settings';

function mapStateToProps(state) {
return {
visible: getMettingSettingsModalState(state).visible,
channelId: getMettingSettingsModalState(state).channelId,
visible: getMeetingSettingsModalState(state).visible,
channelId: getMeetingSettingsModalState(state).channelId,
meeting: getMeetingSettings(state).meeting,
saveMeetingSettings,
};
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import {id as pluginId} from './manifest';

const getPluginState = (state) => state['plugins-' + pluginId] || {};

export const getMettingSettingsModalState = (state) => getPluginState(state).meetingSettingsModal;
export const getMeetingSettingsModalState = (state) => getPluginState(state).meetingSettingsModal;
export const getMeetingSettings = (state) => getPluginState(state).meetingSettings;