Skip to content

Commit

Permalink
MI-814: Render a template for atlassian-connect.json file. (#19)
Browse files Browse the repository at this point in the history
* Use a template for atlassian-connect.json file.

* Fix formatting

* Fix plugin url path

* Code refactoring

* Add a slash command to show URL for atlassian-connect.json file.

* Update the list of available commands

* Add help commands.

* Use constants

* Fix lint

* Fix failing test
- Update .gitignore
  • Loading branch information
chetanyakan authored Feb 21, 2020
1 parent 33ed4de commit 737d5bb
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 136 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,4 @@ vendor/
coverage.txt

# webapp test cases
build/test-results.xml
webapp/build/test-results.xml
60 changes: 60 additions & 0 deletions assets/templates/atlassian-connect.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"key": "{{ .PluginKey }}",
"name": "Mattermost Plugin ({{ .ExternalURL }})",
"description": "Publish confluence cloud notifications to Mattermost.",
"vendor": {
"name": "Mattermost",
"url": "https://github.com/mattermost"
},
"baseUrl": "{{ .BaseURL }}",
"links": {
"self": "{{ .BaseURL }}{{ .RouteACJSON }}",
"homepage": "https://www.mattermost.com"
},
"authentication": {
"type": "none"
},
"scopes": [
"READ"
],
"modules": {
"webhooks": [
{
"event": "comment_created",
"url": "/cloud/comment_created?secret={{ .SharedSecret }}"
},
{
"event": "comment_deleted",
"url": "/cloud/comment_deleted?secret={{ .SharedSecret }}"
},
{
"event": "comment_updated",
"url": "/cloud/comment_updated?secret={{ .SharedSecret }}"
},
{
"event": "comment_removed",
"url": "/cloud/comment_removed?secret={{ .SharedSecret }}"
},
{
"event": "page_created",
"url": "/cloud/page_created?secret={{ .SharedSecret }}"
},
{
"event": "page_removed",
"url": "/cloud/page_removed?secret={{ .SharedSecret }}"
},
{
"event": "page_restored",
"url": "/cloud/page_restored?secret={{ .SharedSecret }}"
},
{
"event": "page_trashed",
"url": "/cloud/page_trashed?secret={{ .SharedSecret }}"
},
{
"event": "page_updated",
"url": "/cloud/page_updated?secret={{ .SharedSecret }}"
}
]
}
}
76 changes: 69 additions & 7 deletions server/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/Brightscout/mattermost-plugin-confluence/server/config"
"github.com/Brightscout/mattermost-plugin-confluence/server/serializer"
"github.com/Brightscout/mattermost-plugin-confluence/server/service"
"github.com/Brightscout/mattermost-plugin-confluence/server/util"
)

type HandlerFunc func(context *model.CommandArgs, args ...string) *model.CommandResponse
Expand All @@ -22,19 +23,53 @@ const (
specifyAlias = "Please specify an alias."
subscriptionDeleteSuccess = "**%s** has been deleted."
noChannelSubscription = "No subscriptions found for this channel."
helpText = "###### Mattermost Confluence Plugin - Slash Command Help\n\n" +
commonHelpText = "###### Mattermost Confluence Plugin - Slash Command Help\n\n" +
"* `/confluence subscribe` - Subscribe the current channel to notifications from Confluence.\n" +
"* `/confluence unsubscribe \"<alias>\"` - Unsubscribe the current channel from notifications associated with the given alias.\n" +
"* `/confluence list` - List all subscriptions for the current channel.\n" +
"* `/confluence edit \"<alias>\"` - Edit the subscription settings associated with the given alias."
invalidCommand = "Invalid command parameters. Please use `/confluence help` for more information."
"* `/confluence edit \"<alias>\"` - Edit the subscription settings associated with the given alias.\n"

sysAdminHelpText = "\n###### For System Administrators:\n" +
"Setup Instructions:\n" +
"* `/confluence install cloud` - Connect Mattermost to a Confluence Cloud instance.\n" +
"* `/confluence install server` - Connect Mattermost to a Confluence Server or Data Center instance.\n"

invalidCommand = "Invalid command parameters. Please use `/confluence help` for more information."
installOnlySystemAdmin = "`/confluence install` can only be run by a system administrator."
)

const (
installServerHelp = `
To configure the plugin, create a new app in your Confluence instance following these steps:
1. Navigate to **Settings > Apps > Manage Apps**.
- For older versions of Confluence, navigate to **Administration > Applications > Add-ons > Manage add-ons**.
2. Click **Settings** at bottom of page, enable development mode, and apply this change.
- Enabling development mode allows you to install apps that are not from the Atlassian Marketplace.
3. Click **Upload app**.
4. Chose 'From my computer' and upload the **Mattermost for Confluence OBR** file.
5. Wait for the app to install.
6. Use the 'configure' button to open the **Mattermost Configuration** page.
7. Enter the following URL as the **Webhook URL** and click on Save.
%s
`
installCloudHelp = `
To finish the configuration, add a new app in your Confluence instance following these steps:
1. Navigate to **Settings > Apps > Manage Apps**.
2. Click **Settings** at bottom of page, enable development mode, and apply this change.
- Enabling development mode allows you to install apps that are not from the Atlassian Marketplace.
3. Click **Upload app**.
4. In the **From this URL field**, enter: %s
5. Wait for the app to install. Once completed, you should see an "Installed and ready to go!" message.
`
)

var ConfluenceCommandHandler = Handler{
handlers: map[string]HandlerFunc{
"list": listChannelSubscription,
"unsubscribe": deleteSubscription,
"help": confluenceHelp,
"list": listChannelSubscription,
"unsubscribe": deleteSubscription,
"install/cloud": showInstallCloudHelp,
"install/server": showInstallServerHelp,
"help": confluenceHelp,
},
defaultHandler: executeConfluenceDefault,
}
Expand All @@ -45,7 +80,7 @@ func GetCommand() *model.Command {
DisplayName: "Confluence",
Description: "Integration with Confluence.",
AutoComplete: true,
AutoCompleteDesc: "Available commands: subscribe, list, unsubscribe \"<alias>\", edit \"<alias>\", help.",
AutoCompleteDesc: "Available commands: subscribe, list, unsubscribe \"<alias>\", edit \"<alias>\", install cloud/server, help.",
AutoCompleteHint: "[command]",
}
}
Expand Down Expand Up @@ -79,6 +114,28 @@ func (ch Handler) Handle(context *model.CommandArgs, args ...string) *model.Comm
return ch.defaultHandler(context, args...)
}

func showInstallCloudHelp(context *model.CommandArgs, args ...string) *model.CommandResponse {
if !util.IsSystemAdmin(context.UserId) {
postCommandResponse(context, installOnlySystemAdmin)
return &model.CommandResponse{}
}

cloudURL := util.GetPluginURL() + util.GetAtlassianConnectURLPath()
postCommandResponse(context, fmt.Sprintf(installCloudHelp, cloudURL))
return &model.CommandResponse{}
}

func showInstallServerHelp(context *model.CommandArgs, args ...string) *model.CommandResponse {
if !util.IsSystemAdmin(context.UserId) {
postCommandResponse(context, installOnlySystemAdmin)
return &model.CommandResponse{}
}

serverURL := util.GetPluginURL() + util.GetConfluenceServerWebhookURLPath()
postCommandResponse(context, fmt.Sprintf(installServerHelp, serverURL))
return &model.CommandResponse{}
}

func deleteSubscription(context *model.CommandArgs, args ...string) *model.CommandResponse {
if len(args) == 0 {
postCommandResponse(context, specifyAlias)
Expand Down Expand Up @@ -110,6 +167,11 @@ func listChannelSubscription(context *model.CommandArgs, args ...string) *model.
}

func confluenceHelp(context *model.CommandArgs, args ...string) *model.CommandResponse {
helpText := commonHelpText
if util.IsSystemAdmin(context.UserId) {
helpText += sysAdminHelpText
}

postCommandResponse(context, helpText)
return &model.CommandResponse{}
}
54 changes: 54 additions & 0 deletions server/controller/atlassian_connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package controller

import (
"html/template"
"net/http"
"net/url"
"path"
"path/filepath"

"github.com/Brightscout/mattermost-plugin-confluence/server/config"
"github.com/Brightscout/mattermost-plugin-confluence/server/util"
)

var atlassianConnectJSON = &Endpoint{
Path: "/atlassian-connect.json",
Method: http.MethodGet,
Execute: renderAtlassianConnectJSON,
RequiresAuth: false,
}

func renderAtlassianConnectJSON(w http.ResponseWriter, r *http.Request) {
conf := config.GetConfig()
if status, err := verifyHTTPSecret(conf.Secret, r.FormValue("secret")); err != nil {
http.Error(w, err.Error(), status)
return
}

bundlePath, err := config.Mattermost.GetBundlePath()
if err != nil {
config.Mattermost.LogWarn("Failed to get bundle path.", "Error", err.Error())
return
}

templateDir := filepath.Join(bundlePath, "assets", "templates")
tmplPath := path.Join(templateDir, "atlassian-connect.json")
values := map[string]string{
"BaseURL": util.GetPluginURL(),
"RouteACJSON": util.GetAtlassianConnectURLPath(),
"ExternalURL": util.GetSiteURL(),
"PluginKey": util.GetPluginKey(),
"SharedSecret": url.QueryEscape(conf.Secret),
}
tmpl, err := template.ParseFiles(tmplPath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
if err = tmpl.Execute(w, values); err != nil {
http.Error(w, "failed to write response: "+err.Error(), http.StatusInternalServerError)
return
}
}
1 change: 1 addition & 0 deletions server/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Endpoint struct {
// Endpoints is a map of endpoint key to endpoint object
// Usage: getEndpointKey(GetMetadata): GetMetadata
var Endpoints = map[string]*Endpoint{
getEndpointKey(atlassianConnectJSON): atlassianConnectJSON,
getEndpointKey(confluenceCloudWebhook): confluenceCloudWebhook,
getEndpointKey(saveChannelSubscription): saveChannelSubscription,
getEndpointKey(editChannelSubscription): editChannelSubscription,
Expand Down
24 changes: 11 additions & 13 deletions server/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/Brightscout/mattermost-plugin-confluence/server/config"
"github.com/Brightscout/mattermost-plugin-confluence/server/service"
"github.com/Brightscout/mattermost-plugin-confluence/server/util"
)

const (
Expand All @@ -22,7 +23,7 @@ const (
"* `/confluence subscribe` - Subscribe the current channel to notifications from Confluence.\n" +
"* `/confluence unsubscribe \"<alias>\"` - Unsubscribe the current channel from notifications associated with the given alias.\n" +
"* `/confluence list` - List all subscriptions for the current channel.\n" +
"* `/confluence edit \"<alias>\"` - Edit the subscription settings associated with the given alias."
"* `/confluence edit \"<alias>\"` - Edit the subscription settings associated with the given alias.\n"
invalidCommand = "Invalid command parameters. Please use `/confluence help` for more information."
)

Expand All @@ -38,9 +39,8 @@ func TestExecuteCommand(t *testing.T) {
mockAPI := baseMock()

for name, val := range map[string]struct {
commandArgs *model.CommandArgs
patchFunctionCalls func()
ephemeralMessage string
commandArgs *model.CommandArgs
ephemeralMessage string
}{
"empty command ": {
commandArgs: &model.CommandArgs{Command: "/confluence", UserId: "abcdabcdabcdabcd", ChannelId: "testtesttesttest"},
Expand All @@ -51,12 +51,7 @@ func TestExecuteCommand(t *testing.T) {
ephemeralMessage: helpText,
},
"unsubscribe command ": {
commandArgs: &model.CommandArgs{Command: "/confluence unsubscribe \"abc\"", UserId: "abcdabcdabcdabcd", ChannelId: "testtesttesttest"},
patchFunctionCalls: func() {
monkey.Patch(service.DeleteSubscription, func(channelID, alias string) error {
return nil
})
},
commandArgs: &model.CommandArgs{Command: "/confluence unsubscribe \"abc\"", UserId: "abcdabcdabcdabcd", ChannelId: "testtesttesttest"},
ephemeralMessage: fmt.Sprintf(subscriptionDeleteSuccess, "abc"),
},
"unsubscribe command no alias": {
Expand All @@ -74,9 +69,12 @@ func TestExecuteCommand(t *testing.T) {
post := args.Get(1).(*model.Post)
assert.Equal(t, val.ephemeralMessage, post.Message)
}).Once().Return(&model.Post{})
if val.patchFunctionCalls != nil {
val.patchFunctionCalls()
}
monkey.Patch(service.DeleteSubscription, func(channelID, alias string) error {
return nil
})
monkey.Patch(util.IsSystemAdmin, func(userID string) bool {
return false
})
res, err := p.ExecuteCommand(&plugin.Context{}, val.commandArgs)
assert.Nil(t, err)
assert.NotNil(t, res)
Expand Down
16 changes: 8 additions & 8 deletions server/service/delete_subscription_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ func TestDeleteSubscription(t *testing.T) {
t.Run(name, func(t *testing.T) {
defer monkey.UnpatchAll()
subscriptions := serializer.Subscriptions{
ByChannelID: map[string]serializer.StringSubscription {
"testtesttesttest" : {
"test": serializer.SpaceSubscription{
SpaceKey: "TS",
ByChannelID: map[string]serializer.StringSubscription{
"testtesttesttest": {
"test": serializer.SpaceSubscription{
SpaceKey: "TS",
BaseSubscription: serializer.BaseSubscription{
Alias: "test",
BaseURL: "https://test.com",
Expand All @@ -67,9 +67,9 @@ func TestDeleteSubscription(t *testing.T) {
},
},
},
"testtesttesttes1" : {
"test": serializer.PageSubscription{
PageID: "1234",
"testtesttesttes1": {
"test": serializer.PageSubscription{
PageID: "1234",
BaseSubscription: serializer.BaseSubscription{
Alias: "test",
BaseURL: "https://test.com",
Expand All @@ -90,7 +90,7 @@ func TestDeleteSubscription(t *testing.T) {
},
},
}
monkey.Patch(GetSubscriptions, func()(serializer.Subscriptions, error) {
monkey.Patch(GetSubscriptions, func() (serializer.Subscriptions, error) {
return subscriptions, nil
})
monkey.Patch(store.AtomicModify, func(key string, modify func(initialValue []byte) ([]byte, error)) error {
Expand Down
Loading

0 comments on commit 737d5bb

Please sign in to comment.