From 32d98c79e9b8a3d19d02aba72d6b6a15b7d1651f Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Thu, 12 Nov 2015 20:23:03 +0100 Subject: [PATCH 01/10] dont display line breaks in preview mode --- web/sass-files/sass/partials/_post.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index b7609bb7d2..d60bf77856 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -53,6 +53,7 @@ body.ios { top: 0; left: 0; box-shadow: none; + white-space: normal; } .textbox-preview-link, .textbox-help-link { position: absolute; From 11942ef955c67d0b616cfcaa670becea0af624db Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Fri, 13 Nov 2015 00:45:32 +0100 Subject: [PATCH 02/10] fix jumping of textbox upon entering a multiline message --- web/sass-files/sass/partials/_post.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index d60bf77856..da161e54f2 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -284,14 +284,14 @@ body.ios { .custom-textarea { padding-top: 8px; padding-right: 28px; - max-height: 160px; + max-height: 162px !important; overflow: auto; line-height: 1.5; } .textarea-div { padding-top: 8px; padding-right: 30px; - max-height: 160px; + max-height: 163px !important; overflow: auto; line-height: 1.5; } From abec097da19a56a73270a3e9b30198c07b1210ea Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Fri, 13 Nov 2015 03:15:33 +0100 Subject: [PATCH 03/10] PLT-1125: allow to toggle features in frontend --- config/pre_release_features.json | 6 + .../user_settings/user_settings_advanced.jsx | 134 +++++++++++++++--- web/react/utils/client.jsx | 13 ++ web/react/utils/utils.jsx | 4 + web/static/config/pre_release_features.json | 1 + 5 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 config/pre_release_features.json create mode 120000 web/static/config/pre_release_features.json diff --git a/config/pre_release_features.json b/config/pre_release_features.json new file mode 100644 index 0000000000..d1b27ecf9b --- /dev/null +++ b/config/pre_release_features.json @@ -0,0 +1,6 @@ +[ + { + "label": "#1389", + "description": "Show markdown preview option in message input box" + } +] \ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx index ac82595f5e..ae7f410206 100644 --- a/web/react/components/user_settings/user_settings_advanced.jsx +++ b/web/react/components/user_settings/user_settings_advanced.jsx @@ -13,21 +13,38 @@ export default class AdvancedSettingsDisplay extends React.Component { this.updateSection = this.updateSection.bind(this); this.updateSetting = this.updateSetting.bind(this); + this.toggleFeature = this.toggleFeature.bind(this); + this.saveEnabledFeatures = this.saveEnabledFeatures.bind(this); this.setupInitialState = this.setupInitialState.bind(this); - this.state = this.setupInitialState(); + this.state = {settings: {}, preReleaseFeatures: []}; + this.setupInitialState(); } setupInitialState() { - const sendOnCtrlEnter = PreferenceStore.getPreference( - Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, - 'send_on_ctrl_enter', - {value: 'false'} - ).value; - - return { - settings: {send_on_ctrl_enter: sendOnCtrlEnter} + const settings = { + send_on_ctrl_enter: PreferenceStore.getPreference( + Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, + 'send_on_ctrl_enter', + {value: 'false'} + ).value }; + + let enabledFeatures = 0; + PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS).forEach((setting) => { + if (setting.name.lastIndexOf('feature_enabled_') === 0) { + settings[setting.name] = setting.value; + if (setting.value === 'true') { + enabledFeatures++; + } + } + }); + + Client.getAvailablePreReleaseFeatures((data) => { + this.setState({settings, preReleaseFeatures: data || [], enabledFeatures}); + }, () => { + this.setState({settings, preReleaseFeatures: [], enabledFeatures}); + }); } updateSetting(setting, value) { @@ -36,14 +53,45 @@ export default class AdvancedSettingsDisplay extends React.Component { this.setState(settings); } - handleSubmit(setting) { - const preference = PreferenceStore.setPreference( - Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, - setting, - this.state.settings[setting] - ); + toggleFeature(feature, checked) { + const settings = this.state.settings; + settings['feature_enabled_' + feature] = String(checked); - Client.savePreferences([preference], + let enabledFeatures = 0; + Object.keys(this.state.settings).forEach((setting) => { + if (setting.lastIndexOf('feature_enabled_') === 0 && this.state.settings[setting] === 'true') { + enabledFeatures++; + } + }); + + this.setState({settings, enabledFeatures}); + } + + saveEnabledFeatures() { + const features = []; + Object.keys(this.state.settings).forEach((setting) => { + if (setting.lastIndexOf('feature_enabled_') === 0) { + features.push(setting); + } + }); + + this.handleSubmit(features); + } + + handleSubmit(settings) { + const preferences = []; + + (Array.isArray(settings) ? settings : [settings]).forEach((setting) => { + preferences.push( + PreferenceStore.setPreference( + Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, + setting, + String(this.state.settings[setting]) + ) + ); + }); + + Client.savePreferences(preferences, () => { PreferenceStore.emitChange(); this.updateSection(''); @@ -118,6 +166,58 @@ export default class AdvancedSettingsDisplay extends React.Component { ); } + let previewFeaturesSection; + if (this.props.activeSection === 'advancedPreviewFeatures') { + const inputs = []; + + this.state.preReleaseFeatures.forEach((feature) => { + inputs.push( +
+
+ +
+
+ ); + }); + + inputs.push( +
+
+ {'Check any pre-released features you\'d like to preview.'} +
+ ); + + previewFeaturesSection = ( + { + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + previewFeaturesSection = ( + this.props.updateSection('advancedPreviewFeatures')} + /> + ); + } + return (
@@ -145,6 +245,8 @@ export default class AdvancedSettingsDisplay extends React.Component {

{'Advanced Settings'}

{ctrlSendSection} +
+ {previewFeaturesSection}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 09e962161a..628e885f23 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -1320,3 +1320,16 @@ export function regenOutgoingHookToken(data, success, error) { } }); } + +export function getAvailablePreReleaseFeatures(success, error) { + $.ajax({ + url: '/static/config/pre_release_features.json', + dataType: 'json', + type: 'GET', + success, + error: (xhr, status, err) => { + var e = handleError('getAvailablePreReleaseFeatures', xhr, status, err); + error(e); + } + }); +} diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 9b2f7e0575..432cb05e0b 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -1221,3 +1221,7 @@ export function getPostTerm(post) { return postTerm; } + +export function featureToggle(feature) { + return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'feature_enabled_' + feature, {value: 'false'}).value === 'true'; +} diff --git a/web/static/config/pre_release_features.json b/web/static/config/pre_release_features.json new file mode 120000 index 0000000000..fa410e1d12 --- /dev/null +++ b/web/static/config/pre_release_features.json @@ -0,0 +1 @@ +../../../config/pre_release_features.json \ No newline at end of file From 79e9244dc9977c6ec6adc78631c0485efa9f60af Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Fri, 13 Nov 2015 22:56:41 +0100 Subject: [PATCH 04/10] allow to toggle features in backend --- config/pre_release_features.json | 6 --- store/sql_preference_store.go | 47 +++++++++++++++++++++ store/sql_store.go | 2 + store/store.go | 3 +- web/static/config/pre_release_features.json | 7 ++- 5 files changed, 57 insertions(+), 8 deletions(-) delete mode 100644 config/pre_release_features.json mode change 120000 => 100644 web/static/config/pre_release_features.json diff --git a/config/pre_release_features.json b/config/pre_release_features.json deleted file mode 100644 index d1b27ecf9b..0000000000 --- a/config/pre_release_features.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "label": "#1389", - "description": "Show markdown preview option in message input box" - } -] \ No newline at end of file diff --git a/store/sql_preference_store.go b/store/sql_preference_store.go index 8454abcbdd..63561a5095 100644 --- a/store/sql_preference_store.go +++ b/store/sql_preference_store.go @@ -4,6 +4,7 @@ package store import ( + l4g "code.google.com/p/log4go" "github.com/go-gorp/gorp" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" @@ -13,6 +14,10 @@ type SqlPreferenceStore struct { *SqlStore } +const ( + FEATURE_TOGGLE_PREFIX = "feature_enabled_" +) + func NewSqlPreferenceStore(sqlStore *SqlStore) PreferenceStore { s := &SqlPreferenceStore{sqlStore} @@ -36,6 +41,23 @@ func (s SqlPreferenceStore) CreateIndexesIfNotExists() { s.CreateIndexIfNotExists("idx_preferences_name", "Preferences", "Name") } +func (s SqlPreferenceStore) DeleteUnusedFeatures() { + l4g.Debug("Deleting any unused pre-release features") + + sql := `DELETE + FROM Preferences + WHERE + Category = :Category + AND Value = :Value + AND Name LIKE '` + FEATURE_TOGGLE_PREFIX + `%'` + + queryParams := map[string]string{ + "Category": "advanced_settings", + "Value": "false", + } + s.GetMaster().Exec(sql, queryParams) +} + func (s SqlPreferenceStore) Save(preferences *model.Preferences) StoreChannel { storeChannel := make(StoreChannel) @@ -257,3 +279,28 @@ func (s SqlPreferenceStore) PermanentDeleteByUser(userId string) StoreChannel { return storeChannel } + +func (s SqlPreferenceStore) FeatureToggle(feature, userId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + if value, err := s.GetReplica().SelectStr(`SELECT + value + FROM + Preferences + WHERE + UserId = :UserId + AND Category = :Category + AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": "advanced_settings", "Name": FEATURE_TOGGLE_PREFIX + feature}); err != nil { + result.Err = model.NewAppError("SqlPreferenceStore.featureToggle", "We encountered an error while finding a pre release feature preference", err.Error()) + } else { + result.Data = value == "true" + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/sql_store.go b/store/sql_store.go index f348db10bf..d17a3e8c3d 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -148,6 +148,8 @@ func NewSqlStore() Store { sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists() sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists() + sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures() + if model.IsPreviousVersion(schemaVersion) || isSchemaVersion07 || isSchemaVersion10 { sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion}) l4g.Warn("The database schema has been upgraded to version " + model.CurrentVersion) diff --git a/store/store.go b/store/store.go index 338ae186fa..36d991a15a 100644 --- a/store/store.go +++ b/store/store.go @@ -186,4 +186,5 @@ type PreferenceStore interface { GetCategory(userId string, category string) StoreChannel GetAll(userId string) StoreChannel PermanentDeleteByUser(userId string) StoreChannel -} + FeatureToggle(feature, userId string) StoreChannel +} \ No newline at end of file diff --git a/web/static/config/pre_release_features.json b/web/static/config/pre_release_features.json deleted file mode 120000 index fa410e1d12..0000000000 --- a/web/static/config/pre_release_features.json +++ /dev/null @@ -1 +0,0 @@ -../../../config/pre_release_features.json \ No newline at end of file diff --git a/web/static/config/pre_release_features.json b/web/static/config/pre_release_features.json new file mode 100644 index 0000000000..d1b27ecf9b --- /dev/null +++ b/web/static/config/pre_release_features.json @@ -0,0 +1,6 @@ +[ + { + "label": "#1389", + "description": "Show markdown preview option in message input box" + } +] \ No newline at end of file From 6e1e5f7ae4dcc5645a5412e17b36e5d95bf4f7c6 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Fri, 13 Nov 2015 23:05:08 +0100 Subject: [PATCH 05/10] only show advanced settings section if there are any pre-release features --- .../user_settings/user_settings_advanced.jsx | 105 ++++++++++-------- web/react/utils/constants.jsx | 3 +- web/react/utils/utils.jsx | 2 +- 3 files changed, 59 insertions(+), 51 deletions(-) diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx index ae7f410206..cfb360152b 100644 --- a/web/react/components/user_settings/user_settings_advanced.jsx +++ b/web/react/components/user_settings/user_settings_advanced.jsx @@ -32,7 +32,7 @@ export default class AdvancedSettingsDisplay extends React.Component { let enabledFeatures = 0; PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS).forEach((setting) => { - if (setting.name.lastIndexOf('feature_enabled_') === 0) { + if (setting.name.lastIndexOf(Constants.FeatureTogglePrefix) === 0) { settings[setting.name] = setting.value; if (setting.value === 'true') { enabledFeatures++; @@ -55,11 +55,11 @@ export default class AdvancedSettingsDisplay extends React.Component { toggleFeature(feature, checked) { const settings = this.state.settings; - settings['feature_enabled_' + feature] = String(checked); + settings[Constants.FeatureTogglePrefix + feature] = String(checked); let enabledFeatures = 0; Object.keys(this.state.settings).forEach((setting) => { - if (setting.lastIndexOf('feature_enabled_') === 0 && this.state.settings[setting] === 'true') { + if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0 && this.state.settings[setting] === 'true') { enabledFeatures++; } }); @@ -70,7 +70,7 @@ export default class AdvancedSettingsDisplay extends React.Component { saveEnabledFeatures() { const features = []; Object.keys(this.state.settings).forEach((setting) => { - if (setting.lastIndexOf('feature_enabled_') === 0) { + if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0) { features.push(setting); } }); @@ -167,55 +167,62 @@ export default class AdvancedSettingsDisplay extends React.Component { } let previewFeaturesSection; - if (this.props.activeSection === 'advancedPreviewFeatures') { - const inputs = []; + let previewFeaturesSectionDivider; + if (this.state.preReleaseFeatures.length > 0) { + previewFeaturesSectionDivider = ( +
+ ); - this.state.preReleaseFeatures.forEach((feature) => { - inputs.push( -
-
- + if (this.props.activeSection === 'advancedPreviewFeatures') { + const inputs = []; + + this.state.preReleaseFeatures.forEach((feature) => { + inputs.push( +
+
+ +
+ ); + }); + + inputs.push( +
+
+ {'Check any pre-released features you\'d like to preview.'}
); - }); - inputs.push( -
-
- {'Check any pre-released features you\'d like to preview.'} -
- ); - - previewFeaturesSection = ( - { - this.updateSection(''); - e.preventDefault(); - }} - /> - ); - } else { - previewFeaturesSection = ( - this.props.updateSection('advancedPreviewFeatures')} - /> - ); + previewFeaturesSection = ( + { + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + previewFeaturesSection = ( + this.props.updateSection('advancedPreviewFeatures')} + /> + ); + } } return ( @@ -245,7 +252,7 @@ export default class AdvancedSettingsDisplay extends React.Component {

{'Advanced Settings'}

{ctrlSendSection} -
+ {previewFeaturesSectionDivider} {previewFeaturesSection}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 372e155563..31c2b940e5 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -398,5 +398,6 @@ export default { }, NotificationPrefs: { MENTION: 'mention' - } + }, + FeatureTogglePrefix: 'feature_enabled_' }; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 432cb05e0b..58e5704556 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -1223,5 +1223,5 @@ export function getPostTerm(post) { } export function featureToggle(feature) { - return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'feature_enabled_' + feature, {value: 'false'}).value === 'true'; + return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature, {value: 'false'}).value === 'true'; } From ca3856ab3b7d47b42b46db176c6678a24c1fc402 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Tue, 17 Nov 2015 19:01:26 +0100 Subject: [PATCH 06/10] move preview-link-toggle outside of component markup --- web/react/components/textbox.jsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index e2868e9468..1e9e912a8d 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -303,7 +303,19 @@ export default class Textbox extends React.Component { } render() { - const previewLinkVisible = this.props.messageText.length > 0; + let previewLink = null; + if (Utils.featureToggle('#1389')) { + const previewLinkVisible = this.props.messageText.length > 0; + previewLink = ( + + {this.state.preview ? 'Edit message' : 'Preview'} + + ); + } return (
+ {previewLink} {'Help'} - - {this.state.preview ? 'Edit' : 'Preview'} -
); } From 82f86f7050a803fd5ffb7872d9773bcff0fa6b8c Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Thu, 19 Nov 2015 17:54:01 +0100 Subject: [PATCH 07/10] go fmt --- store/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/store.go b/store/store.go index 36d991a15a..ef7dcde3ad 100644 --- a/store/store.go +++ b/store/store.go @@ -187,4 +187,4 @@ type PreferenceStore interface { GetAll(userId string) StoreChannel PermanentDeleteByUser(userId string) StoreChannel FeatureToggle(feature, userId string) StoreChannel -} \ No newline at end of file +} From 192b606bc6da20b1148b14116de0a234814615c9 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Fri, 27 Nov 2015 02:20:06 +0100 Subject: [PATCH 08/10] add unit tests --- model/preference.go | 1 + store/sql_preference_store.go | 4 +- store/sql_preference_store_test.go | 63 ++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/model/preference.go b/model/preference.go index 892ae82aae..4f2ba0099b 100644 --- a/model/preference.go +++ b/model/preference.go @@ -12,6 +12,7 @@ import ( const ( PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show" PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step" + PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings" ) type Preference struct { diff --git a/store/sql_preference_store.go b/store/sql_preference_store.go index 63561a5095..09546c872b 100644 --- a/store/sql_preference_store.go +++ b/store/sql_preference_store.go @@ -52,7 +52,7 @@ func (s SqlPreferenceStore) DeleteUnusedFeatures() { AND Name LIKE '` + FEATURE_TOGGLE_PREFIX + `%'` queryParams := map[string]string{ - "Category": "advanced_settings", + "Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Value": "false", } s.GetMaster().Exec(sql, queryParams) @@ -292,7 +292,7 @@ func (s SqlPreferenceStore) FeatureToggle(feature, userId string) StoreChannel { WHERE UserId = :UserId AND Category = :Category - AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": "advanced_settings", "Name": FEATURE_TOGGLE_PREFIX + feature}); err != nil { + AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Name": FEATURE_TOGGLE_PREFIX + feature}); err != nil { result.Err = model.NewAppError("SqlPreferenceStore.featureToggle", "We encountered an error while finding a pre release feature preference", err.Error()) } else { result.Data = value == "true" diff --git a/store/sql_preference_store_test.go b/store/sql_preference_store_test.go index 77da71fd6f..5aed7dba3b 100644 --- a/store/sql_preference_store_test.go +++ b/store/sql_preference_store_test.go @@ -232,3 +232,66 @@ func TestPreferenceDelete(t *testing.T) { t.Fatal(result.Err) } } + +func TestDeleteUnusedFeatures(t *testing.T) { + Setup() + + userId1 := model.NewId() + userId2 := model.NewId() + category := model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS + feature1 := "feature1" + feature2 := "feature2" + + features := model.Preferences{ + { + UserId: userId1, + Category: category, + Name: FEATURE_TOGGLE_PREFIX + feature1, + Value: "true", + }, + { + UserId: userId2, + Category: category, + Name: FEATURE_TOGGLE_PREFIX + feature1, + Value: "false", + }, + { + UserId: userId1, + Category: category, + Name: FEATURE_TOGGLE_PREFIX + feature2, + Value: "false", + }, + { + UserId: userId2, + Category: category, + Name: FEATURE_TOGGLE_PREFIX + feature2, + Value: "true", + }, + } + + Must(store.Preference().Save(&features)) + + store.(*SqlStore).preference.(*SqlPreferenceStore).DeleteUnusedFeatures() + + //make sure features with value "false" have actually been deleted from the database + if val, err := store.(*SqlStore).preference.(*SqlPreferenceStore).GetReplica().SelectInt(`SELECT COUNT(*) + FROM Preferences + WHERE Category = :Category + AND Value = :Val + AND Name LIKE '`+FEATURE_TOGGLE_PREFIX+`%'`, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Val": "false"}); err != nil { + t.Fatal(err) + } else if val != 0 { + t.Fatalf("Found %d features with value 'false', expected all to be deleted", val) + } + // + // make sure features with value "true" remain saved + if val, err := store.(*SqlStore).preference.(*SqlPreferenceStore).GetReplica().SelectInt(`SELECT COUNT(*) + FROM Preferences + WHERE Category = :Category + AND Value = :Val + AND Name LIKE '`+FEATURE_TOGGLE_PREFIX+`%'`, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Val": "true"}); err != nil { + t.Fatal(err) + } else if val == 0 { + t.Fatalf("Found %d features with value 'true', expected to find at least %d features", val, 2) + } +} From 61a35066b64085d264fbd049e2a57f70125ca65a Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Fri, 27 Nov 2015 23:16:56 +0100 Subject: [PATCH 09/10] rename 'featureToggle' to 'isFeatureEnable' and move definition of available prerelease-features to constants.jsx --- store/sql_preference_store.go | 4 +-- store/store.go | 2 +- web/react/components/textbox.jsx | 3 +- .../user_settings/user_settings_advanced.jsx | 35 +++++++++---------- web/react/utils/client.jsx | 13 ------- web/react/utils/constants.jsx | 8 ++++- web/react/utils/utils.jsx | 4 +-- web/static/config/pre_release_features.json | 6 ---- 8 files changed, 30 insertions(+), 45 deletions(-) delete mode 100644 web/static/config/pre_release_features.json diff --git a/store/sql_preference_store.go b/store/sql_preference_store.go index 09546c872b..f73dad3ace 100644 --- a/store/sql_preference_store.go +++ b/store/sql_preference_store.go @@ -280,7 +280,7 @@ func (s SqlPreferenceStore) PermanentDeleteByUser(userId string) StoreChannel { return storeChannel } -func (s SqlPreferenceStore) FeatureToggle(feature, userId string) StoreChannel { +func (s SqlPreferenceStore) IsFeatureEnabled(feature, userId string) StoreChannel { storeChannel := make(StoreChannel) go func() { @@ -293,7 +293,7 @@ func (s SqlPreferenceStore) FeatureToggle(feature, userId string) StoreChannel { UserId = :UserId AND Category = :Category AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Name": FEATURE_TOGGLE_PREFIX + feature}); err != nil { - result.Err = model.NewAppError("SqlPreferenceStore.featureToggle", "We encountered an error while finding a pre release feature preference", err.Error()) + result.Err = model.NewAppError("SqlPreferenceStore.IsFeatureEnabled", "We encountered an error while finding a pre release feature preference", err.Error()) } else { result.Data = value == "true" } diff --git a/store/store.go b/store/store.go index ef7dcde3ad..0695ea27fe 100644 --- a/store/store.go +++ b/store/store.go @@ -186,5 +186,5 @@ type PreferenceStore interface { GetCategory(userId string, category string) StoreChannel GetAll(userId string) StoreChannel PermanentDeleteByUser(userId string) StoreChannel - FeatureToggle(feature, userId string) StoreChannel + IsFeatureEnabled(feature, userId string) StoreChannel } diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index 1e9e912a8d..10b3c0069d 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -11,6 +11,7 @@ import * as Utils from '../utils/utils.jsx'; import Constants from '../utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; const KeyCodes = Constants.KeyCodes; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; export default class Textbox extends React.Component { constructor(props) { @@ -304,7 +305,7 @@ export default class Textbox extends React.Component { render() { let previewLink = null; - if (Utils.featureToggle('#1389')) { + if (Utils.isFeatureEnabled(PreReleaseFeatures.MARKDOWN_PREVIEW)) { const previewLinkVisible = this.props.messageText.length > 0; previewLink = ( { - if (setting.name.lastIndexOf(Constants.FeatureTogglePrefix) === 0) { - settings[setting.name] = setting.value; - if (setting.value === 'true') { - enabledFeatures++; + advancedSettings.forEach((setting) => { + preReleaseFeaturesKeys.forEach((key) => { + const feature = PreReleaseFeatures[key]; + if (setting.name === Constants.FeatureTogglePrefix + feature.label) { + settings[setting.name] = setting.value; + if (setting.value === 'true') { + enabledFeatures++; + } } - } + }); }); - Client.getAvailablePreReleaseFeatures((data) => { - this.setState({settings, preReleaseFeatures: data || [], enabledFeatures}); - }, () => { - this.setState({settings, preReleaseFeatures: [], enabledFeatures}); - }); + this.state = {preReleaseFeatures: PreReleaseFeatures, settings, preReleaseFeaturesKeys, enabledFeatures}; } updateSetting(setting, value) { @@ -168,7 +164,7 @@ export default class AdvancedSettingsDisplay extends React.Component { let previewFeaturesSection; let previewFeaturesSectionDivider; - if (this.state.preReleaseFeatures.length > 0) { + if (this.state.preReleaseFeaturesKeys.length > 0) { previewFeaturesSectionDivider = (
); @@ -176,7 +172,8 @@ export default class AdvancedSettingsDisplay extends React.Component { if (this.props.activeSection === 'advancedPreviewFeatures') { const inputs = []; - this.state.preReleaseFeatures.forEach((feature) => { + this.state.preReleaseFeaturesKeys.forEach((key) => { + const feature = this.state.preReleaseFeatures[key]; inputs.push(
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 628e885f23..09e962161a 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -1320,16 +1320,3 @@ export function regenOutgoingHookToken(data, success, error) { } }); } - -export function getAvailablePreReleaseFeatures(success, error) { - $.ajax({ - url: '/static/config/pre_release_features.json', - dataType: 'json', - type: 'GET', - success, - error: (xhr, status, err) => { - var e = handleError('getAvailablePreReleaseFeatures', xhr, status, err); - error(e); - } - }); -} diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 31c2b940e5..2009e07dd8 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -399,5 +399,11 @@ export default { NotificationPrefs: { MENTION: 'mention' }, - FeatureTogglePrefix: 'feature_enabled_' + FeatureTogglePrefix: 'feature_enabled_', + PRE_RELEASE_FEATURES: { + MARKDOWN_PREVIEW: { + label: 'markdown_preview', // github issue: https://github.com/mattermost/platform/pull/1389 + description: 'Show markdown preview option in message input box' + } + } }; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 58e5704556..80c377d7f0 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -1222,6 +1222,6 @@ export function getPostTerm(post) { return postTerm; } -export function featureToggle(feature) { - return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature, {value: 'false'}).value === 'true'; +export function isFeatureEnabled(feature) { + return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature.label, {value: 'false'}).value === 'true'; } diff --git a/web/static/config/pre_release_features.json b/web/static/config/pre_release_features.json deleted file mode 100644 index d1b27ecf9b..0000000000 --- a/web/static/config/pre_release_features.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "label": "#1389", - "description": "Show markdown preview option in message input box" - } -] \ No newline at end of file From c50144de0475da2c5865cb4246f63d285aa452c4 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Fri, 27 Nov 2015 23:17:26 +0100 Subject: [PATCH 10/10] add missing unit test --- store/sql_preference_store_test.go | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/store/sql_preference_store_test.go b/store/sql_preference_store_test.go index 5aed7dba3b..6f8f44f471 100644 --- a/store/sql_preference_store_test.go +++ b/store/sql_preference_store_test.go @@ -233,6 +233,78 @@ func TestPreferenceDelete(t *testing.T) { } } +func TestIsFeatureEnabled(t *testing.T) { + Setup() + + feature1 := "testFeat1" + feature2 := "testFeat2" + feature3 := "testFeat3" + + userId := model.NewId() + category := model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS + + features := model.Preferences{ + { + UserId: userId, + Category: category, + Name: FEATURE_TOGGLE_PREFIX + feature1, + Value: "true", + }, + { + UserId: userId, + Category: category, + Name: model.NewId(), + Value: "false", + }, + { + UserId: userId, + Category: model.NewId(), + Name: FEATURE_TOGGLE_PREFIX + feature1, + Value: "false", + }, + { + UserId: model.NewId(), + Category: category, + Name: FEATURE_TOGGLE_PREFIX + feature2, + Value: "false", + }, + { + UserId: model.NewId(), + Category: category, + Name: FEATURE_TOGGLE_PREFIX + feature3, + Value: "foobar", + }, + } + + Must(store.Preference().Save(&features)) + + if result := <-store.Preference().IsFeatureEnabled(feature1, userId); result.Err != nil { + t.Fatal(result.Err) + } else if data := result.Data.(bool); data != true { + t.Fatalf("got incorrect setting for feature1, %v=%v", true, data) + } + + if result := <-store.Preference().IsFeatureEnabled(feature2, userId); result.Err != nil { + t.Fatal(result.Err) + } else if data := result.Data.(bool); data != false { + t.Fatalf("got incorrect setting for feature2, %v=%v", false, data) + } + + // make sure we get false if something different than "true" or "false" has been saved to database + if result := <-store.Preference().IsFeatureEnabled(feature3, userId); result.Err != nil { + t.Fatal(result.Err) + } else if data := result.Data.(bool); data != false { + t.Fatalf("got incorrect setting for feature3, %v=%v", false, data) + } + + // make sure false is returned if a non-existent feature is queried + if result := <-store.Preference().IsFeatureEnabled("someOtherFeature", userId); result.Err != nil { + t.Fatal(result.Err) + } else if data := result.Data.(bool); data != false { + t.Fatalf("got incorrect setting for non-existent feature 'someOtherFeature', %v=%v", false, data) + } +} + func TestDeleteUnusedFeatures(t *testing.T) { Setup()