MM-27918 In-Product notices support (#15316)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
43ed6ad690
Коммит
4e9ddd4686
@@ -5540,6 +5540,29 @@ func (c *Client4) CheckIntegrity() ([]IntegrityCheckResult, *Response) {
|
||||
return results, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client4) GetNotices(lastViewed int64, teamId string, client NoticeClientType, clientVersion, locale, etag string) (NoticeMessages, *Response) {
|
||||
url := fmt.Sprintf("/system/notices/%s?lastViewed=%d&client=%s&clientVersion=%s&locale=%s", teamId, lastViewed, client, clientVersion, locale)
|
||||
r, appErr := c.DoApiGet(url, etag)
|
||||
if appErr != nil {
|
||||
return nil, BuildErrorResponse(r, appErr)
|
||||
}
|
||||
defer closeBody(r)
|
||||
notices, err := UnmarshalProductNoticeMessages(r.Body)
|
||||
if err != nil {
|
||||
return nil, &Response{StatusCode: http.StatusBadRequest, Error: NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), http.StatusForbidden)}
|
||||
}
|
||||
return notices, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client4) MarkNoticesViewed(ids []string) *Response {
|
||||
r, err := c.DoApiPut("/system/notices/view", ArrayToJson(ids))
|
||||
if err != nil {
|
||||
return BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r)
|
||||
}
|
||||
|
||||
// CreateUpload creates a new upload session.
|
||||
func (c *Client4) CreateUpload(us *UploadSession) (*UploadSession, *Response) {
|
||||
r, err := c.DoApiPost(c.GetUploadsRoute(), us.ToJson())
|
||||
|
||||
@@ -1665,6 +1665,8 @@ type AnnouncementSettings struct {
|
||||
BannerColor *string `access:"site"`
|
||||
BannerTextColor *string `access:"site"`
|
||||
AllowBannerDismissal *bool `access:"site"`
|
||||
AdminNoticesEnabled *bool `access:"site"`
|
||||
UserNoticesEnabled *bool `access:"site"`
|
||||
}
|
||||
|
||||
func (s *AnnouncementSettings) SetDefaults() {
|
||||
@@ -1687,6 +1689,14 @@ func (s *AnnouncementSettings) SetDefaults() {
|
||||
if s.AllowBannerDismissal == nil {
|
||||
s.AllowBannerDismissal = NewBool(true)
|
||||
}
|
||||
|
||||
if s.AdminNoticesEnabled == nil {
|
||||
s.AdminNoticesEnabled = NewBool(true)
|
||||
}
|
||||
|
||||
if s.UserNoticesEnabled == nil {
|
||||
s.UserNoticesEnabled = NewBool(true)
|
||||
}
|
||||
}
|
||||
|
||||
type ThemeSettings struct {
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
JOB_TYPE_MIGRATIONS = "migrations"
|
||||
JOB_TYPE_PLUGINS = "plugins"
|
||||
JOB_TYPE_EXPIRY_NOTIFY = "expiry_notify"
|
||||
JOB_TYPE_PRODUCT_NOTICES = "product_notices"
|
||||
JOB_TYPE_ACTIVE_USERS = "active_users"
|
||||
|
||||
JOB_STATUS_PENDING = "pending"
|
||||
@@ -61,6 +62,7 @@ func (j *Job) IsValid() *AppError {
|
||||
case JOB_TYPE_MESSAGE_EXPORT:
|
||||
case JOB_TYPE_MIGRATIONS:
|
||||
case JOB_TYPE_PLUGINS:
|
||||
case JOB_TYPE_PRODUCT_NOTICES:
|
||||
case JOB_TYPE_EXPIRY_NOTIFY:
|
||||
case JOB_TYPE_ACTIVE_USERS:
|
||||
default:
|
||||
|
||||
210
model/product_notices.go
Обычный файл
210
model/product_notices.go
Обычный файл
@@ -0,0 +1,210 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ProductNotices []ProductNotice
|
||||
|
||||
func (r *ProductNotices) Marshal() ([]byte, error) {
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
func UnmarshalProductNotices(data []byte) (ProductNotices, error) {
|
||||
var r ProductNotices
|
||||
err := json.Unmarshal(data, &r)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// List of product notices. Order is important and is used to resolve priorities.
|
||||
// Each notice will only be show if conditions are met.
|
||||
type ProductNotice struct {
|
||||
Conditions Conditions `json:"conditions"`
|
||||
ID string `json:"id"` // Unique identifier for this notice. Can be a running number. Used for storing 'viewed'; state on the server.
|
||||
LocalizedMessages map[string]NoticeMessageInternal `json:"localizedMessages"` // Notice message data, organized by locale.; Example:; "localizedMessages": {; "en": { "title": "English", description: "English description"},; "frFR": { "title": "Frances", description: "French description"}; }
|
||||
Repeatable *bool `json:"repeatable,omitempty"` // Configurable flag if the notice should reappear after it’s seen and dismissed
|
||||
}
|
||||
|
||||
func (n *ProductNotice) SysAdminOnly() bool {
|
||||
return n.Conditions.Audience != nil && *n.Conditions.Audience == NoticeAudience_Sysadmin
|
||||
}
|
||||
|
||||
func (n *ProductNotice) TeamAdminOnly() bool {
|
||||
return n.Conditions.Audience != nil && *n.Conditions.Audience == NoticeAudience_TeamAdmin
|
||||
}
|
||||
|
||||
type Conditions struct {
|
||||
Audience *NoticeAudience `json:"audience,omitempty"`
|
||||
ClientType *NoticeClientType `json:"clientType,omitempty"` // Only show the notice on specific clients. Defaults to 'all'
|
||||
DesktopVersion []string `json:"desktopVersion,omitempty"` // What desktop client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
|
||||
DisplayDate *string `json:"displayDate,omitempty"` // When to display the notice.; Examples:; "2020-03-01T00:00:00Z" - show on specified date; ">= 2020-03-01T00:00:00Z" - show after specified date; "< 2020-03-01T00:00:00Z" - show before the specified date; "> 2020-03-01T00:00:00Z <= 2020-04-01T00:00:00Z" - show only between the specified dates
|
||||
InstanceType *NoticeInstanceType `json:"instanceType,omitempty"`
|
||||
MobileVersion []string `json:"mobileVersion,omitempty"` // What mobile client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
|
||||
NumberOfPosts *int64 `json:"numberOfPosts,omitempty"` // Only show the notice when server has more than specified number of posts
|
||||
NumberOfUsers *int64 `json:"numberOfUsers,omitempty"` // Only show the notice when server has more than specified number of users
|
||||
ServerConfig map[string]interface{} `json:"serverConfig,omitempty"` // Map of mattermost server config paths and their values. Notice will be displayed only if; the values match the target server config; Example: serverConfig: { "PluginSettings.Enable": true, "GuestAccountsSettings.Enable":; false }
|
||||
ServerVersion []string `json:"serverVersion,omitempty"` // What server versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
|
||||
Sku *NoticeSKU `json:"sku,omitempty"`
|
||||
UserConfig map[string]interface{} `json:"userConfig,omitempty"` // Map of user's settings and their values. Notice will be displayed only if the values; match the viewing users' config; Example: userConfig: { "new_sidebar.disabled": true }
|
||||
}
|
||||
|
||||
type NoticeMessageInternal struct {
|
||||
Action *NoticeAction `json:"action,omitempty"` // Optional action to perform on action button click. (defaults to closing the notice)
|
||||
ActionParam *string `json:"actionParam,omitempty"` // Optional action parameter.; Example: {"action": "url", actionParam: "/console/some-page"}
|
||||
ActionText *string `json:"actionText,omitempty"` // Optional override for the action button text (defaults to OK)
|
||||
Description string `json:"description"` // Notice content. Use {{Mattermost}} instead of plain text to support white-labeling. Text; supports Markdown.
|
||||
Image *string `json:"image,omitempty"`
|
||||
Title string `json:"title"` // Notice title. Use {{Mattermost}} instead of plain text to support white-labeling. Text; supports Markdown.
|
||||
}
|
||||
type NoticeMessages []NoticeMessage
|
||||
|
||||
type NoticeMessage struct {
|
||||
NoticeMessageInternal
|
||||
ID string `json:"id"`
|
||||
SysAdminOnly bool `json:"sysAdminOnly"`
|
||||
TeamAdminOnly bool `json:"teamAdminOnly"`
|
||||
}
|
||||
|
||||
func (r *NoticeMessages) Marshal() ([]byte, error) {
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
func UnmarshalProductNoticeMessages(data io.Reader) (NoticeMessages, error) {
|
||||
var r NoticeMessages
|
||||
err := json.NewDecoder(data).Decode(&r)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// User role, i.e. who will see the notice. Defaults to "all"
|
||||
type NoticeAudience string
|
||||
|
||||
func NewNoticeAudience(s NoticeAudience) *NoticeAudience {
|
||||
return &s
|
||||
}
|
||||
|
||||
func (a *NoticeAudience) Matches(sysAdmin bool, teamAdmin bool) bool {
|
||||
switch *a {
|
||||
case NoticeAudience_All:
|
||||
return true
|
||||
case NoticeAudience_Member:
|
||||
return !sysAdmin && !teamAdmin
|
||||
case NoticeAudience_Sysadmin:
|
||||
return sysAdmin
|
||||
case NoticeAudience_TeamAdmin:
|
||||
return teamAdmin
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
NoticeAudience_All NoticeAudience = "all"
|
||||
NoticeAudience_Member NoticeAudience = "member"
|
||||
NoticeAudience_Sysadmin NoticeAudience = "sysadmin"
|
||||
NoticeAudience_TeamAdmin NoticeAudience = "teamadmin"
|
||||
)
|
||||
|
||||
// Only show the notice on specific clients. Defaults to 'all'
|
||||
//
|
||||
// Client type. Defaults to "all"
|
||||
type NoticeClientType string
|
||||
|
||||
func NewNoticeClientType(s NoticeClientType) *NoticeClientType { return &s }
|
||||
|
||||
func (c *NoticeClientType) Matches(other NoticeClientType) bool {
|
||||
switch *c {
|
||||
case NoticeClientType_All:
|
||||
return true
|
||||
case NoticeClientType_Mobile:
|
||||
return other == NoticeClientType_MobileIos || other == NoticeClientType_MobileAndroid
|
||||
default:
|
||||
return *c == other
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
NoticeClientType_All NoticeClientType = "all"
|
||||
NoticeClientType_Desktop NoticeClientType = "desktop"
|
||||
NoticeClientType_Mobile NoticeClientType = "mobile"
|
||||
NoticeClientType_MobileAndroid NoticeClientType = "mobile-android"
|
||||
NoticeClientType_MobileIos NoticeClientType = "mobile-ios"
|
||||
NoticeClientType_Web NoticeClientType = "web"
|
||||
)
|
||||
|
||||
func NoticeClientTypeFromString(s string) (NoticeClientType, error) {
|
||||
switch s {
|
||||
case "web":
|
||||
return NoticeClientType_Web, nil
|
||||
case "mobile-ios":
|
||||
return NoticeClientType_MobileIos, nil
|
||||
case "mobile-android":
|
||||
return NoticeClientType_MobileAndroid, nil
|
||||
case "desktop":
|
||||
return NoticeClientType_Desktop, nil
|
||||
}
|
||||
return NoticeClientType_All, errors.New("Invalid client type supplied")
|
||||
}
|
||||
|
||||
// Instance type. Defaults to "both"
|
||||
type NoticeInstanceType string
|
||||
|
||||
func NewNoticeInstanceType(n NoticeInstanceType) *NoticeInstanceType { return &n }
|
||||
func (t *NoticeInstanceType) Matches(isCloud bool) bool {
|
||||
if *t == NoticeInstanceType_Both {
|
||||
return true
|
||||
}
|
||||
if *t == NoticeInstanceType_Cloud && !isCloud {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const (
|
||||
NoticeInstanceType_Both NoticeInstanceType = "both"
|
||||
NoticeInstanceType_Cloud NoticeInstanceType = "cloud"
|
||||
NoticeInstanceType_OnPrem NoticeInstanceType = "onprem"
|
||||
)
|
||||
|
||||
// SKU. Defaults to "all"
|
||||
type NoticeSKU string
|
||||
|
||||
func NewNoticeSKU(s NoticeSKU) *NoticeSKU { return &s }
|
||||
func (c *NoticeSKU) Matches(s string) bool {
|
||||
switch *c {
|
||||
case NoticeSKU_All:
|
||||
return true
|
||||
case NoticeSKU_E0, NoticeSKU_Team:
|
||||
return s == ""
|
||||
default:
|
||||
return s == string(*c)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
NoticeSKU_E0 NoticeSKU = "e0"
|
||||
NoticeSKU_E10 NoticeSKU = "e10"
|
||||
NoticeSKU_E20 NoticeSKU = "e20"
|
||||
NoticeSKU_All NoticeSKU = "all"
|
||||
NoticeSKU_Team NoticeSKU = "team"
|
||||
)
|
||||
|
||||
// Optional action to perform on action button click. (defaults to closing the notice)
|
||||
//
|
||||
// Possible actions to execute on button press
|
||||
type NoticeAction string
|
||||
|
||||
const (
|
||||
URL NoticeAction = "url"
|
||||
)
|
||||
|
||||
// Definition of the table keeping the 'viewed' state of each in-product notice per user
|
||||
type ProductNoticeViewState struct {
|
||||
UserId string
|
||||
NoticeId string
|
||||
Viewed int32
|
||||
Timestamp int64
|
||||
}
|
||||
Ссылка в новой задаче
Block a user