diff --git a/assets/javascripts/discourse/components/wizard-export.js.es6 b/assets/javascripts/discourse/components/wizard-export.js.es6
new file mode 100644
index 00000000..a2505d24
--- /dev/null
+++ b/assets/javascripts/discourse/components/wizard-export.js.es6
@@ -0,0 +1,42 @@
+export default Ember.Component.extend({
+ classNames: ['container', 'export'],
+ selected: Ember.A(),
+
+ actions: {
+ checkChanged(event) {
+ this.set('exportMessage', '');
+
+ let selected = this.get('selected');
+
+ if (event.target.checked) {
+ selected.addObject(event.target.id);
+ } else if (!event.target.checked) {
+ selected.removeObject(event.target.id);
+ }
+
+ this.set('selected', selected);
+ },
+
+ export() {
+ const wizards = this.get('selected');
+
+ if (!wizards.length) {
+ this.set('exportMessage', I18n.t("admin.wizard.transfer.export.none_selected"));
+ } else {
+ this.set('exportMessage', '');
+
+ let url = Discourse.BaseUrl;
+ let route = '/admin/wizards/transfer/export';
+ url += route + '?';
+
+ wizards.forEach((wizard) => {
+ let step = 'wizards[]=' + wizard;
+ step += '&';
+ url += step;
+ });
+
+ location.href = url;
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/assets/javascripts/discourse/components/wizard-import.js.es6 b/assets/javascripts/discourse/components/wizard-import.js.es6
new file mode 100644
index 00000000..1cf6618d
--- /dev/null
+++ b/assets/javascripts/discourse/components/wizard-import.js.es6
@@ -0,0 +1,81 @@
+import { ajax } from 'discourse/lib/ajax';
+import { default as computed } from 'ember-addons/ember-computed-decorators';
+
+export default Ember.Component.extend({
+ classNames: ['container', 'import'],
+ hasLogs: Ember.computed.notEmpty('logs'),
+
+ @computed('successIds', 'failureIds')
+ logs(successIds, failureIds) {
+ let logs = [];
+
+ if (successIds) {
+ logs.push(...successIds.map(id => {
+ return { id, type: 'success' };
+ }));
+ }
+
+ if (failureIds) {
+ logs.push(...failureIds.map(id => {
+ return { id, type: 'failure' };
+ }));
+ }
+
+ return logs;
+ },
+
+ actions: {
+ setFilePath(event) {
+ this.set('importMessage', '');
+
+ // 512 kb is the max file size
+ let maxFileSize = 512 * 1024;
+
+ if (event.target.files[0] === undefined) {
+ this.set('filePath', null);
+ return;
+ }
+
+ if (maxFileSize < event.target.files[0].size) {
+ this.setProperties({
+ importMessage: I18n.t('admin.wizard.transfer.import.file_size_error'),
+ filePath: null
+ });
+ $('#file-url').val('');
+ } else {
+ this.set('filePath', event.target.files[0]);
+ }
+ },
+
+ import() {
+ const filePath = this.get('filePath');
+ let $formData = new FormData();
+
+ if (filePath) {
+ $formData.append('file', filePath);
+
+ ajax('/admin/wizards/transfer/import', {
+ type: 'POST',
+ data: $formData,
+ processData: false,
+ contentType: false,
+ }).then(result => {
+ if (result.error) {
+ this.set('importMessage', result.error);
+ } else {
+ this.setProperties({
+ successIds: result.success,
+ failureIds: result.failed,
+ fileName: $('#file-url')[0].files[0].name
+ });
+ }
+
+ this.set('filePath', null);
+ $('#file-url').val('');
+ });
+ } else {
+ this.set('importMessage', I18n.t("admin.wizard.transfer.import.no_file"));
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6
new file mode 100644
index 00000000..77c79b72
--- /dev/null
+++ b/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6
@@ -0,0 +1 @@
+export default Ember.Controller.extend();
diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6
index f5022153..001569f2 100644
--- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6
+++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6
@@ -11,6 +11,9 @@ export default {
this.route('adminWizardsApis', { path: '/apis', resetNamespace: true }, function() {
this.route('adminWizardsApi', { path: '/:name', resetNamespace: true });
});
+
+ this.route('adminWizardsTransfer', { path: '/transfer', resetNamespace: true });
+
});
}
};
diff --git a/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6
new file mode 100644
index 00000000..b86b49cc
--- /dev/null
+++ b/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6
@@ -0,0 +1,7 @@
+import CustomWizard from '../models/custom-wizard';
+
+export default Discourse.Route.extend({
+ model() {
+ return CustomWizard.all();
+ }
+});
diff --git a/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs b/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs
new file mode 100644
index 00000000..ff36a823
--- /dev/null
+++ b/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs
@@ -0,0 +1,2 @@
+{{wizard-export wizards=model}}
+{{wizard-import}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs
index 3c26e24a..4ebb6a36 100644
--- a/assets/javascripts/discourse/templates/admin-wizards.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizards.hbs
@@ -2,6 +2,7 @@
{{nav-item route='adminWizardsCustom' label='admin.wizard.custom_label'}}
{{nav-item route='adminWizardsSubmissions' label='admin.wizard.submissions_label'}}
{{nav-item route='adminWizardsApis' label='admin.wizard.api.nav_label'}}
+ {{nav-item route='adminWizardsTransfer' label='admin.wizard.transfer.nav_label'}}
{{/admin-nav}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-export.hbs b/assets/javascripts/discourse/templates/components/wizard-export.hbs
new file mode 100644
index 00000000..869b372a
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/wizard-export.hbs
@@ -0,0 +1,25 @@
+
{{i18n 'admin.wizard.transfer.export.label'}}
+
+
+ {{#each wizards as |w|}}
+ -
+ {{input type="checkbox"
+ id=(dasherize w.id)
+ change=(action 'checkChanged')}}
+ {{#link-to "adminWizard" (dasherize w.id)}}
+ {{w.name}}
+ {{/link-to}}
+
+ {{/each}}
+
+
+{{d-button id="export-button"
+ class="btn btn-primary side"
+ label="admin.wizard.transfer.export.label"
+ action=(action "export")}}
+
+{{#if exportMessage}}
+
+ {{exportMessage}}
+
+{{/if}}
\ No newline at end of file
diff --git a/assets/javascripts/discourse/templates/components/wizard-import.hbs b/assets/javascripts/discourse/templates/components/wizard-import.hbs
new file mode 100644
index 00000000..9f46e32b
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/wizard-import.hbs
@@ -0,0 +1,32 @@
+
{{i18n 'admin.wizard.transfer.import.label'}}
+
+
+ {{input id='file-url' type="file" change=(action "setFilePath")}}
+
+ {{#if importMessage}}
+
+ {{importMessage}}
+
+ {{/if}}
+
+ {{d-button id="import-button"
+ class="btn btn-primary side"
+ label="admin.wizard.transfer.import.label"
+ action=(action "import")}}
+
+
+{{#if hasLogs}}
+
+
+ {{i18n 'admin.wizard.transfer.import.logs' fileName=fileName}}
+
+
+
+ {{#each logs as |l|}}
+ -
+ {{i18n (concat 'admin.wizard.transfer.import.' l.type) id=l.id}}
+
+ {{/each}}
+
+
+{{/if}}
\ No newline at end of file
diff --git a/assets/stylesheets/wizard_custom_admin.scss b/assets/stylesheets/wizard_custom_admin.scss
index 3399b148..7ae598ac 100644
--- a/assets/stylesheets/wizard_custom_admin.scss
+++ b/assets/stylesheets/wizard_custom_admin.scss
@@ -224,11 +224,11 @@
.required-data .setting-value {
flex-flow: wrap;
-
+
.custom-inputs {
margin-bottom: 20px;
}
-
+
.required-data-message .label {
margin-bottom: 5px;
}
@@ -480,3 +480,40 @@
.wizard-step-contents{
height: unset !important;
}
+
+// Tansfer tab
+
+.admin-wizards-transfer .admin-container .container {
+ padding-top: 20px;
+}
+
+#file-url {
+ display: block;
+ margin-bottom: 10px;
+}
+
+.wizard-list-select {
+ list-style-type: none;
+}
+
+.wizard-action-buttons {
+ flex-direction: column;
+}
+
+.import-message {
+ margin: 10px 0;
+}
+
+.import-logs {
+ margin-top: 20px;
+
+ .title {
+ font-weight: 800;
+ margin-bottom: 10px;
+ }
+
+ ul {
+ list-style: none;
+ }
+}
+
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 0aa235d8..d65c42c0 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -215,6 +215,19 @@ en:
log:
label: "Logs"
+ transfer:
+ nav_label: "Transfer"
+ export:
+ label: "Export"
+ none_selected: "Please select atleast one wizard"
+ import:
+ label: "Import"
+ logs: "Import logs for {{fileName}}"
+ success: 'Wizard "{{id}}" saved successfully'
+ failure: 'Wizard "{{id}}" could not be saved'
+ no_file: "Please choose a file to import"
+ file_size_error: "The file must be JSON and 512kb or less"
+
wizard_js:
location:
name:
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 8ce4b4bb..d1c42401 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -10,6 +10,15 @@ en:
too_short: "%{label} must be at least %{min} characters"
none: "We couldn't find a wizard at that address."
no_skip: "Wizard can't be skipped"
+ export:
+ error:
+ select_one: "Please select atleast one wizard"
+ import:
+ error:
+ no_file: "No file selected"
+ file_large: "File too large"
+ invalid_json: "File is not a valid json file"
+ no_valid_wizards: "File doesn't contain any valid wizards"
site_settings:
wizard_redirect_exclude_paths: "Routes excluded from wizard redirects."
diff --git a/controllers/transfer.rb b/controllers/transfer.rb
new file mode 100644
index 00000000..4da52bb9
--- /dev/null
+++ b/controllers/transfer.rb
@@ -0,0 +1,74 @@
+class CustomWizard::TransferController < ::ApplicationController
+ before_action :ensure_logged_in
+ before_action :ensure_admin
+ skip_before_action :check_xhr, :only => [:export]
+
+ def index
+ end
+
+ def export
+ wizards = params['wizards']
+ wizard_objects = []
+
+ if wizards.nil?
+ render json: { error: I18n.t('wizard.export.error.select_one') }
+ return
+ end
+
+ wizards.each do |w|
+ wizard_objects.push(PluginStore.get('custom_wizard', w.tr('-', '_')))
+ end
+
+ send_data wizard_objects.to_json,
+ type: "application/json",
+ disposition: 'attachment',
+ filename: 'wizards.json'
+ end
+
+ def import
+ file = File.read(params['file'].tempfile)
+
+ if file.nil?
+ render json: { error: I18n.t('wizard.import.error.no_file') }
+ return
+ end
+
+ fileSize = file.size
+ maxFileSize = 512 * 1024
+
+ if maxFileSize < fileSize
+ render json: { error: I18n.t('wizard.import.error.file_large') }
+ return
+ end
+
+ begin
+ jsonObject = JSON.parse file
+ rescue JSON::ParserError
+ render json: { error: I18n.t('wizard.import.error.invalid_json') }
+ return
+ end
+
+ countValid = 0
+ success_ids = []
+ failed_ids = []
+
+ jsonObject.each do |o|
+ if !CustomWizard::Template.new(o)
+ failed_ids.push o['id']
+ next
+ end
+
+ countValid += 1
+ pluginStoreEntry = PluginStore.new 'custom_wizard'
+ saved = pluginStoreEntry.set(o['id'], o) unless pluginStoreEntry.get(o['id'])
+ success_ids.push o['id'] if !!saved
+ failed_ids.push o['id'] if !saved
+ end
+
+ if countValid == 0
+ render json: { error: I18n.t('wizard.import.error.no_valid_wizards') }
+ else
+ render json: { success: success_ids, failed: failed_ids }
+ end
+ end
+end
diff --git a/plugin.rb b/plugin.rb
index 1709b7ea..1acacf99 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -12,6 +12,7 @@ config = Rails.application.config
config.assets.paths << Rails.root.join('plugins', 'discourse-custom-wizard', 'assets', 'javascripts')
config.assets.paths << Rails.root.join('plugins', 'discourse-custom-wizard', 'assets', 'stylesheets', 'wizard')
+
if Rails.env.production?
config.assets.precompile += %w{
wizard-custom-lib.js
@@ -75,6 +76,10 @@ after_initialize do
delete 'admin/wizards/apis/logs/:name' => 'api#clearlogs'
get 'admin/wizards/apis/:name/redirect' => 'api#redirect'
get 'admin/wizards/apis/:name/authorize' => 'api#authorize'
+ #transfer code
+ get 'admin/wizards/transfer' => 'transfer#index'
+ get 'admin/wizards/transfer/export' => 'transfer#export'
+ post 'admin/wizards/transfer/import' => 'transfer#import'
end
end
@@ -89,6 +94,8 @@ after_initialize do
load File.expand_path('../controllers/wizard.rb', __FILE__)
load File.expand_path('../controllers/steps.rb', __FILE__)
load File.expand_path('../controllers/admin.rb', __FILE__)
+ #transfer code
+ load File.expand_path('../controllers/transfer.rb', __FILE__)
load File.expand_path('../jobs/refresh_api_access_token.rb', __FILE__)
load File.expand_path('../lib/api/api.rb', __FILE__)
@@ -145,7 +152,7 @@ after_initialize do
@excluded_routes ||= SiteSetting.wizard_redirect_exclude_paths.split('|') + ['/w/']
url = request.referer || request.original_url
- if request.format === 'text/html' && !@excluded_routes.any? { |str| /#{str}/ =~ url } && wizard_id
+ 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)
end
@@ -157,7 +164,7 @@ after_initialize do
end
end
- add_to_serializer(:current_user, :redirect_to_wizard) { object.custom_fields['redirect_to_wizard'] }
+ add_to_serializer(:current_user, :redirect_to_wizard) {object.custom_fields['redirect_to_wizard']}
## TODO limit this to the first admin
SiteSerializer.class_eval do
@@ -169,7 +176,7 @@ after_initialize do
def complete_custom_wizard
if scope.user && requires_completion = CustomWizard::Wizard.prompt_completion(scope.user)
- requires_completion.map { |w| { name: w[:name], url: "/w/#{w[:id]}" } }
+ requires_completion.map {|w| {name: w[:name], url: "/w/#{w[:id]}"}}
end
end