From 17afa26e7a0e393691c2641c65353c2e37520985 Mon Sep 17 00:00:00 2001 From: Christian Zosel Date: Mon, 29 Apr 2019 15:18:43 +0200 Subject: [PATCH] feat(form): add support for static question (#169) This implements support for https://github.com/projectcaluma/caluma/pull/396 --- addon-mirage-support/factories/question.js | 9 ++- addon/-private/fragment-types.js | 6 ++ addon/components/cf-field/input/static.js | 12 +++ addon/components/cfb-form-editor/question.js | 25 +++++- addon/gql/fragments/field-question.graphql | 3 + .../mutations/save-static-question.graphql | 13 ++++ .../gql/queries/form-editor-question.graphql | 3 + addon/lib/field.js | 45 ++++++----- addon/mirage-graphql/mocks/question.js | 14 +++- addon/mirage-graphql/schema.graphql | 77 ++++++++++++++++++- addon/templates/components/cf-field.hbs | 4 +- .../components/cf-field/input/static.hbs | 1 + .../components/cfb-form-editor/question.hbs | 33 +++++--- app/components/cf-field/input/static.js | 1 + .../cfb-form-editor/question-test.js | 28 +++++++ translations/de-de.yaml | 4 +- translations/en-us.yaml | 4 +- 17 files changed, 241 insertions(+), 41 deletions(-) create mode 100644 addon/components/cf-field/input/static.js create mode 100644 addon/gql/mutations/save-static-question.graphql create mode 100644 addon/templates/components/cf-field/input/static.hbs create mode 100644 app/components/cf-field/input/static.js diff --git a/addon-mirage-support/factories/question.js b/addon-mirage-support/factories/question.js index 69f996c5a..07e439fbd 100644 --- a/addon-mirage-support/factories/question.js +++ b/addon-mirage-support/factories/question.js @@ -8,7 +8,8 @@ const TYPES = [ "MULTIPLE_CHOICE", "CHOICE", "TABLE", - "FILE" + "FILE", + "STATIC" ]; export default Factory.extend({ @@ -58,6 +59,12 @@ export default Factory.extend({ rowForm: i => `subform-${i + 1}` }); } + } else if (question.type === "STATIC") { + if (question.staticContent === undefined) { + question.update({ + staticContent: i => `static-${i + 1}` + }); + } } } }); diff --git a/addon/-private/fragment-types.js b/addon/-private/fragment-types.js index f2f2212de..7d257d9ee 100644 --- a/addon/-private/fragment-types.js +++ b/addon/-private/fragment-types.js @@ -62,6 +62,9 @@ export default { { name: "FileQuestion" }, + { + name: "StaticQuestion" + }, { name: "StringAnswer" }, @@ -142,6 +145,9 @@ export default { }, { name: "FileQuestion" + }, + { + name: "StaticQuestion" } ] }, diff --git a/addon/components/cf-field/input/static.js b/addon/components/cf-field/input/static.js new file mode 100644 index 000000000..766a5d0b8 --- /dev/null +++ b/addon/components/cf-field/input/static.js @@ -0,0 +1,12 @@ +import Component from "@ember/component"; +import layout from "../../../templates/components/cf-field/input/static"; + +/** + * Input component for the static question type + * + * @class CfFieldInputTextComponent + * @argument {Field} field The field for this input type + */ +export default Component.extend({ + layout +}); diff --git a/addon/components/cfb-form-editor/question.js b/addon/components/cfb-form-editor/question.js index a6a716b3a..95ffaa77a 100644 --- a/addon/components/cfb-form-editor/question.js +++ b/addon/components/cfb-form-editor/question.js @@ -29,6 +29,7 @@ import saveChoiceQuestionMutation from "ember-caluma/gql/mutations/save-choice-q import saveTableQuestionMutation from "ember-caluma/gql/mutations/save-table-question"; import saveFormQuestionMutation from "ember-caluma/gql/mutations/save-form-question"; import saveFileQuestionMutation from "ember-caluma/gql/mutations/save-file-question"; +import saveStaticQuestionMutation from "ember-caluma/gql/mutations/save-static-question"; export const TYPES = { TextQuestion: saveTextQuestionMutation, @@ -39,7 +40,8 @@ export const TYPES = { ChoiceQuestion: saveChoiceQuestionMutation, TableQuestion: saveTableQuestionMutation, FormQuestion: saveFormQuestionMutation, - FileQuestion: saveFileQuestionMutation + FileQuestion: saveFileQuestionMutation, + StaticQuestion: saveStaticQuestionMutation }; export default Component.extend(ComponentQueryManager, { @@ -167,6 +169,7 @@ export default Component.extend(ComponentQueryManager, { _getIntegerQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), minValue: parseInt(changeset.get("integerMinValue")), maxValue: parseInt(changeset.get("integerMaxValue")), placeholder: changeset.get("placeholder") @@ -175,6 +178,7 @@ export default Component.extend(ComponentQueryManager, { _getFloatQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), minValue: parseFloat(changeset.get("floatMinValue")), maxValue: parseFloat(changeset.get("floatMaxValue")), placeholder: changeset.get("placeholder") @@ -183,6 +187,7 @@ export default Component.extend(ComponentQueryManager, { _getTextQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), maxLength: parseInt(changeset.get("maxLength")), placeholder: changeset.get("placeholder") }; @@ -190,6 +195,7 @@ export default Component.extend(ComponentQueryManager, { _getTextareaQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), maxLength: parseInt(changeset.get("maxLength")), placeholder: changeset.get("placeholder") }; @@ -197,6 +203,7 @@ export default Component.extend(ComponentQueryManager, { _getMultipleChoiceQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), options: changeset.get("options.edges").map(({ node: { slug } }) => slug), meta: JSON.stringify({ widgetOverride: changeset.get("widgetOverride"), @@ -207,6 +214,7 @@ export default Component.extend(ComponentQueryManager, { _getChoiceQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), options: changeset.get("options.edges").map(({ node: { slug } }) => slug), meta: JSON.stringify({ widgetOverride: changeset.get("widgetOverride"), @@ -217,18 +225,28 @@ export default Component.extend(ComponentQueryManager, { _getTableQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), rowForm: changeset.get("rowForm") }; }, _getFormQuestionInput(changeset) { return { + isRequired: changeset.get("isRequired"), subForm: changeset.get("subForm") }; }, - _getFileQuestionInput(/* changeset */) { - return {}; + _getFileQuestionInput(changeset) { + return { + isRequired: changeset.get("isRequired") + }; + }, + + _getStaticQuestionInput(changeset) { + return { + staticContent: changeset.get("staticContent") + }; }, saveOptions: task(function*(changeset) { @@ -261,7 +279,6 @@ export default Component.extend(ComponentQueryManager, { { label: changeset.get("label"), slug, - isRequired: changeset.get("isRequired"), isHidden: changeset.get("isHidden"), infoText: changeset.get("infoText"), meta: JSON.stringify({ diff --git a/addon/gql/fragments/field-question.graphql b/addon/gql/fragments/field-question.graphql index 8e22f1a06..5967d5370 100644 --- a/addon/gql/fragments/field-question.graphql +++ b/addon/gql/fragments/field-question.graphql @@ -43,6 +43,9 @@ fragment SimpleQuestion on Question { } } } + ... on StaticQuestion { + staticContent + } } fragment FieldQuestion on Question { diff --git a/addon/gql/mutations/save-static-question.graphql b/addon/gql/mutations/save-static-question.graphql new file mode 100644 index 000000000..4952028ac --- /dev/null +++ b/addon/gql/mutations/save-static-question.graphql @@ -0,0 +1,13 @@ +#import "ember-caluma/gql/fragments/question-info" + +mutation SaveStaticQuestion($input: SaveStaticQuestionInput!) { + saveStaticQuestion(input: $input) { + question { + ...QuestionInfo + ... on StaticQuestion { + staticContent + } + } + clientMutationId + } +} diff --git a/addon/gql/queries/form-editor-question.graphql b/addon/gql/queries/form-editor-question.graphql index 147daf719..07c24003c 100644 --- a/addon/gql/queries/form-editor-question.graphql +++ b/addon/gql/queries/form-editor-question.graphql @@ -53,6 +53,9 @@ query FormEditorQuestion($slug: String!) { slug } } + ... on StaticQuestion { + staticContent + } } } } diff --git a/addon/lib/field.js b/addon/lib/field.js index c86d880d1..4d08c90e5 100644 --- a/addon/lib/field.js +++ b/addon/lib/field.js @@ -27,7 +27,8 @@ const TYPE_MAP = { ChoiceQuestion: "StringAnswer", TableQuestion: "TableAnswer", FormQuestion: "FormAnswer", - FileQuestion: "FileAnswer" + FileQuestion: "FileAnswer", + StaticQuestion: null }; /** @@ -81,26 +82,28 @@ export default EmberObject.extend({ }) ); - const answer = Answer.create( - getOwner(this).ownerInjection(), - Object.assign( - this._answer || { - __typename, - question: { slug: this._question.slug }, - [camelize(__typename.replace(/Answer$/, "Value"))]: null - }, - { document: this.document, field: this }, - this._answer && Array.isArray(this._answer.value) - ? { - rowDocuments: this._answer.value.map(document => - Document.create(getOwner(this).ownerInjection(), { - raw: document - }) - ) - } - : {} - ) - ); + const answer = + __typename && + Answer.create( + getOwner(this).ownerInjection(), + Object.assign( + this._answer || { + __typename, + question: { slug: this._question.slug }, + [camelize(__typename.replace(/Answer$/, "Value"))]: null + }, + { document: this.document, field: this }, + this._answer && Array.isArray(this._answer.value) + ? { + rowDocuments: this._answer.value.map(document => + Document.create(getOwner(this).ownerInjection(), { + raw: document + }) + ) + } + : {} + ) + ); this.setProperties({ _errors: [], diff --git a/addon/mirage-graphql/mocks/question.js b/addon/mirage-graphql/mocks/question.js index e9f0fab19..b2bfe8a31 100644 --- a/addon/mirage-graphql/mocks/question.js +++ b/addon/mirage-graphql/mocks/question.js @@ -67,6 +67,11 @@ export default class extends BaseMock { return this.handleInterfaceType(...arguments); } + @register("StaticQuestion") + handleStaticQuestion() { + return this.handleInterfaceType(...arguments); + } + @register("SaveTextQuestionPayload") handleSaveTextQuestion(_, { input }) { return this.handleSavePayload.fn.call(this, _, { @@ -95,6 +100,13 @@ export default class extends BaseMock { }); } + @register("SaveStaticQuestionPayload") + handleSaveStaticQuestion(_, { input }) { + return this.handleSavePayload.fn.call(this, _, { + input: { ...input, type: "STATIC" } + }); + } + @register("SaveChoiceQuestionPayload") handleSaveChoiceQuestion(_, { input }) { const options = input.options.map(slug => @@ -144,7 +156,7 @@ export default class extends BaseMock { } @register("SaveTableQuestionPayload") - handleSaveTableFloatQuestion(_, { input }) { + handleSaveTableQuestion(_, { input }) { return this.handleSavePayload.fn.call(this, _, { input: { ...input, type: "TABLE" } }); diff --git a/addon/mirage-graphql/schema.graphql b/addon/mirage-graphql/schema.graphql index c136ed243..0d3ff1428 100644 --- a/addon/mirage-graphql/schema.graphql +++ b/addon/mirage-graphql/schema.graphql @@ -1,5 +1,5 @@ # source: http://localhost:8000/graphql -# timestamp: Tue Apr 16 2019 21:09:35 GMT+0200 (Central European Summer Time) +# timestamp: Sun Apr 28 2019 16:45:11 GMT+0200 (Central European Summer Time) input AddFormQuestionInput { form: ID! @@ -1134,6 +1134,8 @@ type Mutation { input: SaveCompleteTaskFormTaskInput! ): SaveCompleteTaskFormTaskPayload startCase(input: StartCaseInput!): StartCasePayload + @deprecated(reason: "Use SaveCase mutation instead") + saveCase(input: SaveCaseInput!): SaveCasePayload cancelCase(input: CancelCaseInput!): CancelCasePayload completeWorkItem(input: CompleteWorkItemInput!): CompleteWorkItemPayload saveWorkItem(input: SaveWorkItemInput!): SaveWorkItemPayload @@ -1165,6 +1167,7 @@ type Mutation { saveTableQuestion(input: SaveTableQuestionInput!): SaveTableQuestionPayload saveFormQuestion(input: SaveFormQuestionInput!): SaveFormQuestionPayload saveFileQuestion(input: SaveFileQuestionInput!): SaveFileQuestionPayload + saveStaticQuestion(input: SaveStaticQuestionInput!): SaveStaticQuestionPayload saveDocument(input: SaveDocumentInput!): SaveDocumentPayload saveDocumentStringAnswer( input: SaveDocumentStringAnswerInput! @@ -1632,6 +1635,20 @@ type ReorderFormQuestionsPayload { clientMutationId: String } +input SaveCaseInput { + id: String + workflow: ID! + meta: JSONString + parentWorkItem: ID + form: ID + clientMutationId: String +} + +type SaveCasePayload { + case: Case + clientMutationId: String +} + input SaveChoiceQuestionInput { slug: String! label: String! @@ -1795,6 +1812,7 @@ type SaveDocumentFormAnswerPayload { } input SaveDocumentInput { + id: String form: ID! meta: JSONString clientMutationId: String @@ -2007,6 +2025,22 @@ type SaveSimpleTaskPayload { clientMutationId: String } +input SaveStaticQuestionInput { + label: String! + slug: String! + infoText: String + isHidden: QuestionJexl + meta: JSONString + isArchived: Boolean + staticContent: String! + clientMutationId: String +} + +type SaveStaticQuestionPayload { + question: Question + clientMutationId: String +} + input SaveTableQuestionInput { slug: String! label: String! @@ -2149,6 +2183,47 @@ type StartCasePayload { clientMutationId: String } +type StaticQuestion implements Question & Node { + createdAt: DateTime! + modifiedAt: DateTime! + createdByUser: String + createdByGroup: String + slug: String! + label: String! + isHidden: QuestionJexl! + isArchived: Boolean! + infoText: String + meta: GenericScalar! + source: Question + forms( + before: String + after: String + first: Int + last: Int + + """ + FormOrdering + """ + orderBy: [FormOrdering] + slug: String + name: String + description: String + isPublished: Boolean + isArchived: Boolean + createdByUser: String + createdByGroup: String + metaHasKey: String + search: String + ): FormConnection + staticContent: String + + """ + The ID of the object. + """ + id: ID! + isRequired: QuestionJexl! +} + type StringAnswer implements Answer & Node { createdAt: DateTime! modifiedAt: DateTime! diff --git a/addon/templates/components/cf-field.hbs b/addon/templates/components/cf-field.hbs index 9f3dd3bd4..7abb94679 100644 --- a/addon/templates/components/cf-field.hbs +++ b/addon/templates/components/cf-field.hbs @@ -1,7 +1,7 @@ {{#if (not-eq field.question.__typename "FormQuestion")}} - {{#unless field.question.meta.hideLabel}} + {{#if (and (not field.question.meta.hideLabel) (not-eq field.question.__typename "StaticQuestion"))}} {{cf-field/label field=field}} - {{/unless}} + {{/if}}
{{#if componentOverride}} diff --git a/addon/templates/components/cf-field/input/static.hbs b/addon/templates/components/cf-field/input/static.hbs new file mode 100644 index 000000000..b9fcd8956 --- /dev/null +++ b/addon/templates/components/cf-field/input/static.hbs @@ -0,0 +1 @@ +{{markdown-to-html field.question.staticContent}} diff --git a/addon/templates/components/cfb-form-editor/question.hbs b/addon/templates/components/cfb-form-editor/question.hbs index 3babdba86..b2de538bd 100644 --- a/addon/templates/components/cfb-form-editor/question.hbs +++ b/addon/templates/components/cfb-form-editor/question.hbs @@ -76,17 +76,32 @@ {{/if}}
-
- {{f.input - name="isRequired" - label=(t "caluma.form-builder.question.isRequired") - required=true - renderComponent=(component "cfb-jexl-boolean-toggle-switch" size="small") - class="uk-flex uk-flex-between uk-flex-column" - }} -
+ {{#if (not-eq f.model.__typename "StaticQuestion")}} +
+ {{f.input + name="isRequired" + label=(t "caluma.form-builder.question.isRequired") + required=true + renderComponent=(component "cfb-jexl-boolean-toggle-switch" size="small") + class="uk-flex uk-flex-between uk-flex-column" + }} +
+ {{/if}}
+ {{#if (eq f.model.__typename "StaticQuestion")}} + {{f.input + type="textarea" + label=(t "caluma.form-builder.question.staticContent") + name="staticContent" + class="uk-margin-remove-bottom" + }} + + {{t "caluma.form-builder.question.supportsMarkdownPrefix"}} + {{t "caluma.form-builder.question.markdown"}} + + {{/if}} + {{f.input type="textarea" label=(t "caluma.form-builder.question.infoText") diff --git a/app/components/cf-field/input/static.js b/app/components/cf-field/input/static.js new file mode 100644 index 000000000..57edab380 --- /dev/null +++ b/app/components/cf-field/input/static.js @@ -0,0 +1 @@ +export { default } from "ember-caluma/components/cf-field/input/static"; diff --git a/tests/integration/components/cfb-form-editor/question-test.js b/tests/integration/components/cfb-form-editor/question-test.js index 3de9383ab..67ec695fa 100644 --- a/tests/integration/components/cfb-form-editor/question-test.js +++ b/tests/integration/components/cfb-form-editor/question-test.js @@ -403,6 +403,34 @@ module("Integration | Component | cfb-form-editor/question", function(hooks) { assert.verifySteps(["after-submit"]); }); + test("it can create a static question", async function(assert) { + assert.expect(6); + + this.server.create("form", { slug: "test-form" }); + + this.set("afterSubmit", question => { + assert.equal(question.__typename, "StaticQuestion"); + assert.equal(question.label, "Label"); + assert.equal(question.slug, "slug"); + assert.equal(question.staticContent, "#bazz"); + + assert.step("after-submit"); + }); + + await render( + hbs`{{cfb-form-editor/question form='test-form' on-after-submit=(action afterSubmit)}}` + ); + + await fillIn("[name=__typename]", "StaticQuestion"); + await fillIn("[name=label]", "Label"); + await fillIn("[name=slug]", "slug"); + await fillIn("[name=staticContent]", "#bazz"); + + await click("button[type=submit]"); + + assert.verifySteps(["after-submit"]); + }); + test("it validates the slug", async function(assert) { assert.expect(3); diff --git a/translations/de-de.yaml b/translations/de-de.yaml index 7d9c68d73..a1091300a 100644 --- a/translations/de-de.yaml +++ b/translations/de-de.yaml @@ -7,7 +7,7 @@ caluma: form: optional: "Optional" save: "Speichern" - selectFile: "Klicken um Datei hochzuladen" + selectFile: "Klicken um Datei hochzuladen" changeFile: "Klicken um andere Datei hochzuladen" notification: @@ -68,6 +68,7 @@ caluma: slug: "Slug" type: "Typ" isRequired: "Pflichtfeld" + staticContent: "Statischer Inhalt" infoText: "Infotext" placeholder: "Platzhalter" isHidden: "Versteckt (JEXL)" @@ -109,6 +110,7 @@ caluma: TableQuestion: "Tabelle" FormQuestion: "Formular" FileQuestion: "Datei" + StaticQuestion: "Statischer Inhalt" widgetOverrides: powerselect: "Power Select" diff --git a/translations/en-us.yaml b/translations/en-us.yaml index e6a1ccfe2..44af2e525 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -7,7 +7,7 @@ caluma: form: optional: "Optional" save: "Save" - selectFile: "Click to upload a file" + selectFile: "Click to upload a file" changeFile: "Click to upload a different file" notification: @@ -68,6 +68,7 @@ caluma: slug: "Slug" type: "Type" isRequired: "Required" + staticContent: "Static content" infoText: "Information text" placeholder: "Placeholder" isHidden: "Hidden (JEXL)" @@ -109,6 +110,7 @@ caluma: TableQuestion: "Table" FormQuestion: "Form" FileQuestion: "File" + StaticQuestion: "Static content" widgetOverrides: powerselect: "Power Select"