diff --git a/assets/javascripts/discourse/components/wizard-export.js.es6 b/assets/javascripts/discourse/components/wizard-export.js.es6
deleted file mode 100644
index c75b745e..00000000
--- a/assets/javascripts/discourse/components/wizard-export.js.es6
+++ /dev/null
@@ -1,46 +0,0 @@
-import Component from "@ember/component";
-import { A } from "@ember/array";
-import I18n from "I18n";
-
-export default Component.extend({
- classNames: ['container', 'export'],
- selected: 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
deleted file mode 100644
index 446d2b00..00000000
--- a/assets/javascripts/discourse/components/wizard-import.js.es6
+++ /dev/null
@@ -1,84 +0,0 @@
-import { ajax } from 'discourse/lib/ajax';
-import { default as discourseComputed } from 'discourse-common/utils/decorators';
-import { notEmpty } from "@ember/object/computed";
-import Component from "@ember/component";
-import I18n from "I18n";
-
-export default Component.extend({
- classNames: ['container', 'import'],
- hasLogs: notEmpty('logs'),
-
- @discourseComputed('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/components/wizard-message.js.es6 b/assets/javascripts/discourse/components/wizard-message.js.es6
index 0592e9ca..afcde014 100644
--- a/assets/javascripts/discourse/components/wizard-message.js.es6
+++ b/assets/javascripts/discourse/components/wizard-message.js.es6
@@ -1,13 +1,28 @@
import { default as discourseComputed } from 'discourse-common/utils/decorators';
+import { not, notEmpty } from "@ember/object/computed";
import Component from "@ember/component";
import I18n from "I18n";
+const icons = {
+ error: 'times-circle',
+ success: 'check-circle',
+ info: 'info-circle'
+}
+
export default Component.extend({
- classNames: 'wizard-message',
+ classNameBindings: [':wizard-message', 'type', 'loading'],
+ showDocumentation: not('loading'),
+ showIcon: not('loading'),
+ hasItems: notEmpty('items'),
- @discourseComputed('key', 'component')
- message(key, component) {
- return I18n.t(`admin.wizard.message.${component}.${key}`);
+ @discourseComputed('type')
+ icon(type) {
+ return icons[type] || 'info-circle';
+ },
+
+ @discourseComputed('key', 'component', 'opts')
+ message(key, component, opts) {
+ return I18n.t(`admin.wizard.message.${component}.${key}`, opts || {});
},
@discourseComputed('component')
diff --git a/assets/javascripts/discourse/controllers/admin-wizards-manager.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-manager.js.es6
new file mode 100644
index 00000000..56aa4446
--- /dev/null
+++ b/assets/javascripts/discourse/controllers/admin-wizards-manager.js.es6
@@ -0,0 +1,229 @@
+import Controller from "@ember/controller";
+import {
+ default as discourseComputed,
+ observes
+} from 'discourse-common/utils/decorators';
+import { empty } from "@ember/object/computed";
+import CustomWizardManager from '../models/custom-wizard-manager';
+import { A } from "@ember/array";
+import I18n from "I18n";
+import { underscore } from "@ember/string";
+
+export default Controller.extend({
+ messageUrl: 'https://thepavilion.io/t/3652',
+ messageKey: 'info',
+ messageIcon: 'info-circle',
+ messageClass: 'info',
+ importDisabled: empty('file'),
+ exportWizards: A(),
+ destroyWizards: A(),
+ exportDisabled: empty('exportWizards'),
+ destoryDisabled: empty('destroyWizards'),
+
+ setMessage(type, key, opts={}, items=[]) {
+ this.setProperties({
+ messageKey: key,
+ messageOpts: opts,
+ messageType: type,
+ messageItems: items
+ });
+ setTimeout(() => {
+ this.setProperties({
+ messageKey: 'info',
+ messageOpts: null,
+ messageType: null,
+ messageItems: null
+ })
+ }, 10000);
+ },
+
+ buildWizardLink(wizard) {
+ let html = `${wizard.name}`;
+ html += `${I18n.t('admin.wizard.manager.imported')}`;
+ return {
+ icon: 'check-circle',
+ html
+ };
+ },
+
+ buildDestroyedItem(destroyed) {
+ let html = `${destroyed.name}`;
+ html += `${I18n.t('admin.wizard.manager.destroyed')}`;
+ return {
+ icon: 'check-circle',
+ html
+ };
+ },
+
+ buildFailureItem(failure) {
+ return {
+ icon: 'times-circle',
+ html: `${failure.id}: ${failure.messages}`
+ };
+ },
+
+ clearFile() {
+ this.setProperties({
+ file: null,
+ filename: null
+ });
+ $('#file-upload').val('');
+ },
+
+ @observes('importing', 'destroying')
+ setLoadingMessages() {
+ if (this.importing) {
+ this.setMessage("loading", "importing");
+ }
+ if (this.destroying) {
+ this.setMessage("loading", "destroying");
+ }
+ },
+
+ actions: {
+ upload() {
+ $('#file-upload').click();
+ },
+
+ clearFile() {
+ this.clearFile();
+ },
+
+ setFile(event) {
+ let maxFileSize = 512 * 1024;
+ const file = event.target.files[0];
+
+ if (file === undefined) {
+ this.set('file', null);
+ return;
+ }
+
+ if (maxFileSize < file.size) {
+ this.setMessage("error", "file_size_error");
+ this.set("file", null);
+ $('#file-upload').val('');
+ } else {
+ this.setProperties({
+ file,
+ filename: file.name
+ });
+ }
+ },
+
+ selectWizard(event) {
+ const type = event.target.classList.contains('export') ? 'export' : 'destroy';
+ const wizards = this.get(`${type}Wizards`);
+ const checked = event.target.checked;
+
+ let wizardId = event.target.closest('tr').getAttribute('data-wizard-id');
+
+ if (wizardId) {
+ wizardId = underscore(wizardId);
+ } else {
+ return false;
+ }
+
+ if (checked) {
+ wizards.addObject(wizardId);
+ } else {
+ wizards.removeObject(wizardId);
+ }
+ },
+
+ import() {
+ const file = this.get('file');
+
+ if (!file) {
+ this.setMessage("error", 'no_file');
+ return;
+ }
+
+ let $formData = new FormData();
+ $formData.append('file', file);
+
+ this.set('importing', true);
+ this.setMessage("loading", "importing");
+
+ CustomWizardManager.import($formData).then(result => {
+ if (result.error) {
+ this.setMessage("error", "server_error", {
+ message: result.error
+ });
+ } else {
+ this.setMessage("success", "import_complete", {},
+ result.imported.map(imported => {
+ return this.buildWizardLink(imported);
+ }).concat(
+ result.failures.map(failure => {
+ return this.buildFailureItem(failure);
+ })
+ )
+ );
+
+ if (result.imported.length) {
+ this.get('wizards').addObjects(result.imported);
+ }
+ }
+ this.clearFile();
+ }).finally(() => {
+ this.set('importing', false);
+ });
+ },
+
+ export() {
+ const exportWizards = this.get('exportWizards');
+
+ if (!exportWizards.length) {
+ this.setMessage("error", 'none_selected');
+ } else {
+ CustomWizardManager.export(exportWizards);
+ exportWizards.clear();
+ $('input.export').prop("checked", false);
+ }
+ },
+
+ destroy() {
+ const destroyWizards = this.get('destroyWizards');
+
+ if (!destroyWizards.length) {
+ this.setMessage("error", 'none_selected');
+ } else {
+ this.set('destroying', true);
+
+ CustomWizardManager.destroy(destroyWizards).then((result) => {
+ if (result.error) {
+ this.setMessage("error", "server_error", {
+ message: result.error
+ });
+ } else {
+ this.setMessage("success", "destroy_complete", {},
+ result.destroyed.map(destroyed => {
+ return this.buildDestroyedItem(destroyed);
+ }).concat(
+ result.failures.map(failure => {
+ return this.buildFailureItem(failure);
+ })
+ )
+ );
+
+ if (result.destroyed.length) {
+ const destroyedIds = result.destroyed.map(d => d.id);
+ const destroyWizards = this.get('destroyWizards');
+ const wizards = this.get('wizards');
+
+ wizards.removeObjects(
+ wizards.filter(w => {
+ return destroyedIds.includes(w.id);
+ })
+ );
+
+ destroyWizards.removeObjects(destroyedIds);
+ }
+ }
+ }).finally(() => {
+ this.set('destroying', false);
+ });
+ }
+ }
+ }
+});
diff --git a/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6
deleted file mode 100644
index 7ae8f5a1..00000000
--- a/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6
+++ /dev/null
@@ -1,3 +0,0 @@
-import Controller from "@ember/controller";
-
-export default 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 97157964..8274ef12 100644
--- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6
+++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6
@@ -19,7 +19,7 @@ export default {
this.route('adminWizardsLogs', { path: '/logs', resetNamespace: true });
- this.route('adminWizardsTransfer', { path: '/transfer', resetNamespace: true });
+ this.route('adminWizardsManager', { path: '/manager', resetNamespace: true });
});
}
};
diff --git a/assets/javascripts/discourse/models/custom-wizard-manager.js.es6 b/assets/javascripts/discourse/models/custom-wizard-manager.js.es6
new file mode 100644
index 00000000..68d8d567
--- /dev/null
+++ b/assets/javascripts/discourse/models/custom-wizard-manager.js.es6
@@ -0,0 +1,43 @@
+import { ajax } from 'discourse/lib/ajax';
+import { popupAjaxError } from 'discourse/lib/ajax-error';
+import EmberObject from "@ember/object";
+
+const CustomWizardManager = EmberObject.extend();
+
+const basePath = "admin/wizards/manager";
+
+CustomWizardManager.reopenClass({
+ import($formData) {
+ return ajax(`/${basePath}/import`, {
+ type: 'POST',
+ data: $formData,
+ processData: false,
+ contentType: false,
+ }).catch(popupAjaxError);
+ },
+
+ export(wizardIds) {
+ let url = `${Discourse.BaseUrl}/${basePath}/export?`;
+
+ wizardIds.forEach((wizardId, index) => {
+ let step = 'wizard_ids[]=' + wizardId;
+ if (index !== wizardIds[wizardIds.length - 1]) {
+ step += '&';
+ }
+ url += step;
+ });
+
+ location.href = url;
+ },
+
+ destroy(wizardIds) {
+ return ajax(`/${basePath}/destroy`, {
+ type: "DELETE",
+ data: {
+ wizard_ids: wizardIds
+ }
+ }).catch(popupAjaxError);
+ }
+});
+
+export default CustomWizardManager;
\ No newline at end of file
diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6
index 7a842987..ea555f41 100644
--- a/assets/javascripts/discourse/models/custom-wizard.js.es6
+++ b/assets/javascripts/discourse/models/custom-wizard.js.es6
@@ -4,6 +4,7 @@ import { buildProperties, present, mapped } from '../lib/wizard-json';
import { listProperties, camelCase, snakeCase } from '../lib/wizard';
import wizardSchema from '../lib/wizard-schema';
import { Promise } from "rsvp";
+import { popupAjaxError } from 'discourse/lib/ajax-error';
const CustomWizard = EmberObject.extend({
save(opts) {
@@ -185,7 +186,7 @@ const CustomWizard = EmberObject.extend({
remove() {
return ajax(`/admin/wizards/wizard/${this.id}`, {
type: 'DELETE'
- }).then(() => this.destroy());
+ }).then(() => this.destroy()).catch(popupAjaxError);
}
});
@@ -195,13 +196,13 @@ CustomWizard.reopenClass({
type: 'GET'
}).then(result => {
return result.wizard_list;
- });
+ }).catch(popupAjaxError);
},
submissions(wizardId) {
return ajax(`/admin/wizards/submissions/${wizardId}`, {
type: "GET"
- });
+ }).catch(popupAjaxError);
},
create(wizardJson = {}) {
diff --git a/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-manager.js.es6
similarity index 100%
rename from assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6
rename to assets/javascripts/discourse/routes/admin-wizards-manager.js.es6
diff --git a/assets/javascripts/discourse/templates/admin-wizards-manager.hbs b/assets/javascripts/discourse/templates/admin-wizards-manager.hbs
new file mode 100644
index 00000000..3eda71e2
--- /dev/null
+++ b/assets/javascripts/discourse/templates/admin-wizards-manager.hbs
@@ -0,0 +1,83 @@
+
+
{{i18n 'admin.wizard.manager.title'}}
+
+
+
+
+{{wizard-message
+ key=messageKey
+ url=messageUrl
+ type=messageType
+ opts=messageOpts
+ items=messageItems
+ loading=loading
+ component='manager'}}
+
+
+
+
+
+ {{i18n 'admin.wizard.label'}} |
+ {{i18n 'admin.wizard.manager.export'}} |
+ {{i18n 'admin.wizard.manager.destroy'}} |
+
+
+
+ {{#each wizards as |wizard|}}
+
+
+ {{#link-to "adminWizardsWizardShow" (dasherize wizard.id)}}
+ {{wizard.name}}
+ {{/link-to}}
+ |
+
+ {{input
+ type="checkbox"
+ class="export"
+ change=(action 'selectWizard')}}
+ |
+
+ {{input
+ type="checkbox"
+ class="destroy"
+ change=(action 'selectWizard')}}
+ |
+
+ {{/each}}
+
+
+
\ No newline at end of file
diff --git a/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs b/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs
deleted file mode 100644
index 5cfa3f56..00000000
--- a/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs
+++ /dev/null
@@ -1,2 +0,0 @@
-{{wizard-export wizards=wizards}}
-{{wizard-import}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs
index 53ec86a6..c616e668 100644
--- a/assets/javascripts/discourse/templates/admin-wizards.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizards.hbs
@@ -6,7 +6,7 @@
{{nav-item route='adminWizardsApi' label='admin.wizard.api.nav_label'}}
{{/if}}
{{nav-item route='adminWizardsLogs' label='admin.wizard.log.nav_label'}}
- {{nav-item route='adminWizardsTransfer' label='admin.wizard.transfer.nav_label'}}
+ {{nav-item route='adminWizardsManager' label='admin.wizard.manager.nav_label'}}
{{/admin-nav}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-export.hbs b/assets/javascripts/discourse/templates/components/wizard-export.hbs
deleted file mode 100644
index 9bbfabcf..00000000
--- a/assets/javascripts/discourse/templates/components/wizard-export.hbs
+++ /dev/null
@@ -1,27 +0,0 @@
-
{{i18n 'admin.wizard.transfer.export.label'}}
-
-
- {{#each wizards as |w|}}
- -
- {{input
- type="checkbox"
- id=(dasherize w.id)
- change=(action 'checkChanged')}}
-
- {{#link-to "adminWizardsWizardShow" (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
deleted file mode 100644
index 9f46e32b..00000000
--- a/assets/javascripts/discourse/templates/components/wizard-import.hbs
+++ /dev/null
@@ -1,32 +0,0 @@
-
{{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/javascripts/discourse/templates/components/wizard-message.hbs b/assets/javascripts/discourse/templates/components/wizard-message.hbs
index 9f66fc77..78c7558b 100644
--- a/assets/javascripts/discourse/templates/components/wizard-message.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-message.hbs
@@ -1,12 +1,26 @@
-
- {{d-icon 'info-circle'}}
-
{{message}}
+
+ {{#if showIcon}}
+ {{d-icon icon}}
+ {{/if}}
+
{{{message}}}
+ {{#if hasItems}}
+
+ {{#each items as |item|}}
+ -
+ {{d-icon item.icon}}
+ {{{item.html}}}
+
+ {{/each}}
+
+ {{/if}}
-
\ No newline at end of file
+{{#if showDocumentation}}
+
+{{/if}}
\ No newline at end of file
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss
index b92e6e33..79cfb033 100644
--- a/assets/stylesheets/common/wizard-admin.scss
+++ b/assets/stylesheets/common/wizard-admin.scss
@@ -1,5 +1,5 @@
@import 'wizard-mapper';
-@import 'wizard-transfer';
+@import 'wizard-manager';
@import 'wizard-api';
.admin-wizard-controls {
@@ -20,22 +20,50 @@
box-sizing: border-box;
display: flex;
justify-content: space-between;
+ align-items: flex-start;
.message-block {
.d-icon {
margin-right: 4px;
}
+ .d-icon-check-circle {
+ color: var(--success);
+ }
+
+ .d-icon-times-circle {
+ color: var(--danger);
+ }
+
a + a {
border-left: 1px solid $primary-medium;
padding-left: 5px;
margin-left: 5px;
}
+
+ .message {
+ white-space: nowrap;
+ }
+
+ .list-colon {
+ margin-right: 5px;
+ }
+
+ ul {
+ list-style: none;
+ margin: 0;
+
+ span.action {
+ margin-left: 5px;
+ }
+ }
}
& + div {
margin-top: 30px;
}
+
+
}
.wizard-submissions {
diff --git a/assets/stylesheets/common/wizard-manager.scss b/assets/stylesheets/common/wizard-manager.scss
new file mode 100644
index 00000000..5437134f
--- /dev/null
+++ b/assets/stylesheets/common/wizard-manager.scss
@@ -0,0 +1,60 @@
+.admin-wizards-manager .admin-wizard-controls {
+ display: flex;
+ justify-content: flex-start;
+
+ h3 {
+ margin-bottom: 0;
+ }
+
+ .buttons {
+ display: flex;
+ margin-left: auto;
+
+ > * {
+ margin-left: 10px;
+ }
+
+ #import-button:enabled,
+ #export-button:enabled {
+ background-color: $tertiary;
+ color: $secondary;
+ }
+
+ #destroy-button:enabled {
+ background-color: $danger;
+ color: $secondary;
+ }
+ }
+
+ #file-upload {
+ display: none;
+ }
+
+ .filename {
+ padding: 0 10px;
+ border: 1px solid $primary;
+ display: inline-flex;
+ height: 28px;
+ line-height: 28px;
+
+ a {
+ color: $primary;
+ margin-right: 5px;
+ display: inline-flex;
+ align-items: center;
+ }
+ }
+}
+
+.wizard-list-select {
+ list-style-type: none;
+}
+
+.wizard-action-buttons {
+ flex-direction: column;
+}
+
+.control-column {
+ width: 100px;
+ text-align: center;
+}
diff --git a/assets/stylesheets/common/wizard-transfer.scss b/assets/stylesheets/common/wizard-transfer.scss
deleted file mode 100644
index e31168ba..00000000
--- a/assets/stylesheets/common/wizard-transfer.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-.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;
- }
-}
\ No newline at end of file
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 906d6f77..8d57523a 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -75,6 +75,18 @@ en:
custom_fields:
create: "Create or edit a custom field record"
documentation: Check out the custom field documentation
+ manager:
+ info: "Export, import or destroy wizards"
+ documentation: Check out the manager documentation
+ none_selected: Please select atleast one wizard
+ no_file: Please choose a file to import
+ file_size_error: The file size must be 512kb or less
+ file_format_error: The file must be a .json file
+ server_error: "Error: {{message}}"
+ importing: Importing wizards...
+ destroying: Destroying wizards...
+ import_complete: Import complete
+ destroy_complete: Destruction complete
editor:
show: "Show"
@@ -373,18 +385,15 @@ en:
log:
nav_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"
+ manager:
+ nav_label: Manage
+ title: Manage Wizards
+ export: Export
+ import: Import
+ imported: imported
+ upload: Select wizards.json
+ destroy: Destroy
+ destroyed: destroyed
wizard_js:
group:
diff --git a/config/locales/client.pt_br.yml b/config/locales/client.pt_br.yml
index 23ab4b9a..b79830f3 100644
--- a/config/locales/client.pt_br.yml
+++ b/config/locales/client.pt_br.yml
@@ -303,7 +303,7 @@ pt_br:
log:
nav_label: "Logs"
- transfer:
+ manager:
nav_label: "Transferir"
export:
label: "Exportar"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index b06be5f8..6aa27a7b 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -30,18 +30,23 @@ en:
no_skip: "Wizard can't be skipped"
export:
error:
- select_one: "Please select at least one wizard"
+ select_one: "Please select at least one valid wizard"
+ invalid_wizards: "No valid wizards selected"
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"
+
+ destroy:
+ error:
+ no_template: No template found
+ default: Error destroying wizard
validation:
required: "%{property} is required"
- conflict: "Wizard with %{wizard_id} already exists"
+ conflict: "Wizard with id '%{wizard_id}' already exists"
after_time: "After time setting is invalid"
site_settings:
diff --git a/config/routes.rb b/config/routes.rb
index a765a809..311de5e6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -36,8 +36,9 @@ Discourse::Application.routes.append do
get 'admin/wizards/logs' => 'admin_logs#index'
- get 'admin/wizards/transfer' => 'admin_transfer#index'
- get 'admin/wizards/transfer/export' => 'admin_transfer#export'
- post 'admin/wizards/transfer/import' => 'admin_transfer#import'
+ get 'admin/wizards/manager' => 'admin_manager#index'
+ get 'admin/wizards/manager/export' => 'admin_manager#export'
+ post 'admin/wizards/manager/import' => 'admin_manager#import'
+ delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy'
end
end
\ No newline at end of file
diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb
index 5da337a4..a2cfb977 100644
--- a/controllers/custom_wizard/admin/admin.rb
+++ b/controllers/custom_wizard/admin/admin.rb
@@ -15,4 +15,8 @@ class CustomWizard::AdminController < ::Admin::AdminController
def custom_field_list
serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer)
end
+
+ def render_error(message)
+ render json: failed_json.merge(error: message)
+ end
end
\ No newline at end of file
diff --git a/controllers/custom_wizard/admin/manager.rb b/controllers/custom_wizard/admin/manager.rb
new file mode 100644
index 00000000..d7fe5ac6
--- /dev/null
+++ b/controllers/custom_wizard/admin/manager.rb
@@ -0,0 +1,123 @@
+class CustomWizard::AdminManagerController < CustomWizard::AdminController
+ skip_before_action :check_xhr, only: [:export]
+ before_action :get_wizard_ids, except: [:import]
+
+ def export
+ templates = []
+
+ @wizard_ids.each do |wizard_id|
+ if template = CustomWizard::Template.find(wizard_id)
+ templates.push(template)
+ end
+ end
+
+ if templates.empty?
+ return render_error(I18n.t('wizard.export.error.invalid_wizards'))
+ end
+
+ basename = SiteSetting.title.parameterize || 'discourse'
+ time = Time.now.to_i
+ filename = "#{basename}-wizards-#{time}.json"
+
+ send_data templates.to_json,
+ type: "application/json",
+ disposition: 'attachment',
+ filename: filename
+ end
+
+ def import
+ file = File.read(params['file'].tempfile)
+
+ if file.nil?
+ return render_error(I18n.t('wizard.export.error.no_file'))
+ end
+
+ file_size = file.size
+ max_file_size = 512 * 1024
+
+ if max_file_size < file_size
+ return render_error(I18n.t('wizard.import.error.file_large'))
+ end
+
+ begin
+ template_json = JSON.parse file
+ rescue JSON::ParserError
+ return render_error(I18n.t('wizard.import.error.invalid_json'))
+ end
+
+ imported = []
+ failures = []
+
+ template_json.each do |json|
+ template = CustomWizard::Template.new(json)
+ template.save(skip_jobs: true, create: true)
+
+ if template.errors.any?
+ failures.push(
+ id: json['id'],
+ messages: template.errors.full_messages.join(', ')
+ )
+ else
+ imported.push(
+ id: json['id'],
+ name: json['name']
+ )
+ end
+ end
+
+ render json: success_json.merge(
+ imported: imported,
+ failures: failures
+ )
+ end
+
+ def destroy
+ destroyed = []
+ failures = []
+
+ @wizard_ids.each do |wizard_id|
+ template = CustomWizard::Template.find(wizard_id)
+
+ if template && CustomWizard::Template.remove(wizard_id)
+ destroyed.push(
+ id: wizard_id,
+ name: template['name']
+ )
+ else
+ failures.push(
+ id: wizard_id,
+ messages: I18n.t("wizard.destroy.error.#{template ? 'default' : 'no_template'}")
+ )
+ end
+ end
+
+ render json: success_json.merge(
+ destroyed: destroyed,
+ failures: failures
+ )
+ end
+
+ private
+
+ def get_wizard_ids
+ if params['wizard_ids'].blank?
+ return render_error(I18n.t('wizard.export.error.select_one'))
+ end
+
+ wizard_ids = []
+
+ params['wizard_ids'].each do |wizard_id|
+ begin
+ wizard_ids.push(wizard_id.underscore)
+ rescue
+ #
+ end
+ end
+
+ if wizard_ids.empty?
+ return render_error(I18n.t('wizard.export.error.invalid_wizards'))
+ end
+
+ @wizard_ids = wizard_ids
+ end
+end
diff --git a/controllers/custom_wizard/admin/transfer.rb b/controllers/custom_wizard/admin/transfer.rb
deleted file mode 100644
index b55fe872..00000000
--- a/controllers/custom_wizard/admin/transfer.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-class CustomWizard::AdminTransferController < CustomWizard::AdminController
- skip_before_action :check_xhr, :only => [:export]
-
- def export
- wizard_ids = params['wizards']
- templates = []
-
- if wizard_ids.nil?
- render json: { error: I18n.t('wizard.export.error.select_one') }
- return
- end
-
- wizard_ids.each do |wizard_id|
- if template = CustomWizard::Template.find(wizard_id)
- templates.push(template)
- end
- end
-
- send_data templates.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
-
- file_size = file.size
- max_file_size = 512 * 1024
-
- if max_file_size < file_size
- render json: { error: I18n.t('wizard.import.error.file_large') }
- return
- end
-
- begin
- template_json = JSON.parse file
- rescue JSON::ParserError
- render json: { error: I18n.t('wizard.import.error.invalid_json') }
- return
- end
-
- success_ids = []
- failed_ids = []
-
- template_json.each do |t_json|
- template = CustomWizard::Template.new(t_json)
- template.save(skip_jobs: true)
-
- if template.errors.any?
- failed_ids.push t_json['id']
- else
- success_ids.push t_json['id']
- end
- end
-
- if success_ids.length == 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/coverage/.last_run.json b/coverage/.last_run.json
deleted file mode 100644
index 8e48d1a2..00000000
--- a/coverage/.last_run.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "result": {
- "covered_percent": 88.16
- }
-}
diff --git a/jobs/clear_after_time_wizard.rb b/jobs/clear_after_time_wizard.rb
index ffaa6fa9..50443691 100644
--- a/jobs/clear_after_time_wizard.rb
+++ b/jobs/clear_after_time_wizard.rb
@@ -4,7 +4,7 @@ module Jobs
def execute(args)
User.human_users.each do |u|
- if u.custom_fields['redirect_to_wizard'] === args[:wizard_id]
+ if u.custom_fields['redirect_to_wizard'] == args[:wizard_id]
u.custom_fields.delete('redirect_to_wizard')
u.save_custom_fields(true)
end
diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb
index fc79e91d..584df568 100644
--- a/lib/custom_wizard/template.rb
+++ b/lib/custom_wizard/template.rb
@@ -36,6 +36,8 @@ class CustomWizard::Template
def self.remove(wizard_id)
wizard = CustomWizard::Wizard.create(wizard_id)
+ return false if !wizard
+
ActiveRecord::Base.transaction do
PluginStore.remove('custom_wizard', wizard.id)
@@ -44,6 +46,8 @@ class CustomWizard::Template
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id)
end
end
+
+ true
end
def self.exists?(wizard_id)
diff --git a/lib/custom_wizard/validator.rb b/lib/custom_wizard/validator.rb
index e98023d0..241e3b8e 100644
--- a/lib/custom_wizard/validator.rb
+++ b/lib/custom_wizard/validator.rb
@@ -1,5 +1,6 @@
class CustomWizard::Validator
include HasErrors
+ include ActiveModel::Model
def initialize(data, opts={})
@data = data
@@ -50,14 +51,14 @@ class CustomWizard::Validator
def check_required(object, type)
CustomWizard::Validator.required[type].each do |property|
if object[property].blank?
- errors.add :validation, I18n.t("wizard.validation.required", property: property)
+ errors.add :base, I18n.t("wizard.validation.required", property: property)
end
end
end
def check_id(object, type)
if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id])
- errors.add :validation, I18n.t("wizard.validation.conflict", id: object[:id])
+ errors.add :base, I18n.t("wizard.validation.conflict", wizard_id: object[:id])
end
end
@@ -75,7 +76,7 @@ class CustomWizard::Validator
end
if invalid_time || active_time.blank? || active_time < Time.now.utc
- errors.add :validation, I18n.t("wizard.validation.after_time")
+ errors.add :base, I18n.t("wizard.validation.after_time")
end
end
end
\ No newline at end of file
diff --git a/plugin.rb b/plugin.rb
index 95608e6e..a2e4ffee 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -42,7 +42,7 @@ after_initialize do
../controllers/custom_wizard/admin/submissions.rb
../controllers/custom_wizard/admin/api.rb
../controllers/custom_wizard/admin/logs.rb
- ../controllers/custom_wizard/admin/transfer.rb
+ ../controllers/custom_wizard/admin/manager.rb
../controllers/custom_wizard/admin/custom_fields.rb
../controllers/custom_wizard/wizard.rb
../controllers/custom_wizard/steps.rb
diff --git a/spec/jobs/clear_after_time_wizard_spec.rb b/spec/jobs/clear_after_time_wizard_spec.rb
index b6d675fe..ad7e0982 100644
--- a/spec/jobs/clear_after_time_wizard_spec.rb
+++ b/spec/jobs/clear_after_time_wizard_spec.rb
@@ -25,6 +25,15 @@ describe Jobs::ClearAfterTimeWizard do
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(
diff --git a/spec/requests/custom_wizard/admin/manager_controller_spec.rb b/spec/requests/custom_wizard/admin/manager_controller_spec.rb
new file mode 100644
index 00000000..3912bf35
--- /dev/null
+++ b/spec/requests/custom_wizard/admin/manager_controller_spec.rb
@@ -0,0 +1,104 @@
+require 'rails_helper'
+
+describe CustomWizard::AdminManagerController do
+ fab!(:admin_user) { Fabricate(:user, admin: true) }
+
+ let(:template) {
+ JSON.parse(File.open(
+ "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
+ ).read)
+ }
+
+ before do
+ sign_in(admin_user)
+
+ template_2 = template.dup
+ template_2["id"] = 'super_mega_fun_wizard_2'
+
+ template_3 = template.dup
+ template_3["id"] = 'super_mega_fun_wizard_3'
+ template_3["after_signup"] = true
+
+ @template_array = [template, template_2, template_3]
+
+ FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exists?(file_from_fixtures_tmp_folder)
+ @tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json')
+ File.write(@tmp_file_path, @template_array.to_json)
+ end
+
+ it 'exports all the wizard templates' do
+ @template_array.each do |template|
+ CustomWizard::Template.save(template, skip_jobs: true)
+ end
+
+ get '/admin/wizards/manager/export.json', params: {
+ wizard_ids: [
+ 'super_mega_fun_wizard',
+ 'super_mega_fun_wizard_2',
+ 'super_mega_fun_wizard_3'
+ ]
+ }
+
+ expect(response.status).to eq(200)
+ expect(response.parsed_body).to match_array(@template_array)
+ end
+
+ context "import" do
+ it "works" do
+ templates = @template_array.map { |t| t.slice('id', 'name') }
+
+ post '/admin/wizards/manager/import.json', params: {
+ file: fixture_file_upload(File.open(@tmp_file_path))
+ }
+
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['imported']).to match_array(templates)
+ expect(CustomWizard::Template.list.map {|t| t.slice('id', 'name') }).to match_array(templates)
+ end
+
+ it 'rejects a template with the same id as a saved template' do
+ templates = @template_array.map { |t| t.slice('id', 'name') }
+
+ post '/admin/wizards/manager/import.json', params: {
+ file: fixture_file_upload(File.open(@tmp_file_path))
+ }
+
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['imported']).to match_array(templates)
+
+ post '/admin/wizards/manager/import.json', params: {
+ file: fixture_file_upload(File.open(@tmp_file_path))
+ }
+
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['failures']).to match_array(
+ @template_array.map do |t|
+ {
+ id: t['id'],
+ messages: I18n.t("wizard.validation.conflict", wizard_id: t['id'])
+ }.as_json
+ end
+ )
+ end
+ end
+
+ it 'destroys wizard templates' do
+ templates = @template_array.map { |t| t.slice('id', 'name') }
+
+ @template_array.each do |template|
+ CustomWizard::Template.save(template, skip_jobs: true)
+ end
+
+ delete '/admin/wizards/manager/destroy.json', params: {
+ wizard_ids: [
+ 'super_mega_fun_wizard',
+ 'super_mega_fun_wizard_2',
+ 'super_mega_fun_wizard_3'
+ ]
+ }
+
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['destroyed']).to match_array(templates)
+ expect(CustomWizard::Template.list.length).to eq(0)
+ end
+end
\ No newline at end of file
diff --git a/spec/requests/custom_wizard/admin/transfer_controller_spec.rb b/spec/requests/custom_wizard/admin/transfer_controller_spec.rb
deleted file mode 100644
index 217311c2..00000000
--- a/spec/requests/custom_wizard/admin/transfer_controller_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'rails_helper'
-
-describe CustomWizard::AdminTransferController do
- fab!(:admin_user) { Fabricate(:user, admin: true) }
-
- let(:template) {
- JSON.parse(File.open(
- "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
- ).read)
- }
-
- before do
- sign_in(admin_user)
-
- 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)
-
- template_3 = template.dup
- template_3["id"] = 'super_mega_fun_wizard_3'
- template_3["after_signup"] = true
- CustomWizard::Template.save(template_3, skip_jobs: true)
-
- @template_array = [template, template_2, template_3]
-
- FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exists?(file_from_fixtures_tmp_folder)
- @tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json')
- File.write(@tmp_file_path, @template_array.to_json)
- end
-
- it 'exports all the wizard templates' do
- get '/admin/wizards/transfer/export.json', params: {
- wizards: [
- 'super_mega_fun_wizard',
- 'super_mega_fun_wizard_2',
- 'super_mega_fun_wizard_3'
- ]
- }
- expect(response.status).to eq(200)
- expect(response.parsed_body).to match_array(@template_array)
- end
-
- it 'imports wizard a template' do
- post '/admin/wizards/transfer/import.json', params: {
- file: fixture_file_upload(File.open(@tmp_file_path))
- }
- expect(response.status).to eq(200)
- expect(response.parsed_body['success']).to eq(@template_array.map { |t| t['id'] })
- end
-end
\ No newline at end of file