0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-09 20:02:54 +01:00
Dieser Commit ist enthalten in:
Angus McLeod 2020-04-13 22:17:22 +10:00
Ursprung dae08e53d4
Commit ab18769820
65 geänderte Dateien mit 995 neuen und 1011 gelöschten Zeilen

Datei anzeigen

@ -1,7 +1,6 @@
import Component from "@ember/component";
import { gt } from '@ember/object/computed';
import { computed } from "@ember/object";
import { removeMapperClasses } from '../lib/wizard-mapper';
export default Component.extend({
classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'],
@ -10,11 +9,5 @@ export default Component.extend({
let key = this.connector;
let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`;
return I18n.t(`admin.wizard.${path}`);
}),
actions: {
onOpen() {
removeMapperClasses(this);
}
}
})
});

Datei anzeigen

@ -2,7 +2,7 @@ import { alias, or, gt } from "@ember/object/computed";
import { computed } from "@ember/object";
import { default as discourseComputed, observes, on } from "discourse-common/utils/decorators";
import { getOwner } from 'discourse-common/lib/get-owner';
import { defaultSelectionType, selectionTypes, removeMapperClasses } from '../lib/wizard-mapper';
import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper';
import { snakeCase } from '../lib/wizard';
import Component from "@ember/component";
import { bind } from "@ember/runloop";
@ -30,6 +30,7 @@ export default Component.extend({
userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }),
listEnabled: computed('options.listSelection', 'inputType', function() { return this.optionEnabled('listSelection') }),
hasTypes: gt('selectorTypes.length', 1),
showTypes: false,
didInsertElement() {
$(document).on("click", bind(this, this.documentClick));
@ -41,15 +42,10 @@ export default Component.extend({
documentClick(e) {
if (this._state == "destroying") return;
let $target = $(e.target);
if (!$target.parents('.wizard-mapper .input').length) {
this.send('disableActive');
}
if (!$target.parents('.type-selector').length) {
this.send('hideTypes');
if (!$target.parents('.type-selector').length && this.showTypes) {
this.set('showTypes', false);
}
},
@ -80,7 +76,7 @@ export default Component.extend({
@discourseComputed('activeType')
comboBoxContent(activeType) {
const controller = getOwner(this).lookup('controller:admin-wizard');
const controller = getOwner(this).lookup('controller:admin-wizards-wizard-show');
let content = controller[`${activeType}s`];
// you can't select the current field in the field context
@ -148,34 +144,14 @@ export default Component.extend({
return this.activeType === type && this[`${type}Enabled`];
},
removeClasses() {
removeMapperClasses(this);
},
actions: {
toggleType(type) {
this.set('activeType', type);
this.send('hideTypes');
this.set('showTypes', false);
},
// jquery is used here to ensure other selectors and types disable properly
showTypes() {
this.removeClasses();
$(this.element).find('.selector-types').addClass('show');
},
hideTypes() {
$(this.element).find('.selector-types').removeClass('show');
},
enableActive() {
this.removeClasses();
$(this.element).addClass('active');
},
disableActive() {
$(this.element).removeClass('active');
toggleTypes() {
this.toggleProperty('showTypes');
}
}
})

Datei anzeigen

@ -1,5 +0,0 @@
import Controller from "@ember/controller";
export default Controller.extend({
queryParams: ['refresh']
});

Datei anzeigen

@ -8,10 +8,10 @@ import EmberObject from "@ember/object";
import { scheduleOnce, later } from "@ember/runloop";
import Controller from "@ember/controller";
import copyText from "discourse/lib/copy-text";
import CustomWizard from '../models/custom-wizard';
export default Controller.extend({
hasName: notEmpty('model.name'),
userFields: alias('model.userFields'),
hasName: notEmpty('wizard.name'),
@observes('currentStep')
resetCurrentObjects() {
@ -25,28 +25,29 @@ export default Controller.extend({
scheduleOnce('afterRender', () => ($("body").addClass('admin-wizard')));
},
@observes('model.name')
@observes('wizard.name')
setId() {
if (!this.model.existingId) {
this.set('model.id', generateId(this.model.name));
const wizard = this.wizard;
if (wizard && !wizard.existingId) {
this.set('wizard.id', generateId(wizard.name));
}
},
@discourseComputed('model.id')
@discourseComputed('wizard.id')
wizardUrl(wizardId) {
return window.location.origin + '/w/' + dasherize(wizardId);
},
@discourseComputed('model.after_time_scheduled')
@discourseComputed('wizard.after_time_scheduled')
nextSessionScheduledLabel(scheduled) {
return scheduled ?
moment(scheduled).format('MMMM Do, HH:mm') :
I18n.t('admin.wizard.after_time_time_label');
},
@discourseComputed('currentStep.id', 'model.save_submissions', 'model.steps.@each.fields[]')
@discourseComputed('currentStep.id', 'wizard.save_submissions', 'wizard.steps.@each.fields[]')
wizardFields(currentStepId, saveSubmissions) {
const allSteps = this.get('model.steps');
const allSteps = this.get('wizard.steps');
let steps = allSteps;
let fields = [];
@ -78,24 +79,17 @@ export default Controller.extend({
error: null
});
const wizard = this.model;
const wizard = this.wizard;
const creating = this.creating;
let opts = {};
wizard.save().then((result) => {
this.model.setProperties(
buildProperties(result.wizard)
);
this.set('saving', false);
if (this.get('newWizard')) {
this.send("refreshAllWizards");
} else {
this.send("refreshWizard");
if (creating) {
opts.create = true;
}
}).catch((result) => {
this.set('saving', false);
wizard.save(opts).then((result) => {
this.send('afterSave', result.wizard_id);
}).catch((result) => {
let errorType = 'failed';
let errorParams = {};
@ -107,21 +101,18 @@ export default Controller.extend({
this.set('error', I18n.t(`admin.wizard.error.${errorType}`, errorParams));
later(() => this.set('error', null), 10000);
});
}).finally(() => this.set('saving', false));
},
remove() {
const wizard = this.get('model');
wizard.remove().then(() => {
this.send("refreshAllWizards");
});
this.wizard.remove().then(() => this.send('afterDestroy'));
},
setNextSessionScheduled() {
let controller = showModal('next-session-scheduled', {
model: {
dateTime: this.get('model.after_time_scheduled'),
update: (dateTime) => this.set('model.after_time_scheduled', dateTime)
dateTime: this.wizard.after_time_scheduled,
update: (dateTime) => this.set('wizard.after_time_scheduled', dateTime)
}
});
@ -129,7 +120,7 @@ export default Controller.extend({
},
toggleAdvanced() {
this.toggleProperty('model.showAdvanced');
this.toggleProperty('wizard.showAdvanced');
},
copyUrl() {

Datei anzeigen

@ -0,0 +1,25 @@
import Controller from "@ember/controller";
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { equal } from '@ember/object/computed';
export default Controller.extend({
creating: equal('wizardId', 'create'),
@discourseComputed('creating', 'wizardId')
wizardListVal(creating, wizardId) {
return creating ? null : wizardId;
},
@discourseComputed('creating', 'wizardId')
message(creating, wizardId) {
let type = 'select';
if (creating) {
type = 'create';
} else if (wizardId) {
type = 'edit';
}
return I18n.t(`admin.wizard.message.${type}`);
}
});

Datei anzeigen

@ -2,14 +2,17 @@ export default {
resource: 'admin',
map() {
this.route('adminWizards', { path: '/wizards', resetNamespace: true }, function() {
this.route('adminWizardsCustom', { path: '/custom', resetNamespace: true }, function() {
this.route('adminWizard', { path: '/:wizard_id', resetNamespace: true });
this.route('adminWizardsWizard', { path: '/wizard/', resetNamespace: true }, function() {
this.route('adminWizardsWizardShow', { path: '/:wizardId/', resetNamespace: true });
});
this.route('adminWizardsSubmissions', { path: '/submissions', resetNamespace: true }, function() {
this.route('adminWizardSubmissions', { path: '/:wizard_id', resetNamespace: true });
});
this.route('adminWizardsApis', { path: '/apis', resetNamespace: true }, function() {
this.route('adminWizardsApi', { path: '/:name', resetNamespace: true });
this.route('adminWizardsSubmissionsShow', { path: '/:wizardId/', resetNamespace: true });
})
this.route('adminWizardsApi', { path: '/api', resetNamespace: true }, function() {
this.route('adminWizardsApiShow', { path: '/:name', resetNamespace: true });
});
this.route('adminWizardsTransfer', { path: '/transfer', resetNamespace: true });

Datei anzeigen

@ -22,12 +22,6 @@ function inputTypesContent(options = {}) {
mapInputTypes(selectableInputTypes);
}
function removeMapperClasses(ctx) {
const $mapper = $(ctx.element).parents('.wizard-mapper');
$mapper.find('.selector-types').removeClass('show');
$mapper.find('.mapper-selector').removeClass('active');
}
// Connectors
const connectors = {
@ -160,7 +154,6 @@ export {
defaultInputType,
defaultSelectionType,
defaultConnector,
removeMapperClasses,
connectorContent,
inputTypesContent,
selectionTypes,

Datei anzeigen

@ -5,19 +5,26 @@ import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard';
import { Promise } from "rsvp";
const CustomWizard = EmberObject.extend({
save() {
save(opts) {
return new Promise((resolve, reject) => {
let json = this.buildJson(this, 'wizard');
let wizard = this.buildJson(this, 'wizard');
if (json.error) {
reject({ error: json.error });
if (wizard.error) {
reject(wizard);
}
ajax("/admin/wizards/custom/save", {
let data = {
wizard
};
if (opts.create) {
data.create = true;
}
ajax(`/admin/wizards/wizard/${wizard.id}`, {
type: 'PUT',
data: {
wizard: JSON.stringify(json)
}
contentType: "application/json",
data: JSON.stringify(data)
}).then((result) => {
if (result.error) {
reject(result);
@ -175,23 +182,18 @@ const CustomWizard = EmberObject.extend({
},
remove() {
return ajax("/admin/wizards/custom/remove", {
type: 'DELETE',
data: {
id: this.get('id')
}
return ajax(`/admin/wizards/wizard/${this.id}`, {
type: 'DELETE'
}).then(() => this.destroy());
}
});
CustomWizard.reopenClass({
all() {
return ajax("/admin/wizards/custom/all", {
return ajax("/admin/wizards/wizard", {
type: 'GET'
}).then(result => {
return result.wizards.map(wizard => {
return CustomWizard.create(wizard);
});
return result.wizard_list;
});
},

Datei anzeigen

@ -1,108 +0,0 @@
import CustomWizard from '../models/custom-wizard';
import { ajax } from 'discourse/lib/ajax';
import { selectKitContent, userProperties, generateName } from '../lib/wizard';
import DiscourseRoute from "discourse/routes/discourse";
import { all } from "rsvp";
export default DiscourseRoute.extend({
beforeModel() {
const param = this.paramsFor('adminWizard').wizard_id;
const wizards = this.modelFor('admin-wizards-custom');
if (wizards.length && (param === 'first' || param === 'last')) {
const wizard = wizards.get(`${param}Object`);
if (wizard) {
this.transitionTo('adminWizard', wizard.id.dasherize());
}
}
},
model(params) {
const wizardId = params.wizard_id;
this.set('newWizard', wizardId === 'new');
if (this.newWizard) {
return CustomWizard.create();
} else {
const wizard = this.modelFor('admin-wizards-custom')
.findBy('id', wizardId.underscore());
if (!wizard) {
return this.transitionTo('adminWizard', 'new');
} else {
return wizard;
}
}
},
afterModel(model) {
return all([
this._getFieldTypes(model),
this._getThemes(model),
this._getApis(model),
this._getUserFields(model)
]);
},
_getFieldTypes(model) {
return ajax('/admin/wizards/field-types')
.then((result) => {
model.set(
'fieldTypes',
selectKitContent([...result.types])
)
});
},
_getThemes(model) {
return ajax('/admin/themes')
.then((result) => {
model.set('themes', result.themes.map(t => {
return {
id: t.id,
name: t.name
}
}));
});
},
_getApis(model) {
return ajax('/admin/wizards/apis')
.then((result) => model.set('apis', result));
},
_getUserFields(model) {
return this.store.findAll('user-field').then((result) => {
if (result && result.content) {
model.set('userFields',
result.content.map((f) => ({
id: `user_field_${f.id}`,
name: f.name
})).concat(
userProperties.map((f) => ({
id: f,
name: generateName(f)
}))
)
);
}
});
},
setupController(controller, model) {
const newWizard = this.get('newWizard');
controller.setProperties({
newWizard,
model,
currentStep: model.steps[0],
currentAction: model.actions[0]
});
},
actions: {
refreshWizard() {
this.refresh();
}
}
});

Datei anzeigen

@ -0,0 +1,22 @@
import CustomWizardApi from '../models/custom-wizard-api';
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
queryParams: {
refresh_list: {
refreshModel: true
}
},
model(params) {
if (params.name === 'new') {
return CustomWizardApi.create({ isNew: true });
} else {
return CustomWizardApi.find(params.name);
}
},
setupController(controller, model){
controller.set("api", model);
}
});

Datei anzeigen

@ -1,22 +1,7 @@
import CustomWizardApi from '../models/custom-wizard-api';
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
queryParams: {
refresh_list: {
refreshModel: true
}
},
model(params) {
if (params.name === 'new') {
return CustomWizardApi.create({ isNew: true });
} else {
return CustomWizardApi.find(params.name);
}
},
setupController(controller, model){
controller.set("api", model);
beforeModel() {
this.transitionTo('adminWizardsApiShow');
}
});

Datei anzeigen

@ -1,32 +0,0 @@
import CustomWizardApi from '../models/custom-wizard-api';
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
model() {
return CustomWizardApi.list();
},
afterModel(model) {
const apiParams = this.paramsFor('admin-wizards-api');
if (model.length) {
if (!apiParams.name) {
this.transitionTo('adminWizardsApi', model[0].name.dasherize());
} else {
return;
}
} else {
this.transitionTo('adminWizardsApi', 'new');
}
},
setupController(controller, model){
controller.set("model", model);
},
actions: {
refreshModel() {
this.refresh();
}
}
});

Datei anzeigen

@ -1,27 +0,0 @@
import CustomWizard from '../models/custom-wizard';
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
model() {
return CustomWizard.all();
},
afterModel(model) {
const transitionToWizard = this.get('transitionToWizard');
if (transitionToWizard && model.length) {
this.set('transitionToWizard', null);
this.transitionTo('adminWizard', transitionToWizard);
};
},
setupController(controller, model){
controller.set("model", model.toArray());
},
actions: {
refreshAllWizards() {
this.set('transitionToWizard', 'last');
this.refresh();
}
}
});

Datei anzeigen

@ -1,7 +0,0 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
redirect() {
this.transitionTo('adminWizardsCustom');
}
});

Datei anzeigen

@ -1,7 +0,0 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
redirect() {
this.transitionTo('adminWizardSubmissions', 'first');
}
});

Datei anzeigen

@ -2,25 +2,8 @@ import CustomWizard from '../models/custom-wizard';
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
beforeModel() {
const param = this.paramsFor('adminWizardSubmissions').wizard_id;
const wizards = this.modelFor('admin-wizards-submissions');
if (wizards.length && (param === 'first')) {
const wizard = wizards.get(`${param}Object`);
if (wizard) {
this.transitionTo('adminWizardSubmissions', wizard.id.dasherize());
}
}
},
model(params) {
const wizardId = params.wizard_id;
if (wizardId && wizardId !== 'new') {
return CustomWizard.submissions(params.wizard_id);
} else {
return {};
}
return CustomWizard.submissions(params.wizardId);
},
setupController(controller, model) {
@ -43,8 +26,6 @@ export default DiscourseRoute.extend({
submissions.push(submission);
});
console.log(model.id)
controller.setProperties({
wizard: model.wizard,
submissions,

Datei anzeigen

@ -1,12 +1,24 @@
import CustomWizard from '../models/custom-wizard';
import DiscourseRoute from "discourse/routes/discourse";
import { ajax } from 'discourse/lib/ajax';
export default DiscourseRoute.extend({
model() {
return CustomWizard.all();
return ajax(`/admin/wizards/wizard`);
},
setupController(controller, model) {
controller.set("model", model);
const showParams = this.paramsFor('adminWizardsSubmissionsShow');
controller.setProperties({
wizardId: showParams.wizardId,
wizardList: model.wizard_list
})
},
actions: {
changeWizard(wizardId) {
this.controllerFor('adminWizardsSubmissions').set('wizardId', wizardId);
this.transitionTo('adminWizardsSubmissionsShow', wizardId);
}
}
});

Datei anzeigen

@ -0,0 +1,37 @@
import CustomWizard from '../models/custom-wizard';
import { ajax } from 'discourse/lib/ajax';
import DiscourseRoute from "discourse/routes/discourse";
import { selectKitContent } from '../lib/wizard';
export default DiscourseRoute.extend({
model(params) {
if (params.wizardId === 'create') {
return { create: true };
} else {
return ajax(`/admin/wizards/wizard/${params.wizardId}`);
}
},
afterModel(model) {
if (model.none) {
return this.transitionTo('adminWizardsWizard');
}
},
setupController(controller, model) {
const parentModel = this.modelFor('adminWizardsWizard');
const wizard = CustomWizard.create((!model || model.create) ? {} : model);
controller.setProperties({
wizardList: parentModel.wizard_list,
fieldTypes: selectKitContent(parentModel.field_types),
userFields: parentModel.userFields,
apis: parentModel.apis,
themes: parentModel.themes,
wizard,
currentStep: wizard.steps[0],
currentAction: wizard.actions[0],
creating: model.create
});
}
});

Datei anzeigen

@ -0,0 +1,92 @@
import DiscourseRoute from "discourse/routes/discourse";
import { userProperties, generateName } from '../lib/wizard';
import { set } from "@ember/object";
import { all } from "rsvp";
import { ajax } from 'discourse/lib/ajax';
export default DiscourseRoute.extend({
model() {
return ajax(`/admin/wizards/wizard`);
},
afterModel(model) {
return all([
this._getThemes(model),
this._getApis(model),
this._getUserFields(model)
]);
},
_getThemes(model) {
return ajax('/admin/themes')
.then((result) => {
set(model, 'themes', result.themes.map(t => {
return {
id: t.id,
name: t.name
}
}));
});
},
_getApis(model) {
return ajax('/admin/wizards/apis')
.then((result) => set(model, 'apis', result));
},
_getUserFields(model) {
return this.store.findAll('user-field').then((result) => {
if (result && result.content) {
set(model, 'userFields',
result.content.map((f) => ({
id: `user_field_${f.id}`,
name: f.name
})).concat(
userProperties.map((f) => ({
id: f,
name: generateName(f)
}))
)
);
}
});
},
setupController(controller, model) {
let props = {
wizardList: model.wizard_list
}
const params = this.paramsFor('adminWizardsWizardShow');
if (params && params.wizardId) {
props.wizardId = params.wizardId;
}
controller.setProperties(props);
},
actions: {
changeWizard(wizardId) {
this.controllerFor('adminWizardsWizard').set('wizardId', wizardId);
if (wizardId) {
this.transitionTo('adminWizardsWizardShow', wizardId);
} else {
this.transitionTo('adminWizardsWizard');
}
},
afterDestroy() {
this.transitionTo('adminWizardsWizard').then(() => this.refresh());
},
afterSave(wizardId) {
this.refresh().then(() => this.send('changeWizard', wizardId));
},
createWizard() {
this.controllerFor('adminWizardsWizard').set('wizardId', 'create');
this.transitionTo('adminWizardsWizardShow', 'create');
}
}
});

Datei anzeigen

@ -0,0 +1,9 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
beforeModel(transition) {
if (transition.targetName === "adminWizards.index") {
this.transitionTo('adminWizardsWizard');
}
},
});

Datei anzeigen

@ -1,27 +0,0 @@
<div class="wizard-header large">
<label>{{i18n 'admin.wizard.submissions.title' name=wizard.name}}</label>
<a class="btn btn-default download-link" href="{{downloadUrl}}" target="_blank">
{{d-icon 'download'}}
<span class="d-button-label">
{{i18n "admin.wizard.submissions.download"}}
</span>
</a>
</div>
<div class="wizard-submissions">
<table>
<tr>
{{#each fields as |f|}}
<th>{{f}}</th>
{{/each}}
</tr>
{{#each submissions as |s|}}
<tr>
{{#each-in s as |k v|}}
<td>{{v}}</td>
{{/each-in}}
</tr>
{{/each}}
</table>
</div>

Datei anzeigen

@ -1,26 +0,0 @@
<div class='row'>
<div class='content-list wizard-list'>
<ul>
{{#each model as |api|}}
<li>
{{#link-to "adminWizardsApi" (dasherize api.name)}}
{{#if api.title}}
{{api.title}}
{{else}}
{{api.name}}
{{/if}}
{{/link-to}}
</li>
{{/each}}
</ul>
<div class="new-api">
{{#link-to 'adminWizardsApi' 'new' class="btn"}}
{{d-icon "plus"}} {{i18n 'admin.wizard.api.new'}}
{{/link-to}}
</div>
</div>
<div class="content">
{{outlet}}
</div>
</div>

Datei anzeigen

@ -1 +0,0 @@
<div class="groups-type-index"></div>

Datei anzeigen

@ -1,20 +0,0 @@
<div class='row'>
<div class='content-list wizard-list'>
<ul>
{{#each model as |w|}}
<li>
{{#link-to "adminWizard" (dasherize w.id)}}{{w.name}}{{/link-to}}
</li>
{{/each}}
</ul>
<div class="new-wizard">
{{#link-to 'adminWizard' 'new' class="btn"}}
{{d-icon "plus"}} {{i18n 'admin.wizard.new'}}
{{/link-to}}
</div>
</div>
<div class="content">
{{outlet}}
</div>
</div>

Datei anzeigen

@ -0,0 +1,29 @@
{{#if submissions}}
<div class="wizard-header large">
<label>{{i18n 'admin.wizard.submissions.title' name=wizard.name}}</label>
<a class="btn btn-default download-link" href="{{downloadUrl}}" target="_blank">
{{d-icon 'download'}}
<span class="d-button-label">
{{i18n "admin.wizard.submissions.download"}}
</span>
</a>
</div>
<div class="wizard-submissions">
<table>
<tr>
{{#each fields as |f|}}
<th>{{f}}</th>
{{/each}}
</tr>
{{#each submissions as |s|}}
<tr>
{{#each-in s as |k v|}}
<td>{{v}}</td>
{{/each-in}}
</tr>
{{/each}}
</table>
</div>
{{/if}}

Datei anzeigen

@ -1,15 +1,13 @@
<div class='row'>
<div class='content-list wizard-list'>
<ul>
{{#each model as |w|}}
<li>
{{#link-to "adminWizardSubmissions" w.id}}{{w.name}}{{/link-to}}
</li>
{{/each}}
</ul>
<div class="admin-wizard-select">
{{combo-box
value=wizardId
content=wizardList
onChange=(route-action 'changeWizard')
options=(hash
none='admin.wizard.select'
)}}
</div>
<div class="content submissions">
<div class="admin-wizard-container">
{{outlet}}
</div>
</div>

Datei anzeigen

@ -1,18 +1,18 @@
<div class="admin-wizard settings">
{{#if wizard}}
<div class="wizard-header large">
{{input
name="name"
value=model.name
value=wizard.name
placeholderKey="admin.wizard.name_placeholder"}}
<div class="wizard-url">
{{#if model.name}}
<a href="{{wizardUrl}}" target="_blank">{{wizardUrl}}</a>
{{#if wizard.name}}
{{#if copiedUrl}}
{{d-button class="btn-hover pull-right" icon="copy" label="ip_lookup.copied"}}
{{else}}
{{d-button action=(action "copyUrl") class="pull-right no-text" icon="copy"}}
{{/if}}
<a href="{{wizardUrl}}" target="_blank">{{wizardUrl}}</a>
{{/if}}
</div>
</div>
@ -25,7 +25,7 @@
<div class="setting-value">
{{input
name="background"
value=model.background
value=wizard.background
placeholderKey="admin.wizard.background_placeholder"
class="small"}}
</div>
@ -37,10 +37,10 @@
</div>
<div class="setting-value">
{{combo-box
content=model.themes
content=themes
valueProperty='id'
value=model.theme_id
onChange=(action (mut model.theme_id))
value=wizard.theme_id
onChange=(action (mut wizard.theme_id))
options=(hash
none='admin.wizard.no_theme'
)}}
@ -58,7 +58,7 @@
<label>{{i18n 'admin.wizard.required'}}</label>
</div>
<div class="setting-value">
{{input type='checkbox' checked=model.required}}
{{input type='checkbox' checked=wizard.required}}
<span>{{i18n 'admin.wizard.required_label'}}</span>
</div>
</div>
@ -68,7 +68,7 @@
<label>{{i18n 'admin.wizard.after_signup'}}</label>
</div>
<div class="setting-value">
{{input type='checkbox' checked=model.after_signup}}
{{input type='checkbox' checked=wizard.after_signup}}
<span>{{i18n 'admin.wizard.after_signup_label'}}</span>
</div>
</div>
@ -78,7 +78,7 @@
<label>{{i18n 'admin.wizard.multiple_submissions'}}</label>
</div>
<div class="setting-value">
{{input type='checkbox' checked=model.multiple_submissions}}
{{input type='checkbox' checked=wizard.multiple_submissions}}
<span>{{i18n 'admin.wizard.multiple_submissions_label'}}</span>
</div>
</div>
@ -88,7 +88,7 @@
<label>{{i18n 'admin.wizard.prompt_completion'}}</label>
</div>
<div class="setting-value">
{{input type='checkbox' checked=model.prompt_completion}}
{{input type='checkbox' checked=wizard.prompt_completion}}
<span>{{i18n 'admin.wizard.prompt_completion_label'}}</span>
</div>
</div>
@ -98,7 +98,7 @@
<label>{{i18n 'admin.wizard.after_time'}}</label>
</div>
<div class="setting-value">
{{input type='checkbox' checked=model.after_time}}
{{input type='checkbox' checked=wizard.after_time}}
<span>{{i18n 'admin.wizard.after_time_label'}}</span>
{{d-button
action='setNextSessionScheduled'
@ -114,7 +114,7 @@
</div>
<div class="setting-value">
{{wizard-mapper
inputs=model.permitted
inputs=wizard.permitted
options=(hash
context='wizard'
inputTypes='assignment,validation'
@ -126,9 +126,9 @@
</div>
</div>
{{wizard-advanced-toggle showAdvanced=model.showAdvanced}}
{{wizard-advanced-toggle showAdvanced=wizard.showAdvanced}}
{{#if model.showAdvanced}}
{{#if wizard.showAdvanced}}
<div class="advanced-settings">
<div class="setting">
@ -136,7 +136,7 @@
<label>{{i18n 'admin.wizard.save_submissions'}}</label>
</div>
<div class="setting-value">
{{input type='checkbox' checked=model.save_submissions}}
{{input type='checkbox' checked=wizard.save_submissions}}
<span>{{i18n 'admin.wizard.save_submissions_label'}}</span>
</div>
</div>
@ -146,7 +146,7 @@
<label>{{i18n 'admin.wizard.restart_on_revisit'}}</label>
</div>
<div class="setting-value">
{{input type='checkbox' checked=model.restart_on_revisit}}
{{input type='checkbox' checked=wizard.restart_on_revisit}}
<span>{{i18n 'admin.wizard.restart_on_revisit_label'}}</span>
</div>
</div>
@ -158,26 +158,27 @@
{{wizard-links
itemType="step"
current=currentStep
items=model.steps}}
items=wizard.steps}}
{{#if currentStep}}
{{wizard-custom-step
step=currentStep
wizard=model
wizard=wizard
currentField=currentField
wizardFields=wizardFields}}
wizardFields=wizardFields
fieldTypes=fieldTypes}}
{{/if}}
{{wizard-links
itemType="action"
current=currentAction
items=model.actions
items=wizard.actions
generateLabels=true}}
{{#if currentAction}}
{{wizard-custom-action
action=currentAction
wizard=model
wizard=wizard
removeAction="removeAction"
wizardFields=wizardFields}}
{{/if}}
@ -187,7 +188,7 @@
{{i18n 'admin.wizard.save'}}
</button>
{{#unless newWizard}}
{{#unless creating}}
<button {{action "remove"}} class='btn btn-danger remove'>
{{d-icon "far-trash-alt"}}{{i18n 'admin.wizard.remove'}}
</button>
@ -199,4 +200,4 @@
<span class="error">{{d-icon "times"}}{{error}}</span>
{{/if}}
</div>
</div>
{{/if}}

Datei anzeigen

@ -0,0 +1,32 @@
<div class="admin-wizard-controls">
{{combo-box
value=wizardListVal
content=wizardList
onChange=(route-action 'changeWizard')
options=(hash
none='admin.wizard.select'
)}}
{{d-button
action="createWizard"
label="admin.wizard.create"
icon="plus"}}
</div>
<div class="admin-wizard-message">
<div class="wizard-message">
{{d-icon 'info-circle'}}
<span>{{message}}</span>
</div>
<div class="wizard-message">
{{d-icon 'question-circle'}}
<a href="https://thepavilion.io/c/knowledge/custom-wizard" target="_blank">
{{i18n 'admin.wizard.message.help'}}
</a>
</div>
</div>
<div class="admin-wizard-container settings">
{{outlet}}
</div>

Datei anzeigen

@ -1,5 +1,5 @@
{{#admin-nav}}
{{nav-item route='adminWizardsCustom' label='admin.wizard.nav_label'}}
{{nav-item route='adminWizardsWizard' label='admin.wizard.nav_label'}}
{{nav-item route='adminWizardsSubmissions' label='admin.wizard.submissions.nav_label'}}
{{#if siteSettings.wizard_api_features}}
{{nav-item route='adminWizardsApis' label='admin.wizard.api.nav_label'}}
@ -7,6 +7,6 @@
{{nav-item route='adminWizardsTransfer' label='admin.wizard.transfer.nav_label'}}
{{/admin-nav}}
<div class="admin-container admin-wizard-container">
<div class="admin-container">
{{outlet}}
</div>

Datei anzeigen

@ -64,7 +64,11 @@
</div>
<div class="setting-value">
{{input type="number" name="min_length" value=field.min_length}}
{{input
type="number"
name="min_length"
value=field.min_length
class="small"}}
</div>
</div>
{{/if}}
@ -88,7 +92,7 @@
</div>
<div class="setting-value">
{{input type="number" value=field.limit}}
{{input type="number" value=field.limit class="small"}}
</div>
</div>
{{/if}}
@ -148,8 +152,12 @@
<div class="setting-label">
<label>{{i18n 'admin.wizard.translation'}}</label>
</div>
<div class="setting-value">
{{input name="key" value=field.key placeholderKey="admin.wizard.translation_placeholder"}}
<div class="setting-value medium">
{{input
name="key"
value=field.key
class="small"
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>

Datei anzeigen

@ -104,7 +104,7 @@
{{#if currentField}}
{{wizard-custom-field
field=currentField
fieldTypes=wizard.fieldTypes
fieldTypes=fieldTypes
removeField="removeField"
wizardFields=wizardFields}}
{{/if}}

Datei anzeigen

@ -8,7 +8,7 @@
id=(dasherize w.id)
change=(action 'checkChanged')}}
{{#link-to "adminWizard" (dasherize w.id)}}
{{#link-to "adminWizardsWizardShow" (dasherize w.id)}}
{{w.name}}
{{/link-to}}
</li>

Datei anzeigen

@ -2,8 +2,7 @@
{{combo-box
value=connector
content=connectors
onChange=(action (mut connector))
onOpen=(action "onOpen")}}
onChange=(action (mut connector))}}
{{else}}
<span class="connector-single">
{{connectorLabel}}

Datei anzeigen

@ -1,9 +1,10 @@
<div class="type-selector">
{{#if hasTypes}}
<a {{action "showTypes"}} class="active">
<a {{action "toggleTypes"}} class="active">
{{activeTypeLabel}}
</a>
{{#if showTypes}}
<div class="selector-types">
{{#each selectorTypes as |item|}}
{{wizard-mapper-selector-type
@ -12,6 +13,7 @@
toggle=(action 'toggleType')}}
{{/each}}
</div>
{{/if}}
{{else}}
<span>{{activeTypeLabel}}</span>
{{/if}}
@ -22,7 +24,6 @@
{{input
type="text"
value=value
click=(action 'enableActive')
placeholder=(i18n placeholderKey)}}
{{/if}}
@ -31,8 +32,6 @@
value=value
content=comboBoxContent
onChange=(action (mut value))
onOpen=(action "enableActive")
onClick=(action 'enableActive')
options=(hash
none=placeholderKey
)}}
@ -43,8 +42,6 @@
content=multiSelectContent
value=value
onChange=(action (mut value))
onOpen=(action "enableActive")
onClose=(action "disableActive")
options=multiSelectOptions}}
{{/if}}
@ -58,8 +55,6 @@
{{tag-chooser
tags=value
filterable=true
onOpen=(action "enableActive")
onClose=(action "disableActive")
options=(hash
none=placeholderKey
)}}
@ -70,7 +65,6 @@
includeMessageableGroups='true'
placeholderKey=placeholderKey
usernames=value
autocomplete="discourse"
click=(action "enableActive")}}
autocomplete="discourse"}}
{{/if}}
</div>

Datei anzeigen

@ -92,6 +92,7 @@
//= require discourse/templates/components/d-button
//= require discourse/templates/components/d-editor
//= require discourse/templates/components/emoji-picker
//= require discourse/templates/components/popup-input-tip
//= require discourse/templates/category-tag-autocomplete
//= require discourse/templates/emoji-selector-autocomplete
//= require discourse/templates/user-selector-autocomplete
@ -107,7 +108,6 @@
//= require preload-store
//= require lodash.js
//= require mousetrap.js
//= require jquery.putcursoratend.js
//= require template_include.js
//= require caret_position.js
//= require popper.js

Datei anzeigen

@ -50,7 +50,7 @@ export function findCustomWizard(wizardId, params = {}) {
}
return ajax({ url, cache: false, dataType: 'json' }).then(result => {
const wizard = result.custom_wizard;
const wizard = result;
if (!wizard) return null;
if (!wizard.completed) {

Datei anzeigen

@ -2,34 +2,29 @@
@import 'wizard-transfer';
@import 'wizard-api';
.admin-wizard-container {
.row {
.admin-wizard-controls {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
> .content {
flex: 1;
margin-top: 10px;
margin-left: 30px;
.admin-wizard-message {
background-color: $primary-low;
padding: 10px;
display: flex;
justify-content: space-between;
}
table {
.admin-wizard-container {
margin-top: 20px;
.row > .content table {
margin-top: 0;
min-width: 100%;
width: auto;
}
}
}
}
.wizard-submissions {
overflow: scroll;
}
.wizard-list {
width: 250px;
min-width: 250px;
margin-top: 10px;
float: none;
}
.wizard-settings-parent {
margin-bottom: 30px;
@ -65,25 +60,7 @@
padding: 20px;
}
// style workdarounds for wizard_step_advanced site setting - to be refactored
.wizard-custom-step .wizard-advanced-toggle + .wizard-links.field,
.wizard-custom-step .advanced-settings + .wizard-links.field,
.wizard-links.action {
margin-top: 40px;
}
.wizard-links.field {
margin-top: 20px;
}
// end workdarounds //
.wizard-settings > .advanced-settings > div.setting {
margin-bottom: 0;
}
.admin-wizard.settings .wizard-basic-details {
.admin-wizard-container.settings .wizard-basic-details {
justify-content: initial;
.setting {
@ -97,23 +74,20 @@
}
}
.new-wizard {
margin-top: 15px;
}
.wizard-header {
margin-bottom: 20px;
&.large {
font-size: 1.5em;
min-height: 31px;
margin-bottom: 30px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: flex-start;
input {
margin-bottom: 0;
width: 350px;
width: 400px;
}
label {
@ -124,6 +98,22 @@
font-size: 1rem;
line-height: 20px;
}
.wizard-url {
display: inline-flex;
margin-left: 20px;
max-width: 50%;
a {
padding: 6px 12px;
font-size: 1rem;
background-color: $primary-low;
}
button {
font-size: 1rem;
}
}
}
&.medium {
@ -138,28 +128,13 @@
&.underline {
text-decoration: underline;
}
.wizard-url {
display: inline-flex;
margin-left: 20px;
a {
padding: 6px 12px;
font-size: 1rem;
background-color: $primary-low;
}
button {
font-size: 1rem;
}
}
}
.admin-wizard-buttons {
margin-top: 20px;
}
.admin-wizard.settings {
.admin-wizard-container.settings {
[class~='setting'] {
display: inline-flex;
@ -208,15 +183,14 @@
cursor: not-allowed;
}
input[type="number"] {
width: 70px;
margin-bottom: 0;
}
input.medium {
width: 200px;
}
input.small {
width: 100px;
}
.uploaded-image-preview {
width: 100%;
max-height: 100px;
@ -239,6 +213,11 @@
float: left;
margin: 5px 7px 0 0;
}
.input .select-kit, > .select-kit {
max-width: 250px !important;
min-width: 250px !important;
}
}
&.full, &.full-inline {
@ -255,7 +234,7 @@
}
.uploaded-image-preview {
max-height: 170px;
max-height: 200px;
}
}
}
@ -293,7 +272,7 @@
}
}
.wizard-custom-action > [class~='setting']:last-of-type {
.wizard-custom-action > [class~='setting']:first-of-type {
margin-bottom: 0;
}
@ -328,7 +307,7 @@
width: 100%;
.d-editor-input {
min-height: 130px;
min-height: 150px;
}
.d-editor-container {

Datei anzeigen

@ -100,40 +100,15 @@
}
}
.mapper-input.assignment,
.mapper-input.validation,
.mapper-input.association {
.mapper-selector {
width: 100%;
max-width: 250px;
min-width: 250px;
position: relative;
> input, .select-kit, .ac-wrap, .autocomplete.ac-user {
width: 250px !important;
}
}
}
.mapper-input.conditional {
.mapper-selector {
max-width: 170px;
min-width: 170px;
&:not(.text).active .input {
width: 250px;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
position: absolute;
z-index: 300;
.select-kit, .ac-wrap, .autocomplete.ac-user, .select-kit-wrapper {
width: 250px !important;
}
}
}
}
.mapper-selector {
width: 100%;
position: relative;
.input {
width: 100%;
@ -170,16 +145,12 @@
.selector-types {
box-shadow: shadow('dropdown');
position: absolute;
display: none;
display: flex;
background: $secondary;
z-index: 200;
padding: 5px 7px;
flex-direction: column;
border: 1px solid $primary-low;
&.show {
display: flex;
}
}
.value-list .remove-value-btn {

Datei anzeigen

@ -8,11 +8,12 @@ en:
wizard:
label: "Wizard"
nav_label: "Wizards"
new: "New"
select: "Select a wizard"
create: "Create Wizard"
name: "Name"
name_placeholder: "wizard name"
background: "Background"
background_placeholder: "#hex"
background_placeholder: "color"
save_submissions: "Save"
save_submissions_label: "Save wizard submissions."
multiple_submissions: "Multiple"
@ -53,6 +54,12 @@ en:
permitted: "Permitted"
advanced: "Advanced"
message:
select: "Select a wizard, or create a new one"
edit: "You're editing a wizard"
create: "You're creating a new wizard"
help: "Stuck? Check out the documentation"
editor:
show: "Show"
hide: "Hide"
@ -97,6 +104,7 @@ en:
required: "{{type}} requires {{property}}"
invalid: "{{property}} is invalid"
dependent: "{{property}} is dependent on {{dependent}}"
conflict: "{{type}} with {{property}} '{{value}}' already exists"
step:
header: "Steps"

Datei anzeigen

@ -12,25 +12,26 @@ Discourse::Application.routes.append do
scope module: 'custom_wizard', constraints: AdminConstraint.new do
get 'admin/wizards' => 'admin#index'
get 'admin/wizards/field-types' => 'admin#field_types'
get 'admin/wizards/custom' => 'admin#index'
get 'admin/wizards/custom/new' => 'admin#index'
get 'admin/wizards/custom/all' => 'admin#custom_wizards'
get 'admin/wizards/custom/:wizard_id' => 'admin#find_wizard'
get 'admin/wizards/custom/:wizard_id' => 'admin#find_wizard'
put 'admin/wizards/custom/save' => 'admin#save'
delete 'admin/wizards/custom/remove' => 'admin#remove'
get 'admin/wizards/submissions' => 'admin#index'
get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions'
get 'admin/wizards/submissions/:wizard_id/download' => 'admin#download_submissions'
get 'admin/wizards/apis' => 'api#list'
get 'admin/wizards/apis/new' => 'api#index'
get 'admin/wizards/apis/:name' => 'api#find'
put 'admin/wizards/apis/:name' => 'api#save'
delete 'admin/wizards/apis/:name' => 'api#remove'
delete 'admin/wizards/apis/logs/:name' => 'api#clearlogs'
get 'admin/wizards/apis/:name/redirect' => 'api#redirect'
get 'admin/wizards/apis/:name/authorize' => 'api#authorize'
get 'admin/wizards/wizard' => 'admin_wizard#index'
get 'admin/wizards/wizard/create' => 'admin#index'
get 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#show'
put 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#save'
delete 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#remove'
get 'admin/wizards/submissions' => 'admin_submissions#index'
get 'admin/wizards/submissions/:wizard_id' => 'admin_submissions#show'
get 'admin/wizards/submissions/:wizard_id/download' => 'admin_submissions#download'
get 'admin/wizards/apis' => 'admin_api#list'
get 'admin/wizards/apis/new' => 'admin_api#index'
get 'admin/wizards/apis/:name' => 'admin_api#find'
put 'admin/wizards/apis/:name' => 'admin_api#save'
delete 'admin/wizards/apis/:name' => 'admin_api#remove'
delete 'admin/wizards/apis/logs/:name' => 'admin_api#clearlogs'
get 'admin/wizards/apis/:name/redirect' => 'admin_api#redirect'
get 'admin/wizards/apis/:name/authorize' => 'admin_api#authorize'
get 'admin/wizards/transfer' => 'transfer#index'
get 'admin/wizards/transfer/export' => 'transfer#export'
post 'admin/wizards/transfer/import' => 'transfer#import'

Datei anzeigen

@ -1,271 +0,0 @@
class CustomWizard::AdminController < ::Admin::AdminController
skip_before_action :check_xhr, only: [:download_submissions]
before_action :ensure_admin
def index
render nothing: true
end
def field_types
render json: { types: CustomWizard::Field.types }
end
def save
result = build_wizard
if result[:error]
render json: { error: result[:error] }
else
wizard = result[:wizard]
existing_wizard = result[:existing_wizard]
after_time = result[:after_time]
ActiveRecord::Base.transaction do
PluginStore.set('custom_wizard', wizard["id"], wizard)
if after_time[:enabled]
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id'])
Jobs.enqueue_at(after_time[:scheduled], :set_after_time_wizard, wizard_id: wizard['id'])
end
if existing_wizard && existing_wizard['after_time'] && !after_time[:enabled]
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id'])
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
end
end
render json: success_json.merge(wizard: wizard)
end
end
def remove
params.require(:id)
wizard = PluginStore.get('custom_wizard', params[:id])
if wizard['after_time']
Jobs.cancel_scheduled_job(:set_after_time_wizard)
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
end
PluginStore.remove('custom_wizard', params[:id])
render json: success_json
end
def find_wizard
params.require(:wizard_id)
wizard = PluginStore.get('custom_wizard', params[:wizard_id].underscore)
render json: success_json.merge(wizard: wizard)
end
def custom_wizards
rows = PluginStoreRow.where(plugin_name: 'custom_wizard').order(:id)
wizards = [*rows].map { |r| CustomWizard::Template.new(r.value) }
render json: success_json.merge(wizards: wizards)
end
def submissions
params.require(:wizard_id)
wizard_id = params[:wizard_id].underscore
wizard = PluginStore.get('custom_wizard', wizard_id)
if wizard.present?
render json: success_json.merge(
submissions: build_submissions(wizard_id),
wizard: wizard.slice(:id, :name)
)
else
head :ok
end
end
def download_submissions
params.require(:wizard_id)
wizard_id = params[:wizard_id].underscore
wizard = PluginStore.get('custom_wizard', wizard_id)
submissions = build_submissions(wizard_id).to_json
send_data submissions,
filename: "#{Discourse.current_hostname}-wizard-submissions-#{wizard['name']}.json",
content_type: "application/json"
end
private
def wizard_params
params.require(:wizard)
params[:wizard]
end
def required_properties
{
wizard: ['id', 'name', 'steps'],
step: ['id'],
field: ['id', 'type'],
action: ['id', 'type']
}
end
def dependent_properties
{
wizard: {
after_time: 'after_time_scheduled'
},
step: {},
field: {},
action: {}
}
end
def check_required(object, type, error)
required_properties[type].each do |property|
if object[property].blank?
error = {
type: 'required',
params: { type: type, property: property }
}
end
end
error
end
def check_depdendent(object, type, error)
dependent_properties[type].each do |property, dependent|
if object[property] && object[dependent].blank?
error = {
type: 'dependent',
params: { property: property, dependent: dependent }
}
end
end
error
end
def validate_wizard(wizard)
error = nil
error = check_required(wizard, :wizard, error)
error = check_depdendent(wizard, :wizard, error)
if !error
wizard['steps'].each do |step|
error = check_required(step, :step, error)
error = check_depdendent(step, :step, error)
break if error.present?
if step['fields'].present?
step['fields'].each do |field|
error = check_required(field, :field, error)
error = check_depdendent(field, :field, error)
break if error.present?
end
end
end
if wizard['actions'].present?
wizard['actions'].each do |action|
error = check_required(action, :action, error)
error = check_depdendent(action, :action, error)
break if error.present?
end
end
end
if error
{ error: error }
else
{ success: true }
end
end
def validate_after_time(wizard, existing_wizard)
new = false
error = nil
enabled = false
scheduled = nil
if wizard["after_time"]
enabled = true
if !wizard["after_time_scheduled"] && !existing_wizard["after_time_scheduled"]
error = 'after_time_need_time'
else
scheduled = Time.parse(wizard["after_time_scheduled"]).utc
new = false
if existing_wizard['after_time_scheduled']
new = scheduled != Time.parse(existing_wizard['after_time_scheduled']).utc
end
begin
error = 'after_time_invalid' if new && scheduled < Time.now.utc
rescue ArgumentError
error = 'after_time_invalid'
end
end
end
if error
{ error: { type: error } }
else
{
new: new,
scheduled: scheduled,
enabled: enabled
}
end
end
def build_wizard
wizard = ::JSON.parse(wizard_params)
existing_wizard = PluginStore.get('custom_wizard', wizard['id']) || {}
validation = validate_wizard(wizard)
return validation if validation[:error]
after_time = validate_after_time(wizard, existing_wizard)
return after_time if after_time[:error]
wizard['steps'].each do |step|
if step['raw_description']
step['description'] = PrettyText.cook(step['raw_description'])
end
end
result = {
wizard: wizard,
existing_wizard: existing_wizard
}
if after_time[:enabled]
result[:after_time] = after_time
end
result
end
def build_submissions(wizard_id)
rows = PluginStoreRow.where(plugin_name: "#{wizard_id}_submissions").order('id DESC')
submissions = [*rows].map do |row|
value = ::JSON.parse(row.value)
if user = User.find_by(id: row.key)
username = user.username
else
username = I18n.t('admin.wizard.submissions.no_user', id: row.key)
end
value.map do |submission|
{
username: username
}.merge!(submission.except("redirect_to"))
end
end.flatten
end
end

Datei anzeigen

@ -0,0 +1,13 @@
class CustomWizard::AdminController < ::Admin::AdminController
before_action :ensure_admin
def index
end
private
def find_wizard
params.require(:wizard_id)
@wizard = CustomWizard::Wizard.create(params[:wizard_id].underscore)
end
end

Datei anzeigen

@ -1,6 +1,4 @@
class CustomWizard::ApiController < ::ApplicationController
before_action :ensure_logged_in
before_action :ensure_admin
class CustomWizard::AdminApiController < CustomWizard::AdminController
skip_before_action :check_xhr, only: [:redirect]
def index

Datei anzeigen

@ -0,0 +1,51 @@
class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
skip_before_action :check_xhr, only: [:download_submissions]
before_action :find_wizard
def index
render json: ActiveModel::ArraySerializer.new(
CustomWizard::Wizard.list,
each_serializer: CustomWizard::BasicWizardSerializer
)
end
def show
result = {}
if wizard = @wizard
submissions = build_submissions(wizard.id)
result[:wizard] = CustomWizard::BasicWizardSerializer.new(wizard, root: false)
result[:submissions] = submissions.as_json
end
render_json_dump(result)
end
def download
send_data build_submissions(@wizard.id).to_json,
filename: "#{Discourse.current_hostname}-wizard-submissions-#{wizard['name']}.json",
content_type: "application/json"
end
private
def build_submissions(wizard_id)
rows = PluginStoreRow.where(plugin_name: "#{wizard_id}_submissions").order('id DESC')
submissions = [*rows].map do |row|
value = ::JSON.parse(row.value)
if user = User.find_by(id: row.key)
username = user.username
else
username = I18n.t('admin.wizard.submissions.no_user', id: row.key)
end
value.map do |submission|
{
username: username
}.merge!(submission.except("redirect_to"))
end
end.flatten
end
end

Datei anzeigen

@ -0,0 +1,133 @@
class CustomWizard::AdminWizardController < CustomWizard::AdminController
before_action :find_wizard, only: [:show, :remove]
def index
render_json_dump(
wizard_list: ActiveModel::ArraySerializer.new(
CustomWizard::Wizard.list,
each_serializer: CustomWizard::BasicWizardSerializer
),
field_types: CustomWizard::Field.types
)
end
def show
params.require(:wizard_id)
if data = CustomWizard::Wizard.find(params[:wizard_id].underscore)
render json: data.as_json
else
render json: { none: true }
end
end
def remove
CustomWizard::Wizard.remove(@wizard.id)
render json: success_json
end
def save
opts = {}
opts[:create] = params[:create] if params[:create]
validator = CustomWizard::Validator.new(save_wizard_params.to_h, opts)
validation = validator.perform
if validation[:error]
render json: { error: validation[:error] }
else
params = validation[:wizard]
if wizard_id = CustomWizard::Wizard.save(params)
render json: success_json.merge(wizard_id: wizard_id)
else
render json: failed_json
end
end
end
private
def mapped_params
[
:type,
:connector,
:output,
:output_type,
:output_connector,
pairs: [
:index,
:key,
:key_type,
:value,
:value_type,
:connector,
value: [],
key: [],
],
output: [],
]
end
def save_wizard_params
params.require(:wizard).permit(
:id,
:name,
:background,
:save_submissions,
:multiple_submissions,
:after_signup,
:after_time,
:after_time_scheduled,
:required,
:prompt_completion,
:restart_on_revisit,
:theme_id,
permitted: mapped_params,
steps: [
:id,
:title,
:key,
:banner,
:raw_description,
:required_data_message,
required_data: mapped_params,
permitted_params: mapped_params,
fields: [
:id,
:label,
:image,
:description,
:required,
:key,
:type,
:min_length,
:file_types,
:limit,
:property,
prefill: mapped_params,
content: mapped_params
]
],
actions: [
:id,
:run_after,
:type,
:code,
:skip_redirect,
:url,
title: mapped_params,
post: mapped_params,
post_builder: mapped_params,
post_template: mapped_params,
category: mapped_params,
tags: mapped_params,
custom_fields: mapped_params,
required: mapped_params,
recipient: mapped_params,
profile_updates: mapped_params,
group: mapped_params
]
)
end
end

Datei anzeigen

@ -12,7 +12,7 @@ class CustomWizard::StepsController < ::ApplicationController
permitted.permit!
end
wizard = CustomWizard::Builder.new(current_user, permitted[:wizard_id].underscore).build
wizard = CustomWizard::Builder.new(permitted[:wizard_id].underscore, current_user).build
updater = wizard.create_updater(permitted[:step_id], permitted[:fields])
updater.update

Datei anzeigen

@ -53,7 +53,7 @@ class CustomWizard::TransferController < ::ApplicationController
failed_ids = []
jsonObject.each do |o|
if !CustomWizard::Template.new(o)
if !CustomWizard::Wizard.new(o)
failed_ids.push o['id']
next
end

Datei anzeigen

@ -6,7 +6,7 @@ class CustomWizard::WizardController < ::ApplicationController
helper_method :theme_ids
def wizard
CustomWizard::Template.new(PluginStore.get('custom_wizard', params[:wizard_id].underscore))
CustomWizard::Wizard.create(params[:wizard_id].underscore, current_user)
end
def wizard_page_title
@ -20,12 +20,14 @@ class CustomWizard::WizardController < ::ApplicationController
def index
respond_to do |format|
format.json do
builder = CustomWizard::Builder.new(current_user, params[:wizard_id].underscore)
builder_opts = {}
builder_opts[:reset] = params[:reset] || builder.wizard.restart_on_revisit
builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user)
if builder.wizard.present?
render_serialized(builder.build(builder_opts, params), ::CustomWizardSerializer)
builder_opts = {}
builder_opts[:reset] = params[:reset] || builder.wizard.restart_on_revisit
built_wizard = builder.build(builder_opts, params)
render_serialized(built_wizard, ::CustomWizard::WizardSerializer, root: false)
else
render json: { error: I18n.t('wizard.none') }
end
@ -34,34 +36,28 @@ class CustomWizard::WizardController < ::ApplicationController
end
end
## clean up if user skips wizard
def skip
params.require(:wizard_id)
wizard_id = params[:wizard_id]
user = current_user
wizard_template = PluginStore.get('custom_wizard', wizard_id.underscore)
wizard = CustomWizard::Wizard.new(user, wizard_template)
if wizard.required && !wizard.completed? && wizard.permitted?
return render json: { error: I18n.t('wizard.no_skip') }
end
result = success_json
user = current_user
if user
submission = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id)).last
submission = wizard.submissions.last
if submission && submission['redirect_to']
result.merge!(redirect_to: submission['redirect_to'])
end
if submission && !wizard.save_submissions
PluginStore.remove("#{wizard_id}_submissions", user.id)
PluginStore.remove("#{wizard.id}_submissions", user.id)
end
if user.custom_fields['redirect_to_wizard'] === wizard_id
if user.custom_fields['redirect_to_wizard'] === wizard.id
user.custom_fields.delete('redirect_to_wizard')
user.save_custom_fields(true)
end

Datei anzeigen

@ -5,7 +5,7 @@ module Jobs
user_ids = []
User.human_users.each do |user|
if CustomWizard::Wizard.set_wizard_redirect(user, args[:wizard_id])
if CustomWizard::Wizard.set_wizard_redirect(args[:wizard_id], user)
user_ids.push(user.id)
end
end

Datei anzeigen

@ -1,14 +1,14 @@
class CustomWizard::Builder
attr_accessor :wizard, :updater, :submissions
def initialize(user=nil, wizard_id)
template = PluginStore.get('custom_wizard', wizard_id)
return if template.blank?
def initialize(wizard_id, user=nil)
params = CustomWizard::Wizard.find(wizard_id)
return nil if params.blank?
@steps = template['steps']
@actions = template['actions']
@wizard = CustomWizard::Wizard.new(user, template)
@submissions = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id)) if user
@wizard = CustomWizard::Wizard.new(params, user)
@steps = params['steps'] || []
@actions = params['actions'] || []
@submissions = @wizard.submissions if user && @wizard
end
def self.sorted_handlers
@ -38,13 +38,8 @@ class CustomWizard::Builder
end
def build(build_opts = {}, params = {})
return @wizard if !SiteSetting.custom_wizard_enabled ||
(!@wizard.multiple_submissions &&
@wizard.completed? &&
!@wizard.user.admin) ||
!@steps ||
!@wizard.permitted?
return nil if !SiteSetting.custom_wizard_enabled || !@wizard
return @wizard if !@wizard.can_access?
reset_submissions if build_opts[:reset]
@ -213,7 +208,7 @@ class CustomWizard::Builder
params[:value] = prefill_field(field_template, step_template) || params[:value]
if field_template['type'] === 'group'
if field_template['type'] === 'group' && params[:value].present?
params[:value] = params[:value].first
end

Datei anzeigen

@ -160,6 +160,10 @@ class CustomWizard::Mapper
end
end
def map_text(value)
interpolate(value)
end
def map_wizard_field(value)
data && !data.key?("submitted_at") && data[value]
end

Datei anzeigen

@ -1,46 +0,0 @@
class CustomWizard::Template
attr_reader :id,
:name,
:background,
:save_submissions,
:multiple_submissions,
:prompt_completion,
:restart_on_revisit,
:after_signup,
:after_time,
:after_time_scheduled,
:required,
:theme_id,
:permitted,
:steps,
:actions
def initialize(data)
data = data.is_a?(String) ? ::JSON.parse(data) : data
return nil if data.blank?
@id = data['id']
@name = data['name']
@background = data['background']
@save_submissions = data['save_submissions'] || false
@multiple_submissions = data['multiple_submissions'] || false
@prompt_completion = data['prompt_completion'] || false
@restart_on_revisit = data['restart_on_revisit'] || false
@after_signup = data['after_signup']
@after_time = data['after_time']
@after_time_scheduled = data['after_time_scheduled']
@required = data['required'] || false
@theme_id = data['theme_id']
@permitted = data['permitted'] || nil
if data['theme']
theme = Theme.find_by(name: data['theme'])
@theme_id = theme.id if theme
end
@steps = data['steps']
@actions = data['actions']
end
end

143
lib/custom_wizard/validator.rb Normale Datei
Datei anzeigen

@ -0,0 +1,143 @@
class CustomWizard::Validator
def initialize(params, opts={})
@params = params
@opts = opts
@error = nil
end
def perform
params = @params
check_id(params, :wizard)
check_required(params, :wizard)
check_depdendent(params, :wizard)
after_time = nil
if !@error && @params[:after_time]
after_time = validate_after_time
end
if !@error
params[:steps].each do |step|
check_required(step, :step)
check_depdendent(step, :step)
break if @error.present?
if params[:fields].present?
params[:fields].each do |field|
check_required(field, :field)
check_depdendent(field, :field)
break if @error.present?
end
end
end
if params[:actions].present?
params[:actions].each do |action|
check_required(action, :action)
check_depdendent(action, :action)
break if @error.present?
end
end
end
if @error
{ error: @error }
else
result = { wizard: params }
result[:after_time] = after_time if after_time
result
end
end
def self.required
{
wizard: ['id', 'name', 'steps'],
step: ['id'],
field: ['id', 'type'],
action: ['id', 'type']
}
end
def self.dependent
{
wizard: {
after_time: 'after_time_scheduled'
},
step: {},
field: {},
action: {}
}
end
private
def check_required(object, type)
CustomWizard::Validator.required[type].each do |property|
if object[property].blank?
@error = {
type: 'required',
params: { type: type, property: property }
}
end
end
end
def check_depdendent(object, type)
CustomWizard::Validator.dependent[type].each do |property, dependent|
if object[property] && object[dependent].blank?
@error = {
type: 'dependent',
params: { property: property, dependent: dependent }
}
end
end
end
def check_id(object, type)
if type === :wizard && @opts[:create] && CustomWizard::Wizard.exists?(object[:id])
@error = {
type: 'conflict',
params: { type: type, property: 'id', value: object[:id] }
}
end
end
def validate_after_time
if !@opts[:create]
wizard = CustomWizard::Wizard.create(params)
end
new = false
error = nil
scheduled = nil
if !@params[:after_time_scheduled] && !wizard[:after_time_scheduled]
error = 'after_time_need_time'
else
scheduled = Time.parse(@params[:after_time_scheduled]).utc
new = false
if wizard[:after_time_scheduled]
new = scheduled != Time.parse(wizard[:after_time_scheduled]).utc
end
begin
error = 'after_time_invalid' if new && scheduled < Time.now.utc
rescue ArgumentError
error = 'after_time_invalid'
end
end
if error
@error = { type: error }
else
{
new: new,
scheduled: scheduled
}
end
end
end

Datei anzeigen

@ -8,10 +8,10 @@ UserHistory.actions[:custom_wizard_step] = 1000
class CustomWizard::Wizard
include ActiveModel::SerializerSupport
attr_reader :steps, :user
attr_accessor :id,
:name,
:background,
:theme_id,
:save_submissions,
:multiple_submissions,
:after_time,
@ -22,20 +22,38 @@ class CustomWizard::Wizard
:restart_on_revisit,
:permitted,
:needs_categories,
:needs_groups
:needs_groups,
:steps,
:actions,
:user
def initialize(user=nil, attrs = {})
@steps = []
def initialize(attrs = {}, user=nil)
@user = user
@first_step = nil
@required = false
@id = attrs['id']
@name = attrs['name']
@background = attrs['background']
@save_submissions = attrs['save_submissions'] || false
@multiple_submissions = attrs['multiple_submissions'] || false
@prompt_completion = attrs['prompt_completion'] || false
@restart_on_revisit = attrs['restart_on_revisit'] || false
@after_signup = attrs['after_signup']
@after_time = attrs['after_time']
@after_time_scheduled = attrs['after_time_scheduled']
@required = attrs['required'] || false
@permitted = attrs['permitted'] || nil
@needs_categories = false
@needs_groups = false
@theme_id = attrs['theme_id']
attrs.each do |key, value|
setter = "#{key}="
send(setter, value) if respond_to?(setter.to_sym, false)
if attrs['theme']
theme = Theme.find_by(name: attrs['theme'])
@theme_id = theme.id if theme
end
@first_step = nil
@steps = []
@actions = []
end
def create_step(step_name)
@ -152,6 +170,13 @@ class CustomWizard::Wizard
end
end
def can_access?
return true if user.admin
return false if multiple_submissions && completed?
return false if !permitted?
return true
end
def reset
::UserHistory.create(
action: ::UserHistory.actions[:custom_wizard_step],
@ -169,6 +194,10 @@ class CustomWizard::Wizard
@groups ||= ::Site.new(Guardian.new(@user)).groups
end
def submissions
Array.wrap(PluginStore.get("#{id}_submissions", @user.id))
end
def self.filter_records(filter)
PluginStoreRow.where("
plugin_name = 'custom_wizard' AND
@ -183,7 +212,7 @@ class CustomWizard::Wizard
records
.sort_by { |record| record.value['permitted'].present? ? 0 : 1 }
.each do |record|
wizard = CustomWizard::Wizard.new(user, JSON.parse(record.value))
wizard = CustomWizard::Wizard.new(JSON.parse(record.value), user)
if wizard.permitted?
result = wizard
@ -200,7 +229,7 @@ class CustomWizard::Wizard
def self.prompt_completion(user)
if (records = filter_records('prompt_completion')).any?
records.reduce([]) do |result, record|
wizard = CustomWizard::Wizard.new(user, ::JSON.parse(record.value))
wizard = CustomWizard::Wizard.new(::JSON.parse(record.value), user)
result.push(id: wizard.id, name: wizard.name) if !wizard.completed?
result
end
@ -248,20 +277,68 @@ class CustomWizard::Wizard
PluginStore.get('custom_wizard', wizard_id)
end
def self.list(user=nil)
PluginStoreRow.where(plugin_name: 'custom_wizard').order(:id)
.map { |record| self.new(JSON.parse(record.value), user) }
end
def self.save(wizard)
existing_wizard = self.create(wizard[:id])
wizard[:steps].each do |step|
if step[:raw_description]
step[:description] = PrettyText.cook(step[:raw_description])
end
end
wizard = wizard.slice!(:create)
ActiveRecord::Base.transaction do
PluginStore.set('custom_wizard', wizard[:id], wizard)
if wizard[:after_time]
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard[:id])
Jobs.enqueue_at(wizard[:after_time_scheduled], :set_after_time_wizard, wizard_id: wizard[:id])
end
if existing_wizard && existing_wizard.after_time && !wizard[:after_time]
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard[:id])
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard[:id])
end
end
wizard[:id]
end
def self.remove(wizard_id)
wizard = self.create(wizard_id)
ActiveRecord::Base.transaction do
if wizard.after_time
Jobs.cancel_scheduled_job(:set_after_time_wizard)
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard.id)
end
PluginStore.remove('custom_wizard', wizard.id)
end
end
def self.exists?(wizard_id)
PluginStoreRow.exists?(plugin_name: 'custom_wizard', key: wizard_id)
end
def self.create(user, wizard_id)
CustomWizard::Wizard.new(user, self.find(wizard_id).to_h)
def self.create(wizard_id, user = nil)
if wizard = self.find(wizard_id)
CustomWizard::Wizard.new(wizard.to_h, user)
end
end
def self.set_submission_redirect(user, wizard_id, url)
PluginStore.set("#{wizard_id.underscore}_submissions", user.id, [{ redirect_to: url }])
end
def self.set_wizard_redirect(user, wizard_id)
wizard = CustomWizard::Wizard.create(user, wizard_id)
def self.set_wizard_redirect(wizard_id, user)
wizard = CustomWizard::Wizard.create(wizard_id, user)
if wizard.permitted?
user.custom_fields['redirect_to_wizard'] = wizard_id

Datei anzeigen

@ -44,11 +44,13 @@ after_initialize do
%w[
../lib/custom_wizard/engine.rb
../config/routes.rb
../controllers/custom_wizard/admin/admin.rb
../controllers/custom_wizard/admin/wizard.rb
../controllers/custom_wizard/admin/submissions.rb
../controllers/custom_wizard/admin/api.rb
../controllers/custom_wizard/wizard.rb
../controllers/custom_wizard/steps.rb
../controllers/custom_wizard/admin.rb
../controllers/custom_wizard/transfer.rb
../controllers/custom_wizard/api.rb
../controllers/application_controller.rb
../controllers/extra_locales_controller.rb
../controllers/invites_controller.rb
@ -60,7 +62,7 @@ after_initialize do
../lib/custom_wizard/field.rb
../lib/custom_wizard/mapper.rb
../lib/custom_wizard/step_updater.rb
../lib/custom_wizard/template.rb
../lib/custom_wizard/validator.rb
../lib/custom_wizard/wizard.rb
../lib/custom_wizard/api/api.rb
../lib/custom_wizard/api/authorization.rb
@ -74,6 +76,7 @@ after_initialize do
../serializers/custom_wizard/api/log_serializer.rb
../serializers/custom_wizard/api_serializer.rb
../serializers/custom_wizard/basic_api_serializer.rb
../serializers/custom_wizard/basic_wizard_serializer.rb
../serializers/custom_wizard/wizard_field_serializer.rb
../serializers/custom_wizard/wizard_step_serializer.rb
../serializers/custom_wizard/wizard_serializer.rb
@ -94,7 +97,7 @@ after_initialize do
if !wizard.completed?
custom_redirect = true
CustomWizard::Wizard.set_wizard_redirect(user, wizard.id)
CustomWizard::Wizard.set_wizard_redirect(wizard.id, user)
end
end
@ -115,7 +118,7 @@ after_initialize do
on(:user_approved) do |user|
if wizard_id = CustomWizard::Wizard.after_signup
CustomWizard::Wizard.set_wizard_redirect(user, wizard_id)
CustomWizard::Wizard.set_wizard_redirect(wizard_id, user)
end
end

Datei anzeigen

@ -0,0 +1,3 @@
class CustomWizard::BasicWizardSerializer < ::ApplicationSerializer
attributes :id, :name
end

Datei anzeigen

@ -1,6 +1,6 @@
# frozen_string_literal: true
class CustomWizardFieldSerializer < ::WizardFieldSerializer
class CustomWizard::FieldSerializer < ::WizardFieldSerializer
attributes :image,
:file_types,

Datei anzeigen

@ -1,17 +1,17 @@
# frozen_string_literal: true
class CustomWizardSerializer < ::WizardSerializer
class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
attributes :id,
:name,
attributes :start,
:background,
:theme_id,
:completed,
:required,
:permitted,
:uncategorized_category_id
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
has_many :steps, serializer: ::CustomWizardStepSerializer, embed: :objects
has_many :categories, serializer: ::BasicCategorySerializer, embed: :objects
has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects
@ -29,6 +29,10 @@ class CustomWizardSerializer < ::WizardSerializer
object.permitted?
end
def start
object.start.id
end
def include_start?
object.start && include_steps?
end

Datei anzeigen

@ -1,9 +1,9 @@
# frozen_string_literal: true
class ::CustomWizardStepSerializer < ::WizardStepSerializer
class CustomWizard::StepSerializer < ::WizardStepSerializer
attributes :permitted, :permitted_message
has_many :fields, serializer: ::CustomWizardFieldSerializer, embed: :objects
has_many :fields, serializer: ::CustomWizard::FieldSerializer, embed: :objects
def title
return PrettyText.cook(object.title) if object.title

Datei anzeigen

@ -17,7 +17,7 @@ describe CustomWizard::Builder do
def build_wizard(t = template, u = user, build_opts = {}, params = {})
CustomWizard::Wizard.add_wizard(t)
CustomWizard::Builder.new(u, 'welcome').build(build_opts, params)
CustomWizard::Builder.new('welcome', u).build(build_opts, params)
end
def add_submission_data(data = {})

Datei anzeigen

@ -16,7 +16,7 @@ describe CustomWizardSerializer do
def build_wizard(t = template, u = user, build_opts = {}, params = {})
CustomWizard::Wizard.add_wizard(t)
CustomWizard::Builder.new(u, 'welcome').build(build_opts, params)
CustomWizard::Builder.new('welcome', u).build(build_opts, params)
end
it 'should return the wizard attributes' do

Datei anzeigen

@ -8,7 +8,7 @@
<%= stylesheet_link_tag "wizard_custom_mobile" %>
<%= stylesheet_link_tag "wizard_locations"%>
<%= stylesheet_link_tag "wizard_events"%>
<%- if theme_ids %>
<%- if theme_ids.present? %>
<%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme) %>
<%- end %>