Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-25 18:50:27 +01:00
Action logging and submissions bugixs
Dieser Commit ist enthalten in:
Ursprung
4c3e88beee
Commit
04198339ca
19 geänderte Dateien mit 193 neuen und 42 gelöschten Zeilen
|
@ -1,5 +1,5 @@
|
||||||
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
|
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
|
||||||
import { equal, empty, or } from "@ember/object/computed";
|
import { equal, empty, or, notEmpty } from "@ember/object/computed";
|
||||||
import { generateName, selectKitContent, schema } from '../lib/wizard';
|
import { generateName, selectKitContent, schema } from '../lib/wizard';
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ export default Component.extend({
|
||||||
basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
|
basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
|
||||||
publicTopicFields: or('createTopic', 'openComposer'),
|
publicTopicFields: or('createTopic', 'openComposer'),
|
||||||
showSkipRedirect: or('createTopic', 'sendMessage'),
|
showSkipRedirect: or('createTopic', 'sendMessage'),
|
||||||
|
showPostBuilder: notEmpty('action.post_template'),
|
||||||
|
|
||||||
@discourseComputed('wizard.steps')
|
@discourseComputed('wizard.steps')
|
||||||
runAfterContent(steps) {
|
runAfterContent(steps) {
|
||||||
|
|
52
assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6
Normale Datei
52
assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6
Normale Datei
|
@ -0,0 +1,52 @@
|
||||||
|
import { default as computed } from 'discourse-common/utils/decorators';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
import { notEmpty } from "@ember/object/computed";
|
||||||
|
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
refreshing: false,
|
||||||
|
hasLogs: notEmpty("logs"),
|
||||||
|
page: 0,
|
||||||
|
canLoadMore: true,
|
||||||
|
logs: [],
|
||||||
|
|
||||||
|
loadLogs() {
|
||||||
|
if (!this.canLoadMore) return;
|
||||||
|
|
||||||
|
this.set("refreshing", true);
|
||||||
|
|
||||||
|
ajax('/admin/wizards/logs', {
|
||||||
|
data: {
|
||||||
|
page: this.page
|
||||||
|
}
|
||||||
|
}).catch(popupAjaxError)
|
||||||
|
.then(result => {
|
||||||
|
if (!result || result.length === 0) {
|
||||||
|
this.set('canLoadMore', false);
|
||||||
|
}
|
||||||
|
this.set("logs", this.logs.concat(result));
|
||||||
|
})
|
||||||
|
.finally(() => this.set("refreshing", false));
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('hasLogs', 'refreshing')
|
||||||
|
noResults(hasLogs, refreshing) {
|
||||||
|
return !hasLogs && !refreshing;
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
loadMore() {
|
||||||
|
this.set('page', this.page += 1);
|
||||||
|
this.loadLogs();
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.setProperties({
|
||||||
|
canLoadMore: true,
|
||||||
|
page: 0,
|
||||||
|
logs: []
|
||||||
|
})
|
||||||
|
this.loadLogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -15,6 +15,8 @@ export default {
|
||||||
this.route('adminWizardsApiShow', { path: '/:name', resetNamespace: true });
|
this.route('adminWizardsApiShow', { path: '/:name', resetNamespace: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route('adminWizardsLogs', { path: '/logs', resetNamespace: true });
|
||||||
|
|
||||||
this.route('adminWizardsTransfer', { path: '/transfer', resetNamespace: true });
|
this.route('adminWizardsTransfer', { path: '/transfer', resetNamespace: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
28
assets/javascripts/discourse/templates/admin-wizards-logs.hbs
Normale Datei
28
assets/javascripts/discourse/templates/admin-wizards-logs.hbs
Normale Datei
|
@ -0,0 +1,28 @@
|
||||||
|
<div class="admin-wizard-controls">
|
||||||
|
{{d-button label="refresh" icon="refresh" action="refresh"}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#load-more selector=".log-list tr" action=(action "loadMore")}}
|
||||||
|
{{#if noResults}}
|
||||||
|
<p>{{i18n 'search.no_results'}}</p>
|
||||||
|
{{else}}
|
||||||
|
<table class="table grid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each logs as |log|}}
|
||||||
|
<tr>
|
||||||
|
<td>{{log.message}}</td>
|
||||||
|
<td>{{bound-date log.date}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{conditional-loading-spinner condition=refreshing}}
|
||||||
|
{{/load-more}}
|
|
@ -4,6 +4,7 @@
|
||||||
{{#if siteSettings.wizard_api_features}}
|
{{#if siteSettings.wizard_api_features}}
|
||||||
{{nav-item route='adminWizardsApis' label='admin.wizard.api.nav_label'}}
|
{{nav-item route='adminWizardsApis' label='admin.wizard.api.nav_label'}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{nav-item route='adminWizardsLogs' label='admin.wizard.log.nav_label'}}
|
||||||
{{nav-item route='adminWizardsTransfer' label='admin.wizard.transfer.nav_label'}}
|
{{nav-item route='adminWizardsTransfer' label='admin.wizard.transfer.nav_label'}}
|
||||||
{{/admin-nav}}
|
{{/admin-nav}}
|
||||||
|
|
||||||
|
|
|
@ -57,17 +57,17 @@
|
||||||
onChange=(action (mut action.post))
|
onChange=(action (mut action.post))
|
||||||
options=(hash
|
options=(hash
|
||||||
none='admin.wizard.selector.placeholder.wizard_field'
|
none='admin.wizard.selector.placeholder.wizard_field'
|
||||||
isDisabled=action.post_builder
|
isDisabled=showPostBuilder
|
||||||
)}}
|
)}}
|
||||||
|
|
||||||
<div class="setting-gutter">
|
<div class="setting-gutter">
|
||||||
{{input type='checkbox' checked=action.post_builder}}
|
{{input type='checkbox' checked=showPostBuilder}}
|
||||||
<span>{{i18n 'admin.wizard.action.post_builder.checkbox'}}</span>
|
<span>{{i18n 'admin.wizard.action.post_builder.checkbox'}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if action.post_builder}}
|
{{#if showPostBuilder}}
|
||||||
<div class="setting full">
|
<div class="setting full">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
<label>{{i18n 'admin.wizard.action.post_builder.label'}}</label>
|
<label>{{i18n 'admin.wizard.action.post_builder.label'}}</label>
|
||||||
|
|
|
@ -54,9 +54,9 @@
|
||||||
{{#if showTag}}
|
{{#if showTag}}
|
||||||
{{tag-chooser
|
{{tag-chooser
|
||||||
tags=value
|
tags=value
|
||||||
filterable=true
|
|
||||||
options=(hash
|
options=(hash
|
||||||
none=placeholderKey
|
none=placeholderKey
|
||||||
|
filterable=true
|
||||||
)}}
|
)}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,10 @@
|
||||||
|
|
||||||
.admin-wizard-container {
|
.admin-wizard-container {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
||||||
.row > .content table {
|
|
||||||
margin-top: 0;
|
|
||||||
min-width: 100%;
|
|
||||||
width: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wizard-submissions {
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-settings-parent {
|
.wizard-settings-parent {
|
||||||
|
|
|
@ -261,6 +261,9 @@ en:
|
||||||
log:
|
log:
|
||||||
label: "Logs"
|
label: "Logs"
|
||||||
|
|
||||||
|
log:
|
||||||
|
nav_label: "Logs"
|
||||||
|
|
||||||
transfer:
|
transfer:
|
||||||
nav_label: "Transfer"
|
nav_label: "Transfer"
|
||||||
export:
|
export:
|
||||||
|
|
|
@ -31,4 +31,3 @@ en:
|
||||||
wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview"
|
wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview"
|
||||||
wizard_step_advanced: "Enable advanced settings for wizard steps (experimental)."
|
wizard_step_advanced: "Enable advanced settings for wizard steps (experimental)."
|
||||||
wizard_api_features: "Enable API features (experimental)."
|
wizard_api_features: "Enable API features (experimental)."
|
||||||
wizard_action_debug: "Log action details for debugging."
|
|
|
@ -32,6 +32,8 @@ Discourse::Application.routes.append do
|
||||||
get 'admin/wizards/apis/:name/redirect' => 'admin_api#redirect'
|
get 'admin/wizards/apis/:name/redirect' => 'admin_api#redirect'
|
||||||
get 'admin/wizards/apis/:name/authorize' => 'admin_api#authorize'
|
get 'admin/wizards/apis/:name/authorize' => 'admin_api#authorize'
|
||||||
|
|
||||||
|
get 'admin/wizards/logs' => 'admin_logs#index'
|
||||||
|
|
||||||
get 'admin/wizards/transfer' => 'transfer#index'
|
get 'admin/wizards/transfer' => 'transfer#index'
|
||||||
get 'admin/wizards/transfer/export' => 'transfer#export'
|
get 'admin/wizards/transfer/export' => 'transfer#export'
|
||||||
post 'admin/wizards/transfer/import' => 'transfer#import'
|
post 'admin/wizards/transfer/import' => 'transfer#import'
|
||||||
|
|
|
@ -21,5 +21,3 @@ plugins:
|
||||||
wizard_api_features:
|
wizard_api_features:
|
||||||
client: true
|
client: true
|
||||||
default: false
|
default: false
|
||||||
wizard_action_debug:
|
|
||||||
default: false
|
|
8
controllers/custom_wizard/admin/logs.rb
Normale Datei
8
controllers/custom_wizard/admin/logs.rb
Normale Datei
|
@ -0,0 +1,8 @@
|
||||||
|
class CustomWizard::AdminLogsController < CustomWizard::AdminController
|
||||||
|
def index
|
||||||
|
render_serialized(
|
||||||
|
CustomWizard::Log.list(params[:page].to_i),
|
||||||
|
CustomWizard::LogSerializer
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,6 @@
|
||||||
class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
|
class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
|
||||||
skip_before_action :check_xhr, only: [:download_submissions]
|
skip_before_action :preload_json, :check_xhr, only: [:download]
|
||||||
|
|
||||||
before_action :find_wizard
|
before_action :find_wizard
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -23,8 +24,9 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
|
||||||
|
|
||||||
def download
|
def download
|
||||||
send_data build_submissions(@wizard.id).to_json,
|
send_data build_submissions(@wizard.id).to_json,
|
||||||
filename: "#{Discourse.current_hostname}-wizard-submissions-#{wizard['name']}.json",
|
filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json",
|
||||||
content_type: "application/json"
|
content_type: "application/json",
|
||||||
|
disposition: "attachment"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -6,27 +6,29 @@ class CustomWizard::Action
|
||||||
:result
|
:result
|
||||||
|
|
||||||
def initialize(params)
|
def initialize(params)
|
||||||
|
@wizard = params[:wizard]
|
||||||
@action = params[:action]
|
@action = params[:action]
|
||||||
@user = params[:user]
|
@user = params[:user]
|
||||||
@data = params[:data]
|
@data = params[:data]
|
||||||
@updater = params[:updater]
|
@updater = params[:updater]
|
||||||
|
@log = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
self.send(action['type'].to_sym)
|
self.send(action['type'].to_sym)
|
||||||
|
|
||||||
if SiteSetting.wizard_action_debug
|
|
||||||
log = "action: #{action['type']}; "
|
|
||||||
log << "result: #{@result}"
|
|
||||||
|
|
||||||
updater.errors.messages.each do |field, msg|
|
|
||||||
log << "error: #{field.to_s}; #{msg.to_s}; "
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.logger.warn("Wizard Action: #{log.to_s}")
|
log = "wizard: #{@wizard.id}; action: #{action['type']}; user: #{user.username}"
|
||||||
|
|
||||||
|
if @log.any?
|
||||||
|
@log.each do |item|
|
||||||
|
log << "; result: "
|
||||||
|
log << item.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
CustomWizard::Log.create(log)
|
||||||
end
|
end
|
||||||
|
|
||||||
def mapper
|
def mapper
|
||||||
|
@ -44,17 +46,18 @@ class CustomWizard::Action
|
||||||
post = creator.create
|
post = creator.create
|
||||||
|
|
||||||
if creator.errors.present?
|
if creator.errors.present?
|
||||||
@result = "failed to create"
|
messages = creator.errors.full_messages.join(" ")
|
||||||
updater.errors.add(:create_topic, creator.errors.full_messages.join(" "))
|
log_error("failed to create", messages)
|
||||||
|
updater.errors.add(:create_topic, messages)
|
||||||
elsif action['skip_redirect'].blank?
|
elsif action['skip_redirect'].blank?
|
||||||
data['redirect_on_complete'] = post.topic.url
|
data['redirect_on_complete'] = post.topic.url
|
||||||
end
|
end
|
||||||
|
|
||||||
if creator.errors.blank?
|
if creator.errors.blank?
|
||||||
@result = "success (created topic: #{post.topic.id})"
|
log_success("created topic", post.topic.id)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@result = "invalid params"
|
log_error("invalid topic params")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -75,17 +78,18 @@ class CustomWizard::Action
|
||||||
post = creator.create
|
post = creator.create
|
||||||
|
|
||||||
if creator.errors.present?
|
if creator.errors.present?
|
||||||
@result = "failed to create"
|
messages = creator.errors.full_messages.join(" ")
|
||||||
updater.errors.add(:send_message, creator.errors.full_messages.join(" "))
|
log_error("failed to create message", messages)
|
||||||
|
updater.errors.add(:send_message, messages)
|
||||||
elsif action['skip_redirect'].blank?
|
elsif action['skip_redirect'].blank?
|
||||||
data['redirect_on_complete'] = post.topic.url
|
data['redirect_on_complete'] = post.topic.url
|
||||||
end
|
end
|
||||||
|
|
||||||
if creator.errors.blank?
|
if creator.errors.blank?
|
||||||
@result = "success (created pm: #{post.topic.id})"
|
log_error("created message", post.topic.id)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@result = "invalid params"
|
log_error("invalid message params")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -111,12 +115,12 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
if result
|
if result
|
||||||
@result = "success (updated fields #{params.keys.map{ |p| p.to_s }.join(',')})"
|
log_success("updated profile fields", params.keys.map{ |p| p.to_s }.join(','))
|
||||||
else
|
else
|
||||||
@result = "failed to update"
|
log_error("failed to update profile fields")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@result = "invalid params"
|
log_error("invalid profile fields params")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -201,9 +205,9 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
if result
|
if result
|
||||||
@result = "success (added to groups: #{groups.map { |g| g.id.to_s }.join(',')})"
|
log_success("added to groups", groups.map { |g| g.id.to_s }.join(','))
|
||||||
else
|
else
|
||||||
@result = "failed to add"
|
log_error("failed to add to groups")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -333,4 +337,12 @@ class CustomWizard::Action
|
||||||
user.save!
|
user.save!
|
||||||
user.user_avatar.save!
|
user.user_avatar.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_success(message, detail = nil)
|
||||||
|
@log.push("success - #{message} - #{detail}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_error(message, detail = nil)
|
||||||
|
@log.push("error - #{message} - #{detail}")
|
||||||
|
end
|
||||||
end
|
end
|
|
@ -157,6 +157,7 @@ class CustomWizard::Builder
|
||||||
(final_step && (!action['run_after'] || (action['run_after'] === 'wizard_completion')))
|
(final_step && (!action['run_after'] || (action['run_after'] === 'wizard_completion')))
|
||||||
|
|
||||||
CustomWizard::Action.new(
|
CustomWizard::Action.new(
|
||||||
|
wizard: @wizard,
|
||||||
action: action,
|
action: action,
|
||||||
user: user,
|
user: user,
|
||||||
data: data,
|
data: data,
|
||||||
|
|
38
lib/custom_wizard/log.rb
Normale Datei
38
lib/custom_wizard/log.rb
Normale Datei
|
@ -0,0 +1,38 @@
|
||||||
|
class CustomWizard::Log
|
||||||
|
include ActiveModel::Serialization
|
||||||
|
|
||||||
|
attr_accessor :message, :date
|
||||||
|
|
||||||
|
PAGE_LIMIT = 100
|
||||||
|
|
||||||
|
def initialize(attrs)
|
||||||
|
@message = attrs['message']
|
||||||
|
@date = attrs['date']
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create(message)
|
||||||
|
log_id = SecureRandom.hex(12)
|
||||||
|
|
||||||
|
PluginStore.set('custom_wizard',
|
||||||
|
"log_#{log_id}",
|
||||||
|
{
|
||||||
|
date: Time.now,
|
||||||
|
message: message
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.list_query
|
||||||
|
PluginStoreRow.where("
|
||||||
|
plugin_name = 'custom_wizard' AND
|
||||||
|
key LIKE 'log_%' AND
|
||||||
|
(value::json->'date') IS NOT NULL
|
||||||
|
").order("value::json->>'date' DESC")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.list(page = 0)
|
||||||
|
self.list_query.limit(PAGE_LIMIT)
|
||||||
|
.offset(page * PAGE_LIMIT)
|
||||||
|
.map { |r| self.new(JSON.parse(r.value)) }
|
||||||
|
end
|
||||||
|
end
|
|
@ -48,6 +48,7 @@ after_initialize do
|
||||||
../controllers/custom_wizard/admin/wizard.rb
|
../controllers/custom_wizard/admin/wizard.rb
|
||||||
../controllers/custom_wizard/admin/submissions.rb
|
../controllers/custom_wizard/admin/submissions.rb
|
||||||
../controllers/custom_wizard/admin/api.rb
|
../controllers/custom_wizard/admin/api.rb
|
||||||
|
../controllers/custom_wizard/admin/logs.rb
|
||||||
../controllers/custom_wizard/wizard.rb
|
../controllers/custom_wizard/wizard.rb
|
||||||
../controllers/custom_wizard/steps.rb
|
../controllers/custom_wizard/steps.rb
|
||||||
../controllers/custom_wizard/transfer.rb
|
../controllers/custom_wizard/transfer.rb
|
||||||
|
@ -57,10 +58,11 @@ after_initialize do
|
||||||
../jobs/clear_after_time_wizard.rb
|
../jobs/clear_after_time_wizard.rb
|
||||||
../jobs/refresh_api_access_token.rb
|
../jobs/refresh_api_access_token.rb
|
||||||
../jobs/set_after_time_wizard.rb
|
../jobs/set_after_time_wizard.rb
|
||||||
../lib/custom_wizard/actions.rb
|
../lib/custom_wizard/action.rb
|
||||||
../lib/custom_wizard/builder.rb
|
../lib/custom_wizard/builder.rb
|
||||||
../lib/custom_wizard/field.rb
|
../lib/custom_wizard/field.rb
|
||||||
../lib/custom_wizard/mapper.rb
|
../lib/custom_wizard/mapper.rb
|
||||||
|
../lib/custom_wizard/log.rb
|
||||||
../lib/custom_wizard/step_updater.rb
|
../lib/custom_wizard/step_updater.rb
|
||||||
../lib/custom_wizard/validator.rb
|
../lib/custom_wizard/validator.rb
|
||||||
../lib/custom_wizard/wizard.rb
|
../lib/custom_wizard/wizard.rb
|
||||||
|
@ -80,6 +82,7 @@ after_initialize do
|
||||||
../serializers/custom_wizard/wizard_field_serializer.rb
|
../serializers/custom_wizard/wizard_field_serializer.rb
|
||||||
../serializers/custom_wizard/wizard_step_serializer.rb
|
../serializers/custom_wizard/wizard_step_serializer.rb
|
||||||
../serializers/custom_wizard/wizard_serializer.rb
|
../serializers/custom_wizard/wizard_serializer.rb
|
||||||
|
../serializers/custom_wizard/log_serializer.rb
|
||||||
../serializers/site_serializer.rb
|
../serializers/site_serializer.rb
|
||||||
].each do |path|
|
].each do |path|
|
||||||
load File.expand_path(path, __FILE__)
|
load File.expand_path(path, __FILE__)
|
||||||
|
|
3
serializers/custom_wizard/log_serializer.rb
Normale Datei
3
serializers/custom_wizard/log_serializer.rb
Normale Datei
|
@ -0,0 +1,3 @@
|
||||||
|
class CustomWizard::LogSerializer < ApplicationSerializer
|
||||||
|
attributes :message, :date
|
||||||
|
end
|
Laden …
In neuem Issue referenzieren