First version

This commit is contained in:
Miguel de la Cruz 2024-04-18 16:05:55 +02:00
parent fc2efe3988
commit fcc76b05ca
18 changed files with 264 additions and 32013 deletions

4
go.mod
View file

@ -3,6 +3,7 @@ module github.com/mattermost/mattermost-plugin-starter-template
go 1.21
require (
github.com/Masterminds/squirrel v1.5.4
github.com/mattermost/mattermost/server/public v0.0.14
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.4
@ -22,6 +23,8 @@ require (
github.com/hashicorp/go-hclog v1.6.2 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect
@ -35,6 +38,7 @@ require (
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tinylib/msgp v1.1.9 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect

12
go.sum
View file

@ -8,6 +8,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
@ -86,6 +88,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
@ -160,11 +166,16 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@ -225,6 +236,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=

View file

@ -1,7 +1,7 @@
{
"id": "com.mattermost.plugin-starter-template",
"name": "Plugin Starter Template",
"description": "This plugin serves as a starting point for writing a Mattermost plugin.",
"id": "fawkes",
"name": "Fawkes",
"description": "This plugin shows instance information useful for debugging.",
"homepage_url": "https://github.com/mattermost/mattermost-plugin-starter-template",
"support_url": "https://github.com/mattermost/mattermost-plugin-starter-template/issues",
"icon_path": "assets/starter-template-icon.svg",
@ -15,9 +15,6 @@
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"webapp": {
"bundle_path": "webapp/dist/main.js"
},
"settings_schema": {
"header": "",
"footer": "",

View file

@ -1,11 +1,15 @@
package main
import (
"database/sql"
"fmt"
"net/http"
"strings"
"sync"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
pluginapi "github.com/mattermost/mattermost/server/public/pluginapi"
)
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
@ -18,11 +22,249 @@ type Plugin struct {
// configuration is the active plugin configuration. Consult getConfiguration and
// setConfiguration for usage.
configuration *configuration
apiClient *pluginapi.Client
db *sql.DB
}
// 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!")
// func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
// fmt.Fprint(w, "Hello, world!")
// }
func (p *Plugin) Q() sq.StatementBuilderType {
return sq.StatementBuilder.PlaceholderFormat(sq.Dollar).RunWith(p.db)
}
func (p *Plugin) OnActivate() error {
p.apiClient = pluginapi.NewClient(p.API, p.Driver)
db, dbErr := p.apiClient.Store.GetMasterDB()
if dbErr != nil {
return dbErr
}
p.db = db
if err := p.API.UnregisterCommand("", "fawkes"); err != nil {
return err
}
autocompleteData := model.NewAutocompleteData("fawkes", "[command]", "Gets information about the msteams current state")
whitelistedCmd := model.NewAutocompleteData("count-whitelisted", "", "Counts the amount of whitelisted users in msteamssync_whitelisted_users")
// adds the from arg?
autocompleteData.AddCommand(whitelistedCmd)
lastConnectAtCmd := model.NewAutocompleteData("count-lastconnectat", "", "Counts the amount of users with a lastconnectat value != 0 in msteamssync_whitelisted_users")
autocompleteData.AddCommand(lastConnectAtCmd)
lastDisconnectAtCmd := model.NewAutocompleteData("count-lastdisconnectat", "", "Counts the amount of users with a lastdisconnectat value != 0 in msteamssync_whitelisted_users")
autocompleteData.AddCommand(lastDisconnectAtCmd)
userInfoCmd := model.NewAutocompleteData("user-info", "[username|email]", "Returns information about the user state regarding msteams")
autocompleteData.AddCommand(userInfoCmd)
channelInfoCmd := model.NewAutocompleteData("channel-info", "[channelID]", "Returns information about the channel state regarding msteams")
autocompleteData.AddCommand(channelInfoCmd)
listInvitesCmd := model.NewAutocompleteData("list-invites", "", "List the current invites")
autocompleteData.AddCommand(listInvitesCmd)
removeInviteCmd := model.NewAutocompleteData("remove-invite", "[mmUserID]", "Deletes the invite for the user ID")
autocompleteData.AddCommand(removeInviteCmd)
cmd := &model.Command{
Trigger: "fawkes",
AutoComplete: true,
AutoCompleteDesc: "Get insights of the user state",
AutocompleteData: autocompleteData,
}
if err := p.API.RegisterCommand(cmd); err != nil {
return err
}
p.API.LogDebug("plugin started")
return nil
}
func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
split := strings.Fields(args.Command)
command := split[0]
var parameters []string
action := ""
if len(split) > 1 {
action = split[1]
}
if len(split) > 2 {
parameters = split[2:]
}
if command != "/fawkes" {
return &model.CommandResponse{}, nil
}
switch (action) {
case "count-whitelisted":
rows, err := p.Q().Select("count(*)").From("msteamssync_whitelisted_users").Query()
if err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args)
}
defer rows.Close()
var result int
rows.Next()
if err := rows.Scan(&result); err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args)
}
return p.sendEphemeralAndExit(fmt.Sprintf("There are %d users in the msteamssync_whitelisted_users table", result), args)
case "count-lastconnectat":
rows, err := p.Q().Select("count(*)").From("msteamssync_users").Where(sq.NotEq{"lastconnectat": 0}).Query()
if err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args)
}
defer rows.Close()
var result int
rows.Next()
if err := rows.Scan(&result); err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args)
}
return p.sendEphemeralAndExit(fmt.Sprintf("There are %d users in the msteamssync_users table with `lastconnectat != 0`", result), args)
case "count-lastdisconnectat":
rows, err := p.Q().Select("count(*)").From("msteamssync_users").Where(sq.NotEq{"lastdisconnectat": 0}).Query()
if err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args)
}
defer rows.Close()
var result int
rows.Next()
if err := rows.Scan(&result); err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args)
}
return p.sendEphemeralAndExit(fmt.Sprintf("There are %d users in the msteamssync_users table with `lastdisconnectat != 0`", result), args)
case "user-info":
if len(parameters) != 1 {
return p.sendEphemeralAndExit("user-info must receive one \"username\" or \"email\" parameter", args)
}
email := parameters[0]
var user *model.User
var appErr *model.AppError
user, appErr = p.API.GetUserByEmail(email)
if appErr != nil {
user, appErr = p.API.GetUserByUsername(email)
if appErr != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR getting the user by email and username: %s", appErr.Error()), args)
}
}
rows, err := p.Q().
Select("msteamsuserid, token, lastconnectat, lastdisconnectat").
From("msteamssync_users").
Where(sq.Eq{"mmuserid": user.Id}).
Query()
if err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR querying for msteamssync user: %s", err), args)
}
defer rows.Close()
var mmTeamsUserID string
var token string
var lastConnectAt int
var lastDisconnectAt int
if !rows.Next() {
return p.sendEphemeralAndExit(fmt.Sprintf("User %q not found in the msteamssync_users table", email), args)
}
if err := rows.Scan(&mmTeamsUserID, &token, &lastConnectAt, &lastDisconnectAt); err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR scanning rows: %s", err), args)
}
var remoteID string
if user.RemoteId != nil {
remoteID = *user.RemoteId
}
hasToken := false
if token != "" {
hasToken = true
}
return p.sendEphemeralAndExit(fmt.Sprintf("User %q has\n - `mmTeamsUserID`: %s\n - `hasToken`: %v\n - `lastConnectAt`: %d\n - `lastDisconnectAt`: %d\n - `remoteID`: %s", email, mmTeamsUserID, hasToken, lastConnectAt, lastDisconnectAt, remoteID), args)
case "channel-info":
if len(parameters) != 1 {
return p.sendEphemeralAndExit("channel-info must receive the channel ID", args)
}
channelID := parameters[0]
channel, appErr := p.API.GetChannel(channelID)
if appErr != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR getting the channel: %s", appErr.Error()), args)
}
return p.sendEphemeralAndExit(fmt.Sprintf(" - `Id`: %s\n - `name`: %s\n - `IsShared`: %v", channel.Id, channel.Name, channel.IsShared()), args)
case "list-invites":
rows, err := p.Q().
Select("mmuserid, invitependingsince, invitelastsentat").
From("msteamssync_invited_users").
Query()
if err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR querying for invited users: %s", err), args)
}
defer rows.Close()
invites := []map[string]any{}
for rows.Next() {
var mmUserID string
var invitePendingSince int
var inviteLastSentAt int
if err := rows.Scan(&mmUserID, &invitePendingSince, &inviteLastSentAt); err != nil {
return p.sendEphemeralAndExit(fmt.Sprintf("ERROR scanning row: %s", err), args)
}
invites = append(invites, map[string]any{
"mmUserID": mmUserID,
"invitePendingSince": invitePendingSince,
"inviteLastSentAt": inviteLastSentAt,
})
}
message := "Invite list:\n"
for _, invite := range invites {
message += fmt.Sprintf(
" - `mmUserID`: %s :: `invitePendingSince`: %d :: `inviteLastSentAt`: %d\n",
invite["mmUserID"],
invite["invitePendingSince"],
invite["inviteLastSentAt"],
)
}
return p.sendEphemeralAndExit(message, args)
case "delete-invite":
return p.sendEphemeralAndExit("Not implemented yet", args)
default:
p.sendEphemeral(fmt.Sprintf("Invalid command %q", action), args)
return &model.CommandResponse{}, nil
}
}
func (p *Plugin) sendEphemeral(message string, args *model.CommandArgs) {
_ = p.API.SendEphemeralPost(args.UserId, &model.Post{
Message: message,
UserId: args.UserId,
ChannelId: args.ChannelId,
})
}
func (p *Plugin) sendEphemeralAndExit(message string, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
p.sendEphemeral(message, args)
return &model.CommandResponse{}, nil
}
// See https://developers.mattermost.com/extend/plugins/server/reference/

View file

@ -1,681 +0,0 @@
{
"settings": {
"import/resolver": {
"typescript": {
"project": "./tsconfig.json"
}
},
"react": {
"version": "16.14.0"
}
},
"extends": [
"eslint:recommended",
"plugin:react-hooks/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:jest/recommended"
],
"parser": "@typescript-eslint/parser",
"plugins": [
"jest",
"react",
"import",
"babel",
"cypress",
"no-only-tests",
"@typescript-eslint"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"impliedStrict": true,
"modules": true,
"experimentalObjectRestSpread": true
}
},
"env": {
"browser": true,
"node": true,
"jquery": true,
"es6": true,
"jest": true
},
"rules": {
"array-bracket-spacing": [
2,
"never"
],
"array-callback-return": 2,
"arrow-body-style": 0,
"arrow-parens": [
2,
"always"
],
"arrow-spacing": [
2,
{
"before": true,
"after": true
}
],
"block-scoped-var": 2,
"brace-style": [
2,
"1tbs",
{
"allowSingleLine": false
}
],
"capitalized-comments": 0,
"class-methods-use-this": 0,
"comma-dangle": [
2,
"always-multiline"
],
"comma-spacing": [
2,
{
"before": false,
"after": true
}
],
"comma-style": [
2,
"last"
],
"complexity": [
0,
10
],
"computed-property-spacing": [
2,
"never"
],
"consistent-return": 2,
"consistent-this": [
2,
"self"
],
"constructor-super": 2,
"curly": [
2,
"all"
],
"dot-location": [
2,
"object"
],
"dot-notation": 2,
"eqeqeq": [
2,
"smart"
],
"func-call-spacing": [
2,
"never"
],
"func-name-matching": 0,
"func-names": 2,
"func-style": [
2,
"declaration",
{
"allowArrowFunctions": true
}
],
"generator-star-spacing": [
2,
{
"before": false,
"after": true
}
],
"global-require": 2,
"guard-for-in": 2,
"id-blacklist": 0,
"import/no-unresolved": 2,
"import/order": [
"error",
{
"newlines-between": "always-and-inside-groups",
"groups": [
"builtin",
"external",
[
"internal",
"parent"
],
"sibling",
"index"
]
}
],
"indent": [
2,
4,
{
"SwitchCase": 0
}
],
"jsx-quotes": [
2,
"prefer-single"
],
"key-spacing": [
2,
{
"beforeColon": false,
"afterColon": true,
"mode": "strict"
}
],
"keyword-spacing": [
2,
{
"before": true,
"after": true,
"overrides": {}
}
],
"line-comment-position": 0,
"linebreak-style": 2,
"lines-around-comment": [
2,
{
"beforeBlockComment": true,
"beforeLineComment": true,
"allowBlockStart": true,
"allowBlockEnd": true
}
],
"max-lines": [
1,
{
"max": 450,
"skipBlankLines": true,
"skipComments": false
}
],
"max-nested-callbacks": [
2,
{
"max": 2
}
],
"max-statements-per-line": [
2,
{
"max": 1
}
],
"multiline-ternary": [
1,
"never"
],
"new-cap": 2,
"new-parens": 2,
"newline-before-return": 0,
"newline-per-chained-call": 0,
"no-alert": 2,
"no-array-constructor": 2,
"no-await-in-loop": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-class-assign": 2,
"no-compare-neg-zero": 2,
"no-cond-assign": [
2,
"except-parens"
],
"no-confusing-arrow": 2,
"no-console": 2,
"no-const-assign": 2,
"no-constant-condition": 2,
"no-debugger": 2,
"no-div-regex": 2,
"no-dupe-args": 2,
"no-dupe-class-members": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-duplicate-imports": [
2,
{
"includeExports": true
}
],
"no-else-return": 2,
"no-empty": 2,
"no-empty-function": 2,
"no-empty-pattern": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-label": 2,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-global-assign": 2,
"no-implicit-coercion": 2,
"no-implicit-globals": 0,
"no-implied-eval": 2,
"no-inner-declarations": 0,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-loop-func": 2,
"no-magic-numbers": [
0,
{
"ignore": [
-1,
0,
1,
2
],
"enforceConst": true,
"detectObjects": true
}
],
"no-mixed-operators": [
2,
{
"allowSamePrecedence": false
}
],
"no-mixed-spaces-and-tabs": 2,
"no-multi-assign": 2,
"no-multi-spaces": [
2,
{
"exceptions": {
"Property": false
}
}
],
"no-multi-str": 0,
"no-multiple-empty-lines": [
2,
{
"max": 1
}
],
"no-native-reassign": 2,
"no-negated-condition": 2,
"no-nested-ternary": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-symbol": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-param-reassign": 2,
"no-process-env": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-return-assign": [
2,
"always"
],
"no-return-await": 2,
"no-script-url": 2,
"no-self-assign": [
2,
{
"props": true
}
],
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": [
2,
{
"hoist": "functions"
}
],
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-tabs": 0,
"no-template-curly-in-string": 2,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": [
2,
{
"skipBlankLines": false
}
],
"no-undef-init": 2,
"no-undefined": 2,
"no-underscore-dangle": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
"no-unneeded-ternary": [
2,
{
"defaultAssignment": false
}
],
"no-unreachable": 2,
"no-unsafe-finally": 2,
"no-unsafe-negation": 2,
"no-unused-expressions": 2,
"no-unused-vars": [
2,
{
"vars": "all",
"args": "after-used"
}
],
"no-use-before-define": [
2,
{
"classes": false,
"functions": false,
"variables": false
}
],
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-constructor": 2,
"no-useless-escape": 2,
"no-useless-rename": 2,
"no-useless-return": 2,
"no-var": 0,
"no-void": 2,
"no-warning-comments": 1,
"no-whitespace-before-property": 2,
"no-with": 2,
"object-curly-newline": 0,
"object-curly-spacing": [
2,
"never"
],
"object-property-newline": [
2,
{
"allowMultiplePropertiesPerLine": true
}
],
"object-shorthand": [
2,
"always"
],
"one-var": [
2,
"never"
],
"one-var-declaration-per-line": 0,
"operator-assignment": [
2,
"always"
],
"operator-linebreak": [
2,
"after"
],
"padded-blocks": [
2,
"never"
],
"prefer-arrow-callback": 2,
"prefer-const": 2,
"prefer-destructuring": 0,
"prefer-numeric-literals": 2,
"prefer-promise-reject-errors": 2,
"prefer-rest-params": 2,
"prefer-spread": 2,
"prefer-template": 0,
"quote-props": [
2,
"as-needed"
],
"quotes": [
2,
"single",
"avoid-escape"
],
"radix": 2,
"react/display-name": [
0,
{
"ignoreTranspilerName": false
}
],
"react/forbid-component-props": 0,
"react/forbid-elements": [
2,
{
"forbid": [
"embed"
]
}
],
"react/jsx-boolean-value": [
2,
"always"
],
"react/jsx-closing-bracket-location": [
2,
{
"location": "tag-aligned"
}
],
"react/jsx-curly-spacing": [
2,
"never"
],
"react/jsx-equals-spacing": [
2,
"never"
],
"react/jsx-filename-extension": 2,
"react/jsx-first-prop-new-line": [
2,
"multiline"
],
"react/jsx-handler-names": 0,
"react/jsx-indent": [
2,
4
],
"react/jsx-indent-props": [
2,
4
],
"react/jsx-key": 2,
"react/jsx-max-props-per-line": [
2,
{
"maximum": 1
}
],
"react/jsx-no-bind": 0,
"react/jsx-no-comment-textnodes": 2,
"react/jsx-no-duplicate-props": [
2,
{
"ignoreCase": false
}
],
"react/jsx-no-literals": 2,
"react/jsx-no-target-blank": 2,
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-tag-spacing": [
2,
{
"closingSlash": "never",
"beforeSelfClosing": "never",
"afterOpening": "never"
}
],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/jsx-wrap-multilines": 2,
"react/no-array-index-key": 1,
"react/no-children-prop": 2,
"react/no-danger": 0,
"react/no-danger-with-children": 2,
"react/no-deprecated": 1,
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/no-find-dom-node": 1,
"react/no-is-mounted": 2,
"react/no-multi-comp": [
2,
{
"ignoreStateless": true
}
],
"react/no-render-return-value": 2,
"react/no-set-state": 0,
"react/no-string-refs": 0,
"react/no-unescaped-entities": 2,
"react/no-unknown-property": 2,
"react/no-unused-prop-types": [
1,
{
"skipShapeProps": true
}
],
"react/prefer-es6-class": 2,
"react/prefer-stateless-function": 2,
"react/prop-types": [
2,
{
"ignore": [
"location",
"history",
"component"
]
}
],
"react/require-default-props": 0,
"react/require-optimization": 1,
"react/require-render-return": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
"react/style-prop-object": 2,
"require-yield": 2,
"rest-spread-spacing": [
2,
"never"
],
"semi": [
2,
"always"
],
"semi-spacing": [
2,
{
"before": false,
"after": true
}
],
"sort-imports": 0,
"sort-keys": 0,
"space-before-blocks": [
2,
"always"
],
"space-before-function-paren": [
2,
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": [
2,
"never"
],
"space-infix-ops": 2,
"space-unary-ops": [
2,
{
"words": true,
"nonwords": false
}
],
"symbol-description": 2,
"template-curly-spacing": [
2,
"never"
],
"valid-typeof": [
2,
{
"requireStringLiterals": false
}
],
"vars-on-top": 0,
"wrap-iife": [
2,
"outside"
],
"wrap-regex": 2,
"yoda": [
2,
"never",
{
"exceptRange": false,
"onlyEquality": false
}
]
},
"overrides": [
{
"files": [
"**/*.tsx",
"**/*.ts"
],
"extends": "plugin:@typescript-eslint/recommended",
"rules": {
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/ban-types": 1,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/prefer-interface": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/indent": [
2,
4,
{
"SwitchCase": 0
}
],
"@typescript-eslint/no-use-before-define": [
2,
{
"classes": false,
"functions": false,
"variables": false
}
],
"react/jsx-filename-extension": [
1,
{
"extensions": [
".jsx",
".tsx"
]
}
]
}
}
]
}

3
webapp/.gitignore vendored
View file

@ -1,3 +0,0 @@
.eslintcache
junit.xml
node_modules

View file

@ -1 +0,0 @@
save-exact=true

View file

@ -1,46 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const config = {
presets: [
['@babel/preset-env', {
targets: {
chrome: 66,
firefox: 60,
edge: 42,
safari: 12,
},
modules: false,
corejs: 3,
debug: false,
useBuiltIns: 'usage',
shippedProposals: true,
}],
['@babel/preset-react', {
useBuiltIns: true,
}],
['@babel/typescript', {
allExtensions: true,
isTSX: true,
}],
['@emotion/babel-preset-css-prop'],
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
'@babel/proposal-object-rest-spread',
'@babel/plugin-proposal-optional-chaining',
'babel-plugin-typescript-to-proptypes',
],
};
// Jest needs module transformation
config.env = {
test: {
presets: config.presets,
plugins: config.plugins,
},
};
config.env.test.presets[0][1].modules = 'auto';
module.exports = config;

View file

@ -1 +0,0 @@
{}

30965
webapp/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,126 +0,0 @@
{
"private": true,
"scripts": {
"build": "webpack --mode=production",
"build:watch": "webpack --mode=production --watch",
"debug": "webpack --mode=none",
"debug:watch": "webpack --mode=development --watch",
"lint": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet --cache",
"fix": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet --fix --cache",
"test": "jest --forceExit --detectOpenHandles --verbose",
"test:watch": "jest --watch",
"test-ci": "jest --forceExit --detectOpenHandles --maxWorkers=2",
"check-types": "tsc"
},
"devDependencies": {
"@babel/cli": "7.16.8",
"@babel/core": "7.16.12",
"@babel/plugin-proposal-class-properties": "7.16.7",
"@babel/plugin-proposal-object-rest-spread": "7.16.7",
"@babel/plugin-proposal-optional-chaining": "7.16.7",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.16.11",
"@babel/preset-react": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"@babel/runtime": "7.16.7",
"@emotion/babel-preset-css-prop": "11.2.0",
"@emotion/core": "10.3.1",
"@mattermost/types": "6.7.0-0",
"@testing-library/jest-dom": "5.16.1",
"@types/babel__core": "7.1.18",
"@types/babel__template": "7.4.1",
"@types/enzyme": "3.10.11",
"@types/jest": "27.4.0",
"@types/node": "17.0.12",
"@types/react": "16.14.26",
"@types/react-dom": "17.0.11",
"@types/react-redux": "7.1.22",
"@types/react-router-dom": "5.1.5",
"@types/react-transition-group": "4.4.0",
"@typescript-eslint/eslint-plugin": "5.10.1",
"@typescript-eslint/parser": "5.10.1",
"@typescript-eslint/typescript-estree": "5.52.0",
"babel-eslint": "10.1.0",
"babel-loader": "8.2.2",
"babel-plugin-formatjs": "10.3.7",
"babel-plugin-typescript-to-proptypes": "2.0.0",
"css-loader": "6.5.1",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"enzyme-to-json": "3.6.2",
"eslint": "8.8.0",
"eslint-import-resolver-alias": "1.1.2",
"eslint-import-resolver-typescript": "2.7.1",
"eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-babel": "5.3.1",
"eslint-plugin-cypress": "2.12.1",
"eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jest": "26.5.3",
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost",
"eslint-plugin-no-only-tests": "2.6.0",
"eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.3.0",
"file-loader": "6.2.0",
"identity-obj-proxy": "3.0.0",
"isomorphic-fetch": "3.0.0",
"jest": "27.4.7",
"jest-canvas-mock": "2.3.1",
"jest-junit": "13.0.0",
"sass": "1.52.3",
"sass-loader": "13.0.0",
"style-loader": "3.3.1",
"webpack": "5.75.0",
"webpack-cli": "5.0.1"
},
"dependencies": {
"core-js": "3.22.8",
"mattermost-redux": "5.33.1",
"react": "^16.14.0",
"react-redux": "8.0.2",
"redux": "4.2.0",
"typescript": "4.6.4"
},
"jest": {
"snapshotSerializers": [
"<rootDir>/node_modules/enzyme-to-json/serializer"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/non_npm_dependencies/"
],
"clearMocks": true,
"collectCoverageFrom": [
"src/**/*.{js,jsx}"
],
"coverageReporters": [
"lcov",
"text-summary"
],
"moduleNameMapper": {
"^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "identity-obj-proxy",
"^.+\\.(css|less|scss)$": "identity-obj-proxy",
"^.*i18n.*\\.(json)$": "<rootDir>/tests/i18n_mock.json",
"^bundle-loader\\?lazy\\!(.*)$": "$1"
},
"moduleDirectories": [
"",
"node_modules",
"non_npm_dependencies"
],
"reporters": [
"default",
"jest-junit"
],
"transformIgnorePatterns": [
"node_modules/(?!react-native|react-router|mattermost-webapp)"
],
"setupFiles": [
"jest-canvas-mock"
],
"setupFilesAfterEnv": [
"<rootDir>/tests/setup.tsx"
],
"testURL": "http://localhost:8065"
}
}

View file

@ -1,22 +0,0 @@
import {Store, Action} from 'redux';
import {GlobalState} from '@mattermost/types/lib/store';
import manifest from '@/manifest';
import {PluginRegistry} from '@/types/mattermost-webapp';
export default class Plugin {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
public async initialize(registry: PluginRegistry, store: Store<GlobalState, Action<Record<string, unknown>>>) {
// @see https://developers.mattermost.com/extend/plugins/webapp/reference/
}
}
declare global {
interface Window {
registerPlugin(pluginId: string, plugin: Plugin): void
}
}
window.registerPlugin(manifest.id, new Plugin());

View file

@ -1,7 +0,0 @@
import manifest from './manifest';
test('Plugin manifest, id and version are defined', () => {
expect(manifest).toBeDefined();
expect(manifest.id).toBeDefined();
expect(manifest.version).toBeDefined();
});

View file

@ -1,5 +0,0 @@
export interface PluginRegistry {
registerPostTypeComponent(typeName: string, component: React.ElementType)
// Add more if needed from https://developers.mattermost.com/extend/plugins/webapp/reference
}

View file

@ -1 +0,0 @@
{}

View file

@ -1,6 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// import '@mattermost/webapp/tests/setup';
export {};

View file

@ -1,37 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
},
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"experimentalDecorators": true,
"jsx": "react"
},
"include": [
"src",
"tests"
],
"exclude": [
"dist",
"node_modules",
"!node_modules/@types"
]
}

View file

@ -1,103 +0,0 @@
const exec = require('child_process').exec;
const path = require('path');
const PLUGIN_ID = require('../plugin.json').id;
const NPM_TARGET = process.env.npm_lifecycle_event; //eslint-disable-line no-process-env
const isDev = NPM_TARGET === 'debug' || NPM_TARGET === 'debug:watch';
const plugins = [];
if (NPM_TARGET === 'build:watch' || NPM_TARGET === 'debug:watch') {
plugins.push({
apply: (compiler) => {
compiler.hooks.watchRun.tap('WatchStartPlugin', () => {
// eslint-disable-next-line no-console
console.log('Change detected. Rebuilding webapp.');
});
compiler.hooks.afterEmit.tap('AfterEmitPlugin', () => {
exec('cd .. && make deploy-from-watch', (err, stdout, stderr) => {
if (stdout) {
process.stdout.write(stdout);
}
if (stderr) {
process.stderr.write(stderr);
}
});
});
},
});
}
const config = {
entry: [
'./src/index.tsx',
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
modules: [
'src',
'node_modules',
path.resolve(__dirname),
],
extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
// Babel configuration is in babel.config.js because jest requires it to be there.
},
},
},
{
test: /\.(scss|css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
sassOptions: {
includePaths: ['node_modules/compass-mixins/lib', 'sass'],
},
},
},
],
},
],
},
externals: {
react: 'React',
'react-dom': 'ReactDOM',
redux: 'Redux',
'react-redux': 'ReactRedux',
'prop-types': 'PropTypes',
'react-bootstrap': 'ReactBootstrap',
'react-router-dom': 'ReactRouterDom',
},
output: {
devtoolNamespace: PLUGIN_ID,
path: path.join(__dirname, '/dist'),
publicPath: '/',
filename: 'main.js',
},
mode: (isDev) ? 'eval-source-map' : 'production',
plugins,
};
if (isDev) {
Object.assign(config, {devtool: 'eval-source-map'});
}
module.exports = config;