FEATURE: Wizard Manager
The "Transfer" UI has been upgraded into a full wizard manager, adding additional import/export features and bulk-delete functionality
Dieser Commit ist enthalten in:
Ursprung
37c18ff324
Commit
066eef4ef8
33 geänderte Dateien mit 777 neuen und 396 gelöschten Zeilen
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,13 +1,28 @@
|
||||||
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
||||||
|
import { not, notEmpty } from "@ember/object/computed";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
export default Component.extend({
|
const icons = {
|
||||||
classNames: 'wizard-message',
|
error: 'times-circle',
|
||||||
|
success: 'check-circle',
|
||||||
|
info: 'info-circle'
|
||||||
|
}
|
||||||
|
|
||||||
@discourseComputed('key', 'component')
|
export default Component.extend({
|
||||||
message(key, component) {
|
classNameBindings: [':wizard-message', 'type', 'loading'],
|
||||||
return I18n.t(`admin.wizard.message.${component}.${key}`);
|
showDocumentation: not('loading'),
|
||||||
|
showIcon: not('loading'),
|
||||||
|
hasItems: notEmpty('items'),
|
||||||
|
|
||||||
|
@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')
|
@discourseComputed('component')
|
||||||
|
|
229
assets/javascripts/discourse/controllers/admin-wizards-manager.js.es6
Normale Datei
229
assets/javascripts/discourse/controllers/admin-wizards-manager.js.es6
Normale Datei
|
@ -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 = `<a href='/admin/wizards/wizard/${wizard.id}'>${wizard.name}</a>`;
|
||||||
|
html += `<span class='action'>${I18n.t('admin.wizard.manager.imported')}</span>`;
|
||||||
|
return {
|
||||||
|
icon: 'check-circle',
|
||||||
|
html
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
buildDestroyedItem(destroyed) {
|
||||||
|
let html = `<span data-wizard-id="${destroyed.id}">${destroyed.name}</span>`;
|
||||||
|
html += `<span class='action'>${I18n.t('admin.wizard.manager.destroyed')}</span>`;
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,3 +0,0 @@
|
||||||
import Controller from "@ember/controller";
|
|
||||||
|
|
||||||
export default Controller.extend();
|
|
|
@ -19,7 +19,7 @@ export default {
|
||||||
|
|
||||||
this.route('adminWizardsLogs', { path: '/logs', resetNamespace: true });
|
this.route('adminWizardsLogs', { path: '/logs', resetNamespace: true });
|
||||||
|
|
||||||
this.route('adminWizardsTransfer', { path: '/transfer', resetNamespace: true });
|
this.route('adminWizardsManager', { path: '/manager', resetNamespace: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
43
assets/javascripts/discourse/models/custom-wizard-manager.js.es6
Normale Datei
43
assets/javascripts/discourse/models/custom-wizard-manager.js.es6
Normale Datei
|
@ -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;
|
|
@ -4,6 +4,7 @@ import { buildProperties, present, mapped } from '../lib/wizard-json';
|
||||||
import { listProperties, camelCase, snakeCase } from '../lib/wizard';
|
import { listProperties, camelCase, snakeCase } from '../lib/wizard';
|
||||||
import wizardSchema from '../lib/wizard-schema';
|
import wizardSchema from '../lib/wizard-schema';
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
const CustomWizard = EmberObject.extend({
|
const CustomWizard = EmberObject.extend({
|
||||||
save(opts) {
|
save(opts) {
|
||||||
|
@ -185,7 +186,7 @@ const CustomWizard = EmberObject.extend({
|
||||||
remove() {
|
remove() {
|
||||||
return ajax(`/admin/wizards/wizard/${this.id}`, {
|
return ajax(`/admin/wizards/wizard/${this.id}`, {
|
||||||
type: 'DELETE'
|
type: 'DELETE'
|
||||||
}).then(() => this.destroy());
|
}).then(() => this.destroy()).catch(popupAjaxError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -195,13 +196,13 @@ CustomWizard.reopenClass({
|
||||||
type: 'GET'
|
type: 'GET'
|
||||||
}).then(result => {
|
}).then(result => {
|
||||||
return result.wizard_list;
|
return result.wizard_list;
|
||||||
});
|
}).catch(popupAjaxError);
|
||||||
},
|
},
|
||||||
|
|
||||||
submissions(wizardId) {
|
submissions(wizardId) {
|
||||||
return ajax(`/admin/wizards/submissions/${wizardId}`, {
|
return ajax(`/admin/wizards/submissions/${wizardId}`, {
|
||||||
type: "GET"
|
type: "GET"
|
||||||
});
|
}).catch(popupAjaxError);
|
||||||
},
|
},
|
||||||
|
|
||||||
create(wizardJson = {}) {
|
create(wizardJson = {}) {
|
||||||
|
|
83
assets/javascripts/discourse/templates/admin-wizards-manager.hbs
Normale Datei
83
assets/javascripts/discourse/templates/admin-wizards-manager.hbs
Normale Datei
|
@ -0,0 +1,83 @@
|
||||||
|
<div class="admin-wizard-controls">
|
||||||
|
<h3>{{i18n 'admin.wizard.manager.title'}}</h3>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
{{#if filename}}
|
||||||
|
<div class="filename">
|
||||||
|
<a {{action 'clearFile'}}>
|
||||||
|
{{d-icon 'times'}}
|
||||||
|
</a>
|
||||||
|
<span>{{filename}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{input
|
||||||
|
id='file-upload'
|
||||||
|
type="file"
|
||||||
|
accept="application/json"
|
||||||
|
change=(action "setFile")}}
|
||||||
|
{{d-button
|
||||||
|
id="upload-button"
|
||||||
|
label="admin.wizard.manager.upload"
|
||||||
|
action=(action "upload")}}
|
||||||
|
{{d-button
|
||||||
|
id="import-button"
|
||||||
|
label="admin.wizard.manager.import"
|
||||||
|
action=(action "import")
|
||||||
|
disabled=importDisabled}}
|
||||||
|
{{d-button
|
||||||
|
id="export-button"
|
||||||
|
label="admin.wizard.manager.export"
|
||||||
|
action=(action "export")
|
||||||
|
disabled=exportDisabled}}
|
||||||
|
{{d-button
|
||||||
|
id="destroy-button"
|
||||||
|
label="admin.wizard.manager.destroy"
|
||||||
|
action=(action "destroy")
|
||||||
|
disabled=destoryDisabled}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{wizard-message
|
||||||
|
key=messageKey
|
||||||
|
url=messageUrl
|
||||||
|
type=messageType
|
||||||
|
opts=messageOpts
|
||||||
|
items=messageItems
|
||||||
|
loading=loading
|
||||||
|
component='manager'}}
|
||||||
|
|
||||||
|
<div class="admin-wizard-container">
|
||||||
|
<table class="table grid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{i18n 'admin.wizard.label'}}</th>
|
||||||
|
<th class="control-column">{{i18n 'admin.wizard.manager.export'}}</th>
|
||||||
|
<th class="control-column">{{i18n 'admin.wizard.manager.destroy'}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each wizards as |wizard|}}
|
||||||
|
<tr data-wizard-id={{dasherize wizard.id}}>
|
||||||
|
<td>
|
||||||
|
{{#link-to "adminWizardsWizardShow" (dasherize wizard.id)}}
|
||||||
|
{{wizard.name}}
|
||||||
|
{{/link-to}}
|
||||||
|
</td>
|
||||||
|
<td class="control-column">
|
||||||
|
{{input
|
||||||
|
type="checkbox"
|
||||||
|
class="export"
|
||||||
|
change=(action 'selectWizard')}}
|
||||||
|
</td>
|
||||||
|
<td class="control-column">
|
||||||
|
{{input
|
||||||
|
type="checkbox"
|
||||||
|
class="destroy"
|
||||||
|
change=(action 'selectWizard')}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
|
@ -1,2 +0,0 @@
|
||||||
{{wizard-export wizards=wizards}}
|
|
||||||
{{wizard-import}}
|
|
|
@ -6,7 +6,7 @@
|
||||||
{{nav-item route='adminWizardsApi' label='admin.wizard.api.nav_label'}}
|
{{nav-item route='adminWizardsApi' label='admin.wizard.api.nav_label'}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{nav-item route='adminWizardsLogs' label='admin.wizard.log.nav_label'}}
|
{{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}}
|
{{/admin-nav}}
|
||||||
|
|
||||||
<div class="admin-container">
|
<div class="admin-container">
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<h2>{{i18n 'admin.wizard.transfer.export.label'}}</h2>
|
|
||||||
|
|
||||||
<ul class="wizard-list-select">
|
|
||||||
{{#each wizards as |w|}}
|
|
||||||
<li>
|
|
||||||
{{input
|
|
||||||
type="checkbox"
|
|
||||||
id=(dasherize w.id)
|
|
||||||
change=(action 'checkChanged')}}
|
|
||||||
|
|
||||||
{{#link-to "adminWizardsWizardShow" (dasherize w.id)}}
|
|
||||||
{{w.name}}
|
|
||||||
{{/link-to}}
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{{d-button id="export-button"
|
|
||||||
class="btn btn-primary side"
|
|
||||||
label="admin.wizard.transfer.export.label"
|
|
||||||
action=(action "export")}}
|
|
||||||
|
|
||||||
{{#if exportMessage}}
|
|
||||||
<div class="export-message">
|
|
||||||
{{exportMessage}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
|
@ -1,32 +0,0 @@
|
||||||
<h2>{{i18n 'admin.wizard.transfer.import.label'}}</h2>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
{{input id='file-url' type="file" change=(action "setFilePath")}}
|
|
||||||
|
|
||||||
{{#if importMessage}}
|
|
||||||
<div class="import-message">
|
|
||||||
{{importMessage}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{d-button id="import-button"
|
|
||||||
class="btn btn-primary side"
|
|
||||||
label="admin.wizard.transfer.import.label"
|
|
||||||
action=(action "import")}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if hasLogs}}
|
|
||||||
<div class="import-logs">
|
|
||||||
<div class="title">
|
|
||||||
{{i18n 'admin.wizard.transfer.import.logs' fileName=fileName}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{{#each logs as |l|}}
|
|
||||||
<li class="import-log">
|
|
||||||
{{i18n (concat 'admin.wizard.transfer.import.' l.type) id=l.id}}
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
|
@ -1,8 +1,21 @@
|
||||||
<div class="message-block">
|
<div class="message-block primary">
|
||||||
{{d-icon 'info-circle'}}
|
{{#if showIcon}}
|
||||||
<span>{{message}}</span>
|
{{d-icon icon}}
|
||||||
|
{{/if}}
|
||||||
|
<span class="message-content">{{{message}}}</span>
|
||||||
|
{{#if hasItems}}
|
||||||
|
<ul>
|
||||||
|
{{#each items as |item|}}
|
||||||
|
<li>
|
||||||
|
<span>{{d-icon item.icon}}</span>
|
||||||
|
<span>{{{item.html}}}</span>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if showDocumentation}}
|
||||||
<div class="message-block">
|
<div class="message-block">
|
||||||
{{d-icon 'question-circle'}}
|
{{d-icon 'question-circle'}}
|
||||||
|
|
||||||
|
@ -10,3 +23,4 @@
|
||||||
{{documentation}}
|
{{documentation}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
|
@ -1,5 +1,5 @@
|
||||||
@import 'wizard-mapper';
|
@import 'wizard-mapper';
|
||||||
@import 'wizard-transfer';
|
@import 'wizard-manager';
|
||||||
@import 'wizard-api';
|
@import 'wizard-api';
|
||||||
|
|
||||||
.admin-wizard-controls {
|
.admin-wizard-controls {
|
||||||
|
@ -20,22 +20,50 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.message-block {
|
.message-block {
|
||||||
.d-icon {
|
.d-icon {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.d-icon-check-circle {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon-times-circle {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
a + a {
|
a + a {
|
||||||
border-left: 1px solid $primary-medium;
|
border-left: 1px solid $primary-medium;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
margin-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 {
|
& + div {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-submissions {
|
.wizard-submissions {
|
||||||
|
|
60
assets/stylesheets/common/wizard-manager.scss
Normale Datei
60
assets/stylesheets/common/wizard-manager.scss
Normale Datei
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -75,6 +75,18 @@ en:
|
||||||
custom_fields:
|
custom_fields:
|
||||||
create: "Create or edit a custom field record"
|
create: "Create or edit a custom field record"
|
||||||
documentation: Check out the custom field documentation
|
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:
|
editor:
|
||||||
show: "Show"
|
show: "Show"
|
||||||
|
@ -373,18 +385,15 @@ en:
|
||||||
log:
|
log:
|
||||||
nav_label: "Logs"
|
nav_label: "Logs"
|
||||||
|
|
||||||
transfer:
|
manager:
|
||||||
nav_label: "Transfer"
|
nav_label: Manage
|
||||||
export:
|
title: Manage Wizards
|
||||||
label: "Export"
|
export: Export
|
||||||
none_selected: "Please select atleast one wizard"
|
import: Import
|
||||||
import:
|
imported: imported
|
||||||
label: "Import"
|
upload: Select wizards.json
|
||||||
logs: "Import logs for {{fileName}}"
|
destroy: Destroy
|
||||||
success: 'Wizard "{{id}}" saved successfully'
|
destroyed: destroyed
|
||||||
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:
|
wizard_js:
|
||||||
group:
|
group:
|
||||||
|
|
|
@ -303,7 +303,7 @@ pt_br:
|
||||||
log:
|
log:
|
||||||
nav_label: "Logs"
|
nav_label: "Logs"
|
||||||
|
|
||||||
transfer:
|
manager:
|
||||||
nav_label: "Transferir"
|
nav_label: "Transferir"
|
||||||
export:
|
export:
|
||||||
label: "Exportar"
|
label: "Exportar"
|
||||||
|
|
|
@ -30,18 +30,23 @@ en:
|
||||||
no_skip: "Wizard can't be skipped"
|
no_skip: "Wizard can't be skipped"
|
||||||
export:
|
export:
|
||||||
error:
|
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:
|
import:
|
||||||
error:
|
error:
|
||||||
no_file: "No file selected"
|
no_file: "No file selected"
|
||||||
file_large: "File too large"
|
file_large: "File too large"
|
||||||
invalid_json: "File is not a valid json file"
|
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:
|
validation:
|
||||||
required: "%{property} is required"
|
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"
|
after_time: "After time setting is invalid"
|
||||||
|
|
||||||
site_settings:
|
site_settings:
|
||||||
|
|
|
@ -36,8 +36,9 @@ Discourse::Application.routes.append do
|
||||||
|
|
||||||
get 'admin/wizards/logs' => 'admin_logs#index'
|
get 'admin/wizards/logs' => 'admin_logs#index'
|
||||||
|
|
||||||
get 'admin/wizards/transfer' => 'admin_transfer#index'
|
get 'admin/wizards/manager' => 'admin_manager#index'
|
||||||
get 'admin/wizards/transfer/export' => 'admin_transfer#export'
|
get 'admin/wizards/manager/export' => 'admin_manager#export'
|
||||||
post 'admin/wizards/transfer/import' => 'admin_transfer#import'
|
post 'admin/wizards/manager/import' => 'admin_manager#import'
|
||||||
|
delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy'
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -15,4 +15,8 @@ class CustomWizard::AdminController < ::Admin::AdminController
|
||||||
def custom_field_list
|
def custom_field_list
|
||||||
serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer)
|
serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_error(message)
|
||||||
|
render json: failed_json.merge(error: message)
|
||||||
|
end
|
||||||
end
|
end
|
123
controllers/custom_wizard/admin/manager.rb
Normale Datei
123
controllers/custom_wizard/admin/manager.rb
Normale Datei
|
@ -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
|
|
@ -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
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"result": {
|
|
||||||
"covered_percent": 88.16
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ module Jobs
|
||||||
|
|
||||||
def execute(args)
|
def execute(args)
|
||||||
User.human_users.each do |u|
|
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.custom_fields.delete('redirect_to_wizard')
|
||||||
u.save_custom_fields(true)
|
u.save_custom_fields(true)
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,6 +36,8 @@ class CustomWizard::Template
|
||||||
def self.remove(wizard_id)
|
def self.remove(wizard_id)
|
||||||
wizard = CustomWizard::Wizard.create(wizard_id)
|
wizard = CustomWizard::Wizard.create(wizard_id)
|
||||||
|
|
||||||
|
return false if !wizard
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
PluginStore.remove('custom_wizard', wizard.id)
|
PluginStore.remove('custom_wizard', wizard.id)
|
||||||
|
|
||||||
|
@ -44,6 +46,8 @@ class CustomWizard::Template
|
||||||
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id)
|
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.exists?(wizard_id)
|
def self.exists?(wizard_id)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
class CustomWizard::Validator
|
class CustomWizard::Validator
|
||||||
include HasErrors
|
include HasErrors
|
||||||
|
include ActiveModel::Model
|
||||||
|
|
||||||
def initialize(data, opts={})
|
def initialize(data, opts={})
|
||||||
@data = data
|
@data = data
|
||||||
|
@ -50,14 +51,14 @@ class CustomWizard::Validator
|
||||||
def check_required(object, type)
|
def check_required(object, type)
|
||||||
CustomWizard::Validator.required[type].each do |property|
|
CustomWizard::Validator.required[type].each do |property|
|
||||||
if object[property].blank?
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_id(object, type)
|
def check_id(object, type)
|
||||||
if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id])
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ class CustomWizard::Validator
|
||||||
end
|
end
|
||||||
|
|
||||||
if invalid_time || active_time.blank? || active_time < Time.now.utc
|
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
|
end
|
||||||
end
|
end
|
|
@ -42,7 +42,7 @@ after_initialize do
|
||||||
../controllers/custom_wizard/admin/submissions.rb
|
../controllers/custom_wizard/admin/submissions.rb
|
||||||
../controllers/custom_wizard/admin/api.rb
|
../controllers/custom_wizard/admin/api.rb
|
||||||
../controllers/custom_wizard/admin/logs.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/admin/custom_fields.rb
|
||||||
../controllers/custom_wizard/wizard.rb
|
../controllers/custom_wizard/wizard.rb
|
||||||
../controllers/custom_wizard/steps.rb
|
../controllers/custom_wizard/steps.rb
|
||||||
|
|
|
@ -25,6 +25,15 @@ describe Jobs::ClearAfterTimeWizard do
|
||||||
|
|
||||||
CustomWizard::Template.save(after_time_template)
|
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')
|
described_class.new.execute(wizard_id: 'super_mega_fun_wizard')
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
|
104
spec/requests/custom_wizard/admin/manager_controller_spec.rb
Normale Datei
104
spec/requests/custom_wizard/admin/manager_controller_spec.rb
Normale Datei
|
@ -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
|
|
@ -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
|
|
Laden …
In neuem Issue referenzieren