1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-27 08:31:20 +02:00

Person Property Type (#406)

* Added getWorkspaceUsers API

* Integrated workspace user API in UI

* Integrated workspace user API in UI

* Added toto for implementation slot

* Implemenmted getWorkspaceUSers to get data from Focalboard DB

* Updated store mocks

* Made select styles a shared constant

* Removed unwanted diffs

* Removed unwanted diffs

* Updated snapshots for new property type

* Added user store test

* Added missing copyright notice

* Returning error if no users found to retain original behavior

* Minor fixes and added tests

* Minor fixes

* Used React context for workspace users

* Used useContext hook, and added additional user ID -> user context to avoid that computation by all componnets

* Mergerd both workspace user contextx

* Minor review fix
This commit is contained in:
Harshil Sharma 2021-06-04 18:53:15 +05:30 committed by GitHub
parent b45c1e7fd0
commit 90f6389745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1684 additions and 553 deletions

View File

@ -71,6 +71,7 @@ func (a *API) RegisterRoutes(r *mux.Router) {
apiv1.HandleFunc("/workspaces/{workspaceID}", a.sessionRequired(a.handleGetWorkspace)).Methods("GET") apiv1.HandleFunc("/workspaces/{workspaceID}", a.sessionRequired(a.handleGetWorkspace)).Methods("GET")
apiv1.HandleFunc("/workspaces/{workspaceID}/regenerate_signup_token", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)).Methods("POST") apiv1.HandleFunc("/workspaces/{workspaceID}/regenerate_signup_token", a.sessionRequired(a.handlePostWorkspaceRegenerateSignupToken)).Methods("POST")
apiv1.HandleFunc("/workspaces/{workspaceID}/users", a.sessionRequired(a.getWorkspaceUsers)).Methods("GET")
// User APIs // User APIs
apiv1.HandleFunc("/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") apiv1.HandleFunc("/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET")
@ -1133,6 +1134,59 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
jsonBytesResponse(w, http.StatusOK, data) jsonBytesResponse(w, http.StatusOK, data)
} }
func (a *API) getWorkspaceUsers(w http.ResponseWriter, r *http.Request) {
// swagger:operation GET /api/v1/workspaces/{workspaceID}/users getWorkspaceUsers
//
// Returns workspace users
//
// ---
// produces:
// - application/json
// parameters:
// - name: workspaceID
// in: path
// description: Workspace ID
// required: true
// type: string
// security:
// - BearerAuth: []
// responses:
// '200':
// description: success
// schema:
// type: array
// items:
// "$ref": "#/definitions/User"
// default:
// description: internal error
// schema:
// "$ref": "#/definitions/ErrorResponse"
vars := mux.Vars(r)
workspaceID := vars["workspaceID"]
ctx := r.Context()
session := ctx.Value("session").(*model.Session)
if !a.app().DoesUserHaveWorkspaceAccess(session.UserID, workspaceID) {
a.errorResponse(w, http.StatusForbidden, "Access denied to workspace", errors.New("Access denied to workspace"))
return
}
users, err := a.app().GetWorkspaceUsers(workspaceID)
if err != nil {
a.errorResponse(w, http.StatusInternalServerError, "", err)
return
}
data, err := json.Marshal(users)
if err != nil {
a.errorResponse(w, http.StatusInternalServerError, "", err)
return
}
jsonBytesResponse(w, http.StatusOK, data)
}
// Response helpers // Response helpers
func (a *API) errorResponse(w http.ResponseWriter, code int, message string, sourceError error) { func (a *API) errorResponse(w http.ResponseWriter, code int, message string, sourceError error) {

7
server/app/user.go Normal file
View File

@ -0,0 +1,7 @@
package app
import "github.com/mattermost/focalboard/server/model"
func (a *App) GetWorkspaceUsers(workspaceID string) ([]*model.User, error) {
return a.store.GetUsersByWorkspace(workspaceID)
}

View File

@ -27,6 +27,7 @@ require (
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.7.3 // indirect github.com/tidwall/gjson v1.7.3 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
) )

View File

@ -1183,6 +1183,7 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1381,6 +1382,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

View File

@ -36,133 +36,133 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
} }
// CleanUpSessions mocks base method. // CleanUpSessions mocks base method.
func (m *MockStore) CleanUpSessions(arg0 int64) error { func (m *MockStore) CleanUpSessions(expireTime int64) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CleanUpSessions", arg0) ret := m.ctrl.Call(m, "CleanUpSessions", expireTime)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// CleanUpSessions indicates an expected call of CleanUpSessions. // CleanUpSessions indicates an expected call of CleanUpSessions.
func (mr *MockStoreMockRecorder) CleanUpSessions(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) CleanUpSessions(expireTime interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUpSessions", reflect.TypeOf((*MockStore)(nil).CleanUpSessions), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUpSessions", reflect.TypeOf((*MockStore)(nil).CleanUpSessions), expireTime)
} }
// CreateSession mocks base method. // CreateSession mocks base method.
func (m *MockStore) CreateSession(arg0 *model.Session) error { func (m *MockStore) CreateSession(session *model.Session) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSession", arg0) ret := m.ctrl.Call(m, "CreateSession", session)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// CreateSession indicates an expected call of CreateSession. // CreateSession indicates an expected call of CreateSession.
func (mr *MockStoreMockRecorder) CreateSession(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) CreateSession(session interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockStore)(nil).CreateSession), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockStore)(nil).CreateSession), session)
} }
// CreateUser mocks base method. // CreateUser mocks base method.
func (m *MockStore) CreateUser(arg0 *model.User) error { func (m *MockStore) CreateUser(user *model.User) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateUser", arg0) ret := m.ctrl.Call(m, "CreateUser", user)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// CreateUser indicates an expected call of CreateUser. // CreateUser indicates an expected call of CreateUser.
func (mr *MockStoreMockRecorder) CreateUser(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) CreateUser(user interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), user)
} }
// DeleteBlock mocks base method. // DeleteBlock mocks base method.
func (m *MockStore) DeleteBlock(arg0 store.Container, arg1, arg2 string) error { func (m *MockStore) DeleteBlock(c store.Container, blockID, modifiedBy string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteBlock", arg0, arg1, arg2) ret := m.ctrl.Call(m, "DeleteBlock", c, blockID, modifiedBy)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// DeleteBlock indicates an expected call of DeleteBlock. // DeleteBlock indicates an expected call of DeleteBlock.
func (mr *MockStoreMockRecorder) DeleteBlock(arg0, arg1, arg2 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) DeleteBlock(c, blockID, modifiedBy interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlock", reflect.TypeOf((*MockStore)(nil).DeleteBlock), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlock", reflect.TypeOf((*MockStore)(nil).DeleteBlock), c, blockID, modifiedBy)
} }
// DeleteSession mocks base method. // DeleteSession mocks base method.
func (m *MockStore) DeleteSession(arg0 string) error { func (m *MockStore) DeleteSession(sessionId string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteSession", arg0) ret := m.ctrl.Call(m, "DeleteSession", sessionId)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// DeleteSession indicates an expected call of DeleteSession. // DeleteSession indicates an expected call of DeleteSession.
func (mr *MockStoreMockRecorder) DeleteSession(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) DeleteSession(sessionId interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSession", reflect.TypeOf((*MockStore)(nil).DeleteSession), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSession", reflect.TypeOf((*MockStore)(nil).DeleteSession), sessionId)
} }
// GetActiveUserCount mocks base method. // GetActiveUserCount mocks base method.
func (m *MockStore) GetActiveUserCount(arg0 int64) (int, error) { func (m *MockStore) GetActiveUserCount(updatedSecondsAgo int64) (int, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetActiveUserCount", arg0) ret := m.ctrl.Call(m, "GetActiveUserCount", updatedSecondsAgo)
ret0, _ := ret[0].(int) ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetActiveUserCount indicates an expected call of GetActiveUserCount. // GetActiveUserCount indicates an expected call of GetActiveUserCount.
func (mr *MockStoreMockRecorder) GetActiveUserCount(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetActiveUserCount(updatedSecondsAgo interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveUserCount", reflect.TypeOf((*MockStore)(nil).GetActiveUserCount), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveUserCount", reflect.TypeOf((*MockStore)(nil).GetActiveUserCount), updatedSecondsAgo)
} }
// GetAllBlocks mocks base method. // GetAllBlocks mocks base method.
func (m *MockStore) GetAllBlocks(arg0 store.Container) ([]model.Block, error) { func (m *MockStore) GetAllBlocks(c store.Container) ([]model.Block, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAllBlocks", arg0) ret := m.ctrl.Call(m, "GetAllBlocks", c)
ret0, _ := ret[0].([]model.Block) ret0, _ := ret[0].([]model.Block)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetAllBlocks indicates an expected call of GetAllBlocks. // GetAllBlocks indicates an expected call of GetAllBlocks.
func (mr *MockStoreMockRecorder) GetAllBlocks(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetAllBlocks(c interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBlocks", reflect.TypeOf((*MockStore)(nil).GetAllBlocks), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBlocks", reflect.TypeOf((*MockStore)(nil).GetAllBlocks), c)
} }
// GetBlocksWithParent mocks base method. // GetBlocksWithParent mocks base method.
func (m *MockStore) GetBlocksWithParent(arg0 store.Container, arg1 string) ([]model.Block, error) { func (m *MockStore) GetBlocksWithParent(c store.Container, parentID string) ([]model.Block, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksWithParent", arg0, arg1) ret := m.ctrl.Call(m, "GetBlocksWithParent", c, parentID)
ret0, _ := ret[0].([]model.Block) ret0, _ := ret[0].([]model.Block)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetBlocksWithParent indicates an expected call of GetBlocksWithParent. // GetBlocksWithParent indicates an expected call of GetBlocksWithParent.
func (mr *MockStoreMockRecorder) GetBlocksWithParent(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetBlocksWithParent(c, parentID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithParent", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParent), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithParent", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParent), c, parentID)
} }
// GetBlocksWithParentAndType mocks base method. // GetBlocksWithParentAndType mocks base method.
func (m *MockStore) GetBlocksWithParentAndType(arg0 store.Container, arg1, arg2 string) ([]model.Block, error) { func (m *MockStore) GetBlocksWithParentAndType(c store.Container, parentID, blockType string) ([]model.Block, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksWithParentAndType", arg0, arg1, arg2) ret := m.ctrl.Call(m, "GetBlocksWithParentAndType", c, parentID, blockType)
ret0, _ := ret[0].([]model.Block) ret0, _ := ret[0].([]model.Block)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetBlocksWithParentAndType indicates an expected call of GetBlocksWithParentAndType. // GetBlocksWithParentAndType indicates an expected call of GetBlocksWithParentAndType.
func (mr *MockStoreMockRecorder) GetBlocksWithParentAndType(arg0, arg1, arg2 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetBlocksWithParentAndType(c, parentID, blockType interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithParentAndType", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParentAndType), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithParentAndType", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParentAndType), c, parentID, blockType)
} }
// GetBlocksWithRootID mocks base method. // GetBlocksWithRootID mocks base method.
@ -181,33 +181,33 @@ func (mr *MockStoreMockRecorder) GetBlocksWithRootID(arg0, arg1 interface{}) *go
} }
// GetBlocksWithType mocks base method. // GetBlocksWithType mocks base method.
func (m *MockStore) GetBlocksWithType(arg0 store.Container, arg1 string) ([]model.Block, error) { func (m *MockStore) GetBlocksWithType(c store.Container, blockType string) ([]model.Block, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksWithType", arg0, arg1) ret := m.ctrl.Call(m, "GetBlocksWithType", c, blockType)
ret0, _ := ret[0].([]model.Block) ret0, _ := ret[0].([]model.Block)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetBlocksWithType indicates an expected call of GetBlocksWithType. // GetBlocksWithType indicates an expected call of GetBlocksWithType.
func (mr *MockStoreMockRecorder) GetBlocksWithType(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetBlocksWithType(c, blockType interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithType", reflect.TypeOf((*MockStore)(nil).GetBlocksWithType), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithType", reflect.TypeOf((*MockStore)(nil).GetBlocksWithType), c, blockType)
} }
// GetParentID mocks base method. // GetParentID mocks base method.
func (m *MockStore) GetParentID(arg0 store.Container, arg1 string) (string, error) { func (m *MockStore) GetParentID(c store.Container, blockID string) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetParentID", arg0, arg1) ret := m.ctrl.Call(m, "GetParentID", c, blockID)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetParentID indicates an expected call of GetParentID. // GetParentID indicates an expected call of GetParentID.
func (mr *MockStoreMockRecorder) GetParentID(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetParentID(c, blockID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParentID", reflect.TypeOf((*MockStore)(nil).GetParentID), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParentID", reflect.TypeOf((*MockStore)(nil).GetParentID), c, blockID)
} }
// GetRegisteredUserCount mocks base method. // GetRegisteredUserCount mocks base method.
@ -226,78 +226,78 @@ func (mr *MockStoreMockRecorder) GetRegisteredUserCount() *gomock.Call {
} }
// GetRootID mocks base method. // GetRootID mocks base method.
func (m *MockStore) GetRootID(arg0 store.Container, arg1 string) (string, error) { func (m *MockStore) GetRootID(c store.Container, blockID string) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetRootID", arg0, arg1) ret := m.ctrl.Call(m, "GetRootID", c, blockID)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetRootID indicates an expected call of GetRootID. // GetRootID indicates an expected call of GetRootID.
func (mr *MockStoreMockRecorder) GetRootID(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetRootID(c, blockID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRootID", reflect.TypeOf((*MockStore)(nil).GetRootID), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRootID", reflect.TypeOf((*MockStore)(nil).GetRootID), c, blockID)
} }
// GetSession mocks base method. // GetSession mocks base method.
func (m *MockStore) GetSession(arg0 string, arg1 int64) (*model.Session, error) { func (m *MockStore) GetSession(token string, expireTime int64) (*model.Session, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSession", arg0, arg1) ret := m.ctrl.Call(m, "GetSession", token, expireTime)
ret0, _ := ret[0].(*model.Session) ret0, _ := ret[0].(*model.Session)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetSession indicates an expected call of GetSession. // GetSession indicates an expected call of GetSession.
func (mr *MockStoreMockRecorder) GetSession(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetSession(token, expireTime interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockStore)(nil).GetSession), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockStore)(nil).GetSession), token, expireTime)
} }
// GetSharing mocks base method. // GetSharing mocks base method.
func (m *MockStore) GetSharing(arg0 store.Container, arg1 string) (*model.Sharing, error) { func (m *MockStore) GetSharing(c store.Container, rootID string) (*model.Sharing, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSharing", arg0, arg1) ret := m.ctrl.Call(m, "GetSharing", c, rootID)
ret0, _ := ret[0].(*model.Sharing) ret0, _ := ret[0].(*model.Sharing)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetSharing indicates an expected call of GetSharing. // GetSharing indicates an expected call of GetSharing.
func (mr *MockStoreMockRecorder) GetSharing(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetSharing(c, rootID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSharing", reflect.TypeOf((*MockStore)(nil).GetSharing), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSharing", reflect.TypeOf((*MockStore)(nil).GetSharing), c, rootID)
} }
// GetSubTree2 mocks base method. // GetSubTree2 mocks base method.
func (m *MockStore) GetSubTree2(arg0 store.Container, arg1 string) ([]model.Block, error) { func (m *MockStore) GetSubTree2(c store.Container, blockID string) ([]model.Block, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSubTree2", arg0, arg1) ret := m.ctrl.Call(m, "GetSubTree2", c, blockID)
ret0, _ := ret[0].([]model.Block) ret0, _ := ret[0].([]model.Block)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetSubTree2 indicates an expected call of GetSubTree2. // GetSubTree2 indicates an expected call of GetSubTree2.
func (mr *MockStoreMockRecorder) GetSubTree2(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetSubTree2(c, blockID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubTree2", reflect.TypeOf((*MockStore)(nil).GetSubTree2), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubTree2", reflect.TypeOf((*MockStore)(nil).GetSubTree2), c, blockID)
} }
// GetSubTree3 mocks base method. // GetSubTree3 mocks base method.
func (m *MockStore) GetSubTree3(arg0 store.Container, arg1 string) ([]model.Block, error) { func (m *MockStore) GetSubTree3(c store.Container, blockID string) ([]model.Block, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSubTree3", arg0, arg1) ret := m.ctrl.Call(m, "GetSubTree3", c, blockID)
ret0, _ := ret[0].([]model.Block) ret0, _ := ret[0].([]model.Block)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetSubTree3 indicates an expected call of GetSubTree3. // GetSubTree3 indicates an expected call of GetSubTree3.
func (mr *MockStoreMockRecorder) GetSubTree3(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetSubTree3(c, blockID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubTree3", reflect.TypeOf((*MockStore)(nil).GetSubTree3), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubTree3", reflect.TypeOf((*MockStore)(nil).GetSubTree3), c, blockID)
} }
// GetSystemSettings mocks base method. // GetSystemSettings mocks base method.
@ -316,63 +316,78 @@ func (mr *MockStoreMockRecorder) GetSystemSettings() *gomock.Call {
} }
// GetUserByEmail mocks base method. // GetUserByEmail mocks base method.
func (m *MockStore) GetUserByEmail(arg0 string) (*model.User, error) { func (m *MockStore) GetUserByEmail(email string) (*model.User, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserByEmail", arg0) ret := m.ctrl.Call(m, "GetUserByEmail", email)
ret0, _ := ret[0].(*model.User) ret0, _ := ret[0].(*model.User)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetUserByEmail indicates an expected call of GetUserByEmail. // GetUserByEmail indicates an expected call of GetUserByEmail.
func (mr *MockStoreMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetUserByEmail(email interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockStore)(nil).GetUserByEmail), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockStore)(nil).GetUserByEmail), email)
} }
// GetUserById mocks base method. // GetUserById mocks base method.
func (m *MockStore) GetUserById(arg0 string) (*model.User, error) { func (m *MockStore) GetUserById(userID string) (*model.User, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserById", arg0) ret := m.ctrl.Call(m, "GetUserById", userID)
ret0, _ := ret[0].(*model.User) ret0, _ := ret[0].(*model.User)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetUserById indicates an expected call of GetUserById. // GetUserById indicates an expected call of GetUserById.
func (mr *MockStoreMockRecorder) GetUserById(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetUserById(userID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserById", reflect.TypeOf((*MockStore)(nil).GetUserById), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserById", reflect.TypeOf((*MockStore)(nil).GetUserById), userID)
} }
// GetUserByUsername mocks base method. // GetUserByUsername mocks base method.
func (m *MockStore) GetUserByUsername(arg0 string) (*model.User, error) { func (m *MockStore) GetUserByUsername(username string) (*model.User, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserByUsername", arg0) ret := m.ctrl.Call(m, "GetUserByUsername", username)
ret0, _ := ret[0].(*model.User) ret0, _ := ret[0].(*model.User)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetUserByUsername indicates an expected call of GetUserByUsername. // GetUserByUsername indicates an expected call of GetUserByUsername.
func (mr *MockStoreMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetUserByUsername(username interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockStore)(nil).GetUserByUsername), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockStore)(nil).GetUserByUsername), username)
}
// GetUsersByWorkspace mocks base method.
func (m *MockStore) GetUsersByWorkspace(workspaceID string) ([]*model.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUsersByWorkspace", workspaceID)
ret0, _ := ret[0].([]*model.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUsersByWorkspace indicates an expected call of GetUsersByWorkspace.
func (mr *MockStoreMockRecorder) GetUsersByWorkspace(workspaceID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsersByWorkspace", reflect.TypeOf((*MockStore)(nil).GetUsersByWorkspace), workspaceID)
} }
// GetWorkspace mocks base method. // GetWorkspace mocks base method.
func (m *MockStore) GetWorkspace(arg0 string) (*model.Workspace, error) { func (m *MockStore) GetWorkspace(ID string) (*model.Workspace, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetWorkspace", arg0) ret := m.ctrl.Call(m, "GetWorkspace", ID)
ret0, _ := ret[0].(*model.Workspace) ret0, _ := ret[0].(*model.Workspace)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetWorkspace indicates an expected call of GetWorkspace. // GetWorkspace indicates an expected call of GetWorkspace.
func (mr *MockStoreMockRecorder) GetWorkspace(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) GetWorkspace(ID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspace", reflect.TypeOf((*MockStore)(nil).GetWorkspace), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspace", reflect.TypeOf((*MockStore)(nil).GetWorkspace), ID)
} }
// HasWorkspaceAccess mocks base method. // HasWorkspaceAccess mocks base method.
@ -391,45 +406,45 @@ func (mr *MockStoreMockRecorder) HasWorkspaceAccess(arg0, arg1 interface{}) *gom
} }
// InsertBlock mocks base method. // InsertBlock mocks base method.
func (m *MockStore) InsertBlock(arg0 store.Container, arg1 model.Block) error { func (m *MockStore) InsertBlock(c store.Container, block model.Block) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertBlock", arg0, arg1) ret := m.ctrl.Call(m, "InsertBlock", c, block)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// InsertBlock indicates an expected call of InsertBlock. // InsertBlock indicates an expected call of InsertBlock.
func (mr *MockStoreMockRecorder) InsertBlock(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) InsertBlock(c, block interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBlock", reflect.TypeOf((*MockStore)(nil).InsertBlock), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBlock", reflect.TypeOf((*MockStore)(nil).InsertBlock), c, block)
} }
// RefreshSession mocks base method. // RefreshSession mocks base method.
func (m *MockStore) RefreshSession(arg0 *model.Session) error { func (m *MockStore) RefreshSession(session *model.Session) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshSession", arg0) ret := m.ctrl.Call(m, "RefreshSession", session)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// RefreshSession indicates an expected call of RefreshSession. // RefreshSession indicates an expected call of RefreshSession.
func (mr *MockStoreMockRecorder) RefreshSession(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) RefreshSession(session interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshSession", reflect.TypeOf((*MockStore)(nil).RefreshSession), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshSession", reflect.TypeOf((*MockStore)(nil).RefreshSession), session)
} }
// SetSystemSetting mocks base method. // SetSystemSetting mocks base method.
func (m *MockStore) SetSystemSetting(arg0, arg1 string) error { func (m *MockStore) SetSystemSetting(key, value string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetSystemSetting", arg0, arg1) ret := m.ctrl.Call(m, "SetSystemSetting", key, value)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetSystemSetting indicates an expected call of SetSystemSetting. // SetSystemSetting indicates an expected call of SetSystemSetting.
func (mr *MockStoreMockRecorder) SetSystemSetting(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) SetSystemSetting(key, value interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSystemSetting", reflect.TypeOf((*MockStore)(nil).SetSystemSetting), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSystemSetting", reflect.TypeOf((*MockStore)(nil).SetSystemSetting), key, value)
} }
// Shutdown mocks base method. // Shutdown mocks base method.
@ -447,99 +462,99 @@ func (mr *MockStoreMockRecorder) Shutdown() *gomock.Call {
} }
// UpdateSession mocks base method. // UpdateSession mocks base method.
func (m *MockStore) UpdateSession(arg0 *model.Session) error { func (m *MockStore) UpdateSession(session *model.Session) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateSession", arg0) ret := m.ctrl.Call(m, "UpdateSession", session)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpdateSession indicates an expected call of UpdateSession. // UpdateSession indicates an expected call of UpdateSession.
func (mr *MockStoreMockRecorder) UpdateSession(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) UpdateSession(session interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSession", reflect.TypeOf((*MockStore)(nil).UpdateSession), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSession", reflect.TypeOf((*MockStore)(nil).UpdateSession), session)
} }
// UpdateUser mocks base method. // UpdateUser mocks base method.
func (m *MockStore) UpdateUser(arg0 *model.User) error { func (m *MockStore) UpdateUser(user *model.User) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser", arg0) ret := m.ctrl.Call(m, "UpdateUser", user)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpdateUser indicates an expected call of UpdateUser. // UpdateUser indicates an expected call of UpdateUser.
func (mr *MockStoreMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) UpdateUser(user interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockStore)(nil).UpdateUser), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockStore)(nil).UpdateUser), user)
} }
// UpdateUserPassword mocks base method. // UpdateUserPassword mocks base method.
func (m *MockStore) UpdateUserPassword(arg0, arg1 string) error { func (m *MockStore) UpdateUserPassword(username, password string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUserPassword", arg0, arg1) ret := m.ctrl.Call(m, "UpdateUserPassword", username, password)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpdateUserPassword indicates an expected call of UpdateUserPassword. // UpdateUserPassword indicates an expected call of UpdateUserPassword.
func (mr *MockStoreMockRecorder) UpdateUserPassword(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) UpdateUserPassword(username, password interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserPassword", reflect.TypeOf((*MockStore)(nil).UpdateUserPassword), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserPassword", reflect.TypeOf((*MockStore)(nil).UpdateUserPassword), username, password)
} }
// UpdateUserPasswordByID mocks base method. // UpdateUserPasswordByID mocks base method.
func (m *MockStore) UpdateUserPasswordByID(arg0, arg1 string) error { func (m *MockStore) UpdateUserPasswordByID(userID, password string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUserPasswordByID", arg0, arg1) ret := m.ctrl.Call(m, "UpdateUserPasswordByID", userID, password)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpdateUserPasswordByID indicates an expected call of UpdateUserPasswordByID. // UpdateUserPasswordByID indicates an expected call of UpdateUserPasswordByID.
func (mr *MockStoreMockRecorder) UpdateUserPasswordByID(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) UpdateUserPasswordByID(userID, password interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserPasswordByID", reflect.TypeOf((*MockStore)(nil).UpdateUserPasswordByID), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserPasswordByID", reflect.TypeOf((*MockStore)(nil).UpdateUserPasswordByID), userID, password)
} }
// UpsertSharing mocks base method. // UpsertSharing mocks base method.
func (m *MockStore) UpsertSharing(arg0 store.Container, arg1 model.Sharing) error { func (m *MockStore) UpsertSharing(c store.Container, sharing model.Sharing) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertSharing", arg0, arg1) ret := m.ctrl.Call(m, "UpsertSharing", c, sharing)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpsertSharing indicates an expected call of UpsertSharing. // UpsertSharing indicates an expected call of UpsertSharing.
func (mr *MockStoreMockRecorder) UpsertSharing(arg0, arg1 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) UpsertSharing(c, sharing interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertSharing", reflect.TypeOf((*MockStore)(nil).UpsertSharing), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertSharing", reflect.TypeOf((*MockStore)(nil).UpsertSharing), c, sharing)
} }
// UpsertWorkspaceSettings mocks base method. // UpsertWorkspaceSettings mocks base method.
func (m *MockStore) UpsertWorkspaceSettings(arg0 model.Workspace) error { func (m *MockStore) UpsertWorkspaceSettings(workspace model.Workspace) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertWorkspaceSettings", arg0) ret := m.ctrl.Call(m, "UpsertWorkspaceSettings", workspace)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpsertWorkspaceSettings indicates an expected call of UpsertWorkspaceSettings. // UpsertWorkspaceSettings indicates an expected call of UpsertWorkspaceSettings.
func (mr *MockStoreMockRecorder) UpsertWorkspaceSettings(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) UpsertWorkspaceSettings(workspace interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertWorkspaceSettings", reflect.TypeOf((*MockStore)(nil).UpsertWorkspaceSettings), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertWorkspaceSettings", reflect.TypeOf((*MockStore)(nil).UpsertWorkspaceSettings), workspace)
} }
// UpsertWorkspaceSignupToken mocks base method. // UpsertWorkspaceSignupToken mocks base method.
func (m *MockStore) UpsertWorkspaceSignupToken(arg0 model.Workspace) error { func (m *MockStore) UpsertWorkspaceSignupToken(workspace model.Workspace) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertWorkspaceSignupToken", arg0) ret := m.ctrl.Call(m, "UpsertWorkspaceSignupToken", workspace)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpsertWorkspaceSignupToken indicates an expected call of UpsertWorkspaceSignupToken. // UpsertWorkspaceSignupToken indicates an expected call of UpsertWorkspaceSignupToken.
func (mr *MockStoreMockRecorder) UpsertWorkspaceSignupToken(arg0 interface{}) *gomock.Call { func (mr *MockStoreMockRecorder) UpsertWorkspaceSignupToken(workspace interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertWorkspaceSignupToken", reflect.TypeOf((*MockStore)(nil).UpsertWorkspaceSignupToken), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertWorkspaceSignupToken", reflect.TypeOf((*MockStore)(nil).UpsertWorkspaceSignupToken), workspace)
} }

View File

@ -39,4 +39,5 @@ func TestBlocksStore(t *testing.T) {
t.Run("BlocksStore", func(t *testing.T) { storetests.StoreTestBlocksStore(t, SetupTests) }) t.Run("BlocksStore", func(t *testing.T) { storetests.StoreTestBlocksStore(t, SetupTests) })
t.Run("SharingStore", func(t *testing.T) { storetests.StoreTestSharingStore(t, SetupTests) }) t.Run("SharingStore", func(t *testing.T) { storetests.StoreTestSharingStore(t, SetupTests) })
t.Run("SystemStore", func(t *testing.T) { storetests.StoreTestSystemStore(t, SetupTests) }) t.Run("SystemStore", func(t *testing.T) { storetests.StoreTestSystemStore(t, SetupTests) })
t.Run("UserStore", func(t *testing.T) { storetests.StoreTestUserStore(t, SetupTests) })
} }

View File

@ -1,8 +1,10 @@
package sqlstore package sqlstore
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"log"
"time" "time"
"github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/model"
@ -27,26 +29,52 @@ func (s *SQLStore) GetRegisteredUserCount() (int, error) {
} }
func (s *SQLStore) getUserByCondition(condition sq.Eq) (*model.User, error) { func (s *SQLStore) getUserByCondition(condition sq.Eq) (*model.User, error) {
users, err := s.getUsersByCondition(condition)
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, nil
}
return users[0], nil
}
func (s *SQLStore) getUsersByCondition(condition sq.Eq) ([]*model.User, error) {
query := s.getQueryBuilder(). query := s.getQueryBuilder().
Select("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "props", "create_at", "update_at", "delete_at"). Select(
"id",
"username",
"email",
"password",
"mfa_secret",
"auth_service",
"auth_data",
"props",
"create_at",
"update_at",
"delete_at",
).
From(s.tablePrefix + "users"). From(s.tablePrefix + "users").
Where(sq.Eq{"delete_at": 0}). Where(sq.Eq{"delete_at": 0}).
Where(condition) Where(condition)
row := query.QueryRow() rows, err := query.Query()
user := model.User{} if err != nil {
log.Printf("getUsersByCondition ERROR: %v", err)
return nil, err
}
var propsBytes []byte users, err := s.usersFromRows(rows)
err := row.Scan(&user.ID, &user.Username, &user.Email, &user.Password, &user.MfaSecret, &user.AuthService, &user.AuthData, &propsBytes, &user.CreateAt, &user.UpdateAt, &user.DeleteAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = json.Unmarshal(propsBytes, &user.Props) if len(users) == 0 {
if err != nil { return nil, sql.ErrNoRows
return nil, err
} }
return &user, nil return users, nil
} }
func (s *SQLStore) GetUserById(userID string) (*model.User, error) { func (s *SQLStore) GetUserById(userID string) (*model.User, error) {
@ -158,3 +186,44 @@ func (s *SQLStore) UpdateUserPasswordByID(userID, password string) error {
return nil return nil
} }
func (s *SQLStore) GetUsersByWorkspace(workspaceID string) ([]*model.User, error) {
return s.getUsersByCondition(nil)
}
func (s *SQLStore) usersFromRows(rows *sql.Rows) ([]*model.User, error) {
defer rows.Close()
users := []*model.User{}
for rows.Next() {
var user model.User
var propsBytes []byte
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Password,
&user.MfaSecret,
&user.AuthService,
&user.AuthData,
&propsBytes,
&user.CreateAt,
&user.UpdateAt,
&user.DeleteAt,
)
if err != nil {
return nil, err
}
err = json.Unmarshal(propsBytes, &user.Props)
if err != nil {
return nil, err
}
users = append(users, &user)
}
return users, nil
}

View File

@ -36,6 +36,7 @@ type Store interface {
UpdateUser(user *model.User) error UpdateUser(user *model.User) error
UpdateUserPassword(username, password string) error UpdateUserPassword(username, password string) error
UpdateUserPasswordByID(userID, password string) error UpdateUserPasswordByID(userID, password string) error
GetUsersByWorkspace(workspaceID string) ([]*model.User, error)
GetActiveUserCount(updatedSecondsAgo int64) (int, error) GetActiveUserCount(updatedSecondsAgo int64) (int, error)
GetSession(token string, expireTime int64) (*model.Session, error) GetSession(token string, expireTime int64) (*model.Session, error)

View File

@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetests
import (
"database/sql"
"time"
"github.com/google/uuid"
"github.com/mattermost/focalboard/server/model"
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/focalboard/server/services/store"
)
func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
t.Run("SetGetSystemSettings", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testGetWorkspaceUsers(t, store)
})
}
func testGetWorkspaceUsers(t *testing.T, store store.Store) {
t.Run("GetWorkspaceUSers", func(t *testing.T) {
users, err := store.GetUsersByWorkspace("workspace_1")
require.Equal(t, 0, len(users))
require.Equal(t, sql.ErrNoRows, err)
userID := uuid.New().String()
err = store.CreateUser(&model.User{
ID: userID,
Username: "darth.vader",
})
require.Nil(t, err)
defer store.UpdateUser(&model.User{
ID: userID,
DeleteAt: time.Now().Unix(),
})
users, err = store.GetUsersByWorkspace("workspace_1")
require.Equal(t, 1, len(users))
require.Equal(t, "darth.vader", users[0].Username)
require.Nil(t, err)
})
}

View File

@ -1 +1 @@
5.0.1 5.1.1

File diff suppressed because one or more lines are too long

View File

@ -266,6 +266,10 @@ definitions:
description: Token required to register new users description: Token required to register new users
type: string type: string
x-go-name: SignupToken x-go-name: SignupToken
title:
description: Title of the workspace
type: string
x-go-name: Title
updateAt: updateAt:
description: Updated time description: Updated time
format: int64 format: int64
@ -724,6 +728,31 @@ paths:
$ref: '#/definitions/ErrorResponse' $ref: '#/definitions/ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
/api/v1/workspaces/{workspaceID}/users:
get:
description: Returns workspace users
operationId: getWorkspaceUsers
parameters:
- description: Workspace ID
in: path
name: workspaceID
required: true
type: string
produces:
- application/json
responses:
"200":
description: success
schema:
items:
$ref: '#/definitions/User'
type: array
default:
description: internal error
schema:
$ref: '#/definitions/ErrorResponse'
security:
- BearerAuth: []
/workspaces/{workspaceID}/{rootID}/{fileID}: /workspaces/{workspaceID}/{rootID}/{fileID}:
get: get:
description: Returns the contents of an uploaded file description: Returns the contents of an uploaded file
@ -764,7 +793,8 @@ schemes:
- https - https
securityDefinitions: securityDefinitions:
BearerAuth: BearerAuth:
description: 'Pass session token using Bearer authentication, e.g. set header "Authorization: Bearer <session token>"' description: 'Pass session token using Bearer authentication, e.g. set header
"Authorization: Bearer <session token>"'
in: header in: header
name: Authorization name: Authorization
type: apiKey type: apiKey

View File

@ -36,6 +36,7 @@
"@formatjs/ts-transformer": "^3.2.1", "@formatjs/ts-transformer": "^3.2.1",
"@testing-library/jest-dom": "^5.11.10", "@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.9",
"@types/emoji-mart": "^3.0.4", "@types/emoji-mart": "^3.0.4",
"@types/jest": "^26.0.21", "@types/jest": "^26.0.21",
"@types/marked": "^2.0.0", "@types/marked": "^2.0.0",
@ -653,9 +654,9 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.11.2", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
@ -1540,15 +1541,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@testing-library/dom/node_modules/@babel/runtime": {
"version": "7.13.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.13.4"
}
},
"node_modules/@testing-library/jest-dom": { "node_modules/@testing-library/jest-dom": {
"version": "5.11.10", "version": "5.11.10",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.10.tgz", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.10.tgz",
@ -1596,13 +1588,20 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@testing-library/react/node_modules/@babel/runtime": { "node_modules/@testing-library/user-event": {
"version": "7.13.10", "version": "13.1.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.9.tgz",
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", "integrity": "sha512-NZr0zL2TMOs2qk+dNlqrAdbaRW5dAmYwd1yuQ4r7HpkVEOj0MWuUjDWwKhcLd/atdBy8ZSMHSKp+kXSQe47ezg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.4" "@babel/runtime": "^7.12.5"
},
"engines": {
"node": ">=10",
"npm": ">=6"
},
"peerDependencies": {
"@testing-library/dom": ">=7.21.4"
} }
}, },
"node_modules/@types/aria-query": { "node_modules/@types/aria-query": {
@ -12879,14 +12878,6 @@
"react-transition-group": "^4.3.0" "react-transition-group": "^4.3.0"
} }
}, },
"node_modules/react-select/node_modules/@babel/runtime": {
"version": "7.13.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
}
},
"node_modules/react-simplemde-editor": { "node_modules/react-simplemde-editor": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/react-simplemde-editor/-/react-simplemde-editor-4.1.3.tgz", "resolved": "https://registry.npmjs.org/react-simplemde-editor/-/react-simplemde-editor-4.1.3.tgz",
@ -16879,9 +16870,9 @@
} }
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.11.2", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
@ -17685,17 +17676,6 @@
"dom-accessibility-api": "^0.5.4", "dom-accessibility-api": "^0.5.4",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"pretty-format": "^26.6.2" "pretty-format": "^26.6.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
} }
}, },
"@testing-library/jest-dom": { "@testing-library/jest-dom": {
@ -17734,17 +17714,15 @@
"requires": { "requires": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@testing-library/dom": "^7.28.1" "@testing-library/dom": "^7.28.1"
}, }
"dependencies": { },
"@babel/runtime": { "@testing-library/user-event": {
"version": "7.13.10", "version": "13.1.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.9.tgz",
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", "integrity": "sha512-NZr0zL2TMOs2qk+dNlqrAdbaRW5dAmYwd1yuQ4r7HpkVEOj0MWuUjDWwKhcLd/atdBy8ZSMHSKp+kXSQe47ezg==",
"dev": true, "dev": true,
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "@babel/runtime": "^7.12.5"
}
}
} }
}, },
"@types/aria-query": { "@types/aria-query": {
@ -27014,16 +26992,6 @@
"prop-types": "^15.6.0", "prop-types": "^15.6.0",
"react-input-autosize": "^3.0.0", "react-input-autosize": "^3.0.0",
"react-transition-group": "^4.3.0" "react-transition-group": "^4.3.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
} }
}, },
"react-simplemde-editor": { "react-simplemde-editor": {

View File

@ -20,7 +20,8 @@
"cypress:run:firefox": "cypress run --browser firefox", "cypress:run:firefox": "cypress run --browser firefox",
"cypress:run:edge": "cypress run --browser edge", "cypress:run:edge": "cypress run --browser edge",
"cypress:run:electron": "cypress run --browser electron", "cypress:run:electron": "cypress run --browser electron",
"cypress:open": "cypress open" "cypress:open": "cypress open",
"updatesnapshots": "jest --updateSnapshot"
}, },
"dependencies": { "dependencies": {
"cypress": "^6.8.0", "cypress": "^6.8.0",
@ -70,6 +71,7 @@
"@formatjs/ts-transformer": "^3.2.1", "@formatjs/ts-transformer": "^3.2.1",
"@testing-library/jest-dom": "^5.11.10", "@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.9",
"@types/emoji-mart": "^3.0.4", "@types/emoji-mart": "^3.0.4",
"@types/jest": "^26.0.21", "@types/jest": "^26.0.21",
"@types/marked": "^2.0.0", "@types/marked": "^2.0.0",

View File

@ -27,7 +27,6 @@ const Comment: FC<Props> = (props: Props) => {
const html = Utils.htmlFromMarkdown(comment.title) const html = Utils.htmlFromMarkdown(comment.title)
const [username, setUsername] = useState('') const [username, setUsername] = useState('')
useEffect(() => { useEffect(() => {
UserCache.shared.getUser(userId).then((user) => { UserCache.shared.getUser(userId).then((user) => {
if (user) { if (user) {

View File

@ -0,0 +1,185 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/properties/user not readonly 1`] = `
<div>
<div
class="UserProperty css-2b097c-container"
>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class=" css-pwgt1w-Control"
>
<div
class=" css-kpfmlq-ValueContainer"
>
<div
class=" css-1wa3eu0-placeholder"
>
Select...
</div>
<div
class="css-1shkodo-Input"
>
<div
class=""
style="display: inline-block;"
>
<input
aria-autocomplete="list"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
</div>
</div>
<div
class=" css-1hb7zxy-IndicatorsContainer"
>
<span
class=" css-43ykx9-indicatorSeparator"
/>
<div
aria-hidden="true"
class=" css-19sxey8-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
`;
exports[`components/properties/user readonly view 1`] = `
<div>
<div
class="UserProperty octo-propertyvalue"
>
user-id-1
</div>
</div>
`;
exports[`components/properties/user user dropdown open 1`] = `
<div>
<div
class="UserProperty css-2b097c-container"
>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
>
<span
id="aria-selection"
/>
<span
id="aria-context"
>
Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
</span>
</span>
<div
class=" css-pwgt1w-Control"
>
<div
class=" css-kpfmlq-ValueContainer"
>
<div
class=" css-1wa3eu0-placeholder"
>
Select...
</div>
<div
class="css-1shkodo-Input"
>
<div
class=""
style="display: inline-block;"
>
<input
aria-autocomplete="list"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-3-input"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
</div>
</div>
<div
class=" css-1hb7zxy-IndicatorsContainer"
>
<span
class=" css-43ykx9-indicatorSeparator"
/>
<div
aria-hidden="true"
class=" css-hl9mox-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
<div
class=" css-7p3rb2-menu"
>
<div
class=" css-1m3ubgr-MenuList"
>
<div
class=" css-gg45go-NoOptionsMessage"
>
No options
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,4 @@
.UserProperty {
margin-right: 20px;
min-width: 120px;
}

View File

@ -0,0 +1,116 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {IntlProvider} from 'react-intl'
import {render, wait} from '@testing-library/react'
import {act} from 'react-dom/test-utils'
import userEvent from '@testing-library/user-event'
import UserProperty from './user'
const wrapIntl = (children: any) => <IntlProvider locale='en'>{children}</IntlProvider>
const fetchMock = require('fetch-mock-jest')
describe('components/properties/user', () => {
beforeAll(() => {
fetchMock.get('http://localhost/api/v1/workspaces/0/users', JSON.stringify([
{
id: 'user-id-1',
username: 'username-1',
email: 'user-1@example.com',
props: {},
create_at: 1621315184,
update_at: 1621315184,
delete_at: 0,
},
{
id: 'user-id-2',
username: 'username-2',
email: 'user-2@example.com',
props: {},
create_at: 1621315184,
update_at: 1621315184,
delete_at: 0,
},
]),
)
})
afterAll(() => {
fetchMock.mockClear()
})
test('not readonly', async () => {
const component = wrapIntl(
<UserProperty
value={'user-id-1'}
readonly={false}
onChange={() => {
}}
/>,
)
let container
act(() => {
const renderResult = render(component)
container = renderResult.container
})
await wait()
expect(container).toMatchSnapshot()
})
test('readonly view', async () => {
const component = wrapIntl(
<UserProperty
value={'user-id-1'}
readonly={true}
onChange={() => {
}}
/>,
)
let container
act(() => {
const renderResult = render(component)
container = renderResult.container
})
await wait()
expect(container).toMatchSnapshot()
})
test('user dropdown open', async () => {
const component = wrapIntl(
<UserProperty
value={'user-id-1'}
readonly={false}
onChange={() => {
}}
/>,
)
let container: Element | DocumentFragment = {} as Element
act(() => {
const renderResult = render(component)
container = renderResult.container
})
await wait()
if (container) {
// this is the actual element where the click event triggers
// opening of the dropdown
const userProperty = container.querySelector('.UserProperty > div > div:nth-child(1) > div:nth-child(2) > div > input')
expect(userProperty).not.toBeNull()
act(() => {
userEvent.click(userProperty as Element)
})
expect(container).toMatchSnapshot()
} else {
throw new Error('container should have been initialized')
}
})
})

View File

@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useContext} from 'react'
import Select from 'react-select'
import {IUser, WorkspaceUsersContext, WorkspaceUsersContextData} from '../../../user'
import './user.scss'
import {getSelectBaseStyle} from '../../../theme'
type Props = {
value: string,
readonly: boolean,
onChange: (value: string) => void,
}
const UserProperty = (props: Props): JSX.Element => {
const workspaceUsers = useContext<WorkspaceUsersContextData>(WorkspaceUsersContext)
if (props.readonly) {
return (<div className='UserProperty octo-propertyvalue'>{workspaceUsers.usersById.get(props.value)?.username || props.value}</div>)
}
return (
<Select
options={workspaceUsers.users}
isSearchable={true}
isClearable={true}
backspaceRemovesValue={true}
className={'UserProperty'}
styles={getSelectBaseStyle()}
getOptionLabel={(o: IUser) => o.username}
getOptionValue={(a: IUser) => a.id}
value={workspaceUsers.usersById.get(props.value) || null}
onChange={(item, action) => {
if (action.action === 'select-option') {
props.onChange(item?.id || '')
} else if (action.action === 'clear') {
props.onChange('')
}
}}
/>
)
}
export default UserProperty

View File

@ -18,6 +18,7 @@ import Label from '../widgets/label'
import EditableDayPicker from '../widgets/editableDayPicker' import EditableDayPicker from '../widgets/editableDayPicker'
import Switch from '../widgets/switch' import Switch from '../widgets/switch'
import UserProperty from './properties/user/user'
import MultiSelectProperty from './properties/multiSelect' import MultiSelectProperty from './properties/multiSelect'
import URLProperty from './properties/link/link' import URLProperty from './properties/link/link'
@ -133,19 +134,15 @@ const PropertyValueElement = (props:Props): JSX.Element => {
} }
/> />
) )
} else if (propertyTemplate.type === 'url') { } else if (propertyTemplate.type === 'person') {
return ( return (
<URLProperty <UserProperty
value={value as string} value={propertyValue as string}
onChange={setValue} readonly={readOnly}
onSave={() => mutator.changePropertyValue(card, propertyTemplate.id, value)} onChange={(newValue) => mutator.changePropertyValue(card, propertyTemplate.id, newValue)}
onCancel={() => setValue(propertyValue)}
validator={(newValue) => validateProp(propertyTemplate.type, newValue)}
/> />
) )
} } else if (propertyTemplate.type === 'date') {
if (propertyTemplate.type === 'date') {
if (readOnly) { if (readOnly) {
return <div className='octo-propertyvalue'>{displayValue}</div> return <div className='octo-propertyvalue'>{displayValue}</div>
} }
@ -156,6 +153,16 @@ const PropertyValueElement = (props:Props): JSX.Element => {
onChange={(newValue) => mutator.changePropertyValue(card, propertyTemplate.id, newValue)} onChange={(newValue) => mutator.changePropertyValue(card, propertyTemplate.id, newValue)}
/> />
) )
} else if (propertyTemplate.type === 'url') {
return (
<URLProperty
value={value as string}
onChange={setValue}
onSave={() => mutator.changePropertyValue(card, propertyTemplate.id, value)}
onCancel={() => setValue(propertyValue)}
validator={(newValue) => validateProp(propertyTemplate.type, newValue)}
/>
)
} }
if (propertyTemplate.type === 'checkbox') { if (propertyTemplate.type === 'checkbox') {

View File

@ -359,6 +359,15 @@ class OctoClient {
const blob = await response.blob() const blob = await response.blob()
return URL.createObjectURL(blob) return URL.createObjectURL(blob)
} }
async getWorkspaceUsers(): Promise<Array<IUser>> {
const path = this.workspacePath() + '/users'
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return []
}
return (await this.getJson(response, [])) as IUser[]
}
} }
const octoClient = new OctoClient() const octoClient = new OctoClient()

View File

@ -16,6 +16,7 @@ import {Utils} from '../utils'
import {BoardTree, MutableBoardTree} from '../viewModel/boardTree' import {BoardTree, MutableBoardTree} from '../viewModel/boardTree'
import {MutableWorkspaceTree, WorkspaceTree} from '../viewModel/workspaceTree' import {MutableWorkspaceTree, WorkspaceTree} from '../viewModel/workspaceTree'
import './boardPage.scss' import './boardPage.scss'
import {IUser, WorkspaceUsersContext, WorkspaceUsersContextData} from '../user'
type Props = RouteComponentProps<{workspaceId?: string}> & { type Props = RouteComponentProps<{workspaceId?: string}> & {
readonly?: boolean readonly?: boolean
@ -29,6 +30,7 @@ type State = {
workspaceTree: WorkspaceTree workspaceTree: WorkspaceTree
boardTree?: BoardTree boardTree?: BoardTree
syncFailed?: boolean syncFailed?: boolean
workspaceUsers: WorkspaceUsersContextData
} }
class BoardPage extends React.Component<Props, State> { class BoardPage extends React.Component<Props, State> {
@ -57,8 +59,13 @@ class BoardPage extends React.Component<Props, State> {
boardId, boardId,
viewId, viewId,
workspaceTree: new MutableWorkspaceTree(), workspaceTree: new MutableWorkspaceTree(),
workspaceUsers: {
users: new Array<IUser>(),
usersById: new Map<string, IUser>(),
},
} }
this.setWorkspaceUsers()
Utils.log(`BoardPage. boardId: ${boardId}`) Utils.log(`BoardPage. boardId: ${boardId}`)
} }
@ -88,6 +95,28 @@ class BoardPage extends React.Component<Props, State> {
document.title = 'OCTO' document.title = 'OCTO'
} }
} }
if (this.state.workspace?.id !== prevState.workspace?.id) {
this.setWorkspaceUsers()
}
}
async setWorkspaceUsers() {
const workspaceUsers = await octoClient.getWorkspaceUsers()
// storing workspaceUsersById in state to avoid re-computation in each render cycle
this.setState({
workspaceUsers: {
users: workspaceUsers,
usersById: this.getIdToWorkspaceUsers(workspaceUsers),
},
})
}
getIdToWorkspaceUsers(users: Array<IUser>): Map<string, IUser> {
return users.reduce((acc: Map<string, IUser>, user: IUser) => {
acc.set(user.id, user)
return acc
}, new Map())
} }
private undoRedoHandler = async (keyName: string, e: KeyboardEvent) => { private undoRedoHandler = async (keyName: string, e: KeyboardEvent) => {
@ -158,27 +187,29 @@ class BoardPage extends React.Component<Props, State> {
} }
return ( return (
<div className='BoardPage'> <WorkspaceUsersContext.Provider value={this.state.workspaceUsers}>
<HotKeys <div className='BoardPage'>
keyName='shift+ctrl+z,shift+cmd+z,ctrl+z,cmd+z' <HotKeys
onKeyDown={this.undoRedoHandler} keyName='shift+ctrl+z,shift+cmd+z,ctrl+z,cmd+z'
/> onKeyDown={this.undoRedoHandler}
<Workspace />
workspace={workspace} <Workspace
workspaceTree={workspaceTree} workspace={workspace}
boardTree={this.state.boardTree} workspaceTree={workspaceTree}
showView={(id, boardId) => { boardTree={this.state.boardTree}
this.showView(id, boardId) showView={(id, boardId) => {
}} this.showView(id, boardId)
showBoard={(id) => { }}
this.showBoard(id) showBoard={(id) => {
}} this.showBoard(id)
setSearchText={(text) => { }}
this.setSearchText(text) setSearchText={(text) => {
}} this.setSearchText(text)
readonly={this.props.readonly || false} }}
/> readonly={this.props.readonly || false}
</div> />
</div>
</WorkspaceUsersContext.Provider>
) )
} }

View File

@ -1,6 +1,8 @@
// 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 {CSSObject} from '@emotion/serialize'
export type Theme = { export type Theme = {
mainBg: string, mainBg: string,
mainFg: string, mainFg: string,
@ -156,3 +158,67 @@ export function initThemes(): void {
} }
loadTheme() loadTheme()
} }
export function getSelectBaseStyle() {
return {
dropdownIndicator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none !important',
}),
indicatorSeparator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none',
}),
loadingIndicator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none',
}),
clearIndicator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none',
}),
menu: (provided: CSSObject): CSSObject => ({
...provided,
width: 'unset',
background: 'rgb(var(--main-bg))',
}),
option: (provided: CSSObject, state: { isFocused: boolean }): CSSObject => ({
...provided,
background: state.isFocused ? 'rgba(var(--main-fg), 0.1)' : 'rgb(var(--main-bg))',
color: state.isFocused ? 'rgb(var(--main-fg))' : 'rgb(var(--main-fg))',
padding: '2px 8px',
}),
control: (): CSSObject => ({
border: 0,
width: '100%',
margin: '4px 0 0 0',
// display: 'flex',
// marginTop: 0,
}),
valueContainer: (provided: CSSObject): CSSObject => ({
...provided,
padding: '0 5px',
overflow: 'unset',
// height: '20px',
}),
singleValue: (provided: CSSObject): CSSObject => ({
...provided,
color: 'rgb(var(--main-fg))',
overflow: 'unset',
maxWidth: 'calc(100% - 20px)',
}),
input: (provided: CSSObject): CSSObject => ({
...provided,
paddingBottom: 0,
paddingTop: 0,
marginBottom: 0,
marginTop: 0,
}),
menuList: (provided: CSSObject): CSSObject => ({
...provided,
overflowY: 'unset',
}),
}
}

View File

@ -5,6 +5,11 @@ import React from 'react'
const UserContext = React.createContext(undefined as IUser|undefined) const UserContext = React.createContext(undefined as IUser|undefined)
const WorkspaceUsersContext = React.createContext({
users: new Array<IUser>(),
usersById: new Map<string, IUser>(),
})
interface IUser { interface IUser {
id: string, id: string,
username: string, username: string,
@ -14,4 +19,9 @@ interface IUser {
updateAt: number, updateAt: number,
} }
export {IUser, UserContext} type WorkspaceUsersContextData = {
users: Array<IUser>
usersById: Map<string, IUser>
}
export {IUser, UserContext, WorkspaceUsersContext, WorkspaceUsersContextData}

View File

@ -187,6 +187,21 @@ exports[`widgets/PropertyMenu should match snapshot 1`] = `
class="noicon" class="noicon"
/> />
</div> </div>
<div
class="MenuOption TextOption menu-option"
>
<div
class="noicon"
/>
<div
class="menu-name"
>
Person
</div>
<div
class="noicon"
/>
</div>
<div <div
class="MenuOption TextOption menu-option" class="MenuOption TextOption menu-option"
> >

View File

@ -129,6 +129,11 @@ const PropertyMenu = React.memo((props: Props) => {
name={typeDisplayName(intl, 'date')} name={typeDisplayName(intl, 'date')}
onClick={() => props.onTypeChanged('date')} onClick={() => props.onTypeChanged('date')}
/> />
<Menu.Text
id='person'
name={typeDisplayName(intl, 'person')}
onClick={() => props.onTypeChanged('person')}
/>
<Menu.Text <Menu.Text
id='checkbox' id='checkbox'
name={typeDisplayName(intl, 'checkbox')} name={typeDisplayName(intl, 'checkbox')}

View File

@ -2,13 +2,16 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import {ActionMeta, ValueType, FormatOptionLabelMeta} from 'react-select' import {ActionMeta, FormatOptionLabelMeta, ValueType} from 'react-select'
import CreatableSelect from 'react-select/creatable' import CreatableSelect from 'react-select/creatable'
import {CSSObject} from '@emotion/serialize' import {CSSObject} from '@emotion/serialize'
import {IPropertyOption} from '../blocks/board' import {IPropertyOption} from '../blocks/board'
import {Constants} from '../constants' import {Constants} from '../constants'
import {getSelectBaseStyle} from '../theme'
import Menu from './menu' import Menu from './menu'
import MenuWrapper from './menuWrapper' import MenuWrapper from './menuWrapper'
import IconButton from './buttons/iconButton' import IconButton from './buttons/iconButton'
@ -90,70 +93,47 @@ const ValueSelectorLabel = React.memo((props: LabelProps): JSX.Element => {
) )
}) })
const valueSelectorStyle = {
...getSelectBaseStyle(),
option: (provided: CSSObject, state: {isFocused: boolean}): CSSObject => ({
...provided,
background: state.isFocused ? 'rgba(var(--main-fg), 0.1)' : 'rgb(var(--main-bg))',
color: state.isFocused ? 'rgb(var(--main-fg))' : 'rgb(var(--main-fg))',
padding: '8px',
}),
control: (): CSSObject => ({
border: 0,
width: '100%',
margin: '0',
}),
valueContainer: (provided: CSSObject): CSSObject => ({
...provided,
padding: '0 8px',
overflow: 'unset',
}),
multiValue: (provided: CSSObject): CSSObject => ({
...provided,
margin: 0,
padding: 0,
backgroundColor: 'transparent',
}),
multiValueLabel: (provided: CSSObject): CSSObject => ({
...provided,
display: 'flex',
paddingLeft: 0,
padding: 0,
}),
multiValueRemove: (): CSSObject => ({
display: 'none',
}),
}
function ValueSelector(props: Props): JSX.Element { function ValueSelector(props: Props): JSX.Element {
return ( return (
<CreatableSelect <CreatableSelect
isMulti={props.isMulti} isMulti={props.isMulti}
isClearable={true} isClearable={true}
styles={{ styles={valueSelectorStyle}
indicatorsContainer: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none',
}),
menu: (provided: CSSObject): CSSObject => ({
...provided,
width: 'unset',
background: 'rgb(var(--main-bg))',
}),
option: (provided: CSSObject, state: {isFocused: boolean}): CSSObject => ({
...provided,
background: state.isFocused ? 'rgba(var(--main-fg), 0.1)' : 'rgb(var(--main-bg))',
color: state.isFocused ? 'rgb(var(--main-fg))' : 'rgb(var(--main-fg))',
padding: '8px',
}),
control: (): CSSObject => ({
border: 0,
width: '100%',
margin: '0',
}),
valueContainer: (provided: CSSObject): CSSObject => ({
...provided,
padding: '0 8px',
overflow: 'unset',
}),
singleValue: (provided: CSSObject): CSSObject => ({
...provided,
color: 'rgb(var(--main-fg))',
overflow: 'unset',
maxWidth: 'calc(100% - 20px)',
}),
input: (provided: CSSObject): CSSObject => ({
...provided,
paddingBottom: 0,
paddingTop: 0,
marginBottom: 0,
marginTop: 0,
}),
menuList: (provided: CSSObject): CSSObject => ({
...provided,
overflowY: 'unset',
}),
multiValue: (provided: CSSObject): CSSObject => ({
...provided,
margin: 0,
padding: 0,
backgroundColor: 'transparent',
}),
multiValueLabel: (provided: CSSObject): CSSObject => ({
...provided,
display: 'flex',
paddingLeft: 0,
padding: 0,
}),
multiValueRemove: (): CSSObject => ({
display: 'none',
}),
}}
formatOptionLabel={(option: IPropertyOption, meta: FormatOptionLabelMeta<IPropertyOption, true | false>) => ( formatOptionLabel={(option: IPropertyOption, meta: FormatOptionLabelMeta<IPropertyOption, true | false>) => (
<ValueSelectorLabel <ValueSelectorLabel
option={option} option={option}