Generate JSON file for brand configs
Refs CNVS-28275, closes CNVS-28885 Generate a json file to go along with the scss file for each brand config. The intention is that the json file for each brand config will be pushed to the cdn. One difference from the scss file is that it includes all variables, even if they are not specified in the brand config. Variable that have not been customized will use the default value. In addition to generating a json file for each brand, a json file for that inclues all default values is generated so other services don't need to know the defaults if no brand config is available. To allow for long term caching the filename of the json file includes a hash of the current defaults (including fingerprinted urls for default images). This way when the defaults change (or a default image) it will point to a new file even if the brand config didn't change. Test plan: - Save a new brand config. - Look in public/dist/brandable_css/[brand config hash]/ - There should be a [hash of defaults].json file - Should include custom values from brand config - Should include default values not specified in the brand config - Run rake brand_configs:clean && rake brand_configs:write - Should generate json file for all brand configs - Open console in browser - ENV.active_brand_config_json_url should be path the current brand json file - Go back to the default brand - ENV.active_brand_config_json_url should be path to default json file - Test with a real s3 bucket for the CDN - JSON files should be uploaded to the CDN - ENV.active_brand_config_json should work when used with ENV.ASSET_HOST Change-Id: Ibcaf54a2bff324f419a7614a8d3906c0c49aed9e Reviewed-on: https://gerrit.instructure.com/77427 Reviewed-by: Ryan Shaw <ryan@instructure.com> Tested-by: Jenkins QA-Review: August Thornton <august@instructure.com> Product-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
parent
1e6a5a66bf
commit
2134a9b430
|
@ -107,6 +107,7 @@ class ApplicationController < ActionController::Base
|
|||
@js_env = {
|
||||
ASSET_HOST: Canvas::Cdn.config.host,
|
||||
active_brand_config: active_brand_config.try(:md5),
|
||||
active_brand_config_json_url: active_brand_config_json_url,
|
||||
url_to_what_gets_loaded_inside_the_tinymce_editor_css: editor_css,
|
||||
current_user_id: @current_user.try(:id),
|
||||
current_user: user_display_json(@current_user, :profile),
|
||||
|
|
|
@ -677,6 +677,12 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def active_brand_config_json_url(opts={})
|
||||
path = active_brand_config(opts).try(:public_json_path)
|
||||
path ||= BrandableCSS.public_default_json_path
|
||||
"/#{path}"
|
||||
end
|
||||
|
||||
def brand_config_for_account(opts={})
|
||||
account = Context.get_account(@context)
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class BrandConfig < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def get_value(variable_name)
|
||||
self.variables[variable_name]
|
||||
effective_variables[variable_name]
|
||||
end
|
||||
|
||||
def overrides?
|
||||
|
@ -96,24 +96,62 @@ class BrandConfig < ActiveRecord::Base
|
|||
scss_dir.join('_brand_variables.scss')
|
||||
end
|
||||
|
||||
def to_json
|
||||
BrandableCSS.all_brand_variable_values(self).to_json
|
||||
end
|
||||
|
||||
def json_file
|
||||
public_brand_dir.join("variables-#{BrandableCSS.default_variables_md5}.json")
|
||||
end
|
||||
|
||||
def scss_dir
|
||||
BrandableCSS.branded_scss_folder.join(md5)
|
||||
end
|
||||
|
||||
def public_brand_dir
|
||||
BrandableCSS.public_brandable_css_folder.join(md5)
|
||||
end
|
||||
|
||||
def public_folder
|
||||
"dist/brandable_css/#{md5}"
|
||||
end
|
||||
|
||||
def public_json_path
|
||||
"#{public_folder}/variables-#{BrandableCSS.default_variables_md5}.json"
|
||||
end
|
||||
|
||||
def save_scss_file!
|
||||
logger.info "saving brand variables file: #{scss_file}"
|
||||
scss_dir.mkpath
|
||||
scss_file.write(to_scss)
|
||||
end
|
||||
|
||||
def remove_scss_file!
|
||||
return unless scss_dir.exist?
|
||||
logger.info "removing: #{scss_dir}"
|
||||
scss_dir.rmtree
|
||||
def save_json_file!
|
||||
logger.info "saving brand variables file: #{json_file}"
|
||||
public_brand_dir.mkpath
|
||||
json_file.write(to_json)
|
||||
move_json_to_s3_if_enabled!
|
||||
end
|
||||
|
||||
def move_json_to_s3_if_enabled!
|
||||
return unless Canvas::Cdn.enabled?
|
||||
s3_uploader.upload_file(public_json_path)
|
||||
File.delete(json_file)
|
||||
end
|
||||
|
||||
def s3_uploader
|
||||
@s3_uploaderer ||= Canvas::Cdn::S3Uploader.new
|
||||
end
|
||||
|
||||
def save_all_files!
|
||||
save_scss_file!
|
||||
save_json_file!
|
||||
end
|
||||
|
||||
def remove_scss_dir!
|
||||
return unless brand_dir.exist?
|
||||
logger.info "removing: #{brand_dir}"
|
||||
brand_dir.rmtree
|
||||
end
|
||||
|
||||
def compile_css!(opts=nil)
|
||||
|
@ -143,7 +181,7 @@ class BrandConfig < ActiveRecord::Base
|
|||
|
||||
def save_and_sync_to_s3!(progress=nil)
|
||||
progress.update_completion!(5) if progress
|
||||
save_scss_file!
|
||||
save_all_files!
|
||||
progress.update_completion!(10) if progress
|
||||
compile_css! on_progress: -> (percent_complete) {
|
||||
# send at most 1 UPDATE query per 2 seconds
|
||||
|
@ -163,7 +201,7 @@ class BrandConfig < ActiveRecord::Base
|
|||
first
|
||||
if unused_brand_config
|
||||
unused_brand_config.destroy
|
||||
unused_brand_config.remove_scss_file!
|
||||
unused_brand_config.remove_brand_dir!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ require 'open3'
|
|||
# changes here if they happen may need to be mirrored in that file.
|
||||
|
||||
module BrandableCSS
|
||||
extend ActionView::Helpers::AssetTagHelper
|
||||
|
||||
APP_ROOT = defined?(Rails) && Rails.root || Pathname.pwd
|
||||
CONFIG = YAML.load_file(APP_ROOT.join('config/brandable_css.yml')).freeze
|
||||
BRANDABLE_VARIABLES = JSON.parse(File.read(APP_ROOT.join(CONFIG['paths']['brandable_variables_json']))).freeze
|
||||
|
@ -85,13 +87,29 @@ module BrandableCSS
|
|||
end.freeze
|
||||
end
|
||||
|
||||
def variables_map_with_image_urls
|
||||
@variables_map_with_image_urls ||= variables_map.each_with_object({}) do |(key, config), memo|
|
||||
if config['type'] == 'image'
|
||||
memo[key] = config.merge('default' => image_url(config['default']))
|
||||
else
|
||||
memo[key] = config
|
||||
end
|
||||
end.freeze
|
||||
end
|
||||
|
||||
def default_variables_md5
|
||||
@default_variables_md5 ||= Digest::MD5.hexdigest(variables_map_with_image_urls.to_json)
|
||||
end
|
||||
|
||||
# gets the *effective* value for a brandable variable
|
||||
def brand_variable_value(variable_name, active_brand_config=nil)
|
||||
def brand_variable_value(variable_name, active_brand_config=nil, config_map=variables_map)
|
||||
explicit_value = active_brand_config && active_brand_config.get_value(variable_name).presence
|
||||
return explicit_value if explicit_value
|
||||
config = variables_map[variable_name]
|
||||
config = config_map[variable_name]
|
||||
default = config['default']
|
||||
return brand_variable_value(default[1..-1], active_brand_config) if default && default.starts_with?('$')
|
||||
if default && default.starts_with?('$')
|
||||
return brand_variable_value(default[1..-1], active_brand_config, config_map)
|
||||
end
|
||||
|
||||
# while in our sass, we want `url(/images/foo.png)`,
|
||||
# the Rails Asset Helpers expect us to not have the '/images/', eg: <%= image_tag('foo.png') %>
|
||||
|
@ -99,10 +117,52 @@ module BrandableCSS
|
|||
default
|
||||
end
|
||||
|
||||
def all_brand_variable_values(active_brand_config=nil)
|
||||
variables_map.each_with_object({}) do |(key, _), memo|
|
||||
memo[key] = brand_variable_value(key, active_brand_config, variables_map_with_image_urls)
|
||||
end
|
||||
end
|
||||
|
||||
def branded_scss_folder
|
||||
Pathname.new(CONFIG['paths']['branded_scss_folder'])
|
||||
end
|
||||
|
||||
def public_brandable_css_folder
|
||||
Pathname.new('public/dist/brandable_css')
|
||||
end
|
||||
|
||||
def default_brand_folder
|
||||
public_brandable_css_folder.join('default')
|
||||
end
|
||||
|
||||
def default_brand_json_file
|
||||
default_brand_folder.join("variables-#{default_variables_md5}.json")
|
||||
end
|
||||
|
||||
def default_json
|
||||
all_brand_variable_values.to_json
|
||||
end
|
||||
|
||||
def save_default_json!
|
||||
default_brand_folder.mkpath
|
||||
default_brand_json_file.write(default_json)
|
||||
move_default_json_to_s3_if_enabled!
|
||||
end
|
||||
|
||||
def move_default_json_to_s3_if_enabled!
|
||||
return unless Canvas::Cdn.enabled?
|
||||
s3_uploader.upload_file(public_default_json_path)
|
||||
File.delete(default_brand_json_file)
|
||||
end
|
||||
|
||||
def s3_uploader
|
||||
@s3_uploaderer ||= Canvas::Cdn::S3Uploader.new
|
||||
end
|
||||
|
||||
def public_default_json_path
|
||||
"dist/brandable_css/default/variables-#{default_variables_md5}.json"
|
||||
end
|
||||
|
||||
def variants
|
||||
@variants ||= CONFIG['variants'].map{|(k)| k }.freeze
|
||||
end
|
||||
|
|
|
@ -27,6 +27,10 @@ module Canvas
|
|||
uploader = Canvas::Cdn::S3Uploader.new(*args)
|
||||
uploader.upload!(&block)
|
||||
end
|
||||
|
||||
def enabled?
|
||||
!!config.bucket
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ module Canvas
|
|||
end
|
||||
|
||||
def fingerprinted?(path)
|
||||
/-[0-9a-fA-F]{10}$/.match(path.basename(path.extname).to_s)
|
||||
/-[0-9a-fA-F]{10,32}$/.match(path.basename(path.extname).to_s)
|
||||
end
|
||||
|
||||
def font?(path)
|
||||
|
|
|
@ -5,10 +5,10 @@ namespace :brand_configs do
|
|||
"Set BRAND_CONFIG_MD5=<whatever> to save just that one, otherwise writes a file for each BrandConfig in db."
|
||||
task :write => :environment do
|
||||
if md5 = ENV['BRAND_CONFIG_MD5']
|
||||
BrandConfig.find(md5).save_scss_file!
|
||||
BrandConfig.find(md5).save_all_files!
|
||||
else
|
||||
BrandConfig.clean_unused_from_db!
|
||||
BrandConfig.find_each(&:save_scss_file!)
|
||||
BrandConfig.find_each(&:save_all_files!)
|
||||
end
|
||||
end
|
||||
Switchman::Rake.shardify_task('brand_configs:write')
|
||||
|
@ -25,6 +25,7 @@ namespace :brand_configs do
|
|||
desc "generate all brands and upload everything to s3"
|
||||
task :generate_and_upload_all do
|
||||
Rake::Task['brand_configs:clean'].invoke
|
||||
BrandableCSS.save_default_json!
|
||||
Rake::Task['brand_configs:write'].invoke
|
||||
|
||||
# This'll pick up on all those written brand_configs and compile their css.
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
#
|
||||
# Copyright (C) 2016 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 BrandableCSS do
|
||||
describe "all_brand_variable_values" do
|
||||
it "returns defaults if called without a brand config" do
|
||||
expect(BrandableCSS.all_brand_variable_values["ic-link-color"]).to eq '#0081bd'
|
||||
end
|
||||
|
||||
it "includes image_url asset path for default images" do
|
||||
# un-memoize so it calls image_url stub
|
||||
BrandableCSS.remove_instance_variable(:@variables_map_with_image_urls)
|
||||
image_name = "image.png"
|
||||
BrandableCSS.stubs(:image_url).returns(image_name)
|
||||
tile_wide = BrandableCSS.all_brand_variable_values["ic-brand-msapplication-tile-wide"]
|
||||
expect(tile_wide).to eq image_name
|
||||
end
|
||||
|
||||
describe "when called with a brand config" do
|
||||
before :once do
|
||||
parent_account = Account.default
|
||||
parent_account.enable_feature!(:use_new_styles)
|
||||
parent_config = BrandConfig.create(variables: {"ic-brand-primary" => "#321"})
|
||||
|
||||
subaccount_bc = BrandConfig.for(
|
||||
variables: {"ic-brand-global-nav-bgd" => "#123"},
|
||||
parent_md5: parent_config.md5,
|
||||
js_overrides: nil,
|
||||
css_overrides: nil,
|
||||
mobile_js_overrides: nil,
|
||||
mobile_css_overrides: nil
|
||||
)
|
||||
subaccount_bc.save!
|
||||
@brand_variables = BrandableCSS.all_brand_variable_values(subaccount_bc)
|
||||
end
|
||||
|
||||
it "includes custom variables from brand config" do
|
||||
expect(@brand_variables["ic-brand-global-nav-bgd"]).to eq '#123'
|
||||
end
|
||||
|
||||
it "includes custom variables from parent brand config" do
|
||||
expect(@brand_variables["ic-brand-primary"]).to eq '#321'
|
||||
end
|
||||
|
||||
it "includes default variables not found in brand config" do
|
||||
expect(@brand_variables["ic-link-color"]).to eq '#0081bd'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "default_json" do
|
||||
it "includes default variables not found in brand config" do
|
||||
brand_variables = JSON.parse(BrandableCSS.default_json)
|
||||
expect(brand_variables["ic-link-color"]).to eq '#0081bd'
|
||||
end
|
||||
end
|
||||
|
||||
describe "save_default_file!" do
|
||||
it "writes the default json represendation to the default json file" do
|
||||
Canvas::Cdn.stubs(:enabled?).returns(false)
|
||||
file = StringIO.new
|
||||
BrandableCSS.stubs(:default_brand_json_file).returns(file)
|
||||
BrandableCSS.save_default_json!
|
||||
expect(file.string).to eq BrandableCSS.default_json
|
||||
end
|
||||
|
||||
it 'uploads file to s3 if cdn is enabled' do
|
||||
Canvas::Cdn.stubs(:enabled?).returns(true)
|
||||
file = StringIO.new
|
||||
BrandableCSS.stubs(:default_brand_json_file).returns(file)
|
||||
File.stubs(:delete)
|
||||
BrandableCSS.s3_uploader.expects(:upload_file).with(BrandableCSS.public_default_json_path)
|
||||
BrandableCSS.save_default_json!
|
||||
end
|
||||
|
||||
it 'delete the local file if cdn is enabled' do
|
||||
Canvas::Cdn.stubs(:enabled?).returns(true)
|
||||
file = StringIO.new
|
||||
BrandableCSS.stubs(:default_brand_json_file).returns(file)
|
||||
File.expects(:delete).with(BrandableCSS.default_brand_json_file)
|
||||
BrandableCSS.s3_uploader.expects(:upload_file)
|
||||
BrandableCSS.save_default_json!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# Copyright (C) 2016 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 Canvas::Cdn do
|
||||
describe '.enabled?' do
|
||||
before :each do
|
||||
@original_config = Canvas::Cdn.config.dup
|
||||
end
|
||||
|
||||
after :each do
|
||||
Canvas::Cdn.config.replace(@original_config)
|
||||
end
|
||||
|
||||
it 'returns true when the cdn config has a bucket' do
|
||||
Canvas::Cdn.config.merge! enabled: true, bucket: 'bucket_name'
|
||||
expect(Canvas::Cdn.enabled?).to eq true
|
||||
end
|
||||
|
||||
it 'returns false when the cdn config does not have a bucket' do
|
||||
Canvas::Cdn.config.merge! enabled: true, bucket: nil
|
||||
expect(Canvas::Cdn.enabled?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -86,4 +86,77 @@ describe BrandConfig do
|
|||
expect(@parent_config.chain_of_ancestor_configs.length).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_json" do
|
||||
before :once do
|
||||
setup_subaccount_with_config
|
||||
@brand_variables = JSON.parse(@subaccount_bc.to_json)
|
||||
end
|
||||
|
||||
it "includes custom variables from brand config" do
|
||||
expect(@brand_variables["ic-brand-global-nav-bgd"]).to eq '#123'
|
||||
end
|
||||
|
||||
it "includes custom variables from parent brand config" do
|
||||
expect(@brand_variables["ic-brand-primary"]).to eq '#321'
|
||||
end
|
||||
|
||||
it "includes default variables not found in brand config" do
|
||||
expect(@brand_variables["ic-link-color"]).to eq '#0081bd'
|
||||
end
|
||||
end
|
||||
|
||||
describe "save_all_files!" do
|
||||
before :once do
|
||||
setup_subaccount_with_config
|
||||
end
|
||||
|
||||
before :each do
|
||||
@json_file = StringIO.new
|
||||
@scss_file = StringIO.new
|
||||
@subaccount_bc.stubs(:json_file).returns(@json_file)
|
||||
@subaccount_bc.stubs(:scss_file).returns(@scss_file)
|
||||
end
|
||||
|
||||
describe "with cdn disabled" do
|
||||
before :each do
|
||||
Canvas::Cdn.expects(:enabled?).returns(false)
|
||||
@subaccount_bc.s3_uploader.expects(:upload_file).never
|
||||
File.expects(:delete).never
|
||||
end
|
||||
|
||||
it "writes the json represendation to the json file" do
|
||||
@subaccount_bc.save_all_files!
|
||||
expect(@json_file.string).to eq @subaccount_bc.to_json
|
||||
end
|
||||
|
||||
it "writes the scss represendation to scss file" do
|
||||
@subaccount_bc.save_all_files!
|
||||
expect(@scss_file.string).to eq @subaccount_bc.to_scss
|
||||
end
|
||||
end
|
||||
|
||||
describe "with cdn enabled" do
|
||||
before :each do
|
||||
Canvas::Cdn.expects(:enabled?).returns(true)
|
||||
@upload_expectation = @subaccount_bc.s3_uploader.expects(:upload_file).once
|
||||
@delete_expectation = File.expects(:delete).once
|
||||
end
|
||||
|
||||
it "writes the json represendation to the json file" do
|
||||
@subaccount_bc.save_all_files!
|
||||
expect(@json_file.string).to eq @subaccount_bc.to_json
|
||||
end
|
||||
|
||||
it 'uploads json file to s3 if cdn enabled' do
|
||||
@upload_expectation.with(@subaccount_bc.public_json_path)
|
||||
@subaccount_bc.save_all_files!
|
||||
end
|
||||
|
||||
it 'deletes local json file if cdn enabled' do
|
||||
@delete_expectation.with(@subaccount_bc.json_file)
|
||||
@subaccount_bc.save_all_files!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue