From ae918976a30f8c9b561e216206dd309397c69017 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 5 Apr 2021 21:53:34 +0530 Subject: [PATCH 01/58] DEV: add sprockets directive to require discourse folders --- assets/javascripts/wizard-custom.js | 71 +---------------------------- plugin.rb | 12 +++++ 2 files changed, 14 insertions(+), 69 deletions(-) diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 5d18328f..8b30ad94 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,43 +1,4 @@ -//= require discourse/app/lib/autocomplete -//= require discourse/app/lib/utilities -//= require discourse/app/lib/offset-calculator -//= require discourse/app/lib/lock-on -//= require discourse/app/lib/text-direction -//= require discourse/app/lib/to-markdown -//= require discourse/app/lib/load-script -//= require discourse/app/lib/url -//= require discourse/app/lib/ajax -//= require discourse/app/lib/ajax-error -//= require discourse/app/lib/page-visible -//= require discourse/app/lib/logout -//= require discourse/app/lib/render-tag -//= require discourse/app/lib/notification-levels -//= require discourse/app/lib/computed -//= require discourse/app/lib/user-search -//= require discourse/app/lib/text -//= require discourse/app/lib/formatter -//= require discourse/app/lib/quote -//= require discourse/app/lib/link-mentions -//= require discourse/app/lib/link-hashtags -//= require discourse/app/lib/category-hashtags -//= require discourse/app/lib/tag-hashtags -//= require discourse/app/lib/uploads -//= require discourse/app/lib/category-tag-search -//= require discourse/app/lib/intercept-click -//= require discourse/app/lib/show-modal -//= require discourse/app/lib/key-value-store -//= require discourse/app/lib/settings -//= require discourse/app/lib/user-presence -//= require discourse/app/lib/hash -//= require discourse/app/lib/bookmark -//= require discourse/app/lib/put-cursor-at-end -//= require discourse/app/lib/safari-hacks -//= require discourse/app/lib/preload-store -//= require discourse/app/lib/topic-fancy-title -//= require discourse/app/lib/cookie -//= require discourse/app/lib/public-js-versions -//= require discourse/app/lib/load-oneboxes -//= require discourse/app/lib/highlight-syntax +//= require_tree_discourse discourse/app/lib //= require discourse/app/mixins/singleton //= require discourse/app/mixins/upload @@ -46,35 +7,7 @@ //= require message-bus -//= require discourse/app/models/login-method -//= require discourse/app/models/permission-type -//= require discourse/app/models/archetype -//= require discourse/app/models/rest -//= require discourse/app/models/site -//= require discourse/app/models/category -//= require discourse/app/models/session -//= require discourse/app/models/post-action-type -//= require discourse/app/models/trust-level -//= require discourse/app/models/store -//= require discourse/app/models/result-set -//= require discourse/app/models/bookmark -//= require discourse/app/models/user -//= require discourse/app/models/user-stream -//= require discourse/app/models/user-action -//= require discourse/app/models/user-action-group -//= require discourse/app/models/user-posts-stream -//= require discourse/app/models/badge -//= require discourse/app/models/badge-grouping -//= require discourse/app/models/user-badge -//= require discourse/app/models/topic -//= require discourse/app/models/action-summary -//= require discourse/app/models/user-action-stat -//= require discourse/app/models/user-drafts-stream -//= require discourse/app/models/user-draft -//= require discourse/app/models/composer -//= require discourse/app/models/draft -//= require discourse/app/models/group -//= require discourse/app/models/group-history +//= require_tree_discourse discourse/app/models //= require discourse/app/helpers/category-link //= require discourse/app/helpers/user-avatar diff --git a/plugin.rb b/plugin.rb index 0a272aed..48f0e0ef 100644 --- a/plugin.rb +++ b/plugin.rb @@ -97,6 +97,18 @@ after_initialize do load File.expand_path(path, __FILE__) end + add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive ) do |path = "."| + discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" + path = File.expand_path(path, discourse_asset_path) + stat = @environment.stat(path) + + if stat && stat.directory? + require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) + else + raise ArgumentError, "#{path} not found in discourse core" + end + end + add_class_method(:wizard, :user_requires_completion?) do |user| wizard_result = self.new(user).requires_completion? return wizard_result if wizard_result From 1bc55c609910361fbf2f43da0ef0675912a54b56 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 5 Apr 2021 22:04:17 +0530 Subject: [PATCH 02/58] code formatting --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 48f0e0ef..a726c123 100644 --- a/plugin.rb +++ b/plugin.rb @@ -97,7 +97,7 @@ after_initialize do load File.expand_path(path, __FILE__) end - add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive ) do |path = "."| + add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" path = File.expand_path(path, discourse_asset_path) stat = @environment.stat(path) From 13eabe0695f650c4ea20fac05231434f2ec74392 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 3 May 2021 03:42:26 +0530 Subject: [PATCH 03/58] added specs --- plugin.rb | 2 + spec/extensions/sprockets_directive_spec.rb | 55 +++++++++++++++++++ .../sprockets/require_tree_discourse_empty.js | 1 + .../require_tree_discourse_non_existant.js | 1 + .../sprockets/require_tree_discourse_test.js | 1 + .../sprockets/resolved_js_file_contents.txt | 3 + 6 files changed, 63 insertions(+) create mode 100644 spec/extensions/sprockets_directive_spec.rb create mode 100644 spec/fixtures/sprockets/require_tree_discourse_empty.js create mode 100644 spec/fixtures/sprockets/require_tree_discourse_non_existant.js create mode 100644 spec/fixtures/sprockets/require_tree_discourse_test.js create mode 100644 spec/fixtures/sprockets/resolved_js_file_contents.txt diff --git a/plugin.rb b/plugin.rb index eaa386b9..46c6fabb 100644 --- a/plugin.rb +++ b/plugin.rb @@ -97,6 +97,8 @@ after_initialize do end add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| + raise ArgumentError, "path cannot be empty" if path == "." + discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" path = File.expand_path(path, discourse_asset_path) stat = @environment.stat(path) diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb new file mode 100644 index 00000000..f5e2df9f --- /dev/null +++ b/spec/extensions/sprockets_directive_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require_relative '../plugin_helper' + +describe "Sprockets: require_tree_discourse directive" do + let(:discourse_asset_path) { + "#{Rails.root}/app/assets/javascripts/" + } + let(:fixture_asset_path) { + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" + } + let(:test_file_contents) { + "console.log('hello')" + } + let(:resolved_file_contents) { + File.read( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt" + ) + } + + before do + @env ||= Sprockets::Environment.new + discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" + fixture_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" + @env.append_path(discourse_asset_path) + @env.append_path(fixture_asset_path) + @env.cache = {} + end + + def create_tmp_folder_and_run(path, file_contents, &block) + dir = File.dirname(path) + unless File.directory?(dir) + FileUtils.mkdir_p(dir) + end + + File.new(path, 'w') + File.write(path, file_contents) + yield block if block_given? + File.delete(path) + end + + it "includes assets from the discourse core" do + create_tmp_folder_and_run("#{discourse_asset_path}/sptest/test.js", test_file_contents) do + expect(@env.find_asset("require_tree_discourse_test.js").to_s).to eq(resolved_file_contents) + end + end + + it "throws ArgumentError if path is empty" do + expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(ArgumentError).with_message("path cannot be empty") + end + + it "throws ArgumentError if path is non non-existent" do + expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(ArgumentError) + end +end diff --git a/spec/fixtures/sprockets/require_tree_discourse_empty.js b/spec/fixtures/sprockets/require_tree_discourse_empty.js new file mode 100644 index 00000000..df264ec5 --- /dev/null +++ b/spec/fixtures/sprockets/require_tree_discourse_empty.js @@ -0,0 +1 @@ +//= require_tree_discourse \ No newline at end of file diff --git a/spec/fixtures/sprockets/require_tree_discourse_non_existant.js b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js new file mode 100644 index 00000000..d9b2be76 --- /dev/null +++ b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js @@ -0,0 +1 @@ +//= require_tree_discourse dummy_path \ No newline at end of file diff --git a/spec/fixtures/sprockets/require_tree_discourse_test.js b/spec/fixtures/sprockets/require_tree_discourse_test.js new file mode 100644 index 00000000..a86aa0d7 --- /dev/null +++ b/spec/fixtures/sprockets/require_tree_discourse_test.js @@ -0,0 +1 @@ +//= require_tree_discourse sptest \ No newline at end of file diff --git a/spec/fixtures/sprockets/resolved_js_file_contents.txt b/spec/fixtures/sprockets/resolved_js_file_contents.txt new file mode 100644 index 00000000..53e2cfa2 --- /dev/null +++ b/spec/fixtures/sprockets/resolved_js_file_contents.txt @@ -0,0 +1,3 @@ +eval("define(\"sptest/test\", [], function () {\n \"use strict\";\n\n console.log('hello');\n});" + "\n//# sourceURL=sptest/test"); +; +eval("" + "\n//# sourceURL=require_tree_discourse_test"); From 77dd879f802571b226dc3e19c6eee283ab8804aa Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 3 May 2021 03:43:47 +0530 Subject: [PATCH 04/58] minor formatting --- spec/extensions/sprockets_directive_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb index f5e2df9f..c5eec7a0 100644 --- a/spec/extensions/sprockets_directive_spec.rb +++ b/spec/extensions/sprockets_directive_spec.rb @@ -4,7 +4,7 @@ require_relative '../plugin_helper' describe "Sprockets: require_tree_discourse directive" do let(:discourse_asset_path) { - "#{Rails.root}/app/assets/javascripts/" + "#{Rails.root}/app/assets/javascripts/" } let(:fixture_asset_path) { "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" @@ -13,9 +13,9 @@ describe "Sprockets: require_tree_discourse directive" do "console.log('hello')" } let(:resolved_file_contents) { - File.read( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt" - ) + File.read( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt" + ) } before do From cdb9ec6c3eafa25bb36d8f1a0a70bf97edc14420 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 3 May 2021 04:19:27 +0530 Subject: [PATCH 05/58] remove the directory after spec run --- spec/extensions/sprockets_directive_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb index c5eec7a0..d268fd1d 100644 --- a/spec/extensions/sprockets_directive_spec.rb +++ b/spec/extensions/sprockets_directive_spec.rb @@ -36,7 +36,7 @@ describe "Sprockets: require_tree_discourse directive" do File.new(path, 'w') File.write(path, file_contents) yield block if block_given? - File.delete(path) + FileUtils.rm_r(dir) end it "includes assets from the discourse core" do From 40c0c127197a17a708032b92e8e7c1b60badae69 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 5 May 2021 02:36:01 +0530 Subject: [PATCH 06/58] FIX: set correct options while initializing PrettyText --- assets/javascripts/wizard/lib/text-lite.js.es6 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/lib/text-lite.js.es6 b/assets/javascripts/wizard/lib/text-lite.js.es6 index 4f9064a5..c93f6708 100644 --- a/assets/javascripts/wizard/lib/text-lite.js.es6 +++ b/assets/javascripts/wizard/lib/text-lite.js.es6 @@ -1,8 +1,17 @@ import loadScript from "./load-script"; -import { default as PrettyText } from "pretty-text/pretty-text"; +import { default as PrettyText, buildOptions } from "pretty-text/pretty-text"; import Handlebars from "handlebars"; +import getURL from "discourse-common/lib/get-url"; +import { getOwner } from "discourse-common/lib/get-owner"; export function cook(text, options) { + if (!options) { + options = buildOptions({ + getURL: getURL, + siteSettings: getOwner(this).lookup("site-settings:main"), + }); + } + return new Handlebars.SafeString(new PrettyText(options).cook(text)); } From 957a32cf5aa286d642cb0b64e32109fd4fca2e19 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 5 May 2021 13:13:03 +0530 Subject: [PATCH 07/58] FIX: filter conditionally excluded fields from wizard submission --- lib/custom_wizard/wizard.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index 82693eed..396b8706 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -243,10 +243,16 @@ class CustomWizard::Wizard return nil unless save_submissions submissions.pop(1) if unfinished? + submission = filter_conditional_fields(submission) submissions.push(submission) set_submissions(submissions) end + def filter_conditional_fields(submission) + included_fields = steps.map { |s| s.fields.map { |f| f.id } }.flatten + submission.select { |key, _| included_fields.include?(key) } + end + def final_cleanup! if id == user.custom_fields['redirect_to_wizard'] user.custom_fields.delete('redirect_to_wizard') From 56268823c55e54174f280cc4043eba5859b66293 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Fri, 7 May 2021 03:28:16 +0530 Subject: [PATCH 08/58] confine exclusion to wizard fields --- lib/custom_wizard/wizard.rb | 13 ++++++- .../custom_wizard/steps_controller_spec.rb | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index 396b8706..e8370966 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -250,7 +250,18 @@ class CustomWizard::Wizard def filter_conditional_fields(submission) included_fields = steps.map { |s| s.fields.map { |f| f.id } }.flatten - submission.select { |key, _| included_fields.include?(key) } + submission.select do |key, _| + included_fields.include?(key) || + required_fields.include?(key) || + key.include?("action") + end + end + + def required_fields + %w{ + submitted_at + route_to + } end def final_cleanup! diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index c58f13a2..c312cbb1 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -249,4 +249,41 @@ describe CustomWizard::StepsController do expect(response.status).to eq(200) expect(response.parsed_body['final']).to eq(true) end + + it "excludes the non-included conditional fields from the submissions" do + new_template = wizard_template.dup + new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition will pass" + } + } + + put '/w/super-mega-fun-wizard/steps/step_2.json', params: { + fields: { + step_2_field_1: "1995-04-23" + } + } + + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition will not pass" + } + } + wizard_id = response.parsed_body['wizard']['id'] + wizard = CustomWizard::Wizard.create(wizard_id, user) + submission = wizard.submissions.last + expect(submission.keys).to include("step_2_field_1") + + put '/w/super-mega-fun-wizard/steps/step_2.json', params: { + fields: { + step_2_field_1: "1995-04-23" + } + } + wizard = CustomWizard::Wizard.create(wizard_id, user) + submission = wizard.submissions.last + expect(submission.keys).not_to include("step_2_field_1") + end end From c254a0c6b87df7d9c965cad51d55a2ccb0f43d31 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Fri, 7 May 2021 04:02:56 +0530 Subject: [PATCH 09/58] convert key to string --- lib/custom_wizard/wizard.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index e8370966..ab0f0f63 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -251,6 +251,7 @@ class CustomWizard::Wizard def filter_conditional_fields(submission) included_fields = steps.map { |s| s.fields.map { |f| f.id } }.flatten submission.select do |key, _| + key = key.to_s included_fields.include?(key) || required_fields.include?(key) || key.include?("action") @@ -261,6 +262,7 @@ class CustomWizard::Wizard %w{ submitted_at route_to + saved_param } end From 5f24882ef6ecc188683ce0b8957d543d015ea131 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 8 May 2021 23:15:06 +0530 Subject: [PATCH 10/58] DEV: raise plugin specific errors on failure --- plugin.rb | 5 +++-- spec/extensions/sprockets_directive_spec.rb | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin.rb b/plugin.rb index 46c6fabb..1f917493 100644 --- a/plugin.rb +++ b/plugin.rb @@ -74,6 +74,7 @@ after_initialize do ../lib/custom_wizard/api/endpoint.rb ../lib/custom_wizard/api/log_entry.rb ../lib/custom_wizard/liquid_extensions/first_non_empty.rb + ../lib/custom_wizard/exceptions/exceptions.rb ../serializers/custom_wizard/api/authorization_serializer.rb ../serializers/custom_wizard/api/basic_endpoint_serializer.rb ../serializers/custom_wizard/api/endpoint_serializer.rb @@ -97,7 +98,7 @@ after_initialize do end add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| - raise ArgumentError, "path cannot be empty" if path == "." + raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "." discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" path = File.expand_path(path, discourse_asset_path) @@ -106,7 +107,7 @@ after_initialize do if stat && stat.directory? require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) else - raise ArgumentError, "#{path} not found in discourse core" + raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core" end end diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb index d268fd1d..5a074040 100644 --- a/spec/extensions/sprockets_directive_spec.rb +++ b/spec/extensions/sprockets_directive_spec.rb @@ -46,10 +46,10 @@ describe "Sprockets: require_tree_discourse directive" do end it "throws ArgumentError if path is empty" do - expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(ArgumentError).with_message("path cannot be empty") + expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(CustomWizard::SprocketsEmptyPath).with_message("path cannot be empty") end it "throws ArgumentError if path is non non-existent" do - expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(ArgumentError) + expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(CustomWizard::SprocketsFileNotFound) end end From 3a3d23a317206e9adc56d6b864d50b3f22a53fb7 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 8 May 2021 23:18:58 +0530 Subject: [PATCH 11/58] added missing file --- lib/custom_wizard/exceptions/exceptions.rb | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 lib/custom_wizard/exceptions/exceptions.rb diff --git a/lib/custom_wizard/exceptions/exceptions.rb b/lib/custom_wizard/exceptions/exceptions.rb new file mode 100644 index 00000000..5560efc8 --- /dev/null +++ b/lib/custom_wizard/exceptions/exceptions.rb @@ -0,0 +1,4 @@ +module CustomWizard + class SprocketsFileNotFound < StandardError; end + class SprocketsEmptyPath < StandardError; end +end \ No newline at end of file From 4cacd849677b8fe41d4281c2724bc0e2a3e90207 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 8 May 2021 23:38:26 +0530 Subject: [PATCH 12/58] code formatting --- lib/custom_wizard/exceptions/exceptions.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/custom_wizard/exceptions/exceptions.rb b/lib/custom_wizard/exceptions/exceptions.rb index 5560efc8..b5014d27 100644 --- a/lib/custom_wizard/exceptions/exceptions.rb +++ b/lib/custom_wizard/exceptions/exceptions.rb @@ -1,4 +1,5 @@ +# frozen_string_literal: true module CustomWizard class SprocketsFileNotFound < StandardError; end class SprocketsEmptyPath < StandardError; end -end \ No newline at end of file +end From ca125a2a8a1ddbf368e25fdd2f00a7c6bbf2ea23 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 12 May 2021 02:58:12 +0530 Subject: [PATCH 13/58] Revert "DEV: add new sprockets directive to require discourse folders" (#111) --- assets/javascripts/wizard-custom.js | 71 ++++++++++++++++++- lib/custom_wizard/exceptions/exceptions.rb | 5 -- plugin.rb | 15 ---- spec/extensions/sprockets_directive_spec.rb | 55 -------------- .../sprockets/require_tree_discourse_empty.js | 1 - .../require_tree_discourse_non_existant.js | 1 - .../sprockets/require_tree_discourse_test.js | 1 - .../sprockets/resolved_js_file_contents.txt | 3 - 8 files changed, 69 insertions(+), 83 deletions(-) delete mode 100644 lib/custom_wizard/exceptions/exceptions.rb delete mode 100644 spec/extensions/sprockets_directive_spec.rb delete mode 100644 spec/fixtures/sprockets/require_tree_discourse_empty.js delete mode 100644 spec/fixtures/sprockets/require_tree_discourse_non_existant.js delete mode 100644 spec/fixtures/sprockets/require_tree_discourse_test.js delete mode 100644 spec/fixtures/sprockets/resolved_js_file_contents.txt diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 8b30ad94..5d18328f 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,4 +1,43 @@ -//= require_tree_discourse discourse/app/lib +//= require discourse/app/lib/autocomplete +//= require discourse/app/lib/utilities +//= require discourse/app/lib/offset-calculator +//= require discourse/app/lib/lock-on +//= require discourse/app/lib/text-direction +//= require discourse/app/lib/to-markdown +//= require discourse/app/lib/load-script +//= require discourse/app/lib/url +//= require discourse/app/lib/ajax +//= require discourse/app/lib/ajax-error +//= require discourse/app/lib/page-visible +//= require discourse/app/lib/logout +//= require discourse/app/lib/render-tag +//= require discourse/app/lib/notification-levels +//= require discourse/app/lib/computed +//= require discourse/app/lib/user-search +//= require discourse/app/lib/text +//= require discourse/app/lib/formatter +//= require discourse/app/lib/quote +//= require discourse/app/lib/link-mentions +//= require discourse/app/lib/link-hashtags +//= require discourse/app/lib/category-hashtags +//= require discourse/app/lib/tag-hashtags +//= require discourse/app/lib/uploads +//= require discourse/app/lib/category-tag-search +//= require discourse/app/lib/intercept-click +//= require discourse/app/lib/show-modal +//= require discourse/app/lib/key-value-store +//= require discourse/app/lib/settings +//= require discourse/app/lib/user-presence +//= require discourse/app/lib/hash +//= require discourse/app/lib/bookmark +//= require discourse/app/lib/put-cursor-at-end +//= require discourse/app/lib/safari-hacks +//= require discourse/app/lib/preload-store +//= require discourse/app/lib/topic-fancy-title +//= require discourse/app/lib/cookie +//= require discourse/app/lib/public-js-versions +//= require discourse/app/lib/load-oneboxes +//= require discourse/app/lib/highlight-syntax //= require discourse/app/mixins/singleton //= require discourse/app/mixins/upload @@ -7,7 +46,35 @@ //= require message-bus -//= require_tree_discourse discourse/app/models +//= require discourse/app/models/login-method +//= require discourse/app/models/permission-type +//= require discourse/app/models/archetype +//= require discourse/app/models/rest +//= require discourse/app/models/site +//= require discourse/app/models/category +//= require discourse/app/models/session +//= require discourse/app/models/post-action-type +//= require discourse/app/models/trust-level +//= require discourse/app/models/store +//= require discourse/app/models/result-set +//= require discourse/app/models/bookmark +//= require discourse/app/models/user +//= require discourse/app/models/user-stream +//= require discourse/app/models/user-action +//= require discourse/app/models/user-action-group +//= require discourse/app/models/user-posts-stream +//= require discourse/app/models/badge +//= require discourse/app/models/badge-grouping +//= require discourse/app/models/user-badge +//= require discourse/app/models/topic +//= require discourse/app/models/action-summary +//= require discourse/app/models/user-action-stat +//= require discourse/app/models/user-drafts-stream +//= require discourse/app/models/user-draft +//= require discourse/app/models/composer +//= require discourse/app/models/draft +//= require discourse/app/models/group +//= require discourse/app/models/group-history //= require discourse/app/helpers/category-link //= require discourse/app/helpers/user-avatar diff --git a/lib/custom_wizard/exceptions/exceptions.rb b/lib/custom_wizard/exceptions/exceptions.rb deleted file mode 100644 index b5014d27..00000000 --- a/lib/custom_wizard/exceptions/exceptions.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true -module CustomWizard - class SprocketsFileNotFound < StandardError; end - class SprocketsEmptyPath < StandardError; end -end diff --git a/plugin.rb b/plugin.rb index 1f917493..e3d32129 100644 --- a/plugin.rb +++ b/plugin.rb @@ -74,7 +74,6 @@ after_initialize do ../lib/custom_wizard/api/endpoint.rb ../lib/custom_wizard/api/log_entry.rb ../lib/custom_wizard/liquid_extensions/first_non_empty.rb - ../lib/custom_wizard/exceptions/exceptions.rb ../serializers/custom_wizard/api/authorization_serializer.rb ../serializers/custom_wizard/api/basic_endpoint_serializer.rb ../serializers/custom_wizard/api/endpoint_serializer.rb @@ -97,20 +96,6 @@ after_initialize do load File.expand_path(path, __FILE__) end - add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| - raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "." - - discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" - path = File.expand_path(path, discourse_asset_path) - stat = @environment.stat(path) - - if stat && stat.directory? - require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) - else - raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core" - end - end - Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty) add_class_method(:wizard, :user_requires_completion?) do |user| diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb deleted file mode 100644 index 5a074040..00000000 --- a/spec/extensions/sprockets_directive_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require_relative '../plugin_helper' - -describe "Sprockets: require_tree_discourse directive" do - let(:discourse_asset_path) { - "#{Rails.root}/app/assets/javascripts/" - } - let(:fixture_asset_path) { - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" - } - let(:test_file_contents) { - "console.log('hello')" - } - let(:resolved_file_contents) { - File.read( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt" - ) - } - - before do - @env ||= Sprockets::Environment.new - discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" - fixture_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" - @env.append_path(discourse_asset_path) - @env.append_path(fixture_asset_path) - @env.cache = {} - end - - def create_tmp_folder_and_run(path, file_contents, &block) - dir = File.dirname(path) - unless File.directory?(dir) - FileUtils.mkdir_p(dir) - end - - File.new(path, 'w') - File.write(path, file_contents) - yield block if block_given? - FileUtils.rm_r(dir) - end - - it "includes assets from the discourse core" do - create_tmp_folder_and_run("#{discourse_asset_path}/sptest/test.js", test_file_contents) do - expect(@env.find_asset("require_tree_discourse_test.js").to_s).to eq(resolved_file_contents) - end - end - - it "throws ArgumentError if path is empty" do - expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(CustomWizard::SprocketsEmptyPath).with_message("path cannot be empty") - end - - it "throws ArgumentError if path is non non-existent" do - expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(CustomWizard::SprocketsFileNotFound) - end -end diff --git a/spec/fixtures/sprockets/require_tree_discourse_empty.js b/spec/fixtures/sprockets/require_tree_discourse_empty.js deleted file mode 100644 index df264ec5..00000000 --- a/spec/fixtures/sprockets/require_tree_discourse_empty.js +++ /dev/null @@ -1 +0,0 @@ -//= require_tree_discourse \ No newline at end of file diff --git a/spec/fixtures/sprockets/require_tree_discourse_non_existant.js b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js deleted file mode 100644 index d9b2be76..00000000 --- a/spec/fixtures/sprockets/require_tree_discourse_non_existant.js +++ /dev/null @@ -1 +0,0 @@ -//= require_tree_discourse dummy_path \ No newline at end of file diff --git a/spec/fixtures/sprockets/require_tree_discourse_test.js b/spec/fixtures/sprockets/require_tree_discourse_test.js deleted file mode 100644 index a86aa0d7..00000000 --- a/spec/fixtures/sprockets/require_tree_discourse_test.js +++ /dev/null @@ -1 +0,0 @@ -//= require_tree_discourse sptest \ No newline at end of file diff --git a/spec/fixtures/sprockets/resolved_js_file_contents.txt b/spec/fixtures/sprockets/resolved_js_file_contents.txt deleted file mode 100644 index 53e2cfa2..00000000 --- a/spec/fixtures/sprockets/resolved_js_file_contents.txt +++ /dev/null @@ -1,3 +0,0 @@ -eval("define(\"sptest/test\", [], function () {\n \"use strict\";\n\n console.log('hello');\n});" + "\n//# sourceURL=sptest/test"); -; -eval("" + "\n//# sourceURL=require_tree_discourse_test"); From a4adeecf5feb4b5842c0b275a58c36840acd704f Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 12 May 2021 06:14:09 +0530 Subject: [PATCH 14/58] Revert "Revert "DEV: add new sprockets directive to require discourse folders" (#111)" This reverts commit ca125a2a8a1ddbf368e25fdd2f00a7c6bbf2ea23. --- assets/javascripts/wizard-custom.js | 71 +------------------ lib/custom_wizard/exceptions/exceptions.rb | 5 ++ plugin.rb | 15 ++++ spec/extensions/sprockets_directive_spec.rb | 55 ++++++++++++++ .../sprockets/require_tree_discourse_empty.js | 1 + .../require_tree_discourse_non_existant.js | 1 + .../sprockets/require_tree_discourse_test.js | 1 + .../sprockets/resolved_js_file_contents.txt | 3 + 8 files changed, 83 insertions(+), 69 deletions(-) create mode 100644 lib/custom_wizard/exceptions/exceptions.rb create mode 100644 spec/extensions/sprockets_directive_spec.rb create mode 100644 spec/fixtures/sprockets/require_tree_discourse_empty.js create mode 100644 spec/fixtures/sprockets/require_tree_discourse_non_existant.js create mode 100644 spec/fixtures/sprockets/require_tree_discourse_test.js create mode 100644 spec/fixtures/sprockets/resolved_js_file_contents.txt diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 5d18328f..8b30ad94 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,43 +1,4 @@ -//= require discourse/app/lib/autocomplete -//= require discourse/app/lib/utilities -//= require discourse/app/lib/offset-calculator -//= require discourse/app/lib/lock-on -//= require discourse/app/lib/text-direction -//= require discourse/app/lib/to-markdown -//= require discourse/app/lib/load-script -//= require discourse/app/lib/url -//= require discourse/app/lib/ajax -//= require discourse/app/lib/ajax-error -//= require discourse/app/lib/page-visible -//= require discourse/app/lib/logout -//= require discourse/app/lib/render-tag -//= require discourse/app/lib/notification-levels -//= require discourse/app/lib/computed -//= require discourse/app/lib/user-search -//= require discourse/app/lib/text -//= require discourse/app/lib/formatter -//= require discourse/app/lib/quote -//= require discourse/app/lib/link-mentions -//= require discourse/app/lib/link-hashtags -//= require discourse/app/lib/category-hashtags -//= require discourse/app/lib/tag-hashtags -//= require discourse/app/lib/uploads -//= require discourse/app/lib/category-tag-search -//= require discourse/app/lib/intercept-click -//= require discourse/app/lib/show-modal -//= require discourse/app/lib/key-value-store -//= require discourse/app/lib/settings -//= require discourse/app/lib/user-presence -//= require discourse/app/lib/hash -//= require discourse/app/lib/bookmark -//= require discourse/app/lib/put-cursor-at-end -//= require discourse/app/lib/safari-hacks -//= require discourse/app/lib/preload-store -//= require discourse/app/lib/topic-fancy-title -//= require discourse/app/lib/cookie -//= require discourse/app/lib/public-js-versions -//= require discourse/app/lib/load-oneboxes -//= require discourse/app/lib/highlight-syntax +//= require_tree_discourse discourse/app/lib //= require discourse/app/mixins/singleton //= require discourse/app/mixins/upload @@ -46,35 +7,7 @@ //= require message-bus -//= require discourse/app/models/login-method -//= require discourse/app/models/permission-type -//= require discourse/app/models/archetype -//= require discourse/app/models/rest -//= require discourse/app/models/site -//= require discourse/app/models/category -//= require discourse/app/models/session -//= require discourse/app/models/post-action-type -//= require discourse/app/models/trust-level -//= require discourse/app/models/store -//= require discourse/app/models/result-set -//= require discourse/app/models/bookmark -//= require discourse/app/models/user -//= require discourse/app/models/user-stream -//= require discourse/app/models/user-action -//= require discourse/app/models/user-action-group -//= require discourse/app/models/user-posts-stream -//= require discourse/app/models/badge -//= require discourse/app/models/badge-grouping -//= require discourse/app/models/user-badge -//= require discourse/app/models/topic -//= require discourse/app/models/action-summary -//= require discourse/app/models/user-action-stat -//= require discourse/app/models/user-drafts-stream -//= require discourse/app/models/user-draft -//= require discourse/app/models/composer -//= require discourse/app/models/draft -//= require discourse/app/models/group -//= require discourse/app/models/group-history +//= require_tree_discourse discourse/app/models //= require discourse/app/helpers/category-link //= require discourse/app/helpers/user-avatar diff --git a/lib/custom_wizard/exceptions/exceptions.rb b/lib/custom_wizard/exceptions/exceptions.rb new file mode 100644 index 00000000..b5014d27 --- /dev/null +++ b/lib/custom_wizard/exceptions/exceptions.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +module CustomWizard + class SprocketsFileNotFound < StandardError; end + class SprocketsEmptyPath < StandardError; end +end diff --git a/plugin.rb b/plugin.rb index e3d32129..1f917493 100644 --- a/plugin.rb +++ b/plugin.rb @@ -74,6 +74,7 @@ after_initialize do ../lib/custom_wizard/api/endpoint.rb ../lib/custom_wizard/api/log_entry.rb ../lib/custom_wizard/liquid_extensions/first_non_empty.rb + ../lib/custom_wizard/exceptions/exceptions.rb ../serializers/custom_wizard/api/authorization_serializer.rb ../serializers/custom_wizard/api/basic_endpoint_serializer.rb ../serializers/custom_wizard/api/endpoint_serializer.rb @@ -96,6 +97,20 @@ after_initialize do load File.expand_path(path, __FILE__) end + add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| + raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "." + + discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" + path = File.expand_path(path, discourse_asset_path) + stat = @environment.stat(path) + + if stat && stat.directory? + require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) + else + raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core" + end + end + Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty) add_class_method(:wizard, :user_requires_completion?) do |user| diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb new file mode 100644 index 00000000..5a074040 --- /dev/null +++ b/spec/extensions/sprockets_directive_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require_relative '../plugin_helper' + +describe "Sprockets: require_tree_discourse directive" do + let(:discourse_asset_path) { + "#{Rails.root}/app/assets/javascripts/" + } + let(:fixture_asset_path) { + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" + } + let(:test_file_contents) { + "console.log('hello')" + } + let(:resolved_file_contents) { + File.read( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt" + ) + } + + before do + @env ||= Sprockets::Environment.new + discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" + fixture_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" + @env.append_path(discourse_asset_path) + @env.append_path(fixture_asset_path) + @env.cache = {} + end + + def create_tmp_folder_and_run(path, file_contents, &block) + dir = File.dirname(path) + unless File.directory?(dir) + FileUtils.mkdir_p(dir) + end + + File.new(path, 'w') + File.write(path, file_contents) + yield block if block_given? + FileUtils.rm_r(dir) + end + + it "includes assets from the discourse core" do + create_tmp_folder_and_run("#{discourse_asset_path}/sptest/test.js", test_file_contents) do + expect(@env.find_asset("require_tree_discourse_test.js").to_s).to eq(resolved_file_contents) + end + end + + it "throws ArgumentError if path is empty" do + expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(CustomWizard::SprocketsEmptyPath).with_message("path cannot be empty") + end + + it "throws ArgumentError if path is non non-existent" do + expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(CustomWizard::SprocketsFileNotFound) + end +end diff --git a/spec/fixtures/sprockets/require_tree_discourse_empty.js b/spec/fixtures/sprockets/require_tree_discourse_empty.js new file mode 100644 index 00000000..df264ec5 --- /dev/null +++ b/spec/fixtures/sprockets/require_tree_discourse_empty.js @@ -0,0 +1 @@ +//= require_tree_discourse \ No newline at end of file diff --git a/spec/fixtures/sprockets/require_tree_discourse_non_existant.js b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js new file mode 100644 index 00000000..d9b2be76 --- /dev/null +++ b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js @@ -0,0 +1 @@ +//= require_tree_discourse dummy_path \ No newline at end of file diff --git a/spec/fixtures/sprockets/require_tree_discourse_test.js b/spec/fixtures/sprockets/require_tree_discourse_test.js new file mode 100644 index 00000000..a86aa0d7 --- /dev/null +++ b/spec/fixtures/sprockets/require_tree_discourse_test.js @@ -0,0 +1 @@ +//= require_tree_discourse sptest \ No newline at end of file diff --git a/spec/fixtures/sprockets/resolved_js_file_contents.txt b/spec/fixtures/sprockets/resolved_js_file_contents.txt new file mode 100644 index 00000000..53e2cfa2 --- /dev/null +++ b/spec/fixtures/sprockets/resolved_js_file_contents.txt @@ -0,0 +1,3 @@ +eval("define(\"sptest/test\", [], function () {\n \"use strict\";\n\n console.log('hello');\n});" + "\n//# sourceURL=sptest/test"); +; +eval("" + "\n//# sourceURL=require_tree_discourse_test"); From eb5e2fe60431e123abb671bc419f1561d971c188 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 12 May 2021 06:17:01 +0530 Subject: [PATCH 15/58] move sprockets logic outside after_initialize --- plugin.rb | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/plugin.rb b/plugin.rb index 1f917493..253ac110 100644 --- a/plugin.rb +++ b/plugin.rb @@ -35,6 +35,21 @@ if respond_to?(:register_svg_icon) register_svg_icon "save" end + +add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| + raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "." + + discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" + path = File.expand_path(path, discourse_asset_path) + stat = @environment.stat(path) + + if stat && stat.directory? + require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) + else + raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core" + end +end + after_initialize do %w[ ../lib/custom_wizard/engine.rb @@ -97,20 +112,6 @@ after_initialize do load File.expand_path(path, __FILE__) end - add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| - raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "." - - discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" - path = File.expand_path(path, discourse_asset_path) - stat = @environment.stat(path) - - if stat && stat.directory? - require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) - else - raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core" - end - end - Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty) add_class_method(:wizard, :user_requires_completion?) do |user| From a1e80700625f230478c3351031864ae9f7c7ec71 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 12 May 2021 06:33:19 +0530 Subject: [PATCH 16/58] FIX: move sprockets customization to monkeypatch --- plugin.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/plugin.rb b/plugin.rb index 253ac110..6f5f203a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -35,18 +35,19 @@ if respond_to?(:register_svg_icon) register_svg_icon "save" end +class ::Sprockets::DirectiveProcessor + def process_require_tree_discourse_directive(path = ".") + raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "." -add_to_class(::Sprockets::DirectiveProcessor, :process_require_tree_discourse_directive) do |path = "."| - raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "." + discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" + path = File.expand_path(path, discourse_asset_path) + stat = @environment.stat(path) - discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" - path = File.expand_path(path, discourse_asset_path) - stat = @environment.stat(path) - - if stat && stat.directory? - require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) - else - raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core" + if stat && stat.directory? + require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) + else + raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core" + end end end From ff14570d64bc9d8bb1d4f7b96703d923e2ae4fca Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 12 May 2021 18:15:55 +0530 Subject: [PATCH 17/58] FIX: exclude conditional fields while saving step submission --- controllers/custom_wizard/steps.rb | 2 +- lib/custom_wizard/wizard.rb | 7 ++++--- spec/requests/custom_wizard/steps_controller_spec.rb | 10 +--------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index 277b94b2..5465d0dd 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -28,7 +28,7 @@ class CustomWizard::StepsController < ::ApplicationController current_step = @wizard.find_step(update[:step_id]) current_submission = @wizard.current_submission result = {} - + @wizard.filter_conditional_fields if current_step.conditional_final_step && !current_step.last_step current_step.force_final = true end diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index ab0f0f63..f92f3d61 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -243,19 +243,20 @@ class CustomWizard::Wizard return nil unless save_submissions submissions.pop(1) if unfinished? - submission = filter_conditional_fields(submission) submissions.push(submission) set_submissions(submissions) end - def filter_conditional_fields(submission) + def filter_conditional_fields included_fields = steps.map { |s| s.fields.map { |f| f.id } }.flatten - submission.select do |key, _| + filtered_submision = current_submission&.select do |key, _| key = key.to_s included_fields.include?(key) || required_fields.include?(key) || key.include?("action") end + + save_submission(filtered_submision) end def required_fields diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index c312cbb1..2424274b 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -272,16 +272,8 @@ describe CustomWizard::StepsController do step_1_field_1: "Condition will not pass" } } - wizard_id = response.parsed_body['wizard']['id'] - wizard = CustomWizard::Wizard.create(wizard_id, user) - submission = wizard.submissions.last - expect(submission.keys).to include("step_2_field_1") - put '/w/super-mega-fun-wizard/steps/step_2.json', params: { - fields: { - step_2_field_1: "1995-04-23" - } - } + wizard_id = response.parsed_body['wizard']['id'] wizard = CustomWizard::Wizard.create(wizard_id, user) submission = wizard.submissions.last expect(submission.keys).not_to include("step_2_field_1") From 0c4eb02038afc11907927fb81f0e80f97e41b6b8 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 17 May 2021 19:57:09 +1200 Subject: [PATCH 18/58] Exclude gems from simplecov --- spec/plugin_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 6680874f..93f33a81 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -6,7 +6,7 @@ if ENV['SIMPLECOV'] SimpleCov.start do root "plugins/discourse-custom-wizard" track_files "plugins/discourse-custom-wizard/**/*.rb" - add_filter { |src| src.filename =~ /(\/spec\/|\/db\/|plugin\.rb|api)/ } + add_filter { |src| src.filename =~ /(\/spec\/|\/db\/|plugin\.rb|api|gems)/ } SimpleCov.minimum_coverage 80 end end From c574ad7c32040e8fd8e1212202723f1a47c985a1 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 17 May 2021 19:57:13 +1200 Subject: [PATCH 19/58] Update package.json --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c6692218..f06823c8 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,9 @@ "name": "discourse-custom-wizard", "version": "1.0.0", "repository": "git@github.com:paviliondev/discourse-custom-wizard.git", - "author": "Discourse", - "license": "MIT", + "author": "Pavilion", + "license": "GPL V2", "devDependencies": { "eslint-config-discourse": "^1.1.8" } -} +} \ No newline at end of file From 04f008575fea2c63faae6640c8982fe9b7c4f9d0 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 17 May 2021 14:00:23 +0530 Subject: [PATCH 20/58] combine rspec run with coverage report (#101) --- .github/workflows/plugin-tests.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index ce6112af..ce228136 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -123,15 +123,11 @@ jobs: bin/rake db:create bin/rake db:migrate - - name: Plugin RSpec + - name: Plugin RSpec with Coverage if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' - run: bin/rake plugin:spec[${{ github.event.repository.name }}] + run: SIMPLECOV=1 bin/rake plugin:spec[${{ github.event.repository.name }}] - name: Plugin QUnit if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' run: bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000'] timeout-minutes: 30 - - - name: Simplecov Report - if: matrix.build_type == 'backend' - run: COVERAGE=1 bin/rake plugin:spec[${{ github.event.repository.name }}] From db72617b619dc13ef03ea16c1e95ccdfb9f9698e Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Tue, 18 May 2021 16:10:43 +0530 Subject: [PATCH 21/58] FIX: changed wizard id resolution logic --- extensions/extra_locales_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/extra_locales_controller.rb b/extensions/extra_locales_controller.rb index e7c5a02e..6242f7ca 100644 --- a/extensions/extra_locales_controller.rb +++ b/extensions/extra_locales_controller.rb @@ -4,7 +4,8 @@ module ExtraLocalesControllerCustomWizard super || begin return false unless bundle =~ /wizard/ && request.referer =~ /\/w\// path = URI(request.referer).path - wizard_id = path.split('/w/').last + wizard_path = path.split('/w/').last + wizard_id = wizard_path.split('/').first CustomWizard::Template.exists?(wizard_id.underscore) end end From 61ff01824400188ddf281f2bf0faaf31330c4046 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sun, 30 May 2021 18:30:51 +0530 Subject: [PATCH 22/58] added spec --- spec/extensions/extra_locales_controller_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/extensions/extra_locales_controller_spec.rb b/spec/extensions/extra_locales_controller_spec.rb index 91a4e8c3..a71e39c4 100644 --- a/spec/extensions/extra_locales_controller_spec.rb +++ b/spec/extensions/extra_locales_controller_spec.rb @@ -37,6 +37,13 @@ describe ExtraLocalesControllerCustomWizard, type: :request do expect(response.status).to eq(200) end + it "returns wizard locales when requested by user in a wizard step" do + sign_in(new_user) + + get @locale_url, headers: { 'REFERER' => "/w/super-mega-fun-wizard/steps/step_1" } + expect(response.status).to eq(200) + end + it "return wizard locales if user cant access wizard" do template[:permitted] = permitted["permitted"] CustomWizard::Template.save(template.as_json) From 522d4e94893de1c9a233242573f64f16aed8e7bb Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 7 Jun 2021 23:15:41 +1000 Subject: [PATCH 23/58] IMPROVE: ensure redirect_to_wizard is cleaned up even if sidekiq is not working (#116) * Add test of redirect_to_wizard when wizard is removed * Make clear_user_wizard_redirect a synchronous operation --- jobs/clear_after_time_wizard.rb | 15 ------- lib/custom_wizard/template.rb | 15 +++---- plugin.rb | 1 - .../components/custom_wizard/template_spec.rb | 8 ++++ spec/jobs/clear_after_time_wizard_spec.rb | 41 ------------------- .../application_controller_spec.rb | 6 +++ 6 files changed, 22 insertions(+), 64 deletions(-) delete mode 100644 jobs/clear_after_time_wizard.rb delete mode 100644 spec/jobs/clear_after_time_wizard_spec.rb diff --git a/jobs/clear_after_time_wizard.rb b/jobs/clear_after_time_wizard.rb deleted file mode 100644 index 37d997db..00000000 --- a/jobs/clear_after_time_wizard.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true -module Jobs - class ClearAfterTimeWizard < ::Jobs::Base - sidekiq_options queue: 'critical' - - def execute(args) - User.human_users.each do |u| - if u.custom_fields['redirect_to_wizard'] == args[:wizard_id] - u.custom_fields.delete('redirect_to_wizard') - u.save_custom_fields(true) - end - end - end - end -end diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb index a1c0aad0..8e944dca 100644 --- a/lib/custom_wizard/template.rb +++ b/lib/custom_wizard/template.rb @@ -49,18 +49,15 @@ class CustomWizard::Template def self.remove(wizard_id) wizard = CustomWizard::Wizard.create(wizard_id) - return false if !wizard ActiveRecord::Base.transaction do PluginStore.remove(CustomWizard::PLUGIN_NAME, wizard.id) - - if wizard.after_time - Jobs.cancel_scheduled_job(:set_after_time_wizard) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id) - end + clear_user_wizard_redirect(wizard_id) end + Jobs.cancel_scheduled_job(:set_after_time_wizard) if wizard.after_time + true end @@ -88,6 +85,10 @@ class CustomWizard::Template end end + def self.clear_user_wizard_redirect(wizard_id) + UserCustomField.where(name: 'redirect_to_wizard', value: wizard_id).destroy_all + end + private def normalize_data @@ -132,7 +133,7 @@ class CustomWizard::Template Jobs.enqueue_at(enqueue_wizard_at, :set_after_time_wizard, wizard_id: wizard_id) elsif old_data && old_data[:after_time] Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id) + self.class.clear_user_wizard_redirect(wizard_id) end end end diff --git a/plugin.rb b/plugin.rb index e3d32129..0beab1cc 100644 --- a/plugin.rb +++ b/plugin.rb @@ -49,7 +49,6 @@ after_initialize do ../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/steps.rb ../controllers/custom_wizard/realtime_validations.rb - ../jobs/clear_after_time_wizard.rb ../jobs/refresh_api_access_token.rb ../jobs/set_after_time_wizard.rb ../lib/custom_wizard/validators/template.rb diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb index fb76e0c4..0e3dbdbe 100644 --- a/spec/components/custom_wizard/template_spec.rb +++ b/spec/components/custom_wizard/template_spec.rb @@ -41,6 +41,14 @@ describe CustomWizard::Template do ).to eq(nil) end + it "removes user wizard redirects if template is removed" do + user.custom_fields['redirect_to_wizard'] = 'super_mega_fun_wizard' + user.save_custom_fields(true) + + CustomWizard::Template.remove('super_mega_fun_wizard') + expect(user.reload.custom_fields['redirect_to_wizard']).to eq(nil) + end + it "checks for wizard template existence" do expect( CustomWizard::Template.exists?('super_mega_fun_wizard') diff --git a/spec/jobs/clear_after_time_wizard_spec.rb b/spec/jobs/clear_after_time_wizard_spec.rb deleted file mode 100644 index 935036a3..00000000 --- a/spec/jobs/clear_after_time_wizard_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require_relative '../plugin_helper' - -describe Jobs::ClearAfterTimeWizard do - fab!(:user1) { Fabricate(:user) } - fab!(:user2) { Fabricate(:user) } - fab!(:user3) { Fabricate(:user) } - - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read).with_indifferent_access - } - - it "clears wizard redirect for all users " do - after_time_template = template.dup - after_time_template["after_time"] = true - after_time_template["after_time_scheduled"] = (Time.now + 3.hours).iso8601 - - CustomWizard::Template.save(after_time_template) - - Jobs::SetAfterTimeWizard.new.execute(wizard_id: 'super_mega_fun_wizard') - - expect( - UserCustomField.where( - name: 'redirect_to_wizard', - value: 'super_mega_fun_wizard' - ).length - ).to eq(3) - - described_class.new.execute(wizard_id: 'super_mega_fun_wizard') - - expect( - UserCustomField.where(" - name = 'redirect_to_wizard' AND - value = 'super_mega_fun_wizard' - ").exists? - ).to eq(false) - end -end diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb index f79db877..e8b45c48 100644 --- a/spec/requests/custom_wizard/application_controller_spec.rb +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -43,6 +43,12 @@ describe ApplicationController do .first['redirect_to'] ).to eq("/t/2") end + + it "does not redirect if wizard does not exist" do + CustomWizard::Template.remove('super_mega_fun_wizard') + get "/" + expect(response.status).to eq(200) + end end context "who is not required to complete wizard" do From 48fbb61dcc225dff1a0605af501f15cb8b5740ab Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Tue, 8 Jun 2021 16:10:00 +0530 Subject: [PATCH 24/58] FIX: populate repository name from global object --- .github/workflows/plugin-linting.yml | 2 ++ .github/workflows/plugin-tests.yml | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml index a121658d..a79c5462 100644 --- a/.github/workflows/plugin-linting.yml +++ b/.github/workflows/plugin-linting.yml @@ -6,6 +6,8 @@ on: - master - main pull_request: + schedule: + - cron: '0 0 * * *' jobs: build: diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index ce228136..84e055ca 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -6,6 +6,8 @@ on: - master - main pull_request: + schedule: + - cron: '0 0 * * *' jobs: build: @@ -51,23 +53,26 @@ jobs: repository: discourse/discourse fetch-depth: 1 + - run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV + shell: bash + - name: Install plugin uses: actions/checkout@v2 with: - path: plugins/${{ github.event.repository.name }} + path: plugins/${{ env.REPOSITORY_NAME }} fetch-depth: 1 - name: Check spec existence id: check_spec uses: andstor/file-existence-action@v1 with: - files: "plugins/${{ github.event.repository.name }}/spec" + files: "plugins/${{ env.REPOSITORY_NAME }}/spec" - name: Check qunit existence id: check_qunit uses: andstor/file-existence-action@v1 with: - files: "plugins/${{ github.event.repository.name }}/test/javascripts" + files: "plugins/${{ env.REPOSITORY_NAME }}/test/javascripts" - name: Setup Git run: | @@ -100,7 +105,7 @@ jobs: - name: Lint English locale if: matrix.build_type == 'backend' - run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml" + run: bundle exec ruby script/i18n_lint.rb "plugins/${{ env.REPOSITORY_NAME }}/locales/{client,server}.en.yml" - name: Get yarn cache directory id: yarn-cache-dir @@ -125,9 +130,9 @@ jobs: - name: Plugin RSpec with Coverage if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' - run: SIMPLECOV=1 bin/rake plugin:spec[${{ github.event.repository.name }}] + run: SIMPLECOV=1 bin/rake plugin:spec[${{ env.REPOSITORY_NAME }}] - name: Plugin QUnit if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' - run: bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000'] + run: bundle exec rake plugin:qunit['${{ env.REPOSITORY_NAME }}','1200000'] timeout-minutes: 30 From af3e61fe75916439d5530c2592de24a5b294d81f Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 8 Jun 2021 21:39:49 +1000 Subject: [PATCH 25/58] Add custom field improvements (#115) * Add custom field improvements This PR does a few things to improve our support of custom fields 1. Adds custom fields added by other plugins to the list in admin/wizards/custom-fields and the custom field list in the mapper selector 2. Adds support for json custom fields in the wizard actions * Make eslint happy * Make prettier happy * Make rubocop happy * Make ember template lint happy * Don't assume we have the context in the selector * Ensure custom fields don't require optional attributes (with tests) --- .../components/custom-field-input.js.es6 | 8 +-- .../components/wizard-custom-action.js.es6 | 5 ++ .../components/wizard-mapper-selector.js.es6 | 41 +++++++++++++-- .../admin-wizards-custom-fields.js.es6 | 4 +- .../javascripts/discourse/lib/wizard.js.es6 | 1 + .../routes/admin-wizards-wizard-show.js.es6 | 5 +- .../components/custom-field-input.hbs | 36 ++++++++----- .../components/wizard-custom-action.hbs | 2 +- assets/stylesheets/common/wizard-admin.scss | 4 ++ config/locales/client.en.yml | 5 +- controllers/custom_wizard/admin/admin.rb | 2 +- extensions/custom_field/extension.rb | 6 +++ lib/custom_wizard/action.rb | 41 +++++++++++---- lib/custom_wizard/custom_field.rb | 40 +++++++++++++-- plugin.rb | 11 ++-- spec/components/custom_wizard/action_spec.rb | 36 +++++++++++++ .../custom_wizard/custom_field_spec.rb | 50 +++++++++++++++++++ spec/fixtures/wizard.json | 30 +++++++++-- .../admin/custom_fields_controller_spec.rb | 4 +- 19 files changed, 276 insertions(+), 55 deletions(-) create mode 100644 extensions/custom_field/extension.rb diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index f2dca4c7..e49c6f1d 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -1,6 +1,6 @@ import Component from "@ember/component"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; -import { alias, or } from "@ember/object/computed"; +import { alias, equal, or } from "@ember/object/computed"; import I18n from "I18n"; const generateContent = function (array, type) { @@ -29,6 +29,7 @@ export default Component.extend({ loading: or("saving", "destroying"), destroyDisabled: alias("loading"), closeDisabled: alias("loading"), + isExternal: equal("field.id", "external"), didInsertElement() { this.set("originalField", JSON.parse(JSON.stringify(this.field))); @@ -61,13 +62,14 @@ export default Component.extend({ @discourseComputed( "saving", + "isExternal", "field.name", "field.klass", "field.type", "field.serializers" ) - saveDisabled(saving) { - if (saving) { + saveDisabled(saving, isExternal) { + if (saving || isExternal) { return true; } diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index c8309f10..feb83754 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -62,6 +62,11 @@ export default Component.extend(UndoChanges, { return key; }, + @discourseComputed("action.type") + customFieldsContext(type) { + return `action.${type}`; + }, + @discourseComputed("wizard.steps") runAfterContent(steps) { let content = steps.map(function (step) { diff --git a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 index 6d65d782..7d9b0bbd 100644 --- a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 @@ -6,11 +6,24 @@ import { } from "discourse-common/utils/decorators"; import { getOwner } from "discourse-common/lib/get-owner"; import { defaultSelectionType, selectionTypes } from "../lib/wizard-mapper"; -import { generateName, snakeCase, userProperties } from "../lib/wizard"; +import { + generateName, + sentenceCase, + snakeCase, + userProperties, +} from "../lib/wizard"; import Component from "@ember/component"; import { bind, later } from "@ember/runloop"; import I18n from "I18n"; +const customFieldActionMap = { + topic: ["create_topic", "send_message"], + post: ["create_topic", "send_message"], + category: ["create_category"], + group: ["create_group"], + user: ["update_profile"], +}; + export default Component.extend({ classNameBindings: [":mapper-selector", "activeType"], @@ -188,11 +201,19 @@ export default Component.extend({ customFields ) { let content; + let context; + let contextType; + + if (this.options.context) { + let contextAttrs = this.options.context.split("."); + context = contextAttrs[0]; + contextType = contextAttrs[1]; + } if (activeType === "wizardField") { content = wizardFields; - if (this.options.context === "field") { + if (context === "field") { content = content.filter((field) => field.id !== currentFieldId); } } @@ -204,7 +225,7 @@ export default Component.extend({ type: a.type, })); - if (this.options.context === "action") { + if (context === "action") { content = content.filter((a) => a.id !== currentActionId); } } @@ -218,7 +239,7 @@ export default Component.extend({ .concat(userFields || []); if ( - this.options.context === "action" && + context === "action" && this.inputType === "association" && this.selectorType === "key" ) { @@ -234,7 +255,17 @@ export default Component.extend({ } if (activeType === "customField") { - content = customFields; + content = customFields + .filter((f) => { + return ( + f.type !== "json" && + customFieldActionMap[f.klass].includes(contextType) + ); + }) + .map((f) => ({ + id: f.name, + name: `${sentenceCase(f.klass)} ${f.name} (${f.type})`, + })); } return content; diff --git a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 index 2081cfe3..404c6afd 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 @@ -3,12 +3,12 @@ import CustomWizardCustomField from "../models/custom-wizard-custom-field"; export default Controller.extend({ messageKey: "create", - fieldKeys: ["klass", "type", "serializers", "name"], + fieldKeys: ["klass", "type", "name", "serializers"], documentationUrl: "https://thepavilion.io/t/3572", actions: { addField() { - this.get("customFields").pushObject( + this.get("customFields").unshiftObject( CustomWizardCustomField.create({ edit: true }) ); }, diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6 index 1896b1fe..98bdbfdd 100644 --- a/assets/javascripts/discourse/lib/wizard.js.es6 +++ b/assets/javascripts/discourse/lib/wizard.js.es6 @@ -120,4 +120,5 @@ export { listProperties, notificationLevels, wizardFieldList, + sentenceCase, }; diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index eaa6591c..cb2d54c3 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -2,7 +2,6 @@ import CustomWizard from "../models/custom-wizard"; import { ajax } from "discourse/lib/ajax"; import DiscourseRoute from "discourse/routes/discourse"; import I18n from "I18n"; -import { selectKitContent } from "../lib/wizard"; export default DiscourseRoute.extend({ model(params) { @@ -33,9 +32,7 @@ export default DiscourseRoute.extend({ wizardList: parentModel.wizard_list, fieldTypes, userFields: parentModel.userFields, - customFields: selectKitContent( - parentModel.custom_fields.map((f) => f.name) - ), + customFields: parentModel.custom_fields, apis: parentModel.apis, themes: parentModel.themes, wizard, diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index 205b1644..43a97be8 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -13,6 +13,11 @@ none="admin.wizard.custom_field.type.select" onChange=(action (mut field.type))}} + + {{input + value=field.name + placeholder=(i18n "admin.wizard.custom_field.name.select")}} + {{multi-select value=field.serializers @@ -20,11 +25,6 @@ none="admin.wizard.custom_field.serializers.select" onChange=(action (mut field.serializers))}} - - {{input - value=field.name - placeholder=(i18n "admin.wizard.custom_field.name.select")}} - {{#if loading}} {{loading-spinner size="small"}} @@ -51,13 +51,25 @@ {{else}} - - {{#each field.serializers as |serializer|}} - - {{/each}} - - - {{d-button action="edit" icon="pencil-alt"}} + + {{#if isExternal}} + — + {{else}} + {{#each field.serializers as |serializer|}} + + {{/each}} + {{/if}} + {{#if isExternal}} + + + + {{else}} + + {{d-button action="edit" icon="pencil-alt"}} + + {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index f06e0d89..4c645cf7 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -738,7 +738,7 @@ wizardActionSelection="value" userFieldSelection="value" keyPlaceholder="admin.wizard.action.custom_fields.key" - context="action" + context=customFieldsContext )}} diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 3c4b78da..9c2838eb 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -667,6 +667,10 @@ margin-left: 5px !important; } } + + td.external { + font-style: italic; + } } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0c364853..43b86698 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -75,7 +75,7 @@ en: edit: "You're editing an action" documentation: "Check out the action documentation" custom_fields: - create: "Create, edit or destroy a custom field record" + create: "View, create, edit and destroy custom fields" saved: "Saved custom field" error: "Failed to save: {{messages}}" documentation: Check out the custom field documentation @@ -322,6 +322,9 @@ en: custom_field: nav_label: "Custom Fields" add: "Add" + external: + label: "from another plugin" + title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here." name: label: "Name" select: "underscored_name" diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 8d5e3cad..c99954d6 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -14,7 +14,7 @@ class CustomWizard::AdminController < ::Admin::AdminController end def custom_field_list - serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer) + serialize_data(CustomWizard::CustomField.full_list, CustomWizard::CustomFieldSerializer) end def render_error(message) diff --git a/extensions/custom_field/extension.rb b/extensions/custom_field/extension.rb new file mode 100644 index 00000000..876f56d4 --- /dev/null +++ b/extensions/custom_field/extension.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module CustomWizardCustomFieldExtension + def custom_field_types + @custom_field_types + end +end diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index d68e978b..5388a326 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -454,32 +454,51 @@ class CustomWizard::Action data: data, user: user ).perform - - registered_fields = CustomWizard::CustomField.cached_list + registered_fields = CustomWizard::CustomField.full_list field_map.each do |field| keyArr = field[:key].split('.') value = field[:value] if keyArr.length > 1 - klass = keyArr.first - name = keyArr.last + klass = keyArr.first.to_sym + name = keyArr.second + + if keyArr.length === 3 && name.include?("{}") + name = name.gsub("{}", "") + json_attr = keyArr.last + type = :json + end else name = keyArr.first end - registered = registered_fields.select { |f| f[:name] == name } - if registered.first.present? - klass = registered.first[:klass] + registered = registered_fields.select { |f| f.name == name }.first + if registered.present? + klass = registered.klass + type = registered.type end - if klass === 'topic' + next if type === :json && json_attr.blank? + + if klass === :topic params[:topic_opts] ||= {} params[:topic_opts][:custom_fields] ||= {} - params[:topic_opts][:custom_fields][name] = value + + if type === :json + params[:topic_opts][:custom_fields][name] ||= {} + params[:topic_opts][:custom_fields][name][json_attr] = value + else + params[:topic_opts][:custom_fields][name] = value + end else - params[:custom_fields] ||= {} - params[:custom_fields][name] = value + if type === :json + params[:custom_fields][name] ||= {} + params[:custom_fields][name][json_attr] = value + else + params[:custom_fields] ||= {} + params[:custom_fields][name] = value + end end end end diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index e3f01a1a..9cc185ba 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -66,10 +66,12 @@ class ::CustomWizard::CustomField value = send(attr) i18n_key = "wizard.custom_field.error" - if value.blank? - if REQUIRED.include?(attr) - add_error(I18n.t("#{i18n_key}.required_attribute", attr: attr)) - end + if value.blank? && REQUIRED.include?(attr) + add_error(I18n.t("#{i18n_key}.required_attribute", attr: attr)) + break + end + + if attr == 'serializers' && !value.is_a?(Array) next end @@ -140,7 +142,7 @@ class ::CustomWizard::CustomField fields.select do |cf| if attr == :serializers - cf[attr].include?(value) + cf[attr] && cf[attr].include?(value) else cf[attr] == value end @@ -215,4 +217,32 @@ class ::CustomWizard::CustomField def self.enabled? any? end + + def self.external_list + external = [] + + CLASSES.keys.each do |klass| + field_types = klass.to_s.classify.constantize.custom_field_types + + if field_types.present? + field_types.each do |name, type| + unless list.any? { |field| field.name === name } + field = new( + 'external', + name: name, + klass: klass, + type: type + ) + external.push(field) + end + end + end + end + + external + end + + def self.full_list + (list + external_list).uniq + end end diff --git a/plugin.rb b/plugin.rb index 0beab1cc..a4278f65 100644 --- a/plugin.rb +++ b/plugin.rb @@ -91,6 +91,7 @@ after_initialize do ../extensions/users_controller.rb ../extensions/custom_field/preloader.rb ../extensions/custom_field/serializer.rb + ../extensions/custom_field/extension.rb ].each do |path| load File.expand_path(path, __FILE__) end @@ -183,18 +184,18 @@ after_initialize do end CustomWizard::CustomField::CLASSES.keys.each do |klass| + class_constant = klass.to_s.classify.constantize + add_model_callback(klass, :after_initialize) do if CustomWizard::CustomField.enabled? CustomWizard::CustomField.list_by(:klass, klass.to_s).each do |field| - klass.to_s - .classify - .constantize - .register_custom_field_type(field[:name], field[:type].to_sym) + class_constant.register_custom_field_type(field[:name], field[:type].to_sym) end end end - klass.to_s.classify.constantize.singleton_class.prepend CustomWizardCustomFieldPreloader + class_constant.singleton_class.prepend CustomWizardCustomFieldPreloader + class_constant.singleton_class.prepend CustomWizardCustomFieldExtension end CustomWizard::CustomField.serializers.each do |serializer_klass| diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 28f2cab8..9910c0bd 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -72,6 +72,42 @@ describe CustomWizard::Action do raw: "topic body" ).exists?).to eq(false) end + + it "adds custom fields" do + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps.first.id, + step_1_field_1: "Topic Title", + step_1_field_2: "topic body" + ).update + wizard.create_updater(wizard.steps.second.id, {}).update + wizard.create_updater(wizard.steps.last.id, + step_3_field_3: category.id + ).update + + topic = Topic.where( + title: "Topic Title", + category_id: category.id + ).first + topic_custom_field = TopicCustomField.where( + name: "topic_field", + value: "Topic custom field value", + topic_id: topic.id + ) + topic_json_custom_field = TopicCustomField.where(" + name = 'topic_json_field' AND + (value::json->>'key_1') = 'Key 1 value' AND + (value::json->>'key_2') = 'Key 2 value' AND + topic_id = #{topic.id}" + ) + post_custom_field = PostCustomField.where( + name: "post_field", + value: "Post custom field value", + post_id: topic.first_post.id + ) + expect(topic_custom_field.exists?).to eq(true) + expect(topic_json_custom_field.exists?).to eq(true) + expect(post_custom_field.exists?).to eq(true) + end end context 'sending a message' do diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index 3c9f1706..b17e26c6 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -49,6 +49,40 @@ describe CustomWizard::CustomField do end context "validation" do + it "does not save without required attributes" do + invalid_field_json = custom_field_json['custom_fields'].first + invalid_field_json['klass'] = nil + + custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.required_attribute", attr: "klass") + ) + expect( + PluginStoreRow.where( + plugin_name: CustomWizard::CustomField::NAMESPACE, + key: custom_field.name + ).exists? + ).to eq(false) + end + + it "does save without optional attributes" do + field_json = custom_field_json['custom_fields'].first + field_json['serializers'] = nil + + custom_field = CustomWizard::CustomField.new(nil, field_json) + expect(custom_field.save).to eq(true) + expect(custom_field.valid?).to eq(true) + expect( + PluginStoreRow.where(" + plugin_name = '#{CustomWizard::CustomField::NAMESPACE}' AND + key = '#{custom_field.name}' AND + value::jsonb = '#{field_json.except('name').to_json}'::jsonb + ",).exists? + ).to eq(true) + end + it "does not save with an unsupported class" do invalid_field_json = custom_field_json['custom_fields'].first invalid_field_json['klass'] = 'user' @@ -178,6 +212,22 @@ describe CustomWizard::CustomField do it "lists saved custom field records by attribute value" do expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1) end + + it "lists saved custom field records by optional values" do + field_json = custom_field_json['custom_fields'].first + field_json['serializers'] = nil + + custom_field = CustomWizard::CustomField.new(nil, field_json) + expect(CustomWizard::CustomField.list_by(:serializers, ['post']).length).to eq(0) + end + + it "lists custom field records added by other plugins " do + expect(CustomWizard::CustomField.external_list.length).to eq(11) + end + + it "lists all custom field records" do + expect(CustomWizard::CustomField.full_list.length).to eq(15) + end end it "is enabled if there are custom fields" do diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index c21d445c..9a1fab40 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -391,10 +391,34 @@ "pairs": [ { "index": 0, - "key": "custom_field_1", + "key": "post_field", "key_type": "text", - "value": "title", - "value_type": "user_field", + "value": "Post custom field value", + "value_type": "text", + "connector": "association" + }, + { + "index": 1, + "key": "topic.topic_field", + "key_type": "text", + "value": "Topic custom field value", + "value_type": "text", + "connector": "association" + }, + { + "index": 2, + "key": "topic.topic_json_field{}.key_1", + "key_type": "text", + "value": "Key 1 value", + "value_type": "text", + "connector": "association" + }, + { + "index": 3, + "key": "topic.topic_json_field{}.key_2", + "key_type": "text", + "value": "Key 2 value", + "value_type": "text", "connector": "association" } ] diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb index e006e65a..8c1a8550 100644 --- a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb @@ -17,9 +17,9 @@ describe CustomWizard::AdminCustomFieldsController do sign_in(admin_user) end - it "returns the list of custom fields" do + it "returns the full list of custom fields" do get "/admin/wizards/custom-fields.json" - expect(response.parsed_body.length).to eq(4) + expect(response.parsed_body.length).to eq(15) end it "saves custom fields" do From 58b3bc13c90328f1c1128b873d100c591ea1015b Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 15 Jun 2021 16:18:17 +1000 Subject: [PATCH 26/58] FIX: Ensure user_email and user_option attributes are handled properly (#119) --- lib/custom_wizard/mapper.rb | 35 +++++++------ spec/components/custom_wizard/mapper_spec.rb | 52 ++++++++++++-------- spec/fixtures/mapper/inputs.json | 16 ++++++ 3 files changed, 67 insertions(+), 36 deletions(-) diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index c1187b0f..0c3543cf 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -5,20 +5,27 @@ class CustomWizard::Mapper USER_FIELDS = [ 'name', 'username', - 'email', 'date_of_birth', 'title', 'locale', 'trust_level', + 'email' + ] + + USER_OPTION_FIELDS = [ 'email_level', 'email_messages_level', 'email_digests' ] - PROFILE_FIELDS = ['location', 'website', 'bio_raw'] + PROFILE_FIELDS = [ + 'location', + 'website', + 'bio_raw' + ] def self.user_fields - USER_FIELDS + PROFILE_FIELDS + USER_FIELDS + USER_OPTION_FIELDS + PROFILE_FIELDS end OPERATORS = { @@ -197,11 +204,15 @@ class CustomWizard::Mapper def map_user_field(value) if value.include?(User::USER_FIELD_PREFIX) - UserCustomField.where(user_id: user.id, name: value).pluck(:value).first + user.custom_fields[value] elsif PROFILE_FIELDS.include?(value) - UserProfile.find_by(user_id: user.id).send(value) + user.user_profile.send(value) elsif USER_FIELDS.include?(value) - User.find(user.id).send(value) + user.send(value) + elsif USER_OPTION_FIELDS.include?(value) + user.user_option.send(value) + else + nil end end @@ -217,19 +228,11 @@ class CustomWizard::Mapper return string if string.blank? if opts[:user] - string.gsub!(/u\{(.*?)\}/) do |match| - result = '' - result = user.send($1) if USER_FIELDS.include?($1) - result = user.user_profile.send($1) if PROFILE_FIELDS.include?($1) - result - end + string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' } end if opts[:wizard] - string.gsub!(/w\{(.*?)\}/) do |match| - value = recurse(data, [*$1.split('.')]) - value.present? ? value : '' - end + string.gsub!(/w\{(.*?)\}/) { |match| recurse(data, [*$1.split('.')]) || '' } end if opts[:value] diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index 434f0001..ed66d7c1 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -229,28 +229,40 @@ describe CustomWizard::Mapper do ).perform).to eq("value 2") end - it "interpolates user fields" do - expect(CustomWizard::Mapper.new( - inputs: inputs['interpolate_user_field'], - data: data, - user: user1 - ).perform).to eq("Name: Angus") - end + context "interpolates" do + it "user fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_user_field'], + data: data, + user: user1 + ).perform).to eq("Name: Angus") + end - it "interpolates wizard fields" do - expect(CustomWizard::Mapper.new( - inputs: inputs['interpolate_wizard_field'], - data: data, - user: user1 - ).perform).to eq("Input 1: value 1") - end + it "user emails" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_user_email'], + data: data, + user: user1 + ).perform).to eq("Email: angus@email.com") + end - it "interpolates date" do - expect(CustomWizard::Mapper.new( - inputs: inputs['interpolate_timestamp'], - data: data, - user: user1 - ).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}") + it "user options" do + user1.user_option.update_columns(email_level: UserOption.email_level_types[:never]) + + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_user_option'], + data: data, + user: user1 + ).perform).to eq("Email Level: #{UserOption.email_level_types[:never]}") + end + + it "date" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_timestamp'], + data: data, + user: user1 + ).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}") + end end it "handles greater than pairs" do diff --git a/spec/fixtures/mapper/inputs.json b/spec/fixtures/mapper/inputs.json index f7d98903..443f186b 100644 --- a/spec/fixtures/mapper/inputs.json +++ b/spec/fixtures/mapper/inputs.json @@ -57,6 +57,22 @@ "output": "Name: u{name}" } ], + "interpolate_user_email": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Email: u{email}" + } + ], + "interpolate_user_option": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Email Level: u{email_level}" + } + ], "interpolate_wizard_field": [ { "type": "assignment", From f742626786ff0c55dd33f7e80eb95e3a51409f02 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 16 Jun 2021 13:55:10 +1000 Subject: [PATCH 27/58] Ensure our custom paste logic is running --- assets/javascripts/wizard/custom-wizard.js.es6 | 4 ++++ .../wizard/initializers/custom-wizard-field.js.es6 | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6 index 63a9ea10..35fef004 100644 --- a/assets/javascripts/wizard/custom-wizard.js.es6 +++ b/assets/javascripts/wizard/custom-wizard.js.es6 @@ -4,6 +4,10 @@ export default Ember.Application.extend({ rootElement: "#custom-wizard-main", Resolver: buildResolver("wizard"), + customEvents: { + paste: "paste" + }, + start() { Object.keys(requirejs._eak_seen).forEach((key) => { if (/\/pre\-initializers\//.test(key)) { diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 index 3ede2b05..f5deb927 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 +++ b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 @@ -15,7 +15,7 @@ export default { ); const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); - const { toMarkdown } = requirejs("discourse/lib/to-markdown"); + const toMarkdown = requirejs("discourse/lib/to-markdown").default; FieldComponent.reopen({ classNameBindings: ["field.id"], @@ -181,7 +181,7 @@ export default { markdown = pre.match(/\S$/) ? ` ${markdown}` : markdown; } - this.appEvents.trigger("composer:insert-text", { + this.appEvents.trigger("wizard-editor:insert-text", { fieldId: this.fieldId, text: markdown, }); From 9452907f0ae2e7abefa7971a8eae52cf58ad0f6c Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 16 Jun 2021 14:13:12 +1000 Subject: [PATCH 28/58] Don't attempt to save submissions if the user can't access the wizard --- controllers/custom_wizard/wizard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 37728ecb..9670fd62 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -61,7 +61,7 @@ class CustomWizard::WizardController < ::ApplicationController result = success_json user = current_user - if user + if user && wizard.can_access? submission = wizard.current_submission if submission && submission['redirect_to'] result.merge!(redirect_to: submission['redirect_to']) From 33df6f9fbc71a69ac2b79b9331a015e6e5453802 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 16 Jun 2021 14:24:07 +1000 Subject: [PATCH 29/58] Add test for skip when cant access --- .../custom_wizard/wizard_controller_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index 3e7ddd3d..4380bc73 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -11,6 +11,14 @@ describe CustomWizard::WizardController do ) } + let(:permitted_json) { + JSON.parse( + File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" + ).read + ) + } + before do CustomWizard::Template.save( JSON.parse(File.open( @@ -47,6 +55,14 @@ describe CustomWizard::WizardController do expect(response.status).to eq(200) end + it 'lets user skip if user cant access wizard' do + @template["permitted"] = permitted_json["permitted"] + CustomWizard::Template.save(@template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/skip.json' + expect(response.status).to eq(200) + end + it 'returns a no skip message if user is not allowed to skip' do @template['required'] = 'true' CustomWizard::Template.save(@template) From 46bb28bb07079cd5a13b8d840e5a0834b6f4d325 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 16 Jun 2021 14:26:28 +1000 Subject: [PATCH 30/58] Fix linting --- assets/javascripts/wizard/custom-wizard.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6 index 35fef004..8c0a473c 100644 --- a/assets/javascripts/wizard/custom-wizard.js.es6 +++ b/assets/javascripts/wizard/custom-wizard.js.es6 @@ -5,7 +5,7 @@ export default Ember.Application.extend({ Resolver: buildResolver("wizard"), customEvents: { - paste: "paste" + paste: "paste", }, start() { From dc7e43b6d774b8585e2d2fae5e96b6f6c2eae626 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 16 Jun 2021 16:21:24 +1000 Subject: [PATCH 31/58] Force second step build to overcome can_access? check The can_access? check will fail if the wizard does not allow multiple submissions. This means that on the second build of the wizard on the final step of a single submission wizard, the wizard instance will have no steps, and an error will be thrown when logic assumes it does. --- controllers/custom_wizard/steps.rb | 2 +- lib/custom_wizard/builder.rb | 2 +- spec/components/custom_wizard/wizard_spec.rb | 2 ++ spec/fixtures/wizard.json | 1 - 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index 5465d0dd..aa4fbd7f 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -23,7 +23,7 @@ class CustomWizard::StepsController < ::ApplicationController if updater.success? wizard_id = update_params[:wizard_id] builder = CustomWizard::Builder.new(wizard_id, current_user) - @wizard = builder.build + @wizard = builder.build(force: true) current_step = @wizard.find_step(update[:step_id]) current_submission = @wizard.current_submission diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 813680c6..a9fc6263 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -30,7 +30,7 @@ class CustomWizard::Builder def build(build_opts = {}, params = {}) return nil if !SiteSetting.custom_wizard_enabled || !@wizard - return @wizard if !@wizard.can_access? + return @wizard if !@wizard.can_access? && !build_opts[:force] build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index aed44fe6..9808f32f 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -173,6 +173,8 @@ describe CustomWizard::Wizard do progress_step("step_2", acting_user: trusted_user) progress_step("step_3", acting_user: trusted_user) + @permitted_template["multiple_submissions"] = true + expect( CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? ).to eq(true) diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index 9a1fab40..a505c0d3 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -3,7 +3,6 @@ "name": "Super Mega Fun Wizard", "background": "#333333", "save_submissions": true, - "multiple_submissions": true, "after_signup": false, "prompt_completion": false, "theme_id": 2, From d3c6733e59c73508da3746681c5d8b39bde55d28 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Thu, 17 Jun 2021 17:50:22 +1000 Subject: [PATCH 32/58] Abstract and improve submission handling --- config/locales/server.en.yml | 4 +- .../custom_wizard/admin/submissions.rb | 26 ++---- controllers/custom_wizard/steps.rb | 10 +-- controllers/custom_wizard/wizard.rb | 5 +- extensions/invites_controller.rb | 2 +- jobs/set_after_time_wizard.rb | 2 +- lib/custom_wizard/action.rb | 54 +++++------ lib/custom_wizard/builder.rb | 35 ++++---- lib/custom_wizard/submission.rb | 89 +++++++++++++++++++ lib/custom_wizard/wizard.rb | 85 +++++++----------- plugin.rb | 7 +- serializers/custom_wizard/submission.rb | 13 +++ spec/components/custom_wizard/action_spec.rb | 10 +-- spec/components/custom_wizard/builder_spec.rb | 28 +++--- .../custom_wizard/submission_spec.rb | 43 +++++++++ spec/components/custom_wizard/wizard_spec.rb | 25 +----- .../admin/submissions_controller_spec.rb | 37 ++++---- .../custom_wizard/steps_controller_spec.rb | 8 +- .../custom_wizard/wizard_controller_spec.rb | 5 +- 19 files changed, 292 insertions(+), 196 deletions(-) create mode 100644 lib/custom_wizard/submission.rb create mode 100644 serializers/custom_wizard/submission.rb create mode 100644 spec/components/custom_wizard/submission_spec.rb diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 4bda825a..7e507450 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1,8 +1,8 @@ en: admin: wizard: - submissions: - no_user: "deleted (id: %{id})" + submission: + no_user: "deleted (user_id: %{user_id})" wizard: custom_title: "Wizard" diff --git a/controllers/custom_wizard/admin/submissions.rb b/controllers/custom_wizard/admin/submissions.rb index 5467587e..682c4030 100644 --- a/controllers/custom_wizard/admin/submissions.rb +++ b/controllers/custom_wizard/admin/submissions.rb @@ -13,34 +13,20 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController def show render_json_dump( wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), - submissions: build_submissions.as_json + submissions: ActiveModel::ArraySerializer.new(ordered_submissions, each_serializer: CustomWizard::SubmissionSerializer) ) end def download - send_data build_submissions.to_json, + send_data ordered_submissions.to_json, filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json", content_type: "application/json", disposition: "attachment" end + + protected - private - - def build_submissions - PluginStoreRow.where(plugin_name: "#{@wizard.id}_submissions") - .order('id DESC') - .map do |row| - value = ::JSON.parse(row.value) - - if user = User.find_by(id: row.key) - username = user.username - else - username = I18n.t('admin.wizard.submissions.no_user', id: row.key) - end - - value.map do |v| - { username: username }.merge!(v.except("redirect_to")) - end - end.flatten + def ordered_submissions + CustomWizard::Submission.list(@wizard, order_by: 'id') end end diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index aa4fbd7f..9c0fbf92 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -28,7 +28,7 @@ class CustomWizard::StepsController < ::ApplicationController current_step = @wizard.find_step(update[:step_id]) current_submission = @wizard.current_submission result = {} - @wizard.filter_conditional_fields + if current_step.conditional_final_step && !current_step.last_step current_step.force_final = true end @@ -44,7 +44,7 @@ class CustomWizard::StepsController < ::ApplicationController end end - @wizard.save_submission(current_submission) + current_submission.save if redirect = get_redirect updater.result[:redirect_on_complete] = redirect @@ -101,9 +101,9 @@ class CustomWizard::StepsController < ::ApplicationController def get_redirect return @result[:redirect_on_next] if @result[:redirect_on_next].present? - current_submission = @wizard.current_submission - return nil unless current_submission.present? + submission = @wizard.current_submission + return nil unless submission.present? ## route_to set by actions, redirect_on_complete set by actions, redirect_to set at wizard entry - current_submission[:route_to] || current_submission[:redirect_on_complete] || current_submission[:redirect_to] + submission.route_to || submission.redirect_on_complete || submission.redirect_to end end diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 9670fd62..e682a307 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -63,8 +63,9 @@ class CustomWizard::WizardController < ::ApplicationController if user && wizard.can_access? submission = wizard.current_submission - if submission && submission['redirect_to'] - result.merge!(redirect_to: submission['redirect_to']) + + if submission && submission.redirect_to + result.merge!(redirect_to: submission.redirect_to) end wizard.final_cleanup! diff --git a/extensions/invites_controller.rb b/extensions/invites_controller.rb index cafb15bd..5e0094da 100644 --- a/extensions/invites_controller.rb +++ b/extensions/invites_controller.rb @@ -5,7 +5,7 @@ module InvitesControllerCustomWizard wizard_id = @user.custom_fields['redirect_to_wizard'] if wizard_id && url != '/' - CustomWizard::Wizard.set_submission_redirect(@user, wizard_id, url) + CustomWizard::Wizard.set_wizard_redirect(@user, wizard_id, url) url = "/w/#{wizard_id.dasherize}" end end diff --git a/jobs/set_after_time_wizard.rb b/jobs/set_after_time_wizard.rb index 3b2e9e11..7a5b86c6 100644 --- a/jobs/set_after_time_wizard.rb +++ b/jobs/set_after_time_wizard.rb @@ -9,7 +9,7 @@ module Jobs user_ids = [] User.human_users.each do |user| - if CustomWizard::Wizard.set_wizard_redirect(wizard.id, user) + if CustomWizard::Wizard.set_user_redirect(wizard.id, user) user_ids.push(user.id) end end diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 5388a326..acb8dafb 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true class CustomWizard::Action - attr_accessor :data, + attr_accessor :submission, :action, :user, :guardian, @@ -11,7 +11,7 @@ class CustomWizard::Action @action = opts[:action] @user = @wizard.user @guardian = Guardian.new(@user) - @data = opts[:data] + @submission = opts[:submission] @log = [] @result = CustomWizard::ActionResult.new end @@ -26,14 +26,18 @@ class CustomWizard::Action end if @result.success? && @result.output.present? - data[action['id']] = @result.output + @submission.fields[action['id']] = @result.output end save_log end + def mapper_data + @mapper_data ||= @submission&.fields_and_meta || {} + end + def mapper - @mapper ||= CustomWizard::Mapper.new(user: user, data: data) + @mapper ||= CustomWizard::Mapper.new(user: user, data: mapper_data) end def create_topic @@ -47,7 +51,7 @@ class CustomWizard::Action messages = creator.errors.full_messages.join(" ") log_error("failed to create", messages) elsif action['skip_redirect'].blank? - data['redirect_on_complete'] = post.topic.url + @submission.redirect_on_complete = post.topic.url end if creator.errors.blank? @@ -65,7 +69,7 @@ class CustomWizard::Action if action['required'].present? required = CustomWizard::Mapper.new( inputs: action['required'], - data: data, + data: mapper_data, user: user ).perform @@ -79,7 +83,7 @@ class CustomWizard::Action targets = CustomWizard::Mapper.new( inputs: action['recipient'], - data: data, + data: mapper_data, user: user, multiple: true ).perform @@ -115,7 +119,7 @@ class CustomWizard::Action messages = creator.errors.full_messages.join(" ") log_error("failed to create message", messages) elsif action['skip_redirect'].blank? - data['redirect_on_complete'] = post.topic.url + @submission.redirect_on_complete = post.topic.url end if creator.errors.blank? @@ -178,7 +182,7 @@ class CustomWizard::Action def watch_categories watched_categories = CustomWizard::Mapper.new( inputs: action['categories'], - data: data, + data: mapper_data, user: user ).perform @@ -193,7 +197,7 @@ class CustomWizard::Action mute_remainder = CustomWizard::Mapper.new( inputs: action['mute_remainder'], - data: data, + data: mapper_data, user: user ).perform @@ -202,7 +206,7 @@ class CustomWizard::Action if action['usernames'] mapped_users = CustomWizard::Mapper.new( inputs: action['usernames'], - data: data, + data: mapper_data, user: user ).perform @@ -284,7 +288,7 @@ class CustomWizard::Action end route_to = Discourse.base_uri + url - @result.output = data['route_to'] = route_to + @result.output = @submission.route_to = route_to log_success("route: #{route_to}") else @@ -295,7 +299,7 @@ class CustomWizard::Action def add_to_group group_map = CustomWizard::Mapper.new( inputs: action['group'], - data: data, + data: mapper_data, user: user, opts: { multiple: true @@ -345,18 +349,18 @@ class CustomWizard::Action else url = CustomWizard::Mapper.new( inputs: url_input, - data: data, + data: mapper_data, user: user ).perform end if action['code'] - data[action['code']] = SecureRandom.hex(8) - url += "&#{action['code']}=#{data[action['code']]}" + @submission.fields[action['code']] = SecureRandom.hex(8) + url += "&#{action['code']}=#{@submission.fields[action['code']]}" end route_to = UrlHelper.encode(url) - data['route_to'] = route_to + @submission.route_to = route_to log_info("route: #{route_to}") end @@ -416,7 +420,7 @@ class CustomWizard::Action def action_category output = CustomWizard::Mapper.new( inputs: action['category'], - data: data, + data: mapper_data, user: user ).perform @@ -434,7 +438,7 @@ class CustomWizard::Action def action_tags output = CustomWizard::Mapper.new( inputs: action['tags'], - data: data, + data: mapper_data, user: user, ).perform @@ -451,7 +455,7 @@ class CustomWizard::Action if (custom_fields = action['custom_fields']).present? field_map = CustomWizard::Mapper.new( inputs: custom_fields, - data: data, + data: mapper_data, user: user ).perform registered_fields = CustomWizard::CustomField.full_list @@ -513,7 +517,7 @@ class CustomWizard::Action params[:title] = CustomWizard::Mapper.new( inputs: action['title'], - data: data, + data: mapper_data, user: user ).perform @@ -525,7 +529,7 @@ class CustomWizard::Action wizard: true, template: true ) : - data[action['post']] + @submission.fields[action['post']] params[:import_mode] = ActiveRecord::Type::Boolean.new.cast(action['suppress_notifications']) @@ -548,7 +552,7 @@ class CustomWizard::Action unless action[field].nil? || action[field] == "" params[field.to_sym] = CustomWizard::Mapper.new( inputs: action[field], - data: data, + data: mapper_data, user: user ).perform end @@ -587,7 +591,7 @@ class CustomWizard::Action if input.present? value = CustomWizard::Mapper.new( inputs: input, - data: data, + data: mapper_data, user: user ).perform @@ -617,7 +621,7 @@ class CustomWizard::Action if action[attr].present? value = CustomWizard::Mapper.new( inputs: action[attr], - data: data, + data: mapper_data, user: user ).perform diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index a9fc6263..b5c04c27 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -24,7 +24,7 @@ class CustomWizard::Builder def mapper CustomWizard::Mapper.new( user: @wizard.user, - data: @wizard.current_submission + data: @wizard.current_submission&.fields_and_meta ) end @@ -47,9 +47,8 @@ class CustomWizard::Builder step.on_update do |updater| @updater = updater - @submission = (@wizard.current_submission || {}) - .merge(@updater.submission) - .with_indifferent_access + @submission = @wizard.current_submission || CustomWizard::Submission.new(@wizard) + @submission.fields.merge(@updater.submission) @updater.validate next if @updater.errors.any? @@ -60,13 +59,11 @@ class CustomWizard::Builder run_step_actions if @updater.errors.empty? - if route_to = @submission['route_to'] - @submission.delete('route_to') - end + route_to = @submission.route_to + @submission.route_to = nil + @submission.save - @wizard.save_submission(@submission) @updater.result[:redirect_on_next] = route_to if route_to - true else false @@ -93,7 +90,7 @@ class CustomWizard::Builder params[:value] = prefill_field(field_template, step_template) if !build_opts[:reset] && (submission = @wizard.current_submission) - params[:value] = submission[field_template['id']] if submission[field_template['id']] + params[:value] = submission.fields[field_template['id']] if submission.fields[field_template['id']] end if field_template['type'] === 'group' && params[:value].present? @@ -136,7 +133,7 @@ class CustomWizard::Builder content = CustomWizard::Mapper.new( inputs: content_inputs, user: @wizard.user, - data: @wizard.current_submission, + data: @wizard.current_submission&.fields_and_meta, opts: { with_type: true } @@ -171,7 +168,7 @@ class CustomWizard::Builder index = CustomWizard::Mapper.new( inputs: field_template['index'], user: @wizard.user, - data: @wizard.current_submission + data: @wizard.current_submission&.fields_and_meta ).perform params[:index] = index.to_i unless index.nil? @@ -195,7 +192,7 @@ class CustomWizard::Builder CustomWizard::Mapper.new( inputs: prefill, user: @wizard.user, - data: @wizard.current_submission + data: @wizard.current_submission&.fields_and_meta ).perform end end @@ -205,7 +202,7 @@ class CustomWizard::Builder result = CustomWizard::Mapper.new( inputs: template['condition'], user: @wizard.user, - data: @wizard.current_submission, + data: @wizard.current_submission&.fields_and_meta, opts: { multiple: true } @@ -275,7 +272,7 @@ class CustomWizard::Builder permitted_data = {} submission_key = nil params_key = nil - submission = @wizard.current_submission || {} + submission = @wizard.current_submission || CustomWizard::Submission.new(@wizard) permitted_params.each do |pp| pair = pp['pairs'].first @@ -283,11 +280,11 @@ class CustomWizard::Builder submission_key = pair['value'].to_sym if submission_key && params_key - submission[submission_key] = params[params_key] + submission.fields[submission_key] = params[params_key] end end - @wizard.save_submission(submission) + submission.save end def ensure_required_data(step, step_template) @@ -302,7 +299,7 @@ class CustomWizard::Builder end pairs.each do |pair| - pair['key'] = @wizard.current_submission[pair['key']] + pair['key'] = @wizard.current_submission.fields[pair['key']] end if !mapper.validate_pairs(pairs) @@ -329,7 +326,7 @@ class CustomWizard::Builder CustomWizard::Action.new( action: action_template, wizard: @wizard, - data: @submission + submission: @submission ).perform end end diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb new file mode 100644 index 00000000..8cb06a4a --- /dev/null +++ b/lib/custom_wizard/submission.rb @@ -0,0 +1,89 @@ +class CustomWizard::Submission + include ActiveModel::SerializerSupport + + KEY ||= "submissions" + ACTION_KEY ||= "action" + META ||= %w(submitted_at route_to redirect_on_complete redirect_to) + + attr_reader :id, + :user, + :wizard + + attr_accessor :fields + + META.each do |attr| + class_eval { attr_accessor attr } + end + + def initialize(wizard, data = {}, user_id = nil) + @wizard = wizard + + if user_id + @user = User.find_by(id: user_id) || OpenStruct.new(deleted: true, id: user_id) + else + @user = wizard.user + end + + data = data.with_indifferent_access + @id = data['id'] || SecureRandom.hex(12) + @fields = data.except(META + ['id']) || {} + + META.each do |attr| + send("#{attr}=", data[attr]) if data[attr] + end + end + + def save + return nil unless wizard.save_submissions + validate_fields + + submissions = self.class.list(wizard, user_id: user.id).select { |s| s.id != self.id } + submissions.push(self) + + submission_data = submissions.map { |s| s.fields_and_meta } + PluginStore.set("#{wizard.id}_#{KEY}", user.id, submission_data) + end + + def validate_fields + self.fields = fields.select do |key, value| + wizard.field_ids.include?(key) || key.include?(ACTION_KEY) + end + end + + def fields_and_meta + result = fields + + META.each do |attr| + if value = self.send(attr) + result[attr] = value + end + end + + result + end + + def self.get(wizard, user_id) + data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).first + new(wizard, data, user_id) + end + + def self.list(wizard, user_id: nil, order_by: nil) + params = { plugin_name: "#{wizard.id}_#{KEY}" } + params[:key] = user_id if user_id.present? + + query = PluginStoreRow.where(params) + query = query.order("#{order_by} DESC") if order_by.present? + + result = [] + + query.each do |record| + if (submission_data = ::JSON.parse(record.value)).any? + submission_data.each do |data| + result.push(new(wizard, data, record.key)) + end + end + end + + result + end +end \ No newline at end of file diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index f92f3d61..fff7d159 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -29,7 +29,8 @@ class CustomWizard::Wizard :first_step, :start, :actions, - :user + :user, + :submissions def initialize(attrs = {}, user = nil) @user = user @@ -221,72 +222,38 @@ class CustomWizard::Wizard @groups ||= ::Site.new(Guardian.new(user)).groups end + def field_ids + steps.map { |step| step.fields.map { |field| field.id } }.flatten + end + def submissions return nil unless user.present? - @submissions ||= Array.wrap(PluginStore.get("#{id}_submissions", user.id)) + @submissions ||= CustomWizard::Submission.list(self, user_id: user.id) end def current_submission - if submissions.present? && submissions.last.present? && !submissions.last.key?("submitted_at") - submissions.last.with_indifferent_access - else - nil + @current_submission ||= begin + if submissions.present? + unsubmitted = submissions.select { |submission| !submission.submitted_at } + unsubmitted.present? ? unsubmitted.first : nil + else + nil + end end end - def set_submissions(submissions) - PluginStore.set("#{id}_submissions", user.id, Array.wrap(submissions)) - @submissions = nil - end - - def save_submission(submission) - return nil unless save_submissions - - submissions.pop(1) if unfinished? - submissions.push(submission) - set_submissions(submissions) - end - - def filter_conditional_fields - included_fields = steps.map { |s| s.fields.map { |f| f.id } }.flatten - filtered_submision = current_submission&.select do |key, _| - key = key.to_s - included_fields.include?(key) || - required_fields.include?(key) || - key.include?("action") - end - - save_submission(filtered_submision) - end - - def required_fields - %w{ - submitted_at - route_to - saved_param - } - end - def final_cleanup! if id == user.custom_fields['redirect_to_wizard'] user.custom_fields.delete('redirect_to_wizard') user.save_custom_fields(true) end - if submission = current_submission - submission['submitted_at'] = Time.now.iso8601 - save_submission(submission) + if current_submission.present? + current_submission.submitted_at = Time.now.iso8601 + current_submission.save end end - def self.submissions(wizard_id, user) - new({ id: wizard_id }, user).submissions - end - - def self.set_submissions(wizard_id, user, submissions) - new({ id: wizard_id }, user).set_submissions(submissions) - end - def self.create(wizard_id, user = nil) if template = CustomWizard::Template.find(wizard_id) new(template.to_h, user) @@ -339,11 +306,7 @@ class CustomWizard::Wizard end end - def self.set_submission_redirect(user, wizard_id, url) - set_submissions(wizard_id, user, [{ redirect_to: url }]) - end - - def self.set_wizard_redirect(wizard_id, user) + def self.set_user_redirect(wizard_id, user) wizard = self.create(wizard_id, user) if wizard.permitted? @@ -353,4 +316,16 @@ class CustomWizard::Wizard false end end + + def self.set_wizard_redirect(user, wizard_id, url) + wizard = self.create(wizard_id, user) + + if wizard.permitted? + submission = wizard.current_submission || CustomWizard::Submission.new(wizard) + submission.redirect_to = url + submission.save + else + false + end + end end diff --git a/plugin.rb b/plugin.rb index a4278f65..ecbf850a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -66,6 +66,7 @@ after_initialize do ../lib/custom_wizard/log.rb ../lib/custom_wizard/step_updater.rb ../lib/custom_wizard/step.rb + ../lib/custom_wizard/submission.rb ../lib/custom_wizard/template.rb ../lib/custom_wizard/wizard.rb ../lib/custom_wizard/api/api.rb @@ -110,7 +111,7 @@ after_initialize do if !wizard.completed? custom_redirect = true - CustomWizard::Wizard.set_wizard_redirect(wizard.id, user) + CustomWizard::Wizard.set_user_redirect(wizard.id, user) end end @@ -131,7 +132,7 @@ after_initialize do on(:user_approved) do |user| if wizard = CustomWizard::Wizard.after_signup(user) - CustomWizard::Wizard.set_wizard_redirect(wizard.id, user) + CustomWizard::Wizard.set_user_redirect(wizard.id, user) end end @@ -142,7 +143,7 @@ after_initialize do if request.format === 'text/html' && !@excluded_routes.any? { |str| /#{str}/ =~ url } && wizard_id if request.referer !~ /\/w\// && request.referer !~ /\/invites\// - CustomWizard::Wizard.set_submission_redirect(current_user, wizard_id, request.referer) + CustomWizard::Wizard.set_wizard_redirect(current_user, wizard_id, request.referer) end if CustomWizard::Template.exists?(wizard_id) redirect_to "/w/#{wizard_id.dasherize}" diff --git a/serializers/custom_wizard/submission.rb b/serializers/custom_wizard/submission.rb new file mode 100644 index 00000000..bb8bc288 --- /dev/null +++ b/serializers/custom_wizard/submission.rb @@ -0,0 +1,13 @@ +class CustomWizard::SubmissionSerializer + attributes :id, + :username, + :fields, + :redirect_to, + :submitted_at + + def username + object.user.deleted ? + I18n.t('admin.wizard.submission.no_user', user_id: object.user.id) : + object.user.username + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 9910c0bd..6f77d617 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -182,7 +182,7 @@ describe CustomWizard::Action do updater = wizard.create_updater(wizard.steps[1].id, {}) updater.update - category = Category.find_by(id: wizard.current_submission['action_8']) + category = Category.find_by(id: wizard.current_submission.fields['action_8']) 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%40email.com&category_id=#{category.id}&tags=tag1" @@ -215,20 +215,20 @@ describe CustomWizard::Action do wizard = CustomWizard::Builder.new(@template[:id], user).build wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[1].id, {}).update - expect(Category.where(id: wizard.current_submission['action_8']).exists?).to eq(true) + expect(Category.where(id: wizard.current_submission.fields['action_8']).exists?).to eq(true) end it 'creates a group' do wizard = CustomWizard::Builder.new(@template[:id], user).build wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update - expect(Group.where(name: wizard.current_submission['action_9']).exists?).to eq(true) + expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true) end it 'adds a user to a group' do wizard = CustomWizard::Builder.new(@template[:id], user).build step_id = wizard.steps[0].id updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update - group = Group.find_by(name: wizard.current_submission['action_9']) + group = Group.find_by(name: wizard.current_submission.fields['action_9']) expect(group.users.first.username).to eq('angus') end @@ -237,7 +237,7 @@ describe CustomWizard::Action do wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[1].id, {}).update expect(CategoryUser.where( - category_id: wizard.current_submission['action_8'], + category_id: wizard.current_submission.fields['action_8'], user_id: user.id ).first.notification_level).to eq(2) expect(CategoryUser.where( diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index d9d3524e..0504a238 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -189,7 +189,10 @@ describe CustomWizard::Builder do context "user has partially completed" do before do wizard = CustomWizard::Wizard.new(@template, user) - wizard.set_submissions(step_1_field_1: 'I am a user submission') + data = { + step_1_field_1: 'I am a user submission' + } + CustomWizard::Submission.new(wizard, data).save end it 'returns saved submissions' do @@ -253,9 +256,12 @@ describe CustomWizard::Builder do end it 'is permitted if required data is present' do - CustomWizard::Wizard.set_submissions('super_mega_fun_wizard', user, - required_data: "required_value" - ) + wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) + data = { + step_1_field_1: 'I am a user submission' + } + CustomWizard::Submission.new(wizard, data).save + expect( CustomWizard::Builder.new(@template[:id], user).build .steps.first @@ -336,31 +342,27 @@ describe CustomWizard::Builder do context 'on update' do def perform_update(step_id, submission) - wizard = CustomWizard::Builder.new(@template[:id], user).build - updater = wizard.create_updater(step_id, submission) + updater = @wizard.create_updater(step_id, submission) updater.update updater end it 'saves submissions' do + @wizard = CustomWizard::Builder.new(@template[:id], user).build perform_update('step_1', step_1_field_1: 'Text input') - expect( - CustomWizard::Wizard.submissions(@template[:id], user) - .first['step_1_field_1'] - ).to eq('Text input') + expect(@wizard.current_submission.fields['step_1_field_1']).to eq('Text input') end context 'save submissions disabled' do before do @template[:save_submissions] = false CustomWizard::Template.save(@template.as_json) + @wizard = CustomWizard::Builder.new(@template[:id], user).build end it "does not save submissions" do perform_update('step_1', step_1_field_1: 'Text input') - expect( - CustomWizard::Wizard.submissions(@template[:id], user).first - ).to eq(nil) + expect(@wizard.current_submission).to eq(nil) end end end diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb new file mode 100644 index 00000000..798a2969 --- /dev/null +++ b/spec/components/custom_wizard/submission_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +require_relative '../../plugin_helper' + +describe CustomWizard::Submission do + fab!(:user) { Fabricate(:user) } + fab!(:user2) { Fabricate(:user) } + + let(:template_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + CustomWizard::Template.save(template_json, skip_jobs: true) + + template_json_2 = template_json.dup + template_json_2["id"] = "super_mega_fun_wizard_2" + CustomWizard::Template.save(template_json_2, skip_jobs: true) + + @wizard = CustomWizard::Wizard.create(template_json["id"], user) + @wizard2 = CustomWizard::Wizard.create(template_json["id"], user2) + @wizard3 = CustomWizard::Wizard.create(template_json_2["id"], user) + + described_class.new(@wizard, step_1_field_1: "I am a user submission").save + described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save + described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save + end + + it "saves a user's submission" do + expect( + described_class.get(template_json["id"], user.id).fields["step_1_field_1"] + ).to eq("I am a user submission") + end + + it "list submissions by wizard" do + expect(described_class.list(@wizard).size).to eq(2) + end + + it "list submissions by wizard and user" do + expect(described_class.list(@wizard, user).size).to eq(1) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 9808f32f..376fce2c 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -204,14 +204,7 @@ describe CustomWizard::Wizard do context "submissions" do before do - @wizard.set_submissions(step_1_field_1: 'I am a user submission') - end - - it "sets the user's submission" do - expect( - PluginStore.get("#{template_json['id']}_submissions", user.id) - .first['step_1_field_1'] - ).to eq('I am a user submission') + CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission") end it "lists the user's submissions" do @@ -219,20 +212,10 @@ describe CustomWizard::Wizard do end it "returns the user's current submission" do - expect(@wizard.current_submission['step_1_field_1']).to eq('I am a user submission') + expect(@wizard.current_submission.fields["step_1_field_1"]).to eq("I am a user submission") end end - it "provides class methods to set and list submissions" do - CustomWizard::Wizard.set_submissions(template_json['id'], user, - step_1_field_1: 'I am a user submission' - ) - expect( - CustomWizard::Wizard.submissions(template_json['id'], user) - .first['step_1_field_1'] - ).to eq('I am a user submission') - end - context "class methods" do before do CustomWizard::Template.save(@permitted_template, skip_jobs: true) @@ -273,7 +256,7 @@ describe CustomWizard::Wizard do it "sets wizard redirects if user is permitted" do CustomWizard::Template.save(@permitted_template, skip_jobs: true) - CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', trusted_user) + CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user) expect( trusted_user.custom_fields['redirect_to_wizard'] ).to eq("super_mega_fun_wizard") @@ -281,7 +264,7 @@ describe CustomWizard::Wizard do it "does not set a wizard redirect if user is not permitted" do CustomWizard::Template.save(@permitted_template, skip_jobs: true) - CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', user) + CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user) expect( trusted_user.custom_fields['redirect_to_wizard'] ).to eq(nil) diff --git a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb index f63eead5..eae783aa 100644 --- a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb @@ -5,6 +5,7 @@ describe CustomWizard::AdminSubmissionsController do fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:user1) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) } + fab!(:user3) { Fabricate(:user) } let(:template) { JSON.parse(File.open( @@ -14,33 +15,35 @@ describe CustomWizard::AdminSubmissionsController do before do CustomWizard::Template.save(template, skip_jobs: true) - CustomWizard::Wizard.set_submissions(template['id'], user1, - step_1_field_1: "I am a user1's submission" - ) - CustomWizard::Wizard.set_submissions(template['id'], user2, - step_1_field_1: "I am a user2's submission" - ) + + template_2 = template.dup + template_2["id"] = "super_mega_fun_wizard_2" + CustomWizard::Template.save(template_2, skip_jobs: true) + + wizard1 = CustomWizard::Wizard.create(template["id"], user1) + wizard2 = CustomWizard::Wizard.create(template["id"], user2) + wizard3 = CustomWizard::Wizard.create(template_2["id"], user3) + + CustomWizard::Submission.new(wizard1, step_1_field_1: "I am a user1's submission").save + CustomWizard::Submission.new(wizard2, step_1_field_1: "I am a user2's submission").save + CustomWizard::Submission.new(wizard3, step_1_field_1: "I am a user3's submission").save + sign_in(admin_user) end - it "returns a basic list of wizards" do + it "returns a list of wizards" do get "/admin/wizards/submissions.json" - expect(response.parsed_body.length).to eq(1) + expect(response.parsed_body.length).to eq(3) expect(response.parsed_body.first['id']).to eq(template['id']) end - it "returns the all user's submissions for a wizard" do + it "returns users' submissions for a wizard" do get "/admin/wizards/submissions/#{template['id']}.json" expect(response.parsed_body['submissions'].length).to eq(2) end - it "returns the all user's submissions for a wizard" do - get "/admin/wizards/submissions/#{template['id']}.json" - expect(response.parsed_body['submissions'].length).to eq(2) - end - - it "downloads all user submissions" do - get "/admin/wizards/submissions/#{template['id']}/download" - expect(response.parsed_body.length).to eq(2) + it "downloads submissions" do + get "/admin/wizards/submissions/#{template_2['id']}/download" + expect(response.parsed_body.length).to eq(1) end end diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 2424274b..ab705cac 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -68,7 +68,7 @@ describe CustomWizard::StepsController do wizard_id = response.parsed_body['wizard']['id'] wizard = CustomWizard::Wizard.create(wizard_id, user) - expect(wizard.submissions.last['step_1_field_1']).to eq("Text input") + expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input") end context "raises an error" do @@ -175,7 +175,7 @@ describe CustomWizard::StepsController do wizard_id = response.parsed_body['wizard']['id'] wizard = CustomWizard::Wizard.create(wizard_id, user) - group_name = wizard.submissions.last['action_9'] + group_name = wizard.current_submission.fields['action_9'] group = Group.find_by(name: group_name) expect(group.full_name).to eq("My cool group") end @@ -275,7 +275,7 @@ describe CustomWizard::StepsController do wizard_id = response.parsed_body['wizard']['id'] wizard = CustomWizard::Wizard.create(wizard_id, user) - submission = wizard.submissions.last - expect(submission.keys).not_to include("step_2_field_1") + submission = wizard.current_submission + expect(submission.fields.keys).not_to include("step_2_field_1") end end diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index 4380bc73..7a977539 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -71,9 +71,8 @@ describe CustomWizard::WizardController do end it 'skip response contains a redirect_to if in users submissions' do - CustomWizard::Wizard.set_submissions(@template['id'], user, - redirect_to: '/t/2' - ) + @wizard = CustomWizard::Wizard.create(@template["id"], user) + CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save put '/w/super-mega-fun-wizard/skip.json' expect(response.parsed_body['redirect_to']).to eq('/t/2') end From 582b9d82aab548cbb8fc4046e8c7d8953a04c903 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 18 Jun 2021 11:36:18 -0700 Subject: [PATCH 33/58] FIX: Missing refresh button icon in logs pane --- assets/javascripts/discourse/templates/admin-wizards-logs.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs index 18fd3fdb..b0dd3de6 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs @@ -3,7 +3,7 @@ {{d-button label="refresh" - icon="refresh" + icon="sync" action="refresh" class="refresh"}} From e441588aa3d9f1bbeb3e4abafb642bffc514135d Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 23 Jun 2021 16:13:58 +1000 Subject: [PATCH 34/58] Fix specs and tighten conditional handling --- controllers/custom_wizard/steps.rb | 12 +++- controllers/custom_wizard/wizard.rb | 2 +- lib/custom_wizard/action.rb | 3 + lib/custom_wizard/action_result.rb | 2 +- lib/custom_wizard/builder.rb | 23 ++++--- lib/custom_wizard/submission.rb | 51 ++++++++++---- lib/custom_wizard/wizard.rb | 66 ++++++++++++++++--- plugin.rb | 1 + serializers/custom_wizard/submission.rb | 13 ---- .../custom_wizard/submission_serializer.rb | 16 +++++ spec/components/custom_wizard/action_spec.rb | 3 +- spec/components/custom_wizard/builder_spec.rb | 9 +-- .../custom_wizard/submission_spec.rb | 4 +- spec/components/custom_wizard/wizard_spec.rb | 8 +-- spec/fixtures/step/required_data.json | 4 +- .../admin/submissions_controller_spec.rb | 11 ++-- .../application_controller_spec.rb | 4 +- .../custom_wizard/steps_controller_spec.rb | 5 +- .../custom_wizard/wizard_controller_spec.rb | 2 +- 19 files changed, 167 insertions(+), 72 deletions(-) delete mode 100644 serializers/custom_wizard/submission.rb create mode 100644 serializers/custom_wizard/submission_serializer.rb diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index 9c0fbf92..66ec2da9 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -8,7 +8,7 @@ class CustomWizard::StepsController < ::ApplicationController update[:fields] = {} if params[:fields] - field_ids = @step_template['fields'].map { |f| f['id'] } + field_ids = @builder.wizard.field_ids params[:fields].each do |k, v| update[:fields][k] = v if field_ids.include? k end @@ -36,11 +36,15 @@ class CustomWizard::StepsController < ::ApplicationController if current_step.final? builder.template.actions.each do |action_template| if action_template['run_after'] === 'wizard_completion' - CustomWizard::Action.new( + action_result = CustomWizard::Action.new( action: action_template, wizard: @wizard, - data: current_submission + submission: current_submission ).perform + + if action_result.success? + current_submission = action_result.submission + end end end @@ -54,6 +58,8 @@ class CustomWizard::StepsController < ::ApplicationController result[:final] = true else + current_submission.save + result[:final] = false result[:next_step_id] = current_step.next.id end diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index e682a307..fd93ef15 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -64,7 +64,7 @@ class CustomWizard::WizardController < ::ApplicationController if user && wizard.can_access? submission = wizard.current_submission - if submission && submission.redirect_to + if submission.present? && submission.redirect_to result.merge!(redirect_to: submission.redirect_to) end diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index acb8dafb..1b5770d7 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -30,6 +30,9 @@ class CustomWizard::Action end save_log + + @result.submission = @submission + @result end def mapper_data diff --git a/lib/custom_wizard/action_result.rb b/lib/custom_wizard/action_result.rb index 07c81284..53484ceb 100644 --- a/lib/custom_wizard/action_result.rb +++ b/lib/custom_wizard/action_result.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true class CustomWizard::ActionResult - attr_accessor :success, :handler, :output + attr_accessor :success, :handler, :output, :submission def initialize @success = false diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index b5c04c27..3dd881e7 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -47,8 +47,8 @@ class CustomWizard::Builder step.on_update do |updater| @updater = updater - @submission = @wizard.current_submission || CustomWizard::Submission.new(@wizard) - @submission.fields.merge(@updater.submission) + @submission = @wizard.current_submission + @submission.fields.merge!(@updater.submission) @updater.validate next if @updater.errors.any? @@ -63,7 +63,9 @@ class CustomWizard::Builder @submission.route_to = nil @submission.save + @wizard.update! @updater.result[:redirect_on_next] = route_to if route_to + true else false @@ -72,7 +74,7 @@ class CustomWizard::Builder end end - @wizard.update_step_order! + @wizard.update! @wizard end @@ -89,7 +91,7 @@ class CustomWizard::Builder params[:value] = prefill_field(field_template, step_template) - if !build_opts[:reset] && (submission = @wizard.current_submission) + if !build_opts[:reset] && (submission = @wizard.current_submission).present? params[:value] = submission.fields[field_template['id']] if submission.fields[field_template['id']] end @@ -272,14 +274,15 @@ class CustomWizard::Builder permitted_data = {} submission_key = nil params_key = nil - submission = @wizard.current_submission || CustomWizard::Submission.new(@wizard) + submission = @wizard.current_submission permitted_params.each do |pp| pair = pp['pairs'].first params_key = pair['key'].to_sym submission_key = pair['value'].to_sym - if submission_key && params_key + if submission_key && params_key && params[params_key].present? + submission.permitted_param_keys << submission_key.to_s submission.fields[submission_key] = params[params_key] end end @@ -293,7 +296,7 @@ class CustomWizard::Builder pair['key'].present? && pair['value'].present? end - if pairs.any? && !@wizard.current_submission + if pairs.any? && !@wizard.current_submission.present? step.permitted = false break end @@ -323,11 +326,15 @@ class CustomWizard::Builder if @template.actions.present? @template.actions.each do |action_template| if action_template['run_after'] === updater.step.id - CustomWizard::Action.new( + result = CustomWizard::Action.new( action: action_template, wizard: @wizard, submission: @submission ).perform + + if result.success? + @submission = result.submission + end end end end diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb index 8cb06a4a..b937363d 100644 --- a/lib/custom_wizard/submission.rb +++ b/lib/custom_wizard/submission.rb @@ -2,14 +2,15 @@ class CustomWizard::Submission include ActiveModel::SerializerSupport KEY ||= "submissions" - ACTION_KEY ||= "action" META ||= %w(submitted_at route_to redirect_on_complete redirect_to) attr_reader :id, :user, + :user_id, :wizard - - attr_accessor :fields + + attr_accessor :fields, + :permitted_param_keys META.each do |attr| class_eval { attr_accessor attr } @@ -17,9 +18,10 @@ class CustomWizard::Submission def initialize(wizard, data = {}, user_id = nil) @wizard = wizard + @user_id = user_id if user_id - @user = User.find_by(id: user_id) || OpenStruct.new(deleted: true, id: user_id) + @user = User.find_by(id: user_id) else @user = wizard.user end @@ -31,25 +33,32 @@ class CustomWizard::Submission META.each do |attr| send("#{attr}=", data[attr]) if data[attr] end + + @permitted_param_keys = data['permitted_param_keys'] || [] end def save return nil unless wizard.save_submissions - validate_fields + validate - submissions = self.class.list(wizard, user_id: user.id).select { |s| s.id != self.id } + submission_list = self.class.list(wizard, user_id: user.id) + submissions = submission_list.select { |submission| submission.id != self.id } submissions.push(self) - submission_data = submissions.map { |s| s.fields_and_meta } + submission_data = submissions.map { |submission| data_to_save(submission) } PluginStore.set("#{wizard.id}_#{KEY}", user.id, submission_data) end - def validate_fields - self.fields = fields.select do |key, value| - wizard.field_ids.include?(key) || key.include?(ACTION_KEY) - end + def validate + self.fields = fields.select { |key, value| validate_field_key(key) } end - + + def validate_field_key(key) + wizard.field_ids.include?(key) || + wizard.action_ids.include?(key) || + permitted_param_keys.include?(key) + end + def fields_and_meta result = fields @@ -62,6 +71,24 @@ class CustomWizard::Submission result end + def present? + fields_and_meta.present? + end + + def data_to_save(submission) + data = { + id: submission.id + } + + data.merge!(submission.fields_and_meta) + + if submission.permitted_param_keys.present? + data[:permitted_param_keys] = submission.permitted_param_keys + end + + data + end + def self.get(wizard, user_id) data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).first new(wizard, data, user_id) diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index fff7d159..8f5a897f 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -26,12 +26,16 @@ class CustomWizard::Wizard :needs_groups, :steps, :step_ids, + :field_ids, :first_step, :start, :actions, + :action_ids, :user, :submissions + attr_reader :all_step_ids + def initialize(attrs = {}, user = nil) @user = user attrs = attrs.with_indifferent_access @@ -59,11 +63,22 @@ class CustomWizard::Wizard @first_step = nil @steps = [] + if attrs['steps'].present? - @step_ids = attrs['steps'].map { |s| s['id'] } + @step_ids = @all_step_ids = attrs['steps'].map { |s| s['id'] } + + @field_ids = [] + attrs['steps'].each do |step| + if step['fields'].present? + step['fields'].each do |field| + @field_ids << field['id'] + end + end + end end - @actions = [] + @actions = attrs['actions'] || [] + @action_ids = @actions.map { |a| a['id'] } end def cast_bool(val) @@ -84,7 +99,19 @@ class CustomWizard::Wizard step.index = (steps.size == 1 ? 0 : steps.size) if step.index.nil? end - def update_step_order! + def update! + update_step_order + update_step_ids + update_field_ids + update_action_ids + + @submissions = nil + @current_submission = nil + + true + end + + def update_step_order steps.sort_by!(&:index) steps.each_with_index do |step, index| @@ -103,7 +130,7 @@ class CustomWizard::Wizard step.conditional_final_step = true end - if index === (step_ids.length - 1) + if index === (all_step_ids.length - 1) step.last_step = true end @@ -118,7 +145,7 @@ class CustomWizard::Wizard acting_user_id: user.id, action: ::UserHistory.actions[:custom_wizard_step], context: id, - subject: step_ids + subject: all_step_ids ).order("created_at").last last_completed_step.subject @@ -222,8 +249,25 @@ class CustomWizard::Wizard @groups ||= ::Site.new(Guardian.new(user)).groups end - def field_ids - steps.map { |step| step.fields.map { |field| field.id } }.flatten + def update_step_ids + @step_ids = steps.map(&:id) + end + + def update_field_ids + @field_ids = steps.map { |step| step.fields.map { |field| field.id } }.flatten + end + + def update_action_ids + @action_ids = [] + + @actions.each do |action| + if action['run_after'].blank? || + action['run_after'] === 'wizard_completion' || + step_ids.include?(action['run_after']) + + @action_ids << action['id'] + end + end end def submissions @@ -235,9 +279,9 @@ class CustomWizard::Wizard @current_submission ||= begin if submissions.present? unsubmitted = submissions.select { |submission| !submission.submitted_at } - unsubmitted.present? ? unsubmitted.first : nil + unsubmitted.present? ? unsubmitted.first : CustomWizard::Submission.new(self) else - nil + CustomWizard::Submission.new(self) end end end @@ -252,6 +296,8 @@ class CustomWizard::Wizard current_submission.submitted_at = Time.now.iso8601 current_submission.save end + + update! end def self.create(wizard_id, user = nil) @@ -321,7 +367,7 @@ class CustomWizard::Wizard wizard = self.create(wizard_id, user) if wizard.permitted? - submission = wizard.current_submission || CustomWizard::Submission.new(wizard) + submission = wizard.current_submission submission.redirect_to = url submission.save else diff --git a/plugin.rb b/plugin.rb index ecbf850a..b335971c 100644 --- a/plugin.rb +++ b/plugin.rb @@ -86,6 +86,7 @@ after_initialize do ../serializers/custom_wizard/wizard_step_serializer.rb ../serializers/custom_wizard/wizard_serializer.rb ../serializers/custom_wizard/log_serializer.rb + ../serializers/custom_wizard/submission_serializer.rb ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb ../extensions/extra_locales_controller.rb ../extensions/invites_controller.rb diff --git a/serializers/custom_wizard/submission.rb b/serializers/custom_wizard/submission.rb deleted file mode 100644 index bb8bc288..00000000 --- a/serializers/custom_wizard/submission.rb +++ /dev/null @@ -1,13 +0,0 @@ -class CustomWizard::SubmissionSerializer - attributes :id, - :username, - :fields, - :redirect_to, - :submitted_at - - def username - object.user.deleted ? - I18n.t('admin.wizard.submission.no_user', user_id: object.user.id) : - object.user.username - end -end \ No newline at end of file diff --git a/serializers/custom_wizard/submission_serializer.rb b/serializers/custom_wizard/submission_serializer.rb new file mode 100644 index 00000000..e150273d --- /dev/null +++ b/serializers/custom_wizard/submission_serializer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +class CustomWizard::SubmissionSerializer < ApplicationSerializer + attributes :id, + :username, + :fields, + :submitted_at, + :route_to, + :redirect_on_complete, + :redirect_to + + def username + object.user.present? ? + object.user.username : + I18n.t('admin.wizard.submission.no_user', user_id: object.user_id) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 6f77d617..8b617c39 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -194,11 +194,10 @@ describe CustomWizard::Action do open_composer['post_template'] = "Body & more body & more body".dup wizard = CustomWizard::Wizard.new(@template, user) - action = CustomWizard::Action.new( wizard: wizard, action: open_composer, - data: {} + submission: wizard.current_submission ) action.perform diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 0504a238..8e80d806 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -257,10 +257,7 @@ describe CustomWizard::Builder do it 'is permitted if required data is present' do wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) - data = { - step_1_field_1: 'I am a user submission' - } - CustomWizard::Submission.new(wizard, data).save + CustomWizard::Submission.new(wizard, step_1_field_1: "required").save expect( CustomWizard::Builder.new(@template[:id], user).build @@ -280,7 +277,7 @@ describe CustomWizard::Builder do wizard = CustomWizard::Builder.new(@template[:id], user).build({}, param: 'param_value' ) - expect(wizard.current_submission['saved_param']).to eq('param_value') + expect(wizard.current_submission.fields['saved_param']).to eq('param_value') end end @@ -362,7 +359,7 @@ describe CustomWizard::Builder do it "does not save submissions" do perform_update('step_1', step_1_field_1: 'Text input') - expect(@wizard.current_submission).to eq(nil) + expect(@wizard.current_submission.present?).to eq(false) end end end diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index 798a2969..23855daf 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -29,7 +29,7 @@ describe CustomWizard::Submission do it "saves a user's submission" do expect( - described_class.get(template_json["id"], user.id).fields["step_1_field_1"] + described_class.get(@wizard, user.id).fields["step_1_field_1"] ).to eq("I am a user submission") end @@ -38,6 +38,6 @@ describe CustomWizard::Submission do end it "list submissions by wizard and user" do - expect(described_class.list(@wizard, user).size).to eq(1) + expect(described_class.list(@wizard, user_id: user.id).size).to eq(1) end end \ No newline at end of file diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 376fce2c..1c38ab1f 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -34,7 +34,7 @@ describe CustomWizard::Wizard do template_json['steps'].each do |step_template| @wizard.append_step(step_template['id']) end - @wizard.update_step_order! + @wizard.update! end def progress_step(step_id, acting_user: user, wizard: @wizard) @@ -44,7 +44,7 @@ describe CustomWizard::Wizard do context: wizard.id, subject: step_id ) - @wizard.update_step_order! + @wizard.update! end it "appends steps" do @@ -72,7 +72,7 @@ describe CustomWizard::Wizard do expect(@wizard.steps.first.index).to eq(2) expect(@wizard.steps.last.index).to eq(0) - @wizard.update_step_order! + @wizard.update! expect(@wizard.steps.first.id).to eq("step_3") expect(@wizard.steps.last.id).to eq("step_1") @@ -204,7 +204,7 @@ describe CustomWizard::Wizard do context "submissions" do before do - CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission") + CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save end it "lists the user's submissions" do diff --git a/spec/fixtures/step/required_data.json b/spec/fixtures/step/required_data.json index 9f65d516..7ff8bcaf 100644 --- a/spec/fixtures/step/required_data.json +++ b/spec/fixtures/step/required_data.json @@ -6,9 +6,9 @@ "pairs": [ { "index": 0, - "key": "required_data", + "key": "step_1_field_1", "key_type": "text", - "value": "required_value", + "value": "required", "value_type": "text", "connector": "equal" } diff --git a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb index eae783aa..36296e95 100644 --- a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb @@ -13,11 +13,14 @@ describe CustomWizard::AdminSubmissionsController do ).read) } + let(:template_2) { + temp = template.dup + temp["id"] = "super_mega_fun_wizard_2" + temp + } + before do CustomWizard::Template.save(template, skip_jobs: true) - - template_2 = template.dup - template_2["id"] = "super_mega_fun_wizard_2" CustomWizard::Template.save(template_2, skip_jobs: true) wizard1 = CustomWizard::Wizard.create(template["id"], user1) @@ -33,7 +36,7 @@ describe CustomWizard::AdminSubmissionsController do it "returns a list of wizards" do get "/admin/wizards/submissions.json" - expect(response.parsed_body.length).to eq(3) + expect(response.parsed_body.length).to eq(2) expect(response.parsed_body.first['id']).to eq(template['id']) end diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb index e8b45c48..9f5a1a60 100644 --- a/spec/requests/custom_wizard/application_controller_spec.rb +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -39,8 +39,8 @@ describe ApplicationController do it "saves original destination of user" do get '/', headers: { 'REFERER' => "/t/2" } expect( - CustomWizard::Wizard.submissions(@template['id'], user) - .first['redirect_to'] + CustomWizard::Wizard.create(@template['id'], user).submissions + .first.fields['redirect_to'] ).to eq("/t/2") end diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index ab705cac..5da75d8d 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -175,8 +175,11 @@ describe CustomWizard::StepsController do wizard_id = response.parsed_body['wizard']['id'] wizard = CustomWizard::Wizard.create(wizard_id, user) - group_name = wizard.current_submission.fields['action_9'] + + group_name = wizard.submissions.first.fields['action_9'] group = Group.find_by(name: group_name) + + expect(group.present?).to eq(true) expect(group.full_name).to eq("My cool group") end diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index 7a977539..f2000bda 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -72,7 +72,7 @@ describe CustomWizard::WizardController do it 'skip response contains a redirect_to if in users submissions' do @wizard = CustomWizard::Wizard.create(@template["id"], user) - CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save + CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save put '/w/super-mega-fun-wizard/skip.json' expect(response.parsed_body['redirect_to']).to eq('/t/2') end From 098e8418fbe90c45365988f46f6938a7c6663c02 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 23 Jun 2021 16:15:17 +1000 Subject: [PATCH 35/58] Apply rubocop --- controllers/custom_wizard/admin/submissions.rb | 2 +- lib/custom_wizard/submission.rb | 5 +++-- serializers/custom_wizard/submission_serializer.rb | 2 +- spec/components/custom_wizard/submission_spec.rb | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/controllers/custom_wizard/admin/submissions.rb b/controllers/custom_wizard/admin/submissions.rb index 682c4030..4cb2a0e4 100644 --- a/controllers/custom_wizard/admin/submissions.rb +++ b/controllers/custom_wizard/admin/submissions.rb @@ -23,7 +23,7 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController content_type: "application/json", disposition: "attachment" end - + protected def ordered_submissions diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb index b937363d..47d2581a 100644 --- a/lib/custom_wizard/submission.rb +++ b/lib/custom_wizard/submission.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true class CustomWizard::Submission include ActiveModel::SerializerSupport @@ -23,7 +24,7 @@ class CustomWizard::Submission if user_id @user = User.find_by(id: user_id) else - @user = wizard.user + @user = wizard.user end data = data.with_indifferent_access @@ -113,4 +114,4 @@ class CustomWizard::Submission result end -end \ No newline at end of file +end diff --git a/serializers/custom_wizard/submission_serializer.rb b/serializers/custom_wizard/submission_serializer.rb index e150273d..52f0cb32 100644 --- a/serializers/custom_wizard/submission_serializer.rb +++ b/serializers/custom_wizard/submission_serializer.rb @@ -13,4 +13,4 @@ class CustomWizard::SubmissionSerializer < ApplicationSerializer object.user.username : I18n.t('admin.wizard.submission.no_user', user_id: object.user_id) end -end \ No newline at end of file +end diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index 23855daf..a8c33861 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -40,4 +40,4 @@ describe CustomWizard::Submission do it "list submissions by wizard and user" do expect(described_class.list(@wizard, user_id: user.id).size).to eq(1) end -end \ No newline at end of file +end From 39ee61b42283a467731f68102435914963d28551 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 23 Jun 2021 16:17:18 +1000 Subject: [PATCH 36/58] Update coverage --- coverage/.last_run.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage/.last_run.json b/coverage/.last_run.json index 3e7f27f6..2d4d0378 100644 --- a/coverage/.last_run.json +++ b/coverage/.last_run.json @@ -1,5 +1,5 @@ { "result": { - "line": 90.52 + "line": 91.83 } } From 3d9f6aac9894ff3bf7a8be10ac2c8d3e4a19109d Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 23 Jun 2021 17:02:21 +1000 Subject: [PATCH 37/58] Ensure data is not nil --- lib/custom_wizard/submission.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb index 47d2581a..a1f93d82 100644 --- a/lib/custom_wizard/submission.rb +++ b/lib/custom_wizard/submission.rb @@ -27,7 +27,7 @@ class CustomWizard::Submission @user = wizard.user end - data = data.with_indifferent_access + data = (data || {}).with_indifferent_access @id = data['id'] || SecureRandom.hex(12) @fields = data.except(META + ['id']) || {} From 5a6856fcff89c8cf54c63f22155c5e0cc34c749b Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Fri, 25 Jun 2021 11:45:22 +1000 Subject: [PATCH 38/58] COMPATIBILITY: categories are being loaded differently on Site model See https://github.com/discourse/discourse/commit/0e4b8c5318569ef7e7a111563709699e3b9ce219 --- serializers/custom_wizard/wizard_serializer.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/serializers/custom_wizard/wizard_serializer.rb b/serializers/custom_wizard/wizard_serializer.rb index f858c195..7a162ba5 100644 --- a/serializers/custom_wizard/wizard_serializer.rb +++ b/serializers/custom_wizard/wizard_serializer.rb @@ -8,11 +8,11 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer :completed, :required, :permitted, - :uncategorized_category_id + :uncategorized_category_id, + :categories has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects has_one :user, serializer: ::BasicUserSerializer, embed: :objects - has_many :categories, serializer: ::BasicCategorySerializer, embed: :objects has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects def completed @@ -56,4 +56,8 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer def include_uncategorized_category_id? object.needs_categories end + + def categories + object.categories.map { |c| c.to_h } + end end From 7bfa0aff707f5f456478722b51fda15a7b340fdc Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 26 Jun 2021 17:45:19 +1000 Subject: [PATCH 39/58] Apply format to new picker in wizard-date-input (#123) --- .../components/wizard-date-input.js.es6 | 41 ++++++++++++++++++- .../components/wizard-field-date.hbs | 1 + 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index 93c7ed2d..bb11b655 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -1,3 +1,42 @@ import DateInput from "discourse/components/date-input"; +import loadScript from "discourse/lib/load-script"; +import discourseComputed from "discourse-common/utils/decorators"; +import I18n from "I18n"; +/* global Pikaday:true */ -export default DateInput.extend(); +export default DateInput.extend({ + useNativePicker: false, + + @discourseComputed() + placeholder() { + return this.format; + }, + + _loadPikadayPicker(container) { + return loadScript("/javascripts/pikaday.js").then(() => { + let defaultOptions = { + field: this.element.querySelector(".date-picker"), + container: container || this.element.querySelector(".picker-container"), + bound: container === null, + format: this.format, + firstDay: 1, + i18n: { + previousMonth: I18n.t("dates.previous_month"), + nextMonth: I18n.t("dates.next_month"), + months: moment.months(), + weekdays: moment.weekdays(), + weekdaysShort: moment.weekdaysShort(), + }, + onSelect: (date) => this._handleSelection(date), + }; + + if (this.relativeDate) { + defaultOptions = Object.assign({}, defaultOptions, { + minDate: moment(this.relativeDate).toDate(), + }); + } + + return new Pikaday(Object.assign({}, defaultOptions, this._opts())); + }); + }, +}); diff --git a/assets/javascripts/wizard/templates/components/wizard-field-date.hbs b/assets/javascripts/wizard/templates/components/wizard-field-date.hbs index 4ac6571b..ed4d14e3 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-date.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-date.hbs @@ -2,4 +2,5 @@ date=date onChange=(action "onChange") tabindex=field.tabindex + format=field.format }} From f80f40d6b3d5249ed43c63aa6576439609a18000 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 26 Jun 2021 17:45:33 +1000 Subject: [PATCH 40/58] Apply consistent date validation on the server (using v8) (#124) * Apply consistent date validation on the server (using v8) * Variable fix * added specs to verify date/time field validation * minor text change Co-authored-by: Faizaan Gagan --- lib/custom_wizard/validators/update.rb | 19 +++++---- .../custom_wizard/update_validator_spec.rb | 40 +++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/custom_wizard/validators/update.rb b/lib/custom_wizard/validators/update.rb index d84b448a..93d4955f 100644 --- a/lib/custom_wizard/validators/update.rb +++ b/lib/custom_wizard/validators/update.rb @@ -52,7 +52,7 @@ class ::CustomWizard::UpdateValidator @updater.errors.add(field_id, I18n.t('wizard.field.invalid_file', label: label, types: file_types)) end - if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value) + if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value, format) @updater.errors.add(field_id, I18n.t('wizard.field.invalid_date')) end @@ -88,13 +88,8 @@ class ::CustomWizard::UpdateValidator .include?(File.extname(value['original_filename'])[1..-1]) end - def validate_date(value) - begin - Date.parse(value) - true - rescue ArgumentError - false - end + def validate_date(value, format) + v8.eval("moment('#{value}', '#{format}', true).isValid()") end def validate_time(value) @@ -126,4 +121,12 @@ class ::CustomWizard::UpdateValidator def standardise_boolean(value) ActiveRecord::Type::Boolean.new.cast(value) end + + def v8 + return @ctx if @ctx + + @ctx = PrettyText.v8 + PrettyText.ctx_load(@ctx, "#{Rails.root}/vendor/assets/javascripts/moment.js") + @ctx + end end diff --git a/spec/components/custom_wizard/update_validator_spec.rb b/spec/components/custom_wizard/update_validator_spec.rb index 81212b4b..e7658d8c 100644 --- a/spec/components/custom_wizard/update_validator_spec.rb +++ b/spec/components/custom_wizard/update_validator_spec.rb @@ -132,4 +132,44 @@ describe CustomWizard::UpdateValidator do updater.errors.messages[:step_2_field_6].first ).to eq(nil) end + + it 'validates date fields' do + @template[:steps][1][:fields][0][:format] = "DD-MM-YYYY" + CustomWizard::Template.save(@template) + + updater = perform_validation('step_2', step_2_field_1: '13-11-2021') + expect( + updater.errors.messages[:step_2_field_1].first + ).to eq(nil) + end + + it 'doesn\'t validate date field if the format is not respected' do + @template[:steps][1][:fields][0][:format] = "MM-DD-YYYY" + CustomWizard::Template.save(@template) + + updater = perform_validation('step_2', step_2_field_1: '13-11-2021') + expect( + updater.errors.messages[:step_2_field_1].first + ).to eq(I18n.t('wizard.field.invalid_date')) + end + + it 'validates date time fields' do + @template[:steps][1][:fields][2][:format] = "DD-MM-YYYY HH:mm:ss" + CustomWizard::Template.save(@template) + + updater = perform_validation('step_2', step_2_field_3: '13-11-2021 09:15:00') + expect( + updater.errors.messages[:step_2_field_3].first + ).to eq(nil) + end + + it 'doesn\'t validate date time field if the format is not respected' do + @template[:steps][1][:fields][2][:format] = "MM-DD-YYYY HH:mm:ss" + CustomWizard::Template.save(@template) + + updater = perform_validation('step_2', step_2_field_3: '13-11-2021 09:15') + expect( + updater.errors.messages[:step_2_field_3].first + ).to eq(I18n.t('wizard.field.invalid_date')) + end end From be86c77a77e20bafd9281d1acd8cb21e98fcb38d Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 26 Jun 2021 19:07:08 +0530 Subject: [PATCH 41/58] IMPROVE: cron schedule and use step output (#126) * use step output for populating repo name * schedule cron every 12 hours --- .github/workflows/plugin-tests.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 84e055ca..fcf1a1d0 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -7,7 +7,7 @@ on: - main pull_request: schedule: - - cron: '0 0 * * *' + - cron: '0 */12 * * *' jobs: build: @@ -53,26 +53,27 @@ jobs: repository: discourse/discourse fetch-depth: 1 - - run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV - shell: bash + - name: Fetch Repo Name + id: repo-name + run: echo "::set-output name=value::$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" - name: Install plugin uses: actions/checkout@v2 with: - path: plugins/${{ env.REPOSITORY_NAME }} + path: plugins/${{ steps.repo-name.outputs.value }} fetch-depth: 1 - name: Check spec existence id: check_spec uses: andstor/file-existence-action@v1 with: - files: "plugins/${{ env.REPOSITORY_NAME }}/spec" + files: "plugins/${{ steps.repo-name.outputs.value }}/spec" - name: Check qunit existence id: check_qunit uses: andstor/file-existence-action@v1 with: - files: "plugins/${{ env.REPOSITORY_NAME }}/test/javascripts" + files: "plugins/${{ steps.repo-name.outputs.value }}/test/javascripts" - name: Setup Git run: | @@ -105,7 +106,7 @@ jobs: - name: Lint English locale if: matrix.build_type == 'backend' - run: bundle exec ruby script/i18n_lint.rb "plugins/${{ env.REPOSITORY_NAME }}/locales/{client,server}.en.yml" + run: bundle exec ruby script/i18n_lint.rb "plugins/${{ steps.repo-name.outputs.value }}/locales/{client,server}.en.yml" - name: Get yarn cache directory id: yarn-cache-dir @@ -130,9 +131,9 @@ jobs: - name: Plugin RSpec with Coverage if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' - run: SIMPLECOV=1 bin/rake plugin:spec[${{ env.REPOSITORY_NAME }}] + run: SIMPLECOV=1 bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] - name: Plugin QUnit if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' - run: bundle exec rake plugin:qunit['${{ env.REPOSITORY_NAME }}','1200000'] + run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000'] timeout-minutes: 30 From 9ec2da5a9f34dcf4dcdfe2c2cbf70e91f15c1ed8 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sun, 27 Jun 2021 16:58:26 +0530 Subject: [PATCH 42/58] DEV: fix compatibility with core theme arch changes --- assets/javascripts/wizard/lib/wizard-i18n.js.es6 | 2 +- controllers/custom_wizard/wizard.rb | 10 +++++----- views/layouts/wizard.html.erb | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 index 17242e58..26247bc0 100644 --- a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 +++ b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 @@ -1,7 +1,7 @@ import I18n from "I18n"; const getThemeId = () => { - let themeId = parseInt($("meta[name=discourse_theme_ids]")[0].content, 10); + let themeId = parseInt(document.querySelector("meta[name=discourse_theme_id]").content, 10); if (!isNaN(themeId)) { return themeId.toString(); diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 9670fd62..bac15fa9 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -6,7 +6,7 @@ class CustomWizard::WizardController < ::ApplicationController before_action :ensure_plugin_enabled helper_method :wizard_page_title - helper_method :wizard_theme_ids + helper_method :wizard_theme_id helper_method :wizard_theme_lookup helper_method :wizard_theme_translations_lookup @@ -20,16 +20,16 @@ class CustomWizard::WizardController < ::ApplicationController wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') end - def wizard_theme_ids - wizard ? [wizard.theme_id] : nil + def wizard_theme_id + wizard ? wizard.theme_id : nil end def wizard_theme_lookup(name) - Theme.lookup_field(wizard_theme_ids, mobile_view? ? :mobile : :desktop, name) + Theme.lookup_field(wizard_theme_id, mobile_view? ? :mobile : :desktop, name) end def wizard_theme_translations_lookup - Theme.lookup_field(wizard_theme_ids, :translations, I18n.locale) + Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) end def index diff --git a/views/layouts/wizard.html.erb b/views/layouts/wizard.html.erb index efa09734..16d119b7 100644 --- a/views/layouts/wizard.html.erb +++ b/views/layouts/wizard.html.erb @@ -4,8 +4,8 @@ <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> <%= discourse_stylesheet_link_tag :wizard_custom %> - <%- if wizard_theme_ids.present? %> - <%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme), theme_ids: wizard_theme_ids %> + <%- if wizard_theme_id.present? %> + <%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme), theme_id: wizard_theme_id %> <%- end %> <%= preload_script "locales/#{I18n.locale}" %> @@ -29,7 +29,7 @@ <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> - "> + <%= render partial: "layouts/head" %> From 13faa849d8f62c71d73563b30e7aed2d015caac5 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sun, 27 Jun 2021 17:02:19 +0530 Subject: [PATCH 43/58] minor formatting --- assets/javascripts/wizard/lib/wizard-i18n.js.es6 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 index 26247bc0..fdefab77 100644 --- a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 +++ b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 @@ -1,7 +1,10 @@ import I18n from "I18n"; const getThemeId = () => { - let themeId = parseInt(document.querySelector("meta[name=discourse_theme_id]").content, 10); + let themeId = parseInt( + document.querySelector("meta[name=discourse_theme_id]").content, + 10 + ); if (!isNaN(themeId)) { return themeId.toString(); From 0bd4dbd08be2cc5d67833e90389958dcb2dbe23c Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Tue, 29 Jun 2021 03:49:28 -0400 Subject: [PATCH 44/58] DEV: Switch to CSS custom properties for colors (#127) --- assets/stylesheets/common/wizard-admin.scss | 28 +++++++++---------- assets/stylesheets/common/wizard-api.scss | 6 ++-- assets/stylesheets/common/wizard-manager.scss | 12 ++++---- assets/stylesheets/common/wizard-mapper.scss | 24 ++++++++-------- assets/stylesheets/wizard/custom/badges.scss | 4 +-- .../stylesheets/wizard/custom/composer.scss | 6 ++-- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 9c2838eb..2d6c9a6a 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -15,7 +15,7 @@ } .wizard-message { - background-color: $primary-low; + background-color: var(--primary-low); width: 100%; padding: 10px; box-sizing: border-box; @@ -37,7 +37,7 @@ } a + a { - border-left: 1px solid $primary-medium; + border-left: 1px solid var(--primary-medium); padding-left: 5px; margin-left: 5px; } @@ -89,7 +89,7 @@ .wizard-settings-parent { padding: 20px; - border: 1px solid $primary-low; + border: 1px solid var(--primary-low); } .wizard-settings-group { @@ -115,7 +115,7 @@ .wizard-custom-field { background: transparent; - background-color: dark-light-diff($primary, $secondary, 96%, -65%); + background-color: var(--primary-very-low); padding: 20px; } @@ -182,7 +182,7 @@ a { padding: 6px 12px; font-size: 1rem; - background-color: $primary-low; + background-color: var(--primary-low); } button { @@ -263,7 +263,7 @@ } input[disabled] { - background-color: $primary-low; + background-color: var(--primary-low); cursor: not-allowed; } @@ -434,8 +434,8 @@ display: none; margin: 0 0 10px 0; padding: 10px; - background-color: $secondary; - border: 1px solid $primary-medium; + background-color: var(--secondary); + border: 1px solid var(--primary-medium); max-width: 100%; &.force-preview { @@ -454,17 +454,17 @@ .btn { margin-right: 10px; - color: $primary; + color: var(--primary); &:hover { - color: $secondary; + color: var(--secondary); } } .wizard-editor-gutter-popover { position: absolute; padding: 10px; - background-color: $secondary; + background-color: var(--secondary); box-shadow: shadow("card"); z-index: 200; top: 40px; @@ -502,7 +502,7 @@ width: 100px; display: inline-block; vertical-align: top; - background-color: $primary-low; + background-color: var(--primary-low); margin-right: 10px; margin-left: 3px; } @@ -587,8 +587,8 @@ .add-mapper-input .btn, .btn-after-time, .wizard-editor-gutter .btn { - background-color: $secondary; - border: 1px solid $primary-medium; + background-color: var(--secondary); + border: 1px solid var(--primary-medium); } .admin-wizards-custom-fields { diff --git a/assets/stylesheets/common/wizard-api.scss b/assets/stylesheets/common/wizard-api.scss index 4fdd0fc2..9d0ad261 100644 --- a/assets/stylesheets/common/wizard-api.scss +++ b/assets/stylesheets/common/wizard-api.scss @@ -48,7 +48,7 @@ .wizard-api-authentication { display: flex; - background-color: $primary-very-low; + background-color: var(--primary-very-low); padding: 20px; margin-bottom: 20px; @@ -68,7 +68,7 @@ } .status { - border-left: 1px solid $primary; + border-left: 1px solid var(--primary); margin-left: 20px; padding-left: 20px; width: 50%; @@ -89,7 +89,7 @@ } .wizard-api-endpoints { - background-color: $primary-very-low; + background-color: var(--primary-very-low); padding: 20px; margin-bottom: 20px; diff --git a/assets/stylesheets/common/wizard-manager.scss b/assets/stylesheets/common/wizard-manager.scss index 135ddb8c..a3b30c98 100644 --- a/assets/stylesheets/common/wizard-manager.scss +++ b/assets/stylesheets/common/wizard-manager.scss @@ -16,13 +16,13 @@ #import-button:enabled, #export-button:enabled { - background-color: $tertiary; - color: $secondary; + background-color: var(--tertiary); + color: var(--secondary); } #destroy-button:enabled { - background-color: $danger; - color: $secondary; + background-color: var(--danger); + color: var(--secondary); } } @@ -32,13 +32,13 @@ .filename { padding: 0 10px; - border: 1px solid $primary; + border: 1px solid var(--primary); display: inline-flex; height: 28px; line-height: 28px; a { - color: $primary; + color: var(--primary); margin-right: 5px; display: inline-flex; align-items: center; diff --git a/assets/stylesheets/common/wizard-mapper.scss b/assets/stylesheets/common/wizard-mapper.scss index 69e9c88e..f56a2cab 100644 --- a/assets/stylesheets/common/wizard-mapper.scss +++ b/assets/stylesheets/common/wizard-mapper.scss @@ -22,7 +22,7 @@ width: min-content; margin-bottom: 10px; height: 20px; - border: 2px solid $primary-low; + border: 2px solid var(--primary-low); } } @@ -33,8 +33,8 @@ position: relative; padding: 25px 7px 7px 7px; margin-bottom: 10px; - background: rgba($secondary, 0.5); - border: 2px solid $primary-low; + background: rgba(var(--secondary-rgb), 0.5); + border: 2px solid var(--primary-low); .d-icon { text-align: center; @@ -45,7 +45,7 @@ } input[disabled] { - background-color: $primary-low; + background-color: var(--primary-low); border-color: #ddd; } @@ -62,10 +62,10 @@ align-items: center; justify-content: center; transform: translateY(-50%); - background: $secondary; + background: var(--secondary); border-radius: 50%; font-size: 0.8em; - border: 2px solid $primary-low; + border: 2px solid var(--primary-low); } &.association, @@ -89,8 +89,8 @@ &.single { height: 28px; - background: $secondary; - border: 1px solid $primary-medium; + background: var(--secondary); + border: 1px solid var(--primary-medium); display: flex; align-items: center; justify-content: center; @@ -126,7 +126,7 @@ } .type-selector a { - color: $primary; + color: var(--primary); margin-right: 4px; display: flex; align-items: center; @@ -150,11 +150,11 @@ box-shadow: shadow("dropdown"); position: absolute; display: flex; - background: $secondary; + background: var(--secondary); z-index: 200; padding: 5px 7px; flex-direction: column; - border: 1px solid $primary-low; + border: 1px solid var(--primary-low); } .value-list .remove-value-btn { @@ -162,7 +162,7 @@ border: none; .d-icon { - color: $primary; + color: var(--primary); } } diff --git a/assets/stylesheets/wizard/custom/badges.scss b/assets/stylesheets/wizard/custom/badges.scss index ca9c0450..53a869c0 100644 --- a/assets/stylesheets/wizard/custom/badges.scss +++ b/assets/stylesheets/wizard/custom/badges.scss @@ -31,7 +31,7 @@ overflow: hidden; text-overflow: ellipsis; .extra-info-wrapper & { - color: $header-primary; + color: var(--header_primary); } } @@ -108,7 +108,7 @@ text-overflow: ellipsis; .extra-info-wrapper & { - color: $header-primary; + color: var(--header_primary); } } diff --git a/assets/stylesheets/wizard/custom/composer.scss b/assets/stylesheets/wizard/custom/composer.scss index 4f5ff442..4e561c61 100644 --- a/assets/stylesheets/wizard/custom/composer.scss +++ b/assets/stylesheets/wizard/custom/composer.scss @@ -199,11 +199,11 @@ left: 0; width: 100%; height: 100%; - background-color: rgba($primary, 0.8); + background-color: rgba(var(--primary-rgb), 0.8); } .wizard-composer-hyperlink-contents { - background-color: $secondary; + background-color: var(--secondary); padding: 20px; h3 { @@ -227,7 +227,7 @@ bottom: 1px; right: 1px; padding: 10px; - background-color: $secondary; + background-color: var(--secondary); } .bottom-bar { From d8f6f00e6030c70f69d5d47efcf3dee093e335db Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Thu, 1 Jul 2021 11:25:31 +0530 Subject: [PATCH 45/58] FIX: add csrf token to all wizard ajax requests in dev (#129) * FIX: add csrf token to all wizard ajax requests in dev * fix formatting * simplified code * Update field.rb --- .../wizard/initializers/custom-wizard.js.es6 | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 index ab7c9146..24102bed 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 +++ b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 @@ -26,7 +26,11 @@ export default { const setDefaultOwner = requirejs("discourse-common/lib/get-owner") .setDefaultOwner; const messageBus = requirejs("message-bus-client").default; - + const getToken = requirejs("wizard/lib/ajax").getToken; + const setEnvironment = requirejs("discourse-common/config/environment") + .setEnvironment; + const isDevelopment = requirejs("discourse-common/config/environment") + .isDevelopment; const container = app.__container__; Discourse.Model = EmberObject.extend(); Discourse.__container__ = container; @@ -89,6 +93,7 @@ export default { const session = container.lookup("session:main"); const setupData = document.getElementById("data-discourse-setup").dataset; session.set("highlightJsPath", setupData.highlightJsPath); + setEnvironment(setupData.environment); Router.reopen({ rootURL: getUrl("/w/"), @@ -107,5 +112,11 @@ export default { }, model() {}, }); + + $.ajaxPrefilter(function (_, __, jqXHR) { + if (isDevelopment()) { + jqXHR.setRequestHeader("X-CSRF-Token", getToken()); + } + }); }, }; From e235a66b0190bef28036a3a2cee39b2a87c29d13 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 3 Jul 2021 13:06:06 +0530 Subject: [PATCH 46/58] FIX: avoid translating pre-translated placeholder --- .../wizard/templates/components/wizard-composer-editor.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs index 1e7c27df..c4bf1c74 100644 --- a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs @@ -1,7 +1,7 @@ {{d-editor tabindex=field.tabindex value=composer.reply - placeholder=replyPlaceholder + placeholderTranslated=replyPlaceholder previewUpdated=(action "previewUpdated") markdownOptions=markdownOptions extraButtons=(action "extraButtons") From c2e51cba3a86cad4ac92d0fe3d9018ccef13b930 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 3 Jul 2021 13:23:33 +0530 Subject: [PATCH 47/58] FIX: hide placeholder on field focus --- assets/stylesheets/wizard/custom/field.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/assets/stylesheets/wizard/custom/field.scss b/assets/stylesheets/wizard/custom/field.scss index e5a7f5e2..f2bb3aa0 100644 --- a/assets/stylesheets/wizard/custom/field.scss +++ b/assets/stylesheets/wizard/custom/field.scss @@ -162,4 +162,15 @@ .text-field input { margin-bottom: 0; } + + .text-field, + .textarea-field, + .composer-field { + input[type="text"], + textarea { + &:focus::placeholder { + color: transparent; + } + } + } } From 21ac87cddaa0531f8073139059311d5fff7f0d9e Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 3 Jul 2021 15:41:49 +0530 Subject: [PATCH 48/58] FEATURE: placeholder setting for text type fields --- .../templates/components/wizard-custom-field.hbs | 13 +++++++++++++ config/locales/client.en.yml | 1 + controllers/custom_wizard/admin/wizard.rb | 1 + lib/custom_wizard/builder.rb | 2 +- lib/custom_wizard/field.rb | 13 +++++++++---- .../custom_wizard/wizard_field_serializer.rb | 1 + 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 2f0b20ba..001acbe5 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -111,6 +111,19 @@ checked=field.char_counter}} + +
+
+ +
+ +
+ {{input + type="text" + class="medium" + value=field.placeholder}} +
+
{{/if}} {{#if isUpload}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 43b86698..a21f79bc 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -173,6 +173,7 @@ en: max_length_placeholder: "Maximum length in characters" char_counter: "Character Counter" char_counter_placeholder: "Display Character Counter" + field_placeholder: "Field Placeholder" file_types: "File Types" limit: "Limit" property: "Property" diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index 0af55d95..9b128d4d 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -109,6 +109,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController :format, :limit, :property, + :placeholder, prefill: mapped_params, content: mapped_params, condition: mapped_params, diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index a9fc6263..da10a100 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -86,7 +86,7 @@ class CustomWizard::Builder required: field_template['required'] } - %w(label description image key validations min_length max_length char_counter).each do |key| + %w(label description image key validations min_length max_length char_counter placeholder).each do |key| params[key.to_sym] = field_template[key] if field_template[key] end diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index a8160b4c..36a800c1 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -20,7 +20,8 @@ class CustomWizard::Field :format, :limit, :property, - :content + :content, + :placeholder attr_accessor :index, :step @@ -44,6 +45,7 @@ class CustomWizard::Field @limit = attrs[:limit] @property = attrs[:property] @content = attrs[:content] + @placeholder = attrs[:placeholder] end def label @@ -63,18 +65,21 @@ class CustomWizard::Field max_length: nil, prefill: nil, char_counter: nil, - validations: nil + validations: nil, + placeholder: nil }, textarea: { min_length: nil, max_length: nil, prefill: nil, - char_counter: nil + char_counter: nil, + placeholder: nil }, composer: { min_length: nil, max_length: nil, - char_counter: nil + char_counter: nil, + placeholder: nil }, text_only: {}, date: { diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/serializers/custom_wizard/wizard_field_serializer.rb index 42d5eba0..fe22fdeb 100644 --- a/serializers/custom_wizard/wizard_field_serializer.rb +++ b/serializers/custom_wizard/wizard_field_serializer.rb @@ -71,6 +71,7 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer end def placeholder + return object.placeholder if object.placeholder.present? I18n.t("#{object.key || i18n_key}.placeholder", default: '') end From d47950733d9022f1f3e8a30178cc8bc44ea846b9 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 5 Jul 2021 11:52:29 +0530 Subject: [PATCH 49/58] enabled interpolation and templating --- lib/custom_wizard/builder.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index da10a100..fab99446 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -86,7 +86,7 @@ class CustomWizard::Builder required: field_template['required'] } - %w(label description image key validations min_length max_length char_counter placeholder).each do |key| + %w(label description image key validations min_length max_length char_counter).each do |key| params[key.to_sym] = field_template[key] if field_template[key] end @@ -187,6 +187,16 @@ class CustomWizard::Builder ) end + if field_template['placeholder'].present? + params[:placeholder] = mapper.interpolate( + field_template['placeholder'], + user: true, + value: true, + wizard: true, + template: true + ) + end + field = step.add_field(params) end From c9f90fb8ced10b177125a094545cc6ed079a5fdc Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 7 Jul 2021 11:50:56 +0530 Subject: [PATCH 50/58] FIX: text length validation bug --- lib/custom_wizard/validators/update.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/custom_wizard/validators/update.rb b/lib/custom_wizard/validators/update.rb index 93d4955f..c722a763 100644 --- a/lib/custom_wizard/validators/update.rb +++ b/lib/custom_wizard/validators/update.rb @@ -32,11 +32,11 @@ class ::CustomWizard::UpdateValidator @updater.errors.add(field_id, I18n.t('wizard.field.required', label: label)) end - if min_length && value.is_a?(String) && value.strip.length < min_length.to_i + if min_length.present? && value.is_a?(String) && value.strip.length < min_length.to_i @updater.errors.add(field_id, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i)) end - if max_length && value.is_a?(String) && value.strip.length > max_length.to_i + if max_length.present? && value.is_a?(String) && value.strip.length > max_length.to_i @updater.errors.add(field_id, I18n.t('wizard.field.too_long', label: label, max: max_length.to_i)) end From e4df646022b24765e6d4c90075472359de7501f9 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Thu, 8 Jul 2021 13:04:03 +0530 Subject: [PATCH 51/58] FIX: clear site model cache to prevent spec failure (#132) --- spec/components/custom_wizard/wizard_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 9808f32f..ebb3ed7f 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -199,6 +199,7 @@ describe CustomWizard::Wizard do end it "lists the site categories" do + Site.clear_cache expect(@wizard.categories.length).to eq(1) end From d88d2b7d97034717197c4f1ae52cd5f5e534589d Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Sat, 10 Jul 2021 11:33:00 +0530 Subject: [PATCH 52/58] converted placeholder input to a textarea (#133) --- .../discourse/templates/components/wizard-custom-field.hbs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 001acbe5..2238ae98 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -118,9 +118,8 @@
- {{input - type="text" - class="medium" + {{textarea + name="field_placeholder" value=field.placeholder}}
From 56f58414b3e24f398a525824760e8934c9a102b3 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 12 Jul 2021 15:53:58 +0800 Subject: [PATCH 53/58] Remove meta keys from fields attribute and update submissions ui to handle new submission structure --- .../admin-wizards-submissions-show.js.es6 | 26 ++++++++++++++----- lib/custom_wizard/submission.rb | 3 ++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 index abcaeb9e..7d8e2cf5 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 @@ -1,17 +1,25 @@ import CustomWizard from "../models/custom-wizard"; import DiscourseRoute from "discourse/routes/discourse"; +const excludedMetaFields = [ + 'route_to', + 'redirect_on_complete', + 'redirect_to' +]; + export default DiscourseRoute.extend({ model(params) { return CustomWizard.submissions(params.wizardId); }, setupController(controller, model) { - if (model.submissions) { - let fields = []; + if (model && model.submissions) { + let fields = [ + 'username' + ]; model.submissions.forEach((s) => { - Object.keys(s).forEach((k) => { - if (fields.indexOf(k) < 0) { + Object.keys(s.fields).forEach((k) => { + if (!excludedMetaFields.includes(k) && fields.indexOf(k) < 0) { fields.push(k); } }); @@ -19,9 +27,13 @@ export default DiscourseRoute.extend({ let submissions = []; model.submissions.forEach((s) => { - let submission = {}; - fields.forEach((f) => { - submission[f] = s[f]; + let submission = { + username: s.username + }; + Object.keys(s.fields).forEach((f) => { + if (fields.includes(f)) { + submission[f] = s.fields[f]; + } }); submissions.push(submission); }); diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb index a1f93d82..e50cb259 100644 --- a/lib/custom_wizard/submission.rb +++ b/lib/custom_wizard/submission.rb @@ -29,7 +29,8 @@ class CustomWizard::Submission data = (data || {}).with_indifferent_access @id = data['id'] || SecureRandom.hex(12) - @fields = data.except(META + ['id']) || {} + non_field_keys = META + ['id'] + @fields = data.except(*non_field_keys) || {} META.each do |attr| send("#{attr}=", data[attr]) if data[attr] From dfd382cd8affc344ff4049cec695580df35dc8b1 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 12 Jul 2021 16:01:52 +0800 Subject: [PATCH 54/58] Apply prettier --- .../routes/admin-wizards-submissions-show.js.es6 | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 index 7d8e2cf5..73168ff3 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 @@ -1,11 +1,7 @@ import CustomWizard from "../models/custom-wizard"; import DiscourseRoute from "discourse/routes/discourse"; -const excludedMetaFields = [ - 'route_to', - 'redirect_on_complete', - 'redirect_to' -]; +const excludedMetaFields = ["route_to", "redirect_on_complete", "redirect_to"]; export default DiscourseRoute.extend({ model(params) { @@ -14,9 +10,7 @@ export default DiscourseRoute.extend({ setupController(controller, model) { if (model && model.submissions) { - let fields = [ - 'username' - ]; + let fields = ["username"]; model.submissions.forEach((s) => { Object.keys(s.fields).forEach((k) => { if (!excludedMetaFields.includes(k) && fields.indexOf(k) < 0) { @@ -28,7 +22,7 @@ export default DiscourseRoute.extend({ let submissions = []; model.submissions.forEach((s) => { let submission = { - username: s.username + username: s.username, }; Object.keys(s.fields).forEach((f) => { if (fields.includes(f)) { From d602281a6a8849b0e9284015c70e72967c01e52d Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 12 Jul 2021 16:39:37 +0800 Subject: [PATCH 55/58] Fix failing test --- spec/requests/custom_wizard/application_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb index 9f5a1a60..0835f246 100644 --- a/spec/requests/custom_wizard/application_controller_spec.rb +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -40,7 +40,7 @@ describe ApplicationController do get '/', headers: { 'REFERER' => "/t/2" } expect( CustomWizard::Wizard.create(@template['id'], user).submissions - .first.fields['redirect_to'] + .first.redirect_to ).to eq("/t/2") end From ca10ae797a2ba1ca111b7f46db1b8e65c349d671 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 12 Jul 2021 17:05:05 +0530 Subject: [PATCH 56/58] FEATURE: new field type Composer Preview (#130) * WIP * composer preview field working * remove redundant variable * fix linting issues * fix rubocop * remove unnecessary entry * consolidate preview generation code * add styles for onebox * add css for @ mentions * fixed eslint issues * FIX: ensure oneboxes load every time * remove unused import * fix prettier issues * removed unused code * remove unused imports * fixed prettier issue * improve css structure * add csrf header in all cases --- .../components/wizard-custom-field.js.es6 | 1 + .../components/wizard-custom-field.hbs | 12 +++++ .../wizard-field-composer-preview.js.es6 | 49 +++++++++++++++++ .../wizard/initializers/custom-wizard.js.es6 | 6 +-- .../wizard-field-composer-preview.hbs | 5 ++ .../stylesheets/wizard/custom/composer.scss | 52 ++++++++++++------- assets/stylesheets/wizard/wizard_custom.scss | 1 + config/locales/client.en.yml | 2 + controllers/custom_wizard/admin/wizard.rb | 1 + lib/custom_wizard/builder.rb | 12 +++++ lib/custom_wizard/field.rb | 5 ++ .../custom_wizard/wizard_field_serializer.rb | 7 ++- 12 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 create mode 100644 assets/javascripts/wizard/templates/components/wizard-field-composer-preview.hbs diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index b5f6b0ee..b5c10c65 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -25,6 +25,7 @@ export default Component.extend(UndoChanges, { showContent: or("isCategory", "isTag", "isGroup", "isDropdown"), showLimit: or("isCategory", "isTag"), isTextType: or("isText", "isTextarea", "isComposer"), + isComposerPreview: equal("field.type", "composer_preview"), categoryPropertyTypes: selectKitContent(["id", "slug"]), showAdvanced: alias("field.type"), messageUrl: "https://thepavilion.io/t/2809", diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 2238ae98..c669692a 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -125,6 +125,18 @@ {{/if}} +{{#if isComposerPreview}} +
+
+ +
+ +
+ {{textarea name="preview-template" value=field.preview_template}} +
+
+{{/if}} + {{#if isUpload}}
diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 new file mode 100644 index 00000000..b49233f2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 @@ -0,0 +1,49 @@ +import Component from "@ember/component"; +import { loadOneboxes } from "discourse/lib/load-oneboxes"; +import { schedule } from "@ember/runloop"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { resolveAllShortUrls } from "pretty-text/upload-short-url"; +import { ajax } from "discourse/lib/ajax"; +import { on } from "discourse-common/utils/decorators"; + +export default Component.extend({ + @on("init") + updatePreview() { + if (this.isDestroyed) { + return; + } + + schedule("afterRender", () => { + if (this._state !== "inDOM" || !this.element) { + return; + } + + const $preview = $(this.element); + + if ($preview.length === 0) { + return; + } + + this.previewUpdated($preview); + }); + }, + + previewUpdated($preview) { + // Paint oneboxes + const paintFunc = () => { + loadOneboxes( + $preview[0], + ajax, + null, + null, + this.siteSettings.max_oneboxes_per_post, + true // refresh on every load + ); + }; + + discourseDebounce(this, paintFunc, 450); + + // Short upload urls need resolution + resolveAllShortUrls(ajax, this.siteSettings, $preview[0]); + }, +}); diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 index 24102bed..ca396081 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 +++ b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 @@ -29,8 +29,6 @@ export default { const getToken = requirejs("wizard/lib/ajax").getToken; const setEnvironment = requirejs("discourse-common/config/environment") .setEnvironment; - const isDevelopment = requirejs("discourse-common/config/environment") - .isDevelopment; const container = app.__container__; Discourse.Model = EmberObject.extend(); Discourse.__container__ = container; @@ -114,9 +112,7 @@ export default { }); $.ajaxPrefilter(function (_, __, jqXHR) { - if (isDevelopment()) { - jqXHR.setRequestHeader("X-CSRF-Token", getToken()); - } + jqXHR.setRequestHeader("X-CSRF-Token", getToken()); }); }, }; diff --git a/assets/javascripts/wizard/templates/components/wizard-field-composer-preview.hbs b/assets/javascripts/wizard/templates/components/wizard-field-composer-preview.hbs new file mode 100644 index 00000000..508cf31d --- /dev/null +++ b/assets/javascripts/wizard/templates/components/wizard-field-composer-preview.hbs @@ -0,0 +1,5 @@ +
+
+ {{html-safe field.preview_template}} +
+
diff --git a/assets/stylesheets/wizard/custom/composer.scss b/assets/stylesheets/wizard/custom/composer.scss index 4e561c61..24cad2a4 100644 --- a/assets/stylesheets/wizard/custom/composer.scss +++ b/assets/stylesheets/wizard/custom/composer.scss @@ -239,31 +239,43 @@ // Markdown table styles for wizard composer preview -.cooked table, -.d-editor-preview table { - border-collapse: collapse; +.cooked, +.d-editor-preview { + a.mention { + display: inline-block; // https://bugzilla.mozilla.org/show_bug.cgi?id=1656119 + font-weight: bold; + font-size: 0.93em; + color: var(--primary-high-or-secondary-low); + padding: 0 4px 1px; + background: var(--primary-low); + border-radius: 8px; + } - tr { - border-bottom: 1px solid var(--primary-low); - &.highlighted { - animation: background-fade-highlight 2.5s ease-out; + table { + border-collapse: collapse; + + tr { + border-bottom: 1px solid var(--primary-low); + &.highlighted { + animation: background-fade-highlight 2.5s ease-out; + } } - } - thead { - th { - text-align: left; - padding: 0.5em; - font-weight: bold; - color: var(--primary); + thead { + th { + text-align: left; + padding: 0.5em; + font-weight: bold; + color: var(--primary); + } } - } - tbody { - border-top: 3px solid var(--primary-low); - } + tbody { + border-top: 3px solid var(--primary-low); + } - td { - padding: 3px 3px 3px 0.5em; + td { + padding: 3px 3px 3px 0.5em; + } } } diff --git a/assets/stylesheets/wizard/wizard_custom.scss b/assets/stylesheets/wizard/wizard_custom.scss index aef346fc..6290ce6e 100644 --- a/assets/stylesheets/wizard/wizard_custom.scss +++ b/assets/stylesheets/wizard/wizard_custom.scss @@ -2,6 +2,7 @@ @import "common/foundation/variables"; @import "common/base/code_highlighting"; @import "common/base/modal"; +@import "common/base/onebox"; @import "common/components/buttons"; @import "common/d-editor"; @import "desktop/modal"; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a21f79bc..ef826cab 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -175,6 +175,7 @@ en: char_counter_placeholder: "Display Character Counter" field_placeholder: "Field Placeholder" file_types: "File Types" + preview_template: "Preview Template" limit: "Limit" property: "Property" prefill: "Prefill" @@ -201,6 +202,7 @@ en: text: "Text" textarea: Textarea composer: Composer + composer_preview: Composer Preview text_only: Text Only number: Number checkbox: Checkbox diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index 9b128d4d..8da64fac 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -109,6 +109,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController :format, :limit, :property, + :preview_template, :placeholder, prefill: mapped_params, content: mapped_params, diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index fab99446..91dbbeb0 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -187,6 +187,18 @@ class CustomWizard::Builder ) end + if field_template['preview_template'].present? + preview_template = mapper.interpolate( + field_template['preview_template'], + user: true, + value: true, + wizard: true, + template: true + ) + + params[:preview_template] = PrettyText.cook(preview_template) + end + if field_template['placeholder'].present? params[:placeholder] = mapper.interpolate( field_template['placeholder'], diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index 36a800c1..eb4af65d 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -21,6 +21,7 @@ class CustomWizard::Field :limit, :property, :content, + :preview_template, :placeholder attr_accessor :index, @@ -45,6 +46,7 @@ class CustomWizard::Field @limit = attrs[:limit] @property = attrs[:property] @content = attrs[:content] + @preview_template = attrs[:preview_template] @placeholder = attrs[:placeholder] end @@ -82,6 +84,9 @@ class CustomWizard::Field placeholder: nil }, text_only: {}, + composer_preview: { + preview_template: nil, + }, date: { format: "YYYY-MM-DD" }, diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/serializers/custom_wizard/wizard_field_serializer.rb index fe22fdeb..37f7a80f 100644 --- a/serializers/custom_wizard/wizard_field_serializer.rb +++ b/serializers/custom_wizard/wizard_field_serializer.rb @@ -18,7 +18,8 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer :content, :validations, :max_length, - :char_counter + :char_counter, + :preview_template def id object.id @@ -117,4 +118,8 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer def char_counter object.char_counter end + + def preview_template + object.preview_template + end end From 7375c63403452453a7c90d8c4da595375867be94 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 13 Jul 2021 12:03:11 +0800 Subject: [PATCH 57/58] Version bump --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index f6342f3f..449d0237 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards -# version: 0.7.0 +# version: 0.8.0 # authors: Angus McLeod # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io From 247f7ca466d52b4f8b418f8ef1de06ae08cc362f Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 14 Jul 2021 13:32:19 +0530 Subject: [PATCH 58/58] IMPROVE: reduce field placeholder setting textarea width --- .../discourse/templates/components/wizard-custom-field.hbs | 1 + assets/stylesheets/common/wizard-admin.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index c669692a..3b63cef7 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -120,6 +120,7 @@
{{textarea name="field_placeholder" + class="medium" value=field.placeholder}}
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 2d6c9a6a..66cc6b43 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -256,6 +256,10 @@ width: 100%; box-sizing: border-box; margin-bottom: 0; + + &.medium { + width: 40%; + } } input[type="number"] {