make 'development' features invisible in production
also remove the 'development' tag on existing features, other than those that manually made themselves invisible in prod. we'll let 'beta' suffice for the purpose of informing the user that a feature is not fully baked. test plan: - Deploy normally to your QA portal. The 'quiz stats' feature flag should be available and operational. - Remove the testcluster.yml file from your config/ directory (or move / back it up to a safe place) - Restart your QA portal. - The quiz stats feature flag should not be available or visible. fixes CNVS-15724 Change-Id: I9e4e46f8cc4f54ac62f5226dbca09ae4885c93a9 Reviewed-on: https://gerrit.instructure.com/41551 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Josh Simpson <jsimpson@instructure.com> QA-Review: Clare Strong <clare@instructure.com> Product-Review: Cosme Salazar <cosme@instructure.com>
This commit is contained in:
parent
aff0ad2ba2
commit
58fb282035
|
@ -63,13 +63,13 @@
|
|||
# "type": "boolean"
|
||||
# },
|
||||
# "beta": {
|
||||
# "description": "Whether the feature is a beta feature",
|
||||
# "example": false,
|
||||
# "description": "Whether the feature is a beta feature. If true, the feature may not be fully polished and may be subject to change in the future.",
|
||||
# "example": true,
|
||||
# "type": "boolean"
|
||||
# },
|
||||
# "development": {
|
||||
# "description": "Whether the feature is in development",
|
||||
# "example": true,
|
||||
# "description": "Whether the feature is in active development. Features in this state are only visible in test and beta instances and are not yet available for production use.",
|
||||
# "example": false,
|
||||
# "type": "boolean"
|
||||
# },
|
||||
# "release_notes_url": {
|
||||
|
|
|
@ -51,6 +51,10 @@ class Feature
|
|||
@state == 'hidden'
|
||||
end
|
||||
|
||||
def self.production_environment?
|
||||
Rails.env.production? && !(ApplicationController.respond_to?(:test_cluster?) && ApplicationController.test_cluster?)
|
||||
end
|
||||
|
||||
# Register one or more features. Must be done during application initialization.
|
||||
# The feature_hash is as follows:
|
||||
=begin
|
||||
|
@ -63,7 +67,7 @@ class Feature
|
|||
# will be inherited in "off" state by root accounts
|
||||
enable_at: Date.new(2014, 1, 1), # estimated release date shown in UI
|
||||
beta: false, # 'beta' tag shown in UI
|
||||
development: false, # 'development' tag shown in UI
|
||||
development: false, # whether the feature is restricted to development / test / beta instances
|
||||
release_notes_url: 'http://example.com/',
|
||||
|
||||
# optional: you can supply a Proc to attach warning messages to and/or forbid certain transitions
|
||||
|
@ -83,9 +87,10 @@ class Feature
|
|||
|
||||
def self.register(feature_hash)
|
||||
@features ||= {}
|
||||
feature_hash.each do |k, v|
|
||||
feature = k.to_s
|
||||
@features[feature] = Feature.new({feature: feature}.merge(v))
|
||||
feature_hash.each do |feature_name, attrs|
|
||||
next if attrs[:development] && production_environment?
|
||||
feature = feature_name.to_s
|
||||
@features[feature] = Feature.new({feature: feature}.merge(attrs))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -116,8 +121,7 @@ END
|
|||
applies_to: 'RootAccount',
|
||||
state: 'hidden',
|
||||
root_opt_in: true,
|
||||
beta: true,
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'html5_first_videos' =>
|
||||
{
|
||||
|
@ -128,8 +132,7 @@ then fall back to Flash.
|
|||
END
|
||||
applies_to: 'RootAccount',
|
||||
state: 'allowed',
|
||||
beta: true,
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'high_contrast' =>
|
||||
{
|
||||
|
@ -140,8 +143,7 @@ This might be useful for people with impaired vision or difficulty reading.
|
|||
END
|
||||
applies_to: 'User',
|
||||
state: 'allowed',
|
||||
beta: true,
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'outcome_gradebook' =>
|
||||
{
|
||||
|
@ -154,8 +156,7 @@ mastery/remedial.
|
|||
END
|
||||
applies_to: 'Course',
|
||||
state: 'allowed',
|
||||
root_opt_in: false,
|
||||
development: false
|
||||
root_opt_in: false
|
||||
},
|
||||
'student_outcome_gradebook' =>
|
||||
{
|
||||
|
@ -168,22 +169,21 @@ mastery/remedial.
|
|||
END
|
||||
applies_to: 'Course',
|
||||
state: 'allowed',
|
||||
root_opt_in: false,
|
||||
development: false
|
||||
root_opt_in: false
|
||||
},
|
||||
'post_grades' =>
|
||||
{
|
||||
display_name: -> { I18n.t('features.post_grades', 'Post Grades to SIS') },
|
||||
description: -> { I18n.t('post_grades_description', <<-END) },
|
||||
'post_grades' =>
|
||||
{
|
||||
display_name: -> { I18n.t('features.post_grades', 'Post Grades to SIS') },
|
||||
description: -> { I18n.t('post_grades_description', <<-END) },
|
||||
Post Grades allows teachers to post grades back to enabled SIS systems: Powerschool,
|
||||
Aspire (SIS2000), JMC, and any other SIF-enabled SIS that accepts the SIF elements GradingCategory,
|
||||
GradingAssignment, GradingAssignmentScore.
|
||||
END
|
||||
applies_to: 'Course',
|
||||
state: 'hidden',
|
||||
root_opt_in: true,
|
||||
development: true
|
||||
},
|
||||
GradingAssignment, GradingAssignmentScore.
|
||||
END
|
||||
applies_to: 'Course',
|
||||
state: 'hidden',
|
||||
root_opt_in: true,
|
||||
beta: true
|
||||
},
|
||||
'k12' =>
|
||||
{
|
||||
display_name: -> { I18n.t('features.k12', 'K-12 specific features') },
|
||||
|
@ -194,8 +194,7 @@ END
|
|||
applies_to: 'RootAccount',
|
||||
state: 'hidden',
|
||||
root_opt_in: true,
|
||||
beta: true,
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'quiz_moderate' =>
|
||||
{
|
||||
|
@ -205,7 +204,7 @@ When Draft State and Quiz Statistics is allowed/on, this enables the new quiz mo
|
|||
END
|
||||
applies_to: 'Course',
|
||||
state: 'hidden',
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'student_groups_next' =>
|
||||
{
|
||||
|
@ -217,7 +216,7 @@ END
|
|||
applies_to: 'RootAccount',
|
||||
state: 'allowed',
|
||||
root_opt_in: true,
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'better_file_browsing' =>
|
||||
{
|
||||
|
@ -230,7 +229,7 @@ END
|
|||
|
||||
applies_to: 'Course',
|
||||
state: 'hidden',
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'modules_next' =>
|
||||
{
|
||||
|
@ -260,7 +259,7 @@ Allow users to view and use external tools configured for LOR.
|
|||
END
|
||||
applies_to: 'User',
|
||||
state: 'hidden',
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'lor_for_account' =>
|
||||
{
|
||||
|
@ -270,8 +269,18 @@ Allow users to view and use external tools configured for LOR.
|
|||
END
|
||||
applies_to: 'RootAccount',
|
||||
state: 'hidden',
|
||||
development: true
|
||||
beta: true
|
||||
},
|
||||
'quiz_stats' =>
|
||||
{
|
||||
display_name: -> { I18n.t('features.new_quiz_statistics', 'New Quiz Statistics Page') },
|
||||
description: -> { I18n.t('new_quiz_statistics_desc', <<-END) },
|
||||
Enable the new quiz statistics page for an account.
|
||||
END
|
||||
applies_to: 'Course',
|
||||
state: 'allowed',
|
||||
development: true
|
||||
}
|
||||
)
|
||||
|
||||
def self.definitions
|
||||
|
|
|
@ -41,7 +41,7 @@ END
|
|||
applies_to: 'Course',
|
||||
state: 'hidden',
|
||||
root_opt_in: true,
|
||||
development: true,
|
||||
beta: true,
|
||||
custom_transition_proc: ->(user, context, from_state, transitions) do
|
||||
if context.is_a?(Course) && from_state == 'on'
|
||||
transitions['off']['message'] = I18n.t('features.differentiated_assignments_course_disable_warning', <<END)
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2014 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
module Features
|
||||
class QuizStatFeatureFlag
|
||||
def register!
|
||||
Feature.register(definition) if should_register?
|
||||
end
|
||||
|
||||
private
|
||||
def definition
|
||||
@definition ||= {
|
||||
'quiz_stats' =>
|
||||
{
|
||||
display_name: -> { I18n.t('features.new_quiz_statistics', 'New Quiz Statistics Page') },
|
||||
description: -> { I18n.t('new_quiz_statistics_desc', <<-END) },
|
||||
When Draft State is allowed/on, this enables the new quiz statistics page for an account.
|
||||
END
|
||||
applies_to: 'Course',
|
||||
state: 'allowed',
|
||||
development: true,
|
||||
beta: true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def should_register?
|
||||
Rails.env.test? || Rails.env.development? || in_beta?
|
||||
end
|
||||
|
||||
def in_beta?
|
||||
Rails.env.production? && (!ApplicationController.respond_to?(:test_cluster?) || ApplicationController.test_cluster?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Features::QuizStatFeatureFlag.new.register!
|
|
@ -150,3 +150,58 @@ describe Feature do
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
describe "Feature.register" do
|
||||
before do
|
||||
# unregister the default features
|
||||
Feature.instance_variable_set(:@features, nil)
|
||||
end
|
||||
|
||||
let(:t_feature_hash) do
|
||||
{
|
||||
display_name: -> { "some feature or other" },
|
||||
description: -> { "this does something" },
|
||||
applies_to: 'RootAccount',
|
||||
state: 'allowed'
|
||||
}
|
||||
end
|
||||
|
||||
let(:t_dev_feature_hash) do
|
||||
t_feature_hash.merge(development: true)
|
||||
end
|
||||
|
||||
it "should register a feature" do
|
||||
Feature.register({some_feature: t_feature_hash})
|
||||
Feature.definitions.should be_frozen
|
||||
Feature.definitions['some_feature'].display_name.call.should eql('some feature or other')
|
||||
end
|
||||
|
||||
describe "development" do
|
||||
it "should register in a test environment" do
|
||||
Feature.register({dev_feature: t_dev_feature_hash})
|
||||
Feature.definitions['dev_feature'].should_not be_nil
|
||||
end
|
||||
|
||||
it "should register in a dev environment" do
|
||||
Rails.env.stubs(:test?).returns(false)
|
||||
Rails.env.stubs(:development?).returns(true)
|
||||
Feature.register({dev_feature: t_dev_feature_hash})
|
||||
Feature.definitions['dev_feature'].should_not be_nil
|
||||
end
|
||||
|
||||
it "should register in a production test cluster" do
|
||||
Rails.env.stubs(:test?).returns(false)
|
||||
Rails.env.stubs(:production?).returns(true)
|
||||
ApplicationController.stubs(:test_cluster?).returns(true)
|
||||
Feature.register({dev_feature: t_dev_feature_hash})
|
||||
Feature.definitions['dev_feature'].should_not be_nil
|
||||
end
|
||||
|
||||
it "should not register in production" do
|
||||
Rails.env.stubs(:test?).returns(false)
|
||||
Rails.env.stubs(:production?).returns(true)
|
||||
Feature.register({dev_feature: t_dev_feature_hash})
|
||||
Feature.definitions['dev_feature'].should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2014 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
|
||||
|
||||
describe "Features::QuizStatFeatureFlag" do
|
||||
let(:account) { Account.default }
|
||||
context "enables the feature" do
|
||||
it "when in a development env" do
|
||||
remove_quiz_stats!
|
||||
flag = Features::QuizStatFeatureFlag.new
|
||||
|
||||
Rails.env.stubs(:test?).returns(false)
|
||||
Rails.env.stubs(:development?).returns(true)
|
||||
flag.register!
|
||||
|
||||
account.feature_allowed?('quiz_stats').should be_truthy
|
||||
end
|
||||
|
||||
it "when in a test env" do
|
||||
remove_quiz_stats!
|
||||
flag = Features::QuizStatFeatureFlag.new
|
||||
|
||||
Rails.env.stubs(:test?).returns(true)
|
||||
Rails.env.stubs(:development?).returns(false)
|
||||
flag.register!
|
||||
|
||||
account.feature_allowed?('quiz_stats').should be_truthy
|
||||
end
|
||||
|
||||
it "when in beta" do
|
||||
remove_quiz_stats!
|
||||
flag = Features::QuizStatFeatureFlag.new
|
||||
|
||||
Rails.env.stubs(:test?).returns(false)
|
||||
Rails.env.stubs(:development?).returns(false)
|
||||
flag.stubs(:in_beta?).returns(true)
|
||||
|
||||
flag.register!
|
||||
|
||||
account.feature_allowed?('quiz_stats').should be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "doesn't enable the feature" do
|
||||
it "when in production" do
|
||||
remove_quiz_stats!
|
||||
flag = Features::QuizStatFeatureFlag.new
|
||||
|
||||
Rails.env.stubs(:test?).returns(false)
|
||||
Rails.env.stubs(:development?).returns(false)
|
||||
Rails.env.stubs(:production?).returns(true)
|
||||
ApplicationController.stubs(:test_cluster?).returns(false)
|
||||
|
||||
flag.register!
|
||||
|
||||
account.feature_allowed?('quiz_stats').should be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
def remove_quiz_stats!
|
||||
# need to strip quiz_stats from the frozen definitions hash on Feature
|
||||
features = {}
|
||||
Feature.definitions.each do |k, v|
|
||||
features[k] = v
|
||||
end
|
||||
|
||||
features.delete('quiz_stats')
|
||||
|
||||
Feature.instance_variable_set(:@features, features)
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue