diff --git a/app/coffeescripts/bundles/account_settings.coffee b/app/coffeescripts/bundles/account_settings.coffee index 2bf504d2a44..e865abcc73d 100644 --- a/app/coffeescripts/bundles/account_settings.coffee +++ b/app/coffeescripts/bundles/account_settings.coffee @@ -1 +1 @@ -require ['account_settings', 'external_tools'] +require ['account_settings'] diff --git a/app/coffeescripts/bundles/course_settings.coffee b/app/coffeescripts/bundles/course_settings.coffee index dff084e2c3b..c5cb6d6ab0e 100644 --- a/app/coffeescripts/bundles/course_settings.coffee +++ b/app/coffeescripts/bundles/course_settings.coffee @@ -5,7 +5,6 @@ require [ 'compiled/views/course_settings/tabs/tabUsers' 'vendor/jquery.cookie' 'course_settings' - 'external_tools' 'grading_standards' ], (NavigationView, UserCollectionView, UserCollection) -> diff --git a/app/coffeescripts/bundles/external_tools.coffee b/app/coffeescripts/bundles/external_tools.coffee new file mode 100644 index 00000000000..2b997a7e0bd --- /dev/null +++ b/app/coffeescripts/bundles/external_tools.coffee @@ -0,0 +1,10 @@ +require [ + 'compiled/collections/ExternalToolCollection', + 'compiled/views/ExternalTools/IndexView' + ], (ExternalToolCollection, ExternalToolsIndexView) -> + collection = new ExternalToolCollection() + collection.fetch() + view = new ExternalToolsIndexView + el: '#external_tools' + collection: collection + view.render() \ No newline at end of file diff --git a/app/coffeescripts/collections/ExternalToolCollection.coffee b/app/coffeescripts/collections/ExternalToolCollection.coffee new file mode 100644 index 00000000000..dca50fe5efe --- /dev/null +++ b/app/coffeescripts/collections/ExternalToolCollection.coffee @@ -0,0 +1,7 @@ +define [ + 'compiled/collections/PaginatedCollection' + 'compiled/models/ExternalTool' +], (PaginatedCollection, ExternalTool) -> + + class ExternalToolCollection extends PaginatedCollection + model: ExternalTool diff --git a/app/coffeescripts/handlebars_helpers.coffee b/app/coffeescripts/handlebars_helpers.coffee index ff84e136806..bed8d7e9067 100644 --- a/app/coffeescripts/handlebars_helpers.coffee +++ b/app/coffeescripts/handlebars_helpers.coffee @@ -301,7 +301,11 @@ define [ truncate: ( string, max ) -> return textHelper.truncateText( string, { max: max } ) - enrollmentName: enrollmentName + + titleize: (str) -> + words = str.split(/[ _]+/) + titleizedWords = _(words).map (w) -> w[0].toUpperCase() + w.slice(1) + titleizedWords.join(' ') } return Handlebars diff --git a/app/coffeescripts/models/ExternalTool.coffee b/app/coffeescripts/models/ExternalTool.coffee new file mode 100644 index 00000000000..aabdf974f13 --- /dev/null +++ b/app/coffeescripts/models/ExternalTool.coffee @@ -0,0 +1,14 @@ +define ['Backbone'], ({Model}) -> + + class ExternalTool extends Model + resourceName: 'external_tools' + + computedAttributes: [ + { + name: 'custom_field_string' + deps: ['custom_fields'] + } + ] + + custom_field_string: -> + ("#{k}=#{v}" for k,v of @get('custom_fields')).join("\n") diff --git a/app/coffeescripts/views/ExternalTools/EditView.coffee b/app/coffeescripts/views/ExternalTools/EditView.coffee new file mode 100644 index 00000000000..568dd9d80f5 --- /dev/null +++ b/app/coffeescripts/views/ExternalTools/EditView.coffee @@ -0,0 +1,74 @@ +define [ + 'i18n!external_tools' + 'jst/ExternalTools/EditView' + 'compiled/views/ValidatedFormView' + 'compiled/jquery/fixDialogButtons' +], (I18n, template, ValidatedFormView) -> + + class EditView extends ValidatedFormView + template: template + + id: 'external_tool_form' + + className: 'validated-form-view form-horizontal bootstrap-form' + + events: + 'change #external_tool_config_type': 'onConfigTypeChange' + + render: -> + super + @$el.dialog + title: I18n.t 'dialog_title', 'Edit External Tool' + width: 520 + height: "auto" + resizable: true + close: => @$el.remove() + buttons: [ + class: "btn-primary" + text: I18n.t 'submit', 'Submit' + 'data-text-while-loading': I18n.t 'saving', 'Saving...' + click: => @submit() + ] + @onConfigTypeChange() + @$el.submit (e) => + @submit() + return false + this + + submit: -> + this.$el.parent().find('.btn-primary').removeClass('ui-state-hover') + super + + onSaveSuccess: -> + @$el.dialog 'close' + + onConfigTypeChange: -> + configType = @$('#external_tool_config_type').val() + @$('.config_type').hide().attr('aria-expanded', false) + @$(".config_type.#{configType}").show().attr('aria-expanded', true) + + showErrors: (errors) -> + @removeErrors() + for fieldName, field of errors + $input = @findField fieldName + html = (@translations[message] or message for {message} in field).join('

') + @addError($input, html) + + removeErrors: -> + @$('.error .help-inline').remove() + @$('.control-group').removeClass('error') + @$('.alert.alert-error').remove() + + addError: (input, message) -> + input = $(input) + input.parents('.control-group').addClass('error') + input.after("#{message}") + input.one 'keypress', -> + $(this).parents('.control-group').removeClass('error') + $(this).parents('.control-group').find('.help-inline').remove() + + onSaveFail: (xhr) => + super + message = I18n.t 'generic_error', 'There was an error in processing your request' + @$el.prepend("

#{message}") + diff --git a/app/coffeescripts/views/ExternalTools/IndexView.coffee b/app/coffeescripts/views/ExternalTools/IndexView.coffee new file mode 100644 index 00000000000..23a8ae5ca3f --- /dev/null +++ b/app/coffeescripts/views/ExternalTools/IndexView.coffee @@ -0,0 +1,70 @@ +define [ + 'underscore' + 'jquery' + 'jst/ExternalTools/IndexView' + 'compiled/views/ExternalTools/EditView' + 'compiled/views/PaginatedView' + 'i18n!external_tools' +], (_, $, template, EditView, PaginatedView, I18n) -> + + class IndexView extends PaginatedView + + template: template + + events: + 'click [data-delete-external-tool]': 'deleteExternalToolHandler' + 'click [data-edit-external-tool]': 'editExternalToolHandler' + 'click .add_tool_link': 'addTool' + + initialize: -> + super + @collection.on 'sync', @render, this + @collection.on 'reset', @render, this + @collection.on 'destroy', @render, this + @render() + + deleteExternalToolHandler: (e) => + id = @$(e.target).closest('a').data('delete-external-tool') + @confirmDelete => + @collection.get(id).destroy() + + confirmDelete: (deleteFunc) -> + msg = I18n.t 'remove_tool', + "Are you sure you want to remove this tool? + Any courses using this tool will no longer work." + dialog = $("
#{msg}
").dialog + modal: true, + resizable: false + title: I18n.t 'are_you_sure', 'Are you sure?' + buttons: [ + text: I18n.t 'buttons.cancel', 'Cancel' + click: => dialog.dialog 'close' + , + text: I18n.t 'buttons.delete', 'Delete' + click: => + deleteFunc() + dialog.dialog 'close' + ] + + editExternalToolHandler: (e) => + id = @$(e.target).closest('a').data('edit-external-tool') + new EditView(model: @collection.get(id)).render() + + addTool: => + @collection.add({}, silent: true) + new EditView(model: @collection.last()).render() + + toJSON: -> + extras = [ + {extension_type: 'editor_button', text: I18n.t 'editor_button_configured', 'Editor button configured'} + {extension_type: 'resource_selection', text: I18n.t 'resource_selection_configured', 'Resource selection configured'} + {extension_type: 'course_navigation', text: I18n.t 'course_navigation_configured', 'Course navigation configured'} + {extension_type: 'account_navigation', text: I18n.t 'account_navigation_configured', 'Account navigation configured'} + {extension_type: 'user_navigation', text: I18n.t 'user_navigation_configured', 'User navigation configured'} + {extension_type: 'homework_submission', text: I18n.t 'homework_submission_configured', 'Homework submission configured'} + ] + + json = super + for tool in json + tool.extras = (extra for extra in extras when tool[extra.extension_type]?) + json diff --git a/app/coffeescripts/views/ValidatedFormView.coffee b/app/coffeescripts/views/ValidatedFormView.coffee index 6b55e489b88..c2087792408 100644 --- a/app/coffeescripts/views/ValidatedFormView.coffee +++ b/app/coffeescripts/views/ValidatedFormView.coffee @@ -4,10 +4,11 @@ define [ 'jquery' 'underscore' 'compiled/fn/preventDefault' + 'i18n!errors' 'jquery.toJSON' 'jquery.disableWhileLoading' 'jquery.instructure_forms' -], (Backbone, ValidatedMixin, $, _) -> +], (Backbone, ValidatedMixin, $, _, preventDefault, I18n) -> ## # Sets model data from a form, saves it, and displays errors returned in a @@ -121,3 +122,33 @@ define [ $.parseJSON(response.responseText).errors catch error {} + + translations: + required: I18n.t "required", "Required" + blank: I18n.t "blank", "Required" + + ## + # Errors are displayed relative to the field to which they belong. If + # the key of the error in the response doesn't match the name attribute + # of the form input element, configure a selector here. + # + # For example, given a form field like this: + # + # + # + # and an error response like this: + # + # {errors: { first_name: {...} }} + # + # you would do this: + # + # fieldSelectors: + # first_name: '[name=user[first_name]]' + fieldSelectors: null + + findField: (field) -> + selector = @fieldSelectors?[field] or "[name='#{field}']" + $el = @$(selector) + if $el.data('rich_text') + $el = $el.next('.mceEditor').find(".mceIframeContainer") + $el diff --git a/app/models/context_external_tool.rb b/app/models/context_external_tool.rb index 84203fcad07..ba093365844 100644 --- a/app/models/context_external_tool.rb +++ b/app/models/context_external_tool.rb @@ -12,6 +12,9 @@ class ContextExternalTool < ActiveRecord::Base validates_length_of :name, :maximum => maximum_string_length validates_presence_of :consumer_key validates_presence_of :shared_secret + validates_presence_of :config_url, :if => lambda { |t| t.config_type == "by_url" } + validates_presence_of :config_xml, :if => lambda { |t| t.config_type == "by_xml" } + validates_length_of :domain, :maximum => 253, :allow_blank => true validate :url_or_domain_is_set serialize :settings attr_accessor :config_type, :config_url, :config_xml @@ -76,15 +79,10 @@ class ContextExternalTool < ActiveRecord::Base settings[:text] || name || "External Tool" end - def xml_error(error) - @xml_error = error - end - def check_for_xml_error - if @xml_error - errors.add_to_base(@xml_error) - false - end + (@config_errors || []).each { |attr,msg| + errors.add attr, msg + } end protected :check_for_xml_error @@ -154,18 +152,37 @@ class ContextExternalTool < ActiveRecord::Base rescue CC::Importer::BLTIConverter::CCImportError => e tool_hash = {:error => e.message} end + + @config_errors = [] + error_field = config_type == 'by_xml' ? 'config_xml' : 'config_url' + + converter = CC::Importer::BLTIConverter.new + tool_hash = if config_type == 'by_url' + uri = URI.parse(config_url) + raise URI::InvalidURIError unless uri.host && uri.port + converter.retrieve_and_convert_blti_url(config_url) + else + converter.convert_blti_xml(config_xml) + end + real_name = self.name if tool_hash[:error] - xml_error(tool_hash[:error]) + @config_errors << [error_field, tool_hash[:error]] else - ContextExternalTool.import_from_migration(tool_hash, self.context, self) + ContextExternalTool.import_from_migration(tool_hash, context, self) end self.name = real_name unless real_name.blank? + rescue CC::Importer::BLTIConverter::CCImportError => e + @config_errors << [error_field, e.message] + rescue URI::InvalidURIError + @config_errors << [:config_url, "Invalid URL"] + rescue ActiveRecord::RecordInvalid => e + @config_errors += Array(e.record.errors) end def custom_fields_string=(str) hash = {} - str.split(/\n/).each do |line| + str.split(/[\r\n]+/).each do |line| key, val = line.split(/=/) hash[key] = val if key.present? && val.present? end diff --git a/app/stylesheets/external_tools.sass b/app/stylesheets/external_tools.sass index c65688a5364..a3bf6fd939e 100644 --- a/app/stylesheets/external_tools.sass +++ b/app/stylesheets/external_tools.sass @@ -21,7 +21,6 @@ .content padding: 0 20px 5px .extras - display: none div font-style: italic font-size: 0.9em @@ -52,4 +51,9 @@ &.has_account_navigation div.account_navigation display: block + table + td:first-child + width: 100px + + diff --git a/app/views/external_tools/_external_tools.html.erb b/app/views/external_tools/_external_tools.html.erb index 954dd5e252a..a0eedfffc83 100644 --- a/app/views/external_tools/_external_tools.html.erb +++ b/app/views/external_tools/_external_tools.html.erb @@ -1,94 +1,3 @@ +<% js_bundle :external_tools %>
-

<%= mt :external_tools_note, <<-HEREDOC, :lti_index_url => "https://lti-examples.heroku.com/index.html", :lti_examples_url => "http://help.instructure.com/entries/20878626-lti-tools-and-examples" - External (LTI) Tools are an easy way to add new features to Canvas. - They can be added to individual courses, or to all courses in an account. - Once configured, you can link to them through course modules and create assignments for - assessment tools. - - Click [here](%{lti_index_url}) to see some LTI tools that work great with Canvas. You can also check out the Canvas Community topics about LTI tools [here](%{lti_examples_url}). - HEREDOC - %> -

- <% @context.context_external_tools.active.each do |tool| %> - <%= render :partial => 'external_tools/external_tool', :object => tool %> - <% end %> - <%= render :partial => 'external_tools/external_tool' %> - -
-
-
diff --git a/app/views/jst/ExternalTools/EditView.handlebars b/app/views/jst/ExternalTools/EditView.handlebars new file mode 100644 index 00000000000..685da671d14 --- /dev/null +++ b/app/views/jst/ExternalTools/EditView.handlebars @@ -0,0 +1,116 @@ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + {{#if id}} +

{{#t "shared_secret_note"}}Enter a new value + to change{{/t}}

+ {{/if}} +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
{{#t "custom_feilds_explanation"}}One per + line. Format: name=value{{/t}}
+
+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/app/views/jst/ExternalTools/IndexView.handlebars b/app/views/jst/ExternalTools/IndexView.handlebars new file mode 100644 index 00000000000..dd64562a3e3 --- /dev/null +++ b/app/views/jst/ExternalTools/IndexView.handlebars @@ -0,0 +1,83 @@ +

{{#t "external_tools_note"}} +External (LTI) Tools are an easy way to add new features to Canvas. +They can be added to individual courses, or to all courses in an account. +Once configured, you can link to them through course modules and create assignments for +assessment tools.{{/t}}

+ +

{{#t "external_tools_references"}}Click here to see some +LTI tools that work great with Canvas. You can also check out the Canvas +Community topics about LTI tools +here +{{/t}}

+ +{{#each this}} +
+
+
{{name}}
+ +
+
+ + + + + + + + + {{#if url}} + + + + + {{/if}} + {{#if domain}} + + + + + {{/if}} + + + + + {{#if vendor_help_link}} + + + + + {{/if}} + {{#if extras}} + + + + + {{/if}} +
{{#t "privacy"}}Privacy{{/t}}:{{titleize privacy_level}}
{{#t "consumer_key"}}Consumer Key{{/t}}:{{consumer_key}}
{{#t "url"}}URL{{/t}}:{{url}}
{{#t "domain"}}Domain{{/t}}:{{domain}}
{{#t "description"}}Description{{/t}}:{{description}}
{{#t "extras"}}Extras{{/t}}: + {{#each extras}}
{{this.text}}
{{/each}} +
+
+
+
+{{/each}} + +
+ +
diff --git a/lib/api/v1/external_tools.rb b/lib/api/v1/external_tools.rb index 1b40b7d434d..b36e7de8eaf 100644 --- a/lib/api/v1/external_tools.rb +++ b/lib/api/v1/external_tools.rb @@ -26,10 +26,11 @@ module Api::V1::ExternalTools end def external_tool_json(tool, context, user, session, extension_types = ContextExternalTool::EXTENSION_TYPES) - methods = %w[privacy_level custom_fields] + methods = %w[privacy_level custom_fields workflow_state vendor_help_link] methods += extension_types json = api_json(tool, user, session, - :only => %w(id name description url domain consumer_key created_at updated_at), + :only => %w(id name description url domain consumer_key + created_at updated_at description), :methods => methods ) diff --git a/public/javascripts/external_tools.js b/public/javascripts/external_tools.js deleted file mode 100644 index 40cffe10e2d..00000000000 --- a/public/javascripts/external_tools.js +++ /dev/null @@ -1,171 +0,0 @@ -define([ - 'i18n!external_tools', - 'jquery' /* $ */, - 'jquery.instructure_forms' /* formSubmit, fillFormData */, - 'jqueryui/dialog', - 'compiled/jquery/fixDialogButtons' /* fix dialog formatting */, - 'jquery.instructure_misc_plugins' /* confirmDelete, showIf */, - 'jquery.templateData' /* fillTemplateData, getTemplateData */ -], function(I18n, $) { - -$(document).ready(function() { - var $dialog = $("#external_tools_dialog"); - - $(".add_tool_link").click(function(event) { - event.preventDefault(); - - var formData = { - domain: "", - url: "", - config_url: "", - config_xml: "", - description: "", - name: "", - custom_fields_string: "", - privacy: "anonymous", - consumer_key: "", - shared_secret: "" - } - - $dialog.dialog({ - title: I18n.t('titles.edit_external_tool', "Edit External Tool"), - width: 600 - }).fixDialogButtons(); - $dialog.find(".shared_secret_note").hide(); - $dialog.find("form") - .attr('method', 'POST') - .attr('action', $dialog.find(".external_tools_url").attr('href')); - $dialog.fillFormData(formData, {object_name: 'external_tool'}); - $dialog.find(".config_type_option").show(); - - $("#external_tool_match_by").val('domain').change(); - $("#external_tool_config_type").val('manual').change(); - }); - - /* Form Submit Summary */ - $dialog.find("form").formSubmit({ - beforeSubmit: function(data) { - $(this).find("button") - .attr('disabled', true) - .filter('.save_button') - .text(I18n.t('messages.saving_tool_settings', "Saving Tool Settings...")); - }, - success: function(tool) { - var $this = $(this); - var $tool = $("#external_tool_" + tool.id); - - $this.find("button") - .attr('disabled', false) - .filter('.save_button') - .text(I18n.t('buttons.save_tool_settings', "Save Tool Settings")); - - $dialog.dialog('close'); - - var tool_div_doesnt_exist = $tool.length == 0; - if(tool_div_doesnt_exist) { - $tool = $("#external_tool_blank").clone(true) - .removeAttr('id'); - $("#external_tools").append($tool); - } - - $tool.fillTemplateData({ - data: tool, - dataValues: ['id', 'workflow_state'], - hrefValues: ['id', 'vendor_help_link'], - id: 'external_tool_' + tool.id - }); - - /* Clear the shared seceret input field because it never gets updated and - * should no longer be visible after you update * - */ - $('#external_tool_shared_secret').val(''); - - $tool - .toggleClass('has_editor_button', tool.has_editor_button) - .toggleClass('has_resource_selection', tool.has_resource_selection) - .toggleClass('has_course_navigation', tool.has_course_navigation) - .toggleClass('has_homework_submission', tool.has_homework_submission) - .toggleClass('has_user_navigation', tool.has_user_navigation) - .toggleClass('has_account_navigation', tool.has_account_navigation); - - $tool.find(".tool_url") - .showIf(tool.url).end() - .find(".tool_domain") - .showIf(tool.domain); - - $tool.find(".tool_vendor_help_link") - .showIf(tool.vendor_help_link); - - $tool.show(); - }, - error: function(data) { - $(this).find("button") - .attr('disabled', false) - .filter('.save_button') - .text(I18n.t('errors.save_tool_settings_failed', "Save Tool Settings Failed")); - } - }); - - $dialog.find(".cancel_button").click(function() { - $dialog.dialog('close'); - $('#external_tool_shared_secret').val(''); - }); - - $("#external_tools").delegate('.edit_tool_link', 'click', function(event) { - event.preventDefault(); - - var $tool = $(this).parents(".external_tool"); - var data = $tool.getTemplateData({textValues: ['name', 'description', 'domain', 'url', 'consumer_key', 'custom_fields_string'], dataValues: ['id', 'workflow_state']}); - - data.privacy_level = data.workflow_state; - - $("#external_tool_match_by").val(data.url ? 'url' : 'domain').change(); - - $dialog.find(".shared_secret_note").show(); - - $dialog.find("form") - .attr('method', 'PUT') - .attr('action', $tool.find(".update_tool_url").attr('rel')); - - $dialog.fillFormData(data, {object_name: 'external_tool'}); - - $dialog.dialog({ - title: I18n.t('titles.edit_external_tool', "Edit External Tool"), - width: 600 - }); - - $dialog.find(".config_type_option").hide(); - $("#external_tool_config_type").val('manual').change(); - - }).delegate('.delete_tool_link', 'click', function(event) { - event.preventDefault(); - var $tool = $(this).parents(".external_tool"); - var url = $tool.find(".update_tool_url").attr('rel'); - $tool.confirmDelete({ - url: url, - message: I18n.t('prompts.remove_tool', "Are you sure you want to remove this tool? Any courses using this tool will no longer work."), - success: function() { - $(this).slideUp(function() { - $(this).remove(); - }); - } - }); - }); - - $("#external_tool_match_by").change(function() { - if($(this).val() == 'url') { - $(this).parents("form").find(".tool_domain").hide().find(":text").val("").end().end() - .find(".tool_url").show(); - } else { - $(this).parents("form").find(".tool_url").hide().find(":text").val("").end().end() - .find(".tool_domain").show(); - } - }); - - $("#external_tool_config_type").change(function(event) { - $("#external_tool_form .config_type").hide(); - $("#external_tool_form .config_type." + $(this).val()).show(); - }); - -}); -}); diff --git a/spec/apis/v1/external_tools_api_spec.rb b/spec/apis/v1/external_tools_api_spec.rb index ff58f4eda5d..6884c0d7c5e 100644 --- a/spec/apis/v1/external_tools_api_spec.rb +++ b/spec/apis/v1/external_tools_api_spec.rb @@ -111,7 +111,7 @@ describe ExternalToolsController, :type => :integration do {:controller => 'external_tools', :action => 'show', :format => 'json', :"#{type}_id" => context.id.to_s, :external_tool_id => et.id.to_s}) - json.should == example_json(et) + json.diff(example_json(et)).should == {} end def not_found_call(context, type="course") @@ -128,7 +128,8 @@ describe ExternalToolsController, :type => :integration do {:controller => 'external_tools', :action => 'index', :format => 'json', :"#{type}_id" => context.id.to_s}) - json.should == [example_json(et)] + json.size.should == 1 + json.first.diff(example_json(et)).should == {} end def create_call(context, type="course") @@ -138,7 +139,7 @@ describe ExternalToolsController, :type => :integration do context.context_external_tools.count.should == 1 et = context.context_external_tools.last - json.should == example_json(et) + json.diff(example_json(et)).should == {} end def update_call(context, type="course") @@ -148,7 +149,7 @@ describe ExternalToolsController, :type => :integration do {:controller => 'external_tools', :action => 'update', :format => 'json', :"#{type}_id" => context.id.to_s, :external_tool_id => et.id.to_s}, post_hash) et.reload - json.should == example_json(et) + json.diff(example_json(et)).should == {} end def destroy_call(context, type="course") @@ -251,6 +252,7 @@ describe ExternalToolsController, :type => :integration do "domain"=>nil, "url"=>"http://www.example.com/ims/lti", "id"=>et ? et.id : nil, + "workflow_state"=>"public", "resource_selection"=> {"text"=>"", "url"=>"http://www.example.com/ims/lti/resource", diff --git a/spec/coffeescripts/views/external_tools/EditViewSpec.coffee b/spec/coffeescripts/views/external_tools/EditViewSpec.coffee new file mode 100644 index 00000000000..c5e11fe6c7f --- /dev/null +++ b/spec/coffeescripts/views/external_tools/EditViewSpec.coffee @@ -0,0 +1,29 @@ +define [ + 'jquery' + 'compiled/views/ExternalTools/EditView' +], ($, EditView) -> + + module 'ExternalTools', + setup: -> + @view = new EditView() + @view.render() + + teardown: -> + @view.$el.dialog 'close' + + test 'adds errors', 6, -> + @view.addError(@view.$('input').first(), 'Wrong!') + equal $('.help-inline').size(), 1 + equal $('.error').size(), 1 + ok $(".help-inline:contains('Wrong!')").is ':visible' + @view.addError(@view.$('input').last(), 'Also Wrong...') + equal $('.help-inline').size(), 2 + equal $('.error').size(), 2 + ok $(".help-inline:contains('Also Wrong...')").is ':visible' + + test 'removes all errors', 2, -> + @view.addError(@view.$('input').first(), 'Wrong!') + @view.addError(@view.$('input').last(), 'Also Wrong...') + equal $('.help-inline').size(), 2 + @view.removeErrors() + equal $('.help-inline').size(), 0 \ No newline at end of file diff --git a/spec/controllers/external_tools_controller_spec.rb b/spec/controllers/external_tools_controller_spec.rb index 5bb8cdf5f8a..17beef3257a 100644 --- a/spec/controllers/external_tools_controller_spec.rb +++ b/spec/controllers/external_tools_controller_spec.rb @@ -285,7 +285,7 @@ describe ExternalToolsController do response.should_not be_success assigns[:tool].should be_new_record json = json_parse(response.body) - json['errors']['base'][0]['message'].should == I18n.t(:invalid_xml_syntax, 'Invalid xml syntax') + json['errors']['config_xml'][0]['message'].should == I18n.t(:invalid_xml_syntax, 'Invalid xml syntax') course_with_teacher_logged_in(:active_all => true) xml = "c" @@ -293,7 +293,7 @@ describe ExternalToolsController do response.should_not be_success assigns[:tool].should be_new_record json = json_parse(response.body) - json['errors']['base'][0]['message'].should == I18n.t(:invalid_xml_syntax, 'Invalid xml syntax') + json['errors']['config_xml'][0]['message'].should == I18n.t(:invalid_xml_syntax, 'Invalid xml syntax') end it "should handle advanced xml configurations by URL retrieval" do @@ -348,7 +348,7 @@ describe ExternalToolsController do response.should_not be_success assigns[:tool].should be_new_record json = json_parse(response.body) - json['errors']['base'][0]['message'].should == I18n.t(:retrieve_timeout, 'could not retrieve configuration, the server response timed out') + json['errors']['config_url'][0]['message'].should == I18n.t(:retrieve_timeout, 'could not retrieve configuration, the server response timed out') end end diff --git a/spec/selenium/admin/admin_settings_spec.rb b/spec/selenium/admin/admin_settings_spec.rb index 2aa1b07bee6..d05235649a3 100644 --- a/spec/selenium/admin/admin_settings_spec.rb +++ b/spec/selenium/admin/admin_settings_spec.rb @@ -55,7 +55,7 @@ describe "admin settings tabs" do it "should delete an external tool" do add_external_tool hover_and_click(".delete_tool_link:visible") - driver.switch_to.alert.accept + fj('.ui-dialog button:contains(Delete):visible').click wait_for_ajax_requests tool = ContextExternalTool.last tool.workflow_state.should == "deleted" @@ -67,7 +67,7 @@ describe "admin settings tabs" do new_description = "a different description" hover_and_click(".edit_tool_link:visible") replace_content(f("#external_tool_description"), new_description) - fj(".save_button:visible").click + fj('.ui-dialog button:contains(Submit):visible').click wait_for_ajax_requests tool = ContextExternalTool.last tool.description.should == new_description diff --git a/spec/selenium/external_tools_spec.rb b/spec/selenium/external_tools_spec.rb index 1d5388e5af7..a1dbce5d419 100644 --- a/spec/selenium/external_tools_spec.rb +++ b/spec/selenium/external_tools_spec.rb @@ -16,7 +16,7 @@ describe "editing external tools" do f('#external_tool_consumer_key').send_keys('fdjaklfjdaklfdjaslfjajfkljsalkjflas') f('#external_tool_shared_secret').send_keys('r08132ufio1jfj1iofj3j1kf3ljl1') f('#external_tool_domain').send_keys('instructure.com') - submit_form('#external_tool_form') + fj('.ui-dialog:visible .btn-primary').click() wait_for_ajaximations f("#external_tool_#{ContextExternalTool.find_by_name(tool_name).id} .edit_tool_link").click f('#external_tool_name').should have_attribute(:value, tool_name) @@ -27,7 +27,6 @@ describe "editing external tools" do get "/courses/#{@course.id}/settings" f("#tab-tools-link").click add_external_tool - f("#external_tools_dialog").should_not be_displayed tool = ContextExternalTool.last tool_elem = f("#external_tool_#{tool.id}") tool_elem.should be_displayed @@ -44,18 +43,15 @@ describe "editing external tools" do get "/courses/#{@course.id}/settings" keep_trying_until { f("#tab-tools-link").should be_displayed } f("#tab-tools-link").click - tool_elem = f("#external_tool_#{tool.id}") - tool_elem.find_element(:css, ".edit_tool_link").click - f("#external_tools_dialog").should be_displayed + f("#external_tool_#{tool.id} .edit_tool_link").click replace_content(f("#external_tool_name"), "new tool (updated)") replace_content(f("#external_tool_consumer_key"), "key (updated)") replace_content(f("#external_tool_shared_secret"), "secret (updated)") replace_content(f("#external_tool_domain"), "example2.com") replace_content(f("#external_tool_custom_fields_string"), "a=9\nb=8") - f("#external_tools_dialog .save_button").click + fj('.ui-dialog:visible .btn-primary').click() wait_for_ajax_requests - f("#external_tools_dialog").should_not be_displayed - tool_elem = fj("#external_tools .external_tool:visible").should be_displayed + tool_elem = fj("#external_tools .external_tool").should be_displayed tool_elem.should_not be_nil tool.reload tool.name.should == "new tool (updated)" diff --git a/spec/selenium/helpers/external_tools_common.rb b/spec/selenium/helpers/external_tools_common.rb index e815ae3560f..0f2d7b2a7c9 100644 --- a/spec/selenium/helpers/external_tools_common.rb +++ b/spec/selenium/helpers/external_tools_common.rb @@ -9,7 +9,6 @@ shared_examples_for "external tools tests" do secret = "secret" f("#tab-tools .add_tool_link").click - f("#external_tools_dialog").should be_displayed f("#external_tool_name").send_keys(name) f("#external_tool_consumer_key").send_keys(key) @@ -21,7 +20,7 @@ shared_examples_for "external tools tests" do else add_manual opts end - submit_form("#external_tools_dialog") + fj('.ui-dialog:visible .btn-primary').click() wait_for_ajax_requests # ContextExternalTool.count.should != 0 tool = ContextExternalTool.last @@ -38,24 +37,16 @@ shared_examples_for "external tools tests" do tool.url.should == url tool.workflow_state.should == "public" tool.description.should == "Description" - tool.has_editor_button.should be_true - tool.has_resource_selection.should be_true - tool.has_course_navigation.should be_true - tool.has_account_navigation.should be_true - tool.has_user_navigation.should be_true f("#external_tool_#{tool.id} .url").text.should == url - f("#external_tool_#{tool.id} .editor_button").should be_displayed - f("#external_tool_#{tool.id} .resource_selection").should be_displayed - f("#external_tool_#{tool.id} .course_navigation").should be_displayed - f("#external_tool_#{tool.id} .user_navigation").should be_displayed - f("#external_tool_#{tool.id} .account_navigation").should be_displayed f("#external_tool_#{tool.id} .readable_state").text.should == "Public" f("#external_tool_#{tool.id} .description").text.should == "Description" f("#external_tool_#{tool.id} .vendor_help_link").should be_displayed f("#external_tool_#{tool.id} .vendor_help_link").text.should == tool.vendor_help_link ContextExternalTool::EXTENSION_TYPES.each do |type| + tool.extension_setting(type).should_not be_empty f("#external_tool_#{tool.id} .#{type}").should be_displayed end + f("#external_tool_#{tool.id} .url").text.should eql url elsif opts.include? :url url = "https://lti-examples.heroku.com/tool_redirect" kitten_text = "pictures of kittens to your site" @@ -100,7 +91,7 @@ shared_examples_for "external tools tests" do def add_manual (opts) f("#external_tool_config_type option[value='manual']").click - f("#external_tool_form .config_type.manual").should be_displayed + f(".config_type.manual").should be_displayed f("#external_tool_config_url").should_not be_displayed f("#external_tool_config_xml").should_not be_displayed @custom_key = "value" @@ -111,10 +102,8 @@ shared_examples_for "external tools tests" do f("#external_tool_description").send_keys(@description) if opts.include? :manual_url @manual_url = @domain+":80" - f("#external_tool_match_by option[value='url']").click f("#external_tool_url").send_keys(@manual_url) else - f("#external_tool_match_by option[value='domain']").click f("#external_tool_domain").send_keys(@domain) end @@ -139,7 +128,7 @@ shared_examples_for "external tools tests" do def add_xml f("#external_tool_config_type option[value='by_xml']").click - f("#external_tool_form .config_type.manual").should_not be_displayed + f(".config_type.manual").should_not be_displayed f("#external_tool_config_url").should_not be_displayed f("#external_tool_config_xml").should be_displayed