Commit 1f65f0e3 authored by George Goldberg's avatar George Goldberg Committed by GitHub

MM-10233: Add scheme importing to bulk importer. (#8928)

parent 8e24dd78
......@@ -20,6 +20,7 @@ import (
"github.com/mattermost/mattermost-server/store/sqlstore"
"github.com/mattermost/mattermost-server/store/storetest"
"github.com/mattermost/mattermost-server/utils"
"testing"
)
type TestHelper struct {
......@@ -446,6 +447,54 @@ func (me *TestHelper) ResetEmojisMigration() {
}
}
func (me *TestHelper) CheckTeamCount(t *testing.T, expected int64) {
if r := <-me.App.Srv.Store.Team().AnalyticsTeamCount(); r.Err == nil {
if r.Data.(int64) != expected {
t.Fatalf("Unexpected number of teams. Expected: %v, found: %v", expected, r.Data.(int64))
}
} else {
t.Fatalf("Failed to get team count.")
}
}
func (me *TestHelper) CheckChannelsCount(t *testing.T, expected int64) {
if r := <-me.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_OPEN); r.Err == nil {
if r.Data.(int64) != expected {
t.Fatalf("Unexpected number of channels. Expected: %v, found: %v", expected, r.Data.(int64))
}
} else {
t.Fatalf("Failed to get channel count.")
}
}
func (me *TestHelper) SetupTeamScheme() *model.Scheme {
scheme := model.Scheme{
Name: model.NewId(),
DisplayName: model.NewId(),
Scope: model.SCHEME_SCOPE_TEAM,
}
if scheme, err := me.App.CreateScheme(&scheme); err == nil {
return scheme
} else {
panic(err)
}
}
func (me *TestHelper) SetupChannelScheme() *model.Scheme {
scheme := model.Scheme{
Name: model.NewId(),
DisplayName: model.NewId(),
Scope: model.SCHEME_SCOPE_CHANNEL,
}
if scheme, err := me.App.CreateScheme(&scheme); err == nil {
return scheme
} else {
panic(err)
}
}
type FakeClusterInterface struct {
clusterMessageHandler einterfaces.ClusterMessageHandler
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -50,6 +50,21 @@ func (a *App) PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role,
return role, err
}
func (a *App) CreateRole(role *model.Role) (*model.Role, *model.AppError) {
role.Id = ""
role.CreateAt = 0
role.UpdateAt = 0
role.DeleteAt = 0
role.BuiltIn = false
role.SchemeManaged = false
if result := <-a.Srv.Store.Role().Save(role); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.Role), nil
}
}
func (a *App) UpdateRole(role *model.Role) (*model.Role, *model.AppError) {
if result := <-a.Srv.Store.Role().Save(role); result.Err != nil {
return nil, result.Err
......
......@@ -22,6 +22,18 @@ func (a *App) GetScheme(id string) (*model.Scheme, *model.AppError) {
}
}
func (a *App) GetSchemeByName(name string) (*model.Scheme, *model.AppError) {
if err := a.IsPhase2MigrationCompleted(); err != nil {
return nil, err
}
if result := <-a.Srv.Store.Scheme().GetByName(name); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.Scheme), nil
}
}
func (a *App) GetSchemesPage(scope string, page int, perPage int) ([]*model.Scheme, *model.AppError) {
if err := a.IsPhase2MigrationCompleted(); err != nil {
return nil, err
......
......@@ -105,8 +105,9 @@ func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
oldTeam.AllowedDomains = team.AllowedDomains
oldTeam.LastTeamIconUpdate = team.LastTeamIconUpdate
if result := <-a.Srv.Store.Team().Update(oldTeam); result.Err != nil {
return nil, result.Err
oldTeam, err = a.updateTeamUnsanitized(oldTeam)
if err != nil {
return team, err
}
a.sendTeamEvent(oldTeam, model.WEBSOCKET_EVENT_UPDATE_TEAM)
......@@ -114,6 +115,14 @@ func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
return oldTeam, nil
}
func (a *App) updateTeamUnsanitized(team *model.Team) (*model.Team, *model.AppError) {
if result := <-a.Srv.Store.Team().Update(team); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.Team), nil
}
}
func (a *App) UpdateTeamScheme(team *model.Team) (*model.Team, *model.AppError) {
var oldTeam *model.Team
var err *model.AppError
......
......@@ -3446,6 +3446,14 @@
"id": "app.import.import_line.null_channel.error",
"translation": "Import data line has type \"channel\" but the channel object is null."
},
{
"id": "app.import.import_line.null_scheme.error",
"translation": "Import data line has type \"scheme\" but the scheme object is null."
},
{
"id": "app.import.import_scheme.scope_change.error",
"translation": "The bulk importer cannot change the scope of an already-existing scheme."
},
{
"id": "app.import.import_line.null_direct_channel.error",
"translation": "Import data line has type \"direct_channel\" but the direct_channel object is null."
......@@ -3466,6 +3474,70 @@
"id": "app.import.import_line.null_user.error",
"translation": "Import data line has type \"user\" but the user object is null."
},
{
"id": "app.import.validate_scheme_import_data.null_scope.error",
"translation": "Scheme scope is required."
},
{
"id": "app.import.validate_scheme_import_data.wrong_roles_for_scope.error",
"translation": "The wrong roles were provided for a scheme with this scope."
},
{
"id": "app.import.validate_scheme_import_data.unknown_scheme.error",
"translation": "Unknown scheme scope."
},
{
"id": "app.import.validate_scheme_import_data.name_invalid.error",
"translation": "Invalid scheme name."
},
{
"id": "app.import.validate_scheme_import_data.display_name_invalid.error",
"translation": "Invalid scheme display name."
},
{
"id": "app.import.validate_scheme_import_data.description_invalid.error",
"translation": "Invalid scheme description."
},
{
"id": "app.import.validate_role_import_data.name_invalid.error",
"translation": "Invalid role name."
},
{
"id": "app.import.validate_role_import_data.display_name_invalid.error",
"translation": "Invalid role display name."
},
{
"id": "app.import.validate_role_import_data.description_invalid.error",
"translation": "Invalid role description."
},
{
"id": "app.import.validate_role_import_data.invalid_permission.error",
"translation": "Invalid permission on role."
},
{
"id": "app.import.validate_team_import_data.scheme_invalid.error",
"translation": "Invalid scheme name for team."
},
{
"id": "app.import.validate_channel_import_data.scheme_invalid.error",
"translation": "Invalid scheme name for channel."
},
{
"id": "app.import.import_team.scheme_deleted.error",
"translation": "Cannot set a team to use a deleted scheme."
},
{
"id": "app.import.import_team.scheme_wrong_scope.error",
"translation": "Team must be assigned to a Team-scoped scheme."
},
{
"id": "app.import.import_channel.scheme_deleted.error",
"translation": "Cannot set a channel to use a deleted scheme."
},
{
"id": "app.import.import_channel.scheme_wrong_scope.error",
"translation": "Channel must be assigned to a Channel-scoped scheme."
},
{
"id": "app.import.import_line.unknown_line_type.error",
"translation": "Import data line has unknown type \"{{.Type}}\"."
......
......@@ -192,7 +192,7 @@ func (p *SchemeIDPatch) ToJson() string {
}
func IsValidSchemeName(name string) bool {
re := regexp.MustCompile(fmt.Sprintf("^[a-z0-9_]{0,%d}$", SCHEME_NAME_MAX_LENGTH))
re := regexp.MustCompile(fmt.Sprintf("^[a-z0-9_]{2,%d}$", SCHEME_NAME_MAX_LENGTH))
return re.MatchString(name)
}
......
......@@ -287,6 +287,12 @@ func (s *LayeredSchemeStore) Get(schemeId string) StoreChannel {
})
}
func (s *LayeredSchemeStore) GetByName(schemeName string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.SchemeGetByName(s.TmpContext, schemeName)
})
}
func (s *LayeredSchemeStore) Delete(schemeId string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.SchemeDelete(s.TmpContext, schemeId)
......
......@@ -41,6 +41,7 @@ type LayeredStoreSupplier interface {
// Schemes
SchemeSave(ctx context.Context, scheme *model.Scheme, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
SchemeGet(ctx context.Context, schemeId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
SchemeGetByName(ctx context.Context, schemeName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
SchemeDelete(ctx context.Context, schemeId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
SchemePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
......
......@@ -36,6 +36,10 @@ func (s *LocalCacheSupplier) SchemeGet(ctx context.Context, schemeId string, hin
return result
}
func (s *LocalCacheSupplier) SchemeGetByName(ctx context.Context, schemeName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().SchemeGetByName(ctx, schemeName, hints...)
}
func (s *LocalCacheSupplier) SchemeDelete(ctx context.Context, schemeId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
defer s.doInvalidateCacheCluster(s.schemeCache, schemeId)
defer s.doClearCacheCluster(s.roleCache)
......
......@@ -19,6 +19,11 @@ func (s *RedisSupplier) SchemeGet(ctx context.Context, schemeId string, hints ..
return s.Next().SchemeGet(ctx, schemeId, hints...)
}
func (s *RedisSupplier) SchemeGetByName(ctx context.Context, schemeName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().SchemeGetByName(ctx, schemeName, hints...)
}
func (s *RedisSupplier) SchemeDelete(ctx context.Context, schemeId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().SchemeDelete(ctx, schemeId, hints...)
......
......@@ -48,7 +48,7 @@ func (s *SqlSupplier) SchemeSave(ctx context.Context, scheme *model.Scheme, hint
}
} else {
if !scheme.IsValid() {
result.Err = model.NewAppError("SqlSchemeStore.Save", "store.sql_scheme.save.invalid_scheme.app_error", nil, "", http.StatusBadRequest)
result.Err = model.NewAppError("SqlSchemeStore.Save", "store.sql_scheme.save.invalid_scheme.app_error", nil, "schemeId="+scheme.Id, http.StatusBadRequest)
return result
}
......@@ -199,6 +199,24 @@ func (s *SqlSupplier) SchemeGet(ctx context.Context, schemeId string, hints ...s
return result
}
func (s *SqlSupplier) SchemeGetByName(ctx context.Context, schemeName string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var scheme model.Scheme
if err := s.GetReplica().SelectOne(&scheme, "SELECT * from Schemes WHERE Name = :Name", map[string]interface{}{"Name": schemeName}); err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlSchemeStore.GetByName", "store.sql_scheme.get.app_error", nil, "Name="+schemeName+", "+err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewAppError("SqlSchemeStore.GetByName", "store.sql_scheme.get.app_error", nil, err.Error(), http.StatusInternalServerError)
}
}
result.Data = &scheme
return result
}
func (s *SqlSupplier) SchemeDelete(ctx context.Context, schemeId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
......
......@@ -489,6 +489,7 @@ type RoleStore interface {
type SchemeStore interface {
Save(scheme *model.Scheme) StoreChannel
Get(schemeId string) StoreChannel
GetByName(schemeName string) StoreChannel
GetAllPage(scope string, offset int, limit int) StoreChannel
Delete(schemeId string) StoreChannel
PermanentDeleteAll() StoreChannel
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......@@ -655,6 +655,29 @@ func (_m *LayeredStoreDatabaseLayer) SchemeGetAllPage(ctx context.Context, scope
return r0
}
// SchemeGetByName provides a mock function with given fields: ctx, schemeName, hints
func (_m *LayeredStoreDatabaseLayer) SchemeGetByName(ctx context.Context, schemeName string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, schemeName)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, schemeName, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// SchemePermanentDeleteAll provides a mock function with given fields: ctx, hints
func (_m *LayeredStoreDatabaseLayer) SchemePermanentDeleteAll(ctx context.Context, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......@@ -352,6 +352,29 @@ func (_m *LayeredStoreSupplier) SchemeGetAllPage(ctx context.Context, scope stri
return r0
}
// SchemeGetByName provides a mock function with given fields: ctx, schemeName, hints
func (_m *LayeredStoreSupplier) SchemeGetByName(ctx context.Context, schemeName string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, schemeName)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, schemeName, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// SchemePermanentDeleteAll provides a mock function with given fields: ctx, hints
func (_m *LayeredStoreSupplier) SchemePermanentDeleteAll(ctx context.Context, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......@@ -66,9 +66,9 @@ func (_m *PostStore) ClearCaches() {
_m.Called()
}
// Delete provides a mock function with given fields: postId, time
// Delete provides a mock function with given fields: postId, time, deleteByID
func (_m *PostStore) Delete(postId string, time int64, deleteByID string) store.StoreChannel {
ret := _m.Called(postId, time)
ret := _m.Called(postId, time, deleteByID)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, int64, string) store.StoreChannel); ok {
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
......@@ -61,6 +61,22 @@ func (_m *SchemeStore) GetAllPage(scope string, offset int, limit int) store.Sto
return r0
}
// GetByName provides a mock function with given fields: schemeName
func (_m *SchemeStore) GetByName(schemeName string) store.StoreChannel {
ret := _m.Called(schemeName)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(schemeName)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// PermanentDeleteAll provides a mock function with given fields:
func (_m *SchemeStore) PermanentDeleteAll() store.StoreChannel {
ret := _m.Called()
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v1.0.0
// Regenerate this file using `make store-mocks`.
......
......@@ -179,6 +179,42 @@ func testSchemeStoreGet(t *testing.T, ss store.Store) {
assert.NotNil(t, res3.Err)
}
func testSchemeStoreGetByName(t *testing.T, ss store.Store) {
// Save a scheme to test with.
s1 := &model.Scheme{
DisplayName: model.NewId(),
Name: model.NewId(),
Description: model.NewId(),
Scope: model.SCHEME_SCOPE_TEAM,
}
res1 := <-ss.Scheme().Save(s1)
assert.Nil(t, res1.Err)
d1 := res1.Data.(*model.Scheme)
assert.Len(t, d1.Id, 26)
// Get a valid scheme
res2 := <-ss.Scheme().GetByName(d1.Name)
assert.Nil(t, res2.Err)
d2 := res1.Data.(*model.Scheme)
assert.Equal(t, d1.Id, d2.Id)
assert.Equal(t, s1.DisplayName, d2.DisplayName)
assert.Equal(t, s1.Name, d2.Name)
assert.Equal(t, d1.Description, d2.Description)
assert.NotZero(t, d2.CreateAt)
assert.NotZero(t, d2.UpdateAt)
assert.Zero(t, d2.DeleteAt)
assert.Equal(t, s1.Scope, d2.Scope)
assert.Equal(t, d1.DefaultTeamAdminRole, d2.DefaultTeamAdminRole)
assert.Equal(t, d1.DefaultTeamUserRole, d2.DefaultTeamUserRole)
assert.Equal(t, d1.DefaultChannelAdminRole, d2.DefaultChannelAdminRole)
assert.Equal(t, d1.DefaultChannelUserRole, d2.DefaultChannelUserRole)
// Get an invalid scheme
res3 := <-ss.Scheme().GetByName(model.NewId())
assert.NotNil(t, res3.Err)
}
func testSchemeStoreGetAllPage(t *testing.T, ss store.Store) {
// Save a scheme to test with.
schemes := []*model.Scheme{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment