Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 144 additions & 9 deletions server/channels/app/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,30 @@ import (
"github.com/mattermost/mattermost/server/v8/channels/store"
)

const EmojisPermissionsMigrationKey = "EmojisPermissionsMigrationComplete"
const GuestRolesCreationMigrationKey = "GuestRolesCreationMigrationComplete"
const SystemConsoleRolesCreationMigrationKey = "SystemConsoleRolesCreationMigrationComplete"
const CustomGroupAdminRoleCreationMigrationKey = "CustomGroupAdminRoleCreationMigrationComplete"
const ContentExtractionConfigDefaultTrueMigrationKey = "ContentExtractionConfigDefaultTrueMigrationComplete"
const PlaybookRolesCreationMigrationKey = "PlaybookRolesCreationMigrationComplete"
const FirstAdminSetupCompleteKey = model.SystemFirstAdminSetupComplete
const remainingSchemaMigrationsKey = "RemainingSchemaMigrations"
const postPriorityConfigDefaultTrueMigrationKey = "PostPriorityConfigDefaultTrueMigrationComplete"
const (
EmojisPermissionsMigrationKey = "EmojisPermissionsMigrationComplete"
GuestRolesCreationMigrationKey = "GuestRolesCreationMigrationComplete"
SystemConsoleRolesCreationMigrationKey = "SystemConsoleRolesCreationMigrationComplete"
CustomGroupAdminRoleCreationMigrationKey = "CustomGroupAdminRoleCreationMigrationComplete"
ContentExtractionConfigDefaultTrueMigrationKey = "ContentExtractionConfigDefaultTrueMigrationComplete"
PlaybookRolesCreationMigrationKey = "PlaybookRolesCreationMigrationComplete"
FirstAdminSetupCompleteKey = model.SystemFirstAdminSetupComplete
remainingSchemaMigrationsKey = "RemainingSchemaMigrations"
postPriorityConfigDefaultTrueMigrationKey = "PostPriorityConfigDefaultTrueMigrationComplete"
contentFlaggingSetupDoneKey = "content_flagging_setup_done"
contentFlaggingMigrationVersion = "v1"

contentFlaggingPropertyNameFlaggedPostId = "flagged_post_id"
contentFlaggingPropertyNameStatus = "status"
contentFlaggingPropertyNameReportingUserID = "reporting_user_id"
contentFlaggingPropertyNameReportingReason = "reporting_reason"
contentFlaggingPropertyNameReportingComment = "reporting_comment"
contentFlaggingPropertyNameReportingTime = "reporting_time"
contentFlaggingPropertyNameReviewerUserID = "reviewer_user_id"
contentFlaggingPropertyNameActorUserID = "actor_user_id"
contentFlaggingPropertyNameActorComment = "actor_comment"
contentFlaggingPropertyNameActionTime = "action_time"
)

// This function migrates the default built in roles from code/config to the database.
func (a *App) DoAdvancedPermissionsMigration() error {
Expand Down Expand Up @@ -582,6 +597,125 @@ func (s *Server) doPostPriorityConfigDefaultTrueMigration() error {
return nil
}

func (s *Server) doSetupContentFlaggingProperties() error {
// This migration is designed in a way to allow adding more properties in the future.
// When a new property needs to be added, add it to the expectedPropertiesMap map and
// update the contentFlaggingMigrationVersion to a new value..

// If the migration is already marked as completed, don't do it again.
var nfErr *store.ErrNotFound
data, err := s.Store().System().GetByName(contentFlaggingSetupDoneKey)
if err != nil && !errors.As(err, &nfErr) {
return fmt.Errorf("could not query migration: %w", err)
}

if data != nil && data.Value == contentFlaggingMigrationVersion {
return nil
}

// RegisterPropertyGroup is idempotent, so no need to check if group is already registered
group, err := s.propertyService.RegisterPropertyGroup(model.ContentFlaggingGroupName)
if err != nil {
return fmt.Errorf("failed to register Content Flagging group: %w", err)
}

// Using page size of 100 and not iterating through all pages because the
// number of fields are static and defined here and not expected to be more than 100 for now.
existingProperties, appErr := s.propertyService.SearchPropertyFields(group.ID, "", model.PropertyFieldSearchOpts{PerPage: 100})
if appErr != nil {
return fmt.Errorf("failed to search for existing content flagging properties: %w", appErr)
}

existingPropertiesMap := map[string]*model.PropertyField{}
for _, property := range existingProperties {
existingPropertiesMap[property.Name] = property
}

expectedPropertiesMap := map[string]*model.PropertyField{
contentFlaggingPropertyNameFlaggedPostId: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameFlaggedPostId,
Type: model.PropertyFieldTypeText,
},
contentFlaggingPropertyNameStatus: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameStatus,
Type: model.PropertyFieldTypeText,
},
contentFlaggingPropertyNameReportingUserID: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameReportingUserID,
Type: model.PropertyFieldTypeUser,
},
contentFlaggingPropertyNameReportingReason: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameReportingReason,
Type: model.PropertyFieldTypeText,
},
contentFlaggingPropertyNameReportingComment: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameReportingComment,
Type: model.PropertyFieldTypeText,
},
contentFlaggingPropertyNameReportingTime: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameReportingTime,
Type: model.PropertyFieldTypeText,
},
contentFlaggingPropertyNameReviewerUserID: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameReviewerUserID,
Type: model.PropertyFieldTypeUser,
},
contentFlaggingPropertyNameActorUserID: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameActorUserID,
Type: model.PropertyFieldTypeUser,
},
contentFlaggingPropertyNameActorComment: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameActorComment,
Type: model.PropertyFieldTypeText,
},
contentFlaggingPropertyNameActionTime: {
GroupID: group.ID,
Name: contentFlaggingPropertyNameActionTime,
Type: model.PropertyFieldTypeText,
},
}

var propertiesToUpdate []*model.PropertyField
var propertiesToCreate []*model.PropertyField

for name, expectedProperty := range expectedPropertiesMap {
if _, exists := existingPropertiesMap[name]; exists {
property := existingPropertiesMap[name]
property.Type = expectedProperty.Type
propertiesToUpdate = append(propertiesToUpdate, property)
} else {
propertiesToCreate = append(propertiesToCreate, expectedProperty)
}
}

for _, property := range propertiesToCreate {
if _, err := s.propertyService.CreatePropertyField(property); err != nil {
return fmt.Errorf("failed to create content flagging property: %q, error: %w", property.Name, err)
}
}

if len(propertiesToUpdate) > 0 {
if _, err := s.propertyService.UpdatePropertyFields(group.ID, propertiesToUpdate); err != nil {
return fmt.Errorf("failed to update content flagging property fields: %w", err)
}
}

if err := s.Store().System().SaveOrUpdate(&model.System{Name: contentFlaggingSetupDoneKey, Value: contentFlaggingMigrationVersion}); err != nil {
return fmt.Errorf("failed to save content flagging setup done flag in system store %w", err)
}

return nil
}

func (s *Server) doCloudS3PathMigrations(c request.CTX) error {
// This migration is only applicable for cloud environments
if os.Getenv("MM_CLOUD_FILESTORE_BIFROST") == "" {
Expand Down Expand Up @@ -695,6 +829,7 @@ func (s *Server) doAppMigrations() {
{"First Admin Setup Complete Migration", s.doFirstAdminSetupCompleteMigration},
{"Remaining Schema Migrations", s.doRemainingSchemaMigrations},
{"Post Priority Config Default True Migration", s.doPostPriorityConfigDefaultTrueMigration},
{"Content Flagging Properties Setup", s.doSetupContentFlaggingProperties},
}

for i := range m1 {
Expand Down
52 changes: 52 additions & 0 deletions server/channels/app/migrations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

package app

import (
"testing"

"github.com/mattermost/mattermost/server/public/model"
"github.com/stretchr/testify/require"
)

func TestDoSetupContentFlaggingProperties(t *testing.T) {
t.Run("should register property group and fields", func(t *testing.T) {
//we need to call the Setup method and run the full setup instead of
//just creating a new server via NewServer() because the Setup method
//also care of using the correct database DSN based on environment,
//setting up the store and initializing services used in store such as property services.
th := Setup(t)
defer th.TearDown()

group, err := th.Server.propertyService.GetPropertyGroup(model.ContentFlaggingGroupName)
require.NoError(t, err)
require.NotNil(t, group)
require.Equal(t, model.ContentFlaggingGroupName, group.Name)

propertyFields, err := th.Server.propertyService.SearchPropertyFields(group.ID, "", model.PropertyFieldSearchOpts{PerPage: 100})
require.NoError(t, err)
require.Len(t, propertyFields, 10)
})

t.Run("the migration is idempotent", func(t *testing.T) {
th := Setup(t)
defer th.TearDown()

// Now we will remove the migration done key from systems table to allow the data migration to run again
_, err := th.Store.System().PermanentDeleteByName(contentFlaggingSetupDoneKey)
require.NoError(t, err)

// Run the content flagging data migration again
err = th.Server.doSetupContentFlaggingProperties()
require.NoError(t, err)

group, err := th.Server.propertyService.GetPropertyGroup(model.ContentFlaggingGroupName)
require.NoError(t, err)
require.Equal(t, model.ContentFlaggingGroupName, group.Name)

propertyFields, err := th.Server.propertyService.SearchPropertyFields(group.ID, "", model.PropertyFieldSearchOpts{PerPage: 100})
require.NoError(t, err)
require.Len(t, propertyFields, 10)
})
}
8 changes: 8 additions & 0 deletions server/channels/testlib/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
systemStore.On("GetByName", "SystemConsoleRolesCreationMigrationComplete").Return(&model.System{Name: "SystemConsoleRolesCreationMigrationComplete", Value: "true"}, nil)
systemStore.On("GetByName", "PlaybookRolesCreationMigrationComplete").Return(&model.System{Name: "PlaybookRolesCreationMigrationComplete", Value: "true"}, nil)
systemStore.On("GetByName", "PostPriorityConfigDefaultTrueMigrationComplete").Return(&model.System{Name: "PostPriorityConfigDefaultTrueMigrationComplete", Value: "true"}, nil)
systemStore.On("GetByName", "content_flagging_setup_done").Return(&model.System{Name: "content_flagging_setup_done", Value: "true"}, nil)
systemStore.On("GetByName", model.MigrationKeyEmojiPermissionsSplit).Return(&model.System{Name: model.MigrationKeyEmojiPermissionsSplit, Value: "true"}, nil)
systemStore.On("GetByName", model.MigrationKeyWebhookPermissionsSplit).Return(&model.System{Name: model.MigrationKeyWebhookPermissionsSplit, Value: "true"}, nil)
systemStore.On("GetByName", model.MigrationKeyListJoinPublicPrivateTeams).Return(&model.System{Name: model.MigrationKeyListJoinPublicPrivateTeams, Value: "true"}, nil)
Expand Down Expand Up @@ -91,6 +92,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {

systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once()
systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)
systemStore.On("SaveOrUpdate", mock.AnythingOfType("*model.System")).Return(nil)

userStore := mocks.UserStore{}
userStore.On("Count", mock.AnythingOfType("model.UserCountOptions")).Return(int64(1), nil)
Expand Down Expand Up @@ -125,6 +127,12 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
propertyFieldStore := mocks.PropertyFieldStore{}
propertyValueStore := mocks.PropertyValueStore{}

propertyGroupStore.On("Register", model.ContentFlaggingGroupName).Return(&model.PropertyGroup{ID: model.NewId(), Name: model.ContentFlaggingGroupName}, nil)

propertyFieldStore.On("SearchPropertyFields", mock.Anything).Return([]*model.PropertyField{}, nil)
propertyFieldStore.On("CreatePropertyField", mock.Anything).Return(&model.PropertyField{}, nil)
propertyFieldStore.On("Create", mock.AnythingOfType("*model.PropertyField")).Return(&model.PropertyField{}, nil)

mockStore.On("System").Return(&systemStore)
mockStore.On("User").Return(&userStore)
mockStore.On("Post").Return(&postStore)
Expand Down
6 changes: 6 additions & 0 deletions server/public/model/content_flagging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

package model

const ContentFlaggingGroupName = "content_flagging"
Loading