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-05 11:37:09 +10:00
Ursprung 666c4f1eb4
Commit 7b3ed54f29
46 geänderte Dateien mit 363 neuen und 496 gelöschten Zeilen

Datei anzeigen

@ -1,13 +1,9 @@
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
import { equal, not, empty, or } from "@ember/object/computed";
import {
actionTypes,
generateName,
selectKitContent,
profileFields
} from '../lib/wizard';
import { actionTypes, generateName, selectKitContent, profileFields } from '../lib/wizard';
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNames: 'wizard-custom-action',
types: actionTypes.map(t => ({ id: t, name: generateName(t) })),
createTopic: equal('action.type', 'create_topic'),

Datei anzeigen

@ -1,8 +1,9 @@
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
import { equal, not, or } from "@ember/object/computed";
import { selectKitContent } from '../lib/wizard';
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNames: 'wizard-custom-field',
isDropdown: equal('field.type', 'dropdown'),
isUpload: equal('field.type', 'upload'),
@ -10,13 +11,9 @@ export default Ember.Component.extend({
isGroup: equal('field.type', 'group'),
isTag: equal('field.type', 'tag'),
disableId: not('field.isNew'),
choicesTypes: selectKitContent(['translation', 'custom']),
choicesTranslation: equal('field.choices_type', 'translation'),
choicesCustom: equal('field.choices_type', 'custom'),
categoryPropertyTypes: selectKitContent(['id', 'slug']),
prefillEnabled: or('isCategory', 'isTag', 'isGroup'),
contentEnabled: or('isCategory', 'isTag', 'isGroup'),
hasAdvanced: or('isCategory', 'isTag', 'isGroup'),
prefillEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
contentEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
@discourseComputed('field.type')
isInput: (type) => type === 'text' || type === 'textarea' || type === 'url',
@ -33,34 +30,41 @@ export default Ember.Component.extend({
},
@discourseComputed('field.type')
prefillOptions(fieldType) {
if (!this.prefillEnabled) return {};
contentOptions(fieldType) {
let options = {
hasOutput: true,
wizardFieldSelection: true,
textSelection: 'key,value',
wizardSelection: true,
userFieldSelection: 'key,value'
}
options[`${fieldType}Selection`] = 'output';
options[`outputDefaultSelection`] = fieldType;
if (this.isDropdown) {
options.inputTypes = 'pair,assignment';
options.pairConnector = 'equal';
options.keyPlaceholder = 'admin.wizard.key';
options.valuePlaceholder = 'admin.wizard.value';
}
return options;
},
@discourseComputed('field.type')
contentOptions(fieldType) {
if (!this.contentEnabled) return {};
prefillOptions(fieldType) {
let options = {
hasOutput: true,
wizardSelection: 'key,value',
userFieldSelection: 'key,value',
textSelection: 'key,value'
wizardFieldSelection: true,
textSelection: 'key,value',
userFieldSelection: 'key,value'
}
if (!this.isDropdown) {
let selectionType = {
category: 'category',
tag: 'tag',
group: 'group',
dropdown: 'text'
}[fieldType];
options[`${selectionType}Selection`] = 'output';
options.outputDefaultSelection = selectionType;
}
options[`${fieldType}Selection`] = 'output';
return options;
},

Datei anzeigen

@ -1,8 +1,9 @@
import { observes, on, default as discourseComputed } from 'discourse-common/utils/decorators';
import { not } from "@ember/object/computed";
import EmberObject from "@ember/object";
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNames: 'wizard-custom-step',
currentField: null,
currentAction: null,

Datei anzeigen

@ -1,6 +1,9 @@
export default Ember.Component.extend({
import Component from "@ember/component";
import { A } from "@ember/array";
export default Component.extend({
classNames: ['container', 'export'],
selected: Ember.A(),
selected: A(),
actions: {
checkChanged(event) {

Datei anzeigen

@ -1,11 +1,13 @@
import { ajax } from 'discourse/lib/ajax';
import { default as computed } from 'discourse-common/utils/decorators';
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { notEmpty } from "@ember/object/computed";
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNames: ['container', 'import'],
hasLogs: Ember.computed.notEmpty('logs'),
hasLogs: notEmpty('logs'),
@computed('successIds', 'failureIds')
@discourseComputed('successIds', 'failureIds')
logs(successIds, failureIds) {
let logs = [];

Datei anzeigen

@ -1,11 +1,13 @@
import { default as computed, on, observes } from 'discourse-common/utils/decorators';
import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators';
import { notEmpty } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import { scheduleOnce, bind } from "@ember/runloop";
import EmberObject from "@ember/object";
import Component from "@ember/component";
import { A } from "@ember/array";
export default Ember.Component.extend({
export default Component.extend({
classNameBindings: [':wizard-links', 'type'],
items: Ember.A(),
items: A(),
anyLinks: notEmpty('links'),
@on('didInsertElement')
@ -18,7 +20,7 @@ export default Ember.Component.extend({
$(this.element).find("ul").sortable({tolerance: 'pointer'}).on('sortupdate', (e, ui) => {
const itemId = ui.item.data('id');
const index = ui.item.index();
Ember.run.bind(this, this.updateItemOrder(itemId, index));
bind(this, this.updateItemOrder(itemId, index));
});
},
@ -30,10 +32,10 @@ export default Ember.Component.extend({
scheduleOnce('afterRender', this, () => this.applySortable());
},
@computed('type')
@discourseComputed('type')
header: (type) => `admin.wizard.${type}.header`,
@computed('items.@each.id', 'current')
@discourseComputed('items.@each.id', 'current')
links(items, current) {
if (!items) return;
@ -64,8 +66,8 @@ export default Ember.Component.extend({
let params = { id: newId, isNew: true };
if (type === 'step') {
params['fields'] = Ember.A();
params['actions'] = Ember.A();
params['fields'] = A();
params['actions'] = A();
};
const newItem = EmberObject.create(params);

Datei anzeigen

@ -1,13 +1,16 @@
import { computed, set } from "@ember/object";
import { alias, equal } from "@ember/object/computed";
import { alias, equal, or } from "@ember/object/computed";
import { newPair, connectorContent, inputTypesContent } from '../lib/wizard-mapper';
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNameBindings: [':mapper-input', 'type'],
inputType: alias('input.type'),
isConditional: equal('inputType', 'conditional'),
hasOutput: alias('options.hasOutput'),
hasPairs: computed('hasOutput', 'isConditional', function() { return !this.hasOutput || this.isConditional; }),
isAssignment: equal('inputType', 'assignment'),
isPair: equal('inputType', 'pair'),
hasOutput: or('isConditional', 'isAssignment'),
hasPairs: or('isConditional', 'isPair'),
connectors: computed(function() { return connectorContent('output', this.input.type, this.options) }),
inputTypes: computed(function() { return inputTypesContent(this.options) }),

Datei anzeigen

@ -1,15 +1,12 @@
import { connectorContent } from '../lib/wizard-mapper';
import { gt, or, alias } from "@ember/object/computed";
import { computed, observes } from "@ember/object";
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNameBindings: [':mapper-pair', 'hasConnector::no-connector'],
firstPair: gt('pair.index', 0),
showRemove: alias('firstPair'),
showJoin: computed('pair.pairCount', function() {
return this.pair.index < (this.pair.pairCount - 1);
}),
connectors: computed(function() {
return connectorContent('pair', this.inputType, this.options);
})
showJoin: computed('pair.pairCount', function() { return this.pair.index < (this.pair.pairCount - 1) }),
connectors: computed(function() { return connectorContent('pair', this.inputType, this.options) })
});

Datei anzeigen

@ -1,27 +1,17 @@
import discourseComputed from 'discourse-common/utils/decorators';
import { snakeCase } from '../lib/wizard';
import { selectionTypes } from '../lib/wizard-mapper';
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
tagName: 'a',
classNameBindings: ['type', 'active'],
@discourseComputed('type', 'activeType')
active(type, activeType) {
return type === activeType;
},
active(type, activeType) { return type === activeType },
@discourseComputed('type')
label(type) {
let map = {
text: I18n.t('admin.wizard.text'),
wizard: I18n.t('admin.wizard.label'),
userField: I18n.t('users_lowercase.one'),
category: I18n.t('categories.category'),
tag: I18n.t('tagging.tags'),
group: I18n.t('groups.title.one'),
user: I18n.t('users_lowercase.other')
};
return map[type].toLowerCase();
},
label(type) { return I18n.t(`admin.wizard.selector.label.${snakeCase(type)}`) },
click() {
this.toggle(this.type)

Datei anzeigen

@ -1,15 +1,38 @@
import { alias } from "@ember/object/computed";
import { alias, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import { default as discourseComputed, observes } from "discourse-common/utils/decorators";
import { getOwner } from 'discourse-common/lib/get-owner';
import { defaultSelectionType } from '../lib/wizard-mapper';
import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper';
import { snakeCase, selectKitContent } from '../lib/wizard';
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNames: 'mapper-selector',
groups: alias('site.groups'),
categories: computed(function() {
return this.site.categories.map(c => ({ id: c.id, name: c.name }));
}),
categories: computed(function() { return selectKitContent(this.site.categories) }),
showText: computed('activeType', function() { return this.showInput('text') }),
showWizardField: computed('activeType', function() { return this.showInput('wizardField') }),
showUserField: computed('activeType', function() { return this.showInput('userField') }),
showCategory: computed('activeType', function() { return this.showInput('category') }),
showTag: computed('activeType', function() { return this.showInput('tag') }),
showGroup: computed('activeType', function() { return this.showInput('group') }),
showUser: computed('activeType', function() { return this.showInput('user') }),
showList: computed('activeType', function() { return this.showInput('list') }),
showComboBox: or('showWizardField', 'showUserField'),
showMultiSelect: or('showCategory', 'showGroup'),
textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }),
wizardFieldEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }),
userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }),
categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }),
tagEnabled: computed('options.tagSelection', 'inputType', function() { return this.optionEnabled('tagSelection') }),
groupEnabled: computed('options.groupSelection', 'inputType', function() { return this.optionEnabled('groupSelection') }),
userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }),
listEnabled: computed('options.listSelection', 'inputType', function() { return this.optionEnabled('listSelection') }),
@discourseComputed('activeType')
selectorTypes(activeType) {
return selectionTypes.filter(type => (this[`${type}Enabled`]));
},
@discourseComputed
userFields() {
@ -32,23 +55,41 @@ export default Ember.Component.extend({
clearValue() {
this.set('value', null);
},
@discourseComputed('customPlaceholder')
textPlaceholder(customPlaceholder) {
return customPlaceholder || 'admin.wizard.text';
@discourseComputed('activeType')
comboBoxContent(activeType) {
return this[`${activeType}Fields`];
},
showInput(type) {
return this.activeType === type && this[`${type}Enabled`];
@discourseComputed('activeType')
multiSelectContent(activeType) {
return {
category: this.categories,
group: this.groups,
list: ''
}[activeType];
},
showText: computed('activeType', function() { return this.showInput('text') }),
showWizard: computed('activeType', function() { return this.showInput('wizard') }),
showUserField: computed('activeType', function() { return this.showInput('userField') }),
showCategory: computed('activeType', function() { return this.showInput('category') }),
showTag: computed('activeType', function() { return this.showInput('tag') }),
showGroup: computed('activeType', function() { return this.showInput('group') }),
showUser: computed('activeType', function() { return this.showInput('user') }),
@discourseComputed('activeType')
placeholder(activeType) {
if (activeType === 'text' && this.options[`${this.selectorType}Placeholder`]) {
return this.options[`${this.selectorType}Placeholder`];
}
return `admin.wizard.selector.placeholder.${snakeCase(activeType)}`;
},
@discourseComputed('activeType')
multiSelectOptions(activeType) {
let result = {
none: this.placeholder
};
if (activeType === 'list') {
result.allowAny = true;
}
return result;
},
optionEnabled(type) {
const options = this.options;
@ -57,19 +98,15 @@ export default Ember.Component.extend({
const option = options[type];
if (option === true) return true;
if (typeof option !== 'string') return false;
const types = [this.selectorType, this.inputType];
return option.split(',').filter(o => types.indexOf(o) !== -1).length
return option.split(',').filter(option => {
return [this.selectorType, this.inputType].indexOf(option) !== -1;
}).length;
},
textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }),
wizardEnabled: computed('options.wizardSelection', 'inputType', function() { return this.optionEnabled('wizardSelection') }),
userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }),
categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }),
tagEnabled: computed('options.tagSelection', 'inputType', function() { return this.optionEnabled('tagSelection') }),
groupEnabled: computed('options.groupSelection', 'inputType', function() { return this.optionEnabled('groupSelection') }),
userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }),
showInput(type) {
return this.activeType === type && this[`${type}Enabled`];
},
actions: {
toggleType(type) {

Datei anzeigen

@ -1,9 +1,11 @@
import { getOwner } from 'discourse-common/lib/get-owner';
import { on } from 'discourse-common/utils/decorators';
import { newInput } from '../lib/wizard-mapper';
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { newInput, selectionTypes } from '../lib/wizard-mapper';
import { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
import Component from "@ember/component";
import { A } from "@ember/array";
export default Ember.Component.extend({
export default Component.extend({
classNames: 'wizard-mapper',
@discourseComputed('inputs.[]', 'options.singular')
@ -11,29 +13,38 @@ export default Ember.Component.extend({
return !singular || !inputs || inputs.length < 1;
},
@discourseComputed('options')
@discourseComputed('options.@each')
inputOptions(options) {
return {
hasOutput: options.hasOutput || false,
inputTypes: options.inputTypes || null,
let result = {
inputTypes: options.inputTypes || 'conditional,assignment',
pairConnector: options.pairConnector || null,
outputConnector: options.outputConnector || null,
textSelection: options.textSelection || true,
wizardSelection: options.wizardSelection || false,
userFieldSelection: options.userFieldSelection || false,
categorySelection: options.categorySelection || false,
tagSelection: options.tagSelection || false,
groupSelection: options.groupSelection || false,
userSelection: options.userSelection || false,
keyDefaultSelection: options.keyDefaultSelection || null,
valueDefaultSelection: options.valueDefaultSelection || null,
outputDefaultSelection: options.outputDefaultSelection || null
outputConnector: options.outputConnector || null
}
let inputTypes = ['key', 'value', 'output'];
inputTypes.forEach(type => {
result[`${type}DefaultSelection`] = options[`${type}DefaultSelection`] || null;
});
selectionTypes.forEach(type => {
if (options[`${type}Selection`]) {
result[`${type}Selection`] = options[`${type}Selection`]
} else {
result[`${type}Selection`] = type === 'text' ? true : false;
}
});
return result;
},
@observes('options.inputTypes')
clearInputs() {
this.get('inputs').clear();
},
actions: {
add() {
if (!this.get('inputs')) this.set('inputs', Ember.A());
if (!this.get('inputs')) this.set('inputs', A());
this.get('inputs').pushObject(newInput(this.inputOptions));
},

Datei anzeigen

@ -1,11 +1,9 @@
import {
default as discourseComputed,
on
} from 'discourse-common/utils/decorators';
import { default as discourseComputed, on } from 'discourse-common/utils/decorators';
import { profileFields } from '../lib/wizard';
import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
export default Ember.Component.extend({
export default Component.extend({
classNames: 'wizard-text-editor',
barEnabled: true,
previewEnabled: true,

Datei anzeigen

@ -5,9 +5,10 @@ import { generateId } from '../lib/wizard';
import { buildProperties } from '../lib/wizard-json';
import { dasherize } from "@ember/string";
import EmberObject from "@ember/object";
import { scheduleOnce } from "@ember/runloop";
import { scheduleOnce, later } from "@ember/runloop";
import Controller from "@ember/controller";
export default Ember.Controller.extend({
export default Controller.extend({
hasName: notEmpty('model.name'),
init() {
@ -84,7 +85,7 @@ export default Ember.Controller.extend({
}).catch((result) => {
this.set('saving', false);
this.set('error', I18n.t(`admin.wizard.error.${result.error}`));
Ember.run.later(() => this.set('error', null), 10000);
later(() => this.set('error', null), 10000);
});
},

Datei anzeigen

@ -1,42 +1,44 @@
import { ajax } from 'discourse/lib/ajax';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import CustomWizardApi from '../models/custom-wizard-api';
import { default as computed } from 'discourse-common/utils/decorators';
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { not, and, equal } from "@ember/object/computed";
import { selectKitContent } from '../lib/wizard';
import Controller from "@ember/controller";
export default Ember.Controller.extend({
export default Controller.extend({
queryParams: ['refresh_list'],
loadingSubscriptions: false,
notAuthorized: Ember.computed.not('api.authorized'),
notAuthorized: not('api.authorized'),
endpointMethods: selectKitContent(['GET', 'PUT', 'POST', 'PATCH', 'DELETE']),
showRemove: Ember.computed.not('isNew'),
showRedirectUri: Ember.computed.and('threeLeggedOauth', 'api.name'),
showRemove: not('isNew'),
showRedirectUri: and('threeLeggedOauth', 'api.name'),
responseIcon: null,
contentTypes: selectKitContent(['application/json', 'application/x-www-form-urlencoded']),
successCodes: selectKitContent([100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 303, 304, 305, 306, 307, 308]),
@computed('saveDisabled', 'api.authType', 'api.authUrl', 'api.tokenUrl', 'api.clientId', 'api.clientSecret', 'threeLeggedOauth')
@discourseComputed('saveDisabled', 'api.authType', 'api.authUrl', 'api.tokenUrl', 'api.clientId', 'api.clientSecret', 'threeLeggedOauth')
authDisabled(saveDisabled, authType, authUrl, tokenUrl, clientId, clientSecret, threeLeggedOauth) {
if (saveDisabled || !authType || !tokenUrl || !clientId || !clientSecret) return true;
if (threeLeggedOauth) return !authUrl;
return false;
},
@computed('api.name', 'api.authType')
@discourseComputed('api.name', 'api.authType')
saveDisabled(name, authType) {
return !name || !authType;
},
authorizationTypes: selectKitContent(['none', 'basic', 'oauth_2', 'oauth_3']),
isBasicAuth: Ember.computed.equal('api.authType', 'basic'),
isBasicAuth: equal('api.authType', 'basic'),
@computed('api.authType')
@discourseComputed('api.authType')
isOauth(authType) {
return authType && authType.indexOf('oauth') > -1;
},
twoLeggedOauth: Ember.computed.equal('api.authType', 'oauth_2'),
threeLeggedOauth: Ember.computed.equal('api.authType', 'oauth_3'),
twoLeggedOauth: equal('api.authType', 'oauth_2'),
threeLeggedOauth: equal('api.authType', 'oauth_3'),
actions: {
addParam() {

Datei anzeigen

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

Datei anzeigen

@ -1 +1,3 @@
export default Ember.Controller.extend();
import Controller from "@ember/controller";
export default Controller.extend();

Datei anzeigen

@ -1,7 +1,8 @@
import { default as computed } from 'discourse-common/utils/decorators';
import { scheduleOnce } from "@ember/runloop";
import Controller from "@ember/controller";
export default Ember.Controller.extend({
export default Controller.extend({
title: 'admin.wizard.after_time_modal.title',
setup() {

Datei anzeigen

@ -1,5 +1,6 @@
import { registerUnbound } from 'discourse-common/lib/helpers';
import { dasherize } from "@ember/string";
registerUnbound('dasherize', function(string) {
return Ember.String.dasherize(string);
return dasherize(string);
});

Datei anzeigen

@ -1,11 +1,6 @@
import {
properties,
mappedProperties,
advancedProperties,
camelCase,
snakeCase
} from '../lib/wizard';
import { properties, mappedProperties, advancedProperties, camelCase, snakeCase } from '../lib/wizard';
import EmberObject from '@ember/object';
import { A } from "@ember/array";
function present(val) {
if (val === null || val === undefined) {
@ -197,7 +192,7 @@ function buildObject(json, type) {
);
});
params[prop] = Ember.A(inputs);
params[prop] = A(inputs);
} else {
params[prop] = json[prop];
}
@ -223,7 +218,7 @@ function hasAdvanced(params, type) {
}
function buildProperties(json) {
let steps = Ember.A();
let steps = A();
let props = {
steps
};
@ -254,7 +249,7 @@ function buildProperties(json) {
}
});
stepParams.fields = Ember.A();
stepParams.fields = A();
if (present(stepJson.fields)) {
stepJson.fields.forEach((f) => {
@ -268,7 +263,7 @@ function buildProperties(json) {
});
}
stepParams.actions = Ember.A();
stepParams.actions = A();
if (present(stepJson.actions)) {
stepJson.actions.forEach((a) => {
@ -299,7 +294,7 @@ function buildProperties(json) {
props.prompt_completion = false;
props.restart_on_revisit = false;
props.permitted = null;
props.steps = Ember.A();
props.steps = A();
}
return props;

Datei anzeigen

@ -1,15 +1,9 @@
import EmberObject from "@ember/object";
import { A } from "@ember/array";
// Inputs
const selectableInputTypes = [
'conditional',
'assignment'
]
function defaultInputType(options = {}) {
if (!options.hasOutput) return 'pair';
if (!options.inputTypes) return selectableInputTypes[0];
return options.inputTypes.split(',')[0];
}
@ -73,12 +67,13 @@ function connectorContent(connectorType, inputType, opts) {
const selectionTypes = [
'text',
'wizard',
'wizardField',
'userField',
'group',
'category',
'tag',
'user'
'user',
'list'
]
function defaultSelectionType(inputType, options = {}) {
@ -122,9 +117,11 @@ function newPair(inputType, options = {}) {
function newInput(options = {}) {
const inputType = defaultInputType(options);
console.log(inputType);
let params = {
type: inputType,
pairs: Ember.A(
pairs: A(
[
newPair(
inputType,
@ -137,7 +134,7 @@ function newInput(options = {}) {
)
}
if (options.hasOutput) {
if (['conditional', 'assignment'].indexOf(inputType) > -1) {
params['output_type'] = defaultSelectionType('output', options);
params['connector'] = defaultConnector('output', inputType, options);
}
@ -150,6 +147,7 @@ export {
defaultSelectionType,
connectorContent,
inputTypesContent,
selectionTypes,
newInput,
newPair,
newPair
}

Datei anzeigen

@ -1,5 +1,5 @@
function selectKitContent(content) {
return content.map(i => ({id: i, name: i}))
return content.map(i => ({id: i, name: i}));
}
function generateName(id) {
@ -13,13 +13,13 @@ function generateId(name) {
function sentenceCase(string) {
return string.replace(/[_\-]+/g, ' ')
.toLowerCase()
.replace(/(^\w|\b\w)/g, (m) => m.toUpperCase())
.replace(/(^\w|\b\w)/g, (m) => m.toUpperCase());
}
function snakeCase(string) {
return string.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map(x => x.toLowerCase())
.join('_')
.join('_');
}
function camelCase(string) {
@ -124,7 +124,6 @@ const mappedProperties = {
'permitted_params'
],
field: [
'choices',
'prefill',
'content'
],

Datei anzeigen

@ -1,6 +1,7 @@
import { ajax } from 'discourse/lib/ajax';
import { default as computed } from 'discourse-common/utils/decorators';
import EmberObject from "@ember/object";
import { A } from "@ember/array";
const CustomWizardApi = EmberObject.extend({
@computed('name')
@ -28,14 +29,14 @@ CustomWizardApi.reopenClass({
clientSecret: authorization.client_secret,
username: authorization.username,
password: authorization.password,
authParams: Ember.A(authorization.auth_params),
authParams: A(authorization.auth_params),
authorized: authorization.authorized,
accessToken: authorization.access_token,
refreshToken: authorization.refresh_token,
code: authorization.code,
tokenExpiresAt: authorization.token_expires_at,
tokenRefreshAt: authorization.token_refresh_at,
endpoints: Ember.A(endpoints),
endpoints: A(endpoints),
isNew: params.isNew,
log: params.log
});

Datei anzeigen

@ -1,10 +1,11 @@
import { ajax } from 'discourse/lib/ajax';
import EmberObject from "@ember/object";
import { buildStepJson, buildJson, buildProperties } from '../lib/wizard-json';
import { Promise } from "rsvp";
const CustomWizard = EmberObject.extend({
save() {
return new Ember.RSVP.Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
let wizardJson = buildJson(this, 'wizard');
if (wizardJson.after_time && !wizardJson.after_time_scheduled) {

Datei anzeigen

@ -1,11 +1,8 @@
import CustomWizard from '../models/custom-wizard';
import { ajax } from 'discourse/lib/ajax';
import {
selectKitContent,
profileFields,
generateName
} from '../lib/wizard';
import { selectKitContent, profileFields, generateName } from '../lib/wizard';
import DiscourseRoute from "discourse/routes/discourse";
import { all } from "rsvp";
export default DiscourseRoute.extend({
beforeModel() {
@ -39,7 +36,7 @@ export default DiscourseRoute.extend({
},
afterModel(model) {
return Ember.RSVP.all([
return all([
this._getFieldTypes(model),
this._getThemes(model),
this._getApis(model),

Datei anzeigen

@ -114,7 +114,6 @@
options=(hash
singular=true
inputTypes='assignment'
hasOutput=true
groupSelection='output'
textSelection='key,value'
)}}

Datei anzeigen

@ -135,8 +135,8 @@
<div class="controls">
{{#each api.authParams as |param|}}
<div class="param">
{{input value=param.key placeholder=(i18n 'admin.wizard.api.auth.params.key')}}
{{input value=param.value placeholder=(i18n 'admin.wizard.api.auth.params.value')}}
{{input value=param.key placeholder=(i18n 'admin.wizard.key')}}
{{input value=param.value placeholder=(i18n 'admin.wizard.value')}}
{{d-button action=(action "removeParam") actionParam=param icon='times'}}
</div>
{{/each}}

Datei anzeigen

@ -24,8 +24,7 @@
{{wizard-mapper
inputs=action.title
options=(hash
hasOutput=true
wizardSelection=true
wizardFieldSelection=true
userFieldSelection='key,value'
)}}
</div>
@ -43,7 +42,7 @@
nameProperty='label'
onChange=(action (mut action.post))
options=(hash
none='admin.wizard.select_field'
none='admin.wizard.selector.placeholder.wizard_field'
isDisabled=action.post_builder
)}}
@ -79,9 +78,8 @@
{{wizard-mapper
inputs=action.category
options=(hash
hasOutput=true
textSelection='key,value'
wizardSelection=true
wizardFieldSelection=true
userFieldSelection='key,value'
categorySelection='output'
outputDefaultSelection='category'
@ -98,9 +96,8 @@
{{wizard-mapper
inputs=action.tags
options=(hash
hasOutput=true
tagSelection='output'
wizardSelection=true
wizardFieldSelection=true
userFieldSelection='key,value'
)}}
</div>
@ -117,9 +114,8 @@
{{wizard-mapper
inputs=action.recipient
options=(hash
hasOutput=true
textSelection='value,output'
wizardSelection=true
wizardFieldSelection=true
userFieldSelection='key,value'
groupSelection='key,value'
userSelection='output'
@ -137,12 +133,12 @@
{{wizard-mapper
inputs=action.profile_updates
keyPlaceholder='admin.wizard.action.update_profile.key'
options=(hash
pairConnector='set'
userFieldSelection='key'
wizardSelection='value'
wizardFieldSelection='value'
keyDefaultSelection='userField'
keyPlaceholder='admin.wizard.action.update_profile.key'
)}}
</div>
{{/if}}
@ -208,9 +204,8 @@
{{wizard-mapper
inputs=action.group
options=(hash
hasOutput=true
textSelection='value,output'
wizardSelection='key,value,assignment'
wizardFieldSelection='key,value,assignment'
userFieldSelection='key,value,assignment'
groupSelection='value,output'
outputDefaultSelection='group'
@ -246,11 +241,11 @@
<div class="setting-value">
{{wizard-mapper
inputs=action.custom_fields
keyPlaceholder='admin.wizard.action.custom_fields.key'
options=(hash
pairConnector='set'
wizardSelection='value'
wizardFieldSelection='value'
userFieldSelection='value'
keyPlaceholder='admin.wizard.action.custom_fields.key'
)}}
</div>
</div>
@ -267,7 +262,7 @@
inputs=action.required
options=(hash
textSelection='value'
wizardSelection=true
wizardFieldSelection=true
userFieldSelection=true
groupSelection=true
)}}

Datei anzeigen

@ -69,41 +69,6 @@
</div>
{{/if}}
{{#if isDropdown}}
<div class="wizard-dropdown-choices">
<div class="wizard-header small underline">
{{i18n 'admin.wizard.field.choices_label'}}
</div>
{{combo-box
value=field.choices_type
content=choicesTypes
onChange=(action (mut field.choices_type))
options=(hash
none="admin.wizard.field.choices_type"
)}}
{{#if choicesTranslation}}
<div class="wizard-header small">
{{i18n 'admin.wizard.field.choices_translation'}}
</div>
{{input name="key" value=field.choices_key placeholderKey="admin.wizard.translation_placeholder"}}
{{/if}}
{{#if choicesCustom}}
<div class="wizard-header small">
{{i18n 'admin.wizard.field.choices_custom'}}
</div>
{{wizard-mapper inputs=field.choices}}
{{/if}}
<div class="wizard-header small">
{{i18n 'admin.wizard.field.dropdown_none'}}
</div>
{{input name="dropdown_none" value=field.dropdown_none placeholder=(i18n 'admin.wizard.field.dropdown_none_placeholder')}}
</div>
{{/if}}
{{#if isUpload}}
<div class="setting">
<div class="setting-label">
@ -128,63 +93,61 @@
</div>
{{/if}}
{{#if hasAdvanced}}
{{wizard-advanced-toggle showAdvanced=field.showAdvanced}}
{{wizard-advanced-toggle showAdvanced=field.showAdvanced}}
{{#if field.showAdvanced}}
<div class="advanced-settings">
{{#if isCategory}}
<div class="setting">
<div class="setting-label">
<label>{{i18n 'admin.wizard.field.property'}}</label>
</div>
<div class="setting-value">
{{combo-box
value=field.property
content=categoryPropertyTypes
onChange=(action (mut field.property))
options=(hash
none='admin.wizard.select_property'
)}}
</div>
</div>
{{/if}}
{{#if prefillEnabled}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n 'admin.wizard.field.prefill'}}</label>
</div>
<div class="setting-value">
{{wizard-mapper inputs=field.prefill options=prefillOptions}}
</div>
</div>
{{/if}}
{{#if contentEnabled}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n 'admin.wizard.field.content'}}</label>
</div>
<div class="setting-value">
{{wizard-mapper inputs=field.content options=contentOptions}}
</div>
</div>
{{/if}}
{{#if field.showAdvanced}}
<div class="advanced-settings">
{{#if isCategory}}
<div class="setting">
<div class="setting-label">
<label>{{i18n 'admin.wizard.translation'}}</label>
<label>{{i18n 'admin.wizard.field.property'}}</label>
</div>
<div class="setting-value">
{{input name="key" value=field.key placeholderKey="admin.wizard.translation_placeholder"}}
{{combo-box
value=field.property
content=categoryPropertyTypes
onChange=(action (mut field.property))
options=(hash
none='admin.wizard.select_property'
)}}
</div>
</div>
{{/if}}
{{#if prefillEnabled}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n 'admin.wizard.field.prefill'}}</label>
</div>
<div class="setting-value">
{{wizard-mapper inputs=field.prefill options=prefillOptions}}
</div>
</div>
{{/if}}
{{#if contentEnabled}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n 'admin.wizard.field.content'}}</label>
</div>
<div class="setting-value">
{{wizard-mapper inputs=field.content options=contentOptions}}
</div>
</div>
{{/if}}
<div class="setting">
<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>
</div>
{{/if}}
</div>
{{/if}}

Datei anzeigen

@ -46,10 +46,10 @@
<div class="setting-value">
{{wizard-mapper
inputs=step.required_data
keyPlaceholder="admin.wizard.submission_key"
options=(hash
wizardSelection='value'
wizardFieldSelection='value'
userFieldSelection='value'
keyPlaceholder="admin.wizard.submission_key"
)}}
{{#if step.required_data}}
<div class="required-data-message">
@ -69,10 +69,10 @@
<div class="setting-value">
{{wizard-mapper
inputs=step.permitted_params
keyPlaceholder='admin.wizard.param_key'
valuePlaceholder='admin.wizard.submission_key'
options=(hash
pairConnector='set'
keyPlaceholder='admin.wizard.param_key'
valuePlaceholder='admin.wizard.submission_key'
)}}
</div>
</div>

Datei anzeigen

@ -1,9 +1,7 @@
{{#if hasOutput}}
{{wizard-mapper-connector
connector=input.type
connectors=inputTypes
inputTypes=true}}
{{/if}}
{{wizard-mapper-connector
connector=input.type
connectors=inputTypes
inputTypes=true}}
{{#if hasPairs}}
<div class="mapper-pairs mapper-block">
@ -12,8 +10,6 @@
pair=pair
last=pair.last
inputType=inputType
keyPlaceholder=keyPlaceholder
valuePlaceholder=valuePlaceholder
options=options
removePair=(action 'removePair')}}
{{/each}}
@ -39,7 +35,6 @@
inputType=inputType
value=input.output
activeType=input.output_type
customPlaceholder=outputPlaceholder
options=options}}
</div>
{{/if}}

Datei anzeigen

@ -4,7 +4,6 @@
inputType=inputType
value=pair.key
activeType=pair.key_type
customPlaceholder=keyPlaceholder
options=options}}
</div>
@ -18,7 +17,6 @@
inputType=inputType
value=pair.value
activeType=pair.value_type
customPlaceholder=valuePlaceholder
options=options}}
</div>

Datei anzeigen

@ -1,52 +1,10 @@
<div class="type-selector">
{{#if textEnabled}}
{{#each selectorTypes as |type|}}
{{wizard-mapper-selector-type
activeType=activeType
type='text'
type=type
toggle=(action 'toggleType')}}
{{/if}}
{{#if wizardEnabled}}
{{wizard-mapper-selector-type
activeType=activeType
type='wizard'
toggle=(action 'toggleType')}}
{{/if}}
{{#if userFieldEnabled}}
{{wizard-mapper-selector-type
activeType=activeType
type='userField'
toggle=(action 'toggleType')}}
{{/if}}
{{#if categoryEnabled}}
{{wizard-mapper-selector-type
activeType=activeType
type='category'
toggle=(action 'toggleType')}}
{{/if}}
{{#if tagEnabled}}
{{wizard-mapper-selector-type
activeType=activeType
type='tag'
toggle=(action 'toggleType')}}
{{/if}}
{{#if groupEnabled}}
{{wizard-mapper-selector-type
activeType=activeType
type='group'
toggle=(action 'toggleType')}}
{{/if}}
{{#if userEnabled}}
{{wizard-mapper-selector-type
activeType=activeType
type='user'
toggle=(action 'toggleType')}}
{{/if}}
{{/each}}
</div>
<div class="input">
@ -54,60 +12,44 @@
{{input
type="text"
value=value
placeholder=(i18n textPlaceholder)}}
{{/if}}
{{#if showWizard}}
{{combo-box
value=value
content=wizardFields
onChange=(action (mut value))
options=(hash
none='admin.wizard.wizard_field'
)}}
placeholder=(i18n placeholder)}}
{{/if}}
{{#if showUserField}}
{{#if showComboBox}}
{{combo-box
value=value
content=userFields
content=comboBoxContent
onChange=(action (mut value))
options=(hash
none='admin.wizard.user_field'
none=placeholder
)}}
{{/if}}
{{#if showCategory}}
{{#if showMultiSelect}}
{{multi-select
content=categories
content=multiSelectContent
value=value
onChange=(action (mut value))
options=(hash
none='admin.wizard.select_category'
)}}
options=multiSelectOptions}}
{{/if}}
{{#if showList}}
{{value-list values=value}}
{{/if}}
{{#if showTag}}
{{tag-chooser
tags=value
filterable=true}}
{{/if}}
{{#if showGroup}}
{{multi-select
content=groups
value=value
onChange=(action (mut value))
filterable=true
options=(hash
none='admin.wizard.select_group'
none=placeholder
)}}
{{/if}}
{{#if showUser}}
{{user-selector
topicId=topicId
includeMessageableGroups='true'
placeholderKey="composer.users_placeholder"
placeholderKey=placeholder
usernames=value
autocomplete="discourse"}}
{{/if}}

Datei anzeigen

@ -1,8 +1,6 @@
{{#each inputs as |input|}}
{{wizard-mapper-input
input=input
keyPlaceholder=keyPlaceholder
valuePlaceholder=valuePlaceholder
options=inputOptions
remove=(action 'remove')}}
{{/each}}

Datei anzeigen

@ -1,4 +1,5 @@
import { default as computed } from 'discourse-common/utils/decorators';
import { dasherize } from "@ember/string";
export default {
name: 'custom-routes',
@ -219,7 +220,7 @@ export default {
const type = this.get('field.type');
const id = this.get('field.id');
if (['text-only'].includes(type)) return false;
return (type === 'component') ? Ember.String.dasherize(id) : `wizard-field-${type}`;
return (type === 'component') ? dasherize(id) : `wizard-field-${type}`;
}.property('field.type', 'field.id')
});

Datei anzeigen

@ -1,7 +1,7 @@
{{combo-box elementId=field.id
class=fieldClass
value=field.value
content=field.choices
content=field.content
none=(hash id="__none__" label=field.dropdown_none)
nameProperty="label"
tabindex="9"}}

Datei anzeigen

@ -379,17 +379,6 @@ body.admin-wizard {
position: relative;
}
.wizard-dropdown-choices {
padding: 15px;
margin-bottom: 20px;
background-color: $secondary;
width: 100%;
.wizard-header:not(.underline) {
margin-top: 15px;
}
}
.required-data-message {
display: inline-block;
margin-top: 20px;

Datei anzeigen

@ -24,11 +24,10 @@
align-items: flex-start;
width: min-content;
position: relative;
padding-bottom: 30px;
&:last-of-type {
padding-bottom: 0;
}
padding: 25px 7px 7px 7px;
margin-bottom: 10px;
background: rgba($secondary, 0.5);
border: 2px solid $primary-low;
.d-icon {
text-align: center;
@ -42,11 +41,16 @@
background-color: $primary-low;
border-color: #ddd;
}
.output {
position: relative;
}
a.remove-input {
position: absolute;
right: -25px;
top: 5px;
top: 50%;
transform: translateY(-50%);
}
}
@ -54,10 +58,6 @@
display: block;
}
.mapper-input + .add-mapper-input {
padding-top: 10px;
}
.mapper-connector {
width: auto;
min-width: 40px;
@ -105,6 +105,15 @@
margin-right: 0;
}
}
.value-list .remove-value-btn {
background: none;
border: none;
.d-icon {
color: $primary;
}
}
}
.mapper-pairs {

Datei anzeigen

@ -42,20 +42,13 @@ en:
remove: "Delete Wizard"
add: "Add"
url: "Url"
key: "Key"
value: "Value"
property: "Property"
text: "text"
profile: "profile"
translation: "Translation"
translation_placeholder: "key"
type: "Type"
none: "Make a selection"
user_field: "User Field"
wizard_field: "Wizard Field"
select_field: "Select Field"
select_property: "Select Property"
select_group: "Select Group"
profile_field: "Profile Field"
none: "Make a selection"
submission_key: 'submission key'
param_key: 'param'
group: "Group"
@ -74,7 +67,31 @@ en:
output: 'then'
assignment:
name: 'set'
pair:
name: 'pair'
selector:
label:
text: "text"
wizard_field: "wizard ○"
user_field: "user ○"
user: "user"
category: "category"
tag: "tag"
group: "group"
list: "list"
placeholder:
text: "Enter text"
property: "Select property"
wizard_field: "Select field"
user_field: "Select field"
user: "Select user"
category: "Select category"
tag: "Select tag"
group: "Select group"
list: "Enter item"
error:
name_required: "Wizards must have a name."
steps_required: "Wizards must have at least one step."
@ -83,9 +100,6 @@ en:
type_required: "All fields need a type."
after_time_need_time: "After time is enabled but no time is set."
after_time_invalid: "After time is invalid."
field:
need_choices: "All dropdowns need choices."
choices_label_empty: "Custom choice labels cannot be empty."
step:
header: "Steps"
@ -107,13 +121,6 @@ en:
image_placeholder: "Image url"
dropdown_none: "None"
dropdown_none_placeholder: "Label"
choices_label: "Dropdown Choices"
choices_type: "Choose a type"
choices_translation: "Translation"
choices_custom: "Custom"
choice:
value: "Value"
label: "Label"
required: "Required"
required_label: "Field is Required"
min_length: "Min Length"
@ -214,8 +221,6 @@ en:
params:
label: 'Params'
new: 'New param'
key: 'key'
value: 'value'
status:
label: "Status"

Datei anzeigen

@ -59,9 +59,6 @@ fr:
type_required: "Tous les champs ont besoin d'un type."
after_time_need_time: "Le délai est activé mais aucune heure n'est établie."
after_time_invalid: "Le délai est invalide."
field:
need_choices: "Tous les menus déroulants ont besoin d'options."
choices_label_empty: "Les étiquettes pour le choix personnalisé ne peuvent être vides."
step:
header: "Étapes"
title: "Titre"
@ -74,15 +71,6 @@ fr:
description: "Description"
image: "Image"
image_placeholder: "URL de l'image"
dropdown_none: "Aucun"
dropdown_none_placeholder: "Clé de traduction"
choices_label: "Options du menu déroulant"
choices_type: "Choisir un type"
choices_translation: "Traduction"
choices_custom: "Personnalisé"
choice:
value: "Valeur"
label: "Étiquette"
required: "Requis"
required_label: "Le champ est obligatoire"
min_length: "Longueur min."
@ -103,7 +91,6 @@ fr:
category: "Catégorie"
update_profile:
label: "Mettre à jour le profil"
profile_field: "Champ du profil"
post_builder:
checkbox: "Générateur de message"
label: "Générateur"

Datei anzeigen

@ -63,14 +63,6 @@ class CustomWizard::AdminController < ::ApplicationController
error = 'type_required'
break
end
if f["type"] === 'dropdown'
choices = f["choices"]
if (!choices || choices.length < 1) && !f["choices_key"]
error = 'field.need_choices'
break
end
end
end
end

Datei anzeigen

@ -244,10 +244,6 @@ class CustomWizard::Builder
end
field = step.add_field(params)
if field_template['type'] === 'dropdown'
build_dropdown_list(field, field_template)
end
end
def prefill_field(field_template, step_template)
@ -260,26 +256,6 @@ class CustomWizard::Builder
end
end
def build_dropdown_list(field, template)
field.dropdown_none = template['dropdown_none'] if template['dropdown_none']
method = "build_dropdown_#{template['choices_type']}"
self.send(method, field, template) if self.respond_to?(method)
end
def build_dropdown_custom(field, template)
template['choices'].each do |c|
field.add_choice(c['key'], label: c['value'])
end
end
def build_dropdown_translation(field, template)
choices = I18n.t(template['choices_key'])
if choices.is_a?(Hash)
choices.each { |k, v| field.add_choice(k, label: v) }
end
end
def validate_field(field, updater, step_template)
value = updater.fields[field['id']]
min_length = false

Datei anzeigen

@ -1,17 +0,0 @@
module CustomWizardChoiceExtension
def initialize(id, opts)
@id = id
@opts = opts
@data = opts[:data]
@extra_label = opts[:extra_label]
@icon = opts[:icon]
end
def label
@label ||= PrettyText.cook(@opts[:label])
end
end
class Wizard::Choice
prepend CustomWizardChoiceExtension if SiteSetting.custom_wizard_enabled
end

Datei anzeigen

@ -9,8 +9,6 @@ module CustomWizardFieldExtension
:property,
:content
attr_accessor :dropdown_none
def initialize(attrs)
@attrs = attrs || {}
@id = attrs[:id]
@ -21,8 +19,6 @@ module CustomWizardFieldExtension
@key = attrs[:key]
@min_length = attrs[:min_length]
@value = attrs[:value]
@choices = []
@dropdown_none = attrs[:dropdown_none]
@file_types = attrs[:file_types]
@limit = attrs[:limit]
@property = attrs[:property]

Datei anzeigen

@ -63,7 +63,6 @@ after_initialize do
../lib/custom_wizard/api/authorization.rb
../lib/custom_wizard/api/endpoint.rb
../lib/custom_wizard/api/log_entry.rb
../lib/wizard/choice.rb
../lib/wizard/field.rb
../lib/wizard/step.rb
../serializers/custom_wizard/api/authorization_serializer.rb

Datei anzeigen

@ -8,9 +8,7 @@ class CustomWizardFieldSerializer < ::WizardFieldSerializer
:limit,
:property,
:content
has_many :choices, serializer: WizardFieldChoiceSerializer, embed: :objects
def label
return object.label if object.label.present?
I18n.t("#{object.key || i18n_key}.label", default: '')

Datei anzeigen

@ -28,8 +28,6 @@ describe CustomWizard::Builder do
let(:text_only_field) {{"id": "text_only","type": "text-only","label": "Text only"}}
let(:upload_field) {{"id": "upload","type": "upload","file_types": ".jpg,.png,.pdf","label": "Upload"}}
let(:user_selector_field) {{"id": "user_selector","type": "user-selector","label": "User selector"}}
let(:dropdown_custom_field) {{"id": "dropdown_custom","type": "dropdown","choices_type": "custom","choices": [{"key": "option_1","value": "Option 1"},{"key": "option_2","value": "Option 2"}]}}
let(:dropdown_translation_field) {{"id": "dropdown_translation","type": "dropdown","choices_type": "translation","choices_key": "key1.key2"}}
let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}}
let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}}
let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}}