1
0
Fork 0

Complete pro-feature functionality

Dieser Commit ist enthalten in:
angusmcleod 2021-09-03 16:46:32 +08:00
Ursprung 6b1e7568c1
Commit 6ef333a657
26 geänderte Dateien mit 329 neuen und 280 gelöschten Zeilen

Datei anzeigen

@ -1,13 +1,29 @@
import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import I18n from "I18n";
const generateContent = function (array, type) {
return array.map((key) => ({
id: key,
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`),
}));
const klasses = ["topic", "post", "group", "category"];
const types = ["string", "boolean", "integer", "json"];
const proTypes = {
klass: ["group", "category"],
type: ["json"]
}
const generateContent = function (array, type, proSubscribed = false) {
return array.reduce((result, key) => {
let proArr = proTypes[type];
let pro = proArr && proArr.includes(key);
if (!pro || proSubscribed) {
result.push({
id: key,
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`),
pro
});
}
return result;
}, []);
};
export default Component.extend({
@ -16,14 +32,12 @@ export default Component.extend({
postSerializers: ["post"],
groupSerializers: ["basic_group"],
categorySerializers: ["basic_category"],
klassContent: generateContent(
["topic", "post", "group", "category"],
"klass"
),
typeContent: generateContent(
["string", "boolean", "integer", "json"],
"type"
),
klassContent: computed("proSubscribed", function() {
return generateContent(klasses, "klass", this.proSubscribed);
}),
typeContent: computed("proSubscribed", function() {
return generateContent(types, "type", this.proSubscribed);
}),
showInputs: or("field.new", "field.edit"),
classNames: ["custom-field-input"],
loading: or("saving", "destroying"),
@ -40,7 +54,7 @@ export default Component.extend({
const serializers = this.get(`${klass}Serializers`);
if (serializers) {
return generateContent(serializers, "serializers");
return generateContent(serializers, "serializers", this.proSubscribed);
} else {
return [];
}

Datei anzeigen

@ -1,21 +0,0 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import Component from "@ember/component";
export default Component.extend({
classNames: "wizard-advanced-toggle",
@discourseComputed("showAdvanced")
toggleClass(showAdvanced) {
let classes = "btn";
if (showAdvanced) {
classes += " btn-primary";
}
return classes;
},
actions: {
toggleAdvanced() {
this.toggleProperty("showAdvanced");
},
},
});

Datei anzeigen

@ -1,8 +1,8 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import { and, empty, equal, or } from "@ember/object/computed";
import { notificationLevels, selectKitContent } from "../lib/wizard";
import { computed } from "@ember/object";
import wizardSchema from "../lib/wizard-schema";
import UndoChanges from "../mixins/undo-changes";
import Component from "@ember/component";
import I18n from "I18n";
@ -25,8 +25,6 @@ export default Component.extend(UndoChanges, {
createGroup: equal("action.type", "create_group"),
apiEmpty: empty("action.api"),
groupPropertyTypes: selectKitContent(["id", "name"]),
hasAdvanced: or("hasCustomFields", "routeTo"),
showAdvanced: and("hasAdvanced", "action.type"),
hasCustomFields: or(
"basicTopicFields",
"updateProfile",
@ -36,12 +34,6 @@ export default Component.extend(UndoChanges, {
basicTopicFields: or("createTopic", "sendMessage", "openComposer"),
publicTopicFields: or("createTopic", "openComposer"),
showPostAdvanced: or("createTopic", "sendMessage"),
actionTypes: Object.keys(wizardSchema.action.types).map((type) => {
return {
id: type,
name: I18n.t(`admin.wizard.action.${type}.label`),
};
}),
availableNotificationLevels: notificationLevels.map((type) => {
return {
id: type,
@ -101,4 +93,19 @@ export default Component.extend(UndoChanges, {
}
return apis.find((a) => a.name === api).endpoints;
},
@discourseComputed("proSubscribed")
actionTypes(proSubscribed) {
return Object.keys(wizardSchema.action.types).reduce((result, type) => {
let pro = wizardSchema.action.proTypes.includes(type);
if (proSubscribed || !pro) {
result.push({
id: type,
name: I18n.t(`admin.wizard.action.${type}.label`),
pro
});
}
return result;
}, []);
}
});

Datei anzeigen

@ -27,7 +27,6 @@ export default Component.extend(UndoChanges, {
isTextType: or("isText", "isTextarea", "isComposer"),
isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]),
showAdvanced: alias("field.type"),
messageUrl: "https://thepavilion.io/t/2809",
@discourseComputed("field.type")

Datei anzeigen

@ -0,0 +1,19 @@
import SingleSelectComponent from "select-kit/components/single-select";
import { computed } from "@ember/object";
export default SingleSelectComponent.extend({
classNames: ["combo-box", 'wizard-pro-selector'],
selectKitOptions: {
autoFilterable: false,
filterable: false,
showFullTitle: true,
headerComponent: "wizard-pro-selector/wizard-pro-selector-header",
caretUpIcon: "caret-up",
caretDownIcon: "caret-down"
},
modifyComponentForRow() {
return "wizard-pro-selector/wizard-pro-selector-row";
}
});

Datei anzeigen

@ -0,0 +1,17 @@
import SingleSelectHeaderComponent from "select-kit/components/select-kit/single-select-header";
import { computed } from "@ember/object";
import { reads } from "@ember/object/computed";
export default SingleSelectHeaderComponent.extend({
classNames: ["combo-box-header", "wizard-pro-selector-header"],
caretUpIcon: reads("selectKit.options.caretUpIcon"),
caretDownIcon: reads("selectKit.options.caretDownIcon"),
caretIcon: computed(
"selectKit.isExpanded",
"caretUpIcon",
"caretDownIcon",
function () {
return this.selectKit.isExpanded ? this.caretUpIcon : this.caretDownIcon;
}
),
});

Datei anzeigen

@ -0,0 +1,3 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
export default SelectKitRowComponent.extend();

Datei anzeigen

@ -77,7 +77,6 @@ export default Controller.extend({
wizard
.save(opts)
.then((result) => {
console.log(result)
if (result.wizard_id) {
this.send("afterSave", result.wizard_id);
} else if (result.errors) {
@ -119,10 +118,6 @@ export default Controller.extend({
controller.setup();
},
toggleAdvanced() {
this.toggleProperty("wizard.showAdvanced");
},
copyUrl() {
const $copyRange = $('<p id="copy-range"></p>');
$copyRange.html(this.wizardUrl);

Datei anzeigen

@ -97,11 +97,6 @@ function buildObjectArray(json, type) {
if (present(json)) {
json.forEach((objJson, objectIndex) => {
let object = buildObject(objJson, type, objectIndex);
if (hasAdvancedProperties(object, type)) {
object.set("showAdvanced", true);
}
array.pushObject(object);
});
}
@ -112,21 +107,11 @@ function buildObjectArray(json, type) {
function buildBasicProperties(json, type, props, objectIndex = null) {
listProperties(type).forEach((p) => {
props[p] = buildProperty(json, p, type, objectIndex);
if (hasAdvancedProperties(json, type)) {
props.showAdvanced = true;
}
});
return props;
}
function hasAdvancedProperties(object, type) {
return Object.keys(object).some((p) => {
return wizardSchema[type].advanced.indexOf(p) > -1 && present(object[p]);
});
}
/// to be removed: necessary due to action array being moved from step to wizard
function actionPatch(json) {
let actions = json.actions || [];

Datei anzeigen

@ -18,7 +18,6 @@ const wizard = {
permitted: null,
},
mapped: ["permitted"],
advanced: ["restart_on_revisit"],
required: ["id"],
dependent: {
after_time: "after_time_scheduled",
@ -50,7 +49,6 @@ const step = {
force_final: false,
},
mapped: ["required_data", "permitted_params", "condition", "index"],
advanced: ["required_data", "permitted_params", "condition", "index"],
required: ["id"],
dependent: {},
objectArrays: {
@ -68,6 +66,7 @@ const field = {
label: null,
image: null,
description: null,
property: null,
required: null,
key: null,
type: null,
@ -75,7 +74,6 @@ const field = {
},
types: {},
mapped: ["prefill", "content", "condition", "index"],
advanced: ["property", "key", "condition", "index"],
required: ["id", "type"],
dependent: {},
objectArrays: {},
@ -196,14 +194,14 @@ const action = {
"visibility_level",
"members_visibility_level",
],
advanced: [
"code",
"custom_fields",
"skip_redirect",
"suppress_notifications",
"required",
],
required: ["id", "type"],
proTypes: [
'send_message',
'create_category',
'create_group',
'watch_categories',
'send_to_api'
],
dependent: {},
objectArrays: {},
};

Datei anzeigen

@ -8,7 +8,12 @@ export default DiscourseRoute.extend({
},
setupController(controller, model) {
const customFields = A(model || []);
controller.set("customFields", customFields);
const customFields = A(model.custom_fields || []);
const proSubscribed = model.pro_subscribed;
controller.setProperties({
customFields,
proSubscribed
});
},
});

Datei anzeigen

@ -32,7 +32,8 @@
{{custom-field-input
field=field
removeField=(action "removeField")
saveField=(action "saveField")}}
saveField=(action "saveField")
proSubscribed=proSubscribed}}
{{/each}}
</tbody>
</table>

Datei anzeigen

@ -126,33 +126,25 @@
</div>
</div>
{{wizard-advanced-toggle showAdvanced=wizard.showAdvanced}}
{{#if wizard.showAdvanced}}
<div class="advanced-settings">
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.save_submissions"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.save_submissions}}
<span>{{i18n "admin.wizard.save_submissions_label"}}</span>
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.restart_on_revisit"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.restart_on_revisit}}
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.save_submissions"}}</label>
</div>
{{/if}}
<div class="setting-value">
{{input type="checkbox" checked=wizard.save_submissions}}
<span>{{i18n "admin.wizard.save_submissions_label"}}</span>
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.restart_on_revisit"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.restart_on_revisit}}
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div>
</div>
</div>
{{wizard-links
@ -183,7 +175,8 @@
wizard=wizard
apis=apis
removeAction="removeAction"
wizardFields=wizardFields}}
wizardFields=wizardFields
proSubscribed=proSubscribed}}
{{/each}}
<div class="admin-wizard-buttons">

Datei anzeigen

@ -1,13 +1,13 @@
{{#if showInputs}}
<td>
{{combo-box
{{wizard-pro-selector
value=field.klass
content=klassContent
none="admin.wizard.custom_field.klass.select"
onChange=(action (mut field.klass))}}
</td>
<td>
{{combo-box
{{wizard-pro-selector
value=field.type
content=typeContent
none="admin.wizard.custom_field.type.select"

Datei anzeigen

@ -1,4 +0,0 @@
{{d-button
action="toggleAdvanced"
label="admin.wizard.advanced"
class=toggleClass}}

Datei anzeigen

@ -12,13 +12,14 @@
</div>
<div class="setting-value">
{{combo-box
{{wizard-pro-selector
value=action.type
content=actionTypes
onChange=(action "changeType")
options=(hash
none="admin.wizard.select_type"
)}}
)
}}
</div>
</div>
@ -714,99 +715,90 @@
</div>
{{/if}}
{{#if showAdvanced}}
{{wizard-advanced-toggle showAdvanced=action.showAdvanced}}
{{#if action.showAdvanced}}
<div class="advanced-settings">
{{#if hasCustomFields}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.custom_fields.label"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.custom_fields
property="custom_fields"
onUpdate=(action "mappedFieldUpdated")
options=(hash
inputTypes="association"
customFieldSelection="key"
wizardFieldSelection="value"
wizardActionSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.action.custom_fields.key"
context=customFieldsContext
)}}
</div>
</div>
{{/if}}
{{#if sendMessage}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.required"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.required
property="required"
onUpdate=(action "mappedFieldUpdated")
options=(hash
textSelection="value"
wizardFieldSelection=true
userFieldSelection=true
groupSelection=true
context="action"
)}}
</div>
</div>
{{/if}}
{{#if showPostAdvanced}}
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.skip_redirect}}
<span>
{{i18n "admin.wizard.action.skip_redirect.description" type="topic"}}
</span>
</div>
</div>
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.suppress_notifications.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.suppress_notifications}}
<span>
{{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}}
</span>
</div>
</div>
{{/if}}
{{#if routeTo}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.route_to.code"}}</label>
</div>
<div class="setting-value">
{{input value=action.code}}
</div>
</div>
{{/if}}
{{#if hasCustomFields}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.custom_fields.label"}}</label>
</div>
{{/if}}
<div class="setting-value">
{{wizard-mapper
inputs=action.custom_fields
property="custom_fields"
onUpdate=(action "mappedFieldUpdated")
options=(hash
inputTypes="association"
customFieldSelection="key"
wizardFieldSelection="value"
wizardActionSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.action.custom_fields.key"
context=customFieldsContext
)}}
</div>
</div>
{{/if}}
{{#if sendMessage}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.required"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.required
property="required"
onUpdate=(action "mappedFieldUpdated")
options=(hash
textSelection="value"
wizardFieldSelection=true
userFieldSelection=true
groupSelection=true
context="action"
)}}
</div>
</div>
{{/if}}
{{#if showPostAdvanced}}
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.skip_redirect}}
<span>
{{i18n "admin.wizard.action.skip_redirect.description" type="topic"}}
</span>
</div>
</div>
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.suppress_notifications.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.suppress_notifications}}
<span>
{{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}}
</span>
</div>
</div>
{{/if}}
{{#if routeTo}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.route_to.code"}}</label>
</div>
<div class="setting-value">
{{input value=action.code}}
</div>
</div>
{{/if}}

Datei anzeigen

@ -258,11 +258,10 @@
<label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value medium">
<div class="setting-value">
{{input
name="key"
value=field.key
class="medium"
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>

Datei anzeigen

@ -55,66 +55,62 @@
<span>{{i18n "admin.wizard.step.force_final.description"}}</span>
</div>
</div>
{{/if}}
{{wizard-advanced-toggle showAdvanced=step.showAdvanced}}
{{#if step.showAdvanced}}
<div class="advanced-settings">
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.required_data.label"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.required_data
options=(hash
inputTypes="validation"
inputConnector="and"
wizardFieldSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.submission_key"
context="step"
)}}
{{#if step.required_data}}
<div class="required-data-message">
<div class="label">
{{i18n "admin.wizard.step.required_data.not_permitted_message"}}
</div>
{{input value=step.required_data_message}}
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.required_data.label"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.required_data
options=(hash
inputTypes="validation"
inputConnector="and"
wizardFieldSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.submission_key"
context="step"
)}}
{{#if step.required_data}}
<div class="required-data-message">
<div class="label">
{{i18n "admin.wizard.step.required_data.not_permitted_message"}}
</div>
{{/if}}
</div>
{{input value=step.required_data_message}}
</div>
{{/if}}
</div>
</div>
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.permitted_params.label"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.permitted_params
options=(hash
pairConnector="set"
inputTypes="association"
keyPlaceholder="admin.wizard.param_key"
valuePlaceholder="admin.wizard.submission_key"
context="step"
)}}
</div>
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.permitted_params.label"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.permitted_params
options=(hash
pairConnector="set"
inputTypes="association"
keyPlaceholder="admin.wizard.param_key"
valuePlaceholder="admin.wizard.submission_key"
context="step"
)}}
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
</div>
<div class="setting-value">
{{input
name="key"
value=step.key
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{input
name="key"
value=step.key
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>
{{/if}}

Datei anzeigen

@ -0,0 +1,15 @@
<div class="select-kit-header-wrapper">
{{component selectKit.options.selectedNameComponent
tabindex=tabindex
item=selectedContent
selectKit=selectKit
shouldDisplayClearableButton=shouldDisplayClearableButton
}}
{{#if selectedContent.pro}}
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
{{/if}}
{{d-icon caretIcon class="caret-icon"}}
</div>

Datei anzeigen

@ -0,0 +1,15 @@
{{#if icons}}
<div class="icons">
<span class="selection-indicator"></span>
{{#each icons as |icon|}}
{{d-icon icon translatedtitle=(dasherize title)}}
{{/each}}
</div>
{{/if}}
<div class="texts">
<span class="name">{{html-safe label}}</span>
{{#if item.pro}}
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
{{/if}}
</div>

Datei anzeigen

@ -243,7 +243,7 @@
font-size: 0.85em;
}
span {
> span {
font-size: 0.929em;
}
@ -382,11 +382,6 @@
margin: 0;
}
}
.pro-label {
color: $tertiary;
font-size: .75em;
}
}
}
@ -764,6 +759,11 @@
vertical-align: middle;
}
.pro-label {
color: var(--tertiary);
font-size: .75em;
}
.admin-wizards-pro {
.admin-wizard-controls {
h3, label {
@ -773,8 +773,8 @@
label {
padding: .4em .5em;
margin-left: .75em;
background-color: $success;
color: $secondary;
background-color: var(--success);
color: var(--secondary);
}
.buttons {
@ -807,17 +807,29 @@
display: flex;
align-items: center;
padding: 1em;
background-color: $primary-very-low;
background-color: var(--primary-very-low);
.subscription-state {
padding: .25em .5em;
margin-right: .75em;
&.active {
background-color: $success;
color: $secondary;
background-color: var(--success);
color: var(--secondary);
}
}
}
}
}
.wizard-pro-selector.select-kit.single-select {
.select-kit-row .texts {
display: flex;
align-items: center;
}
.pro-label {
margin-left: .75em;
padding-top: .25em;
}
}

Datei anzeigen

@ -179,9 +179,9 @@ en:
min_length_placeholder: "Minimum length in characters"
max_length: "Max Length"
max_length_placeholder: "Maximum length in characters"
char_counter: "Character Counter"
char_counter: "Counter"
char_counter_placeholder: "Display Character Counter"
field_placeholder: "Field Placeholder"
field_placeholder: "Placeholder"
file_types: "File Types"
preview_template: "Preview Template"
limit: "Limit"

Datei anzeigen

@ -1,7 +1,10 @@
# frozen_string_literal: true
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
def index
render_json_dump(custom_field_list)
render_json_dump(
custom_fields: custom_field_list,
pro_subscribed: CustomWizard::Pro.subscribed?
)
end
def update

Datei anzeigen

@ -17,7 +17,9 @@ class ::CustomWizard::CustomField
category: ["basic_category"],
post: ["post"]
}
PRO_CLASSES ||= ['category', 'group']
TYPES ||= ["string", "boolean", "integer", "json"]
PRO_TYPES ||= ["json"]
LIST_CACHE_KEY ||= 'custom_field_list'
def self.serializers
@ -83,6 +85,10 @@ class ::CustomWizard::CustomField
next
end
if attr == 'klass' && PRO_CLASSES.include?(value) && !@pro.subscribed?
add_error(I18n.t("wizard.custom_field.error.pro_type", type: value))
end
if attr == 'serializers' && (unsupported = value - CLASSES[klass.to_sym]).length > 0
add_error(I18n.t("#{i18n_key}.unsupported_serializers",
class: klass,
@ -94,7 +100,7 @@ class ::CustomWizard::CustomField
add_error(I18n.t("#{i18n_key}.unsupported_type", type: value))
end
if attr == 'type' && value == 'json' && !@pro.subscribed?
if attr == 'type' && PRO_TYPES.include?(value) && !@pro.subscribed?
add_error(I18n.t("wizard.custom_field.error.pro_type", type: value))
end

Datei anzeigen

@ -16,7 +16,7 @@ class CustomWizard::Pro
end
def subscribed?
@subscription.active?
false #@subscription.active?
end
def server

Datei anzeigen

@ -31,7 +31,7 @@ class CustomWizard::TemplateValidator
if data[:actions].present?
data[:actions].each do |action|
validate_pro_action(action)
validate_pro(action, :action)
check_required(action, :action)
end
end