0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-25 18:50:27 +01:00
Dieser Commit ist enthalten in:
angusmcleod 2021-09-06 17:26:34 +08:00
Commit 5063e3eaeb
24 geänderte Dateien mit 402 neuen und 164 gelöschten Zeilen

Datei anzeigen

@ -1,10 +1,9 @@
import { action } from "@ember/object";
import Component from "@ember/component"; import Component from "@ember/component";
import { action } from "@ember/object";
import { equal } from "@ember/object/computed"; import { equal } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
export default Component.extend({ export default Component.extend({
isText: equal("value.type", "text"), isText: equal("value.type", "text"),
isComposer: equal("value.type", "composer"), isComposer: equal("value.type", "composer"),
@ -25,7 +24,7 @@ export default Component.extend({
isTextArea: equal("value.type", "textarea"), isTextArea: equal("value.type", "textarea"),
isComposerPreview: equal("value.type", "composer_preview"), isComposerPreview: equal("value.type", "composer_preview"),
textState: "text-collapsed", textState: "text-collapsed",
toggleText: I18n.t('admin.wizard.submissions.expand_text'), toggleText: I18n.t("admin.wizard.submissions.expand_text"),
@discourseComputed("value") @discourseComputed("value")
checkboxValue(value) { checkboxValue(value) {
@ -45,14 +44,14 @@ export default Component.extend({
if (state === "text-collapsed") { if (state === "text-collapsed") {
this.set("textState", "text-expanded"); this.set("textState", "text-expanded");
this.set("toggleText", I18n.t('admin.wizard.submissions.collapse_text')); this.set("toggleText", I18n.t("admin.wizard.submissions.collapse_text"));
} else if (state === "text-expanded") { } else if (state === "text-expanded") {
this.set("textState", "text-collapsed"); this.set("textState", "text-collapsed");
this.set("toggleText", I18n.t('admin.wizard.submissions.expand_text')); this.set("toggleText", I18n.t("admin.wizard.submissions.expand_text"));
} }
}, },
@discourseComputed('value') @discourseComputed("value")
file(value) { file(value) {
const isUpload = this.get("isUpload"); const isUpload = this.get("isUpload");
if (isUpload) { if (isUpload) {
@ -60,52 +59,51 @@ export default Component.extend({
} }
}, },
@discourseComputed('value') @discourseComputed("value")
submittedUsers(value) { submittedUsers(value) {
const isUserSelector = this.get("isUserSelector"); const isUserSelector = this.get("isUserSelector");
const users = []; const users = [];
if (isUserSelector) { if (isUserSelector) {
const userData = value.value; const userData = value.value;
const usernames = []; const usernames = [];
if (userData.indexOf(',')) { if (userData.indexOf(",")) {
usernames.push(...userData.split(',')); usernames.push(...userData.split(","));
usernames.forEach(u => { usernames.forEach((u) => {
const user = { const user = {
username: u, username: u,
url: `/u/${u}` url: `/u/${u}`,
} };
users.push(user); users.push(user);
}) });
} }
} }
return users; return users;
}, },
@discourseComputed('value') @discourseComputed("value")
userProfileUrl(value) { userProfileUrl(value) {
const isUser = this.get("isUser"); const isUser = this.get("isUser");
const isUserSelector = this.get("isUserSelector");
if (isUser) { if (isUser) {
return `/u/${value.username}`; return `/u/${value.username}`;
} }
}, },
@discourseComputed('value') @discourseComputed("value")
categoryUrl(value) { categoryUrl(value) {
const isCategory = this.get('isCategory'); const isCategory = this.get("isCategory");
if (isCategory) { if (isCategory) {
return `/c/${value.value}`; return `/c/${value.value}`;
} }
}, },
@discourseComputed('value') @discourseComputed("value")
groupUrl(value) { groupUrl(value) {
const isGroup = this.get('isGroup'); const isGroup = this.get("isGroup");
if (isGroup) { if (isGroup) {
return `/g/${value.value}`; return `/g/${value.value}`;

Datei anzeigen

@ -1,15 +1,10 @@
import { import discourseComputed from "discourse-common/utils/decorators";
default as discourseComputed,
observes,
on,
} from "discourse-common/utils/decorators";
import { generateName } from "../lib/wizard"; import { generateName } from "../lib/wizard";
import { import {
setWizardDefaults, setWizardDefaults,
default as wizardSchema, default as wizardSchema,
} from "../lib/wizard-schema"; } from "../lib/wizard-schema";
import { notEmpty } from "@ember/object/computed"; import { notEmpty } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import Component from "@ember/component"; import Component from "@ember/component";
import { A } from "@ember/array"; import { A } from "@ember/array";
@ -19,28 +14,12 @@ export default Component.extend({
items: A(), items: A(),
anyLinks: notEmpty("links"), anyLinks: notEmpty("links"),
@on("didInsertElement")
@observes("links.[]")
setupSortable() {
scheduleOnce("afterRender", () => this.applySortable());
},
applySortable() {
$(this.element)
.find(".link-list")
.sortable({ tolerance: "pointer" })
.on("sortupdate", (e, ui) => {
this.updateItemOrder(ui.item.data("id"), ui.item.index());
});
},
updateItemOrder(itemId, newIndex) { updateItemOrder(itemId, newIndex) {
const items = this.items; const items = this.items;
const item = items.findBy("id", itemId); const item = items.findBy("id", itemId);
items.removeObject(item); items.removeObject(item);
item.set("index", newIndex); item.set("index", newIndex);
items.insertAt(newIndex, item); items.insertAt(newIndex, item);
scheduleOnce("afterRender", this, () => this.applySortable());
}, },
@discourseComputed("itemType") @discourseComputed("itemType")
@ -58,7 +37,7 @@ export default Component.extend({
return; return;
} }
return items.map((item) => { return items.map((item, index) => {
if (item) { if (item) {
let link = { let link = {
id: item.id, id: item.id,
@ -77,6 +56,15 @@ export default Component.extend({
} }
link.classes = classes; link.classes = classes;
link.index = index;
if (index === 0) {
link.first = true;
}
if (index === items.length - 1) {
link.last = true;
}
return link; return link;
} }
@ -118,6 +106,14 @@ export default Component.extend({
this.set("current", newItem); this.set("current", newItem);
}, },
back(item) {
this.updateItemOrder(item.id, item.index - 1);
},
forward(item) {
this.updateItemOrder(item.id, item.index + 1);
},
change(itemId) { change(itemId) {
this.set("current", this.items.findBy("id", itemId)); this.set("current", this.items.findBy("id", itemId));
}, },

Datei anzeigen

@ -2,15 +2,14 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, { export default Controller.extend(ModalFunctionality, {
actions: { actions: {
save() { save() {
this.send("closeModal"); this.send("closeModal");
}, },
resetToDefault() { resetToDefault() {
this.get('model.fields').forEach(field => { this.get("model.fields").forEach((field) => {
field.set("enabled", true); field.set("enabled", true);
}); });
} },
} },
}); });

Datei anzeigen

@ -1,10 +1,9 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { fmt } from "discourse/lib/computed";
import { empty } from "@ember/object/computed"; import { empty } from "@ember/object/computed";
import CustomWizard from "../models/custom-wizard";
import showModal from "discourse/lib/show-modal";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import showModal from "discourse/lib/show-modal";
import CustomWizard from "../models/custom-wizard";
export default Controller.extend({ export default Controller.extend({
downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"), downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"),
@ -27,17 +26,16 @@ export default Controller.extend({
this.set("loadingMore", false); this.set("loadingMore", false);
}); });
}, },
@discourseComputed("submissions", "fields.@each.enabled")
@discourseComputed('submissions', 'fields.@each.enabled')
displaySubmissions(submissions, fields) { displaySubmissions(submissions, fields) {
let result = []; let result = [];
submissions.forEach(submission => { submissions.forEach((submission) => {
let sub = {}; let sub = {};
Object.keys(submission).forEach(fieldId => { Object.keys(submission).forEach((fieldId) => {
if (fields.some(f => f.id === fieldId && f.enabled)) { if (fields.some((f) => f.id === fieldId && f.enabled)) {
sub[fieldId] = submission[fieldId]; sub[fieldId] = submission[fieldId];
} }
}); });
@ -56,11 +54,11 @@ export default Controller.extend({
}, },
showEditColumnsModal() { showEditColumnsModal() {
const controller = showModal("admin-wizards-submissions-columns", { return showModal("admin-wizards-submissions-columns", {
model: { model: {
fields: this.get('fields'), fields: this.get("fields"),
submissions: this.get('submissions') submissions: this.get("submissions"),
} },
}); });
}, },
}, },

Datei anzeigen

@ -1,10 +1,10 @@
import { ajax } from "discourse/lib/ajax";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { buildProperties, mapped, present } from "../lib/wizard-json"; import { ajax } from "discourse/lib/ajax";
import { listProperties, snakeCase } from "../lib/wizard";
import wizardSchema from "../lib/wizard-schema";
import { Promise } from "rsvp";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { Promise } from "rsvp";
import { listProperties, snakeCase } from "../lib/wizard";
import { buildProperties, mapped, present } from "../lib/wizard-json";
import wizardSchema from "../lib/wizard-schema";
const CustomWizard = EmberObject.extend({ const CustomWizard = EmberObject.extend({
save(opts) { save(opts) {
@ -224,8 +224,7 @@ CustomWizard.reopenClass({
}) })
.then((result) => { .then((result) => {
if (result.wizard) { if (result.wizard) {
console.log(result); let fields = [{ id: "username", label: "User" }];
let fields = [{ id: "username", label: "User"}];
let submissions = []; let submissions = [];
let wizard = result.wizard; let wizard = result.wizard;
let total = result.total; let total = result.total;
@ -236,7 +235,7 @@ CustomWizard.reopenClass({
}; };
Object.keys(s.fields).forEach((fieldId) => { Object.keys(s.fields).forEach((fieldId) => {
if (!fields.some(field => field.id === fieldId)) { if (!fields.some((field) => field.id === fieldId)) {
fields.push({ id: fieldId, label: s.fields[fieldId].label }); fields.push({ id: fieldId, label: s.fields[fieldId].label });
} }
submission[fieldId] = s.fields[fieldId]; submission[fieldId] = s.fields[fieldId];
@ -247,9 +246,9 @@ CustomWizard.reopenClass({
let submittedAt = { let submittedAt = {
id: "submitted_at", id: "submitted_at",
label: "Submitted At" label: "Submitted At",
} };
fields.push(submittedAt); fields.push(submittedAt);
return { return {

Datei anzeigen

@ -1,16 +1,24 @@
{{#if submissions}} {{#if submissions}}
<div class="wizard-header large"> <div class="wizard-header large">
<label>{{i18n "admin.wizard.submissions.title" name=wizard.name}}</label> <label>
{{i18n "admin.wizard.submissions.title" name=wizard.name}}
</label>
<div class="controls"> <div class="controls">
{{d-button {{d-button
icon="sliders-h" icon="sliders-h"
label="admin.wizard.submissions.edit_columns" label="admin.wizard.submissions.edit_columns"
action=(action "showEditColumnsModal") action=(action "showEditColumnsModal")
class="btn-default open-edit-columns-btn download-link"}} class="btn-default open-edit-columns-btn download-link"
}}
</div> </div>
<a class="btn btn-default download-link" href={{downloadUrl}} target="_blank" rel="noopener noreferrer"> <a
class="btn btn-default download-link"
href={{downloadUrl}}
target="_blank"
rel="noopener noreferrer"
>
{{d-icon "download"}} {{d-icon "download"}}
<span class="d-button-label"> <span class="d-button-label">
{{i18n "admin.wizard.submissions.download"}} {{i18n "admin.wizard.submissions.download"}}
@ -21,14 +29,18 @@
<div class="wizard-submissions"> <div class="wizard-submissions">
{{#load-more selector=".wizard-submissions tr" action=(action "loadMore")}} {{#load-more selector=".wizard-submissions tr" action=(action "loadMore")}}
{{#if noResults}} {{#if noResults}}
<p>{{i18n "search.no_results"}}</p> <p>
{{i18n "search.no_results"}}
</p>
{{else}} {{else}}
<table> <table>
<thead> <thead>
<tr> <tr>
{{#each fields as |field|}} {{#each fields as |field|}}
{{#if field.enabled}} {{#if field.enabled}}
<th>{{field.label}}</th> <th>
{{field.label}}
</th>
{{/if}} {{/if}}
{{/each}} {{/each}}
</tr> </tr>
@ -37,7 +49,9 @@
{{#each displaySubmissions as |submission|}} {{#each displaySubmissions as |submission|}}
<tr> <tr>
{{#each-in submission as |field value|}} {{#each-in submission as |field value|}}
<td>{{submission-field fieldName=field value=value}}</td> <td>
{{submission-field fieldName=field value=value}}
</td>
{{/each-in}} {{/each-in}}
</tr> </tr>
{{/each}} {{/each}}
@ -48,4 +62,4 @@
{{conditional-loading-spinner condition=loadingMore}} {{conditional-loading-spinner condition=loadingMore}}
{{/load-more}} {{/load-more}}
</div> </div>
{{/if}} {{/if}}

Datei anzeigen

@ -7,6 +7,10 @@
{{/if}} {{/if}}
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}}
{{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
<div class="admin-actions">
<a target="_blank" class="btn btn-pavilion-pro" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.pro_support_button.title"}}>{{d-icon "far-life-ring"}}{{i18n "admin.wizard.pro_support_button.label"}}</a>
</div>
{{/admin-nav}} {{/admin-nav}}
<div class="admin-container"> <div class="admin-container">

Datei anzeigen

@ -4,20 +4,35 @@
{{#if isTextArea}} {{#if isTextArea}}
<div class="submission-long-text"> <div class="submission-long-text">
<p class="submission-long-text-content {{textState}}">{{value.value}}</p> <p class="submission-long-text-content {{textState}}">
<a href {{action "expandText"}}>{{toggleText}}</a> {{value.value}}
</p>
<a href {{action "expandText"}}>
{{toggleText}}
</a>
</div> </div>
{{/if}} {{/if}}
{{#if isComposer}} {{#if isComposer}}
<div class="submission-long-text"> <div class="submission-long-text">
<p class="submission-composer-text submission-long-text-content {{textState}}">{{value.value}}</p> <p
<a href {{action "expandText"}}>{{toggleText}}</a> class="submission-composer-text submission-long-text-content {{
textState
}}"
>
{{value.value}}
</p>
<a href {{action "expandText"}}>
{{toggleText}}
</a>
</div> </div>
{{/if}} {{/if}}
{{#if isComposerPreview}} {{#if isComposerPreview}}
{{d-icon "comment-alt" }} <span class="submission-composer-text">{{i18n "admin.wizard.submissions.composer_preview"}}: {{value.value}}</span> {{d-icon "comment-alt"}}
<span class="submission-composer-text">
{{i18n "admin.wizard.submissions.composer_preview"}}: {{value.value}}
</span>
{{/if}} {{/if}}
{{#if isTextOnly}} {{#if isTextOnly}}
@ -48,35 +63,41 @@
{{#if isCheckbox}} {{#if isCheckbox}}
{{#if checkboxValue}} {{#if checkboxValue}}
<span class="submission-icon-item submission-checkbox-true"> <span class="submission-icon-item submission-checkbox-true">
{{d-icon "check"}}{{value.value}} {{d-icon "check"}}{{value.value}}
</span> </span>
{{else}} {{else}}
<span class="submission-icon-item submission-checkbox-false"> <span class="submission-icon-item submission-checkbox-false">
{{d-icon "times"}}{{value.value}} {{d-icon "times"}}{{value.value}}
</span> </span>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#if isUrl}} {{#if isUrl}}
<span class="submission-icon-item submission-url"> <span class="submission-icon-item submission-url">
{{ d-icon "link" }} {{d-icon "link"}}
<a target="_blank" href={{value.value}}> <a target="_blank" rel="noopener noreferrer" href={{value.value}}>
{{value.value}} {{value.value}}
</a> </a>
</span> </span>
{{/if}} {{/if}}
{{#if isUpload}} {{#if isUpload}}
<a target="_blank" class="attachment" href={{file.url}} download> <a
target="_blank"
rel="noopener noreferrer"
class="attachment"
href={{file.url}}
download
>
{{file.original_filename}} {{file.original_filename}}
</a> </a>
{{/if}} {{/if}}
{{#if isDropdown}} {{#if isDropdown}}
<span class="submission-icon-item"> <span class="submission-icon-item">
{{ d-icon "check-square" }} {{d-icon "check-square"}}
{{ value.value }} {{value.value}}
</span> </span>
{{/if}} {{/if}}
@ -87,28 +108,56 @@
{{/if}} {{/if}}
{{#if isCategory}} {{#if isCategory}}
<strong>{{i18n "admin.wizard.submissions.category_id"}}: </strong><a target="_blank" href={{categoryUrl}} title={{value.value}}>{{value.value}}</a> <strong>
{{i18n "admin.wizard.submissions.category_id"}}:
</strong>
<a
target="_blank"
rel="noopener noreferrer"
href={{categoryUrl}}
title={{value.value}}
>
{{value.value}}
</a>
{{/if}} {{/if}}
{{#if isGroup}} {{#if isGroup}}
<strong>{{i18n "admin.wizard.submissions.group_id"}}: </strong> {{ value.value }} <strong>
{{i18n "admin.wizard.submissions.group_id"}}:
</strong>
{{value.value}}
{{/if}} {{/if}}
{{#if isUserSelector}} {{#if isUserSelector}}
{{#each submittedUsers as |user|}} {{#each submittedUsers as |user|}}
{{ d-icon "user" }} {{d-icon "user"}}
<a target="_blank" href={{user.url}} title={{user.username}}>{{user.username}}</a> <a
target="_blank"
rel="noopener noreferrer"
href={{user.url}}
title={{user.username}}
>
{{user.username}}
</a>
{{/each}} {{/each}}
{{/if}} {{/if}}
{{#if isUser}} {{#if isUser}}
{{#link-to "user" value}}{{avatar value imageSize="tiny"}}{{/link-to}} {{#link-to "user" value}}
<a target="_blank" href={{userProfileUrl}} title={{value.name}}>{{value.username}}</a> {{avatar value imageSize="tiny"}}
{{/link-to}}
<a
target="_blank"
rel="noopener noreferrer"
href={{userProfileUrl}}
title={{value.name}}
>
{{value.username}}
</a>
{{/if}} {{/if}}
{{#if isSubmittedAt}} {{#if isSubmittedAt}}
<span class="submission-date" title={{value.value}}> <span class="submission-date" title={{value.value}}>
{{d-icon "clock"}}{{format-date value format="tiny"}} {{d-icon "clock"}}{{format-date value format="tiny"}}
</span> </span>
{{/if}} {{/if}}

Datei anzeigen

@ -2,10 +2,16 @@
<div class="link-list"> <div class="link-list">
{{#if anyLinks}} {{#if anyLinks}}
{{#each links as |l|}} {{#each links as |link|}}
<div data-id={{l.id}}> <div data-id={{link.id}}>
{{d-button action="change" actionParam=l.id translatedLabel=l.label class=l.classes}} {{d-button action="change" actionParam=link.id translatedLabel=link.label class=link.classes}}
{{d-button action="remove" actionParam=l.id icon="times" class="remove"}} {{#unless link.first}}
{{d-button action="back" actionParam=link icon="arrow-left" class="back"}}
{{/unless}}
{{#unless link.last}}
{{d-button action="forward" actionParam=link icon="arrow-right" class="forward"}}
{{/unless}}
{{d-button action="remove" actionParam=link.id icon="times" class="remove"}}
</div> </div>
{{/each}} {{/each}}
{{/if}} {{/if}}

Datei anzeigen

@ -29,4 +29,4 @@
label="directory.edit_columns.reset_to_default" label="directory.edit_columns.reset_to_default"
action=(action "resetToDefault") action=(action "resetToDefault")
}} }}
</div> </div>

Datei anzeigen

@ -2,6 +2,7 @@
//= require discourse/app/mixins/singleton //= require discourse/app/mixins/singleton
//= require discourse/app/mixins/upload //= require discourse/app/mixins/upload
//= require discourse/app/mixins/composer-upload
//= require discourse/app/adapters/rest //= require discourse/app/adapters/rest

Datei anzeigen

@ -2,6 +2,7 @@
@import "wizard-manager"; @import "wizard-manager";
@import "wizard-api"; @import "wizard-api";
@import "common/components/buttons"; @import "common/components/buttons";
@import "wizard-variables";
.admin-wizard-controls { .admin-wizard-controls {
display: flex; display: flex;
@ -766,3 +767,21 @@
vertical-align: middle; vertical-align: middle;
} }
.btn.btn-pavilion-pro {
background: var(--pavilion-primary);
color: var(--pavilion-secondary);
.d-icon {
color: var(--pavilion-secondary);
}
&:hover,
&:focus {
background: darken($pavilionPrimary, 5%);
&[href],
svg.d-icon {
color: darken($pavilionSecondary, 10%);
}
}
}

Datei anzeigen

@ -0,0 +1,7 @@
$pavilionPrimary: #3c1c8c;
$pavilionSecondary: #ffffff;
:root {
--pavilion-primary: #3c1c8c;
--pavilion-secondary: #ffffff;
}

Datei anzeigen

@ -58,6 +58,9 @@ en:
select_type: "Select a type" select_type: "Select a type"
condition: "Condition" condition: "Condition"
index: "Index" index: "Index"
pro_support_button:
title: "Request Pro Support"
label: "Pro Support"
message: message:
wizard: wizard:

Datei anzeigen

@ -68,7 +68,8 @@ class CustomWizard::WizardController < ::ApplicationController
result.merge!(redirect_to: submission.redirect_to) result.merge!(redirect_to: submission.redirect_to)
end end
wizard.final_cleanup! submission.remove if submission.present?
wizard.reset
end end
render json: result render json: result

Datei anzeigen

@ -392,7 +392,7 @@ class CustomWizard::Action
user_ids.each { |user_id| group.group_users.build(user_id: user_id) } user_ids.each { |user_id| group.group_users.build(user_id: user_id) }
end end
GroupActionLogger.new(user, group, skip_guardian: true).log_change_group_settings GroupActionLogger.new(user, group).log_change_group_settings
log_success("Group created", group.name) log_success("Group created", group.name)
result.output = group.name result.output = group.name

Datei anzeigen

@ -75,6 +75,7 @@ class CustomWizard::Builder
end end
@wizard.update! @wizard.update!
CustomWizard::Submission.cleanup_incomplete_submissions(@wizard)
@wizard @wizard
end end

Datei anzeigen

@ -4,7 +4,7 @@ class CustomWizard::Submission
PAGE_LIMIT = 50 PAGE_LIMIT = 50
KEY ||= "submissions" KEY ||= "submissions"
META ||= %w(submitted_at route_to redirect_on_complete redirect_to) META ||= %w(updated_at submitted_at route_to redirect_on_complete redirect_to)
attr_reader :id, attr_reader :id,
:user, :user,
@ -46,6 +46,7 @@ class CustomWizard::Submission
submission_list = self.class.list(wizard, user_id: user.id) submission_list = self.class.list(wizard, user_id: user.id)
submissions = submission_list.submissions.select { |submission| submission.id != self.id } submissions = submission_list.submissions.select { |submission| submission.id != self.id }
self.updated_at = Time.now.iso8601
submissions.push(self) submissions.push(self)
submission_data = submissions.map { |submission| data_to_save(submission) } submission_data = submissions.map { |submission| data_to_save(submission) }
@ -97,7 +98,41 @@ class CustomWizard::Submission
new(wizard, data, user_id) new(wizard, data, user_id)
end end
def self.list(wizard, user_id: nil, page: nil) def remove
if present?
user_id = @user.id
wizard_id = @wizard.id
submission_id = @id
data = PluginStore.get("#{wizard_id}_#{KEY}", user_id)
data.delete_if { |sub| sub["id"] == submission_id }
PluginStore.set("#{wizard_id}_#{KEY}", user_id, data)
end
end
def self.cleanup_incomplete_submissions(wizard)
user_id = wizard.user.id
all_submissions = list(wizard, user_id: user_id)
sorted_submissions = all_submissions.submissions.sort_by do |submission|
zero_epoch_time = DateTime.strptime("0", '%s')
[
submission.submitted_at ? Time.iso8601(submission.submitted_at) : zero_epoch_time,
submission.updated_at ? Time.iso8601(submission.updated_at) : zero_epoch_time
]
end.reverse
has_incomplete = false
valid_submissions = sorted_submissions.select do |submission|
to_be_included = submission.submitted_at || !has_incomplete
has_incomplete = true if !submission.submitted_at
to_be_included
end
valid_data = valid_submissions.map { |submission| submission.data_to_save(submission) }
PluginStore.set("#{wizard.id}_#{KEY}", user_id, valid_data)
end
def self.list(wizard, user_id: nil, order_by: nil, page: nil)
params = { plugin_name: "#{wizard.id}_#{KEY}" } params = { plugin_name: "#{wizard.id}_#{KEY}" }
params[:key] = user_id if user_id.present? params[:key] = user_id if user_id.present?

Datei anzeigen

@ -32,12 +32,14 @@ class ::CustomWizard::UpdateValidator
@updater.errors.add(field_id, I18n.t('wizard.field.required', label: label)) @updater.errors.add(field_id, I18n.t('wizard.field.required', label: label))
end end
if min_length.present? && value.is_a?(String) && value.strip.length < min_length.to_i if value.is_a?(String) && (stripped_length = value.strip.length) > 0
@updater.errors.add(field_id, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i)) if min_length.present? && stripped_length < min_length.to_i
end @updater.errors.add(field_id, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i))
end
if max_length.present? && value.is_a?(String) && value.strip.length > max_length.to_i if max_length.present? && stripped_length > max_length.to_i
@updater.errors.add(field_id, I18n.t('wizard.field.too_long', label: label, max: max_length.to_i)) @updater.errors.add(field_id, I18n.t('wizard.field.too_long', label: label, max: max_length.to_i))
end
end end
if is_url_type(field) && value.present? && !check_if_url(value) if is_url_type(field) && value.present? && !check_if_url(value)

Datei anzeigen

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
# name: discourse-custom-wizard # name: discourse-custom-wizard
# about: Create custom wizards # about: Create custom wizards
# version: 0.8.0 # version: 0.8.1
# authors: Angus McLeod # authors: Angus McLeod
# url: https://github.com/paviliondev/discourse-custom-wizard # url: https://github.com/paviliondev/discourse-custom-wizard
# contact emails: angus@thepavilion.io # contact emails: angus@thepavilion.io
@ -40,6 +40,8 @@ if respond_to?(:register_svg_icon)
register_svg_icon "clock" register_svg_icon "clock"
register_svg_icon "link" register_svg_icon "link"
register_svg_icon "comment-alt" register_svg_icon "comment-alt"
register_svg_icon "far-life-ring"
register_svg_icon "arrow-right"
end end
class ::Sprockets::DirectiveProcessor class ::Sprockets::DirectiveProcessor

Datei anzeigen

@ -13,38 +13,89 @@ describe CustomWizard::Submission do
before do before do
CustomWizard::Template.save(template_json, skip_jobs: true) CustomWizard::Template.save(template_json, skip_jobs: true)
template_json_2 = template_json.dup
template_json_2["id"] = "super_mega_fun_wizard_2"
CustomWizard::Template.save(template_json_2, skip_jobs: true)
@wizard = CustomWizard::Wizard.create(template_json["id"], user) @wizard = CustomWizard::Wizard.create(template_json["id"], user)
@wizard2 = CustomWizard::Wizard.create(template_json["id"], user2) described_class.new(@wizard, step_1_field_1: "I am user submission").save
@wizard3 = CustomWizard::Wizard.create(template_json_2["id"], user)
@count = CustomWizard::Submission::PAGE_LIMIT + 20
@count.times do |index|
described_class.new(@wizard, step_1_field_1: "I am user submission #{index + 1}").save
end
described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save
described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save
end end
it "saves a user's submission" do it "saves a user's submission" do
expect( expect(
described_class.get(@wizard, user.id).fields["step_1_field_1"] described_class.get(@wizard, user.id).fields["step_1_field_1"]
).to eq("I am user submission #{@count}") ).to eq("I am user submission")
end end
it "list submissions by wizard" do context "#list" do
expect(described_class.list(@wizard).total).to eq(@count + 1) before do
template_json_2 = template_json.dup
template_json_2["id"] = "super_mega_fun_wizard_2"
CustomWizard::Template.save(template_json_2, skip_jobs: true)
@wizard2 = CustomWizard::Wizard.create(template_json["id"], user2)
@wizard3 = CustomWizard::Wizard.create(template_json_2["id"], user)
@count = CustomWizard::Submission::PAGE_LIMIT + 20
@count.times do |index|
described_class.new(@wizard, step_1_field_1: "I am user submission #{index + 1}").save
end
described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save
described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save
end
it "list submissions by wizard" do
expect(described_class.list(@wizard).total).to eq(@count + 2)
end
it "list submissions by wizard and user" do
expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count + 1)
end
it "paginates submission lists" do
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT)
end
end end
it "list submissions by wizard and user" do context "#cleanup_incomplete_submissions" do
expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count) it "cleans up redundant incomplete submissions on each build" do
end freeze_time Time.now + 1
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
it "paginates submission lists" do expect(submissions.length).to eq(1)
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 1) - CustomWizard::Submission::PAGE_LIMIT) expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
end
it "handles submissions without 'updated_at' field correctly" do
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
described_class.new(@wizard, step_1_field_1: "I am the third submission").save
sub_data = PluginStore.get("#{@wizard.id}_submissions", @wizard.user.id)
sub_data.each do |sub|
sub['updated_at'] = nil
end
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")
end
it "handles submissions with and without 'updated_at' field correctly" do
freeze_time Time.now + 1
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
freeze_time Time.now + 2
described_class.new(@wizard, step_1_field_1: "I am the third submission").save
sub_data = PluginStore.get("#{@wizard.id}_submissions", @wizard.user.id)
sub_data[0]['updated_at'] = nil
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")
end
end end
end end

Datei anzeigen

@ -97,6 +97,31 @@ describe CustomWizard::UpdateValidator do
).to eq(nil) ).to eq(nil)
end end
it "applies min length only if the input is non-empty" do
min_length = 3
@template[:steps][0][:fields][0][:min_length] = min_length
CustomWizard::Template.save(@template)
updater = perform_validation('step_1', step_1_field_1: '')
expect(
updater.errors.messages[:step_1_field_1].first
).to eq(nil)
end
it "applies max length only if the input is non-empty" do
max_length = 100
@template[:steps][0][:fields][0][:max_length] = max_length
CustomWizard::Template.save(@template)
updater = perform_validation('step_1', step_1_field_1: "")
expect(
updater.errors.messages[:step_1_field_1].first
).to eq(nil)
end
it 'standardises boolean entries' do it 'standardises boolean entries' do
updater = perform_validation('step_2', step_2_field_5: 'false') updater = perform_validation('step_2', step_2_field_5: 'false')
expect(updater.submission['step_2_field_5']).to eq(false) expect(updater.submission['step_2_field_5']).to eq(false)

Datei anzeigen

@ -11,4 +11,7 @@ if ENV['SIMPLECOV']
end end
end end
require 'oj'
Oj.default_options = Oj.default_options.merge(cache_str: -1)
require 'rails_helper' require 'rails_helper'

Datei anzeigen

@ -50,30 +50,55 @@ describe CustomWizard::WizardController do
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.") expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
end end
it 'skips a wizard if user is allowed to skip' do context 'when user skips the wizard' do
put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200)
end
it 'lets user skip if user cant access wizard' do it 'skips a wizard if user is allowed to skip' do
@template["permitted"] = permitted_json["permitted"] put '/w/super-mega-fun-wizard/skip.json'
CustomWizard::Template.save(@template, skip_jobs: true) expect(response.status).to eq(200)
end
put '/w/super-mega-fun-wizard/skip.json' it 'lets user skip if user cant access wizard' do
expect(response.status).to eq(200) @template["permitted"] = permitted_json["permitted"]
end CustomWizard::Template.save(@template, skip_jobs: true)
it 'returns a no skip message if user is not allowed to skip' do put '/w/super-mega-fun-wizard/skip.json'
@template['required'] = 'true' expect(response.status).to eq(200)
CustomWizard::Template.save(@template) end
put '/w/super-mega-fun-wizard/skip.json'
expect(response.parsed_body['error']).to eq("Wizard can't be skipped")
end
it 'skip response contains a redirect_to if in users submissions' do it 'returns a no skip message if user is not allowed to skip' do
@wizard = CustomWizard::Wizard.create(@template["id"], user) @template['required'] = 'true'
CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save CustomWizard::Template.save(@template)
put '/w/super-mega-fun-wizard/skip.json' put '/w/super-mega-fun-wizard/skip.json'
expect(response.parsed_body['redirect_to']).to eq('/t/2') expect(response.parsed_body['error']).to eq("Wizard can't be skipped")
end
it 'skip response contains a redirect_to if in users submissions' do
@wizard = CustomWizard::Wizard.create(@template["id"], user)
CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save
put '/w/super-mega-fun-wizard/skip.json'
expect(response.parsed_body['redirect_to']).to eq('/t/2')
end
it "deletes the submission if user has filled up some data" do
@wizard = CustomWizard::Wizard.create(@template["id"], user)
CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save
current_submission = @wizard.current_submission
put '/w/super-mega-fun-wizard/skip.json'
submissions = CustomWizard::Submission.list(@wizard).submissions
expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false)
end
it "starts from the first step if user visits after skipping the wizard" do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Text input"
}
}
put '/w/super-mega-fun-wizard/skip.json'
get '/w/super-mega-fun-wizard.json'
expect(response.parsed_body["start"]).to eq('step_1')
end
end end
end end