MM-33903 rename slash commands & permission (#17494)
- rename slash commands
- "remote" -> "secure-connection"
- "share" -> "share-channel"
- change status icons to text (translated)
- remove channel invite "Description" field
- rename permission "manage_remote_clusters" -> "manage_secure_connections"
Этот коммит содержится в:
@@ -182,7 +182,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
|
||||
"system_admin": allPermissionIDs,
|
||||
}
|
||||
assert.Contains(t, allPermissionIDs, model.PERMISSION_MANAGE_SHARED_CHANNELS.Id, "manage_shared_channels permission not found")
|
||||
assert.Contains(t, allPermissionIDs, model.PERMISSION_MANAGE_REMOTE_CLUSTERS.Id, "manage_remote_clusters permission not found")
|
||||
assert.Contains(t, allPermissionIDs, model.PERMISSION_MANAGE_SECURE_CONNECTIONS.Id, "manage_secure_connections permission not found")
|
||||
|
||||
// Check the migration matches what's expected.
|
||||
for name, permissions := range expected1 {
|
||||
|
||||
@@ -70,7 +70,8 @@ const (
|
||||
PermissionReadPrivateChannelGroups = "read_private_channel_groups"
|
||||
PermissionEditBrand = "edit_brand"
|
||||
PermissionManageSharedChannels = "manage_shared_channels"
|
||||
PermissionManageRemoteClusters = "manage_remote_clusters"
|
||||
PermissionManageSecureConnections = "manage_secure_connections"
|
||||
PermissionManageRemoteClusters = "manage_remote_clusters" // deprecated; use `manage_secure_connections`
|
||||
)
|
||||
|
||||
func isRole(roleName string) func(*model.Role, map[string]map[string]bool) bool {
|
||||
@@ -525,13 +526,24 @@ func (a *App) getBillingPermissionsMigration() (permissionsMap, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddManageRemoteClustersPermissionsMigration() (permissionsMap, error) {
|
||||
return permissionsMap{
|
||||
func (a *App) getAddManageSecureConnectionsPermissionsMigration() (permissionsMap, error) {
|
||||
transformations := []permissionTransformation{}
|
||||
|
||||
// add the new permission to system admin
|
||||
transformations = append(transformations,
|
||||
permissionTransformation{
|
||||
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
|
||||
Add: []string{PermissionManageRemoteClusters},
|
||||
},
|
||||
}, nil
|
||||
Add: []string{PermissionManageSecureConnections},
|
||||
})
|
||||
|
||||
// remote the decprecated permission from system admin
|
||||
transformations = append(transformations,
|
||||
permissionTransformation{
|
||||
On: isRole(model.SYSTEM_ADMIN_ROLE_ID),
|
||||
Remove: []string{PermissionManageRemoteClusters},
|
||||
})
|
||||
|
||||
return transformations, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddDownloadComplianceExportResult() (permissionsMap, error) {
|
||||
@@ -910,7 +922,7 @@ func (a *App) DoPermissionsMigrations() error {
|
||||
{Key: model.MIGRATION_KEY_ADD_SYSTEM_CONSOLE_PERMISSIONS, Migration: a.getAddSystemConsolePermissionsMigration},
|
||||
{Key: model.MIGRATION_KEY_ADD_CONVERT_CHANNEL_PERMISSIONS, Migration: a.getAddConvertChannelPermissionsMigration},
|
||||
{Key: model.MIGRATION_KEY_ADD_MANAGE_SHARED_CHANNEL_PERMISSIONS, Migration: a.getAddManageSharedChannelsPermissionsMigration},
|
||||
{Key: model.MIGRATION_KEY_ADD_MANAGE_REMOTE_CLUSTERS_PERMISSIONS, Migration: a.getAddManageRemoteClustersPermissionsMigration},
|
||||
{Key: model.MIGRATION_KEY_ADD_MANAGE_SECURE_CONNECTIONS_PERMISSIONS, Migration: a.getAddManageSecureConnectionsPermissionsMigration},
|
||||
{Key: model.MIGRATION_KEY_ADD_SYSTEM_ROLES_PERMISSIONS, Migration: a.getSystemRolesPermissionsMigration},
|
||||
{Key: model.MIGRATION_KEY_ADD_BILLING_PERMISSIONS, Migration: a.getBillingPermissionsMigration},
|
||||
{Key: model.MIGRATION_KEY_ADD_DOWNLOAD_COMPLIANCE_EXPORT_RESULTS, Migration: a.getAddDownloadComplianceExportResult},
|
||||
|
||||
@@ -10,19 +10,20 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/shared/i18n"
|
||||
)
|
||||
|
||||
func TestAddRemoteCluster(t *testing.T) {
|
||||
t.Run("adding remote cluster with duplicate site url and remote team id", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("adding remote cluster with duplicate site url and remote team id", func(t *testing.T) {
|
||||
remoteCluster := &model.RemoteCluster{
|
||||
RemoteTeamId: model.NewId(),
|
||||
Name: "test",
|
||||
SiteURL: "http://localhost:8065",
|
||||
Token: "test",
|
||||
RemoteToken: "test",
|
||||
Name: "test1",
|
||||
SiteURL: "http://www1.example.com:8065",
|
||||
Token: model.NewId(),
|
||||
RemoteToken: model.NewId(),
|
||||
Topics: "",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
@@ -33,19 +34,16 @@ func TestAddRemoteCluster(t *testing.T) {
|
||||
remoteCluster.RemoteId = model.NewId()
|
||||
_, err = th.App.AddRemoteCluster(remoteCluster)
|
||||
require.NotNil(t, err, "Adding a duplicate remote cluster should error")
|
||||
assert.Contains(t, err.Error(), "Remote cluster has already been added.")
|
||||
assert.Contains(t, err.Error(), i18n.T("api.remote_cluster.save_not_unique.app_error"))
|
||||
})
|
||||
|
||||
t.Run("adding remote cluster with duplicate site url or remote team id is allowed", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
remoteCluster := &model.RemoteCluster{
|
||||
RemoteTeamId: model.NewId(),
|
||||
Name: "test",
|
||||
SiteURL: "http://localhost:8065",
|
||||
Token: "test",
|
||||
RemoteToken: "test",
|
||||
Name: "test2",
|
||||
SiteURL: "http://www2.exmaple.com:8065",
|
||||
Token: model.NewId(),
|
||||
RemoteToken: model.NewId(),
|
||||
Topics: "",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
@@ -70,26 +68,26 @@ func TestAddRemoteCluster(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateRemoteCluster(t *testing.T) {
|
||||
t.Run("update remote cluster with an already existing site url and team id", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("update remote cluster with an already existing site url and team id", func(t *testing.T) {
|
||||
remoteCluster := &model.RemoteCluster{
|
||||
RemoteTeamId: model.NewId(),
|
||||
Name: "test",
|
||||
SiteURL: "http://localhost:8065",
|
||||
Token: "test",
|
||||
RemoteToken: "test",
|
||||
Name: "test3",
|
||||
SiteURL: "http://www3.exmaple.com:8065",
|
||||
Token: model.NewId(),
|
||||
RemoteToken: model.NewId(),
|
||||
Topics: "",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
|
||||
otherRemoteCluster := &model.RemoteCluster{
|
||||
RemoteTeamId: model.NewId(),
|
||||
Name: "test",
|
||||
SiteURL: "http://localhost:8066",
|
||||
Token: "test",
|
||||
RemoteToken: "test",
|
||||
Name: "test4",
|
||||
SiteURL: "http://www4.example.com:8066",
|
||||
Token: model.NewId(),
|
||||
RemoteToken: model.NewId(),
|
||||
Topics: "",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
@@ -104,29 +102,26 @@ func TestUpdateRemoteCluster(t *testing.T) {
|
||||
savedRemoteClustered.RemoteTeamId = remoteCluster.RemoteTeamId
|
||||
_, err = th.App.UpdateRemoteCluster(savedRemoteClustered)
|
||||
require.NotNil(t, err, "Updating remote cluster with duplicate site url should error")
|
||||
assert.Contains(t, err.Error(), "Remote cluster with the same url already exists.")
|
||||
assert.Contains(t, err.Error(), i18n.T("api.remote_cluster.update_not_unique.app_error"))
|
||||
})
|
||||
|
||||
t.Run("update remote cluster with an already existing site url or team id, is allowed", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
remoteCluster := &model.RemoteCluster{
|
||||
RemoteTeamId: model.NewId(),
|
||||
Name: "test",
|
||||
SiteURL: "http://localhost:8065",
|
||||
Token: "test",
|
||||
RemoteToken: "test",
|
||||
Name: "test5",
|
||||
SiteURL: "http://www5.example.com:8065",
|
||||
Token: model.NewId(),
|
||||
RemoteToken: model.NewId(),
|
||||
Topics: "",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
|
||||
otherRemoteCluster := &model.RemoteCluster{
|
||||
RemoteTeamId: model.NewId(),
|
||||
Name: "test",
|
||||
SiteURL: "http://localhost:8066",
|
||||
Token: "test",
|
||||
RemoteToken: "test",
|
||||
Name: "test6",
|
||||
SiteURL: "http://www6.example.com:8065",
|
||||
Token: model.NewId(),
|
||||
RemoteToken: model.NewId(),
|
||||
Topics: "",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func handleInvitation(s *Server, syncService SharedChannelServiceIFace, event *m
|
||||
return errors.Wrap(err, fmt.Sprintf("couldn't find remote cluster %s, for creating shared channel invitation for a DM", *participant.RemoteId))
|
||||
}
|
||||
|
||||
return syncService.SendChannelInvite(channel, creator.Id, "", rc, sharedchannel.WithDirectParticipantID(creator.Id), sharedchannel.WithDirectParticipantID(participant.Id))
|
||||
return syncService.SendChannelInvite(channel, creator.Id, rc, sharedchannel.WithDirectParticipantID(creator.Id), sharedchannel.WithDirectParticipantID(participant.Id))
|
||||
}
|
||||
|
||||
func getUserFromEvent(s *Server, event *model.WebSocketEvent, key string) (*model.User, error) {
|
||||
|
||||
@@ -13,7 +13,7 @@ type SharedChannelServiceIFace interface {
|
||||
Shutdown() error
|
||||
Start() error
|
||||
NotifyChannelChanged(channelId string)
|
||||
SendChannelInvite(channel *model.Channel, userId string, description string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error
|
||||
SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error
|
||||
Active() bool
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (mrcs *mockSharedChannelService) Active() bool {
|
||||
return mrcs.active
|
||||
}
|
||||
|
||||
func (mrcs *mockSharedChannelService) SendChannelInvite(channel *model.Channel, userId string, description string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error {
|
||||
func (mrcs *mockSharedChannelService) SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error {
|
||||
mrcs.numInvitations += 1
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
AvailableRemoteActions = "invite, accept, remove, status"
|
||||
AvailableRemoteActions = "create, accept, remove, status"
|
||||
)
|
||||
|
||||
type RemoteProvider struct {
|
||||
}
|
||||
|
||||
const (
|
||||
CommandTriggerRemote = "remote"
|
||||
CommandTriggerRemote = "secure-connection"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,23 +37,23 @@ func (rp *RemoteProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Co
|
||||
|
||||
remote := model.NewAutocompleteData(rp.GetTrigger(), "[action]", T("api.command_remote.remote_add_remove.help", map[string]interface{}{"Actions": AvailableRemoteActions}))
|
||||
|
||||
invite := model.NewAutocompleteData("invite", "", T("api.command_remote.invite.help"))
|
||||
invite.AddNamedTextArgument("password", T("api.command_remote.invite_password.help"), T("api.command_remote.invite_password.hint"), "", true)
|
||||
invite.AddNamedTextArgument("name", T("api.command_remote.name.help"), T("api.command_remote.name.hint"), "", true)
|
||||
invite.AddNamedTextArgument("displayname", T("api.command_remote.displayname.help"), T("api.command_remote.displayname.hint"), "", false)
|
||||
create := model.NewAutocompleteData("create", "", T("api.command_remote.invite.help"))
|
||||
create.AddNamedTextArgument("name", T("api.command_remote.name.help"), T("api.command_remote.name.hint"), "", true)
|
||||
create.AddNamedTextArgument("displayname", T("api.command_remote.displayname.help"), T("api.command_remote.displayname.hint"), "", false)
|
||||
create.AddNamedTextArgument("password", T("api.command_remote.invite_password.help"), T("api.command_remote.invite_password.hint"), "", true)
|
||||
|
||||
accept := model.NewAutocompleteData("accept", "", T("api.command_remote.accept.help"))
|
||||
accept.AddNamedTextArgument("password", T("api.command_remote.invite_password.help"), T("api.command_remote.invite_password.hint"), "", true)
|
||||
accept.AddNamedTextArgument("name", T("api.command_remote.name.help"), T("api.command_remote.name.hint"), "", true)
|
||||
accept.AddNamedTextArgument("displayname", T("api.command_remote.displayname.help"), T("api.command_remote.displayname.hint"), "", false)
|
||||
accept.AddNamedTextArgument("password", T("api.command_remote.invite_password.help"), T("api.command_remote.invite_password.hint"), "", true)
|
||||
accept.AddNamedTextArgument("invite", T("api.command_remote.invitation.help"), T("api.command_remote.invitation.hint"), "", true)
|
||||
|
||||
remove := model.NewAutocompleteData("remove", "", T("api.command_remote.remove.help"))
|
||||
remove.AddNamedDynamicListArgument("remoteId", T("api.command_remote.remove_remote_id.help"), "builtin:remote", true)
|
||||
remove.AddNamedDynamicListArgument("connectionID", T("api.command_remote.remove_remote_id.help"), "builtin:"+CommandTriggerRemote, true)
|
||||
|
||||
status := model.NewAutocompleteData("status", "", T("api.command_remote.status.help"))
|
||||
|
||||
remote.AddCommand(invite)
|
||||
remote.AddCommand(create)
|
||||
remote.AddCommand(accept)
|
||||
remote.AddCommand(remove)
|
||||
remote.AddCommand(status)
|
||||
@@ -69,8 +69,8 @@ func (rp *RemoteProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Co
|
||||
}
|
||||
|
||||
func (rp *RemoteProvider) DoCommand(a *app.App, args *model.CommandArgs, message string) *model.CommandResponse {
|
||||
if !a.HasPermissionTo(args.UserId, model.PERMISSION_MANAGE_SHARED_CHANNELS) {
|
||||
return responsef(args.T("api.command_remote.permission_required", map[string]interface{}{"Permission": "manage_shared_channels"}))
|
||||
if !a.HasPermissionTo(args.UserId, model.PERMISSION_MANAGE_SECURE_CONNECTIONS) {
|
||||
return responsef(args.T("api.command_remote.permission_required", map[string]interface{}{"Permission": "manage_secure_connections"}))
|
||||
}
|
||||
|
||||
margs := parseNamedArgs(args.Command)
|
||||
@@ -80,8 +80,8 @@ func (rp *RemoteProvider) DoCommand(a *app.App, args *model.CommandArgs, message
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "invite":
|
||||
return rp.doInvite(a, args, margs)
|
||||
case "create":
|
||||
return rp.doCreate(a, args, margs)
|
||||
case "accept":
|
||||
return rp.doAccept(a, args, margs)
|
||||
case "remove":
|
||||
@@ -94,19 +94,19 @@ func (rp *RemoteProvider) DoCommand(a *app.App, args *model.CommandArgs, message
|
||||
}
|
||||
|
||||
func (rp *RemoteProvider) GetAutoCompleteListItems(a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
|
||||
if !a.HasPermissionTo(commandArgs.UserId, model.PERMISSION_MANAGE_SHARED_CHANNELS) {
|
||||
return nil, errors.New("You require `manage_shared_channels` permission to manage remote clusters.")
|
||||
if !a.HasPermissionTo(commandArgs.UserId, model.PERMISSION_MANAGE_SECURE_CONNECTIONS) {
|
||||
return nil, errors.New("You require `manage_secure_connections` permission to manage secure connections.")
|
||||
}
|
||||
|
||||
if arg.Name == "remoteId" && strings.Contains(parsed, " remove ") {
|
||||
if arg.Name == "connectionID" && strings.Contains(parsed, " remove ") {
|
||||
return getRemoteClusterAutocompleteListItems(a, true)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("`%s` is not a dynamic argument", arg.Name)
|
||||
}
|
||||
|
||||
// doInvite creates and displays an encrypted invite that can be used by a remote site to establish a simple trust.
|
||||
func (rp *RemoteProvider) doInvite(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
|
||||
// doCreate creates and displays an encrypted invite that can be used by a remote site to establish a simple trust.
|
||||
func (rp *RemoteProvider) doCreate(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
|
||||
password := margs["password"]
|
||||
if password == "" {
|
||||
return responsef(args.T("api.command_remote.missing_empty", map[string]interface{}{"Arg": "password"}))
|
||||
@@ -153,7 +153,7 @@ func (rp *RemoteProvider) doInvite(a *app.App, args *model.CommandArgs, margs ma
|
||||
encoded := base64.URLEncoding.EncodeToString(encrypted)
|
||||
|
||||
return responsef("##### " + args.T("api.command_remote.invitation_created") + "\n" +
|
||||
args.T("api.command_remote.invite_summary", map[string]interface{}{"Command": "/remote accept", "Invitation": encoded, "SiteURL": invite.SiteURL}))
|
||||
args.T("api.command_remote.invite_summary", map[string]interface{}{"Command": "/secure-connection accept", "Invitation": encoded, "SiteURL": invite.SiteURL}))
|
||||
}
|
||||
|
||||
// doAccept accepts an invitation generated by a remote site.
|
||||
@@ -209,7 +209,7 @@ func (rp *RemoteProvider) doAccept(a *app.App, args *model.CommandArgs, margs ma
|
||||
|
||||
// doRemove removes a remote cluster from the database, effectively revoking the trust relationship.
|
||||
func (rp *RemoteProvider) doRemove(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
|
||||
id, ok := margs["remoteId"]
|
||||
id, ok := margs["connectionID"]
|
||||
if !ok {
|
||||
return responsef(args.T("api.command_remote.missing_empty", map[string]interface{}{"Arg": "remoteId"}))
|
||||
}
|
||||
@@ -238,23 +238,16 @@ func (rp *RemoteProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[st
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, args.T("api.command_remote.remote_table_header")+"| \n")
|
||||
fmt.Fprintf(&sb, "| ---- | -------- | ---------- | :-------------: | :----: | ---------- |\n")
|
||||
fmt.Fprintf(&sb, args.T("api.command_remote.remote_table_header")+" \n")
|
||||
// | Secure Connection | Display name | ConnectionID | Site URL | Invite accepted | Online | Last ping |
|
||||
fmt.Fprintf(&sb, "| :---- | :---- | :---- | :---- | :---- | :---- | :---- | \n")
|
||||
|
||||
for _, rc := range list {
|
||||
accepted := ":white_check_mark:"
|
||||
if rc.SiteURL == "" {
|
||||
accepted = ":x:"
|
||||
}
|
||||
accepted := formatBool(args.T, rc.SiteURL != "")
|
||||
online := formatBool(args.T, isOnline(rc.LastPingAt))
|
||||
lastPing := formatTimestamp(rc.LastPingAt)
|
||||
|
||||
online := ":white_check_mark:"
|
||||
if !isOnline(rc.LastPingAt) {
|
||||
online = ":skull_and_crossbones:"
|
||||
}
|
||||
|
||||
lastPing := formatTimestamp(model.GetTimeForMillis(rc.LastPingAt))
|
||||
|
||||
fmt.Fprintf(&sb, "| %s | %s | %s | %s | %s | %s | %s |\n", rc.Name, rc.DisplayName, rc.SiteURL, rc.RemoteId, accepted, online, lastPing)
|
||||
fmt.Fprintf(&sb, "| %s | %s | %s | %s | %s | %s | %s |\n", rc.Name, rc.DisplayName, rc.RemoteId, rc.SiteURL, accepted, online, lastPing)
|
||||
}
|
||||
return responsef(sb.String())
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ type ShareProvider struct {
|
||||
}
|
||||
|
||||
const (
|
||||
CommandTriggerShare = "share"
|
||||
AvailableShareActions = "share_channel, unshare_channel, invite_remove, uninvite_remote, status"
|
||||
CommandTriggerShare = "share-channel"
|
||||
AvailableShareActions = "invite, uninvite, unshare, status"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -32,29 +32,20 @@ func (sp *ShareProvider) GetTrigger() string {
|
||||
func (sp *ShareProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command {
|
||||
share := model.NewAutocompleteData(CommandTriggerShare, "[action]", T("api.command_share.available_actions", map[string]interface{}{"Actions": AvailableShareActions}))
|
||||
|
||||
shareChannel := model.NewAutocompleteData("share_channel", "", T("api.command_share.share_current"))
|
||||
shareChannel.AddNamedTextArgument("readonly", T("api.command_share.share_read_only.help"), T("api.command_share.share_read_only.hint"), "Y|N|y|n", false)
|
||||
shareChannel.AddNamedTextArgument("name", T("api.command_share.channel_name.help"), T("api.command_share.channel_name.hint"), "", false)
|
||||
shareChannel.AddNamedTextArgument("displayname", T("api.command_share.channel_display_name.help"), T("api.command_share.channel_display_name.hint"), "", false)
|
||||
shareChannel.AddNamedTextArgument("purpose", T("api.command_share.channel_purpose.help"), T("api.command_share.channel_purpose.hint"), "", false)
|
||||
shareChannel.AddNamedTextArgument("header", T("api.command_share.channel_header.help"), T("api.command_share.channel_header.hint"), "", false)
|
||||
inviteRemote := model.NewAutocompleteData("invite", "", T("api.command_share.invite_remote.help"))
|
||||
inviteRemote.AddNamedDynamicListArgument("connectionID", T("api.command_share.remote_id.help"), "builtin:"+CommandTriggerShare, true)
|
||||
inviteRemote.AddNamedTextArgument("readonly", T("api.command_share.share_read_only.help"), T("api.command_share.share_read_only.hint"), "Y|N|y|n", false)
|
||||
|
||||
unshareChannel := model.NewAutocompleteData("unshare_channel", "", T("api.command_share.unshare_channel.help"))
|
||||
unshareChannel.AddNamedTextArgument("are_you_sure", T("api.command_share.unshare_confirmation.help"), T("api.command_share.unshare_confirmation.hint"), "Y|N|y|n", true)
|
||||
unInviteRemote := model.NewAutocompleteData("uninvite", "", T("api.command_share.uninvite_remote.help"))
|
||||
unInviteRemote.AddNamedDynamicListArgument("connectionID", T("api.command_share.uninvite_remote_id.help"), "builtin:"+CommandTriggerShare, true)
|
||||
|
||||
inviteRemote := model.NewAutocompleteData("invite_remote", "", T("api.command_share.invite_remote.help"))
|
||||
inviteRemote.AddNamedDynamicListArgument("remoteId", T("api.command_share.remote_id.help"), "builtin:share", true)
|
||||
inviteRemote.AddNamedTextArgument("description", T("api.command_share.description_invite.help"), T("api.command_share.description_invite.hint"), "", false)
|
||||
|
||||
unInviteRemote := model.NewAutocompleteData("uninvite_remote", "", T("api.command_share.uninvite_remote.help"))
|
||||
unInviteRemote.AddNamedDynamicListArgument("remoteId", T("api.command_share.uninvite_remote_id.help"), "builtin:share", true)
|
||||
unshareChannel := model.NewAutocompleteData("unshare", "", T("api.command_share.unshare_channel.help"))
|
||||
|
||||
status := model.NewAutocompleteData("status", "", T("api.command_share.channel_status.help"))
|
||||
|
||||
share.AddCommand(shareChannel)
|
||||
share.AddCommand(unshareChannel)
|
||||
share.AddCommand(inviteRemote)
|
||||
share.AddCommand(unInviteRemote)
|
||||
share.AddCommand(unshareChannel)
|
||||
share.AddCommand(status)
|
||||
|
||||
return &model.Command{
|
||||
@@ -69,15 +60,15 @@ func (sp *ShareProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Com
|
||||
|
||||
func (sp *ShareProvider) GetAutoCompleteListItems(a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
|
||||
switch {
|
||||
case strings.Contains(parsed, " share_channel "):
|
||||
case strings.Contains(parsed, " share "):
|
||||
|
||||
return sp.getAutoCompleteShareChannel(a, commandArgs, arg)
|
||||
|
||||
case strings.Contains(parsed, " invite_remote "):
|
||||
case strings.Contains(parsed, " invite "):
|
||||
|
||||
return sp.getAutoCompleteInviteRemote(a, commandArgs, arg)
|
||||
|
||||
case strings.Contains(parsed, " uninvite_remote "):
|
||||
case strings.Contains(parsed, " uninvite "):
|
||||
|
||||
return sp.getAutoCompleteUnInviteRemote(a, commandArgs, arg)
|
||||
|
||||
@@ -112,7 +103,7 @@ func (sp *ShareProvider) getAutoCompleteShareChannel(a *app.App, commandArgs *mo
|
||||
|
||||
func (sp *ShareProvider) getAutoCompleteInviteRemote(a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg) ([]model.AutocompleteListItem, error) {
|
||||
switch arg.Name {
|
||||
case "remoteId":
|
||||
case "connectionID":
|
||||
return getRemoteClusterAutocompleteListItemsNotInChannel(a, commandArgs.ChannelId, true)
|
||||
default:
|
||||
return nil, fmt.Errorf("%s not a dynamic argument", arg.Name)
|
||||
@@ -121,7 +112,7 @@ func (sp *ShareProvider) getAutoCompleteInviteRemote(a *app.App, commandArgs *mo
|
||||
|
||||
func (sp *ShareProvider) getAutoCompleteUnInviteRemote(a *app.App, _ *model.CommandArgs, arg *model.AutocompleteArg) ([]model.AutocompleteListItem, error) {
|
||||
switch arg.Name {
|
||||
case "remoteId":
|
||||
case "connectionID":
|
||||
return getRemoteClusterAutocompleteListItems(a, true)
|
||||
default:
|
||||
return nil, fmt.Errorf("%s not a dynamic argument", arg.Name)
|
||||
@@ -148,13 +139,13 @@ func (sp *ShareProvider) DoCommand(a *app.App, args *model.CommandArgs, message
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "share_channel":
|
||||
case "share":
|
||||
return sp.doShareChannel(a, args, margs)
|
||||
case "unshare_channel":
|
||||
case "unshare":
|
||||
return sp.doUnshareChannel(a, args, margs)
|
||||
case "invite_remote":
|
||||
case "invite":
|
||||
return sp.doInviteRemote(a, args, margs)
|
||||
case "uninvite_remote":
|
||||
case "uninvite":
|
||||
return sp.doUninviteRemote(a, args, margs)
|
||||
case "status":
|
||||
return sp.doStatus(a, args, margs)
|
||||
@@ -212,15 +203,6 @@ func (sp *ShareProvider) doShareChannel(a *app.App, args *model.CommandArgs, mar
|
||||
}
|
||||
|
||||
func (sp *ShareProvider) doUnshareChannel(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
|
||||
if _, ok := margs["are_you_sure"]; !ok {
|
||||
margs["are_you_sure"] = "N"
|
||||
}
|
||||
|
||||
sure, err := parseBool(margs["are_you_sure"])
|
||||
if err != nil || !sure {
|
||||
return responsef(args.T("api.command_share.shared_channel_not_deleted", map[string]interface{}{"Arg": "are_you_sure", "Expected": "Y"}))
|
||||
}
|
||||
|
||||
sc, appErr := a.GetSharedChannel(args.ChannelId)
|
||||
if appErr != nil {
|
||||
return responsef(args.T("api.command_share.shared_channel_unshare.error", map[string]interface{}{"Error": appErr.Error()}))
|
||||
@@ -240,7 +222,7 @@ func (sp *ShareProvider) doUnshareChannel(a *app.App, args *model.CommandArgs, m
|
||||
}
|
||||
|
||||
func (sp *ShareProvider) doInviteRemote(a *app.App, args *model.CommandArgs, margs map[string]string) (resp *model.CommandResponse) {
|
||||
remoteId, ok := margs["remoteId"]
|
||||
remoteId, ok := margs["connectionID"]
|
||||
if !ok || remoteId == "" {
|
||||
return responsef(args.T("api.command_share.must_specify_valid_remote"))
|
||||
}
|
||||
@@ -284,7 +266,7 @@ func (sp *ShareProvider) doInviteRemote(a *app.App, args *model.CommandArgs, mar
|
||||
return responsef(args.T("api.command_share.channel_invite.error", map[string]interface{}{"Name": rc.DisplayName, "Error": errApp.Error()}))
|
||||
}
|
||||
// send channel invite to remote cluster
|
||||
if err := a.Srv().GetSharedChannelSyncService().SendChannelInvite(channel, args.UserId, margs["description"], rc); err != nil {
|
||||
if err := a.Srv().GetSharedChannelSyncService().SendChannelInvite(channel, args.UserId, rc); err != nil {
|
||||
return responsef(args.T("api.command_share.channel_invite.error", map[string]interface{}{"Name": rc.DisplayName, "Error": err.Error()}))
|
||||
}
|
||||
|
||||
@@ -292,7 +274,7 @@ func (sp *ShareProvider) doInviteRemote(a *app.App, args *model.CommandArgs, mar
|
||||
}
|
||||
|
||||
func (sp *ShareProvider) doUninviteRemote(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
|
||||
remoteId, ok := margs["remoteId"]
|
||||
remoteId, ok := margs["connectionID"]
|
||||
if !ok || remoteId == "" {
|
||||
return responsef(args.T("api.command_share.remote_not_valid"))
|
||||
}
|
||||
@@ -323,19 +305,18 @@ func (sp *ShareProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[str
|
||||
fmt.Fprintf(&sb, args.T("api.command_share.channel_status_id", map[string]interface{}{"ChannelId": statuses[0].ChannelId})+"\n\n")
|
||||
|
||||
fmt.Fprintf(&sb, args.T("api.command_share.remote_table_header")+" \n")
|
||||
fmt.Fprintf(&sb, "| ------ | ------- | ----------- | -------- | -------------- | ------ | --------- | \n")
|
||||
// "| Secure Connection | SiteURL | ReadOnly | InviteAccepted | Online | Last Sync |"
|
||||
fmt.Fprintf(&sb, "| ---- | ---- | ---- | ---- | ---- | ---- | \n")
|
||||
|
||||
for _, status := range statuses {
|
||||
online := ":white_check_mark:"
|
||||
if !isOnline(status.LastPingAt) {
|
||||
online = ":skull_and_crossbones:"
|
||||
}
|
||||
readonly := formatBool(args.T, status.ReadOnly)
|
||||
accepted := formatBool(args.T, status.IsInviteAccepted)
|
||||
online := formatBool(args.T, isOnline(status.LastPingAt))
|
||||
|
||||
lastSync := formatTimestamp(model.GetTimeForMillis(status.NextSyncAt))
|
||||
lastSync := formatTimestamp(status.NextSyncAt)
|
||||
|
||||
fmt.Fprintf(&sb, "| %s | %s | %s | %t | %t | %s | %s |\n",
|
||||
status.DisplayName, status.SiteURL, status.Description,
|
||||
status.ReadOnly, status.IsInviteAccepted, online, lastSync)
|
||||
fmt.Fprintf(&sb, "| %s | %s | %s | %s | %s | %s |\n",
|
||||
status.DisplayName, status.SiteURL, readonly, accepted, online, lastSync)
|
||||
}
|
||||
return responsef(sb.String())
|
||||
}
|
||||
|
||||
@@ -37,12 +37,13 @@ func TestShareProviderDoCommand(t *testing.T) {
|
||||
|
||||
commandProvider := ShareProvider{}
|
||||
channel := th.CreateChannel(th.BasicTeam, WithShared(false))
|
||||
|
||||
args := &model.CommandArgs{
|
||||
T: func(s string, args ...interface{}) string { return s },
|
||||
ChannelId: channel.Id,
|
||||
UserId: th.BasicUser.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
Command: "/share share_channel",
|
||||
Command: "/share-channel share",
|
||||
}
|
||||
|
||||
response := commandProvider.DoCommand(th.App, args, "")
|
||||
@@ -77,7 +78,7 @@ func TestShareProviderDoCommand(t *testing.T) {
|
||||
ChannelId: channel.Id,
|
||||
UserId: th.BasicUser.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
Command: "/share unshare_channel --are_you_sure Y",
|
||||
Command: "/share-channel unshare",
|
||||
}
|
||||
|
||||
response := commandProvider.DoCommand(th.App, args, "")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/shared/i18n"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,12 +73,25 @@ func parseBool(s string) (bool, error) {
|
||||
return false, fmt.Errorf("cannot parse '%s' as a boolean", s)
|
||||
}
|
||||
|
||||
func formatTimestamp(ts time.Time) string {
|
||||
func formatBool(fn i18n.TranslateFunc, b bool) string {
|
||||
if b {
|
||||
return fn("True")
|
||||
}
|
||||
return fn("False")
|
||||
}
|
||||
|
||||
func formatTimestamp(timestamp int64) string {
|
||||
if timestamp == 0 {
|
||||
return "--"
|
||||
}
|
||||
|
||||
ts := model.GetTimeForMillis(timestamp)
|
||||
|
||||
if !isToday(ts) {
|
||||
return ts.Format("Jan 2 15:04:05 MST 2006")
|
||||
}
|
||||
date := ts.Format("15:04:05 MST 2006")
|
||||
return fmt.Sprintf("today %s", date)
|
||||
return fmt.Sprintf("Today %s", date)
|
||||
}
|
||||
|
||||
func isToday(ts time.Time) bool {
|
||||
|
||||
Ссылка в новой задаче
Block a user