[MM-31674] app/product_notices: implement notices for ext. dependencies (#16871)

* app/product_notices: implement notices for ext. dependencies

* remove a log line

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2021-02-17 12:38:52 +03:00
коммит произвёл GitHub
родитель 31a80a5351
Коммит 013c495a97
3 изменённых файлов: 127 добавлений и 13 удалений

Просмотреть файл

@@ -32,6 +32,8 @@ var noticesCache = utils.RequestCache{}
var cachedPostCount int64 var cachedPostCount int64
var cachedUserCount int64 var cachedUserCount int64
var cachedDBMSVersion string
// previously fetched notices // previously fetched notices
var cachedNotices model.ProductNotices var cachedNotices model.ProductNotices
var rcStripRegexp = regexp.MustCompile(`(.*?)(-rc\d+)(.*?)`) var rcStripRegexp = regexp.MustCompile(`(.*?)(-rc\d+)(.*?)`)
@@ -52,7 +54,8 @@ func cleanupVersion(originalVersion string) string {
func noticeMatchesConditions(config *model.Config, preferences store.PreferenceStore, userID string, func noticeMatchesConditions(config *model.Config, preferences store.PreferenceStore, userID string,
client model.NoticeClientType, clientVersion string, postCount int64, userCount int64, isSystemAdmin bool, client model.NoticeClientType, clientVersion string, postCount int64, userCount int64, isSystemAdmin bool,
isTeamAdmin bool, isCloud bool, sku string, notice *model.ProductNotice) (bool, error) { isTeamAdmin bool, isCloud bool, sku, dbName, dbVer string,
notice *model.ProductNotice) (bool, error) {
cnd := notice.Conditions cnd := notice.Conditions
// check client type // check client type
@@ -144,6 +147,27 @@ func noticeMatchesConditions(config *model.Config, preferences store.PreferenceS
} }
} }
if cnd.DeprecatingDependency != nil {
extDepVersion, err := semver.NewVersion(cnd.DeprecatingDependency.MinimumVersion)
if err != nil {
return false, errors.Wrapf(err, "Cannot parse external dependency version %s", cnd.DeprecatingDependency.MinimumVersion)
}
switch cnd.DeprecatingDependency.Name {
case model.DATABASE_DRIVER_MYSQL, model.DATABASE_DRIVER_POSTGRES:
if dbName != cnd.DeprecatingDependency.Name {
return false, nil
}
serverDBMSVersion, err := semver.NewVersion(dbVer)
if err != nil {
return false, errors.Wrapf(err, "Cannot parse DBMS version %s", dbVer)
}
return extDepVersion.GreaterThan(serverDBMSVersion), nil
default:
return false, nil
}
}
// check if our server config matches the notice // check if our server config matches the notice
for k, v := range cnd.ServerConfig { for k, v := range cnd.ServerConfig {
if !validateConfigEntry(config, k, v) { if !validateConfigEntry(config, k, v) {
@@ -225,6 +249,7 @@ func (a *App) GetProductNotices(userID, teamID string, client model.NoticeClient
sku := a.Srv().ClientLicense()["SkuShortName"] sku := a.Srv().ClientLicense()["SkuShortName"]
isCloud := a.Srv().License() != nil && *a.Srv().License().Features.Cloud isCloud := a.Srv().License() != nil && *a.Srv().License().Features.Cloud
dbName := *a.Srv().Config().SqlSettings.DriverName
filteredNotices := make([]model.NoticeMessage, 0) filteredNotices := make([]model.NoticeMessage, 0)
@@ -262,6 +287,8 @@ func (a *App) GetProductNotices(userID, teamID string, client model.NoticeClient
isTeamAdmin, isTeamAdmin,
isCloud, isCloud,
sku, sku,
dbName,
cachedDBMSVersion,
&cachedNotices[noticeIndex]) &cachedNotices[noticeIndex])
if err != nil { if err != nil {
return nil, model.NewAppError("GetProductNotices", "api.system.update_notices.validating_failed", nil, err.Error(), http.StatusBadRequest) return nil, model.NewAppError("GetProductNotices", "api.system.update_notices.validating_failed", nil, err.Error(), http.StatusBadRequest)
@@ -316,6 +343,13 @@ func (a *App) UpdateProductNotices() *model.AppError {
mlog.Warn("Failed to fetch user count", mlog.String("error", err.Error())) mlog.Warn("Failed to fetch user count", mlog.String("error", err.Error()))
} }
cachedDBMSVersion, err = a.Srv().Store.GetDbVersion(false)
if err != nil {
mlog.Warn("Failed to get DBMS version", mlog.String("error", err.Error()))
}
cachedDBMSVersion = strings.Split(cachedDBMSVersion, " ")[0] // get rid of trailing strings attached to the version
data, err := utils.GetUrlWithCache(url, &noticesCache, skip) data, err := utils.GetUrlWithCache(url, &noticesCache, skip)
if err != nil { if err != nil {
return model.NewAppError("UpdateProductNotices", "api.system.update_notices.fetch_failed", nil, err.Error(), http.StatusBadRequest) return model.NewAppError("UpdateProductNotices", "api.system.update_notices.fetch_failed", nil, err.Error(), http.StatusBadRequest)

Просмотреть файл

@@ -60,6 +60,8 @@ func TestNoticeValidation(t *testing.T) {
systemAdmin bool systemAdmin bool
serverVersion string serverVersion string
notice *model.ProductNotice notice *model.ProductNotice
dbmsName string
dbmsVer string
} }
messages := map[string]model.NoticeMessageInternal{ messages := map[string]model.NoticeMessageInternal{
"en": { "en": {
@@ -538,6 +540,76 @@ func TestNoticeValidation(t *testing.T) {
wantErr: false, wantErr: false,
wantOk: true, wantOk: true,
}, },
{
name: "notice with depreacting an external dependency",
args: args{
dbmsName: "mysql",
dbmsVer: "5.6",
notice: &model.ProductNotice{
Conditions: model.Conditions{
DeprecatingDependency: &model.ExternalDependency{
Name: "mysql",
MinimumVersion: "5.7",
},
},
},
},
wantErr: false,
wantOk: true,
},
{
name: "notice with depreacting an external dependency, on a future version",
args: args{
dbmsName: "mysql",
dbmsVer: "5.6",
serverVersion: "5.32",
notice: &model.ProductNotice{
Conditions: model.Conditions{
ServerVersion: []string{">=v5.33"},
DeprecatingDependency: &model.ExternalDependency{
Name: "mysql",
MinimumVersion: "5.7",
},
},
},
},
wantErr: false,
wantOk: false,
},
{
name: "notice on a deprecating dependency, server is all good",
args: args{
dbmsName: "postgres",
dbmsVer: "10",
notice: &model.ProductNotice{
Conditions: model.Conditions{
DeprecatingDependency: &model.ExternalDependency{
Name: "postgres",
MinimumVersion: "10",
},
},
},
},
wantErr: false,
wantOk: false,
},
{
name: "notice on a deprecating dependency, server has different dbms",
args: args{
dbmsName: "mysql",
dbmsVer: "5.7",
notice: &model.ProductNotice{
Conditions: model.Conditions{
DeprecatingDependency: &model.ExternalDependency{
Name: "postgres",
MinimumVersion: "10",
},
},
},
},
wantErr: false,
wantOk: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -561,6 +633,8 @@ func TestNoticeValidation(t *testing.T) {
tt.args.teamAdmin, tt.args.teamAdmin,
tt.args.cloud, tt.args.cloud,
tt.args.sku, tt.args.sku,
tt.args.dbmsName,
tt.args.dbmsVer,
tt.args.notice, tt.args.notice,
); (err != nil) != tt.wantErr { ); (err != nil) != tt.wantErr {
t.Errorf("noticeMatchesConditions() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("noticeMatchesConditions() error = %v, wantErr %v", err, tt.wantErr)

Просмотреть файл

@@ -40,18 +40,19 @@ func (n *ProductNotice) TeamAdminOnly() bool {
} }
type Conditions struct { type Conditions struct {
Audience *NoticeAudience `json:"audience,omitempty"` Audience *NoticeAudience `json:"audience,omitempty"`
ClientType *NoticeClientType `json:"clientType,omitempty"` // Only show the notice on specific clients. Defaults to 'all' 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"] 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 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"` 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"] 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 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 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 } 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"] 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"` 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 } 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 }
DeprecatingDependency *ExternalDependency `json:"deprecating_dependency,omitempty"` // External dependency which is going to be deprecated
} }
type NoticeMessageInternal struct { type NoticeMessageInternal struct {
@@ -212,3 +213,8 @@ type ProductNoticeViewState struct {
Viewed int32 Viewed int32
Timestamp int64 Timestamp int64
} }
type ExternalDependency struct {
Name string `json:"name"`
MinimumVersion string `json:"minimum_version"`
}