diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 272c3155..03565ab8 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -113,6 +113,7 @@ wizardFieldSelection=true userFieldSelection='key,value' categorySelection='output' + wizardActionSelection='output' outputDefaultSelection='category' context='action' )}} @@ -198,6 +199,7 @@ textSelection='value' userFieldSelection='key' wizardFieldSelection='value' + wizardActionSelection='value' keyDefaultSelection='userField' context='action' )}} @@ -270,6 +272,7 @@ textSelection='value,output' wizardFieldSelection='key,value,assignment' userFieldSelection='key,value,assignment' + wizardActionSelection=true groupSelection='value,output' outputDefaultSelection='group' context='action' diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index e370a1dd..9318d16b 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -11,11 +11,11 @@ class CustomWizard::StepsController < ::ApplicationController permitted[:fields] = params[:fields].select { |k, v| field_ids.include? k } permitted.permit! end - + wizard = CustomWizard::Builder.new(permitted[:wizard_id].underscore, current_user).build updater = wizard.create_updater(permitted[:step_id], permitted[:fields]) updater.update - + if updater.success? result = success_json result.merge!(updater.result) if updater.result diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index e37a9a4c..a28498e6 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -77,6 +77,11 @@ class CustomWizard::Action multiple: true ).perform + if targets.blank? + log_error("no recipients", "send_message has no recipients") + return + end + targets.each do |target| if Group.find_by(name: target) params[:target_group_names] = target @@ -119,12 +124,18 @@ class CustomWizard::Action def update_profile params = {} - + if (profile_updates = action['profile_updates']) profile_updates.first[:pairs].each do |pair| if allowed_profile_field?(pair['key']) key = cast_profile_key(pair['key']) - value = cast_profile_value(mapper.map_field(pair['value'], pair['value_type']), pair['key']) + value = cast_profile_value( + mapper.map_field( + pair['value'], + pair['value_type'] + ), + pair['key'] + ) if user_field?(pair['key']) params[:custom_fields] ||= {} @@ -137,10 +148,10 @@ class CustomWizard::Action end params = add_custom_fields(params) - + if params.present? result = UserUpdater.new(Discourse.system_user, user).update(params) - + if params[:avatar].present? result = update_avatar(params[:avatar]) end @@ -250,11 +261,11 @@ class CustomWizard::Action def open_composer params = basic_topic_params - + if params[:title].present? && params[:raw].present? url = "/new-topic?title=#{params[:title]}" url += "&body=#{params[:raw]}" - + if category_id = action_category if category_id && category = Category.find(category_id) url += "&category=#{category.full_slug('/')}" @@ -266,7 +277,7 @@ class CustomWizard::Action end route_to = Discourse.base_uri + URI.encode(url) - data['redirect_on_complete'] = route_to + data['route_to'] = route_to log_info("route: #{route_to}") else @@ -283,8 +294,15 @@ class CustomWizard::Action multiple: true } ).perform + + group_map = group_map.flatten.compact + + unless group_map.present? + log_error("invalid group map") + return + end - groups = group_map.flatten.reduce([]) do |groups, g| + groups = group_map.reduce([]) do |groups, g| begin groups.push(Integer(g)) rescue ArgumentError @@ -324,7 +342,7 @@ class CustomWizard::Action user: user ).perform end - + if action['code'] data[action['code']] = SecureRandom.hex(8) url += "&#{action['code']}=#{data[action['code']]}" @@ -336,20 +354,36 @@ class CustomWizard::Action log_info("route: #{route_to}") end - def create_group + def create_group group = begin - Group.new(new_group_params) + Group.new(new_group_params.except(:usernames, :owner_usernames)) rescue ArgumentError => e raise Discourse::InvalidParameters, "Invalid group params" end if group.save - GroupActionLogger.new(user, group).log_change_group_settings + def get_user_ids(username_string) + User.where(username: username_string.split(",")).pluck(:id) + end + + if new_group_params[:owner_usernames].present? + owner_ids = get_user_ids(new_group_params[:owner_usernames]) + owner_ids.each { |user_id| group.group_users.build(user_id: user_id, owner: true) } + end + + if new_group_params[:usernames].present? + user_ids = get_user_ids(new_group_params[:usernames]) + user_ids -= owner_ids if owner_ids + user_ids.each { |user_id| group.group_users.build(user_id: user_id) } + end + + GroupActionLogger.new(user, group, skip_guardian: true).log_change_group_settings log_success("Group created", group.name) + result.output = group.name else - log_error("Group creation failed") + log_error("Group creation failed", group.errors.messages) end end @@ -366,7 +400,7 @@ class CustomWizard::Action log_success("Category created", category.name) result.output = category.id else - log_error("Category creation failed") + log_error("Category creation failed", category.errors.messages) end end @@ -378,7 +412,7 @@ class CustomWizard::Action data: data, user: user ).perform - + if output.is_a?(Array) output.first elsif output.is_a?(Integer) @@ -505,10 +539,12 @@ class CustomWizard::Action user: user ).perform - value = value.parameterize(separator: '_') if attr === "name" - value = value.to_i if attr.include?("_level") - - params[attr.to_sym] = value + if value + value = value.parameterize(separator: '_') if attr === "name" + value = value.to_i if attr.include?("_level") + + params[attr.to_sym] = value + end end end @@ -533,30 +569,36 @@ class CustomWizard::Action user: user ).perform - if attr === "parent_category_id" && value.is_a?(Array) - value = value[0] - end - - if attr === "permissions" && value.is_a?(Array) - permissions = value - value = {} - - permissions.each do |p| - k = p[:key] - v = p[:value].to_i - - if k.is_a?(Array) - group = Group.find_by(id: k[0]) - k = group.name - else - k = k.parameterize(separator: '_') - end - - value[k] = v + if value + if attr === "parent_category_id" && value.is_a?(Array) + value = value[0] end + + if attr === "permissions" && value.is_a?(Array) + permissions = value + value = {} + + permissions.each do |p| + k = p[:key] + v = p[:value].to_i + + if k.is_a?(Array) + group = Group.find_by(id: k[0]) + k = group.name + else + k = k.parameterize(separator: '_') + end + + value[k] = v + end + end + + if attr === 'slug' + value = value.parameterize(separator: '-') + end + + params[attr.to_sym] = value end - - params[attr.to_sym] = value end end @@ -584,6 +626,8 @@ class CustomWizard::Action end def cast_profile_value(value, key) + return value if value.nil? + if profile_url_fields.include?(key) value['url'] elsif key === 'avatar' diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 0af53245..728ed847 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -133,7 +133,7 @@ class CustomWizard::Builder validate_field(field, updater, step_template) if field['type'] != 'text_only' end end - + next if updater.errors.any? CustomWizard::Builder.step_handlers.each do |handler| @@ -156,10 +156,10 @@ class CustomWizard::Builder if @actions.present? @actions.each do |action| - + if (action['run_after'] === updater.step.id) || (final_step && (!action['run_after'] || (action['run_after'] === 'wizard_completion'))) - + CustomWizard::Action.new( wizard: @wizard, action: action, @@ -217,7 +217,7 @@ class CustomWizard::Builder submission = @submissions.last params[:value] = submission[field_template['id']] if submission[field_template['id']] end - + params[:value] = prefill_field(field_template, step_template) || params[:value] if field_template['type'] === 'group' && params[:value].present? @@ -339,7 +339,7 @@ class CustomWizard::Builder if type === 'time' && value.present? && !validate_time(value) updater.errors.add(id, I18n.t('wizard.field.invalid_time')) end - + CustomWizard::Builder.field_validators.each do |validator| if type === validator[:type] validator[:block].call(field, updater, step_template) diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index 72bf8396..97791703 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -48,7 +48,7 @@ class CustomWizard::Mapper inputs.each do |input| input_type = input['type'] pairs = input['pairs'] - + if (input_type === 'conditional' && validate_pairs(pairs)) || input_type === 'assignment' output = input['output'] output_type = input['output_type'] diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 6437c8c4..9ebfe779 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -1,17 +1,27 @@ +require 'rails_helper' + describe CustomWizard::Action do - let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}} - let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}} - let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}} - let(:open_composer_action) {{"id":"open_composer","type":"open_composer","title":"text","post":"textarea"}} - let(:add_to_group_action) {{"id":"add_to_group","type":"add_to_group","group_id":"dropdown_groups"}} + fab!(:user) { Fabricate(:user, name: "Angus", username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) } + fab!(:category) { Fabricate(:category, name: 'cat1', slug: 'cat-slug') } + fab!(:group) { Fabricate(:group) } + + before do + Group.refresh_automatic_group!(:trust_level_2) + template = JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + CustomWizard::Wizard.add_wizard(template) + @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) + end it 'creates a topic' do - template['steps'][0]['fields'] = [text_field, textarea_field] - template['steps'][0]["actions"] = [create_topic_action] - updater = run_update(template, nil, - text: "Topic Title", - textarea: "topic body" - ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps[0].id, + step_1_field_1: "Topic Title", + step_1_field_2: "topic body" + ).update + updater2 = built_wizard.create_updater(built_wizard.steps[1].id, {}).update + topic = Topic.where(title: "Topic Title") expect(topic.exists?).to eq(true) @@ -22,71 +32,102 @@ describe CustomWizard::Action do end it 'sends a message' do - fields = [text_field, textarea_field] + User.create(username: 'angus1', email: "angus1@email.com") - if extra_field - fields.push(extra_field) - end - - template['steps'][0]['fields'] = fields - template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)] - - run_update(template, nil, - text: "Message Title", - textarea: "message body" - ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, {}).update + built_wizard.create_updater(built_wizard.steps[1].id, {}).update topic = Topic.where( archetype: Archetype.private_message, - title: "Message Title" + title: "Message title" ) - expect(topic.exists?).to eq(true) - expect( - topic.first.topic_allowed_users.first.user.username - ).to eq('angus') - expect(Post.where( + post = Post.where( topic_id: topic.pluck(:id), - raw: "message body" - ).exists?).to eq(true) + raw: "I will interpolate some wizard fields" + ) + + expect(topic.exists?).to eq(true) + expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1') + expect(post.exists?).to eq(true) end it 'updates a profile' do - run_update(template, template['steps'][1]['id'], name: "Sally") - expect(user.name).to eq('Sally') + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + upload = Upload.create!( + url: '/images/image.png', + original_filename: 'image.png', + filesize: 100, + user_id: -1, + ) + steps = built_wizard.steps + built_wizard.create_updater(steps[0].id, {}).update + built_wizard.create_updater(steps[1].id, + step_2_field_7: upload.as_json, + ).update + expect(user.profile_background_upload.id).to eq(upload.id) end it 'opens a composer' do - template['steps'][0]['fields'] = [text_field, textarea_field] - template['steps'][0]["actions"] = [open_composer_action] + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update - updater = run_update(template, nil, - text: "Topic Title", - textarea: "topic body" - ) + updater = built_wizard.create_updater(built_wizard.steps[1].id, {}) + updater.update - expect(updater.result.blank?).to eq(true) + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + category = Category.find_by(id: submissions.first['action_8']) - updater = run_update(template, template['steps'][1]['id']) - - expect(updater.result[:redirect_on_complete]).to eq( - "/new-topic?title=Topic%20Title&body=topic%20body" + expect(updater.result[:redirect_on_next]).to eq( + "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus@email.com&category=#{category.slug}/#{category.id}&tags=tag1" ) end - it 'adds a user to a group' do - template['steps'][0]['fields'] = [dropdown_groups_field] - template['steps'][0]["actions"] = [add_to_group_action] - - updater = run_update(template, nil, dropdown_groups: group.id) + it 'creates a category' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update + built_wizard.create_updater(built_wizard.steps[1].id, {}).update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(Category.where(id: submissions.first['action_8']).exists?).to eq(true) + end + + it 'creates a group' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + step_id = built_wizard.steps[0].id + updater = built_wizard.create_updater(step_id, step_1_field_1: "Text input").update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(Group.where(name: submissions.first['action_9']).exists?).to eq(true) + end + + it 'adds a user to a group' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + step_id = built_wizard.steps[0].id + updater = built_wizard.create_updater(step_id, step_1_field_1: "Text input").update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + group = Group.find_by(name: submissions.first['action_9']) expect(group.users.first.username).to eq('angus') end + it 'watches categories' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update + built_wizard.create_updater(built_wizard.steps[1].id, {}).update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(CategoryUser.where( + category_id: submissions.first['action_8'], + user_id: user.id + ).first.notification_level).to eq(2) + expect(CategoryUser.where( + category_id: category.id, + user_id: user.id + ).first.notification_level).to eq(0) + end + it 're-routes a user' do - template['steps'][0]["actions"] = [route_to_action] - updater = run_update(template, nil, {}) - expect(updater.result[:redirect_on_next]).to eq( - "https://google.com" - ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps.last.id, {}) + updater.update + expect(updater.result[:redirect_on_complete]).to eq("https://google.com") end end diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index ba8da001..9a7c8b60 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -3,39 +3,19 @@ require 'rails_helper' describe CustomWizard::Builder do - fab!(:user) { Fabricate(:user, username: 'angus') } - fab!(:trusted_user) { Fabricate(:user, trust_level: 3) } + fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) } + fab!(:new_user) { Fabricate(:user, trust_level: 0) } fab!(:category1) { Fabricate(:category, name: 'cat1') } fab!(:category2) { Fabricate(:category, name: 'cat2') } fab!(:group) { Fabricate(:group) } - let!(:template) do - JSON.parse(File.open( + before do + Group.refresh_automatic_group!(:trust_level_2) + template = JSON.parse(File.open( "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" ).read) - end - - def build_wizard(t = template, u = user, build_opts = {}, params = {}) - CustomWizard::Wizard.add_wizard(t) - CustomWizard::Builder.new('welcome', u).build(build_opts, params) - end - - def add_submission_data(data = {}) - PluginStore.set("welcome_submissions", user.id, { - name: 'Angus', - website: 'https://thepavilion.io' - }.merge(data)) - end - - def get_submission_data - PluginStore.get("welcome_submissions", user.id) - end - - def run_update(t = template, step_id = nil, data = {}) - wizard = build_wizard(t) - updater = wizard.create_updater(step_id || t['steps'][0]['id'], data) - updater.update - updater + CustomWizard::Wizard.add_wizard(template) + @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) end context 'disabled' do @@ -43,15 +23,8 @@ describe CustomWizard::Builder do SiteSetting.custom_wizard_enabled = false end - it "returns no steps" do - wizard = build_wizard - expect(wizard.steps.length).to eq(0) - expect(wizard.name).to eq('Welcome') - end - - it "doesn't save submissions" do - run_update(template, nil, name: 'Angus') - expect(get_submission_data.blank?).to eq(true) + it "returns nil" do + expect(CustomWizard::Builder.new(@wizard.id, user).build).to eq(nil) end end @@ -61,123 +34,164 @@ describe CustomWizard::Builder do end it "returns steps" do - expect(build_wizard.steps.length).to eq(2) + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps.length + ).to eq(2) end it 'returns no steps if multiple submissions are disabled and user has completed' do + wizard_template = CustomWizard::Wizard.find(@wizard.id) + wizard_template[:multiple_submissions] = false + CustomWizard::Wizard.save(wizard_template) + history_params = { action: UserHistory.actions[:custom_wizard_step], acting_user_id: user.id, - context: template['id'] + context: @wizard.id } - UserHistory.create!(history_params.merge(subject: template['steps'][0]['id'])) - UserHistory.create!(history_params.merge(subject: template['steps'][1]['id'])) + @wizard.steps.each do |step| + UserHistory.create!(history_params.merge(subject: step.id)) + end - template["multiple_submissions"] = false - expect(build_wizard(template).steps.length).to eq(0) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps.length + ).to eq(0) end it 'returns no steps if user is not permitted' do - template["min_trust"] = 3 - expect(build_wizard(template).steps.length).to eq(0) + expect( + CustomWizard::Builder.new(@wizard.id, new_user).build.steps.length + ).to eq(0) end it 'returns steps if user is permitted' do - template["min_trust"] = 3 - expect(build_wizard(template, trusted_user).steps.length).to eq(2) + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps.length + ).to eq(3) end it 'returns a wizard with prefilled data if user has partially completed it' do - add_submission_data - wizard = build_wizard - expect(wizard.steps[0].fields.first.value).to eq('Angus') - expect(wizard.steps[1].fields.first.value).to eq('https://thepavilion.io') + expect( + CustomWizard::Builder.new(@wizard.id, user) + .build + .steps[0].fields[0].value + ).to eq('I am prefilled') end it 'returns a wizard with no prefilled data if options include reset' do - add_submission_data - wizard = build_wizard(template, user, reset: true) - expect(wizard.steps[0].fields.first.value).to eq(nil) - expect(wizard.steps[1].fields.first.value).to eq(nil) + PluginStore.set("super_mega_fun_wizard_submissions", user.id, { + text: 'Input into text', + }) + expect( + CustomWizard::Builder.new(@wizard.id, user) + .build(reset: true) + .steps[0].fields[0].value + ).to eq(nil) end context 'building steps' do it 'returns step metadata' do - expect(build_wizard.steps[0].title).to eq('Welcome to Pavilion') - expect(build_wizard.steps[1].title).to eq('Tell us about you') + expect( + CustomWizard::Builder.new(@wizard.id, user) + .build(reset: true) + .steps[0] + ).to eq('Super Mega Fun Wizard') end it 'saves permitted params' do - template['steps'][0]['permitted_params'] = permitted_params - wizard = build_wizard(template, user, {}, param_key: 'param_value') - submissions = get_submission_data - expect(submissions.first['submission_param_key']).to eq('param_value') + @wizard.steps[0].permitted_params = permitted_params + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build({}, param_key: 'param_value') + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(submissions[0]['submission_param_key']).to eq('param_value') end it 'is not permitted if required data is not present' do - template['steps'][0]['required_data'] = required_data - expect(build_wizard(template, user).steps[0].permitted).to eq(false) + @wizard.steps[0].required_data = required_data + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps[0].permitted + ).to eq(false) end it 'it shows required data message if required data has message' do - template['steps'][0]['required_data'] = required_data - template['steps'][0]['required_data_message'] = required_data_message - add_submission_data(nickname: "John") - wizard = build_wizard(template, user) - expect(wizard.steps[0].permitted).to eq(false) - expect(wizard.steps[0].permitted_message).to eq(required_data_message) + @wizard.steps[0].required_data = required_data + @wizard.steps[0].required_data_message = "Data is required" + PluginStore.set("super_mega_fun_wizard_submissions", user.id, + text: 'Input into text', + ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect(built_wizard.steps[0].permitted).to eq(false) + expect(built_wizard.steps[0].permitted_message).to eq("Data is required") end it 'is permitted if required data is present' do - template['steps'][0]['required_data'] = required_data - PluginStore.set('welcome_submissions', user.id, nickname: "Angus", name: "Angus") - expect(build_wizard(template, user).steps[0].permitted).to eq(true) + @wizard.steps[0].required_data = required_data + PluginStore.set('super_mega_fun_wizard_submissions', user.id, + text: "Input into text" + ) + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps[0].permitted + ).to eq(true) end it 'returns field metadata' do - expect(build_wizard(template, user).steps[0].fields[0].label).to eq("

Name

") - expect(build_wizard(template, user).steps[0].fields[0].type).to eq("text") + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect(built_wizard.steps[0].fields[0].label).to eq("

Name

") + expect(built_wizard.steps[0].fields[0].type).to eq("text") end it 'returns fields' do - template['steps'][0]['fields'][1] = checkbox_field - expect(build_wizard(template, user).steps[0].fields.length).to eq(2) + @wizard.steps[0].fields[1] = checkbox_field + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect(built_wizard.steps[0].fields.length).to eq(2) end end context 'on update' do it 'saves submissions' do - run_update(template, nil, name: 'Angus') - expect(get_submission_data.first['name']).to eq('Angus') + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, + step_1_field_1: 'Text input' + ).update + expect( + PluginStore.get("super_mega_fun_wizard_submissions", user.id) + .first['step_1_field_1'] + ).to eq('Text input') end context 'validation' do it 'applies min length' do - template['steps'][0]['fields'][0]['min_length'] = 10 - updater = run_update(template, nil, name: 'short') - expect(updater.errors.messages[:name].first).to eq( - I18n.t('wizard.field.too_short', label: 'Name', min: 10) + @wizard.steps[0].fields[0].min_length = 10 + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps[0].id, + step_1_field_1: 'Te' + ).update + expect(updater.errors.messages[:text].first).to eq( + I18n.t('wizard.field.too_short', label: 'Text', min: 3) ) end it 'standardises boolean entries' do - template['steps'][0]['fields'][0] = checkbox_field - run_update(template, nil, checkbox: 'false') - expect(get_submission_data.first['checkbox']).to eq(false) + @wizard.steps[0].fields[0] = checkbox_field + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps[1].id, + step_2_field_5: 'false' + ).update + expect( + PluginStore.get("super_mega_fun_wizard_submissions", user.id) + .first['step_2_field_5'] + ).to eq(false) end it 'requires required fields' do - template['steps'][0]['fields'][0]['required'] = true - expect(run_update(template).errors.messages[:name].first).to eq( - I18n.t('wizard.field.required', label: 'Name') - ) + @wizard.steps[0].fields[0]['required'] = true + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps.second.id).update + expect( + updater.errors.messages[:step_1_field_1].first + ).to eq(I18n.t('wizard.field.required', label: 'Text')) end end - - it 'runs actions attached to a step' do - run_update(template, template['steps'][1]['id'], name: "Gus") - expect(user.name).to eq('Gus') - end end end end \ No newline at end of file diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb deleted file mode 100644 index 4fb8a10e..00000000 --- a/spec/components/custom_wizard/mapper_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -describe CustomWizard::Mapper do - -it 'interpolates user data' do - user.name = "Angus" - user.save! - - expect( - CustomWizard::Builder.fill_placeholders( - "My name is u{name}", - user, - {} - ) - ).to eq('My name is Angus') -end \ No newline at end of file diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index a9251f65..09ffc95e 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -1,49 +1,525 @@ { - "id": "welcome", - "name": "Welcome", - "background": "#006da3", + "id": "super_mega_fun_wizard", + "name": "Super Mega Fun Wizard", + "background": "#333333", "save_submissions": true, "multiple_submissions": true, - "after_signup": true, - "min_trust": 1, - "theme_id": 4, + "after_signup": false, + "prompt_completion": true, + "theme_id": 2, + "permitted": [ + { + "type": "assignment", + "output_type": "group", + "output_connector": "set", + "output": [ + 12 + ] + } + ], "steps": [ { - "id": "welcome", - "title": "Welcome to Pavilion", - "raw_description": "Hey there, thanks for signing up.\n\nWe're Pavilion, an international freelancer cooperative that specialises in online communities.\n\nThis site is our own community, where we work with our clients, users of our open source work and our broader community.\n\n", - "description": "

Hey there, thanks for signing up.

\n

We’re Pavilion, an international freelancer cooperative that specialises in online communities.

\n

This site is our own community, where we work with our clients, users of our open source work and our broader community.

", + "id": "step_1", + "title": "Text", + "raw_description": "Text inputs!", "fields": [ { - "id": "name", + "id": "step_1_field_1", + "label": "Text", "type": "text", - "label": "Name" + "min_length": "3", + "prefill": [ + { + "type": "assignment", + "output": "I am prefilled", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "step_1_field_2", + "label": "Textarea", + "type": "textarea", + "min_length": "" + }, + { + "id": "step_1_field_3", + "label": "Composer", + "type": "composer" + }, + { + "id": "step_1_field_4", + "label": "I'm only text", + "description": "", + "type": "text_only" + } + ], + "description": "

Text inputs!

" + }, + { + "id": "step_2", + "title": "Values", + "raw_description": "Because I couldn't think of another name for this step :)", + "fields": [ + { + "id": "step_2_field_1", + "label": "Date", + "type": "date", + "format": "YYYY-MM-DD" + }, + { + "id": "step_2_field_2", + "label": "Time", + "type": "time", + "format": "HH:mm" + }, + { + "id": "step_2_field_3", + "label": "Date & Time", + "type": "date_time", + "format": "" + }, + { + "id": "step_2_field_4", + "label": "Number", + "type": "number" + }, + { + "id": "step_2_field_5", + "label": "Checkbox", + "type": "checkbox" + }, + { + "id": "step_2_field_7", + "label": "Upload", + "type": "upload", + "file_types": ".jpg,.png" + } + ], + "description": "

Because I couldn’t think of another name for this step \":slight_smile:\"

" + }, + { + "id": "step_3", + "title": "Combo-boxes", + "raw_description": "Unfortunately not the edible type :sushi: ", + "fields": [ + { + "id": "step_3_field_1", + "label": "Custom Dropdown", + "type": "dropdown", + "content": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "choice1", + "key_type": "text", + "value": "Choice 1", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "choice2", + "key_type": "text", + "value": "Choice 2", + "value_type": "text", + "connector": "association" + }, + { + "index": 2, + "key": "choice3", + "key_type": "text", + "value": "Choice 3", + "value_type": "text", + "connector": "association" + } + ] + } + ] + }, + { + "id": "step_3_field_2", + "label": "Tag", + "type": "tag" + }, + { + "id": "step_3_field_3", + "label": "Category", + "type": "category", + "limit": 1, + "property": "id" + }, + { + "id": "step_3_field_4", + "label": "Group", + "type": "group" + }, + { + "id": "step_3_field_5", + "label": "User Selector", + "description": "", + "type": "user_selector" + } + ], + "description": "

Unfortunately not the edible type \":sushi:\"

" + } + ], + "actions": [ + { + "id": "action_9", + "run_after": "step_1", + "type": "create_group", + "title": [ + { + "type": "assignment", + "output": "New Group Member", + "output_type": "text", + "output_connector": "set" + } + ], + "custom_fields": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "group_custom_field", + "key_type": "text", + "value": "step_3_field_1", + "value_type": "wizard_field", + "connector": "association" + } + ] + } + ], + "name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "full_name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "usernames": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus1" + ] + } + ], + "owner_usernames": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus" + ] + } + ], + "grant_trust_level": [ + { + "type": "assignment", + "output": "3", + "output_type": "text", + "output_connector": "set" + } + ], + "mentionable_level": [ + { + "type": "assignment", + "output": "1", + "output_type": "text", + "output_connector": "set" + } + ], + "messageable_level": [ + { + "type": "assignment", + "output": "2", + "output_type": "text", + "output_connector": "set" + } + ], + "visibility_level": [ + { + "type": "assignment", + "output": "3", + "output_type": "text", + "output_connector": "set" + } + ], + "members_visibility_level": [ + { + "type": "assignment", + "output": "99", + "output_type": "text", + "output_connector": "set" } ] }, { - "id": "about_you", - "title": "Tell us about you", - "raw_description": "We'd like to know a little more about you. Add a your name and your website below. This will update your user profile.", - "description": "

We’d like to know a little more about you. Add a your name and your website below. This will update your user profile.

", - "fields": [ + "id": "action_6", + "run_after": "step_1", + "type": "add_to_group", + "group": [ { - "id": "website", - "label": "Website", - "type": "text" + "type": "assignment", + "output": "action_9", + "output_type": "wizard_action", + "output_connector": "set" } - ], - "actions": [ + ] + }, + { + "id": "action_8", + "run_after": "step_1", + "type": "create_category", + "custom_fields": [ { - "id": "update_profile", - "type": "update_profile", - "profile_updates": [ + "type": "association", + "pairs": [ { - "key": "name", - "value": "name" + "index": 0, + "key": "category_custom_field", + "key_type": "text", + "value": "CC Val", + "value_type": "text", + "connector": "association" } ] } + ], + "name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "slug": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "permissions": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "action_9", + "key_type": "wizard_action", + "value": "2", + "value_type": "text", + "connector": "association" + } + ] + } + ] + }, + { + "id": "action_5", + "run_after": "step_1", + "type": "watch_categories", + "notification_level": "tracking", + "wizard_user": true, + "categories": [ + { + "type": "assignment", + "output": "action_8", + "output_type": "wizard_action", + "output_connector": "set" + } + ], + "mute_remainder": [ + { + "type": "assignment", + "output": "true", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "action_1", + "run_after": "step_2", + "type": "create_topic", + "skip_redirect": true, + "post": "step_1_field_2", + "title": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "category": [ + { + "type": "assignment", + "output": "step_3_field_3", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "tags": [ + { + "type": "assignment", + "output": "step_3_field_2", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "custom_fields": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "custom_field_1", + "key_type": "text", + "value": "title", + "value_type": "user_field", + "connector": "association" + } + ] + } + ], + "visible": [ + { + "type": "conditional", + "output": "true", + "output_type": "text", + "output_connector": "then", + "pairs": [ + { + "index": 0, + "key": "name", + "key_type": "user_field", + "value": "Angus", + "value_type": "text", + "connector": "equal" + } + ] + } + ] + }, + { + "id": "action_4", + "run_after": "step_2", + "type": "update_profile", + "profile_updates": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "profile_background", + "key_type": "user_field", + "value": "step_2_field_7", + "value_type": "wizard_field", + "connector": "association" + } + ] + } + ] + }, + { + "id": "action_2", + "run_after": "step_2", + "type": "send_message", + "post_builder": true, + "post_template": "I will interpolate some wizard fields w{step_1_field_1} w{step_1_field_2}", + "title": [ + { + "type": "assignment", + "output": "Message title", + "output_type": "text", + "output_connector": "set" + } + ], + "recipient": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus1" + ] + } + ] + }, + { + "id": "action_3", + "run_after": "step_2", + "type": "open_composer", + "post_builder": true, + "post_template": "I am interpolating some user fields u{name} u{username} u{email}", + "title": [ + { + "type": "assignment", + "output": "Title of the composer topic", + "output_type": "text", + "output_connector": "set" + } + ], + "category": [ + { + "type": "assignment", + "output": "action_8", + "output_type": "wizard_action", + "output_connector": "set", + "pairs": [ + { + "index": 0, + "key": "step_2_field_5", + "key_type": "wizard_field", + "value": "true", + "value_type": "text", + "connector": "is" + } + ] + } + ], + "tags": [ + { + "type": "assignment", + "output": "tag1", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "action_10", + "run_after": "wizard_completion", + "type": "route_to", + "url": [ + { + "type": "assignment", + "output": "https://google.com", + "output_type": "text", + "output_connector": "set" + } ] } ] diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb deleted file mode 100644 index a470bccd..00000000 --- a/spec/serializers/custom_wizard/wizard_serializer_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe CustomWizardSerializer do - fab!(:user) { Fabricate(:user) } - fab!(:category) { Fabricate(:category) } - - let!(:template) do - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - end - - let(:category_field) {{"id": "category","type": "category","limit": "1","label": "Category"}} - - def build_wizard(t = template, u = user, build_opts = {}, params = {}) - CustomWizard::Wizard.add_wizard(t) - CustomWizard::Builder.new('welcome', u).build(build_opts, params) - end - - it 'should return the wizard attributes' do - json = CustomWizardSerializer.new(build_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:id]).to eq("welcome") - expect(json[:custom_wizard][:name]).to eq("Welcome") - expect(json[:custom_wizard][:background]).to eq("#006da3") - expect(json[:custom_wizard][:required]).to eq(false) - expect(json[:custom_wizard][:min_trust]).to eq(1) - end - - it "should return the wizard user attributes" do - json = CustomWizardSerializer.new(build_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:permitted]).to eq(true) - expect(json[:custom_wizard][:user]).to eq(BasicUserSerializer.new(user, root: false).as_json) - end - - it "should not return category attributes if there are no category fields" do - json = CustomWizardSerializer.new(build_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(false) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(false) - end - - it "should return category attributes if there is a category selector field" do - template['steps'][0]['fields'][0] = category_field - json = CustomWizardSerializer.new(build_wizard(template), scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(true) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(true) - end -end \ No newline at end of file diff --git a/spec/components/custom_wizard/api_spec.rb b/spec_offload/components/custom_wizard/api_spec.rb similarity index 100% rename from spec/components/custom_wizard/api_spec.rb rename to spec_offload/components/custom_wizard/api_spec.rb diff --git a/spec_offload/components/custom_wizard/mapper_spec.rb b/spec_offload/components/custom_wizard/mapper_spec.rb new file mode 100644 index 00000000..b186ddb0 --- /dev/null +++ b/spec_offload/components/custom_wizard/mapper_spec.rb @@ -0,0 +1,13 @@ +describe CustomWizard::Mapper do + fab!(:user) { Fabricate(:user, name: 'Angus', username: 'angus', email: "angus@email.com") } + + it 'interpolates user data' do + expect( + CustomWizard::Mapper.fill_placeholders( + "My name is u{name}", + user, + {} + ) + ).to eq('My name is Angus') + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin_controller_spec.rb b/spec_offload/requests/custom_wizard/admin_controller_spec.rb similarity index 100% rename from spec/requests/custom_wizard/admin_controller_spec.rb rename to spec_offload/requests/custom_wizard/admin_controller_spec.rb diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec_offload/requests/custom_wizard/application_controller_spec.rb similarity index 100% rename from spec/requests/custom_wizard/application_controller_spec.rb rename to spec_offload/requests/custom_wizard/application_controller_spec.rb diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec_offload/requests/custom_wizard/wizard_controller_spec.rb similarity index 100% rename from spec/requests/custom_wizard/wizard_controller_spec.rb rename to spec_offload/requests/custom_wizard/wizard_controller_spec.rb diff --git a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb new file mode 100644 index 00000000..c036e9fa --- /dev/null +++ b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::WizardSerializer do + fab!(:user) { Fabricate(:user) } + fab!(:category) { Fabricate(:category) } + + before do + template = JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + CustomWizard::Wizard.add_wizard(template) + @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) + end + + it 'should return the wizard attributes' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:id]).to eq("super_mega_fun_wizard") + expect(json[:custom_wizard][:name]).to eq("Super Mega Fun Wizard") + expect(json[:custom_wizard][:background]).to eq("#333333") + expect(json[:custom_wizard][:required]).to eq(false) + end + + it "should return the wizard user attributes" do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:user]).to eq(BasicUserSerializer.new(user, root: false).as_json) + end + + it "should not return category attributes if there are no category fields" do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:categories].present?).to eq(false) + expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(false) + end + + it "should return category attributes if there is a category selector field" do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:categories].present?).to eq(true) + expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(true) + end +end \ No newline at end of file