mirror of
https://github.com/mattermost/focalboard.git
synced 2025-01-26 18:48:15 +02:00
Merge branch 'main' into MM-40430-redirect-login
This commit is contained in:
commit
6fdee7a38b
@ -1,8 +1,8 @@
|
|||||||
.PHONY: run
|
.PHONY: run
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go run ./main.go
|
go run -tags json1 ./main.go
|
||||||
|
|
||||||
build:
|
build:
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
go build -o bin/focalboard-app
|
go build -tags json1 -o bin/focalboard-app
|
||||||
|
@ -6,7 +6,7 @@ replace github.com/mattermost/focalboard/server => ../server
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/mattermost/focalboard/server v0.0.0-20210422230105-f5ae0b265a8d
|
github.com/mattermost/focalboard/server v0.0.0-20220325164658-33557093b00d
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0
|
github.com/mattermost/mattermost-server/v6 v6.5.0
|
||||||
github.com/webview/webview v0.0.0-20200724072439-e0c01595b361
|
github.com/webview/webview v0.0.0-20220314230258-a2b7746141c3
|
||||||
)
|
)
|
||||||
|
1377
linux/go.sum
1377
linux/go.sum
File diff suppressed because it is too large
Load Diff
@ -67,7 +67,6 @@ linters:
|
|||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
- unused
|
||||||
- whitespace
|
- whitespace
|
||||||
- gocyclo
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
@ -5,8 +5,8 @@ go 1.16
|
|||||||
replace github.com/mattermost/focalboard/server => ../server
|
replace github.com/mattermost/focalboard/server => ../server
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattermost/focalboard/server v0.0.0-20210525112228-f43e4028dbdc
|
github.com/mattermost/focalboard/server v0.0.0-20220325164658-33557093b00d
|
||||||
github.com/mattermost/mattermost-plugin-api v0.0.21
|
github.com/mattermost/mattermost-plugin-api v0.0.27
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20211022142730-a6cca93ba3c3
|
github.com/mattermost/mattermost-server/v6 v6.5.0
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.1
|
||||||
)
|
)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/mattermost/focalboard/server/ws"
|
"github.com/mattermost/focalboard/server/ws"
|
||||||
|
|
||||||
pluginapi "github.com/mattermost/mattermost-plugin-api"
|
pluginapi "github.com/mattermost/mattermost-plugin-api"
|
||||||
|
apierrors "github.com/mattermost/mattermost-plugin-api/errors"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|
||||||
@ -113,5 +114,5 @@ func (da *pluginAPIAdapter) GetChannelMember(channelID string, userID string) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (da *pluginAPIAdapter) IsErrNotFound(err error) bool {
|
func (da *pluginAPIAdapter) IsErrNotFound(err error) bool {
|
||||||
return errors.Is(err, pluginapi.ErrNotFound)
|
return errors.Is(err, apierrors.ErrNotFound)
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,7 @@ func (p *Plugin) OnActivate() error {
|
|||||||
NewMutexFn: func(name string) (*cluster.Mutex, error) {
|
NewMutexFn: func(name string) (*cluster.Mutex, error) {
|
||||||
return cluster.NewMutex(p.API, name)
|
return cluster.NewMutex(p.API, name)
|
||||||
},
|
},
|
||||||
|
PluginAPI: &p.API,
|
||||||
}
|
}
|
||||||
|
|
||||||
var db store.Store
|
var db store.Store
|
||||||
|
14912
mattermost-plugin/webapp/package-lock.json
generated
14912
mattermost-plugin/webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,71 +14,71 @@
|
|||||||
"check-types": "tsc"
|
"check-types": "tsc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.16.8",
|
"@babel/cli": "7.17.6",
|
||||||
"@babel/core": "7.16.12",
|
"@babel/core": "7.17.8",
|
||||||
"@babel/plugin-proposal-class-properties": "7.13.0",
|
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "7.14.2",
|
"@babel/plugin-proposal-object-rest-spread": "7.17.3",
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.14.2",
|
"@babel/plugin-proposal-optional-chaining": "7.16.7",
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||||
"@babel/polyfill": "7.10.4",
|
"@babel/polyfill": "7.10.4",
|
||||||
"@babel/preset-env": "7.14.2",
|
"@babel/preset-env": "7.16.11",
|
||||||
"@babel/preset-react": "7.13.13",
|
"@babel/preset-react": "7.16.7",
|
||||||
"@babel/preset-typescript": "7.13.0",
|
"@babel/preset-typescript": "7.16.7",
|
||||||
"@babel/runtime": "7.14.0",
|
"@babel/runtime": "7.17.8",
|
||||||
"@formatjs/ts-transformer": "3.4.2",
|
"@formatjs/ts-transformer": "3.9.2",
|
||||||
"@types/enzyme": "3.10.8",
|
"@types/enzyme": "3.10.11",
|
||||||
"@types/jest": "26.0.23",
|
"@types/jest": "27.4.1",
|
||||||
"@types/node": "15.6.1",
|
"@types/node": "17.0.23",
|
||||||
"@types/react": "17.0.6",
|
"@types/react": "17.0.42",
|
||||||
"@types/react-dom": "17.0.5",
|
"@types/react-dom": "17.0.14",
|
||||||
"@types/react-intl": "3.0.0",
|
"@types/react-intl": "3.0.0",
|
||||||
"@types/react-redux": "7.1.16",
|
"@types/react-redux": "7.1.23",
|
||||||
"@types/react-router-dom": "5.1.7",
|
"@types/react-router-dom": "5.3.3",
|
||||||
"@types/react-transition-group": "4.4.1",
|
"@types/react-transition-group": "4.4.4",
|
||||||
"@typescript-eslint/eslint-plugin": "4.25.0",
|
"@typescript-eslint/eslint-plugin": "5.16.0",
|
||||||
"@typescript-eslint/parser": "4.25.0",
|
"@typescript-eslint/parser": "5.16.0",
|
||||||
"babel-eslint": "10.1.0",
|
"babel-eslint": "10.1.0",
|
||||||
"babel-jest": "27.0.1",
|
"babel-jest": "27.5.1",
|
||||||
"babel-loader": "8.2.2",
|
"babel-loader": "8.2.4",
|
||||||
"babel-plugin-typescript-to-proptypes": "1.4.2",
|
"babel-plugin-typescript-to-proptypes": "2.0.0",
|
||||||
"css-loader": "5.2.6",
|
"css-loader": "6.7.1",
|
||||||
"eslint": "7.27.0",
|
"eslint": "8.11.0",
|
||||||
"eslint-import-resolver-webpack": "0.13.1",
|
"eslint-import-resolver-webpack": "0.13.2",
|
||||||
"eslint-plugin-babel": "^5.3.1",
|
"eslint-plugin-babel": "^5.3.1",
|
||||||
"eslint-plugin-cypress": "2.11.2",
|
"eslint-plugin-cypress": "2.12.1",
|
||||||
"eslint-plugin-header": "3.1.1",
|
"eslint-plugin-header": "3.1.1",
|
||||||
"eslint-plugin-import": "2.23.3",
|
"eslint-plugin-import": "2.25.4",
|
||||||
"eslint-plugin-jquery": "1.5.1",
|
"eslint-plugin-jquery": "1.5.1",
|
||||||
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee",
|
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee",
|
||||||
"eslint-plugin-no-only-tests": "2.4.0",
|
"eslint-plugin-no-only-tests": "2.6.0",
|
||||||
"eslint-plugin-react": "7.23.2",
|
"eslint-plugin-react": "7.29.4",
|
||||||
"eslint-plugin-react-hooks": "4.2.0",
|
"eslint-plugin-react-hooks": "4.3.0",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"identity-obj-proxy": "3.0.0",
|
"identity-obj-proxy": "3.0.0",
|
||||||
"image-webpack-loader": "8.1.0",
|
"image-webpack-loader": "8.1.0",
|
||||||
"imagemin-gifsicle": "^7.0.0",
|
"imagemin-gifsicle": "^7.0.0",
|
||||||
"imagemin-mozjpeg": "^9.0.0",
|
"imagemin-mozjpeg": "^10.0.0",
|
||||||
"imagemin-optipng": "^8.0.0",
|
"imagemin-optipng": "^8.0.0",
|
||||||
"imagemin-pngquant": "^9.0.2",
|
"imagemin-pngquant": "^9.0.2",
|
||||||
"imagemin-svgo": "^8.0.0",
|
"imagemin-svgo": "^10.0.1",
|
||||||
"imagemin-webp": "7.0.0",
|
"imagemin-webp": "7.0.0",
|
||||||
"jest": "27.0.1",
|
"jest": "27.5.1",
|
||||||
"jest-canvas-mock": "2.3.1",
|
"jest-canvas-mock": "2.3.1",
|
||||||
"jest-junit": "12.0.0",
|
"jest-junit": "13.0.0",
|
||||||
"sass": "1.25.0",
|
"sass": "1.49.9",
|
||||||
"sass-loader": "11.1.1",
|
"sass-loader": "12.6.0",
|
||||||
"style-loader": "2.0.0",
|
"style-loader": "3.3.1",
|
||||||
"ts-loader": "9.2.3",
|
"ts-loader": "9.2.8",
|
||||||
"typescript": "4.2.4",
|
"typescript": "4.6.2",
|
||||||
"webpack": "5.37.1",
|
"webpack": "5.70.0",
|
||||||
"webpack-cli": "4.7.0"
|
"webpack-cli": "4.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "3.12.1",
|
"core-js": "3.21.1",
|
||||||
"glob-parent": "6.0.2",
|
"glob-parent": "6.0.2",
|
||||||
"marked": ">=4.0.12",
|
"marked": ">=4.0.12",
|
||||||
"mattermost-redux": "5.33.1",
|
"mattermost-redux": "5.33.1",
|
||||||
"react-intl": "^5.13.5",
|
"react-intl": "^5.24.7",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"trim-newlines": "4.0.2"
|
"trim-newlines": "4.0.2"
|
||||||
},
|
},
|
||||||
|
@ -112,20 +112,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|eot|tiff|svg|woff2|woff|ttf|jpg|gif)$/,
|
test: /\.(png|eot|tiff|svg|woff2|woff|ttf|jpg|gif)$/,
|
||||||
use: [
|
type: 'asset/resource',
|
||||||
{
|
generator: {
|
||||||
loader: 'file-loader',
|
filename: 'static/[name].[ext]',
|
||||||
options: {
|
}
|
||||||
name: '[name].[ext]',
|
|
||||||
outputPath: 'static',
|
|
||||||
publicPath: '/static/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'image-webpack-loader',
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -65,4 +65,3 @@ linters:
|
|||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
- unused
|
||||||
- whitespace
|
- whitespace
|
||||||
- gocyclo
|
|
||||||
|
@ -81,7 +81,7 @@ func (a *API) RegisterRoutes(r *mux.Router) {
|
|||||||
apiv1.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handlePatchBoard)).Methods("PATCH")
|
apiv1.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handlePatchBoard)).Methods("PATCH")
|
||||||
apiv1.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handleDeleteBoard)).Methods("DELETE")
|
apiv1.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handleDeleteBoard)).Methods("DELETE")
|
||||||
apiv1.HandleFunc("/boards/{boardID}/duplicate", a.sessionRequired(a.handleDuplicateBoard)).Methods("POST")
|
apiv1.HandleFunc("/boards/{boardID}/duplicate", a.sessionRequired(a.handleDuplicateBoard)).Methods("POST")
|
||||||
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET")
|
apiv1.HandleFunc("/boards/{boardID}/blocks", a.attachSession(a.handleGetBlocks, false)).Methods("GET")
|
||||||
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST")
|
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST")
|
||||||
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePatchBlocks)).Methods("PATCH")
|
apiv1.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePatchBlocks)).Methods("PATCH")
|
||||||
apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE")
|
apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE")
|
||||||
@ -288,15 +288,17 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if board.IsTemplate {
|
if !a.hasValidReadTokenForBoard(r, boardID) {
|
||||||
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
|
if board.IsTemplate {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"})
|
if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {
|
||||||
return
|
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board template"})
|
||||||
}
|
return
|
||||||
} else {
|
}
|
||||||
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
|
} else {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
|
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {
|
||||||
return
|
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3495,7 +3497,6 @@ func (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request
|
|||||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create private boards"})
|
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to create private boards"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
auditRec := a.makeAuditRecord(r, "createBoardsAndBlocks", audit.Fail)
|
auditRec := a.makeAuditRecord(r, "createBoardsAndBlocks", audit.Fail)
|
||||||
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
||||||
auditRec.AddMeta("teamID", teamID)
|
auditRec.AddMeta("teamID", teamID)
|
||||||
|
@ -3,30 +3,40 @@ module github.com/mattermost/focalboard/server
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/squirrel v1.5.0
|
github.com/Masterminds/squirrel v1.5.2
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/golang/mock v1.5.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/hashicorp/go-hclog v1.2.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.15.1 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||||
github.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94
|
github.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94
|
||||||
github.com/lib/pq v1.10.2
|
github.com/lib/pq v1.10.4
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/mattermost/mattermost-plugin-api v0.0.21
|
github.com/mattermost/mattermost-plugin-api v0.0.27
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0
|
github.com/mattermost/mattermost-server/v6 v6.5.0
|
||||||
github.com/mattermost/morph v0.0.0-20220222074146-cff3f12ff131
|
github.com/mattermost/morph v0.0.0-20220324143723-e4896385ec60
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
github.com/minio/minio-go/v7 v7.0.23 // indirect
|
||||||
github.com/oklog/run v1.1.0
|
github.com/oklog/run v1.1.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/prometheus/client_golang v1.12.1
|
||||||
github.com/rudderlabs/analytics-go v3.3.1+incompatible
|
github.com/rs/xid v1.4.0 // indirect
|
||||||
github.com/sergi/go-diff v1.0.0
|
github.com/rudderlabs/analytics-go v3.3.2+incompatible
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
github.com/sergi/go-diff v1.2.0
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/afero v1.8.2 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/viper v1.10.1
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/stretchr/testify v1.7.1
|
||||||
github.com/stretchr/testify v1.7.0
|
|
||||||
github.com/wiggin77/merror v1.0.3
|
github.com/wiggin77/merror v1.0.3
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
github.com/yuin/goldmark v1.4.11 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
|
||||||
|
golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
|
||||||
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
|
||||||
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||||
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
|
modernc.org/sqlite v1.15.3 // indirect
|
||||||
)
|
)
|
||||||
|
1375
server/go.sum
1375
server/go.sum
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,9 @@ func New(store permissions.Store, logger *mlog.Logger) *Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {
|
func (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {
|
||||||
// Locally there is no team, so return true
|
if userID == "" || teamID == "" || permission == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +186,21 @@ func (mr *MockAPIMockRecorder) CreatePost(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePost", reflect.TypeOf((*MockAPI)(nil).CreatePost), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePost", reflect.TypeOf((*MockAPI)(nil).CreatePost), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSession mocks base method.
|
||||||
|
func (m *MockAPI) CreateSession(arg0 *model.Session) (*model.Session, *model.AppError) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateSession", arg0)
|
||||||
|
ret0, _ := ret[0].(*model.Session)
|
||||||
|
ret1, _ := ret[1].(*model.AppError)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSession indicates an expected call of CreateSession.
|
||||||
|
func (mr *MockAPIMockRecorder) CreateSession(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockAPI)(nil).CreateSession), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateTeam mocks base method.
|
// CreateTeam mocks base method.
|
||||||
func (m *MockAPI) CreateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {
|
func (m *MockAPI) CreateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -457,6 +472,20 @@ func (mr *MockAPIMockRecorder) ExecuteSlashCommand(arg0 interface{}) *gomock.Cal
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteSlashCommand", reflect.TypeOf((*MockAPI)(nil).ExecuteSlashCommand), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteSlashCommand", reflect.TypeOf((*MockAPI)(nil).ExecuteSlashCommand), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtendSessionExpiry mocks base method.
|
||||||
|
func (m *MockAPI) ExtendSessionExpiry(arg0 string, arg1 int64) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ExtendSessionExpiry", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendSessionExpiry indicates an expected call of ExtendSessionExpiry.
|
||||||
|
func (mr *MockAPIMockRecorder) ExtendSessionExpiry(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendSessionExpiry", reflect.TypeOf((*MockAPI)(nil).ExtendSessionExpiry), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBot mocks base method.
|
// GetBot mocks base method.
|
||||||
func (m *MockAPI) GetBot(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {
|
func (m *MockAPI) GetBot(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -1573,6 +1602,20 @@ func (mr *MockAPIMockRecorder) InstallPlugin(arg0, arg1 interface{}) *gomock.Cal
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallPlugin", reflect.TypeOf((*MockAPI)(nil).InstallPlugin), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallPlugin", reflect.TypeOf((*MockAPI)(nil).InstallPlugin), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsEnterpriseReady mocks base method.
|
||||||
|
func (m *MockAPI) IsEnterpriseReady() bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "IsEnterpriseReady")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnterpriseReady indicates an expected call of IsEnterpriseReady.
|
||||||
|
func (mr *MockAPIMockRecorder) IsEnterpriseReady() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnterpriseReady", reflect.TypeOf((*MockAPI)(nil).IsEnterpriseReady))
|
||||||
|
}
|
||||||
|
|
||||||
// KVCompareAndDelete mocks base method.
|
// KVCompareAndDelete mocks base method.
|
||||||
func (m *MockAPI) KVCompareAndDelete(arg0 string, arg1 []byte) (bool, *model.AppError) {
|
func (m *MockAPI) KVCompareAndDelete(arg0 string, arg1 []byte) (bool, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2014,6 +2057,20 @@ func (mr *MockAPIMockRecorder) RemoveTeamIcon(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveTeamIcon", reflect.TypeOf((*MockAPI)(nil).RemoveTeamIcon), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveTeamIcon", reflect.TypeOf((*MockAPI)(nil).RemoveTeamIcon), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveUserCustomStatus mocks base method.
|
||||||
|
func (m *MockAPI) RemoveUserCustomStatus(arg0 string) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RemoveUserCustomStatus", arg0)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUserCustomStatus indicates an expected call of RemoveUserCustomStatus.
|
||||||
|
func (mr *MockAPIMockRecorder) RemoveUserCustomStatus(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveUserCustomStatus", reflect.TypeOf((*MockAPI)(nil).RemoveUserCustomStatus), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// RequestTrialLicense mocks base method.
|
// RequestTrialLicense mocks base method.
|
||||||
func (m *MockAPI) RequestTrialLicense(arg0 string, arg1 int, arg2, arg3 bool) *model.AppError {
|
func (m *MockAPI) RequestTrialLicense(arg0 string, arg1 int, arg2, arg3 bool) *model.AppError {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2028,6 +2085,20 @@ func (mr *MockAPIMockRecorder) RequestTrialLicense(arg0, arg1, arg2, arg3 interf
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestTrialLicense", reflect.TypeOf((*MockAPI)(nil).RequestTrialLicense), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestTrialLicense", reflect.TypeOf((*MockAPI)(nil).RequestTrialLicense), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RevokeSession mocks base method.
|
||||||
|
func (m *MockAPI) RevokeSession(arg0 string) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RevokeSession", arg0)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeSession indicates an expected call of RevokeSession.
|
||||||
|
func (mr *MockAPIMockRecorder) RevokeSession(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeSession", reflect.TypeOf((*MockAPI)(nil).RevokeSession), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// RevokeUserAccessToken mocks base method.
|
// RevokeUserAccessToken mocks base method.
|
||||||
func (m *MockAPI) RevokeUserAccessToken(arg0 string) *model.AppError {
|
func (m *MockAPI) RevokeUserAccessToken(arg0 string) *model.AppError {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2042,6 +2113,20 @@ func (mr *MockAPIMockRecorder) RevokeUserAccessToken(arg0 interface{}) *gomock.C
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeUserAccessToken", reflect.TypeOf((*MockAPI)(nil).RevokeUserAccessToken), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeUserAccessToken", reflect.TypeOf((*MockAPI)(nil).RevokeUserAccessToken), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RolesGrantPermission mocks base method.
|
||||||
|
func (m *MockAPI) RolesGrantPermission(arg0 []string, arg1 string) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RolesGrantPermission", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RolesGrantPermission indicates an expected call of RolesGrantPermission.
|
||||||
|
func (mr *MockAPIMockRecorder) RolesGrantPermission(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RolesGrantPermission", reflect.TypeOf((*MockAPI)(nil).RolesGrantPermission), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// SaveConfig mocks base method.
|
// SaveConfig mocks base method.
|
||||||
func (m *MockAPI) SaveConfig(arg0 *model.Config) *model.AppError {
|
func (m *MockAPI) SaveConfig(arg0 *model.Config) *model.AppError {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2437,6 +2522,20 @@ func (mr *MockAPIMockRecorder) UpdateUserActive(arg0, arg1 interface{}) *gomock.
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserActive", reflect.TypeOf((*MockAPI)(nil).UpdateUserActive), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserActive", reflect.TypeOf((*MockAPI)(nil).UpdateUserActive), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUserCustomStatus mocks base method.
|
||||||
|
func (m *MockAPI) UpdateUserCustomStatus(arg0 string, arg1 *model.CustomStatus) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateUserCustomStatus", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserCustomStatus indicates an expected call of UpdateUserCustomStatus.
|
||||||
|
func (mr *MockAPIMockRecorder) UpdateUserCustomStatus(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserCustomStatus", reflect.TypeOf((*MockAPI)(nil).UpdateUserCustomStatus), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateUserStatus mocks base method.
|
// UpdateUserStatus mocks base method.
|
||||||
func (m *MockAPI) UpdateUserStatus(arg0, arg1 string) (*model.Status, *model.AppError) {
|
func (m *MockAPI) UpdateUserStatus(arg0, arg1 string) (*model.Status, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -213,14 +213,34 @@ func (s *SQLStore) getBoardsForUserAndTeam(db sq.BaseRunner, userID, teamID stri
|
|||||||
func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID string) (*model.Board, error) {
|
func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID string) (*model.Board, error) {
|
||||||
propertiesBytes, err := json.Marshal(board.Properties)
|
propertiesBytes, err := json.Marshal(board.Properties)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.logger.Error(
|
||||||
|
"failed to marshal board.Properties",
|
||||||
|
mlog.String("board_id", board.ID),
|
||||||
|
mlog.String("board.Properties", fmt.Sprintf("%v", board.Properties)),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cardPropertiesBytes, err := json.Marshal(board.CardProperties)
|
cardPropertiesBytes, err := json.Marshal(board.CardProperties)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.logger.Error(
|
||||||
|
"failed to marshal board.CardProperties",
|
||||||
|
mlog.String("board_id", board.ID),
|
||||||
|
mlog.String("board.CardProperties", fmt.Sprintf("%v", board.CardProperties)),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
columnCalculationsBytes, err := json.Marshal(board.ColumnCalculations)
|
columnCalculationsBytes, err := json.Marshal(board.ColumnCalculations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.logger.Error(
|
||||||
|
"failed to marshal board.ColumnCalculations",
|
||||||
|
mlog.String("board_id", board.ID),
|
||||||
|
mlog.String("board.ColumnCalculations", fmt.Sprintf("%v", board.ColumnCalculations)),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,6 +310,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri
|
|||||||
// writing board history
|
// writing board history
|
||||||
query := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + "boards_history")
|
query := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + "boards_history")
|
||||||
if _, err := query.Exec(); err != nil {
|
if _, err := query.Exec(); err != nil {
|
||||||
|
s.logger.Error("failed to insert board history", mlog.String("board_id", board.ID), mlog.Err(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +416,7 @@ func (s *SQLStore) saveMember(db sq.BaseRunner, bm *model.BoardMember) (*model.B
|
|||||||
} else {
|
} else {
|
||||||
query = query.Suffix(
|
query = query.Suffix(
|
||||||
`ON CONFLICT (board_id, user_id)
|
`ON CONFLICT (board_id, user_id)
|
||||||
DO UPDATE SET scheme_admin = EXCLUDED.scheme_admin, scheme_editor = EXCLUDED.scheme_editor,
|
DO UPDATE SET scheme_admin = EXCLUDED.scheme_admin, scheme_editor = EXCLUDED.scheme_editor,
|
||||||
scheme_commenter = EXCLUDED.scheme_commenter, scheme_viewer = EXCLUDED.scheme_viewer`,
|
scheme_commenter = EXCLUDED.scheme_commenter, scheme_viewer = EXCLUDED.scheme_viewer`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/mattermost/morph/models"
|
"github.com/mattermost/morph/models"
|
||||||
@ -18,7 +23,7 @@ import (
|
|||||||
mysql "github.com/mattermost/morph/drivers/mysql"
|
mysql "github.com/mattermost/morph/drivers/mysql"
|
||||||
postgres "github.com/mattermost/morph/drivers/postgres"
|
postgres "github.com/mattermost/morph/drivers/postgres"
|
||||||
sqlite "github.com/mattermost/morph/drivers/sqlite"
|
sqlite "github.com/mattermost/morph/drivers/sqlite"
|
||||||
mbindata "github.com/mattermost/morph/sources/go_bindata"
|
embedded "github.com/mattermost/morph/sources/embedded"
|
||||||
|
|
||||||
mysqldriver "github.com/go-sql-driver/mysql"
|
mysqldriver "github.com/go-sql-driver/mysql"
|
||||||
_ "github.com/lib/pq" // postgres driver
|
_ "github.com/lib/pq" // postgres driver
|
||||||
@ -33,11 +38,16 @@ import (
|
|||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
const (
|
const (
|
||||||
uniqueIDsMigrationRequiredVersion = 14
|
uniqueIDsMigrationRequiredVersion = 14
|
||||||
|
teamsAndBoardsMigrationRequiredVersion = 17
|
||||||
|
|
||||||
|
teamLessBoardsMigrationKey = "TeamLessBoardsMigrationComplete"
|
||||||
|
|
||||||
tempSchemaMigrationTableName = "temp_schema_migration"
|
tempSchemaMigrationTableName = "temp_schema_migration"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errChannelCreatorNotInTeam = errors.New("channel creator not found in user teams")
|
||||||
|
|
||||||
func appendMultipleStatementsFlag(connectionString string) (string, error) {
|
func appendMultipleStatementsFlag(connectionString string) (string, error) {
|
||||||
config, err := mysqldriver.ParseDSN(connectionString)
|
config, err := mysqldriver.ParseDSN(connectionString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -134,7 +144,7 @@ func (s *SQLStore) Migrate() error {
|
|||||||
"plugin": s.isPlugin,
|
"plugin": s.isPlugin,
|
||||||
}
|
}
|
||||||
|
|
||||||
migrationAssets := &mbindata.AssetSource{
|
migrationAssets := &embedded.AssetSource{
|
||||||
Names: assetNamesForDriver,
|
Names: assetNamesForDriver,
|
||||||
AssetFunc: func(name string) ([]byte, error) {
|
AssetFunc: func(name string) ([]byte, error) {
|
||||||
asset, mErr := assets.ReadFile(filepath.Join("migrations", name))
|
asset, mErr := assets.ReadFile(filepath.Join("migrations", name))
|
||||||
@ -157,11 +167,10 @@ func (s *SQLStore) Migrate() error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := mbindata.WithInstance(migrationAssets)
|
src, err := embedded.WithInstance(migrationAssets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
opts := []morph.EngineOption{
|
opts := []morph.EngineOption{
|
||||||
morph.WithLock("mm-lock-key"),
|
morph.WithLock("mm-lock-key"),
|
||||||
@ -220,6 +229,23 @@ func (s *SQLStore) Migrate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.deleteOldSchemaMigrationTable(); err != nil {
|
if err := s.deleteOldSchemaMigrationTable(); err != nil {
|
||||||
|
if s.isPlugin {
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ensureMigrationsAppliedUpToVersion(engine, driver, teamsAndBoardsMigrationRequiredVersion); err != nil {
|
||||||
|
if s.isPlugin {
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.migrateTeamLessBoards(); err != nil {
|
||||||
|
if s.isPlugin {
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,6 +468,192 @@ func (s *SQLStore) deleteOldSchemaMigrationTable() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We no longer support boards existing in DMs and private
|
||||||
|
// group messages. This function migrates all boards
|
||||||
|
// belonging to a DM to the best possible team.
|
||||||
|
func (s *SQLStore) migrateTeamLessBoards() error {
|
||||||
|
if !s.isPlugin {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
setting, err := s.GetSystemSetting(teamLessBoardsMigrationKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get teamless boards migration state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the migration is already completed, do not run it again.
|
||||||
|
if hasAlreadyRun, _ := strconv.ParseBool(setting); hasAlreadyRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
boards, err := s.getDMBoards(s.db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info(fmt.Sprintf("Migrating %d teamless boards to a team", len(boards)))
|
||||||
|
|
||||||
|
// cache for best suitable team for a DM. Since a DM can
|
||||||
|
// contain multiple boards, caching this avoids
|
||||||
|
// duplicate queries for the same DM.
|
||||||
|
channelToTeamCache := map[string]string{}
|
||||||
|
|
||||||
|
tx, err := s.db.BeginTx(context.Background(), nil)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("error starting transaction in migrateTeamLessBoards", mlog.Err(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range boards {
|
||||||
|
// check the cache first
|
||||||
|
teamID, ok := channelToTeamCache[boards[i].ChannelID]
|
||||||
|
|
||||||
|
// query DB if entry not found in cache
|
||||||
|
if !ok {
|
||||||
|
teamID, err = s.getBestTeamForBoard(s.db, boards[i])
|
||||||
|
if err != nil {
|
||||||
|
// don't let one board's error spoil
|
||||||
|
// the mood for others
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channelToTeamCache[boards[i].ChannelID] = teamID
|
||||||
|
boards[i].TeamID = teamID
|
||||||
|
|
||||||
|
query := s.getQueryBuilder(tx).
|
||||||
|
Update(s.tablePrefix+"boards").
|
||||||
|
Set("team_id", teamID).
|
||||||
|
Set("type", model.BoardTypePrivate).
|
||||||
|
Where(sq.Eq{"id": boards[i].ID})
|
||||||
|
|
||||||
|
if _, err := query.Exec(); err != nil {
|
||||||
|
s.logger.Error("failed to set team id for board", mlog.String("board_id", boards[i].ID), mlog.String("team_id", teamID), mlog.Err(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.setSystemSetting(tx, teamLessBoardsMigrationKey, strconv.FormatBool(true)); err != nil {
|
||||||
|
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||||
|
s.logger.Error("transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "migrateTeamLessBoards"))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot mark migration as completed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
s.logger.Error("failed to commit migrateTeamLessBoards transaction", mlog.Err(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) getDMBoards(tx sq.BaseRunner) ([]*model.Board, error) {
|
||||||
|
conditions := sq.And{
|
||||||
|
sq.Eq{"team_id": ""},
|
||||||
|
sq.Or{
|
||||||
|
sq.Eq{"type": "D"},
|
||||||
|
sq.Eq{"type": "G"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.getBoardsByCondition(tx, conditions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The destination is selected as the first team where all members
|
||||||
|
// of the DM are a part of. If no such team exists,
|
||||||
|
// we use the first team to which DM creator belongs to.
|
||||||
|
func (s *SQLStore) getBestTeamForBoard(tx sq.BaseRunner, board *model.Board) (string, error) {
|
||||||
|
userTeams, err := s.getBoardUserTeams(tx, board)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
teams := [][]interface{}{}
|
||||||
|
for _, userTeam := range userTeams {
|
||||||
|
userTeamInterfaces := make([]interface{}, len(userTeam))
|
||||||
|
for i := range userTeam {
|
||||||
|
userTeamInterfaces[i] = userTeam[i]
|
||||||
|
}
|
||||||
|
teams = append(teams, userTeamInterfaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTeams := utils.Intersection(teams...)
|
||||||
|
var teamID string
|
||||||
|
if len(commonTeams) > 0 {
|
||||||
|
teamID = commonTeams[0].(string)
|
||||||
|
} else {
|
||||||
|
// no common teams found. Let's try finding the best suitable team
|
||||||
|
if board.Type == "D" {
|
||||||
|
// get DM's creator and pick one of their team
|
||||||
|
channel, appErr := (*s.pluginAPI).GetChannel(board.ChannelID)
|
||||||
|
if appErr != nil {
|
||||||
|
s.logger.Error("failed to fetch DM channel for board", mlog.String("board_id", board.ID), mlog.String("channel_id", board.ChannelID), mlog.Err(appErr))
|
||||||
|
return "", appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := userTeams[channel.CreatorId]; !ok {
|
||||||
|
err := fmt.Errorf("%w board_id: %s, channel_id: %s, creator_id: %s", errChannelCreatorNotInTeam, board.ID, board.ChannelID, channel.CreatorId)
|
||||||
|
s.logger.Error(err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
teamID = userTeams[channel.CreatorId][0]
|
||||||
|
} else if board.Type == "G" {
|
||||||
|
// pick the team that has the most users as members
|
||||||
|
teamFrequency := map[string]int{}
|
||||||
|
highestFrequencyTeam := ""
|
||||||
|
highestFrequencyTeamFrequency := -1
|
||||||
|
|
||||||
|
for _, teams := range userTeams {
|
||||||
|
for _, teamID := range teams {
|
||||||
|
teamFrequency[teamID]++
|
||||||
|
|
||||||
|
if teamFrequency[teamID] > highestFrequencyTeamFrequency {
|
||||||
|
highestFrequencyTeamFrequency = teamFrequency[teamID]
|
||||||
|
highestFrequencyTeam = teamID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
teamID = highestFrequencyTeam
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return teamID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) getBoardUserTeams(tx sq.BaseRunner, board *model.Board) (map[string][]string, error) {
|
||||||
|
query := s.getQueryBuilder(tx).
|
||||||
|
Select("teammembers.userid", "teammembers.teamid").
|
||||||
|
From("channelmembers").
|
||||||
|
Join("teammembers ON channelmembers.userid = teammembers.userid").
|
||||||
|
Where(sq.Eq{"channelid": board.ChannelID})
|
||||||
|
|
||||||
|
rows, err := query.Query()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to fetch user teams for board", mlog.String("boardID", board.ID), mlog.String("channelID", board.ChannelID), mlog.Err(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
userTeams := map[string][]string{}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var userID, teamID string
|
||||||
|
err := rows.Scan(&userID, &teamID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("getBoardUserTeams failed to scan SQL query result", mlog.String("boardID", board.ID), mlog.String("channelID", board.ChannelID), mlog.Err(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userTeams[userID] = append(userTeams[userID], teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userTeams, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ensureMigrationsAppliedUpToVersion(engine *morph.Morph, driver drivers.Driver, version int) error {
|
func ensureMigrationsAppliedUpToVersion(engine *morph.Morph, driver drivers.Driver, version int) error {
|
||||||
applied, err := driver.AppliedMigrations()
|
applied, err := driver.AppliedMigrations()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -108,8 +108,10 @@ CREATE TABLE {{.prefix}}boards_history (
|
|||||||
{{if .postgres}}
|
{{if .postgres}}
|
||||||
INSERT INTO {{.prefix}}boards (
|
INSERT INTO {{.prefix}}boards (
|
||||||
SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.type, B.title, (B.fields->>'description')::text,
|
SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.type, B.title, (B.fields->>'description')::text,
|
||||||
B.fields->>'icon', (B.fields->'showDescription')::text::boolean, (B.fields->'isTemplate')::text::boolean,
|
B.fields->>'icon',
|
||||||
COALESCE((B.fields->'templateVer')::text, '0')::int,
|
COALESCE((fields->'showDescription')::text::boolean, false),
|
||||||
|
COALESCE((fields->'isTemplate')::text::boolean, false),
|
||||||
|
COALESCE((B.fields->'templateVer')::text::int, 0),
|
||||||
'{}', B.fields->'cardProperties', B.fields->'columnCalculations', B.create_at,
|
'{}', B.fields->'cardProperties', B.fields->'columnCalculations', B.create_at,
|
||||||
B.update_at, B.delete_at
|
B.update_at, B.delete_at
|
||||||
FROM {{.prefix}}blocks AS B
|
FROM {{.prefix}}blocks AS B
|
||||||
@ -118,8 +120,10 @@ CREATE TABLE {{.prefix}}boards_history (
|
|||||||
);
|
);
|
||||||
INSERT INTO {{.prefix}}boards_history (
|
INSERT INTO {{.prefix}}boards_history (
|
||||||
SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.type, B.title, (B.fields->>'description')::text,
|
SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.type, B.title, (B.fields->>'description')::text,
|
||||||
B.fields->>'icon', (B.fields->'showDescription')::text::boolean, (B.fields->'isTemplate')::text::boolean,
|
B.fields->>'icon',
|
||||||
COALESCE((B.fields->'templateVer')::text, '0')::int,
|
COALESCE((fields->'showDescription')::text::boolean, false),
|
||||||
|
COALESCE((fields->'isTemplate')::text::boolean, false),
|
||||||
|
COALESCE((B.fields->'templateVer')::text::int, 0),
|
||||||
'{}', B.fields->'cardProperties', B.fields->'columnCalculations', B.create_at,
|
'{}', B.fields->'cardProperties', B.fields->'columnCalculations', B.create_at,
|
||||||
B.update_at, B.delete_at
|
B.update_at, B.delete_at
|
||||||
FROM {{.prefix}}blocks_history AS B
|
FROM {{.prefix}}blocks_history AS B
|
||||||
@ -157,8 +161,10 @@ CREATE TABLE {{.prefix}}boards_history (
|
|||||||
{{if .postgres}}
|
{{if .postgres}}
|
||||||
INSERT INTO {{.prefix}}boards (
|
INSERT INTO {{.prefix}}boards (
|
||||||
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, (fields->>'description')::text,
|
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, (fields->>'description')::text,
|
||||||
B.fields->>'icon', (fields->'showDescription')::text::boolean, (fields->'isTemplate')::text::boolean,
|
B.fields->>'icon',
|
||||||
(B.fields->'templateVer')::text::int,
|
COALESCE((fields->'showDescription')::text::boolean, false),
|
||||||
|
COALESCE((fields->'isTemplate')::text::boolean, false),
|
||||||
|
COALESCE((B.fields->'templateVer')::text::int, 0),
|
||||||
'{}', fields->'cardProperties', fields->'columnCalculations', create_at,
|
'{}', fields->'cardProperties', fields->'columnCalculations', create_at,
|
||||||
update_at, delete_at
|
update_at, delete_at
|
||||||
FROM {{.prefix}}blocks AS B
|
FROM {{.prefix}}blocks AS B
|
||||||
@ -166,8 +172,10 @@ CREATE TABLE {{.prefix}}boards_history (
|
|||||||
);
|
);
|
||||||
INSERT INTO {{.prefix}}boards_history (
|
INSERT INTO {{.prefix}}boards_history (
|
||||||
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, (fields->>'description')::text,
|
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, (fields->>'description')::text,
|
||||||
B.fields->>'icon', (fields->'showDescription')::text::boolean, (fields->'isTemplate')::text::boolean,
|
B.fields->>'icon',
|
||||||
(B.fields->'templateVer')::text::int,
|
COALESCE((fields->'showDescription')::text::boolean, false),
|
||||||
|
COALESCE((fields->'isTemplate')::text::boolean, false),
|
||||||
|
COALESCE((B.fields->'templateVer')::text::int, 0),
|
||||||
'{}', fields->'cardProperties', fields->'columnCalculations', create_at,
|
'{}', fields->'cardProperties', fields->'columnCalculations', create_at,
|
||||||
update_at, delete_at
|
update_at, delete_at
|
||||||
FROM {{.prefix}}blocks_history AS B
|
FROM {{.prefix}}blocks_history AS B
|
||||||
@ -177,8 +185,10 @@ CREATE TABLE {{.prefix}}boards_history (
|
|||||||
{{if .mysql}}
|
{{if .mysql}}
|
||||||
INSERT INTO {{.prefix}}boards (
|
INSERT INTO {{.prefix}}boards (
|
||||||
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, JSON_UNQUOTE(JSON_EXTRACT(fields,'$.description')),
|
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, JSON_UNQUOTE(JSON_EXTRACT(fields,'$.description')),
|
||||||
JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')), fields->'$.showDescription', fields->'$.isTemplate',
|
JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')),
|
||||||
B.fields->'$.templateVer',
|
COALESCE(B.fields->'$.showDescription', 'false') = 'true',
|
||||||
|
COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',
|
||||||
|
COALESCE(B.fields->'$.templateVer', 0),
|
||||||
'{}', fields->'$.cardProperties', fields->'$.columnCalculations', create_at,
|
'{}', fields->'$.cardProperties', fields->'$.columnCalculations', create_at,
|
||||||
update_at, delete_at
|
update_at, delete_at
|
||||||
FROM {{.prefix}}blocks AS B
|
FROM {{.prefix}}blocks AS B
|
||||||
@ -186,8 +196,10 @@ CREATE TABLE {{.prefix}}boards_history (
|
|||||||
);
|
);
|
||||||
INSERT INTO {{.prefix}}boards_history (
|
INSERT INTO {{.prefix}}boards_history (
|
||||||
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, JSON_UNQUOTE(JSON_EXTRACT(fields,'$.description')),
|
SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O', title, JSON_UNQUOTE(JSON_EXTRACT(fields,'$.description')),
|
||||||
JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')), fields->'$.showDescription', fields->'$.isTemplate',
|
JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')),
|
||||||
B.fields->'$.templateVer',
|
COALESCE(B.fields->'$.showDescription', 'false') = 'true',
|
||||||
|
COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',
|
||||||
|
COALESCE(B.fields->'$.templateVer', 0),
|
||||||
'{}', fields->'$.cardProperties', fields->'$.columnCalculations', create_at,
|
'{}', fields->'$.cardProperties', fields->'$.columnCalculations', create_at,
|
||||||
update_at, delete_at
|
update_at, delete_at
|
||||||
FROM {{.prefix}}blocks_history AS B
|
FROM {{.prefix}}blocks_history AS B
|
||||||
@ -367,12 +379,10 @@ INSERT INTO {{.prefix}}board_members (
|
|||||||
SELECT B.Id, CM.UserId, CM.Roles, (CM.UserId=B.created_by) OR CM.SchemeAdmin, CM.SchemeUser, FALSE, CM.SchemeGuest
|
SELECT B.Id, CM.UserId, CM.Roles, (CM.UserId=B.created_by) OR CM.SchemeAdmin, CM.SchemeUser, FALSE, CM.SchemeGuest
|
||||||
FROM {{.prefix}}boards AS B
|
FROM {{.prefix}}boards AS B
|
||||||
INNER JOIN ChannelMembers as CM ON CM.ChannelId=B.channel_id
|
INNER JOIN ChannelMembers as CM ON CM.ChannelId=B.channel_id
|
||||||
WHERE NOT B.is_template
|
|
||||||
);
|
);
|
||||||
{{else}}
|
{{else}}
|
||||||
{{- /* if we're in personal server or desktop, create memberships for everyone */ -}}
|
{{- /* if we're in personal server or desktop, create memberships for everyone */ -}}
|
||||||
INSERT INTO {{.prefix}}board_members
|
INSERT INTO {{.prefix}}board_members
|
||||||
SELECT B.id, U.id, '', B.created_by=U.id, TRUE, FALSE, FALSE
|
SELECT B.id, U.id, '', B.created_by=U.id, TRUE, FALSE, FALSE
|
||||||
FROM {{.prefix}}boards AS B, {{.prefix}}users AS U
|
FROM {{.prefix}}boards AS B, {{.prefix}}users AS U;
|
||||||
WHERE NOT B.is_template;
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +17,7 @@ type Params struct {
|
|||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
IsPlugin bool
|
IsPlugin bool
|
||||||
NewMutexFn MutexFactory
|
NewMutexFn MutexFactory
|
||||||
|
PluginAPI *plugin.API
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Params) CheckValid() error {
|
func (p Params) CheckValid() error {
|
||||||
|
@ -3,6 +3,8 @@ package sqlstore
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
@ -20,6 +22,7 @@ type SQLStore struct {
|
|||||||
isPlugin bool
|
isPlugin bool
|
||||||
logger *mlog.Logger
|
logger *mlog.Logger
|
||||||
NewMutexFn MutexFactory
|
NewMutexFn MutexFactory
|
||||||
|
pluginAPI *plugin.API
|
||||||
}
|
}
|
||||||
|
|
||||||
// MutexFactory is used by the store in plugin mode to generate
|
// MutexFactory is used by the store in plugin mode to generate
|
||||||
@ -42,6 +45,7 @@ func New(params Params) (*SQLStore, error) {
|
|||||||
logger: params.Logger,
|
logger: params.Logger,
|
||||||
isPlugin: params.IsPlugin,
|
isPlugin: params.IsPlugin,
|
||||||
NewMutexFn: params.NewMutexFn,
|
NewMutexFn: params.NewMutexFn,
|
||||||
|
pluginAPI: params.PluginAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := store.Migrate()
|
err := store.Migrate()
|
||||||
|
@ -3,6 +3,7 @@ package sqlstore
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -33,7 +34,12 @@ func PrepareNewTestDatabase() (dbType string, connectionString string, err error
|
|||||||
var rootUser string
|
var rootUser string
|
||||||
|
|
||||||
if dbType == model.SqliteDBType {
|
if dbType == model.SqliteDBType {
|
||||||
connectionString = "file::memory:?cache=shared&_busy_timeout=5000"
|
file, err := ioutil.TempFile("", "fbtest_*.db")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
connectionString = file.Name() + "?_busy_timeout=5000"
|
||||||
|
_ = file.Close()
|
||||||
} else if port := strings.TrimSpace(os.Getenv("FB_STORE_TEST_DOCKER_PORT")); port != "" {
|
} else if port := strings.TrimSpace(os.Getenv("FB_STORE_TEST_DOCKER_PORT")); port != "" {
|
||||||
// docker unit tests take priority over any DSN env vars
|
// docker unit tests take priority over any DSN env vars
|
||||||
var template string
|
var template string
|
||||||
|
@ -2,6 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
mm_model "github.com/mattermost/mattermost-server/v6/model"
|
||||||
@ -54,3 +55,43 @@ func StructToMap(v interface{}) (m map[string]interface{}) {
|
|||||||
_ = json.Unmarshal(b, &m)
|
_ = json.Unmarshal(b, &m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func intersection(a []interface{}, b []interface{}) []interface{} {
|
||||||
|
set := make([]interface{}, 0)
|
||||||
|
hash := make(map[interface{}]bool)
|
||||||
|
av := reflect.ValueOf(a)
|
||||||
|
bv := reflect.ValueOf(b)
|
||||||
|
|
||||||
|
for i := 0; i < av.Len(); i++ {
|
||||||
|
el := av.Index(i).Interface()
|
||||||
|
hash[el] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < bv.Len(); i++ {
|
||||||
|
el := bv.Index(i).Interface()
|
||||||
|
if _, found := hash[el]; found {
|
||||||
|
set = append(set, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
func Intersection(x ...[]interface{}) []interface{} {
|
||||||
|
if len(x) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(x) == 1 {
|
||||||
|
return x[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
result := x[0]
|
||||||
|
i := 1
|
||||||
|
for i < len(x) {
|
||||||
|
result = intersection(result, x[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -186,6 +186,21 @@ func (mr *MockAPIMockRecorder) CreatePost(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePost", reflect.TypeOf((*MockAPI)(nil).CreatePost), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePost", reflect.TypeOf((*MockAPI)(nil).CreatePost), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSession mocks base method.
|
||||||
|
func (m *MockAPI) CreateSession(arg0 *model.Session) (*model.Session, *model.AppError) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateSession", arg0)
|
||||||
|
ret0, _ := ret[0].(*model.Session)
|
||||||
|
ret1, _ := ret[1].(*model.AppError)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSession indicates an expected call of CreateSession.
|
||||||
|
func (mr *MockAPIMockRecorder) CreateSession(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockAPI)(nil).CreateSession), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateTeam mocks base method.
|
// CreateTeam mocks base method.
|
||||||
func (m *MockAPI) CreateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {
|
func (m *MockAPI) CreateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -457,6 +472,20 @@ func (mr *MockAPIMockRecorder) ExecuteSlashCommand(arg0 interface{}) *gomock.Cal
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteSlashCommand", reflect.TypeOf((*MockAPI)(nil).ExecuteSlashCommand), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteSlashCommand", reflect.TypeOf((*MockAPI)(nil).ExecuteSlashCommand), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtendSessionExpiry mocks base method.
|
||||||
|
func (m *MockAPI) ExtendSessionExpiry(arg0 string, arg1 int64) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ExtendSessionExpiry", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendSessionExpiry indicates an expected call of ExtendSessionExpiry.
|
||||||
|
func (mr *MockAPIMockRecorder) ExtendSessionExpiry(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendSessionExpiry", reflect.TypeOf((*MockAPI)(nil).ExtendSessionExpiry), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBot mocks base method.
|
// GetBot mocks base method.
|
||||||
func (m *MockAPI) GetBot(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {
|
func (m *MockAPI) GetBot(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -1573,6 +1602,20 @@ func (mr *MockAPIMockRecorder) InstallPlugin(arg0, arg1 interface{}) *gomock.Cal
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallPlugin", reflect.TypeOf((*MockAPI)(nil).InstallPlugin), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallPlugin", reflect.TypeOf((*MockAPI)(nil).InstallPlugin), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsEnterpriseReady mocks base method.
|
||||||
|
func (m *MockAPI) IsEnterpriseReady() bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "IsEnterpriseReady")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnterpriseReady indicates an expected call of IsEnterpriseReady.
|
||||||
|
func (mr *MockAPIMockRecorder) IsEnterpriseReady() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnterpriseReady", reflect.TypeOf((*MockAPI)(nil).IsEnterpriseReady))
|
||||||
|
}
|
||||||
|
|
||||||
// KVCompareAndDelete mocks base method.
|
// KVCompareAndDelete mocks base method.
|
||||||
func (m *MockAPI) KVCompareAndDelete(arg0 string, arg1 []byte) (bool, *model.AppError) {
|
func (m *MockAPI) KVCompareAndDelete(arg0 string, arg1 []byte) (bool, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2014,6 +2057,20 @@ func (mr *MockAPIMockRecorder) RemoveTeamIcon(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveTeamIcon", reflect.TypeOf((*MockAPI)(nil).RemoveTeamIcon), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveTeamIcon", reflect.TypeOf((*MockAPI)(nil).RemoveTeamIcon), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveUserCustomStatus mocks base method.
|
||||||
|
func (m *MockAPI) RemoveUserCustomStatus(arg0 string) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RemoveUserCustomStatus", arg0)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUserCustomStatus indicates an expected call of RemoveUserCustomStatus.
|
||||||
|
func (mr *MockAPIMockRecorder) RemoveUserCustomStatus(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveUserCustomStatus", reflect.TypeOf((*MockAPI)(nil).RemoveUserCustomStatus), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// RequestTrialLicense mocks base method.
|
// RequestTrialLicense mocks base method.
|
||||||
func (m *MockAPI) RequestTrialLicense(arg0 string, arg1 int, arg2, arg3 bool) *model.AppError {
|
func (m *MockAPI) RequestTrialLicense(arg0 string, arg1 int, arg2, arg3 bool) *model.AppError {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2028,6 +2085,20 @@ func (mr *MockAPIMockRecorder) RequestTrialLicense(arg0, arg1, arg2, arg3 interf
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestTrialLicense", reflect.TypeOf((*MockAPI)(nil).RequestTrialLicense), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestTrialLicense", reflect.TypeOf((*MockAPI)(nil).RequestTrialLicense), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RevokeSession mocks base method.
|
||||||
|
func (m *MockAPI) RevokeSession(arg0 string) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RevokeSession", arg0)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeSession indicates an expected call of RevokeSession.
|
||||||
|
func (mr *MockAPIMockRecorder) RevokeSession(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeSession", reflect.TypeOf((*MockAPI)(nil).RevokeSession), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// RevokeUserAccessToken mocks base method.
|
// RevokeUserAccessToken mocks base method.
|
||||||
func (m *MockAPI) RevokeUserAccessToken(arg0 string) *model.AppError {
|
func (m *MockAPI) RevokeUserAccessToken(arg0 string) *model.AppError {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2042,6 +2113,20 @@ func (mr *MockAPIMockRecorder) RevokeUserAccessToken(arg0 interface{}) *gomock.C
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeUserAccessToken", reflect.TypeOf((*MockAPI)(nil).RevokeUserAccessToken), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeUserAccessToken", reflect.TypeOf((*MockAPI)(nil).RevokeUserAccessToken), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RolesGrantPermission mocks base method.
|
||||||
|
func (m *MockAPI) RolesGrantPermission(arg0 []string, arg1 string) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RolesGrantPermission", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RolesGrantPermission indicates an expected call of RolesGrantPermission.
|
||||||
|
func (mr *MockAPIMockRecorder) RolesGrantPermission(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RolesGrantPermission", reflect.TypeOf((*MockAPI)(nil).RolesGrantPermission), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// SaveConfig mocks base method.
|
// SaveConfig mocks base method.
|
||||||
func (m *MockAPI) SaveConfig(arg0 *model.Config) *model.AppError {
|
func (m *MockAPI) SaveConfig(arg0 *model.Config) *model.AppError {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -2437,6 +2522,20 @@ func (mr *MockAPIMockRecorder) UpdateUserActive(arg0, arg1 interface{}) *gomock.
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserActive", reflect.TypeOf((*MockAPI)(nil).UpdateUserActive), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserActive", reflect.TypeOf((*MockAPI)(nil).UpdateUserActive), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUserCustomStatus mocks base method.
|
||||||
|
func (m *MockAPI) UpdateUserCustomStatus(arg0 string, arg1 *model.CustomStatus) *model.AppError {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateUserCustomStatus", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*model.AppError)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserCustomStatus indicates an expected call of UpdateUserCustomStatus.
|
||||||
|
func (mr *MockAPIMockRecorder) UpdateUserCustomStatus(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserCustomStatus", reflect.TypeOf((*MockAPI)(nil).UpdateUserCustomStatus), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateUserStatus mocks base method.
|
// UpdateUserStatus mocks base method.
|
||||||
func (m *MockAPI) UpdateUserStatus(arg0, arg1 string) (*model.Status, *model.AppError) {
|
func (m *MockAPI) UpdateUserStatus(arg0, arg1 string) (*model.Status, *model.AppError) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:mattermost/react",
|
"plugin:react/recommended",
|
||||||
"plugin:cypress/recommended",
|
"plugin:cypress/recommended",
|
||||||
"plugin:jquery/deprecated"
|
"plugin:jquery/deprecated"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"react",
|
||||||
"babel",
|
"babel",
|
||||||
"mattermost",
|
|
||||||
"import",
|
"import",
|
||||||
"cypress",
|
"cypress",
|
||||||
"jquery",
|
"jquery",
|
||||||
@ -25,6 +25,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"react/display-name": [
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"ignoreTranspilerName": false
|
||||||
|
}
|
||||||
|
],
|
||||||
"max-lines": "off",
|
"max-lines": "off",
|
||||||
"no-unused-expressions": 0,
|
"no-unused-expressions": 0,
|
||||||
"babel/no-unused-expressions": [2, {"allowShortCircuit": true}],
|
"babel/no-unused-expressions": [2, {"allowShortCircuit": true}],
|
||||||
@ -71,6 +77,7 @@
|
|||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"mattermost/no-dispatch-getstate": 0, // Failing in eslint 8
|
||||||
"import/no-unresolved": 0, // ts handles this better
|
"import/no-unresolved": 0, // ts handles this better
|
||||||
"camelcase": 0,
|
"camelcase": 0,
|
||||||
"semi": "off",
|
"semi": "off",
|
||||||
|
20343
webapp/package-lock.json
generated
20343
webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,29 +23,30 @@
|
|||||||
"cypress:open": "cypress open"
|
"cypress:open": "cypress open"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@draft-js-plugins/editor": "^4.1.0",
|
"@draft-js-plugins/editor": "^4.1.2",
|
||||||
"@draft-js-plugins/emoji": "^4.5.5",
|
"@draft-js-plugins/emoji": "^4.6.0",
|
||||||
"@draft-js-plugins/mention": "^5.0.0",
|
"@draft-js-plugins/mention": "^5.1.2",
|
||||||
"@fullcalendar/core": "^5.10.0",
|
"@fullcalendar/core": "^5.10.1",
|
||||||
"@fullcalendar/daygrid": "^5.10.0",
|
"@fullcalendar/daygrid": "^5.10.1",
|
||||||
"@fullcalendar/interaction": "^5.10.0",
|
"@fullcalendar/interaction": "^5.10.1",
|
||||||
"@fullcalendar/react": "^5.10.0",
|
"@fullcalendar/react": "^5.10.1",
|
||||||
"@mattermost/compass-icons": "^0.1.10",
|
"@mattermost/compass-icons": "^0.1.22",
|
||||||
"@reduxjs/toolkit": "^1.6.0",
|
"@reduxjs/toolkit": "^1.8.0",
|
||||||
"@tippyjs/react": "4.2.6",
|
"@tippyjs/react": "4.2.6",
|
||||||
"color": "^4.0.0",
|
"color": "^4.2.1",
|
||||||
"draft-js": "^0.11.7",
|
"draft-js": "^0.11.7",
|
||||||
"emoji-mart": "^3.0.1",
|
"emoji-mart": "^3.0.1",
|
||||||
"fstream": "^1.0.12",
|
"fstream": "^1.0.12",
|
||||||
"fullcalendar": "^5.10.0",
|
"fullcalendar": "^5.10.2",
|
||||||
"imagemin-gifsicle": "^7.0.0",
|
"imagemin-gifsicle": "^7.0.0",
|
||||||
"imagemin-mozjpeg": "^9.0.0",
|
"imagemin-mozjpeg": "^10.0.0",
|
||||||
"imagemin-optipng": "^8.0.0",
|
"imagemin-optipng": "^8.0.0",
|
||||||
"imagemin-pngquant": "^9.0.2",
|
"imagemin-pngquant": "^9.0.2",
|
||||||
"imagemin-svgo": "^8.0.0",
|
"imagemin-svgo": "^10.0.1",
|
||||||
"imagemin-webp": "^7.0.0",
|
"imagemin-webp": "^7.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "^4.0.12",
|
"marked": "^4.0.12",
|
||||||
|
"mini-create-react-context": "^0.4.1",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"nanoevents": "^5.1.13",
|
"nanoevents": "^5.1.13",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
@ -55,11 +56,11 @@
|
|||||||
"react-dnd-scrolling": "^1.2.1",
|
"react-dnd-scrolling": "^1.2.1",
|
||||||
"react-dnd-touch-backend": "^14.0.0",
|
"react-dnd-touch-backend": "^14.0.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-hot-keys": "^2.6.2",
|
"react-hot-keys": "^2.7.1",
|
||||||
"react-hotkeys-hook": "^3.3.0",
|
"react-hotkeys-hook": "^3.4.4",
|
||||||
"react-intl": "^5.13.5",
|
"react-intl": "^5.24.7",
|
||||||
"react-redux": "^7.2.4",
|
"react-redux": "^7.2.6",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.1",
|
||||||
"react-select": "^4.3.0",
|
"react-select": "^4.3.0",
|
||||||
"trim-newlines": "^4.0.2"
|
"trim-newlines": "^4.0.2"
|
||||||
},
|
},
|
||||||
@ -74,8 +75,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.tsx?$": "ts-jest"
|
"^.+\\.tsx?$": "@swc/jest"
|
||||||
},
|
},
|
||||||
|
"transformIgnorePatterns": [
|
||||||
|
"/nanoevents/"
|
||||||
|
],
|
||||||
|
"maxWorkers": "50%",
|
||||||
|
"testEnvironment": "jsdom",
|
||||||
"collectCoverage": true,
|
"collectCoverage": true,
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"src/**/*.{ts,tsx,js,jsx}",
|
"src/**/*.{ts,tsx,js,jsx}",
|
||||||
@ -83,67 +89,69 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@formatjs/cli": "^3.2.0",
|
"@formatjs/cli": "^4.8.2",
|
||||||
"@formatjs/ts-transformer": "^3.2.1",
|
"@formatjs/ts-transformer": "^3.9.2",
|
||||||
|
"@swc/jest": "^0.2.20",
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
"@testing-library/dom": "^7.31.2",
|
"@testing-library/dom": "^8.11.4",
|
||||||
"@testing-library/jest-dom": "^5.11.10",
|
"@testing-library/jest-dom": "^5.16.3",
|
||||||
"@testing-library/react": "^11.2.5",
|
"@testing-library/react": "^11.2.5",
|
||||||
"@testing-library/user-event": "^13.1.9",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/color": "^3.0.2",
|
"@types/color": "^3.0.3",
|
||||||
"@types/draft-js": "^0.11.6",
|
"@types/draft-js": "^0.11.9",
|
||||||
"@types/emoji-mart": "^3.0.4",
|
"@types/emoji-mart": "^3.0.9",
|
||||||
"@types/jest": "^26.0.21",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/marked": "^4.0.1",
|
"@types/marked": "^4.0.3",
|
||||||
"@types/nanoevents": "^1.0.0",
|
"@types/nanoevents": "^1.0.0",
|
||||||
"@types/react": "^17.0.3",
|
"@types/react": "^17.0.43",
|
||||||
"@types/react-dom": "^17.0.3",
|
"@types/react-dom": "^17.0.14",
|
||||||
"@types/react-intl": "^3.0.0",
|
"@types/react-intl": "^3.0.0",
|
||||||
"@types/react-redux": "^7.1.16",
|
"@types/react-redux": "^7.1.23",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/react-select": "^4.0.13",
|
"@types/react-select": "^4.0.13",
|
||||||
"@types/redux-mock-store": "^1.0.3",
|
"@types/redux-mock-store": "^1.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.19.0",
|
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
||||||
"@typescript-eslint/parser": "^4.19.0",
|
"@typescript-eslint/parser": "^5.16.0",
|
||||||
"copy-webpack-plugin": "^8.1.0",
|
"copy-webpack-plugin": "^10.2.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^5.2.0",
|
"css-loader": "^6.7.1",
|
||||||
"cypress": "^9.5.0",
|
"cypress": "^9.5.2",
|
||||||
"cypress-failed-log": "^2.9.2",
|
"cypress-failed-log": "^2.9.5",
|
||||||
"cypress-real-events": "^1.6.0",
|
"cypress-real-events": "^1.7.0",
|
||||||
"eslint": "^7.22.0",
|
"eslint": "^8.11.0",
|
||||||
"eslint-import-resolver-webpack": "0.13.0",
|
"eslint-import-resolver-webpack": "0.13.2",
|
||||||
"eslint-plugin-babel": "^5.3.1",
|
"eslint-plugin-babel": "^5.3.1",
|
||||||
"eslint-plugin-cypress": "2.11.2",
|
"eslint-plugin-cypress": "2.12.1",
|
||||||
"eslint-plugin-header": "3.1.1",
|
"eslint-plugin-header": "3.1.1",
|
||||||
"eslint-plugin-import": "2.22.1",
|
"eslint-plugin-import": "2.25.4",
|
||||||
"eslint-plugin-jquery": "1.5.1",
|
"eslint-plugin-jquery": "1.5.1",
|
||||||
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee",
|
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee",
|
||||||
"eslint-plugin-no-only-tests": "2.4.0",
|
"eslint-plugin-no-only-tests": "2.6.0",
|
||||||
"eslint-plugin-react": "7.23.1",
|
"eslint-plugin-react": "7.29.4",
|
||||||
"fetch-mock-jest": "^1.5.1",
|
"fetch-mock-jest": "^1.5.1",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"image-webpack-loader": "^8.1.0",
|
"image-webpack-loader": "^8.1.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^27.5.1",
|
||||||
"prettier": "^2.2.1",
|
"jest-mock": "^27.5.1",
|
||||||
|
"prettier": "^2.6.1",
|
||||||
"redux-mock-store": "^1.5.4",
|
"redux-mock-store": "^1.5.4",
|
||||||
"sass": "^1.32.8",
|
"sass": "^1.49.9",
|
||||||
"sass-loader": "^11.0.1",
|
"sass-loader": "^12.6.0",
|
||||||
"start-server-and-test": "^1.12.1",
|
"start-server-and-test": "^1.14.0",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^3.3.1",
|
||||||
"stylelint": "^13.13.1",
|
"stylelint": "^14.6.1",
|
||||||
"stylelint-config-sass-guidelines": "^8.0.0",
|
"stylelint-config-sass-guidelines": "^9.0.1",
|
||||||
"terser-webpack-plugin": "^5.1.1",
|
"terser-webpack-plugin": "^5.3.1",
|
||||||
"ts-jest": "^26.5.4",
|
"ts-jest": "^27.1.4",
|
||||||
"ts-loader": "^8.0.18",
|
"ts-loader": "^9.2.8",
|
||||||
"typescript": "^4.2.3",
|
"typescript": "^4.6.3",
|
||||||
"webpack": "^5.28.0",
|
"webpack": "^5.70.0",
|
||||||
"webpack-cli": "^4.5.0",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack-merge": "^5.7.3"
|
"webpack-merge": "^5.8.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"cypress": "^6.8.0"
|
"cypress": "^9.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {createFilterClause} from './blocks/filterClause'
|
import {createFilterClause} from './blocks/filterClause'
|
||||||
|
|
||||||
|
@ -2403,15 +2403,15 @@ exports[`src/components/workspace show add new view tooltip 1`] = `
|
|||||||
data-escaped=""
|
data-escaped=""
|
||||||
data-placement="bottom-start"
|
data-placement="bottom-start"
|
||||||
data-reference-hidden=""
|
data-reference-hidden=""
|
||||||
data-state="hidden"
|
data-state="visible"
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
style="max-width: 320px; transition-duration: 0ms;"
|
style="max-width: 320px; transition-duration: 250ms;"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="tippy-content"
|
class="tippy-content"
|
||||||
data-state="hidden"
|
data-state="visible"
|
||||||
style="transition-duration: 0ms;"
|
style="transition-duration: 250ms;"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -6,7 +6,7 @@ import {render, screen, waitFor} from '@testing-library/react'
|
|||||||
|
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {fireEvent, render, screen} from '@testing-library/react'
|
import {fireEvent, render, screen, act} from '@testing-library/react'
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import mutator from '../mutator'
|
import mutator from '../mutator'
|
||||||
|
|
||||||
@ -82,19 +82,24 @@ describe('components/blockIconSelector', () => {
|
|||||||
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return a new icon after click on EmojiPicker', async () => {
|
test('return a new icon after click on EmojiPicker', () => {
|
||||||
const {container} = render(wrapIntl(
|
const {container, getByRole, getAllByRole} = render(wrapIntl(
|
||||||
<BlockIconSelector
|
<BlockIconSelector
|
||||||
block={card}
|
block={card}
|
||||||
size='l'
|
size='l'
|
||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
act(() => {
|
||||||
|
userEvent.click(getByRole('button', {name: 'menuwrapper'}))
|
||||||
|
})
|
||||||
const menuPicker = container.querySelector('div#pick')
|
const menuPicker = container.querySelector('div#pick')
|
||||||
expect(menuPicker).not.toBeNull()
|
expect(menuPicker).not.toBeNull()
|
||||||
fireEvent.mouseEnter(menuPicker!)
|
|
||||||
|
|
||||||
const allButtonThumbUp = await screen.findAllByRole('button', {name: /thumbsup/i})
|
act(() => {
|
||||||
|
fireEvent.mouseEnter(menuPicker!)
|
||||||
|
})
|
||||||
|
|
||||||
|
const allButtonThumbUp = getAllByRole('button', {name: /thumbsup/i})
|
||||||
userEvent.click(allButtonThumbUp[0])
|
userEvent.click(allButtonThumbUp[0])
|
||||||
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
||||||
expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '👍')
|
expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '👍')
|
||||||
|
@ -6,7 +6,7 @@ import React from 'react'
|
|||||||
import {MockStoreEnhanced} from 'redux-mock-store'
|
import {MockStoreEnhanced} from 'redux-mock-store'
|
||||||
import {createMemoryHistory} from 'history'
|
import {createMemoryHistory} from 'history'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {FetchMock} from '../../test/fetchMock'
|
import {FetchMock} from '../../test/fetchMock'
|
||||||
import {TestBlockFactory} from '../../test/testBlockFactory'
|
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||||
@ -90,6 +90,11 @@ describe('components/cardDetail/CardDetail', () => {
|
|||||||
},
|
},
|
||||||
current: card.id,
|
current: card.id,
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const component = (
|
const component = (
|
||||||
@ -226,6 +231,11 @@ describe('components/cardDetail/CardDetail', () => {
|
|||||||
},
|
},
|
||||||
current: welcomeCard.id,
|
current: welcomeCard.id,
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const onboardingBoard = TestBlockFactory.createBoard()
|
const onboardingBoard = TestBlockFactory.createBoard()
|
||||||
@ -326,6 +336,11 @@ describe('components/cardDetail/CardDetail', () => {
|
|||||||
},
|
},
|
||||||
current: welcomeCard.id,
|
current: welcomeCard.id,
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const onboardingBoard = TestBlockFactory.createBoard()
|
const onboardingBoard = TestBlockFactory.createBoard()
|
||||||
@ -424,6 +439,11 @@ describe('components/cardDetail/CardDetail', () => {
|
|||||||
},
|
},
|
||||||
current: welcomeCard.id,
|
current: welcomeCard.id,
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const store = mockStore(state)
|
const store = mockStore(state)
|
||||||
|
|
||||||
|
@ -72,6 +72,11 @@ describe('components/cardDetail/cardDetailContents', () => {
|
|||||||
},
|
},
|
||||||
current: card.id,
|
current: card.id,
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const store = mockStateStore([], state)
|
const store = mockStateStore([], state)
|
||||||
const wrap = (child: ReactNode): ReactElement => (
|
const wrap = (child: ReactNode): ReactElement => (
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {render, screen, act} from '@testing-library/react'
|
import {render, screen, act} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import {createIntl} from 'react-intl'
|
import {createIntl} from 'react-intl'
|
||||||
|
|
||||||
@ -99,6 +99,11 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
},
|
},
|
||||||
current: card.id,
|
current: card.id,
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockStore = configureStore([])
|
const mockStore = configureStore([])
|
||||||
|
@ -6,7 +6,7 @@ import React from 'react'
|
|||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapIntl, mockStateStore} from '../../testUtils'
|
import {wrapIntl, mockStateStore} from '../../testUtils'
|
||||||
|
|
||||||
|
@ -64,6 +64,11 @@ describe('components/cardDetail/CommentsList', () => {
|
|||||||
},
|
},
|
||||||
current: 'card_id_1',
|
current: 'card_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const component = (
|
const component = (
|
||||||
|
@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event'
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import mutator from '../mutator'
|
import mutator from '../mutator'
|
||||||
import {Utils} from '../utils'
|
import {Utils} from '../utils'
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {fireEvent, render, screen, within} from '@testing-library/react'
|
import {fireEvent, render, screen, within, act} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import {mockDOM, mockStateStore, wrapDNDIntl} from '../testUtils'
|
import {mockDOM, mockStateStore, wrapDNDIntl} from '../testUtils'
|
||||||
@ -277,9 +277,11 @@ describe('components/centerPanel', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
|
|
||||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
act(() => {
|
||||||
expect(cardElement).not.toBeNull()
|
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||||
userEvent.click(cardElement, {shiftKey: true})
|
expect(cardElement.parentNode).not.toBeNull()
|
||||||
|
userEvent.click(cardElement as HTMLElement, {shiftKey: true})
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//escape
|
//escape
|
||||||
@ -303,16 +305,20 @@ describe('components/centerPanel', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
|
|
||||||
//select card1
|
act(() => {
|
||||||
const card1Element = screen.getByRole('textbox', {name: 'card1'})
|
//select card1
|
||||||
expect(card1Element).not.toBeNull()
|
const card1Element = screen.getByRole('textbox', {name: 'card1'})
|
||||||
userEvent.click(card1Element, {shiftKey: true})
|
expect(card1Element).not.toBeNull()
|
||||||
|
userEvent.click(card1Element, {shiftKey: true})
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//select card2
|
act(() => {
|
||||||
const card2Element = screen.getByRole('textbox', {name: 'card2'})
|
//select card2
|
||||||
expect(card2Element).not.toBeNull()
|
const card2Element = screen.getByRole('textbox', {name: 'card2'})
|
||||||
userEvent.click(card2Element, {shiftKey: true, ctrlKey: true})
|
expect(card2Element).not.toBeNull()
|
||||||
|
userEvent.click(card2Element, {shiftKey: true, ctrlKey: true})
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//escape
|
//escape
|
||||||
@ -335,9 +341,11 @@ describe('components/centerPanel', () => {
|
|||||||
/>
|
/>
|
||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
act(() => {
|
||||||
expect(cardElement).not.toBeNull()
|
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||||
userEvent.click(cardElement, {shiftKey: true})
|
expect(cardElement).not.toBeNull()
|
||||||
|
userEvent.click(cardElement, {shiftKey: true})
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//delete
|
//delete
|
||||||
@ -360,9 +368,11 @@ describe('components/centerPanel', () => {
|
|||||||
/>
|
/>
|
||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
act(() => {
|
||||||
expect(cardElement).not.toBeNull()
|
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||||
userEvent.click(cardElement, {shiftKey: true})
|
expect(cardElement).not.toBeNull()
|
||||||
|
userEvent.click(cardElement, {shiftKey: true})
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//ctrl+d
|
//ctrl+d
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import React, {ReactElement, ReactNode} from 'react'
|
import React, {ReactElement, ReactNode} from 'react'
|
||||||
import {fireEvent, render, screen, waitFor} from '@testing-library/react'
|
import {fireEvent, render, screen, waitFor} from '@testing-library/react'
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {wrapIntl} from '../../testUtils'
|
import {wrapIntl} from '../../testUtils'
|
||||||
|
@ -6,7 +6,7 @@ import {render} from '@testing-library/react'
|
|||||||
|
|
||||||
import {act} from 'react-dom/test-utils'
|
import {act} from 'react-dom/test-utils'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {ImageBlock} from '../../blocks/imageBlock'
|
import {ImageBlock} from '../../blocks/imageBlock'
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
|
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {TextBlock} from '../../blocks/textBlock'
|
import {TextBlock} from '../../blocks/textBlock'
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import '@testing-library/jest-dom'
|
|||||||
import {act, render, screen} from '@testing-library/react'
|
import {act, render, screen} from '@testing-library/react'
|
||||||
|
|
||||||
import React, {ReactNode, ReactElement} from 'react'
|
import React, {ReactNode, ReactElement} from 'react'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
@ -8,7 +8,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapDNDIntl, mockStateStore, blocksById} from '../../testUtils'
|
import {wrapDNDIntl, mockStateStore, blocksById} from '../../testUtils'
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {MockStoreEnhanced} from 'redux-mock-store'
|
import {MockStoreEnhanced} from 'redux-mock-store'
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ import React from 'react'
|
|||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
import {createMemoryHistory} from 'history'
|
import {createMemoryHistory} from 'history'
|
||||||
|
|
||||||
import {render} from '@testing-library/react'
|
import {render, act} from '@testing-library/react'
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import configureStore from 'redux-mock-store'
|
import configureStore from 'redux-mock-store'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapIntl} from '../../testUtils'
|
import {wrapIntl} from '../../testUtils'
|
||||||
|
|
||||||
@ -77,8 +77,12 @@ describe('components/sidebar/GlobalHeaderSettingsMenu', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const {container} = render(component)
|
const {container} = render(component)
|
||||||
userEvent.click(container.querySelector('.menu-entry') as Element)
|
act(() => {
|
||||||
userEvent.hover(container.querySelector('#lang') as Element)
|
userEvent.click(container.querySelector('.menu-entry') as Element)
|
||||||
|
})
|
||||||
|
act(() => {
|
||||||
|
userEvent.hover(container.querySelector('#lang') as Element)
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -91,8 +95,12 @@ describe('components/sidebar/GlobalHeaderSettingsMenu', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const {container} = render(component)
|
const {container} = render(component)
|
||||||
userEvent.click(container.querySelector('.menu-entry') as Element)
|
act(() => {
|
||||||
userEvent.hover(container.querySelector('#import') as Element)
|
userEvent.click(container.querySelector('.menu-entry') as Element)
|
||||||
|
})
|
||||||
|
act(() => {
|
||||||
|
userEvent.hover(container.querySelector('#import') as Element)
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
userEvent.click(container.querySelector('[aria-label="Asana"]') as Element)
|
userEvent.click(container.querySelector('[aria-label="Asana"]') as Element)
|
||||||
|
@ -373,6 +373,7 @@ exports[`components/kanban/calculations/KanbanCalculationOptions with submenu op
|
|||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="dropdown-submenu"
|
class="dropdown-submenu"
|
||||||
|
style="top: -10px; left: 0px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="drops "
|
class="drops "
|
||||||
|
@ -6,7 +6,7 @@ import '@testing-library/jest-dom'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
import {MemoryRouter} from 'react-router-dom'
|
import {MemoryRouter} from 'react-router-dom'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {IPropertyOption, IPropertyTemplate} from '../../blocks/board'
|
import {IPropertyOption, IPropertyTemplate} from '../../blocks/board'
|
||||||
|
@ -9,7 +9,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import Mutator from '../../mutator'
|
import Mutator from '../../mutator'
|
||||||
import {Utils} from '../../utils'
|
import {Utils} from '../../utils'
|
||||||
|
@ -4,7 +4,7 @@ import React from 'react'
|
|||||||
import {fireEvent, render, screen, within} from '@testing-library/react'
|
import {fireEvent, render, screen, within} from '@testing-library/react'
|
||||||
import {createIntl} from 'react-intl'
|
import {createIntl} from 'react-intl'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import Mutator from '../../mutator'
|
import Mutator from '../../mutator'
|
||||||
|
@ -5,7 +5,7 @@ import {render, screen, within} from '@testing-library/react'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import {createIntl} from 'react-intl'
|
import {createIntl} from 'react-intl'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapDNDIntl} from '../../testUtils'
|
import {wrapDNDIntl} from '../../testUtils'
|
||||||
import Mutator from '../../mutator'
|
import Mutator from '../../mutator'
|
||||||
|
@ -38,6 +38,11 @@ describe('components/onboardingTour/addComments/AddCommentTourStep', () => {
|
|||||||
},
|
},
|
||||||
current: 'card_id_1',
|
current: 'card_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
let store = mockStore(state)
|
let store = mockStore(state)
|
||||||
|
|
||||||
|
@ -38,6 +38,11 @@ describe('components/onboardingTour/addComments/AddDescriptionTourStep', () => {
|
|||||||
},
|
},
|
||||||
current: 'card_id_1',
|
current: 'card_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
let store = mockStore(state)
|
let store = mockStore(state)
|
||||||
|
|
||||||
|
@ -38,6 +38,11 @@ describe('components/onboardingTour/addComments/AddPropertiesTourStep', () => {
|
|||||||
},
|
},
|
||||||
current: 'card_id_1',
|
current: 'card_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
let store = mockStore(state)
|
let store = mockStore(state)
|
||||||
|
|
||||||
|
@ -32,6 +32,11 @@ describe('components/onboardingTour/addComments/AddViewTourStep', () => {
|
|||||||
},
|
},
|
||||||
current: 'board_id_1',
|
current: 'board_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
let store = mockStore(state)
|
let store = mockStore(state)
|
||||||
|
|
||||||
|
@ -32,6 +32,11 @@ describe('components/onboardingTour/addComments/CopyLinkTourStep', () => {
|
|||||||
},
|
},
|
||||||
current: 'board_id_1',
|
current: 'board_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
let store = mockStore(state)
|
let store = mockStore(state)
|
||||||
|
|
||||||
|
@ -38,6 +38,11 @@ describe('components/onboardingTour/addComments/OpenCardTourStep', () => {
|
|||||||
},
|
},
|
||||||
current: 'card_id_1',
|
current: 'card_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
let store = mockStore(state)
|
let store = mockStore(state)
|
||||||
|
|
||||||
|
@ -38,6 +38,11 @@ describe('components/onboardingTour/addComments/ShareBoardTourStep', () => {
|
|||||||
},
|
},
|
||||||
current: 'card_id_1',
|
current: 'card_id_1',
|
||||||
},
|
},
|
||||||
|
clientConfig: {
|
||||||
|
value: {
|
||||||
|
featureFlags: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
let store = mockStore(state)
|
let store = mockStore(state)
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import {OnboardingBoardTitle, OnboardingCardTitle} from '../../cardDetail/cardDe
|
|||||||
import {getOnboardingTourCategory, getOnboardingTourStarted, getOnboardingTourStep} from '../../../store/users'
|
import {getOnboardingTourCategory, getOnboardingTourStarted, getOnboardingTourStep} from '../../../store/users'
|
||||||
import TourTip from '../../tutorial_tour_tip/tutorial_tour_tip'
|
import TourTip from '../../tutorial_tour_tip/tutorial_tour_tip'
|
||||||
import {TutorialTourTipPunchout} from '../../tutorial_tour_tip/tutorial_tour_tip_backdrop'
|
import {TutorialTourTipPunchout} from '../../tutorial_tour_tip/tutorial_tour_tip_backdrop'
|
||||||
|
import {ClientConfig} from '../../../config/clientConfig'
|
||||||
|
import {getClientConfig} from '../../../store/clientConfig'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
requireCard: boolean
|
requireCard: boolean
|
||||||
@ -30,12 +32,13 @@ type Props = {
|
|||||||
|
|
||||||
const TourTipRenderer = (props: Props): JSX.Element | null => {
|
const TourTipRenderer = (props: Props): JSX.Element | null => {
|
||||||
const board = useAppSelector(getCurrentBoard)
|
const board = useAppSelector(getCurrentBoard)
|
||||||
|
const clientConfig = useAppSelector<ClientConfig>(getClientConfig)
|
||||||
|
|
||||||
const isOnboardingBoard = board ? board.title === OnboardingBoardTitle : false
|
const isOnboardingBoard = board ? board.title === OnboardingBoardTitle : false
|
||||||
const onboardingTourStarted = useAppSelector(getOnboardingTourStarted)
|
const onboardingTourStarted = useAppSelector(getOnboardingTourStarted)
|
||||||
const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)
|
const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)
|
||||||
const onboardingTourStep = useAppSelector(getOnboardingTourStep)
|
const onboardingTourStep = useAppSelector(getOnboardingTourStep)
|
||||||
const showTour = isOnboardingBoard && onboardingTourStarted && onboardingTourCategory === props.category
|
const showTour = !clientConfig.featureFlags.disableTour && isOnboardingBoard && onboardingTourStarted && onboardingTourCategory === props.category
|
||||||
let showTourTip = showTour && onboardingTourStep === props.step.toString()
|
let showTourTip = showTour && onboardingTourStep === props.step.toString()
|
||||||
|
|
||||||
if (props.requireCard) {
|
if (props.requireCard) {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {render, screen} from '@testing-library/react'
|
import {render, screen} from '@testing-library/react'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
@ -7,7 +7,7 @@ import thunk from 'redux-thunk'
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {MemoryRouter} from 'react-router'
|
import {MemoryRouter} from 'react-router'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {IUser} from '../../user'
|
import {IUser} from '../../user'
|
||||||
import {ISharing} from '../../blocks/sharing'
|
import {ISharing} from '../../blocks/sharing'
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import {render} from '@testing-library/react'
|
import {render, act} from '@testing-library/react'
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import configureStore from 'redux-mock-store'
|
import configureStore from 'redux-mock-store'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapIntl} from '../../testUtils'
|
import {wrapIntl} from '../../testUtils'
|
||||||
|
|
||||||
@ -73,8 +73,12 @@ describe('components/sidebar/SidebarSettingsMenu', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const {container} = render(component)
|
const {container} = render(component)
|
||||||
userEvent.click(container.querySelector('.menu-entry') as Element)
|
act(() => {
|
||||||
userEvent.hover(container.querySelector('#theme') as Element)
|
userEvent.click(container.querySelector('.menu-entry') as Element)
|
||||||
|
})
|
||||||
|
act(() => {
|
||||||
|
userEvent.hover(container.querySelector('#theme') as Element)
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -86,8 +90,12 @@ describe('components/sidebar/SidebarSettingsMenu', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const {container} = render(component)
|
const {container} = render(component)
|
||||||
userEvent.click(container.querySelector('.menu-entry') as Element)
|
act(() => {
|
||||||
userEvent.hover(container.querySelector('#lang') as Element)
|
userEvent.click(container.querySelector('.menu-entry') as Element)
|
||||||
|
})
|
||||||
|
act(() => {
|
||||||
|
userEvent.hover(container.querySelector('#lang') as Element)
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -100,8 +108,12 @@ describe('components/sidebar/SidebarSettingsMenu', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const {container} = render(component)
|
const {container} = render(component)
|
||||||
userEvent.click(container.querySelector('.menu-entry') as Element)
|
act(() => {
|
||||||
userEvent.hover(container.querySelector('#import') as Element)
|
userEvent.click(container.querySelector('.menu-entry') as Element)
|
||||||
|
})
|
||||||
|
act(() => {
|
||||||
|
userEvent.hover(container.querySelector('#import') as Element)
|
||||||
|
})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
userEvent.click(container.querySelector('[aria-label="Asana"]') as Element)
|
userEvent.click(container.querySelector('[aria-label="Asana"]') as Element)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {render} from '@testing-library/react'
|
import {render} from '@testing-library/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapDNDIntl} from '../testUtils'
|
import {wrapDNDIntl} from '../testUtils'
|
||||||
import {Constants} from '../constants'
|
import {Constants} from '../constants'
|
||||||
|
@ -7,7 +7,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapIntl, mockStateStore} from '../../testUtils'
|
import {wrapIntl, mockStateStore} from '../../testUtils'
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import React from 'react'
|
|||||||
import {render, screen} from '@testing-library/react'
|
import {render, screen} from '@testing-library/react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
@ -8,7 +8,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {FilterClause} from '../../blocks/filterClause'
|
import {FilterClause} from '../../blocks/filterClause'
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {FilterClause} from '../../blocks/filterClause'
|
import {FilterClause} from '../../blocks/filterClause'
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapIntl, mockStateStore} from '../../testUtils'
|
import {wrapIntl, mockStateStore} from '../../testUtils'
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {TestBlockFactory} from '../../test/testBlockFactory'
|
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
|
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {MockStoreEnhanced} from 'redux-mock-store'
|
import {MockStoreEnhanced} from 'redux-mock-store'
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {BoardView} from '../../blocks/boardView'
|
import {BoardView} from '../../blocks/boardView'
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import {wrapIntl, mockStateStore} from '../../testUtils'
|
import {wrapIntl, mockStateStore} from '../../testUtils'
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event'
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import mutator from '../mutator'
|
import mutator from '../mutator'
|
||||||
import {Utils} from '../utils'
|
import {Utils} from '../utils'
|
||||||
|
@ -4,7 +4,7 @@ import {act, render, waitFor} from '@testing-library/react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
import {MemoryRouter} from 'react-router-dom'
|
import {MemoryRouter} from 'react-router-dom'
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
@ -449,15 +449,15 @@ describe('src/components/workspace', () => {
|
|||||||
<Workspace readonly={false}/>
|
<Workspace readonly={false}/>
|
||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
), {wrapper: MemoryRouter})
|
), {wrapper: MemoryRouter})
|
||||||
jest.runOnlyPendingTimers()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor(() => {
|
jest.runOnlyPendingTimers()
|
||||||
const elements = document.querySelectorAll('.AddViewTourStep')
|
|
||||||
expect(elements).toBeDefined()
|
await waitFor(() => expect(document.querySelectorAll('.AddViewTourStep')).toBeDefined(), {timeout: 5000})
|
||||||
expect(elements.length).toBe(2)
|
|
||||||
expect(elements[1]).toMatchSnapshot()
|
const elements = document.querySelectorAll('.AddViewTourStep')
|
||||||
})
|
expect(elements.length).toBe(2)
|
||||||
|
expect(elements[1]).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('show copy link tooltip', async () => {
|
test('show copy link tooltip', async () => {
|
||||||
|
@ -261,7 +261,11 @@ class OctoClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAllBlocks(boardID: string): Promise<Block[]> {
|
async getAllBlocks(boardID: string): Promise<Block[]> {
|
||||||
const path = `/api/v1/boards/${boardID}/blocks?all=true`
|
let path = `/api/v1/boards/${boardID}/blocks?all=true`
|
||||||
|
const readToken = Utils.getReadToken()
|
||||||
|
if (readToken) {
|
||||||
|
path += `&read_token=${readToken}`
|
||||||
|
}
|
||||||
return this.getBlocksWithPath(path)
|
return this.getBlocksWithPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,7 +614,11 @@ class OctoClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getBoard(boardID: string): Promise<Board | undefined> {
|
async getBoard(boardID: string): Promise<Board | undefined> {
|
||||||
const path = `/api/v1/boards/${boardID}`
|
let path = `/api/v1/boards/${boardID}`
|
||||||
|
const readToken = Utils.getReadToken()
|
||||||
|
if (readToken) {
|
||||||
|
path += `?read_token=${readToken}`
|
||||||
|
}
|
||||||
const response = await fetch(this.getBaseURL() + path, {
|
const response = await fetch(this.getBaseURL() + path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: this.headers(),
|
headers: this.headers(),
|
||||||
|
@ -80,13 +80,10 @@ const BoardPage = (props: Props): JSX.Element => {
|
|||||||
return initialLoad
|
return initialLoad
|
||||||
}, [props.readonly])
|
}, [props.readonly])
|
||||||
|
|
||||||
const loadOrJoinBoard = useCallback(async (userId: string, boardTeamId: string, boardId: string, viewId: string) => {
|
const loadOrJoinBoard = useCallback(async (userId: string, boardTeamId: string, boardId: string) => {
|
||||||
// set the active board if we're able to pick one
|
|
||||||
dispatch(setCurrentBoard(boardId))
|
|
||||||
|
|
||||||
// and fetch its data
|
// and fetch its data
|
||||||
const result: any = await dispatch(loadBoardData(boardId))
|
const result: any = await dispatch(loadBoardData(boardId))
|
||||||
if (result.payload.blocks.length === 0) {
|
if (result.payload.blocks.length === 0 && userId) {
|
||||||
const member = await octoClient.createBoardMember({userId, boardId})
|
const member = await octoClient.createBoardMember({userId, boardId})
|
||||||
if (!member) {
|
if (!member) {
|
||||||
UserSettings.setLastBoardID(boardTeamId, null)
|
UserSettings.setLastBoardID(boardTeamId, null)
|
||||||
@ -101,23 +98,28 @@ const BoardPage = (props: Props): JSX.Element => {
|
|||||||
teamId: boardTeamId,
|
teamId: boardTeamId,
|
||||||
boardId,
|
boardId,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// and set it as most recently viewed board
|
|
||||||
UserSettings.setLastBoardID(boardTeamId, boardId)
|
|
||||||
|
|
||||||
if (viewId && viewId !== '0') {
|
|
||||||
dispatch(setCurrentView(viewId))
|
|
||||||
UserSettings.setLastViewId(boardId, viewId)
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(loadAction(match.params.boardId))
|
dispatch(loadAction(match.params.boardId))
|
||||||
|
|
||||||
if (match.params.boardId && me) {
|
if (match.params.boardId) {
|
||||||
loadOrJoinBoard(me.id, teamId, match.params.boardId, match.params.viewId)
|
// set the active board
|
||||||
|
dispatch(setCurrentBoard(match.params.boardId))
|
||||||
|
|
||||||
|
// and set it as most recently viewed board
|
||||||
|
UserSettings.setLastBoardID(teamId, match.params.boardId)
|
||||||
|
|
||||||
|
if (match.params.viewId && match.params.viewId !== '0') {
|
||||||
|
dispatch(setCurrentView(match.params.viewId))
|
||||||
|
UserSettings.setLastViewId(match.params.boardId, match.params.viewId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.readonly && me) {
|
||||||
|
loadOrJoinBoard(me.id, teamId, match.params.boardId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [teamId, match.params.boardId, match.params.viewId])
|
}, [teamId, match.params.boardId, match.params.viewId, me?.id])
|
||||||
|
|
||||||
if (props.readonly) {
|
if (props.readonly) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -14,7 +14,7 @@ import userEvent from '@testing-library/user-event'
|
|||||||
|
|
||||||
import configureStore from 'redux-mock-store'
|
import configureStore from 'redux-mock-store'
|
||||||
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
import thunk from 'redux-thunk'
|
import thunk from 'redux-thunk'
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ import {getLoggedIn, getMe} from './store/users'
|
|||||||
import {useAppSelector} from './store/hooks'
|
import {useAppSelector} from './store/hooks'
|
||||||
import {UserSettingKey} from './userSettings'
|
import {UserSettingKey} from './userSettings'
|
||||||
import {IUser, UserPropPrefix} from './user'
|
import {IUser, UserPropPrefix} from './user'
|
||||||
|
import {getClientConfig} from './store/clientConfig'
|
||||||
|
import {ClientConfig} from './config/clientConfig'
|
||||||
|
|
||||||
type RouteProps = {
|
type RouteProps = {
|
||||||
path: string|string[]
|
path: string|string[]
|
||||||
@ -27,13 +29,21 @@ function FBRoute(props: RouteProps) {
|
|||||||
const loggedIn = useAppSelector<boolean|null>(getLoggedIn)
|
const loggedIn = useAppSelector<boolean|null>(getLoggedIn)
|
||||||
const match = useRouteMatch<any>()
|
const match = useRouteMatch<any>()
|
||||||
const me = useAppSelector<IUser|null>(getMe)
|
const me = useAppSelector<IUser|null>(getMe)
|
||||||
|
const clientConfig = useAppSelector<ClientConfig>(getClientConfig)
|
||||||
|
|
||||||
let originalPath
|
let originalPath
|
||||||
if (props.getOriginalPath) {
|
if (props.getOriginalPath) {
|
||||||
originalPath = props.getOriginalPath(match)
|
originalPath = props.getOriginalPath(match)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.isFocalboardPlugin() && (me?.id !== 'single-user') && props.path !== '/welcome' && loggedIn === true && !me?.props[UserPropPrefix + UserSettingKey.WelcomePageViewed]) {
|
const showWelcomePage = !clientConfig.featureFlags.disableTour &&
|
||||||
|
Utils.isFocalboardPlugin() &&
|
||||||
|
(me?.id !== 'single-user') &&
|
||||||
|
props.path !== '/welcome' &&
|
||||||
|
loggedIn === true &&
|
||||||
|
!me?.props[UserPropPrefix + UserSettingKey.WelcomePageViewed]
|
||||||
|
|
||||||
|
if (showWelcomePage) {
|
||||||
if (originalPath) {
|
if (originalPath) {
|
||||||
return <Redirect to={`/welcome?r=${originalPath}`}/>
|
return <Redirect to={`/welcome?r=${originalPath}`}/>
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
useRouteMatch,
|
useRouteMatch,
|
||||||
useHistory,
|
useHistory,
|
||||||
generatePath,
|
generatePath,
|
||||||
|
useLocation,
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
import {createBrowserHistory, History} from 'history'
|
import {createBrowserHistory, History} from 'history'
|
||||||
|
|
||||||
@ -70,16 +71,21 @@ function HomeToCurrentTeam(props: {path: string, exact: boolean}) {
|
|||||||
|
|
||||||
function WorkspaceToTeamRedirect() {
|
function WorkspaceToTeamRedirect() {
|
||||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, workspaceId?: string}>()
|
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, workspaceId?: string}>()
|
||||||
|
const queryParams = new URLSearchParams(useLocation().search)
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
octoClient.getBoard(match.params.boardId).then((board) => {
|
octoClient.getBoard(match.params.boardId).then((board) => {
|
||||||
if (board) {
|
if (board) {
|
||||||
history.replace(generatePath('/team/:teamId/:boardId?/:viewId?/:cardId?', {
|
let newPath = generatePath(match.path.replace('/workspace/:workspaceId', '/team/:teamId'), {
|
||||||
teamId: board?.teamId,
|
teamId: board?.teamId,
|
||||||
boardId: board?.id,
|
boardId: board?.id,
|
||||||
viewId: match.params.viewId,
|
viewId: match.params.viewId,
|
||||||
cardId: match.params.cardId,
|
cardId: match.params.cardId,
|
||||||
}))
|
})
|
||||||
|
if (queryParams) {
|
||||||
|
newPath += '?' + queryParams
|
||||||
|
}
|
||||||
|
history.replace(newPath)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
@ -159,9 +165,10 @@ const FocalboardRouter = (props: Props): JSX.Element => {
|
|||||||
<ChangePasswordPage/>
|
<ChangePasswordPage/>
|
||||||
</FBRoute>}
|
</FBRoute>}
|
||||||
|
|
||||||
<FBRoute path='/shared/:boardId?/:viewId?/:cardId?'>
|
<FBRoute path={['/team/:teamId/shared/:boardId?/:viewId?/:cardId?', '/shared/:boardId?/:viewId?/:cardId?']}>
|
||||||
<BoardPage readonly={true}/>
|
<BoardPage readonly={true}/>
|
||||||
</FBRoute>
|
</FBRoute>
|
||||||
|
|
||||||
<FBRoute
|
<FBRoute
|
||||||
loginRequired={true}
|
loginRequired={true}
|
||||||
path='/board/:boardId?/:viewId?/:cardId?'
|
path='/board/:boardId?/:viewId?/:cardId?'
|
||||||
@ -171,7 +178,7 @@ const FocalboardRouter = (props: Props): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<BoardPage/>
|
<BoardPage/>
|
||||||
</FBRoute>
|
</FBRoute>
|
||||||
<FBRoute path={['/workspace/:workspaceId/:boardId?/:viewId?/:cardId?', '/workspace/:workspaceId/shared/:boardId?/:viewId?/:cardId?']}>
|
<FBRoute path={['/workspace/:workspaceId/shared/:boardId?/:viewId?/:cardId?', '/workspace/:workspaceId/:boardId?/:viewId?/:cardId?']}>
|
||||||
<WorkspaceToTeamRedirect/>
|
<WorkspaceToTeamRedirect/>
|
||||||
</FBRoute>
|
</FBRoute>
|
||||||
<FBRoute
|
<FBRoute
|
||||||
|
@ -7,7 +7,7 @@ import {default as client} from '../octoClient'
|
|||||||
import {Board, BoardMember} from '../blocks/board'
|
import {Board, BoardMember} from '../blocks/board'
|
||||||
import {IUser} from '../user'
|
import {IUser} from '../user'
|
||||||
|
|
||||||
import {initialLoad, loadBoardData} from './initialLoad'
|
import {initialLoad, initialReadOnlyLoad, loadBoardData} from './initialLoad'
|
||||||
|
|
||||||
import {addBoardUsers} from './users'
|
import {addBoardUsers} from './users'
|
||||||
|
|
||||||
@ -147,6 +147,17 @@ const boardsSlice = createSlice({
|
|||||||
builder.addCase(loadBoardData.rejected, (state) => {
|
builder.addCase(loadBoardData.rejected, (state) => {
|
||||||
state.loadingBoard = false
|
state.loadingBoard = false
|
||||||
})
|
})
|
||||||
|
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
||||||
|
state.boards = {}
|
||||||
|
state.templates = {}
|
||||||
|
if (action.payload.board) {
|
||||||
|
if (action.payload.board.isTemplate) {
|
||||||
|
state.templates[action.payload.board.id] = action.payload.board
|
||||||
|
} else {
|
||||||
|
state.boards[action.payload.board.id] = action.payload.board
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
builder.addCase(initialLoad.fulfilled, (state, action) => {
|
builder.addCase(initialLoad.fulfilled, (state, action) => {
|
||||||
state.boards = {}
|
state.boards = {}
|
||||||
action.payload.boards.forEach((board) => {
|
action.payload.boards.forEach((board) => {
|
||||||
|
@ -59,7 +59,7 @@ const cardsSlice = createSlice({
|
|||||||
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
||||||
state.cards = {}
|
state.cards = {}
|
||||||
state.templates = {}
|
state.templates = {}
|
||||||
for (const block of action.payload) {
|
for (const block of action.payload.blocks) {
|
||||||
if (block.type === 'card' && block.fields.isTemplate) {
|
if (block.type === 'card' && block.fields.isTemplate) {
|
||||||
state.templates[block.id] = block as Card
|
state.templates[block.id] = block as Card
|
||||||
} else if (block.type === 'card' && !block.fields.isTemplate) {
|
} else if (block.type === 'card' && !block.fields.isTemplate) {
|
||||||
@ -109,7 +109,7 @@ export function getCard(cardId: string): (state: RootState) => Card|undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getCurrentBoardCards = createSelector(
|
export const getCurrentBoardCards = createSelector(
|
||||||
(state) => state.boards.current,
|
(state: RootState) => state.boards.current,
|
||||||
getCards,
|
getCards,
|
||||||
(boardId, cards) => {
|
(boardId, cards) => {
|
||||||
return Object.values(cards).filter((c) => c.boardId === boardId) as Card[]
|
return Object.values(cards).filter((c) => c.boardId === boardId) as Card[]
|
||||||
@ -117,7 +117,7 @@ export const getCurrentBoardCards = createSelector(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const getCurrentBoardTemplates = createSelector(
|
export const getCurrentBoardTemplates = createSelector(
|
||||||
(state) => state.boards.current,
|
(state: RootState) => state.boards.current,
|
||||||
getTemplates,
|
getTemplates,
|
||||||
(boardId, templates) => {
|
(boardId, templates) => {
|
||||||
return Object.values(templates).filter((c) => c.boardId === boardId) as Card[]
|
return Object.values(templates).filter((c) => c.boardId === boardId) as Card[]
|
||||||
|
@ -53,7 +53,7 @@ const commentsSlice = createSlice({
|
|||||||
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
||||||
state.comments = {}
|
state.comments = {}
|
||||||
state.commentsByCard = {}
|
state.commentsByCard = {}
|
||||||
for (const block of action.payload) {
|
for (const block of action.payload.blocks) {
|
||||||
if (block.type === 'comment') {
|
if (block.type === 'comment') {
|
||||||
state.comments[block.id] = block as CommentBlock
|
state.comments[block.id] = block as CommentBlock
|
||||||
state.commentsByCard[block.parentId] = state.commentsByCard[block.parentId] || []
|
state.commentsByCard[block.parentId] = state.commentsByCard[block.parentId] || []
|
||||||
|
@ -54,7 +54,7 @@ const contentsSlice = createSlice({
|
|||||||
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
||||||
state.contents = {}
|
state.contents = {}
|
||||||
state.contentsByCard = {}
|
state.contentsByCard = {}
|
||||||
for (const block of action.payload) {
|
for (const block of action.payload.blocks) {
|
||||||
if (block.type !== 'board' && block.type !== 'view' && block.type !== 'comment') {
|
if (block.type !== 'board' && block.type !== 'view' && block.type !== 'comment') {
|
||||||
state.contents[block.id] = block as ContentBlock
|
state.contents[block.id] = block as ContentBlock
|
||||||
state.contentsByCard[block.parentId] = state.contentsByCard[block.parentId] || []
|
state.contentsByCard[block.parentId] = state.contentsByCard[block.parentId] || []
|
||||||
@ -90,8 +90,8 @@ export const getContents = createSelector(
|
|||||||
|
|
||||||
export function getCardContents(cardId: string): (state: RootState) => Array<ContentBlock|ContentBlock[]> {
|
export function getCardContents(cardId: string): (state: RootState) => Array<ContentBlock|ContentBlock[]> {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => (state.contents?.contentsByCard && state.contents.contentsByCard[cardId]) || [],
|
(state: RootState) => (state.contents?.contentsByCard && state.contents.contentsByCard[cardId]) || [],
|
||||||
(state) => getCards(state)[cardId]?.fields?.contentOrder || getTemplates(state)[cardId]?.fields?.contentOrder,
|
(state: RootState) => getCards(state)[cardId]?.fields?.contentOrder || getTemplates(state)[cardId]?.fields?.contentOrder,
|
||||||
(contents, contentOrder): Array<ContentBlock|ContentBlock[]> => {
|
(contents, contentOrder): Array<ContentBlock|ContentBlock[]> => {
|
||||||
const result: Array<ContentBlock|ContentBlock[]> = []
|
const result: Array<ContentBlock|ContentBlock[]> = []
|
||||||
if (!contents) {
|
if (!contents) {
|
||||||
|
@ -37,8 +37,12 @@ export const initialLoad = createAsyncThunk(
|
|||||||
export const initialReadOnlyLoad = createAsyncThunk(
|
export const initialReadOnlyLoad = createAsyncThunk(
|
||||||
'initialReadOnlyLoad',
|
'initialReadOnlyLoad',
|
||||||
async (boardId: string) => {
|
async (boardId: string) => {
|
||||||
const blocks = client.getSubtree(boardId, 3)
|
const [board, blocks] = await Promise.all([
|
||||||
return blocks
|
client.getBoard(boardId),
|
||||||
|
client.getAllBlocks(boardId),
|
||||||
|
])
|
||||||
|
|
||||||
|
return {board, blocks}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ const viewsSlice = createSlice({
|
|||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {
|
||||||
state.views = {}
|
state.views = {}
|
||||||
for (const block of action.payload) {
|
for (const block of action.payload.blocks) {
|
||||||
if (block.type === 'view') {
|
if (block.type === 'view') {
|
||||||
state.views[block.id] = block as BoardView
|
state.views[block.id] = block as BoardView
|
||||||
}
|
}
|
||||||
@ -129,7 +129,7 @@ export function getView(viewId: string): (state: RootState) => BoardView|null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getCurrentBoardViews = createSelector(
|
export const getCurrentBoardViews = createSelector(
|
||||||
(state) => state.boards.current,
|
(state: RootState) => state.boards.current,
|
||||||
getViews,
|
getViews,
|
||||||
(boardId, views) => {
|
(boardId, views) => {
|
||||||
Utils.log(`getCurrentBoardViews boardId: ${boardId} views: ${views.length}`)
|
Utils.log(`getCurrentBoardViews boardId: ${boardId} views: ${views.length}`)
|
||||||
|
@ -68,19 +68,10 @@ function makeCommonConfig() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(eot|tiff|svg|woff2|woff|ttf|png|jpg|jpeg|gif)$/,
|
test: /\.(eot|tiff|svg|woff2|woff|ttf|png|jpg|jpeg|gif)$/,
|
||||||
use: [
|
type: 'asset/resource',
|
||||||
{
|
generator: {
|
||||||
loader: 'file-loader',
|
filename: 'static/[name].[ext]',
|
||||||
options: {
|
}
|
||||||
name: '[name].[ext]',
|
|
||||||
outputPath: 'static',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'image-webpack-loader',
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user