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:
Jeremy Stanley 2014-09-22 13:52:19 -06:00
parent aff0ad2ba2
commit 58fb282035
6 changed files with 101 additions and 177 deletions

View File

@ -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": {

View File

@ -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

View File

@ -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)

View File

@ -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!

View File

@ -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

View File

@ -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