Custom status feature (#16835)
* Create the system console setting and send to webapp * MI-1145: Add custom status APIs * MI-1145 Add slash commands to set and clear status * Add validation for custom status API * Trim custom status message * Code refactoring - Run gofmt - Rename constants * Remove sendUserUpdated webhook event * Fix recent custom status length * Update error conditions * Disable /status slash command when config setting is off * MI-1155: Create the feature flag for custom status APIs and slash commands * Move recent custom statuses to user preferences (#7) * Move recent custom statuses to user preferences * Code refactoring and feedback changes * Update slash command text and emoji regex * Make the custom status feature flag off by default * Update SetCustomStatus, handle recents not set better * Update status codes * Update slash command handling * Add telementry settings * Fix i18n order * Revert "Fix i18n order" This reverts commit 499f7eaca8180336f5bcca360cc0133365899e08. * Update i18n strings
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
22308ea21c
Коммит
7585e16d84
@@ -1857,6 +1857,7 @@ type TeamSettings struct {
|
||||
EnableOpenServer *bool `access:"authentication"`
|
||||
EnableUserDeactivation *bool `access:"experimental"`
|
||||
RestrictCreationToDomains *string `access:"authentication"` // telemetry: none
|
||||
EnableCustomUserStatuses *bool `access:"site"`
|
||||
EnableCustomBrand *bool `access:"site"`
|
||||
CustomBrandText *string `access:"site"`
|
||||
CustomDescriptionText *string `access:"site"`
|
||||
@@ -1910,6 +1911,10 @@ func (s *TeamSettings) SetDefaults() {
|
||||
s.RestrictCreationToDomains = NewString("")
|
||||
}
|
||||
|
||||
if s.EnableCustomUserStatuses == nil {
|
||||
s.EnableCustomUserStatuses = NewBool(true)
|
||||
}
|
||||
|
||||
if s.EnableCustomBrand == nil {
|
||||
s.EnableCustomBrand = NewBool(false)
|
||||
}
|
||||
|
||||
103
model/custom_status.go
Обычный файл
103
model/custom_status.go
Обычный файл
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
UserPropsKeyCustomStatus = "customStatus"
|
||||
|
||||
CustomStatusTextMaxRunes = 100
|
||||
MaxRecentCustomStatuses = 5
|
||||
)
|
||||
|
||||
type CustomStatus struct {
|
||||
Emoji string `json:"emoji"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func (cs *CustomStatus) TrimMessage() {
|
||||
runes := []rune(cs.Text)
|
||||
if len(runes) > CustomStatusTextMaxRunes {
|
||||
cs.Text = string(runes[:CustomStatusTextMaxRunes])
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *CustomStatus) ToJson() string {
|
||||
csCopy := *cs
|
||||
b, _ := json.Marshal(csCopy)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func CustomStatusFromJson(data io.Reader) *CustomStatus {
|
||||
var cs *CustomStatus
|
||||
_ = json.NewDecoder(data).Decode(&cs)
|
||||
return cs
|
||||
}
|
||||
|
||||
type RecentCustomStatuses []CustomStatus
|
||||
|
||||
func (rcs *RecentCustomStatuses) Contains(cs *CustomStatus) bool {
|
||||
var csJSON = cs.ToJson()
|
||||
|
||||
// status is empty
|
||||
if cs == nil || csJSON == "" || (cs.Emoji == "" && cs.Text == "") {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, status := range *rcs {
|
||||
if status.ToJson() == csJSON {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (rcs *RecentCustomStatuses) Add(cs *CustomStatus) *RecentCustomStatuses {
|
||||
newRCS := (*rcs)[:0]
|
||||
|
||||
// if same `text` exists in existing recent custom statuses, modify existing status
|
||||
for _, status := range *rcs {
|
||||
if status.Text != cs.Text {
|
||||
newRCS = append(newRCS, status)
|
||||
}
|
||||
}
|
||||
newRCS = append(RecentCustomStatuses{*cs}, newRCS...)
|
||||
if len(newRCS) > MaxRecentCustomStatuses {
|
||||
newRCS = newRCS[:MaxRecentCustomStatuses]
|
||||
}
|
||||
return &newRCS
|
||||
}
|
||||
|
||||
func (rcs *RecentCustomStatuses) Remove(cs *CustomStatus) *RecentCustomStatuses {
|
||||
var csJSON = cs.ToJson()
|
||||
if csJSON == "" || (cs.Emoji == "" && cs.Text == "") {
|
||||
return rcs
|
||||
}
|
||||
|
||||
newRCS := (*rcs)[:0]
|
||||
for _, status := range *rcs {
|
||||
if status.ToJson() != csJSON {
|
||||
newRCS = append(newRCS, status)
|
||||
}
|
||||
}
|
||||
|
||||
return &newRCS
|
||||
}
|
||||
|
||||
func (rcs *RecentCustomStatuses) ToJson() string {
|
||||
rcsCopy := *rcs
|
||||
b, _ := json.Marshal(rcsCopy)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func RecentCustomStatusesFromJson(data io.Reader) *RecentCustomStatuses {
|
||||
var rcs *RecentCustomStatuses
|
||||
_ = json.NewDecoder(data).Decode(&rcs)
|
||||
return rcs
|
||||
}
|
||||
@@ -17,6 +17,12 @@ const (
|
||||
|
||||
var EMOJI_PATTERN = regexp.MustCompile(`:[a-zA-Z0-9_-]+:`)
|
||||
|
||||
// ALL_EMOJI_PATTERN is same as the EMOJI_PATTERN except for allowing a '+' character.
|
||||
// This is to allow the system emoji :+1: to be matched.
|
||||
// We kept a separate variable to avoid renaming help texts for custom emoji's.
|
||||
// TODO: Merge ALL_EMOJI_PATTERN with EMOJI_PATTERN after updating custom emoji help texts
|
||||
var ALL_EMOJI_PATTERN = regexp.MustCompile(`:[a-zA-Z0-9_+-]+:`)
|
||||
|
||||
type Emoji struct {
|
||||
Id string `json:"id"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
|
||||
@@ -18,6 +18,10 @@ type FeatureFlags struct {
|
||||
|
||||
// Toggle on and off support for Collapsed Threads
|
||||
CollapsedThreads bool
|
||||
|
||||
// Toggle on and off support for Custom User Statuses
|
||||
CustomUserStatuses bool
|
||||
|
||||
// Feature flags to control plugin versions
|
||||
PluginIncidentManagement string `plugin_id:"com.mattermost.plugin-incident-management"`
|
||||
}
|
||||
@@ -27,6 +31,7 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.TestBoolFeature = false
|
||||
f.CloudDelinquentEmailJobsEnabled = false
|
||||
f.CollapsedThreads = false
|
||||
f.CustomUserStatuses = false
|
||||
f.PluginIncidentManagement = "1.4.0"
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,12 @@ const (
|
||||
PREFERENCE_NAME_LAST_CHANNEL = "channel"
|
||||
PREFERENCE_NAME_LAST_TEAM = "team"
|
||||
|
||||
PREFERENCE_CATEGORY_CUSTOM_STATUS = "custom_status"
|
||||
PREFERENCE_NAME_RECENT_CUSTOM_STATUSES = "recent_custom_statuses"
|
||||
PREFERENCE_NAME_CUSTOM_STATUS_TUTORIAL_STATE = "custom_status_tutorial_state"
|
||||
|
||||
PREFERENCE_CUSTOM_STATUS_MODAL_VIEWED = "custom_status_modal_viewed"
|
||||
|
||||
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
|
||||
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"
|
||||
|
||||
|
||||
@@ -596,6 +596,16 @@ func (u *User) AddNotifyProp(key string, value string) {
|
||||
u.NotifyProps[key] = value
|
||||
}
|
||||
|
||||
func (u *User) SetCustomStatus(cs *CustomStatus) {
|
||||
u.MakeNonNil()
|
||||
u.Props[UserPropsKeyCustomStatus] = cs.ToJson()
|
||||
}
|
||||
|
||||
func (u *User) ClearCustomStatus() {
|
||||
u.MakeNonNil()
|
||||
u.Props[UserPropsKeyCustomStatus] = ""
|
||||
}
|
||||
|
||||
func (u *User) GetFullName() string {
|
||||
if u.FirstName != "" && u.LastName != "" {
|
||||
return u.FirstName + " " + u.LastName
|
||||
|
||||
Ссылка в новой задаче
Block a user