mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-03 09:53:10 -05:00
Add the ability to patch channel autotranslations (#35078)
* Add the ability to patch channel autotranslations * Fix lint * Update docs * Fix CI * Fix CI * Fix mmctl test * Check whether the channel is translated for the user when checking user enabled * Fix wrong uses of patch acrros e2e and frontend * Fix test * Fix wording * Fix tests and column name * Move group constrained test so they don't mess with the basic entities * Fix patch sending too much information
This commit is contained in:
@@ -606,18 +606,36 @@
|
||||
summary: Patch a channel
|
||||
description: >
|
||||
Partially update a channel by providing only the fields you want to
|
||||
update. Omitted fields will not be updated. The fields that can be
|
||||
updated are defined in the request body, all other provided fields will
|
||||
be ignored.
|
||||
update. Omitted fields will not be updated. At least one of the allowed
|
||||
fields must be provided.
|
||||
|
||||
**Public and private channels:** Can update `name`, `display_name`,
|
||||
`purpose`, `header`, `group_constrained`, `autotranslation`, and
|
||||
`banner_info` (subject to permissions and channel type).
|
||||
|
||||
**Direct and group message channels:** Only `header` and (when not
|
||||
restricted by config) `autotranslation` can be updated; the caller
|
||||
must be a channel member. Updating `name`, `display_name`, or `purpose`
|
||||
is not allowed.
|
||||
|
||||
The default channel (e.g. Town Square) cannot have its `name` changed.
|
||||
|
||||
##### Permissions
|
||||
|
||||
If updating a public channel, `manage_public_channel_members` permission is required. If updating a private channel, `manage_private_channel_members` permission is required.
|
||||
- **Public channel:** For property updates (name, display_name, purpose, header, group_constrained),
|
||||
`manage_public_channel_properties` is required. For `autotranslation`, `manage_public_channel_auto_translation`
|
||||
is required. For `banner_info`, `manage_public_channel_banner` is required (Channel Banner feature and
|
||||
Enterprise license required).
|
||||
- **Private channel:** For property updates, `manage_private_channel_properties` is required. For
|
||||
`autotranslation`, `manage_private_channel_auto_translation` is required. For `banner_info`,
|
||||
`manage_private_channel_banner` is required (Channel Banner feature and Enterprise license required).
|
||||
- **Direct or group message channel:** Must be a member of the channel; only `header` and (when allowed)
|
||||
`autotranslation` can be updated.
|
||||
operationId: PatchChannel
|
||||
parameters:
|
||||
- name: channel_id
|
||||
in: path
|
||||
description: Channel GUID
|
||||
description: Channel ID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
@@ -630,20 +648,34 @@
|
||||
name:
|
||||
type: string
|
||||
description: The unique handle for the channel, will be present in the
|
||||
channel URL
|
||||
channel URL. Cannot be updated for direct or group message channels.
|
||||
Cannot be changed for the default channel (e.g. Town Square).
|
||||
display_name:
|
||||
type: string
|
||||
description: The non-unique UI name for the channel
|
||||
description: The non-unique UI name for the channel. Cannot be updated
|
||||
for direct or group message channels.
|
||||
purpose:
|
||||
type: string
|
||||
description: A short description of the purpose of the channel
|
||||
description: A short description of the purpose of the channel. Cannot
|
||||
be updated for direct or group message channels.
|
||||
header:
|
||||
type: string
|
||||
description: Markdown-formatted text to display in the header of the
|
||||
channel
|
||||
group_constrained:
|
||||
type: boolean
|
||||
description: When true, only members of the linked LDAP groups can join
|
||||
the channel. Only applicable to public and private channels.
|
||||
autotranslation:
|
||||
type: boolean
|
||||
description: Enable or disable automatic message translation in the
|
||||
channel. Requires the auto-translation feature and appropriate
|
||||
channel permission. May be restricted for direct and group message
|
||||
channels by server configuration.
|
||||
banner_info:
|
||||
$ref: "#/components/schemas/ChannelBanner"
|
||||
description: Channel object to be updated
|
||||
description: Channel patch object; include only the fields to update. At least
|
||||
one field must be provided.
|
||||
required: true
|
||||
responses:
|
||||
"200":
|
||||
|
||||
@@ -207,7 +207,6 @@ describe('Channel Info RHS', () => {
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(testChannel.id, {
|
||||
...testChannel,
|
||||
purpose: 'purpose for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('purpose for the tests').should('be.visible');
|
||||
@@ -221,7 +220,6 @@ describe('Channel Info RHS', () => {
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(testChannel.id, {
|
||||
...testChannel,
|
||||
header: 'header for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
|
||||
@@ -433,7 +431,6 @@ describe('Channel Info RHS', () => {
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(groupChannel.id, {
|
||||
...groupChannel,
|
||||
header: 'header for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
|
||||
@@ -527,7 +524,6 @@ describe('Channel Info RHS', () => {
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(directChannel.id, {
|
||||
...directChannel,
|
||||
header: 'header for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -854,5 +854,6 @@ const defaultServerConfig: AdminConfig = {
|
||||
Agents: {
|
||||
LLMServiceID: '',
|
||||
},
|
||||
RestrictDMAndGM: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -345,17 +345,46 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
model.AddEventParameterAuditableToAuditRec(auditRec, "channel", patch)
|
||||
auditRec.AddEventPriorState(oldChannel)
|
||||
|
||||
updatingProperties := patch.DisplayName != nil || patch.Name != nil || patch.Header != nil || patch.Purpose != nil || patch.GroupConstrained != nil
|
||||
updatingAutoTranslation := patch.AutoTranslation != nil
|
||||
|
||||
if !updatingProperties && !updatingAutoTranslation && patch.BannerInfo == nil {
|
||||
c.Err = model.NewAppError("patchChannel", "api.channel.patch_update_channel.no_changes.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if updatingAutoTranslation && (c.App.AutoTranslation() == nil || !c.App.AutoTranslation().IsFeatureAvailable()) {
|
||||
c.Err = model.NewAppError("patchChannel", "api.channel.patch_update_channel.feature_not_available.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
switch oldChannel.Type {
|
||||
case model.ChannelTypeOpen:
|
||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelProperties); !ok {
|
||||
c.SetPermissionError(model.PermissionManagePublicChannelProperties)
|
||||
return
|
||||
if updatingProperties {
|
||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelProperties); !ok {
|
||||
c.SetPermissionError(model.PermissionManagePublicChannelProperties)
|
||||
return
|
||||
}
|
||||
}
|
||||
if updatingAutoTranslation {
|
||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelAutoTranslation); !ok {
|
||||
c.SetPermissionError(model.PermissionManagePublicChannelAutoTranslation)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case model.ChannelTypePrivate:
|
||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelProperties); !ok {
|
||||
c.SetPermissionError(model.PermissionManagePrivateChannelProperties)
|
||||
return
|
||||
if updatingProperties {
|
||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelProperties); !ok {
|
||||
c.SetPermissionError(model.PermissionManagePrivateChannelProperties)
|
||||
return
|
||||
}
|
||||
}
|
||||
if updatingAutoTranslation {
|
||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelAutoTranslation); !ok {
|
||||
c.SetPermissionError(model.PermissionManagePrivateChannelAutoTranslation)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case model.ChannelTypeGroup, model.ChannelTypeDirect:
|
||||
@@ -369,6 +398,11 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if updatingAutoTranslation && *c.App.Config().AutoTranslationSettings.RestrictDMAndGM {
|
||||
c.Err = model.NewAppError("patchChannel", "api.channel.patch_update_channel.auto_translation_restricted.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("patchChannel", "api.channel.patch_update_channel.forbidden.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@ package api4
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -482,76 +483,10 @@ func TestUpdateChannel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchChannel(t *testing.T) {
|
||||
func TestPatchChannelGroupConstrained(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
client := th.Client
|
||||
team := th.BasicTeam
|
||||
|
||||
t.Run("should be unable to apply a null patch", func(t *testing.T) {
|
||||
var nullPatch *model.ChannelPatch
|
||||
|
||||
_, nullResp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, nullPatch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, nullResp)
|
||||
})
|
||||
|
||||
t.Run("should be able to patch values", func(t *testing.T) {
|
||||
patch := &model.ChannelPatch{
|
||||
Name: new(string),
|
||||
DisplayName: new(string),
|
||||
Header: new(string),
|
||||
Purpose: new(string),
|
||||
}
|
||||
*patch.Name = model.NewId()
|
||||
*patch.DisplayName = model.NewId()
|
||||
*patch.Header = model.NewId()
|
||||
*patch.Purpose = model.NewId()
|
||||
|
||||
channel, _, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, *patch.Name, channel.Name, "do not match")
|
||||
require.Equal(t, *patch.DisplayName, channel.DisplayName, "do not match")
|
||||
require.Equal(t, *patch.Header, channel.Header, "do not match")
|
||||
require.Equal(t, *patch.Purpose, channel.Purpose, "do not match")
|
||||
})
|
||||
|
||||
t.Run("should be able to patch with no name", func(t *testing.T) {
|
||||
channel := &model.Channel{
|
||||
DisplayName: GenerateTestChannelName(),
|
||||
Name: GenerateTestChannelName(),
|
||||
Type: model.ChannelTypeOpen,
|
||||
TeamId: team.Id,
|
||||
}
|
||||
var err error
|
||||
channel, _, err = client.CreateChannel(context.Background(), channel)
|
||||
require.NoError(t, err)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
Header: new(string),
|
||||
Purpose: new(string),
|
||||
}
|
||||
|
||||
oldName := channel.Name
|
||||
patchedChannel, _, err := client.PatchChannel(context.Background(), channel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, oldName, patchedChannel.Name, "should not have updated")
|
||||
})
|
||||
|
||||
t.Run("Test updating default channel's name and returns error", func(t *testing.T) {
|
||||
// Test updating default channel's name and returns error
|
||||
defaultChannel, appErr := th.App.GetChannelByName(th.Context, model.DefaultChannelName, team.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
defaultChannelPatch := &model.ChannelPatch{
|
||||
Name: new(string),
|
||||
}
|
||||
*defaultChannelPatch.Name = "testing"
|
||||
_, resp, err := client.PatchChannel(context.Background(), defaultChannel.Id, defaultChannelPatch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Test GroupConstrained flag", func(t *testing.T) {
|
||||
// Test GroupConstrained flag
|
||||
@@ -575,6 +510,9 @@ func TestPatchChannel(t *testing.T) {
|
||||
user := th.CreateUser(t)
|
||||
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
||||
require.NoError(t, err)
|
||||
|
||||
patch.GroupConstrained = model.NewPointer(false)
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
@@ -753,6 +691,78 @@ func TestPatchChannel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchChannel(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
client := th.Client
|
||||
team := th.BasicTeam
|
||||
|
||||
t.Run("should be unable to apply a null patch", func(t *testing.T) {
|
||||
var nullPatch *model.ChannelPatch
|
||||
|
||||
_, nullResp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, nullPatch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, nullResp)
|
||||
})
|
||||
|
||||
t.Run("should be able to patch values", func(t *testing.T) {
|
||||
patch := &model.ChannelPatch{
|
||||
Name: new(string),
|
||||
DisplayName: new(string),
|
||||
Header: new(string),
|
||||
Purpose: new(string),
|
||||
}
|
||||
*patch.Name = model.NewId()
|
||||
*patch.DisplayName = model.NewId()
|
||||
*patch.Header = model.NewId()
|
||||
*patch.Purpose = model.NewId()
|
||||
|
||||
channel, _, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, *patch.Name, channel.Name, "do not match")
|
||||
require.Equal(t, *patch.DisplayName, channel.DisplayName, "do not match")
|
||||
require.Equal(t, *patch.Header, channel.Header, "do not match")
|
||||
require.Equal(t, *patch.Purpose, channel.Purpose, "do not match")
|
||||
})
|
||||
|
||||
t.Run("should be able to patch with no name", func(t *testing.T) {
|
||||
channel := &model.Channel{
|
||||
DisplayName: GenerateTestChannelName(),
|
||||
Name: GenerateTestChannelName(),
|
||||
Type: model.ChannelTypeOpen,
|
||||
TeamId: team.Id,
|
||||
}
|
||||
var err error
|
||||
channel, _, err = client.CreateChannel(context.Background(), channel)
|
||||
require.NoError(t, err)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
Header: new(string),
|
||||
Purpose: new(string),
|
||||
}
|
||||
|
||||
oldName := channel.Name
|
||||
patchedChannel, _, err := client.PatchChannel(context.Background(), channel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, oldName, patchedChannel.Name, "should not have updated")
|
||||
})
|
||||
|
||||
t.Run("Test updating default channel's name and returns error", func(t *testing.T) {
|
||||
// Test updating default channel's name and returns error
|
||||
defaultChannel, appErr := th.App.GetChannelByName(th.Context, model.DefaultChannelName, team.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
defaultChannelPatch := &model.ChannelPatch{
|
||||
Name: new(string),
|
||||
}
|
||||
*defaultChannelPatch.Name = "testing"
|
||||
_, resp, err := client.PatchChannel(context.Background(), defaultChannel.Id, defaultChannelPatch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Test updating the header of someone else's GM channel", func(t *testing.T) {
|
||||
// Test updating the header of someone else's GM channel.
|
||||
@@ -1137,6 +1147,368 @@ func TestPatchChannel(t *testing.T) {
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Nil(t, patchedChannel)
|
||||
})
|
||||
|
||||
t.Run("Patch channel with no changes returns 400", func(t *testing.T) {
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
patch := &model.ChannelPatch{}
|
||||
_, resp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Patch channel with autotranslation when feature is available properly updates the channel for admins", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(true)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := th.SystemAdminClient.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginSystemAdmin(t)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
_, resp, err := th.SystemAdminClient.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
patchedChannel, appErr := th.App.GetChannel(th.Context, th.BasicChannel.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.True(t, patchedChannel.AutoTranslation)
|
||||
|
||||
patch = &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(false),
|
||||
}
|
||||
|
||||
_, resp, err = th.SystemAdminClient.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
patchedChannel, appErr = th.App.GetChannel(th.Context, th.BasicChannel.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.False(t, patchedChannel.AutoTranslation)
|
||||
})
|
||||
|
||||
t.Run("Patch channel with autotranslation when feature is available properly updates the channel for users only with the proper permissions", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(true)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
privateChannel := th.CreateChannelWithClient(t, th.SystemAdminClient, model.ChannelTypePrivate)
|
||||
th.AddUserToChannel(t, th.BasicUser, privateChannel)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
_, resp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), privateChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
th.AddPermissionToRole(t, model.PermissionManagePrivateChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
defer th.RemovePermissionFromRole(t, model.PermissionManagePrivateChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, _, err = client.PatchChannel(context.Background(), privateChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
patchedChannel, appErr := th.App.GetChannel(th.Context, privateChannel.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.True(t, patchedChannel.AutoTranslation)
|
||||
|
||||
th.AddPermissionToRole(t, model.PermissionManagePublicChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
defer th.RemovePermissionFromRole(t, model.PermissionManagePublicChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
|
||||
_, _, err = client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
patchedChannel, appErr = th.App.GetChannel(th.Context, privateChannel.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.True(t, patchedChannel.AutoTranslation)
|
||||
})
|
||||
|
||||
t.Run("Patch channel with AutoTranslation when feature not available returns 403", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(false)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
_, resp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
var appErr *model.AppError
|
||||
require.True(t, errors.As(err, &appErr))
|
||||
require.Contains(t, []string{"api.channel.patch_update_channel.feature_not_available.app_error", "api.channel.patch_update_channel.auto_translation_restricted.app_error"}, appErr.Id)
|
||||
})
|
||||
|
||||
t.Run("Patch channel with autotranslation on DM is only available for members", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(true)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
dmChannel, resp, err := client.CreateDirectChannel(context.Background(), th.BasicUser.Id, th.BasicUser2.Id)
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
nonMemberDmChannel, resp, err := th.SystemAdminClient.CreateDirectChannel(context.Background(), th.BasicUser2.Id, th.SystemAdminUser.Id)
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), dmChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
patchedChannel, appErr := th.App.GetChannel(th.Context, dmChannel.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.True(t, patchedChannel.AutoTranslation)
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), nonMemberDmChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Patch channel with autotranslation on GM is only available for members", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(true)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
user3 := th.CreateUser(t)
|
||||
|
||||
gmChannel, resp, err := client.CreateGroupChannel(context.Background(), []string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
nonMemberGmChannel, resp, err := th.SystemAdminClient.CreateGroupChannel(context.Background(), []string{th.BasicUser2.Id, th.SystemAdminUser.Id, user3.Id})
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), gmChannel.Id, patch)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
patchedChannel, appErr := th.App.GetChannel(th.Context, gmChannel.Id)
|
||||
require.Nil(t, appErr)
|
||||
require.True(t, patchedChannel.AutoTranslation)
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), nonMemberGmChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Patch DM with AutoTranslation when RestrictDMAndGM is true returns 403", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(true)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.AutoTranslationSettings.RestrictDMAndGM = true
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.AutoTranslationSettings.RestrictDMAndGM = false
|
||||
})
|
||||
|
||||
dmChannel, resp, err := client.CreateDirectChannel(context.Background(), th.BasicUser.Id, th.BasicUser2.Id)
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), dmChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
// May be feature_not_available when AutoTranslation is nil, or auto_translation_restricted when RestrictDMAndGM applies
|
||||
var appErr *model.AppError
|
||||
require.True(t, errors.As(err, &appErr))
|
||||
require.Contains(t, []string{"api.channel.patch_update_channel.feature_not_available.app_error", "api.channel.patch_update_channel.auto_translation_restricted.app_error"}, appErr.Id)
|
||||
})
|
||||
|
||||
t.Run("Patch GM with AutoTranslation when RestrictDMAndGM is true returns 403", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(true)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.AutoTranslationSettings.RestrictDMAndGM = true
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.AutoTranslationSettings.RestrictDMAndGM = false
|
||||
})
|
||||
|
||||
user3 := th.CreateUser(t)
|
||||
gmChannel, resp, err := client.CreateGroupChannel(context.Background(), []string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), gmChannel.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
// May be feature_not_available when AutoTranslation is nil, or auto_translation_restricted when RestrictDMAndGM applies
|
||||
var appErr *model.AppError
|
||||
require.True(t, errors.As(err, &appErr))
|
||||
require.Contains(t, []string{"api.channel.patch_update_channel.feature_not_available.app_error", "api.channel.patch_update_channel.auto_translation_restricted.app_error"}, appErr.Id)
|
||||
})
|
||||
|
||||
t.Run("Mixed patch only gets through if all permissions are met", func(t *testing.T) {
|
||||
mockAutoTranslation := &einterfacesmocks.AutoTranslationInterface{}
|
||||
mockAutoTranslation.On("IsFeatureAvailable").Return(true)
|
||||
mockAutoTranslation.On("IsChannelEnabled", mock.Anything).Return(true, nil)
|
||||
mockAutoTranslation.On("Translate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
originalAutoTranslation := th.Server.AutoTranslation
|
||||
th.Server.AutoTranslation = mockAutoTranslation
|
||||
defer func() {
|
||||
th.Server.AutoTranslation = originalAutoTranslation
|
||||
}()
|
||||
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
th.LoginBasic(t)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
defer func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
}()
|
||||
|
||||
// Mixed patch (channel property + AutoTranslation) fails when user lacks AutoTranslation permission
|
||||
newHeader := "mixed patch header"
|
||||
mixedPatch := &model.ChannelPatch{
|
||||
Header: &newHeader,
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
BannerInfo: &model.ChannelBannerInfo{
|
||||
Enabled: model.NewPointer(false),
|
||||
Text: model.NewPointer("mixed patch banner"),
|
||||
},
|
||||
}
|
||||
|
||||
// Permissions missing: AutoTranslation, BannerInfo
|
||||
_, resp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, mixedPatch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Permissions missing: Channel properties
|
||||
th.AddPermissionToRole(t, model.PermissionManagePublicChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
defer th.RemovePermissionFromRole(t, model.PermissionManagePublicChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
th.AddPermissionToRole(t, model.PermissionManagePublicChannelBanner.Id, model.SystemUserRoleId)
|
||||
defer th.RemovePermissionFromRole(t, model.PermissionManagePublicChannelBanner.Id, model.SystemUserRoleId)
|
||||
th.RemovePermissionFromRole(t, model.PermissionManagePublicChannelProperties.Id, model.ChannelUserRoleId)
|
||||
defer th.AddPermissionToRole(t, model.PermissionManagePublicChannelProperties.Id, model.ChannelUserRoleId)
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), th.BasicChannel.Id, mixedPatch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Permissions missing: AutoTranslation
|
||||
th.AddPermissionToRole(t, model.PermissionManagePublicChannelProperties.Id, model.ChannelUserRoleId)
|
||||
th.RemovePermissionFromRole(t, model.PermissionManagePublicChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
|
||||
_, resp, err = client.PatchChannel(context.Background(), th.BasicChannel.Id, mixedPatch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Permission missing: BannerInfo
|
||||
th.AddPermissionToRole(t, model.PermissionManagePublicChannelAutoTranslation.Id, model.SystemUserRoleId)
|
||||
th.RemovePermissionFromRole(t, model.PermissionManagePublicChannelBanner.Id, model.SystemUserRoleId)
|
||||
_, resp, err = client.PatchChannel(context.Background(), th.BasicChannel.Id, mixedPatch)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// No missing permissions
|
||||
th.AddPermissionToRole(t, model.PermissionManagePublicChannelBanner.Id, model.SystemUserRoleId)
|
||||
|
||||
patchedChannel, resp, err := client.PatchChannel(context.Background(), th.BasicChannel.Id, mixedPatch)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
require.Equal(t, newHeader, patchedChannel.Header)
|
||||
require.True(t, patchedChannel.AutoTranslation)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCanEditChannelBanner(t *testing.T) {
|
||||
|
||||
@@ -148,6 +148,8 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
|
||||
model.PermissionManagePublicChannelBanner.Id,
|
||||
model.PermissionManagePrivateChannelBanner.Id,
|
||||
model.PermissionManageChannelAccessRules.Id,
|
||||
model.PermissionManagePublicChannelAutoTranslation.Id,
|
||||
model.PermissionManagePrivateChannelAutoTranslation.Id,
|
||||
},
|
||||
"team_user": {
|
||||
model.PermissionListTeamChannels.Id,
|
||||
|
||||
@@ -948,6 +948,7 @@ func (a *App) PatchChannel(rctx request.CTX, channel *model.Channel, patch *mode
|
||||
oldChannelDisplayName := channel.DisplayName
|
||||
oldChannelHeader := channel.Header
|
||||
oldChannelPurpose := channel.Purpose
|
||||
oldChannelAutotranslation := channel.AutoTranslation
|
||||
|
||||
channel.Patch(patch)
|
||||
a.handleChannelCategoryName(channel)
|
||||
@@ -976,6 +977,12 @@ func (a *App) PatchChannel(rctx request.CTX, channel *model.Channel, patch *mode
|
||||
}
|
||||
}
|
||||
|
||||
if channel.AutoTranslation != oldChannelAutotranslation {
|
||||
if err = a.postUpdateChannelAutotranslationMessage(rctx, userID, channel, channel.AutoTranslation); err != nil {
|
||||
rctx.Logger().Warn(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
@@ -1983,6 +1990,37 @@ func (a *App) PostUpdateChannelPurposeMessage(rctx request.CTX, userID string, c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) postUpdateChannelAutotranslationMessage(rctx request.CTX, userID string, channel *model.Channel, newChannelAutotranslation bool) *model.AppError {
|
||||
user, err := a.Srv().Store().User().Get(context.Background(), userID)
|
||||
if err != nil {
|
||||
return model.NewAppError("PostUpdateChannelAutotranslationMessage", "api.channel.post_update_channel_autotranslation_message.retrieve_user.error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
var message string
|
||||
if newChannelAutotranslation {
|
||||
message = fmt.Sprintf(i18n.T("api.channel.post_update_channel_autotranslation_message.enabled"), user.Username)
|
||||
} else {
|
||||
message = fmt.Sprintf(i18n.T("api.channel.post_update_channel_autotranslation_message.disabled"), user.Username)
|
||||
}
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: message,
|
||||
Type: model.PostTypeAutotranslationChange,
|
||||
UserId: userID,
|
||||
Props: model.StringInterface{
|
||||
"username": user.Username,
|
||||
"enabled": newChannelAutotranslation,
|
||||
},
|
||||
}
|
||||
|
||||
if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil {
|
||||
return model.NewAppError("PostUpdateChannelAutotranslationMessage", "api.channel.post_update_channel_autotranslation_message.create_post.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) PostUpdateChannelDisplayNameMessage(rctx request.CTX, userID string, channel *model.Channel, oldChannelDisplayName, newChannelDisplayName string) *model.AppError {
|
||||
user, err := a.Srv().Store().User().Get(context.Background(), userID)
|
||||
if err != nil {
|
||||
|
||||
@@ -3746,6 +3746,40 @@ func TestPatchChannel(t *testing.T) {
|
||||
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Patch channel with autotranslations post a message to the channel", func(t *testing.T) {
|
||||
channel := th.createChannel(t, th.BasicTeam, model.ChannelTypeOpen)
|
||||
|
||||
patch := &model.ChannelPatch{
|
||||
AutoTranslation: model.NewPointer(true),
|
||||
}
|
||||
|
||||
patchedChannel, appErr := th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
||||
require.Nil(t, appErr)
|
||||
require.True(t, patchedChannel.AutoTranslation)
|
||||
|
||||
posts, appErr := th.App.GetPosts(th.Context, channel.Id, 0, 1)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, posts)
|
||||
systemPost := posts.Posts[posts.Order[0]]
|
||||
require.Equal(t, model.PostTypeAutotranslationChange, systemPost.Type)
|
||||
require.Equal(t, th.BasicUser.Username, systemPost.GetProp("username"))
|
||||
require.Equal(t, true, systemPost.GetProp("enabled"))
|
||||
|
||||
patch.AutoTranslation = model.NewPointer(false)
|
||||
|
||||
patchedChannel, appErr = th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
||||
require.Nil(t, appErr)
|
||||
require.False(t, patchedChannel.AutoTranslation)
|
||||
|
||||
posts, appErr = th.App.GetPosts(th.Context, channel.Id, 0, 1)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, posts)
|
||||
systemPost = posts.Posts[posts.Order[0]]
|
||||
require.Equal(t, model.PostTypeAutotranslationChange, systemPost.Type)
|
||||
require.Equal(t, th.BasicUser.Username, systemPost.GetProp("username"))
|
||||
require.Equal(t, false, systemPost.GetProp("enabled"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateChannelWithCategorySorting(t *testing.T) {
|
||||
|
||||
@@ -21,64 +21,66 @@ type permissionTransformation struct {
|
||||
type permissionsMap []permissionTransformation
|
||||
|
||||
const (
|
||||
PermissionManageSystem = "manage_system"
|
||||
PermissionManageTeam = "manage_team"
|
||||
PermissionManageEmojis = "manage_emojis"
|
||||
PermissionManageOthersEmojis = "manage_others_emojis"
|
||||
PermissionCreateEmojis = "create_emojis"
|
||||
PermissionDeleteEmojis = "delete_emojis"
|
||||
PermissionDeleteOthersEmojis = "delete_others_emojis"
|
||||
PermissionManageWebhooks = "manage_webhooks"
|
||||
PermissionManageOthersWebhooks = "manage_others_webhooks"
|
||||
PermissionManageIncomingWebhooks = "manage_incoming_webhooks"
|
||||
PermissionManageOwnIncomingWebhooks = "manage_own_incoming_webhooks"
|
||||
PermissionManageOthersIncomingWebhooks = "manage_others_incoming_webhooks"
|
||||
PermissionManageOutgoingWebhooks = "manage_outgoing_webhooks"
|
||||
PermissionManageOwnOutgoingWebhooks = "manage_own_outgoing_webhooks"
|
||||
PermissionManageOthersOutgoingWebhooks = "manage_others_outgoing_webhooks"
|
||||
PermissionBypassIncomingWebhookChannelLock = "bypass_incoming_webhook_channel_lock"
|
||||
PermissionListPublicTeams = "list_public_teams"
|
||||
PermissionListPrivateTeams = "list_private_teams"
|
||||
PermissionJoinPublicTeams = "join_public_teams"
|
||||
PermissionJoinPrivateTeams = "join_private_teams"
|
||||
PermissionPermanentDeleteUser = "permanent_delete_user"
|
||||
PermissionCreateBot = "create_bot"
|
||||
PermissionReadBots = "read_bots"
|
||||
PermissionReadOthersBots = "read_others_bots"
|
||||
PermissionManageBots = "manage_bots"
|
||||
PermissionManageOthersBots = "manage_others_bots"
|
||||
PermissionManageSlashCommands = "manage_slash_commands"
|
||||
PermissionManageOwnSlashCommands = "manage_own_slash_commands"
|
||||
PermissionDeletePublicChannel = "delete_public_channel"
|
||||
PermissionDeletePrivateChannel = "delete_private_channel"
|
||||
PermissionManagePublicChannelProperties = "manage_public_channel_properties"
|
||||
PermissionManagePrivateChannelProperties = "manage_private_channel_properties"
|
||||
PermissionConvertPublicChannelToPrivate = "convert_public_channel_to_private"
|
||||
PermissionConvertPrivateChannelToPublic = "convert_private_channel_to_public"
|
||||
PermissionViewMembers = "view_members"
|
||||
PermissionInviteUser = "invite_user"
|
||||
PermissionInviteGuest = "invite_guest"
|
||||
PermissionPromoteGuest = "promote_guest"
|
||||
PermissionDemoteToGuest = "demote_to_guest"
|
||||
PermissionUseChannelMentions = "use_channel_mentions"
|
||||
PermissionCreatePost = "create_post"
|
||||
PermissionCreatePost_PUBLIC = "create_post_public"
|
||||
PermissionUseGroupMentions = "use_group_mentions"
|
||||
PermissionAddReaction = "add_reaction"
|
||||
PermissionRemoveReaction = "remove_reaction"
|
||||
PermissionManagePublicChannelMembers = "manage_public_channel_members"
|
||||
PermissionManagePrivateChannelMembers = "manage_private_channel_members"
|
||||
PermissionReadJobs = "read_jobs"
|
||||
PermissionManageJobs = "manage_jobs"
|
||||
PermissionReadOtherUsersTeams = "read_other_users_teams"
|
||||
PermissionEditOtherUsers = "edit_other_users"
|
||||
PermissionReadPublicChannelGroups = "read_public_channel_groups"
|
||||
PermissionReadPrivateChannelGroups = "read_private_channel_groups"
|
||||
PermissionEditBrand = "edit_brand"
|
||||
PermissionManageSharedChannels = "manage_shared_channels"
|
||||
PermissionManageSecureConnections = "manage_secure_connections"
|
||||
PermissionManageOAuth = "manage_oauth"
|
||||
PermissionManageRemoteClusters = "manage_remote_clusters" // deprecated; use `manage_secure_connections`
|
||||
PermissionManageSystem = "manage_system"
|
||||
PermissionManageTeam = "manage_team"
|
||||
PermissionManageEmojis = "manage_emojis"
|
||||
PermissionManageOthersEmojis = "manage_others_emojis"
|
||||
PermissionCreateEmojis = "create_emojis"
|
||||
PermissionDeleteEmojis = "delete_emojis"
|
||||
PermissionDeleteOthersEmojis = "delete_others_emojis"
|
||||
PermissionManageWebhooks = "manage_webhooks"
|
||||
PermissionManageOthersWebhooks = "manage_others_webhooks"
|
||||
PermissionManageIncomingWebhooks = "manage_incoming_webhooks"
|
||||
PermissionManageOwnIncomingWebhooks = "manage_own_incoming_webhooks"
|
||||
PermissionManageOthersIncomingWebhooks = "manage_others_incoming_webhooks"
|
||||
PermissionManageOutgoingWebhooks = "manage_outgoing_webhooks"
|
||||
PermissionManageOwnOutgoingWebhooks = "manage_own_outgoing_webhooks"
|
||||
PermissionManageOthersOutgoingWebhooks = "manage_others_outgoing_webhooks"
|
||||
PermissionBypassIncomingWebhookChannelLock = "bypass_incoming_webhook_channel_lock"
|
||||
PermissionListPublicTeams = "list_public_teams"
|
||||
PermissionListPrivateTeams = "list_private_teams"
|
||||
PermissionJoinPublicTeams = "join_public_teams"
|
||||
PermissionJoinPrivateTeams = "join_private_teams"
|
||||
PermissionPermanentDeleteUser = "permanent_delete_user"
|
||||
PermissionCreateBot = "create_bot"
|
||||
PermissionReadBots = "read_bots"
|
||||
PermissionReadOthersBots = "read_others_bots"
|
||||
PermissionManageBots = "manage_bots"
|
||||
PermissionManageOthersBots = "manage_others_bots"
|
||||
PermissionManageSlashCommands = "manage_slash_commands"
|
||||
PermissionManageOwnSlashCommands = "manage_own_slash_commands"
|
||||
PermissionDeletePublicChannel = "delete_public_channel"
|
||||
PermissionDeletePrivateChannel = "delete_private_channel"
|
||||
PermissionManagePublicChannelProperties = "manage_public_channel_properties"
|
||||
PermissionManagePrivateChannelProperties = "manage_private_channel_properties"
|
||||
PermissionManagePublicChannelAutoTranslation = "manage_public_channel_auto_translation"
|
||||
PermissionManagePrivateChannelAutoTranslation = "manage_private_channel_auto_translation"
|
||||
PermissionConvertPublicChannelToPrivate = "convert_public_channel_to_private"
|
||||
PermissionConvertPrivateChannelToPublic = "convert_private_channel_to_public"
|
||||
PermissionViewMembers = "view_members"
|
||||
PermissionInviteUser = "invite_user"
|
||||
PermissionInviteGuest = "invite_guest"
|
||||
PermissionPromoteGuest = "promote_guest"
|
||||
PermissionDemoteToGuest = "demote_to_guest"
|
||||
PermissionUseChannelMentions = "use_channel_mentions"
|
||||
PermissionCreatePost = "create_post"
|
||||
PermissionCreatePost_PUBLIC = "create_post_public"
|
||||
PermissionUseGroupMentions = "use_group_mentions"
|
||||
PermissionAddReaction = "add_reaction"
|
||||
PermissionRemoveReaction = "remove_reaction"
|
||||
PermissionManagePublicChannelMembers = "manage_public_channel_members"
|
||||
PermissionManagePrivateChannelMembers = "manage_private_channel_members"
|
||||
PermissionReadJobs = "read_jobs"
|
||||
PermissionManageJobs = "manage_jobs"
|
||||
PermissionReadOtherUsersTeams = "read_other_users_teams"
|
||||
PermissionEditOtherUsers = "edit_other_users"
|
||||
PermissionReadPublicChannelGroups = "read_public_channel_groups"
|
||||
PermissionReadPrivateChannelGroups = "read_private_channel_groups"
|
||||
PermissionEditBrand = "edit_brand"
|
||||
PermissionManageSharedChannels = "manage_shared_channels"
|
||||
PermissionManageSecureConnections = "manage_secure_connections"
|
||||
PermissionManageOAuth = "manage_oauth"
|
||||
PermissionManageRemoteClusters = "manage_remote_clusters" // deprecated; use `manage_secure_connections`
|
||||
)
|
||||
|
||||
// Deprecated: This function should only be used if a case arises where team and/or channel scheme roles do not need to be migrated.
|
||||
@@ -1231,6 +1233,22 @@ func (a *App) getAddChannelAccessRulesPermissionMigration() (permissionsMap, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddChannelAutoTranslationPermissionMigration() (permissionsMap, error) {
|
||||
return permissionsMap{
|
||||
permissionTransformation{
|
||||
On: permissionOr(
|
||||
isRole(model.ChannelAdminRoleId),
|
||||
isRole(model.TeamAdminRoleId),
|
||||
isRole(model.SystemAdminRoleId),
|
||||
),
|
||||
Add: []string{
|
||||
model.PermissionManagePublicChannelAutoTranslation.Id,
|
||||
model.PermissionManagePrivateChannelAutoTranslation.Id,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Only sysadmins, team admins, and users with channels and groups managements have access to "convert channel to public"
|
||||
func (a *App) getRestrictAcessToChannelConversionToPublic() (permissionsMap, error) {
|
||||
return []permissionTransformation{
|
||||
@@ -1304,6 +1322,7 @@ func (s *Server) doPermissionsMigrations() error {
|
||||
{Key: model.MigrationAddSysconsoleMobileSecurityPermission, Migration: a.addSysConsoleMobileSecurityPermission},
|
||||
{Key: model.MigrationKeyAddChannelBannerPermissions, Migration: a.getAddChannelBannerPermissionMigration},
|
||||
{Key: model.MigrationKeyAddChannelAccessRulesPermission, Migration: a.getAddChannelAccessRulesPermissionMigration},
|
||||
{Key: model.MigrationKeyAddChannelAutoTranslationPermissions, Migration: a.getAddChannelAutoTranslationPermissionMigration},
|
||||
}
|
||||
|
||||
roles, err := s.Store().Role().GetAll()
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
@@ -45,31 +43,6 @@ func (s LocalCacheAutoTranslationStore) ClearCaches() {
|
||||
}
|
||||
}
|
||||
|
||||
// IsChannelEnabled checks if auto-translation is enabled for a channel
|
||||
// Uses the existing Channel cache instead of maintaining a separate cache
|
||||
func (s LocalCacheAutoTranslationStore) IsChannelEnabled(channelID string) (bool, error) {
|
||||
// Get channel from cache (with DB fallback)
|
||||
channel, err := s.rootStore.Channel().Get(channelID, true)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to get channel for auto-translation check, channel_id=%s", channelID)
|
||||
}
|
||||
|
||||
return channel.AutoTranslation, nil
|
||||
}
|
||||
|
||||
// SetChannelEnabled sets auto-translation status for a channel and invalidates Channel cache
|
||||
func (s LocalCacheAutoTranslationStore) SetChannelEnabled(channelID string, enabled bool) error {
|
||||
err := s.AutoTranslationStore.SetChannelEnabled(channelID, enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Invalidate the Channel cache since we modified channel.autotranslation
|
||||
s.rootStore.Channel().InvalidateChannel(channelID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsUserEnabled checks if auto-translation is enabled for a user in a channel (with caching)
|
||||
func (s LocalCacheAutoTranslationStore) IsUserEnabled(userID, channelID string) (bool, error) {
|
||||
key := userAutoTranslationKey(userID, channelID)
|
||||
|
||||
@@ -1031,27 +1031,6 @@ func (s *RetryLayerAutoTranslationStore) InvalidateUserLocaleCache(userID string
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAutoTranslationStore) IsChannelEnabled(channelID string) (bool, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.AutoTranslationStore.IsChannelEnabled(channelID)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAutoTranslationStore) IsUserEnabled(userID string, channelID string) (bool, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -1094,27 +1073,6 @@ func (s *RetryLayerAutoTranslationStore) Save(translation *model.Translation) er
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAutoTranslationStore) SetChannelEnabled(channelID string, enabled bool) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.AutoTranslationStore.SetChannelEnabled(channelID, enabled)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerBotStore) Get(userID string, includeDeleted bool) (*model.Bot, error) {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -51,61 +51,13 @@ func newSqlAutoTranslationStore(sqlStore *SqlStore) store.AutoTranslationStore {
|
||||
}
|
||||
}
|
||||
|
||||
// IsChannelEnabled checks if auto-translation is enabled for a channel
|
||||
// Uses the existing Channel cache instead of maintaining a separate cache
|
||||
// Thus this method is really for completeness; callers should use the Channel cache
|
||||
func (s *SqlAutoTranslationStore) IsChannelEnabled(channelID string) (bool, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("AutoTranslation").
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Id": channelID})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to build query for IsChannelEnabled")
|
||||
}
|
||||
|
||||
var enabled bool
|
||||
if err := s.GetReplica().Get(&enabled, queryString, args...); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false, store.NewErrNotFound("Channel", channelID)
|
||||
}
|
||||
return false, errors.Wrapf(err, "failed to get channel enabled status for channel_id=%s", channelID)
|
||||
}
|
||||
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
func (s *SqlAutoTranslationStore) SetChannelEnabled(channelID string, enabled bool) error {
|
||||
query := s.getQueryBuilder().
|
||||
Update("Channels").
|
||||
Set("AutoTranslation", enabled).
|
||||
Set("UpdateAt", model.GetMillis()).
|
||||
Where(sq.Eq{"Id": channelID})
|
||||
|
||||
result, err := s.GetMaster().ExecBuilder(query)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to set channel enabled for channel_id=%s", channelID)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get rows affected for SetChannelEnabled")
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return store.NewErrNotFound("Channel", channelID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlAutoTranslationStore) IsUserEnabled(userID, channelID string) (bool, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("cm.AutoTranslationDisabled").
|
||||
From("ChannelMembers cm").
|
||||
Join("Channels c ON cm.Channelid = c.id").
|
||||
Join("Channels c ON cm.ChannelId = c.Id").
|
||||
Where(sq.Eq{"cm.UserId": userID, "cm.ChannelId": channelID}).
|
||||
Where("cm.AutoTranslationDisabled != true").
|
||||
Where("c.AutoTranslation = true")
|
||||
|
||||
var disabled bool
|
||||
@@ -127,7 +79,7 @@ func (s *SqlAutoTranslationStore) GetUserLanguage(userID, channelID string) (str
|
||||
Join("Channels c ON cm.ChannelId = c.Id").
|
||||
Where(sq.Eq{"u.Id": userID, "c.Id": channelID}).
|
||||
Where("c.AutoTranslation = true").
|
||||
Where("cm.AutoTranslationDisabled = false")
|
||||
Where("cm.AutoTranslationDisabled != true")
|
||||
|
||||
var locale string
|
||||
if err := s.GetReplica().GetBuilder(&locale, query); err != nil {
|
||||
@@ -148,7 +100,7 @@ func (s *SqlAutoTranslationStore) GetActiveDestinationLanguages(channelID, exclu
|
||||
Join("Users u ON u.Id = cm.UserId").
|
||||
Where(sq.Eq{"cm.ChannelId": channelID}).
|
||||
Where("c.AutoTranslation = true").
|
||||
Where("cm.AutoTranslationDisabled = false")
|
||||
Where("cm.AutoTranslationDisabled != true")
|
||||
|
||||
// Filter to specific user IDs if provided (e.g., users with active WebSocket connections)
|
||||
// When filterUserIDs is non-nil and non-empty, squirrel converts it to an IN clause
|
||||
|
||||
@@ -813,7 +813,8 @@ func (s SqlChannelStore) updateChannelT(transaction *sqlxTxWrapper, channel *mod
|
||||
TotalMsgCountRoot=:TotalMsgCountRoot,
|
||||
LastRootPostAt=:LastRootPostAt,
|
||||
BannerInfo=:BannerInfo,
|
||||
DefaultCategoryName=:DefaultCategoryName
|
||||
DefaultCategoryName=:DefaultCategoryName,
|
||||
AutoTranslation=:AutoTranslation
|
||||
WHERE Id=:Id`, channel)
|
||||
if err != nil {
|
||||
if IsUniqueConstraintError(err, []string{"Name", "channels_name_teamid_key"}) {
|
||||
|
||||
@@ -1158,8 +1158,6 @@ type AttributesStore interface {
|
||||
}
|
||||
|
||||
type AutoTranslationStore interface {
|
||||
IsChannelEnabled(channelID string) (bool, error)
|
||||
SetChannelEnabled(channelID string, enabled bool) error
|
||||
IsUserEnabled(userID, channelID string) (bool, error)
|
||||
GetUserLanguage(userID, channelID string) (string, error)
|
||||
// GetActiveDestinationLanguages returns distinct locales of users who have auto-translation enabled.
|
||||
|
||||
@@ -15,142 +15,11 @@ import (
|
||||
)
|
||||
|
||||
func TestAutoTranslationStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("IsChannelEnabled", func(t *testing.T) { testAutoTranslationIsChannelEnabled(t, rctx, ss) })
|
||||
t.Run("SetChannelEnabled", func(t *testing.T) { testAutoTranslationSetChannelEnabled(t, rctx, ss) })
|
||||
t.Run("IsUserEnabled", func(t *testing.T) { testAutoTranslationIsUserEnabled(t, rctx, ss) })
|
||||
t.Run("GetUserLanguage", func(t *testing.T) { testAutoTranslationGetUserLanguage(t, rctx, ss) })
|
||||
t.Run("GetActiveDestinationLanguages", func(t *testing.T) { testAutoTranslationGetActiveDestinationLanguages(t, rctx, ss) })
|
||||
}
|
||||
|
||||
func testAutoTranslationIsChannelEnabled(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
// Setup: Create a test team and channel
|
||||
team := &model.Team{
|
||||
DisplayName: "Test Team",
|
||||
Name: "test-team-" + model.NewId(),
|
||||
Email: "test@example.com",
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
team, err := ss.Team().Save(team)
|
||||
require.NoError(t, err)
|
||||
|
||||
channel := &model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "Test Channel",
|
||||
Name: "test-channel-" + model.NewId(),
|
||||
Type: model.ChannelTypeOpen,
|
||||
}
|
||||
channel, nErr := ss.Channel().Save(rctx, channel, 999)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
defer func() {
|
||||
_ = ss.Team().PermanentDelete(team.Id)
|
||||
_ = ss.Channel().PermanentDelete(rctx, channel.Id)
|
||||
}()
|
||||
|
||||
t.Run("default value is false", func(t *testing.T) {
|
||||
enabled, appErr := ss.AutoTranslation().IsChannelEnabled(channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
assert.False(t, enabled, "autotranslation should be disabled by default")
|
||||
})
|
||||
|
||||
t.Run("returns true after enabling", func(t *testing.T) {
|
||||
// Enable autotranslation
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
|
||||
// Verify it's enabled
|
||||
enabled, appErr := ss.AutoTranslation().IsChannelEnabled(channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
assert.True(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("returns false after disabling", func(t *testing.T) {
|
||||
// Disable autotranslation
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, false)
|
||||
require.NoError(t, appErr)
|
||||
|
||||
// Verify it's disabled
|
||||
enabled, appErr := ss.AutoTranslation().IsChannelEnabled(channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
assert.False(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("returns error for non-existent channel", func(t *testing.T) {
|
||||
enabled, err := ss.AutoTranslation().IsChannelEnabled("nonexistent")
|
||||
assert.Error(t, err)
|
||||
assert.True(t, store.IsErrNotFound(err))
|
||||
assert.False(t, enabled)
|
||||
})
|
||||
}
|
||||
|
||||
func testAutoTranslationSetChannelEnabled(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
// Setup: Create a test team and channel
|
||||
team := &model.Team{
|
||||
DisplayName: "Test Team",
|
||||
Name: "test-team-" + model.NewId(),
|
||||
Email: "test@example.com",
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
team, err := ss.Team().Save(team)
|
||||
require.NoError(t, err)
|
||||
|
||||
channel := &model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "Test Channel",
|
||||
Name: "test-channel-" + model.NewId(),
|
||||
Type: model.ChannelTypeOpen,
|
||||
}
|
||||
channel, nErr := ss.Channel().Save(rctx, channel, 999)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
defer func() {
|
||||
_ = ss.Team().PermanentDelete(team.Id)
|
||||
_ = ss.Channel().PermanentDelete(rctx, channel.Id)
|
||||
}()
|
||||
|
||||
t.Run("successfully enables autotranslation", func(t *testing.T) {
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
|
||||
// Verify via IsChannelEnabled
|
||||
enabled, appErr := ss.AutoTranslation().IsChannelEnabled(channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
assert.True(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("successfully disables autotranslation", func(t *testing.T) {
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, false)
|
||||
require.NoError(t, appErr)
|
||||
|
||||
// Verify via IsChannelEnabled
|
||||
enabled, appErr := ss.AutoTranslation().IsChannelEnabled(channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
assert.False(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("updates channel timestamp", func(t *testing.T) {
|
||||
// Get original update timestamp
|
||||
originalChannel, nErr := ss.Channel().Get(channel.Id, true)
|
||||
require.NoError(t, nErr)
|
||||
originalUpdateAt := originalChannel.UpdateAt
|
||||
|
||||
// Enable autotranslation
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
|
||||
// Verify timestamp was updated
|
||||
updatedChannel, nErr := ss.Channel().Get(channel.Id, true)
|
||||
require.NoError(t, nErr)
|
||||
assert.Greater(t, updatedChannel.UpdateAt, originalUpdateAt)
|
||||
})
|
||||
|
||||
t.Run("returns error for non-existent channel", func(t *testing.T) {
|
||||
err := ss.AutoTranslation().SetChannelEnabled("nonexistent", true)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, store.IsErrNotFound(err))
|
||||
})
|
||||
}
|
||||
|
||||
func testAutoTranslationIsUserEnabled(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
// Setup: Create team, channel, and user
|
||||
team := &model.Team{
|
||||
@@ -196,61 +65,64 @@ func testAutoTranslationIsUserEnabled(t *testing.T, rctx request.CTX, ss store.S
|
||||
|
||||
t.Run("returns false when channel is disabled", func(t *testing.T) {
|
||||
// Channel autotranslation is disabled by default
|
||||
enabled, appErr := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
enabled, err := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("returns false when channel enabled but user disabled", func(t *testing.T) {
|
||||
// Enable channel autotranslation
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Disable user autotranslation (AutoTranslationDisabled = true means disabled)
|
||||
member.AutoTranslationDisabled = true
|
||||
_, appErr = ss.Channel().UpdateMember(rctx, member)
|
||||
require.NoError(t, appErr)
|
||||
_, nErr = ss.Channel().UpdateMember(rctx, member)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
enabled, appErr := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
enabled, getUserEnabledErr := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, getUserEnabledErr)
|
||||
assert.False(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("returns true when both channel and user enabled", func(t *testing.T) {
|
||||
// Enable channel autotranslation
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Enable user autotranslation
|
||||
member.AutoTranslationDisabled = false
|
||||
_, appErr = ss.Channel().UpdateMember(rctx, member)
|
||||
require.NoError(t, appErr)
|
||||
_, nErr = ss.Channel().UpdateMember(rctx, member)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Verify both are enabled
|
||||
enabled, appErr := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
enabled, getUserEnabledErr := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, getUserEnabledErr)
|
||||
assert.True(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("returns false after disabling user", func(t *testing.T) {
|
||||
// Ensure channel is enabled
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Disable user autotranslation
|
||||
member.AutoTranslationDisabled = true
|
||||
_, appErr = ss.Channel().UpdateMember(rctx, member)
|
||||
require.NoError(t, appErr)
|
||||
_, nErr = ss.Channel().UpdateMember(rctx, member)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Verify user is disabled
|
||||
enabled, appErr := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
enabled, nErr := ss.AutoTranslation().IsUserEnabled(user.Id, channel.Id)
|
||||
require.NoError(t, nErr)
|
||||
assert.False(t, enabled)
|
||||
})
|
||||
|
||||
t.Run("returns false for non-existent user or channel", func(t *testing.T) {
|
||||
enabled, appErr := ss.AutoTranslation().IsUserEnabled("nonexistent", channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
enabled, nErr := ss.AutoTranslation().IsUserEnabled("nonexistent", channel.Id)
|
||||
require.NoError(t, nErr)
|
||||
assert.False(t, enabled)
|
||||
})
|
||||
}
|
||||
@@ -318,56 +190,59 @@ func testAutoTranslationGetUserLanguage(t *testing.T, rctx request.CTX, ss store
|
||||
})
|
||||
|
||||
t.Run("returns empty when channel enabled but user disabled", func(t *testing.T) {
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Disable user autotranslation (AutoTranslationDisabled = true means disabled)
|
||||
members[userEN.Id].AutoTranslationDisabled = true
|
||||
_, appErr = ss.Channel().UpdateMember(rctx, members[userEN.Id])
|
||||
require.NoError(t, appErr)
|
||||
_, nErr = ss.Channel().UpdateMember(rctx, members[userEN.Id])
|
||||
require.NoError(t, nErr)
|
||||
|
||||
locale, appErr := ss.AutoTranslation().GetUserLanguage(userEN.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
locale, getLocaleErr := ss.AutoTranslation().GetUserLanguage(userEN.Id, channel.Id)
|
||||
require.NoError(t, getLocaleErr)
|
||||
assert.Empty(t, locale)
|
||||
})
|
||||
|
||||
t.Run("returns user locale when both enabled", func(t *testing.T) {
|
||||
// Enable channel
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Enable user (set AutoTranslationDisabled = false)
|
||||
members[userEN.Id].AutoTranslationDisabled = false
|
||||
_, appErr = ss.Channel().UpdateMember(rctx, members[userEN.Id])
|
||||
require.NoError(t, appErr)
|
||||
_, nErr = ss.Channel().UpdateMember(rctx, members[userEN.Id])
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Get language
|
||||
locale, appErr := ss.AutoTranslation().GetUserLanguage(userEN.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
locale, getLocaleErr := ss.AutoTranslation().GetUserLanguage(userEN.Id, channel.Id)
|
||||
require.NoError(t, getLocaleErr)
|
||||
assert.Equal(t, "en", locale)
|
||||
})
|
||||
|
||||
t.Run("returns correct locale for different users", func(t *testing.T) {
|
||||
// Enable channel
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Enable both users (set AutoTranslationDisabled = false)
|
||||
members[userEN.Id].AutoTranslationDisabled = false
|
||||
_, appErr = ss.Channel().UpdateMember(rctx, members[userEN.Id])
|
||||
require.NoError(t, appErr)
|
||||
_, nErr = ss.Channel().UpdateMember(rctx, members[userEN.Id])
|
||||
require.NoError(t, nErr)
|
||||
members[userES.Id].AutoTranslationDisabled = false
|
||||
_, appErr = ss.Channel().UpdateMember(rctx, members[userES.Id])
|
||||
require.NoError(t, appErr)
|
||||
_, nErr = ss.Channel().UpdateMember(rctx, members[userES.Id])
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Verify English user
|
||||
locale, appErr := ss.AutoTranslation().GetUserLanguage(userEN.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
locale, nErr := ss.AutoTranslation().GetUserLanguage(userEN.Id, channel.Id)
|
||||
require.NoError(t, nErr)
|
||||
assert.Equal(t, "en", locale)
|
||||
|
||||
// Verify Spanish user
|
||||
locale, appErr = ss.AutoTranslation().GetUserLanguage(userES.Id, channel.Id)
|
||||
require.NoError(t, appErr)
|
||||
locale, nErr = ss.AutoTranslation().GetUserLanguage(userES.Id, channel.Id)
|
||||
require.NoError(t, nErr)
|
||||
assert.Equal(t, "es", locale)
|
||||
})
|
||||
}
|
||||
@@ -435,8 +310,9 @@ func testAutoTranslationGetActiveDestinationLanguages(t *testing.T, rctx request
|
||||
|
||||
t.Run("returns all enabled user languages", func(t *testing.T) {
|
||||
// Enable channel
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
languages, appErr := ss.AutoTranslation().GetActiveDestinationLanguages(channel.Id, "", nil)
|
||||
require.NoError(t, appErr)
|
||||
@@ -451,8 +327,9 @@ func testAutoTranslationGetActiveDestinationLanguages(t *testing.T, rctx request
|
||||
|
||||
t.Run("excludes specified user", func(t *testing.T) {
|
||||
// Enable channel
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Exclude Spanish user
|
||||
languages, appErr := ss.AutoTranslation().GetActiveDestinationLanguages(channel.Id, users[1].Id, nil)
|
||||
@@ -468,8 +345,9 @@ func testAutoTranslationGetActiveDestinationLanguages(t *testing.T, rctx request
|
||||
|
||||
t.Run("filters to specific users", func(t *testing.T) {
|
||||
// Enable channel
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Filter to only first two users (en, es)
|
||||
filterIDs := []string{users[0].Id, users[1].Id}
|
||||
@@ -484,8 +362,9 @@ func testAutoTranslationGetActiveDestinationLanguages(t *testing.T, rctx request
|
||||
|
||||
t.Run("filters and excludes user", func(t *testing.T) {
|
||||
// Enable channel
|
||||
appErr := ss.AutoTranslation().SetChannelEnabled(channel.Id, true)
|
||||
require.NoError(t, appErr)
|
||||
channel.AutoTranslation = true
|
||||
channel, nErr = ss.Channel().Update(rctx, channel)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Filter to first two users but exclude the first one
|
||||
filterIDs := []string{users[0].Id, users[1].Id}
|
||||
|
||||
@@ -237,34 +237,6 @@ func (_m *AutoTranslationStore) InvalidateUserLocaleCache(userID string) {
|
||||
_m.Called(userID)
|
||||
}
|
||||
|
||||
// IsChannelEnabled provides a mock function with given fields: channelID
|
||||
func (_m *AutoTranslationStore) IsChannelEnabled(channelID string) (bool, error) {
|
||||
ret := _m.Called(channelID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsChannelEnabled")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (bool, error)); ok {
|
||||
return rf(channelID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) bool); ok {
|
||||
r0 = rf(channelID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(channelID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// IsUserEnabled provides a mock function with given fields: userID, channelID
|
||||
func (_m *AutoTranslationStore) IsUserEnabled(userID string, channelID string) (bool, error) {
|
||||
ret := _m.Called(userID, channelID)
|
||||
@@ -311,24 +283,6 @@ func (_m *AutoTranslationStore) Save(translation *model.Translation) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetChannelEnabled provides a mock function with given fields: channelID, enabled
|
||||
func (_m *AutoTranslationStore) SetChannelEnabled(channelID string, enabled bool) error {
|
||||
ret := _m.Called(channelID, enabled)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetChannelEnabled")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(channelID, enabled)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetUserEnabled provides a mock function with given fields: userID, channelID, enabled
|
||||
func (_m *AutoTranslationStore) SetUserEnabled(userID string, channelID string, enabled bool) error {
|
||||
ret := _m.Called(userID, channelID, enabled)
|
||||
|
||||
@@ -946,22 +946,6 @@ func (s *TimerLayerAutoTranslationStore) InvalidateUserLocaleCache(userID string
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TimerLayerAutoTranslationStore) IsChannelEnabled(channelID string) (bool, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.AutoTranslationStore.IsChannelEnabled(channelID)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AutoTranslationStore.IsChannelEnabled", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAutoTranslationStore) IsUserEnabled(userID string, channelID string) (bool, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -994,22 +978,6 @@ func (s *TimerLayerAutoTranslationStore) Save(translation *model.Translation) er
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAutoTranslationStore) SetChannelEnabled(channelID string, enabled bool) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.AutoTranslationStore.SetChannelEnabled(channelID, enabled)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AutoTranslationStore.SetChannelEnabled", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerBotStore) Get(userID string, includeDeleted bool) (*model.Bot, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
||||
systemStore.On("GetByName", model.MigrationAddSysconsoleMobileSecurityPermission).Return(&model.System{Name: model.MigrationAddSysconsoleMobileSecurityPermission, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddChannelBannerPermissions).Return(&model.System{Name: model.MigrationKeyAddChannelBannerPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddChannelAccessRulesPermission).Return(&model.System{Name: model.MigrationKeyAddChannelAccessRulesPermission, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddChannelAutoTranslationPermissions).Return(&model.System{Name: model.MigrationKeyAddChannelAutoTranslationPermissions, Value: "true"}, nil)
|
||||
|
||||
systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once()
|
||||
systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)
|
||||
|
||||
@@ -249,6 +249,8 @@ func (s *MmctlUnitTestSuite) TestResetPermissionsCmd() {
|
||||
"manage_public_channel_banner",
|
||||
"manage_private_channel_banner",
|
||||
"manage_channel_access_rules",
|
||||
"manage_public_channel_auto_translation",
|
||||
"manage_private_channel_auto_translation",
|
||||
}
|
||||
expectedPatch := &model.RolePatch{
|
||||
Permissions: &expectedPermissions,
|
||||
|
||||
@@ -249,6 +249,7 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li
|
||||
|
||||
props["ContentFlaggingEnabled"] = strconv.FormatBool(c.FeatureFlags.ContentFlagging && *c.ContentFlaggingSettings.EnableContentFlagging)
|
||||
props["EnableAutoTranslation"] = strconv.FormatBool(c.FeatureFlags.AutoTranslation && *c.AutoTranslationSettings.Enable)
|
||||
props["RestrictDMAndGMAutotranslation"] = strconv.FormatBool(*c.AutoTranslationSettings.RestrictDMAndGM)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,6 @@ type AutoTranslationInterface interface {
|
||||
// Returns false if the feature is unavailable (license, config, etc.).
|
||||
IsChannelEnabled(channelID string) (bool, *model.AppError)
|
||||
|
||||
// SetChannelEnabled enables or disables auto-translation for a channel.
|
||||
// Only available when the feature is properly licensed and configured.
|
||||
SetChannelEnabled(channelID string, enabled bool) *model.AppError
|
||||
|
||||
// IsUserEnabled checks if auto-translation is enabled for a specific user in a channel.
|
||||
// This checks both channel enablement AND user opt-in status.
|
||||
// Returns false if the feature is unavailable or the user hasn't opted in.
|
||||
|
||||
@@ -256,26 +256,6 @@ func (_m *AutoTranslationInterface) MakeWorker() model.Worker {
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetChannelEnabled provides a mock function with given fields: channelID, enabled
|
||||
func (_m *AutoTranslationInterface) SetChannelEnabled(channelID string, enabled bool) *model.AppError {
|
||||
ret := _m.Called(channelID, enabled)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetChannelEnabled")
|
||||
}
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, bool) *model.AppError); ok {
|
||||
r0 = rf(channelID, enabled)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetUserEnabled provides a mock function with given fields: channelID, userID, enabled
|
||||
func (_m *AutoTranslationInterface) SetUserEnabled(channelID string, userID string, enabled bool) *model.AppError {
|
||||
ret := _m.Called(channelID, userID, enabled)
|
||||
|
||||
@@ -455,10 +455,22 @@
|
||||
"id": "api.channel.patch_channel_moderations_for_channel.restricted_permission.app_error",
|
||||
"translation": "Cannot add a permission that is restricted by the team or system permission scheme."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.patch_update_channel.auto_translation_restricted.app_error",
|
||||
"translation": "Auto translation is not allowed for this channel."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.patch_update_channel.feature_not_available.app_error",
|
||||
"translation": "Auto translation feature is not available."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.patch_update_channel.forbidden.app_error",
|
||||
"translation": "Failed to update the channel."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.patch_update_channel.no_changes.app_error",
|
||||
"translation": "No changes in the patch."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.patch_update_channel.restricted_dm.app_error",
|
||||
"translation": "Cannot update a restricted direct message channel."
|
||||
@@ -471,6 +483,22 @@
|
||||
"id": "api.channel.post_channel_privacy_message.error",
|
||||
"translation": "Failed to post channel privacy update message."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.post_update_channel_autotranslation_message.create_post.error",
|
||||
"translation": "Failed to post Auto-translation update message."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.post_update_channel_autotranslation_message.disabled",
|
||||
"translation": "@%s disabled Auto-translation for this channel. All new messages will appear in the original language."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.post_update_channel_autotranslation_message.enabled",
|
||||
"translation": "@%s enabled Auto-translation for this channel. All new messages will appear in your preferred language."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.post_update_channel_autotranslation_message.retrieve_user.error",
|
||||
"translation": "Failed to retrieve user while updating Auto-translation status"
|
||||
},
|
||||
{
|
||||
"id": "api.channel.post_update_channel_displayname_message_and_forget.create_post.error",
|
||||
"translation": "Failed to post displayname update message"
|
||||
|
||||
@@ -152,6 +152,7 @@ type ChannelPatch struct {
|
||||
Purpose *string `json:"purpose"`
|
||||
GroupConstrained *bool `json:"group_constrained"`
|
||||
BannerInfo *ChannelBannerInfo `json:"banner_info"`
|
||||
AutoTranslation *bool `json:"autotranslation"`
|
||||
}
|
||||
|
||||
func (c *ChannelPatch) Auditable() map[string]any {
|
||||
@@ -399,6 +400,10 @@ func (o *Channel) Patch(patch *ChannelPatch) {
|
||||
o.BannerInfo.BackgroundColor = patch.BannerInfo.BackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
if patch.AutoTranslation != nil {
|
||||
o.AutoTranslation = *patch.AutoTranslation
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Channel) MakeNonNil() {
|
||||
|
||||
@@ -2785,6 +2785,7 @@ func (s *LocalizationSettings) SetDefaults() {
|
||||
|
||||
type AutoTranslationSettings struct {
|
||||
Enable *bool `access:"site_localization,cloud_restrictable"`
|
||||
RestrictDMAndGM *bool `access:"site_localization,cloud_restrictable"`
|
||||
Provider *string `access:"site_localization,cloud_restrictable"`
|
||||
TargetLanguages *[]string `access:"site_localization,cloud_restrictable"`
|
||||
Workers *int `access:"site_localization,cloud_restrictable"`
|
||||
@@ -2833,6 +2834,10 @@ func (s *AutoTranslationSettings) SetDefaults() {
|
||||
s.Agents = &AgentsProviderSettings{}
|
||||
}
|
||||
s.Agents.SetDefaults()
|
||||
|
||||
if s.RestrictDMAndGM == nil {
|
||||
s.RestrictDMAndGM = NewPointer(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LibreTranslateProviderSettings) SetDefaults() {
|
||||
|
||||
@@ -57,4 +57,5 @@ const (
|
||||
MigrationAddSysconsoleMobileSecurityPermission = "add_sysconsole_mobile_security_permission"
|
||||
MigrationKeyAddChannelBannerPermissions = "add_channel_banner_permissions"
|
||||
MigrationKeyAddChannelAccessRulesPermission = "add_channel_access_rules_permission"
|
||||
MigrationKeyAddChannelAutoTranslationPermissions = "add_channel_auto_translation_permissions"
|
||||
)
|
||||
|
||||
@@ -46,6 +46,8 @@ var PermissionCreateDirectChannel *Permission
|
||||
var PermissionCreateGroupChannel *Permission
|
||||
var PermissionManagePublicChannelProperties *Permission
|
||||
var PermissionManagePrivateChannelProperties *Permission
|
||||
var PermissionManagePublicChannelAutoTranslation *Permission
|
||||
var PermissionManagePrivateChannelAutoTranslation *Permission
|
||||
var PermissionListPublicTeams *Permission
|
||||
var PermissionJoinPublicTeams *Permission
|
||||
var PermissionListPrivateTeams *Permission
|
||||
@@ -544,6 +546,18 @@ func initializePermissions() {
|
||||
"authentication.permissions.manage_private_channel_properties.description",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionManagePublicChannelAutoTranslation = &Permission{
|
||||
"manage_public_channel_auto_translation",
|
||||
"authentication.permissions.manage_public_channel_auto_translation.name",
|
||||
"authentication.permissions.manage_public_channel_auto_translation.description",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionManagePrivateChannelAutoTranslation = &Permission{
|
||||
"manage_private_channel_auto_translation",
|
||||
"authentication.permissions.manage_private_channel_auto_translation.name",
|
||||
"authentication.permissions.manage_private_channel_auto_translation.description",
|
||||
PermissionScopeChannel,
|
||||
}
|
||||
PermissionListPublicTeams = &Permission{
|
||||
"list_public_teams",
|
||||
"authentication.permissions.list_public_teams.name",
|
||||
@@ -2547,6 +2561,8 @@ func initializePermissions() {
|
||||
PermissionManageChannelRoles,
|
||||
PermissionManagePublicChannelProperties,
|
||||
PermissionManagePrivateChannelProperties,
|
||||
PermissionManagePublicChannelAutoTranslation,
|
||||
PermissionManagePrivateChannelAutoTranslation,
|
||||
PermissionConvertPublicChannelToPrivate,
|
||||
PermissionConvertPrivateChannelToPublic,
|
||||
PermissionDeletePublicChannel,
|
||||
|
||||
+35
-33
@@ -26,39 +26,40 @@ import (
|
||||
type PostContextKey string
|
||||
|
||||
const (
|
||||
PostSystemMessagePrefix = "system_"
|
||||
PostTypeDefault = ""
|
||||
PostTypeSlackAttachment = "slack_attachment"
|
||||
PostTypeSystemGeneric = "system_generic"
|
||||
PostTypeJoinLeave = "system_join_leave" // Deprecated, use PostJoinChannel or PostLeaveChannel instead
|
||||
PostTypeJoinChannel = "system_join_channel"
|
||||
PostTypeGuestJoinChannel = "system_guest_join_channel"
|
||||
PostTypeLeaveChannel = "system_leave_channel"
|
||||
PostTypeJoinTeam = "system_join_team"
|
||||
PostTypeLeaveTeam = "system_leave_team"
|
||||
PostTypeAutoResponder = "system_auto_responder"
|
||||
PostTypeAddRemove = "system_add_remove" // Deprecated, use PostAddToChannel or PostRemoveFromChannel instead
|
||||
PostTypeAddToChannel = "system_add_to_channel"
|
||||
PostTypeAddGuestToChannel = "system_add_guest_to_chan"
|
||||
PostTypeRemoveFromChannel = "system_remove_from_channel"
|
||||
PostTypeMoveChannel = "system_move_channel"
|
||||
PostTypeAddToTeam = "system_add_to_team"
|
||||
PostTypeRemoveFromTeam = "system_remove_from_team"
|
||||
PostTypeHeaderChange = "system_header_change"
|
||||
PostTypeDisplaynameChange = "system_displayname_change"
|
||||
PostTypeConvertChannel = "system_convert_channel"
|
||||
PostTypePurposeChange = "system_purpose_change"
|
||||
PostTypeChannelDeleted = "system_channel_deleted"
|
||||
PostTypeChannelRestored = "system_channel_restored"
|
||||
PostTypeEphemeral = "system_ephemeral"
|
||||
PostTypeChangeChannelPrivacy = "system_change_chan_privacy"
|
||||
PostTypeWrangler = "system_wrangler"
|
||||
PostTypeGMConvertedToChannel = "system_gm_to_channel"
|
||||
PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
|
||||
PostTypeMe = "me"
|
||||
PostCustomTypePrefix = "custom_"
|
||||
PostTypeReminder = "reminder"
|
||||
PostTypeBurnOnRead = "burn_on_read"
|
||||
PostSystemMessagePrefix = "system_"
|
||||
PostTypeDefault = ""
|
||||
PostTypeSlackAttachment = "slack_attachment"
|
||||
PostTypeSystemGeneric = "system_generic"
|
||||
PostTypeJoinLeave = "system_join_leave" // Deprecated, use PostJoinChannel or PostLeaveChannel instead
|
||||
PostTypeJoinChannel = "system_join_channel"
|
||||
PostTypeGuestJoinChannel = "system_guest_join_channel"
|
||||
PostTypeLeaveChannel = "system_leave_channel"
|
||||
PostTypeJoinTeam = "system_join_team"
|
||||
PostTypeLeaveTeam = "system_leave_team"
|
||||
PostTypeAutoResponder = "system_auto_responder"
|
||||
PostTypeAutotranslationChange = "system_autotranslation"
|
||||
PostTypeAddRemove = "system_add_remove" // Deprecated, use PostAddToChannel or PostRemoveFromChannel instead
|
||||
PostTypeAddToChannel = "system_add_to_channel"
|
||||
PostTypeAddGuestToChannel = "system_add_guest_to_chan"
|
||||
PostTypeRemoveFromChannel = "system_remove_from_channel"
|
||||
PostTypeMoveChannel = "system_move_channel"
|
||||
PostTypeAddToTeam = "system_add_to_team"
|
||||
PostTypeRemoveFromTeam = "system_remove_from_team"
|
||||
PostTypeHeaderChange = "system_header_change"
|
||||
PostTypeDisplaynameChange = "system_displayname_change"
|
||||
PostTypeConvertChannel = "system_convert_channel"
|
||||
PostTypePurposeChange = "system_purpose_change"
|
||||
PostTypeChannelDeleted = "system_channel_deleted"
|
||||
PostTypeChannelRestored = "system_channel_restored"
|
||||
PostTypeEphemeral = "system_ephemeral"
|
||||
PostTypeChangeChannelPrivacy = "system_change_chan_privacy"
|
||||
PostTypeWrangler = "system_wrangler"
|
||||
PostTypeGMConvertedToChannel = "system_gm_to_channel"
|
||||
PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
|
||||
PostTypeMe = "me"
|
||||
PostCustomTypePrefix = "custom_"
|
||||
PostTypeReminder = "reminder"
|
||||
PostTypeBurnOnRead = "burn_on_read"
|
||||
|
||||
PostFileidsMaxRunes = 300
|
||||
PostFilenamesMaxRunes = 4000
|
||||
@@ -523,6 +524,7 @@ func (o *Post) IsValid(maxPostSize int) *AppError {
|
||||
PostTypeMe,
|
||||
PostTypeWrangler,
|
||||
PostTypeGMConvertedToChannel,
|
||||
PostTypeAutotranslationChange,
|
||||
PostTypeBurnOnRead:
|
||||
default:
|
||||
if !strings.HasPrefix(o.Type, PostCustomTypePrefix) {
|
||||
|
||||
@@ -119,6 +119,8 @@ func init() {
|
||||
PermissionSysconsoleWriteUserManagementChannels.Id: {
|
||||
PermissionManagePublicChannelProperties,
|
||||
PermissionManagePrivateChannelProperties,
|
||||
PermissionManagePublicChannelAutoTranslation,
|
||||
PermissionManagePrivateChannelAutoTranslation,
|
||||
PermissionManagePrivateChannelMembers,
|
||||
PermissionManagePublicChannelMembers,
|
||||
PermissionDeletePrivateChannel,
|
||||
@@ -911,6 +913,8 @@ func MakeDefaultRoles() map[string]*Role {
|
||||
PermissionManagePublicChannelBanner.Id,
|
||||
PermissionManagePrivateChannelBanner.Id,
|
||||
PermissionManageChannelAccessRules.Id,
|
||||
PermissionManagePublicChannelAutoTranslation.Id,
|
||||
PermissionManagePrivateChannelAutoTranslation.Id,
|
||||
},
|
||||
SchemeManaged: true,
|
||||
BuiltIn: true,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {Link} from 'react-router-dom';
|
||||
|
||||
import type {AutoTranslationSettings} from '@mattermost/types/config';
|
||||
|
||||
import BooleanSetting from 'components/admin_console/boolean_setting';
|
||||
import MultiSelectSetting from 'components/admin_console/multiselect_settings';
|
||||
import Setting from 'components/admin_console/setting';
|
||||
import {
|
||||
@@ -283,6 +284,25 @@ export default function AutoTranslation(props: SystemConsoleCustomSettingsCompon
|
||||
onChange={handleTimeoutChange}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
<BooleanSetting
|
||||
id='RestrictDMAndGM'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.site.localization.restrictDMAndGMTitle'
|
||||
defaultMessage='Restrict auto-translation on direct messages and group messages'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.site.localization.restrictDMAndGMDescription'
|
||||
defaultMessage='By default, any member of a direct message or group message can enable auto-translation in those channels. If restricted, auto-translation will not be available in direct messages and group messages.'
|
||||
/>
|
||||
}
|
||||
value={autoTranslationSettings.RestrictDMAndGM}
|
||||
onChange={handleChange}
|
||||
disabled={props.disabled || props.setByEnv}
|
||||
setByEnv={props.setByEnv}
|
||||
/>
|
||||
</SectionContent>
|
||||
}
|
||||
</AdminSection>
|
||||
|
||||
+14
@@ -64,6 +64,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_public_channel",
|
||||
"manage_public_channel_properties",
|
||||
"manage_public_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_public_channel_members_and_read_groups",
|
||||
@@ -81,6 +82,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_private_channel",
|
||||
"manage_private_channel_properties",
|
||||
"manage_private_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_private_channel_members_and_read_groups",
|
||||
@@ -258,6 +260,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_public_channel",
|
||||
"manage_public_channel_properties",
|
||||
"manage_public_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_public_channel_members_and_read_groups",
|
||||
@@ -275,6 +278,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_private_channel",
|
||||
"manage_private_channel_properties",
|
||||
"manage_private_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_private_channel_members_and_read_groups",
|
||||
@@ -479,6 +483,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_public_channel",
|
||||
"manage_public_channel_properties",
|
||||
"manage_public_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_public_channel_members_and_read_groups",
|
||||
@@ -496,6 +501,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_private_channel",
|
||||
"manage_private_channel_properties",
|
||||
"manage_private_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_private_channel_members_and_read_groups",
|
||||
@@ -689,6 +695,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_public_channel",
|
||||
"manage_public_channel_properties",
|
||||
"manage_public_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_public_channel_members_and_read_groups",
|
||||
@@ -706,6 +713,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_private_channel",
|
||||
"manage_private_channel_properties",
|
||||
"manage_private_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_private_channel_members_and_read_groups",
|
||||
@@ -910,6 +918,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_public_channel",
|
||||
"manage_public_channel_properties",
|
||||
"manage_public_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_public_channel_members_and_read_groups",
|
||||
@@ -927,6 +936,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_private_channel",
|
||||
"manage_private_channel_properties",
|
||||
"manage_private_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_private_channel_members_and_read_groups",
|
||||
@@ -1131,6 +1141,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_public_channel",
|
||||
"manage_public_channel_properties",
|
||||
"manage_public_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_public_channel_members_and_read_groups",
|
||||
@@ -1148,6 +1159,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_private_channel",
|
||||
"manage_private_channel_properties",
|
||||
"manage_private_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_private_channel_members_and_read_groups",
|
||||
@@ -1359,6 +1371,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_public_channel",
|
||||
"manage_public_channel_properties",
|
||||
"manage_public_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_public_channel_members_and_read_groups",
|
||||
@@ -1376,6 +1389,7 @@ exports[`components/admin_console/permission_schemes_settings/permission_tree sh
|
||||
"permissions": Array [
|
||||
"create_private_channel",
|
||||
"manage_private_channel_properties",
|
||||
"manage_private_channel_auto_translation",
|
||||
Object {
|
||||
"combined": true,
|
||||
"id": "manage_private_channel_members_and_read_groups",
|
||||
|
||||
+2
@@ -87,6 +87,7 @@ export default class PermissionsTree extends React.PureComponent<Props, State> {
|
||||
permissions: [
|
||||
Permissions.CREATE_PUBLIC_CHANNEL,
|
||||
Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES,
|
||||
Permissions.MANAGE_PUBLIC_CHANNEL_AUTO_TRANSLATION,
|
||||
{
|
||||
id: 'manage_public_channel_members_and_read_groups',
|
||||
combined: true,
|
||||
@@ -104,6 +105,7 @@ export default class PermissionsTree extends React.PureComponent<Props, State> {
|
||||
permissions: [
|
||||
Permissions.CREATE_PRIVATE_CHANNEL,
|
||||
Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES,
|
||||
Permissions.MANAGE_PRIVATE_CHANNEL_AUTO_TRANSLATION,
|
||||
{
|
||||
id: 'manage_private_channel_members_and_read_groups',
|
||||
combined: true,
|
||||
|
||||
+20
@@ -275,6 +275,26 @@ export const permissionRolesStrings: Record<string, Record<string, MessageDescri
|
||||
defaultMessage: 'Update public channel names, headers and purposes.',
|
||||
},
|
||||
}),
|
||||
manage_public_channel_auto_translation: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.permission.manage_public_channel_auto_translation.name',
|
||||
defaultMessage: 'Manage Channel Auto Translation',
|
||||
},
|
||||
description: {
|
||||
id: 'admin.permissions.permission.manage_public_channel_auto_translation.description',
|
||||
defaultMessage: 'Enable or disable auto translations for public channels.',
|
||||
},
|
||||
}),
|
||||
manage_private_channel_auto_translation: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.permission.manage_private_channel_auto_translation.name',
|
||||
defaultMessage: 'Manage Channel Auto Translation',
|
||||
},
|
||||
description: {
|
||||
id: 'admin.permissions.permission.manage_private_channel_auto_translation.description',
|
||||
defaultMessage: 'Enable or disable auto translations for private channels.',
|
||||
},
|
||||
}),
|
||||
manage_roles: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.permission.manage_roles.name',
|
||||
|
||||
+1
-2
@@ -117,7 +117,7 @@ export type ChannelDetailsActions = {
|
||||
getChannel: (channelId: string) => void;
|
||||
getTeam: (teamId: string) => Promise<ActionResult>;
|
||||
getChannelModerations: (channelId: string) => Promise<ActionResult>;
|
||||
patchChannel: (channelId: string, patch: Channel) => Promise<ActionResult>;
|
||||
patchChannel: (channelId: string, patch: Partial<Channel>) => Promise<ActionResult>;
|
||||
updateChannelPrivacy: (channelId: string, privacy: string) => Promise<ActionResult>;
|
||||
patchGroupSyncable: (groupID: string, syncableID: string, syncableType: SyncableType, patch: Partial<SyncablePatch>) => Promise<ActionResult>;
|
||||
patchChannelModerations: (channelID: string, patch: ChannelModerationPatch[]) => Promise<ActionResult>;
|
||||
@@ -556,7 +556,6 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
|
||||
|
||||
// Then patch the channel
|
||||
const patchResult = await actions.patchChannel(channel.id, {
|
||||
...channel,
|
||||
group_constrained: isSynced,
|
||||
});
|
||||
|
||||
|
||||
-2
@@ -169,7 +169,6 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
|
||||
// Verify patchChannel was called with the updated values
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', {
|
||||
...mockChannel,
|
||||
banner_info: {
|
||||
enabled: true,
|
||||
text: 'New banner text',
|
||||
@@ -397,7 +396,6 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
|
||||
// Verify patchChannel was called with the trimmed values
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', {
|
||||
...mockChannelWithBanner,
|
||||
banner_info: {
|
||||
enabled: true,
|
||||
text: 'Banner text with whitespace', // Whitespace should be trimmed
|
||||
|
||||
+1
-3
@@ -150,9 +150,7 @@ function ChannelSettingsConfigurationTab({channel, setAreThereUnsavedChanges, sh
|
||||
return false;
|
||||
}
|
||||
|
||||
const updated: Channel = {
|
||||
...channel,
|
||||
};
|
||||
const updated: Partial<Channel> = {};
|
||||
|
||||
updated.banner_info = {
|
||||
text: updatedChannelBanner.text?.trim() || '',
|
||||
|
||||
-2
@@ -201,7 +201,6 @@ describe('ChannelSettingsInfoTab', () => {
|
||||
// Verify patchChannel was called with the updated values (without type change).
|
||||
// Note: URL should remain unchanged when editing existing channels
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', {
|
||||
...mockChannel,
|
||||
display_name: 'Updated Channel Name',
|
||||
name: 'test-channel', // URL should remain unchanged when editing existing channels
|
||||
purpose: 'Updated purpose',
|
||||
@@ -241,7 +240,6 @@ describe('ChannelSettingsInfoTab', () => {
|
||||
|
||||
// Verify patchChannel was called with the trimmed values
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', {
|
||||
...mockChannel,
|
||||
display_name: 'Channel Name With Whitespace', // Whitespace should be trimmed
|
||||
name: 'test-channel', // URL should remain unchanged when editing existing channels
|
||||
purpose: 'Purpose with whitespace', // Whitespace should be trimmed
|
||||
|
||||
@@ -229,8 +229,7 @@ function ChannelSettingsInfoTab({
|
||||
}
|
||||
|
||||
// Build updated channel object
|
||||
const updated: Channel = {
|
||||
...channel,
|
||||
const updated: Partial<Channel> = {
|
||||
display_name: displayName.trim(),
|
||||
name: channelUrl.trim(),
|
||||
purpose: channelPurpose.trim(),
|
||||
@@ -245,12 +244,12 @@ function ChannelSettingsInfoTab({
|
||||
|
||||
// After every successful save, update local state to match the saved values
|
||||
// with this, we make sure that the unsavedChanges check will return false after saving
|
||||
setDisplayName(data?.display_name ?? updated.display_name);
|
||||
setChannelURL(data?.name ?? updated.name);
|
||||
setChannelPurpose(data?.purpose ?? updated.purpose);
|
||||
setChannelHeader(data?.header ?? updated.header);
|
||||
setDisplayName(data?.display_name ?? updated.display_name ?? '');
|
||||
setChannelURL(data?.name ?? updated.name ?? '');
|
||||
setChannelPurpose(data?.purpose ?? updated.purpose ?? '');
|
||||
setChannelHeader(data?.header ?? updated.header ?? '');
|
||||
return true;
|
||||
}, [channel, displayName, channelUrl, channelPurpose, channelHeader, channelType, setFormError, handleServerError]);
|
||||
}, [channel, displayName, channelType, channelUrl, channelPurpose, channelHeader, dispatch, formatMessage, handleServerError]);
|
||||
|
||||
// Handle save changes panel actions
|
||||
const handleSaveChanges = useCallback(async () => {
|
||||
|
||||
@@ -2032,10 +2032,14 @@
|
||||
"admin.permissions.permission.manage_own_outgoing_webhooks.name": "Manage Own",
|
||||
"admin.permissions.permission.manage_own_slash_commands.description": "Create, edit and delete your own slash commands.",
|
||||
"admin.permissions.permission.manage_own_slash_commands.name": "Manage Own",
|
||||
"admin.permissions.permission.manage_private_channel_auto_translation.description": "Enable or disable auto translations for private channels.",
|
||||
"admin.permissions.permission.manage_private_channel_auto_translation.name": "Manage Channel Auto Translation",
|
||||
"admin.permissions.permission.manage_private_channel_banner.description": "Enable, disable and edit channel banner.",
|
||||
"admin.permissions.permission.manage_private_channel_banner.name": "Manage Channel Banner",
|
||||
"admin.permissions.permission.manage_private_channel_properties.description": "Update private channel names, headers and purposes.",
|
||||
"admin.permissions.permission.manage_private_channel_properties.name": "Manage Channel Settings",
|
||||
"admin.permissions.permission.manage_public_channel_auto_translation.description": "Enable or disable auto translations for public channels.",
|
||||
"admin.permissions.permission.manage_public_channel_auto_translation.name": "Manage Channel Auto Translation",
|
||||
"admin.permissions.permission.manage_public_channel_banner.description": "Enable, disable and edit channel banner.",
|
||||
"admin.permissions.permission.manage_public_channel_banner.name": "Manage Channel Banner",
|
||||
"admin.permissions.permission.manage_public_channel_properties.description": "Update public channel names, headers and purposes.",
|
||||
@@ -2871,6 +2875,8 @@
|
||||
"admin.site.localization.goToAgentsConfig": "Go to Agents plugin config",
|
||||
"admin.site.localization.languages.description": "Choose which languages should be the defaults",
|
||||
"admin.site.localization.languages.title": "Languages",
|
||||
"admin.site.localization.restrictDMAndGMDescription": "By default, any member of a direct message or group message can enable auto-translation in those channels. If restricted, auto-translation will not be available in direct messages and group messages.",
|
||||
"admin.site.localization.restrictDMAndGMTitle": "Restrict auto-translation on direct messages and group messages",
|
||||
"admin.site.localization.targetLanguagesDescription": "Choose which languages you'd like to make available for auto-translation.",
|
||||
"admin.site.localization.targetLanguagesTitle": "Languages allowed",
|
||||
"admin.site.move_thread": "Move Thread",
|
||||
|
||||
@@ -22,6 +22,8 @@ const values = {
|
||||
CREATE_GROUP_CHANNEL: 'create_group_channel',
|
||||
MANAGE_PUBLIC_CHANNEL_PROPERTIES: 'manage_public_channel_properties',
|
||||
MANAGE_PRIVATE_CHANNEL_PROPERTIES: 'manage_private_channel_properties',
|
||||
MANAGE_PUBLIC_CHANNEL_AUTO_TRANSLATION: 'manage_public_channel_auto_translation',
|
||||
MANAGE_PRIVATE_CHANNEL_AUTO_TRANSLATION: 'manage_private_channel_auto_translation',
|
||||
LIST_PUBLIC_TEAMS: 'list_public_teams',
|
||||
JOIN_PUBLIC_TEAMS: 'join_public_teams',
|
||||
LIST_PRIVATE_TEAMS: 'list_private_teams',
|
||||
|
||||
@@ -1113,6 +1113,8 @@ export const PermissionsScope = {
|
||||
[Permissions.CREATE_GROUP_CHANNEL]: 'system_scope',
|
||||
[Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES]: 'channel_scope',
|
||||
[Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES]: 'channel_scope',
|
||||
[Permissions.MANAGE_PUBLIC_CHANNEL_AUTO_TRANSLATION]: 'channel_scope',
|
||||
[Permissions.MANAGE_PRIVATE_CHANNEL_AUTO_TRANSLATION]: 'channel_scope',
|
||||
[Permissions.LIST_PUBLIC_TEAMS]: 'system_scope',
|
||||
[Permissions.JOIN_PUBLIC_TEAMS]: 'system_scope',
|
||||
[Permissions.LIST_PRIVATE_TEAMS]: 'system_scope',
|
||||
|
||||
@@ -238,6 +238,10 @@ export type ClientConfig = {
|
||||
EnableAttributeBasedAccessControl: string;
|
||||
EnableChannelScopeAccessControl: string;
|
||||
EnableUserManagedAttributes: string;
|
||||
|
||||
// Auto Translation Settings
|
||||
EnableAutoTranslation: string;
|
||||
RestrictDMAndGMAutotranslation: string;
|
||||
};
|
||||
|
||||
export type License = {
|
||||
@@ -765,6 +769,7 @@ export type AutoTranslationSettings = {
|
||||
LLMServiceID: string;
|
||||
};
|
||||
TimeoutMs: number;
|
||||
RestrictDMAndGM: boolean;
|
||||
};
|
||||
|
||||
export type SamlSettings = {
|
||||
|
||||
Reference in New Issue
Block a user