Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 09:20:29 +01:00
Add custom wizard integration
Dieser Commit ist enthalten in:
Ursprung
819c76b3ff
Commit
5ffcee1dde
14 geänderte Dateien mit 175 neuen und 30 gelöschten Zeilen
|
@ -3,7 +3,8 @@ import { default as computed, observes } from 'ember-addons/ember-computed-decor
|
||||||
const ACTION_TYPES = [
|
const ACTION_TYPES = [
|
||||||
{ id: 'create_topic', name: 'Create Topic' },
|
{ id: 'create_topic', name: 'Create Topic' },
|
||||||
{ id: 'update_profile', name: 'Update Profile' },
|
{ id: 'update_profile', name: 'Update Profile' },
|
||||||
{ id: 'send_message', name: 'Send Message' }
|
{ id: 'send_message', name: 'Send Message' },
|
||||||
|
{ id: 'send_to_api', name: 'Send to API' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROFILE_FIELDS = [
|
const PROFILE_FIELDS = [
|
||||||
|
@ -26,6 +27,8 @@ export default Ember.Component.extend({
|
||||||
createTopic: Ember.computed.equal('action.type', 'create_topic'),
|
createTopic: Ember.computed.equal('action.type', 'create_topic'),
|
||||||
updateProfile: Ember.computed.equal('action.type', 'update_profile'),
|
updateProfile: Ember.computed.equal('action.type', 'update_profile'),
|
||||||
sendMessage: Ember.computed.equal('action.type', 'send_message'),
|
sendMessage: Ember.computed.equal('action.type', 'send_message'),
|
||||||
|
sendToApi: Ember.computed.equal('action.type', 'send_to_api'),
|
||||||
|
apiEmpty: Ember.computed.empty('action.api'),
|
||||||
disableId: Ember.computed.not('action.isNew'),
|
disableId: Ember.computed.not('action.isNew'),
|
||||||
|
|
||||||
@computed('currentStepId', 'wizard.save_submissions')
|
@computed('currentStepId', 'wizard.save_submissions')
|
||||||
|
@ -75,5 +78,21 @@ export default Ember.Component.extend({
|
||||||
toggleCustomCategoryWizardField() {
|
toggleCustomCategoryWizardField() {
|
||||||
const user = this.get('action.custom_category_user_field');
|
const user = this.get('action.custom_category_user_field');
|
||||||
if (user) this.set('action.custom_category_wizard_field', false);
|
if (user) this.set('action.custom_category_wizard_field', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('wizard.apis')
|
||||||
|
availableApis(apis) {
|
||||||
|
return apis.map(a => {
|
||||||
|
return {
|
||||||
|
id: a.name,
|
||||||
|
name: a.title
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('wizard.apis', 'action.api')
|
||||||
|
availableEndpoints(apis, api) {
|
||||||
|
if (!api) return [];
|
||||||
|
return apis.find(a => a.name === api).endpoints;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,7 +78,6 @@ export default Ember.Controller.extend({
|
||||||
if (api.title) data['title'] = api.title;
|
if (api.title) data['title'] = api.title;
|
||||||
|
|
||||||
const originalTitle = this.get('api.originalTitle');
|
const originalTitle = this.get('api.originalTitle');
|
||||||
console.log(api, originalTitle);
|
|
||||||
if (api.get('isNew') || (originalTitle && (api.title !== originalTitle))) {
|
if (api.get('isNew') || (originalTitle && (api.title !== originalTitle))) {
|
||||||
refreshList = true;
|
refreshList = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,8 @@ export default Discourse.Route.extend({
|
||||||
afterModel(model) {
|
afterModel(model) {
|
||||||
return Ember.RSVP.all([
|
return Ember.RSVP.all([
|
||||||
this._getFieldTypes(model),
|
this._getFieldTypes(model),
|
||||||
this._getThemes(model)
|
this._getThemes(model),
|
||||||
|
this._getApis(model)
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -48,6 +49,11 @@ export default Discourse.Route.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_getApis(model) {
|
||||||
|
return ajax('/admin/wizards/apis')
|
||||||
|
.then((result) => model.set('apis', result));
|
||||||
|
},
|
||||||
|
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
const newWizard = this.get('newWizard');
|
const newWizard = this.get('newWizard');
|
||||||
const steps = model.get('steps') || [];
|
const steps = model.get('steps') || [];
|
||||||
|
|
|
@ -96,7 +96,7 @@
|
||||||
<label>{{i18n 'admin.wizard.action.post_builder.user_fields'}}{{builderUserFields}}</label>
|
<label>{{i18n 'admin.wizard.action.post_builder.user_fields'}}{{builderUserFields}}</label>
|
||||||
<label>{{i18n 'admin.wizard.action.post_builder.wizard_fields'}}{{builderWizardFields}}</label>
|
<label>{{i18n 'admin.wizard.action.post_builder.wizard_fields'}}{{builderWizardFields}}</label>
|
||||||
{{d-editor value=action.post_template
|
{{d-editor value=action.post_template
|
||||||
placeholder='admin.wizard.action.post_builder.placeholder'
|
placeholder='admin.wizard.action.interpolate_fields'
|
||||||
classNames='post-builder-editor'}}
|
classNames='post-builder-editor'}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
<label>{{i18n 'admin.wizard.action.post_builder.user_fields'}}{{builderUserFields}}</label>
|
<label>{{i18n 'admin.wizard.action.post_builder.user_fields'}}{{builderUserFields}}</label>
|
||||||
<label>{{i18n 'admin.wizard.action.post_builder.wizard_fields'}}{{builderWizardFields}}</label>
|
<label>{{i18n 'admin.wizard.action.post_builder.wizard_fields'}}{{builderWizardFields}}</label>
|
||||||
{{d-editor value=action.post_template
|
{{d-editor value=action.post_template
|
||||||
placeholder='admin.wizard.action.post_builder.placeholder'
|
placeholder='admin.wizard.action.interpolate_fields'
|
||||||
classNames='post-builder-editor'}}
|
classNames='post-builder-editor'}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -204,3 +204,41 @@
|
||||||
allowUserField=true}}
|
allowUserField=true}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if sendToApi}}
|
||||||
|
<div class="setting">
|
||||||
|
<div class="setting-label">
|
||||||
|
<h3>{{i18n "admin.wizard.action.send_to_api.api"}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="setting-value">
|
||||||
|
{{combo-box value=action.api
|
||||||
|
content=availableApis
|
||||||
|
none='admin.wizard.action.send_to_api.select_an_api'
|
||||||
|
isDisabled=action.custom_title_enabled}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting">
|
||||||
|
<div class="setting-label">
|
||||||
|
<h3>{{i18n "admin.wizard.action.send_to_api.endpoint"}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="setting-value">
|
||||||
|
{{combo-box value=action.api_endpoint
|
||||||
|
content=availableEndpoints
|
||||||
|
none='admin.wizard.action.send_to_api.select_an_endpoint'
|
||||||
|
isDisabled=apiEmpty}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting api-body">
|
||||||
|
<div class="setting-label">
|
||||||
|
<h3>{{i18n "admin.wizard.action.send_to_api.body"}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="setting-value">
|
||||||
|
<label>{{i18n 'admin.wizard.action.post_builder.user_fields'}}{{builderUserFields}}</label>
|
||||||
|
<label>{{i18n 'admin.wizard.action.post_builder.wizard_fields'}}{{builderWizardFields}}</label>
|
||||||
|
{{textarea value=action.api_body
|
||||||
|
placeholder=(i18n 'admin.wizard.action.interpolate_fields')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
|
@ -117,6 +117,23 @@
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.api-body {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
max-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-value {
|
||||||
|
width: calc(100% - 180px);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-links {
|
.wizard-links {
|
||||||
|
|
|
@ -103,6 +103,8 @@ en:
|
||||||
add_fields: "{{type}} Fields"
|
add_fields: "{{type}} Fields"
|
||||||
available_fields: "* If 'Save wizard submissions' is disabled, only the fields of the current step are available to the current step's actions."
|
available_fields: "* If 'Save wizard submissions' is disabled, only the fields of the current step are available to the current step's actions."
|
||||||
topic_attr: "Topic Attribute"
|
topic_attr: "Topic Attribute"
|
||||||
|
interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}."
|
||||||
|
|
||||||
skip_redirect:
|
skip_redirect:
|
||||||
label: "Skip Redirect"
|
label: "Skip Redirect"
|
||||||
description: "Don't redirect the user to this {{type}} after the wizard completes"
|
description: "Don't redirect the user to this {{type}} after the wizard completes"
|
||||||
|
@ -120,12 +122,19 @@ en:
|
||||||
label: "Builder"
|
label: "Builder"
|
||||||
user_fields: "User Fields: "
|
user_fields: "User Fields: "
|
||||||
wizard_fields: "Wizard Fields: "
|
wizard_fields: "Wizard Fields: "
|
||||||
placeholder: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}."
|
|
||||||
custom_title: "Custom Title"
|
custom_title: "Custom Title"
|
||||||
custom_category:
|
custom_category:
|
||||||
label: "Custom Category"
|
label: "Custom Category"
|
||||||
wizard_field: "Wizard Field"
|
wizard_field: "Wizard Field"
|
||||||
user_field: "User Field"
|
user_field: "User Field"
|
||||||
|
send_to_api:
|
||||||
|
label: "Send to API"
|
||||||
|
api: "API"
|
||||||
|
endpoint: "Endpoint"
|
||||||
|
select_an_api: "Select an API"
|
||||||
|
select_an_endpoint: "Select an endpoint"
|
||||||
|
body: "Request body JSON"
|
||||||
|
|
||||||
api:
|
api:
|
||||||
label: "API"
|
label: "API"
|
||||||
nav_label: 'APIs'
|
nav_label: 'APIs'
|
||||||
|
|
|
@ -106,10 +106,10 @@ class CustomWizard::ApiController < ::ApplicationController
|
||||||
:client_secret,
|
:client_secret,
|
||||||
:username,
|
:username,
|
||||||
:password,
|
:password,
|
||||||
:params
|
:auth_params
|
||||||
)
|
)
|
||||||
|
|
||||||
auth_data[:params] = JSON.parse(auth_data[:params]) if auth_data[:params].present?
|
auth_data[:auth_params] = JSON.parse(auth_data[:auth_params]) if auth_data[:auth_params].present?
|
||||||
|
|
||||||
@auth_data ||= auth_data
|
@auth_data ||= auth_data
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,8 @@ class CustomWizard::Api::Authorization
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.set(api_name, new_data = {})
|
def self.set(api_name, new_data = {})
|
||||||
|
api_name = api_name.underscore
|
||||||
|
|
||||||
data = self.get(api_name, data_only: true) || {}
|
data = self.get(api_name, data_only: true) || {}
|
||||||
|
|
||||||
new_data.each do |k, v|
|
new_data.each do |k, v|
|
||||||
|
@ -44,6 +46,8 @@ class CustomWizard::Api::Authorization
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.get(api_name, opts = {})
|
def self.get(api_name, opts = {})
|
||||||
|
api_name = api_name.underscore
|
||||||
|
|
||||||
if data = PluginStore.get("custom_wizard_api_#{api_name}", 'authorization')
|
if data = PluginStore.get("custom_wizard_api_#{api_name}", 'authorization')
|
||||||
if opts[:data_only]
|
if opts[:data_only]
|
||||||
data
|
data
|
||||||
|
@ -60,22 +64,16 @@ class CustomWizard::Api::Authorization
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.get_header_authorization_string(name)
|
def self.get_header_authorization_string(name)
|
||||||
protocol = authentication_protocol(name)
|
auth = CustomWizard::Api::Authorization.get(name)
|
||||||
raise Discourse::InvalidParameters.new(:name) unless protocol.present?
|
raise Discourse::InvalidParameters.new(:name) unless auth.present?
|
||||||
raise Discourse::InvalidParameters.new(:protocol) unless [BASIC_AUTH, OAUTH2_AUTH].include? protocol
|
|
||||||
|
|
||||||
if protocol = BASIC_AUTH
|
if auth.auth_type === "basic"
|
||||||
username = username(name)
|
raise Discourse::InvalidParameters.new(:username) unless auth.username.present?
|
||||||
raise Discourse::InvalidParameters.new(:username) unless username.present?
|
raise Discourse::InvalidParameters.new(:password) unless auth.password.present?
|
||||||
password = password(name)
|
"Basic #{Base64.strict_encode64((auth.username + ":" + auth.password).chomp)}"
|
||||||
raise Discourse::InvalidParameters.new(:password) unless password.present?
|
|
||||||
authorization_string = (username + ":" + password).chomp
|
|
||||||
"Basic #{Base64.strict_encode64(authorization_string)}"
|
|
||||||
else
|
else
|
||||||
# must be OAUTH2
|
raise Discourse::InvalidParameters.new(auth.access_token) unless auth.access_token.present?
|
||||||
access_token = access_token(name)
|
"Bearer #{auth.access_token}"
|
||||||
raise Discourse::InvalidParameters.new(access_token) unless access_token.present?
|
|
||||||
"Bearer #{access_token}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,13 @@ class CustomWizard::Api::Endpoint
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.set(api_name, new_data)
|
def self.set(api_name, new_data)
|
||||||
data = new_data[:endpoint_id] ? self.get(api_name, new_data[:endpoint_id], data_only: true) : {}
|
if new_data['id']
|
||||||
endpoint_id = new_data[:endpoint_id] || SecureRandom.hex(3)
|
data = self.get(api_name, new_data['id'], data_only: true)
|
||||||
|
endpoint_id = new_data['id']
|
||||||
|
else
|
||||||
|
data = {}
|
||||||
|
endpoint_id = SecureRandom.hex(3)
|
||||||
|
end
|
||||||
|
|
||||||
new_data.each do |k, v|
|
new_data.each do |k, v|
|
||||||
data[k.to_sym] = v
|
data[k.to_sym] = v
|
||||||
|
@ -32,11 +37,10 @@ class CustomWizard::Api::Endpoint
|
||||||
return nil if !endpoint_id
|
return nil if !endpoint_id
|
||||||
|
|
||||||
if data = PluginStore.get("custom_wizard_api_#{api_name}", "endpoint_#{endpoint_id}")
|
if data = PluginStore.get("custom_wizard_api_#{api_name}", "endpoint_#{endpoint_id}")
|
||||||
data[:id] = endpoint_id
|
|
||||||
|
|
||||||
if opts[:data_only]
|
if opts[:data_only]
|
||||||
data
|
data
|
||||||
else
|
else
|
||||||
|
data[:id] = endpoint_id
|
||||||
self.new(api_name, data)
|
self.new(api_name, data)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -48,8 +52,8 @@ class CustomWizard::Api::Endpoint
|
||||||
PluginStoreRow.where("plugin_name = 'custom_wizard_api_#{api_name}' AND key LIKE 'endpoint_%'").destroy_all
|
PluginStoreRow.where("plugin_name = 'custom_wizard_api_#{api_name}' AND key LIKE 'endpoint_%'").destroy_all
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.list
|
def self.list(api_name)
|
||||||
PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_%' AND key LIKE 'endpoint_%'")
|
PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_#{api_name}' AND key LIKE 'endpoint_%'")
|
||||||
.map do |record|
|
.map do |record|
|
||||||
api_name = record['plugin_name'].sub("custom_wizard_api_", "")
|
api_name = record['plugin_name'].sub("custom_wizard_api_", "")
|
||||||
data = ::JSON.parse(record['value'])
|
data = ::JSON.parse(record['value'])
|
||||||
|
@ -57,4 +61,32 @@ class CustomWizard::Api::Endpoint
|
||||||
self.new(api_name, data)
|
self.new(api_name, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.request(api_name, endpoint_id, body)
|
||||||
|
endpoint = self.get(api_name, endpoint_id)
|
||||||
|
auth = CustomWizard::Api::Authorization.get_header_authorization_string(api_name)
|
||||||
|
|
||||||
|
connection = Excon.new(
|
||||||
|
URI.parse(URI.encode(endpoint.url)).to_s,
|
||||||
|
:headers => {
|
||||||
|
"Authorization" => auth,
|
||||||
|
"Accept" => "application/json, */*",
|
||||||
|
"Content-Type" => "application/json"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
method: endpoint.method
|
||||||
|
}
|
||||||
|
|
||||||
|
if body
|
||||||
|
body = JSON.generate(body)
|
||||||
|
body.delete! '\\'
|
||||||
|
params[:body] = body
|
||||||
|
end
|
||||||
|
|
||||||
|
response = connection.request(params)
|
||||||
|
|
||||||
|
JSON.parse(response.body)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -393,6 +393,17 @@ class CustomWizard::Builder
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def send_to_api(user, action, data)
|
||||||
|
api_body = CustomWizard::Builder.fill_placeholders(JSON.generate(JSON.parse(action['api_body'])), user, data)
|
||||||
|
result = CustomWizard::Api::Endpoint.request(action['api'], action['api_endpoint'], api_body)
|
||||||
|
|
||||||
|
if result['error']
|
||||||
|
updater.errors.add(:send_message, result['error'])
|
||||||
|
else
|
||||||
|
## add validation callback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def save_submissions(data, final_step)
|
def save_submissions(data, final_step)
|
||||||
if final_step
|
if final_step
|
||||||
data['submitted_at'] = Time.now.iso8601
|
data['submitted_at'] = Time.now.iso8601
|
||||||
|
|
|
@ -97,6 +97,7 @@ after_initialize do
|
||||||
load File.expand_path('../serializers/api/authorization_serializer.rb', __FILE__)
|
load File.expand_path('../serializers/api/authorization_serializer.rb', __FILE__)
|
||||||
load File.expand_path('../serializers/api/basic_api_serializer.rb', __FILE__)
|
load File.expand_path('../serializers/api/basic_api_serializer.rb', __FILE__)
|
||||||
load File.expand_path('../serializers/api/endpoint_serializer.rb', __FILE__)
|
load File.expand_path('../serializers/api/endpoint_serializer.rb', __FILE__)
|
||||||
|
load File.expand_path('../serializers/api/basic_endpoint_serializer.rb', __FILE__)
|
||||||
|
|
||||||
::UsersController.class_eval do
|
::UsersController.class_eval do
|
||||||
def wizard_path
|
def wizard_path
|
||||||
|
|
|
@ -14,7 +14,7 @@ class CustomWizard::ApiSerializer < ApplicationSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def endpoints
|
def endpoints
|
||||||
if endpoints = CustomWizard::Api::Endpoint.list
|
if endpoints = CustomWizard::Api::Endpoint.list(object.name)
|
||||||
ActiveModel::ArraySerializer.new(
|
ActiveModel::ArraySerializer.new(
|
||||||
endpoints,
|
endpoints,
|
||||||
each_serializer: CustomWizard::Api::EndpointSerializer
|
each_serializer: CustomWizard::Api::EndpointSerializer
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
class CustomWizard::BasicApiSerializer < ApplicationSerializer
|
class CustomWizard::BasicApiSerializer < ApplicationSerializer
|
||||||
attributes :name, :title
|
attributes :name,
|
||||||
|
:title,
|
||||||
|
:endpoints
|
||||||
|
|
||||||
|
def endpoints
|
||||||
|
if endpoints = CustomWizard::Api::Endpoint.list(object.name)
|
||||||
|
ActiveModel::ArraySerializer.new(
|
||||||
|
endpoints,
|
||||||
|
each_serializer: CustomWizard::Api::BasicEndpointSerializer
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
4
serializers/api/basic_endpoint_serializer.rb
Normale Datei
4
serializers/api/basic_endpoint_serializer.rb
Normale Datei
|
@ -0,0 +1,4 @@
|
||||||
|
class CustomWizard::Api::BasicEndpointSerializer < ApplicationSerializer
|
||||||
|
attributes :id,
|
||||||
|
:name
|
||||||
|
end
|
Laden …
In neuem Issue referenzieren