[MM-54288] Support Packet V2 (#29403)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
091d1bba8b
Коммит
8d4bf4bae0
@@ -32,7 +32,7 @@ require (
|
||||
golang.org/x/oauth2 v0.21.0
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/tools v0.23.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -70,7 +70,7 @@ require (
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
// Hack to prevent the willf/bitset module from being upgraded to 1.2.0.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -4778,18 +4779,19 @@ func (c *Client4) GetFileInfosForPostIncludeDeleted(ctx context.Context, postId
|
||||
// General/System Section
|
||||
|
||||
// GenerateSupportPacket generates and downloads a Support Packet.
|
||||
func (c *Client4) GenerateSupportPacket(ctx context.Context) ([]byte, *Response, error) {
|
||||
// It returns a ReadCloser to the packet and the filename. The caller needs to close the ReadCloser.
|
||||
func (c *Client4) GenerateSupportPacket(ctx context.Context) (io.ReadCloser, string, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.systemRoute()+"/support_packet", "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
return nil, "", BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
data, err := io.ReadAll(r.Body)
|
||||
_, params, err := mime.ParseMediaType(r.Header.Get("Content-Disposition"))
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), NewAppError("GetFile", "model.client.read_job_result_file.app_error", nil, "", r.StatusCode).Wrap(err)
|
||||
return nil, "", BuildResponse(r), fmt.Errorf("could not parse Content-Disposition header: %w", err)
|
||||
}
|
||||
return data, BuildResponse(r), nil
|
||||
|
||||
return r.Body, params["filename"], BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above.
|
||||
|
||||
@@ -5,6 +5,8 @@ package model
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/utils/timeutils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -103,6 +105,75 @@ func (j *Job) Auditable() map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Job) MarshalYAML() (any, error) {
|
||||
return struct {
|
||||
Id string `yaml:"id"`
|
||||
Type string `yaml:"type"`
|
||||
Priority int64 `yaml:"priority"`
|
||||
CreateAt string `yaml:"create_at"`
|
||||
StartAt string `yaml:"start_at"`
|
||||
LastActivityAt string `yaml:"last_activity_at"`
|
||||
Status string `yaml:"status"`
|
||||
Progress int64 `yaml:"progress"`
|
||||
Data StringMap `yaml:"data"`
|
||||
}{
|
||||
Id: j.Id,
|
||||
Type: j.Type,
|
||||
Priority: j.Priority,
|
||||
CreateAt: timeutils.FormatMillis(j.CreateAt),
|
||||
StartAt: timeutils.FormatMillis(j.StartAt),
|
||||
LastActivityAt: timeutils.FormatMillis(j.LastActivityAt),
|
||||
Status: j.Status,
|
||||
Progress: j.Progress,
|
||||
Data: j.Data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (j *Job) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
out := struct {
|
||||
Id string `yaml:"id"`
|
||||
Type string `yaml:"type"`
|
||||
Priority int64 `yaml:"priority"`
|
||||
CreateAt string `yaml:"create_at"`
|
||||
StartAt string `yaml:"start_at"`
|
||||
LastActivityAt string `yaml:"last_activity_at"`
|
||||
Status string `yaml:"status"`
|
||||
Progress int64 `yaml:"progress"`
|
||||
Data StringMap `yaml:"data"`
|
||||
}{}
|
||||
|
||||
err := unmarshal(&out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createAt, err := timeutils.ParseFormatedMillis(out.CreateAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAt, err := timeutils.ParseFormatedMillis(out.StartAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteAt, err := timeutils.ParseFormatedMillis(out.LastActivityAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*j = Job{
|
||||
Id: out.Id,
|
||||
Type: out.Type,
|
||||
Priority: out.Priority,
|
||||
CreateAt: createAt,
|
||||
StartAt: updateAt,
|
||||
LastActivityAt: deleteAt,
|
||||
Status: out.Status,
|
||||
Progress: out.Progress,
|
||||
Data: out.Data,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *Job) IsValid() *AppError {
|
||||
if !IsValidId(j.Id) {
|
||||
return NewAppError("Job.IsValid", "model.job.is_valid.id.app_error", nil, "id="+j.Id, http.StatusBadRequest)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type PluginOption struct {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type PacketType string
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestPacketMetadataValidate(t *testing.T) {
|
||||
|
||||
@@ -6,6 +6,8 @@ package model
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/utils/timeutils"
|
||||
)
|
||||
|
||||
// SysconsoleAncillaryPermissions maps the non-sysconsole permissions required by each sysconsole view.
|
||||
@@ -438,6 +440,84 @@ func (r *Role) Auditable() map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Role) Sanitize() {
|
||||
r.DisplayName = FakeSetting
|
||||
r.Description = FakeSetting
|
||||
}
|
||||
|
||||
func (r *Role) MarshalYAML() (any, error) {
|
||||
return struct {
|
||||
Id string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
DisplayName string `yaml:"display_name"`
|
||||
Description string `yaml:"description"`
|
||||
CreateAt string `yaml:"create_at"`
|
||||
UpdateAt string `yaml:"update_at"`
|
||||
DeleteAt string `yaml:"delete_at"`
|
||||
Permissions []string `yaml:"permissions"`
|
||||
SchemeManaged bool `yaml:"scheme_managed"`
|
||||
BuiltIn bool `yaml:"built_in"`
|
||||
}{
|
||||
Id: r.Id,
|
||||
Name: r.Name,
|
||||
DisplayName: r.DisplayName,
|
||||
Description: r.Description,
|
||||
CreateAt: timeutils.FormatMillis(r.CreateAt),
|
||||
UpdateAt: timeutils.FormatMillis(r.UpdateAt),
|
||||
DeleteAt: timeutils.FormatMillis(r.DeleteAt),
|
||||
Permissions: r.Permissions,
|
||||
SchemeManaged: r.SchemeManaged,
|
||||
BuiltIn: r.BuiltIn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Role) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
out := struct {
|
||||
Id string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
DisplayName string `yaml:"display_name"`
|
||||
Description string `yaml:"description"`
|
||||
CreateAt string `yaml:"create_at"`
|
||||
UpdateAt string `yaml:"update_at"`
|
||||
DeleteAt string `yaml:"delete_at"`
|
||||
Permissions []string `yaml:"permissions"`
|
||||
SchemeManaged bool `yaml:"scheme_managed"`
|
||||
BuiltIn bool `yaml:"built_in"`
|
||||
}{}
|
||||
|
||||
err := unmarshal(&out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createAt, err := timeutils.ParseFormatedMillis(out.CreateAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAt, err := timeutils.ParseFormatedMillis(out.UpdateAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteAt, err := timeutils.ParseFormatedMillis(out.DeleteAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = Role{
|
||||
Id: out.Id,
|
||||
Name: out.Name,
|
||||
DisplayName: out.DisplayName,
|
||||
Description: out.Description,
|
||||
CreateAt: createAt,
|
||||
UpdateAt: updateAt,
|
||||
DeleteAt: deleteAt,
|
||||
Permissions: out.Permissions,
|
||||
SchemeManaged: out.SchemeManaged,
|
||||
BuiltIn: out.BuiltIn,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RolePatch struct {
|
||||
Permissions *[]string `json:"permissions"`
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ package model
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/utils/timeutils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -62,6 +64,117 @@ func (scheme *Scheme) Auditable() map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func (scheme *Scheme) Sanitize() {
|
||||
scheme.Name = FakeSetting
|
||||
scheme.DisplayName = FakeSetting
|
||||
scheme.Description = FakeSetting
|
||||
}
|
||||
|
||||
func (scheme *Scheme) MarshalYAML() (any, error) {
|
||||
return struct {
|
||||
Id string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
DisplayName string `yaml:"display_name"`
|
||||
Description string `yaml:"description"`
|
||||
CreateAt string `yaml:"create_at"`
|
||||
UpdateAt string `yaml:"update_at"`
|
||||
DeleteAt string `yaml:"delete_at"`
|
||||
Scope string `yaml:"scope"`
|
||||
DefaultTeamAdminRole string `yaml:"default_team_admin_role"`
|
||||
DefaultTeamUserRole string `yaml:"default_team_user_role"`
|
||||
DefaultChannelAdminRole string `yaml:"default_channel_admin_role"`
|
||||
DefaultChannelUserRole string `yaml:"default_channel_user_role"`
|
||||
DefaultTeamGuestRole string `yaml:"default_team_guest_role"`
|
||||
DefaultChannelGuestRole string `yaml:"default_channel_guest_role"`
|
||||
DefaultPlaybookAdminRole string `yaml:"default_playbook_admin_role"`
|
||||
DefaultPlaybookMemberRole string `yaml:"default_playbook_member_role"`
|
||||
DefaultRunAdminRole string `yaml:"default_run_admin_role"`
|
||||
DefaultRunMemberRole string `yaml:"default_run_member_role"`
|
||||
}{
|
||||
Id: scheme.Id,
|
||||
Name: scheme.Name,
|
||||
DisplayName: scheme.DisplayName,
|
||||
Description: scheme.Description,
|
||||
CreateAt: timeutils.FormatMillis(scheme.CreateAt),
|
||||
UpdateAt: timeutils.FormatMillis(scheme.UpdateAt),
|
||||
DeleteAt: timeutils.FormatMillis(scheme.DeleteAt),
|
||||
Scope: scheme.Scope,
|
||||
DefaultTeamAdminRole: scheme.DefaultTeamAdminRole,
|
||||
DefaultTeamUserRole: scheme.DefaultTeamUserRole,
|
||||
DefaultChannelAdminRole: scheme.DefaultChannelAdminRole,
|
||||
DefaultChannelUserRole: scheme.DefaultChannelUserRole,
|
||||
DefaultTeamGuestRole: scheme.DefaultTeamGuestRole,
|
||||
DefaultChannelGuestRole: scheme.DefaultChannelGuestRole,
|
||||
DefaultPlaybookAdminRole: scheme.DefaultPlaybookAdminRole,
|
||||
DefaultPlaybookMemberRole: scheme.DefaultPlaybookMemberRole,
|
||||
DefaultRunAdminRole: scheme.DefaultRunAdminRole,
|
||||
DefaultRunMemberRole: scheme.DefaultRunMemberRole,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (scheme *Scheme) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
out := struct {
|
||||
Id string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
DisplayName string `yaml:"display_name"`
|
||||
Description string `yaml:"description"`
|
||||
CreateAt string `yaml:"create_at"`
|
||||
UpdateAt string `yaml:"update_at"`
|
||||
DeleteAt string `yaml:"delete_at"`
|
||||
Scope string `yaml:"scope"`
|
||||
DefaultTeamAdminRole string `yaml:"default_team_admin_role"`
|
||||
DefaultTeamUserRole string `yaml:"default_team_user_role"`
|
||||
DefaultChannelAdminRole string `yaml:"default_channel_admin_role"`
|
||||
DefaultChannelUserRole string `yaml:"default_channel_user_role"`
|
||||
DefaultTeamGuestRole string `yaml:"default_team_guest_role"`
|
||||
DefaultChannelGuestRole string `yaml:"default_channel_guest_role"`
|
||||
DefaultPlaybookAdminRole string `yaml:"default_playbook_admin_role"`
|
||||
DefaultPlaybookMemberRole string `yaml:"default_playbook_member_role"`
|
||||
DefaultRunAdminRole string `yaml:"default_run_admin_role"`
|
||||
DefaultRunMemberRole string `yaml:"default_run_member_role"`
|
||||
}{}
|
||||
|
||||
err := unmarshal(&out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createAt, err := timeutils.ParseFormatedMillis(out.CreateAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAt, err := timeutils.ParseFormatedMillis(out.UpdateAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteAt, err := timeutils.ParseFormatedMillis(out.DeleteAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*scheme = Scheme{
|
||||
Id: out.Id,
|
||||
Name: out.Name,
|
||||
DisplayName: out.DisplayName,
|
||||
Description: out.Description,
|
||||
CreateAt: createAt,
|
||||
UpdateAt: updateAt,
|
||||
DeleteAt: deleteAt,
|
||||
Scope: out.Scope,
|
||||
DefaultTeamAdminRole: out.DefaultTeamAdminRole,
|
||||
DefaultTeamUserRole: out.DefaultTeamUserRole,
|
||||
DefaultChannelAdminRole: out.DefaultChannelAdminRole,
|
||||
DefaultChannelUserRole: out.DefaultChannelUserRole,
|
||||
DefaultTeamGuestRole: out.DefaultTeamGuestRole,
|
||||
DefaultChannelGuestRole: out.DefaultChannelGuestRole,
|
||||
DefaultPlaybookAdminRole: out.DefaultPlaybookAdminRole,
|
||||
DefaultPlaybookMemberRole: out.DefaultPlaybookMemberRole,
|
||||
DefaultRunAdminRole: out.DefaultRunAdminRole,
|
||||
DefaultRunMemberRole: out.DefaultRunMemberRole,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SchemePatch struct {
|
||||
Name *string `json:"name"`
|
||||
DisplayName *string `json:"display_name"`
|
||||
|
||||
@@ -9,72 +9,120 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SupportPacketErrorFile = "warning.txt"
|
||||
CurrentSupportPacketVersion = 1
|
||||
SupportPacketErrorFile = "warning.txt"
|
||||
)
|
||||
|
||||
type SupportPacket struct {
|
||||
/* Build information */
|
||||
type SupportPacketDiagnostics struct {
|
||||
Version int `yaml:"version"`
|
||||
|
||||
ServerOS string `yaml:"server_os"`
|
||||
ServerArchitecture string `yaml:"server_architecture"`
|
||||
ServerVersion string `yaml:"server_version"`
|
||||
BuildHash string `yaml:"build_hash"`
|
||||
License struct {
|
||||
Company string `yaml:"company"`
|
||||
Users int `yaml:"users"`
|
||||
SkuShortName string `yaml:"sku_short_name"`
|
||||
IsTrial bool `yaml:"is_trial,omitempty"`
|
||||
IsGovSKU bool `yaml:"is_gov_sku,omitempty"`
|
||||
} `yaml:"license"`
|
||||
|
||||
/* DB */
|
||||
Server struct {
|
||||
OS string `yaml:"os"`
|
||||
Architecture string `yaml:"architecture"`
|
||||
Hostname string `yaml:"hostname"`
|
||||
Version string `yaml:"version"`
|
||||
BuildHash string `yaml:"build_hash"`
|
||||
InstallationType string `yaml:"installation_type"`
|
||||
} `yaml:"server"`
|
||||
|
||||
DatabaseType string `yaml:"database_type"`
|
||||
DatabaseVersion string `yaml:"database_version"`
|
||||
DatabaseSchemaVersion string `yaml:"database_schema_version"`
|
||||
WebsocketConnections int `yaml:"websocket_connections"`
|
||||
MasterDbConnections int `yaml:"master_db_connections"`
|
||||
ReplicaDbConnections int `yaml:"read_db_connections"`
|
||||
Config struct {
|
||||
Source string `yaml:"store_type"`
|
||||
} `yaml:"config"`
|
||||
|
||||
/* Cluster */
|
||||
Database struct {
|
||||
Type string `yaml:"type"`
|
||||
Version string `yaml:"version"`
|
||||
SchemaVersion string `yaml:"schema_version"`
|
||||
MasterConnectios int `yaml:"master_connections"`
|
||||
ReplicaConnectios int `yaml:"replica_connections"`
|
||||
SearchConnections int `yaml:"search_connections"`
|
||||
} `yaml:"database"`
|
||||
|
||||
ClusterID string `yaml:"cluster_id"`
|
||||
FileStore struct {
|
||||
Status string `yaml:"file_status"`
|
||||
Error string `yaml:"erorr,omitempty"`
|
||||
Driver string `yaml:"file_driver"`
|
||||
} `yaml:"file_store"`
|
||||
|
||||
/* File store */
|
||||
Websocket struct {
|
||||
Connections int `yaml:"connections"`
|
||||
} `yaml:"websocket"`
|
||||
|
||||
FileDriver string `yaml:"file_driver"`
|
||||
FileStatus string `yaml:"file_status"`
|
||||
Cluster struct {
|
||||
ID string `yaml:"id"`
|
||||
NumberOfNodes int `yaml:"number_of_nodes"`
|
||||
} `yaml:"cluster"`
|
||||
|
||||
/* LDAP */
|
||||
LDAP struct {
|
||||
Status string `yaml:"status,omitempty"`
|
||||
Error string `yaml:"erorr,omitempty"`
|
||||
ServerName string `yaml:"server_name,omitempty"`
|
||||
ServerVersion string `yaml:"server_version,omitempty"`
|
||||
} `yaml:"ldap"`
|
||||
|
||||
LdapVendorName string `yaml:"ldap_vendor_name,omitempty"`
|
||||
LdapVendorVersion string `yaml:"ldap_vendor_version,omitempty"`
|
||||
ElasticSearch struct {
|
||||
ServerVersion string `yaml:"server_version,omitempty"`
|
||||
ServerPlugins []string `yaml:"server_plugins,omitempty"`
|
||||
} `yaml:"elastic"`
|
||||
}
|
||||
|
||||
/* Elastic Search */
|
||||
|
||||
ElasticServerVersion string `yaml:"elastic_server_version,omitempty"`
|
||||
ElasticServerPlugins []string `yaml:"elastic_server_plugins,omitempty"`
|
||||
|
||||
/* License */
|
||||
|
||||
LicenseTo string `yaml:"license_to"`
|
||||
LicenseSupportedUsers int `yaml:"license_supported_users"`
|
||||
LicenseIsTrial bool `yaml:"license_is_trial,omitempty"`
|
||||
|
||||
/* Server stats */
|
||||
|
||||
ActiveUsers int `yaml:"active_users"`
|
||||
DailyActiveUsers int `yaml:"daily_active_users"`
|
||||
MonthlyActiveUsers int `yaml:"monthly_active_users"`
|
||||
InactiveUserCount int `yaml:"inactive_user_count"`
|
||||
TotalPosts int `yaml:"total_posts"`
|
||||
TotalChannels int `yaml:"total_channels"`
|
||||
TotalTeams int `yaml:"total_teams"`
|
||||
|
||||
/* Jobs */
|
||||
type SupportPacketStats struct {
|
||||
RegisteredUsers int64 `yaml:"registered_users"`
|
||||
ActiveUsers int64 `yaml:"active_users"`
|
||||
DailyActiveUsers int64 `yaml:"daily_active_users"`
|
||||
MonthlyActiveUsers int64 `yaml:"monthly_active_users"`
|
||||
DeactivatedUsers int64 `yaml:"deactivated_users"`
|
||||
Guests int64 `yaml:"guests"`
|
||||
BotAccounts int64 `yaml:"bot_accounts"`
|
||||
Posts int64 `yaml:"posts"`
|
||||
Channels int64 `yaml:"channels"`
|
||||
Teams int64 `yaml:"teams"`
|
||||
SlashCommands int64 `yaml:"slash_commands"`
|
||||
IncomingWebhooks int64 `yaml:"incoming_webhooks"`
|
||||
OutgoingWebhooks int64 `yaml:"outgoing_webhooks"`
|
||||
}
|
||||
|
||||
// SupportPacketJobList contains the list of latest run enterprise job runs.
|
||||
// It is included in the Support Packet.
|
||||
type SupportPacketJobList struct {
|
||||
LDAPSyncJobs []*Job `yaml:"ldap_sync_jobs"`
|
||||
DataRetentionJobs []*Job `yaml:"data_retention_jobs"`
|
||||
MessageExportJobs []*Job `yaml:"message_export_jobs"`
|
||||
ElasticPostIndexingJobs []*Job `yaml:"elastic_post_indexing_jobs"`
|
||||
ElasticPostAggregationJobs []*Job `yaml:"elastic_post_aggregation_jobs"`
|
||||
BlevePostIndexingJobs []*Job `yaml:"bleve_post_indexin_jobs"`
|
||||
LdapSyncJobs []*Job `yaml:"ldap_sync_jobs"`
|
||||
MigrationJobs []*Job `yaml:"migration_jobs"`
|
||||
}
|
||||
|
||||
// SupportPacketPermissionInfo contains the list of schemes and the list of roles.
|
||||
// It is included in the Support Packet.
|
||||
type SupportPacketPermissionInfo struct {
|
||||
Roles []*Role `yaml:"roles"`
|
||||
Schemes []*Scheme `yaml:"schemes"`
|
||||
}
|
||||
|
||||
// SupportPacketConfig contains the Mattermost configuration. In contrast to [Config], it also contains the list of Feature Flags.
|
||||
// It is included in the Support Packet.
|
||||
type SupportPacketConfig struct {
|
||||
*Config
|
||||
FeatureFlags FeatureFlags `json:"FeatureFlags"`
|
||||
}
|
||||
|
||||
// SupportPacketPluginList contains the list of enabled and disabled plugins.
|
||||
// It is included in the Support Packet.
|
||||
type SupportPacketPluginList struct {
|
||||
Enabled []Manifest `json:"enabled"`
|
||||
Disabled []Manifest `json:"disabled"`
|
||||
}
|
||||
|
||||
type FileData struct {
|
||||
Filename string
|
||||
Body []byte
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/plugin/plugintest"
|
||||
|
||||
@@ -6,6 +6,8 @@ package utils
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CommonBaseSearchPaths() []string {
|
||||
@@ -111,3 +113,25 @@ func FindDirRelBinary(dir string) (string, bool) {
|
||||
}
|
||||
return found, true
|
||||
}
|
||||
|
||||
// Valid characters are: alphanumeric, dash, underscore
|
||||
var safeFileNameRegex = regexp.MustCompile(`[^\w\-\_]`)
|
||||
|
||||
// SanitizeFileName takes a string and returns a safe file name without an extension.
|
||||
func SanitizeFileName(input string) string {
|
||||
// Trim leading or trailing dots or spaces
|
||||
safeName := strings.Trim(input, ". ")
|
||||
// Replace dots with nothing
|
||||
safeName = strings.ReplaceAll(safeName, ".", "")
|
||||
|
||||
// Replace all invalid characters with an underscore
|
||||
safeName = safeFileNameRegex.ReplaceAllString(safeName, "_")
|
||||
|
||||
// Limit length
|
||||
const maxLength = 100
|
||||
if len(safeName) > maxLength {
|
||||
safeName = safeName[:maxLength]
|
||||
}
|
||||
|
||||
return safeName
|
||||
}
|
||||
|
||||
@@ -7,12 +7,69 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSanitizeFileName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "normal characters",
|
||||
input: "normal-file_name",
|
||||
expected: "normal-file_name",
|
||||
},
|
||||
{
|
||||
name: "special characters",
|
||||
input: "file*name@#$",
|
||||
expected: "file_name___",
|
||||
},
|
||||
{
|
||||
name: "spaces",
|
||||
input: "my file name",
|
||||
expected: "my_file_name",
|
||||
},
|
||||
{
|
||||
name: "leading/trailing dots and spaces",
|
||||
input: " .filename. ",
|
||||
expected: "filename",
|
||||
},
|
||||
{
|
||||
name: "very long filename",
|
||||
input: strings.Repeat("a", 150) + ".txt",
|
||||
expected: strings.Repeat("a", 100),
|
||||
},
|
||||
{
|
||||
name: "unicode characters",
|
||||
input: "résumé",
|
||||
expected: "r_sum_",
|
||||
},
|
||||
{
|
||||
name: "german umlaute",
|
||||
input: "äëïöüß",
|
||||
expected: "______",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := SanitizeFileName(tt.input)
|
||||
assert.Equal(t, tt.expected, result, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindFile(t *testing.T) {
|
||||
t.Run("files from various paths", func(t *testing.T) {
|
||||
// Create the following directory structure:
|
||||
|
||||
29
server/public/utils/timeutils/time.go
Обычный файл
29
server/public/utils/timeutils/time.go
Обычный файл
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package timeutils
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
RFC3339Milli = "2006-01-02T15:04:05.999Z07:00"
|
||||
)
|
||||
|
||||
func FormatMillis(millis int64) string {
|
||||
return time.UnixMilli(millis).Format(RFC3339Milli)
|
||||
}
|
||||
|
||||
func ParseFormatedMillis(s string) (millis int64, err error) {
|
||||
if s == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
t, err := time.Parse(RFC3339Milli, s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return t.UnixMilli(), nil
|
||||
}
|
||||
57
server/public/utils/timeutils/time_test.go
Обычный файл
57
server/public/utils/timeutils/time_test.go
Обычный файл
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package timeutils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFormatMillis(t *testing.T) {
|
||||
t.Run("zero time", func(t *testing.T) {
|
||||
result := FormatMillis(0)
|
||||
// The concrete time depends on the timezone, so we can't test the exact time
|
||||
assert.Contains(t, result, "1970-01-01")
|
||||
assert.Contains(t, result, "00:00")
|
||||
})
|
||||
|
||||
t.Run("positive time", func(t *testing.T) {
|
||||
result := FormatMillis(1609459200000) // 2021-01-01 00:00:00 UTC
|
||||
assert.Contains(t, result, "2021-01-01")
|
||||
assert.Contains(t, result, "00:00")
|
||||
})
|
||||
|
||||
t.Run("negative time", func(t *testing.T) {
|
||||
result := FormatMillis(-1609459200000) // 1919-01-01 00:00:00 UTC
|
||||
assert.Contains(t, result, "1919-01-01")
|
||||
assert.Contains(t, result, "00:00")
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseFormatedMillis(t *testing.T) {
|
||||
t.Run("empty string", func(t *testing.T) {
|
||||
result, err := ParseFormatedMillis("")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), result)
|
||||
})
|
||||
|
||||
t.Run("valid timestamp", func(t *testing.T) {
|
||||
result, err := ParseFormatedMillis("2021-01-01T00:00:00.000Z")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1609459200000), result)
|
||||
})
|
||||
|
||||
t.Run("invalid format", func(t *testing.T) {
|
||||
result, err := ParseFormatedMillis("2021-01-01")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, int64(0), result)
|
||||
})
|
||||
|
||||
t.Run("invalid date", func(t *testing.T) {
|
||||
result, err := ParseFormatedMillis("2021-13-01T00:00:00.000Z")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, int64(0), result)
|
||||
})
|
||||
}
|
||||
Ссылка в новой задаче
Block a user