Skip to content
This repository has been archived by the owner on Jan 4, 2023. It is now read-only.

Commit

Permalink
Plugin Init
Browse files Browse the repository at this point in the history
- implement /tapleflip and /tabledown
- remove uneeded plugin sample code
  • Loading branch information
crbanman committed Sep 30, 2019
1 parent b89f477 commit f2dbd78
Show file tree
Hide file tree
Showing 19 changed files with 228 additions and 12,636 deletions.
81 changes: 10 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,15 @@
# Plugin Starter Template [![CircleCI branch](https://img.shields.io/circleci/project/github/mattermost/mattermost-plugin-starter-template/master.svg)](https://circleci.com/gh/mattermost/mattermost-plugin-starter-template)
# TableFlipDown [![CircleCI branch](https://img.shields.io/circleci/project/github/crbanman/mattermost-plugin-tableflipdown/master.svg)](https://circleci.com/gh/crbanman/mattermost-plugin-tableflipdown)

This plugin serves as a starting point for writing a Mattermost plugin. Feel free to base your own plugin off this repository.
This Mattermost plugin allows you to use flip and place down tables to your heart's content:

To learn more about plugins, see [our plugin documentation](https://developers.mattermost.com/extend/plugins/).
- `/tableflip` will escalate things by flipping a table `(╯°□°)╯︵ ┻━┻`
- `/tableflip <angry message>` will post the angry message before flipping the table `<angry message> (╯°□°)╯︵ ┻━┻`
- `/tabledown` will post a tablefip `┬─┬ノ( º _ ºノ)`
- `/tabledown <calm message>` will post the calm message before placing down the table `<calm message> ┬─┬ノ( º _ ºノ)`

## Getting Started
Use GitHub's template feature to make a copy of this repository by clicking the "Use this template" button then clone outside of `$GOPATH`.
Inspired by [cmeadows/mattermost-plugin-tableflip](https://github.com/cmeadows/mattermost-plugin-tableflip).

Alternatively shallow clone the repository to a directory outside of `$GOPATH` matching your plugin name:
```
git clone --depth 1 https://github.com/mattermost/mattermost-plugin-starter-template com.example.my-plugin
```
## Credits

Note that this project uses [Go modules](https://github.com/golang/go/wiki/Modules). Be sure to locate the project outside of `$GOPATH`, or allow the use of Go modules within your `$GOPATH` with an `export GO111MODULE=on`.

Edit `plugin.json` with your `id`, `name`, and `description`:
```
{
"id": "com.example.my-plugin",
"name": "My Plugin",
"description": "A plugin to enhance Mattermost."
}
```

Build your plugin:
```
make
```

This will produce a single plugin file (with support for multiple architectures) for upload to your Mattermost server:

```
dist/com.example.my-plugin.tar.gz
```

There is a build target to automate deploying and enabling the plugin to your server, but it requires configuration and [http](https://httpie.org/) to be installed:
```
export MM_SERVICESETTINGS_SITEURL=http://localhost:8065
export MM_ADMIN_USERNAME=admin
export MM_ADMIN_PASSWORD=password
make deploy
```

Alternatively, if you are running your `mattermost-server` out of a sibling directory by the same name, use the `deploy` target alone to unpack the files into the right directory. You will need to restart your server and manually enable your plugin.

In production, deploy and upload your plugin via the [System Console](https://about.mattermost.com/default-plugin-uploads).

## Q&A

### How do I make a server-only or web app-only plugin?

Simply delete the `server` or `webapp` folders and remove the corresponding sections from `plugin.json`. The build scripts will skip the missing portions automatically.

### How do I include assets in the plugin bundle?

Place them into the `assets` directory. To use an asset at runtime, build the path to your asset and open as a regular file:

```go
bundlePath, err := p.API.GetBundlePath()
if err != nil {
return errors.Wrap(err, "failed to get bundle path")
}

profileImage, err := ioutil.ReadFile(filepath.Join(bundlePath, "assets", "profile_image.png"))
if err != nil {
return errors.Wrap(err, "failed to read profile image")
}

if appErr := p.API.SetProfileImage(userID, profileImage); appErr != nil {
return errors.Wrap(err, "failed to set profile image")
}
```

### How do I build the plugin with unminified JavaScript?
Use `make debug-dist` and `make debug-deploy` in place of `make dist` and `make deploy` to configure webpack to generate unminified Javascript.
* [Mattermost's plugin sample](https://github.com/mattermost/mattermost-plugin-sample)
* Special thanks to [Taffer](https://github.com/Taffer) for the [test utility code](https://github.com/Taffer/ca.taffer.mm-rolly/blob/develop/server/plugin_test.go) for testing slash commands
Empty file removed assets/.gitkeep
Empty file.
40 changes: 40 additions & 0 deletions go.sum

Large diffs are not rendered by default.

16 changes: 4 additions & 12 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
{
"id": "com.mattermost.plugin-starter-template",
"name": "Plugin Starter Template",
"description": "This plugin serves as a starting point for writing a Mattermost plugin.",
"version": "0.1.0",
"id": "com.github.crbanman.mattermost-plugin-tableflipdown",
"name": "TableFlipDown",
"description": "This plugin adds the /tableflip (╯°□°)╯︵ ┻━┻ and /tabledown ┬─┬ノ( º _ ºノ) commands.",
"version": "1.0",
"min_server_version": "5.12.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"webapp": {
"bundle_path": "webapp/dist/main.js"
},
"settings_schema": {
"header": "",
"footer": "",
"settings": []
}
}
1 change: 0 additions & 1 deletion public/hello.html

This file was deleted.

2 changes: 1 addition & 1 deletion server/manifest.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 81 additions & 4 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,103 @@ package main
import (
"fmt"
"net/http"
"strings"
"sync"

"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin"
)

// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
type Plugin struct {
plugin.MattermostPlugin

// configurationLock synchronizes access to the configuration.
configurationLock sync.RWMutex

// configuration is the active plugin configuration. Consult getConfiguration and
// setConfiguration for usage.
configuration *configuration
enabled bool
}

const (
flipTrigger string = "tableflip"
flipASCII string = "(╯°□°)╯︵ ┻━┻"
downTrigger string = "tabledown"
downASCII string = "┬─┬ノ( º _ ºノ)"
)

// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world.
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, world!")
}

// See https://developers.mattermost.com/extend/plugins/server/reference/
// OnActivate handles plugin deactivation
func (p *Plugin) OnActivate() error {
p.enabled = true

p.API.RegisterCommand(&model.Command{
Trigger: flipTrigger,
AutoComplete: true,
AutoCompleteHint: "[message]",
AutoCompleteDesc: "Adds " + flipASCII + " to your message",
})

return p.API.RegisterCommand(&model.Command{
Trigger: downTrigger,
AutoComplete: true,
AutoCompleteHint: "[message]",
AutoCompleteDesc: "Adds " + downASCII + " to your message",
})
}

// OnDeactivate handles plugin deactivation
func (p *Plugin) OnDeactivate() error {
p.enabled = false
return nil
}

// ExecuteCommand handles the core functionality of the plugin
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {

if !p.enabled {
return nil, appError("Cannot execute command while the plugin is disabled.", nil)
}

if p.API == nil {
return nil, appError("Cannot access the plugin API.", nil)
}

slashCommand := "/"
ascii := ""

if strings.HasPrefix(args.Command, "/"+flipTrigger) {
slashCommand += flipTrigger
ascii = flipASCII
} else if strings.HasPrefix(args.Command, "/"+downTrigger) {
slashCommand += downTrigger
ascii = downASCII
} else {
return nil, appError("Expected trigger "+flipTrigger+" or "+downTrigger+", but got "+args.Command, nil)
}

message := strings.TrimSpace((strings.Replace(args.Command, slashCommand, "", 1)))

if len(message) == 0 {
return &model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
Text: fmt.Sprintf(ascii),
}, nil
}

return &model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
Text: fmt.Sprintf(message + " " + ascii),
}, nil
}

func appError(message string, err error) *model.AppError {
errorMessage := ""
if err != nil {
errorMessage = err.Error()
}
return model.NewAppError("Acro Plugin", message, nil, errorMessage, http.StatusBadRequest)
}
107 changes: 92 additions & 15 deletions server/plugin_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,104 @@
package main

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"

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

func TestServeHTTP(t *testing.T) {
assert := assert.New(t)
plugin := Plugin{}
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
// TestTableFlip - Test that the standalone tableflip command works.
func TestTableFlip(t *testing.T) {
resp, err := runTestPluginCommand(t, "/tableflip")

plugin.ServeHTTP(nil, w, r)
assert.NotNil(t, resp)
assert.Nil(t, err)

result := w.Result()
assert.NotNil(result)
bodyBytes, err := ioutil.ReadAll(result.Body)
assert.Nil(err)
bodyString := string(bodyBytes)
// Negative tests.
assert.False(t, strings.Contains(resp.Text, "┬─┬ノ( º _ ºノ)"))

assert.Equal("Hello, world!", bodyString)
// Positive tests.
assert.True(t, strings.Contains(resp.Text, "(╯°□°)╯︵ ┻━┻"))
}

// TestTableFlipWithText - Test that the tableflip command with text works.
func TestTableFlipWithText(t *testing.T) {
resp, err := runTestPluginCommand(t, "/tableflip This is horrible")

assert.NotNil(t, resp)
assert.Nil(t, err)

// Negative tests.
assert.False(t, strings.Contains(resp.Text, "┬─┬ノ( º _ ºノ)"))
assert.False(t, strings.Contains(resp.Text, "(╯°□°)╯︵ ┻━┻ This is horrible"))

// Positive tests.
assert.True(t, strings.Contains(resp.Text, "This is horrible (╯°□°)╯︵ ┻━┻"))
}

// TestTableDown - Test that the standalone tabledown command works.
func TestTableDown(t *testing.T) {
resp, err := runTestPluginCommand(t, "/tabledown")

assert.NotNil(t, resp)
assert.Nil(t, err)

// Negative tests.
assert.False(t, strings.Contains(resp.Text, "(╯°□°)╯︵ ┻━┻"))

// Positive tests.
assert.True(t, strings.Contains(resp.Text, "┬─┬ノ( º _ ºノ)"))
}

// TestTableDownWithText - Test that the tabledown command with text works.
func TestTableDownWithText(t *testing.T) {
resp, err := runTestPluginCommand(t, "/tabledown Oh, actually it's fine")

assert.NotNil(t, resp)
assert.Nil(t, err)

// Negative tests.
assert.False(t, strings.Contains(resp.Text, "(╯°□°)╯︵ ┻━┻"))
assert.False(t, strings.Contains(resp.Text, "┬─┬ノ( º _ ºノ) Oh, actually it's fine"))

// Positive tests.
assert.True(t, strings.Contains(resp.Text, "Oh, actually it's fine ┬─┬ノ( º _ ºノ)"))
}

// -----------------------------------------------------------------------------
// Utilities
// -----------------------------------------------------------------------------

func runTestPluginCommand(t *testing.T, cmd string) (*model.CommandResponse, *model.AppError) {
p := initTestPlugin(t)
assert.Nil(t, p.OnActivate())

var command *model.CommandArgs
command = &model.CommandArgs{
Command: cmd,
}

return p.ExecuteCommand(&plugin.Context{}, command)
}

func initTestPlugin(t *testing.T) *Plugin {
api := &plugintest.API{}
api.On("RegisterCommand", mock.Anything).Return(nil)
api.On("UnregisterCommand", mock.Anything, mock.Anything).Return(nil)
api.On("GetUser", mock.Anything).Return(&model.User{
Id: "userid",
Nickname: "User",
Username: "hunter2",
FirstName: "User",
LastName: "McUserface",
}, (*model.AppError)(nil))

p := Plugin{}
p.SetAPI(api)

return &p
}
4 changes: 0 additions & 4 deletions webapp/.babelrc

This file was deleted.

Loading

0 comments on commit f2dbd78

Please sign in to comment.