Skip to content
Permalink
Browse files

feat(lib): support flat answers

BREAKING CHANGE: The whole lib layer changed since we moved to the new
API structure. However, the cf-content component still works the same
way as before.
  • Loading branch information...
anehx committed Jun 21, 2019
1 parent 0b863d8 commit b6020568640020b63c4e097bd4ce968870e977b2
Showing with 2,343 additions and 2,632 deletions.
  1. +0 −6 addon/-private/fragment-types.js
  2. +78 −231 addon/components/cf-content.js
  3. +1 −2 addon/components/cf-field.js
  4. +1 −1 addon/components/cf-field/input/float.js
  5. +1 −1 addon/components/cf-field/input/integer.js
  6. +15 −22 addon/components/cf-field/input/table.js
  7. +1 −1 addon/components/cf-field/input/text.js
  8. +1 −1 addon/components/cf-field/input/textarea.js
  9. +15 −47 addon/components/cf-form.js
  10. +4 −2 addon/components/cf-navigation-item.js
  11. +7 −3 addon/components/cf-navigation.js
  12. +0 −4 addon/components/cfb-form-editor/question.js
  13. +0 −10 addon/gql/fragments/field-answer.graphql
  14. +36 −7 addon/gql/fragments/field-question.graphql
  15. +0 −22 addon/gql/fragments/form-document.graphql
  16. +19 −2 addon/gql/mutations/save-document.graphql
  17. +2 −5 addon/gql/queries/{get-navigation-documents.graphql → get-document-answers.graphql}
  18. +22 −0 addon/gql/queries/get-document-forms.graphql
  19. +0 −7 addon/gql/queries/get-document.graphql
  20. +0 −19 addon/gql/queries/get-navigation-forms.graphql
  21. +88 −19 addon/lib/answer.js
  22. +11 −0 addon/lib/base.js
  23. +163 −214 addon/lib/document.js
  24. +221 −120 addon/lib/field.js
  25. +103 −0 addon/lib/fieldset.js
  26. +34 −0 addon/lib/form.js
  27. +331 −0 addon/lib/navigation.js
  28. +69 −0 addon/lib/parsers.js
  29. +18 −113 addon/lib/question.js
  30. +63 −57 addon/mirage-graphql/schema.graphql
  31. +42 −0 addon/services/caluma-store.js
  32. +0 −67 addon/services/document-store.js
  33. +5 −32 addon/styles/addon.scss
  34. +6 −7 addon/templates/components/cf-content.hbs
  35. +1 −1 addon/templates/components/cf-field/input/checkbox.hbs
  36. +1 −1 addon/templates/components/cf-field/input/date.hbs
  37. +9 −9 addon/templates/components/cf-field/input/file.hbs
  38. +1 −1 addon/templates/components/cf-field/input/radio.hbs
  39. +3 −1 addon/templates/components/cf-field/input/table.hbs
  40. +1 −1 addon/templates/components/cf-form-wrapper.hbs
  41. +7 −3 addon/templates/components/cf-form.hbs
  42. +14 −8 addon/templates/components/cf-navigation-item.hbs
  43. +3 −25 addon/templates/components/cf-navigation.hbs
  44. +4 −4 addon/templates/components/cf-pagination.hbs
  45. +1 −0 app/services/caluma-store.js
  46. 0 {addon → app}/styles/_cf-field.scss
  47. +65 −0 app/styles/_cf-navigation.scss
  48. +34 −0 app/styles/ember-caluma.scss
  49. +1 −1 index.js
  50. +3 −3 package.json
  51. +0 −7 tests/dummy/app/controllers/nested.js
  52. +2 −1 tests/dummy/app/router.js
  53. +48 −0 tests/dummy/app/routes/demo/form.js
  54. +0 −39 tests/dummy/app/routes/nested.js
  55. +0 −57 tests/dummy/app/styles/app.scss
  56. +2 −4 tests/dummy/app/templates/application.hbs
  57. +3 −0 tests/dummy/app/templates/demo/form.hbs
  58. +0 −7 tests/dummy/app/templates/nested.hbs
  59. +260 −14 tests/integration/components/cf-content-test.js
  60. +27 −78 tests/integration/components/cf-field-test.js
  61. +1 −1 tests/integration/components/cf-field/input/float-test.js
  62. +1 −1 tests/integration/components/cf-field/input/integer-test.js
  63. +1 −1 tests/integration/components/cf-field/input/powerselect-test.js
  64. +1 −1 tests/integration/components/cf-field/input/radio-test.js
  65. +1 −1 tests/integration/components/cf-field/input/table-test.js
  66. +1 −1 tests/integration/components/cf-field/input/text-test.js
  67. +1 −1 tests/integration/components/cf-field/input/textarea-test.js
  68. +20 −20 tests/integration/components/cf-field/label-test.js
  69. +2 −266 tests/integration/components/cf-form-test.js
  70. +2 −2 tests/integration/components/cf-navigation-item-test.js
  71. +2 −2 tests/integration/components/cf-navigation-test.js
  72. +95 −2 tests/unit/lib/answer-test.js
  73. +55 −87 tests/unit/lib/document-test.js
  74. +237 −162 tests/unit/lib/field-test.js
  75. +24 −0 tests/unit/lib/fieldset-test.js
  76. +21 −0 tests/unit/lib/form-test.js
  77. +0 −267 tests/unit/lib/nested-with-duplicate-slugs.js
  78. +0 −267 tests/unit/lib/nested.js
  79. +11 −173 tests/unit/lib/question-test.js
  80. +12 −0 tests/unit/services/caluma-store-test.js
  81. +0 −82 tests/unit/services/document-store-test.js
  82. +2 −2 translations/de-de.yaml
  83. +3 −3 translations/en-us.yaml
  84. +3 −3 yarn.lock
@@ -14,9 +14,6 @@ export default {
{
name: "Document"
},
{
name: "FormAnswer"
},
{
name: "Case"
},
@@ -167,9 +164,6 @@ export default {
kind: "INTERFACE",
name: "Answer",
possibleTypes: [
{
name: "FormAnswer"
},
{
name: "StringAnswer"
},
@@ -1,60 +1,19 @@
import Component from "@ember/component";
import layout from "../templates/components/cf-content";
import { inject as service } from "@ember/service";
import { computed, observer } from "@ember/object";
import { reads, filterBy } from "@ember/object/computed";
import { computed } from "@ember/object";
import { reads } from "@ember/object/computed";
import { ComponentQueryManager } from "ember-apollo-client";
import { task } from "ember-concurrency";
import { later, once } from "@ember/runloop";
import Document from "ember-caluma/lib/document";
import Navigation from "ember-caluma/lib/navigation";
import { parseDocument } from "ember-caluma/lib/parsers";

import getNavigationDocumentsQuery from "ember-caluma/gql/queries/get-navigation-documents";
import getNavigationFormsQuery from "ember-caluma/gql/queries/get-navigation-forms";
import getDocumentAnswersQuery from "ember-caluma/gql/queries/get-document-answers";
import getDocumentFormsQuery from "ember-caluma/gql/queries/get-document-forms";
import { getOwner } from "@ember/application";
import { assert } from "@ember/debug";

const isDisplayableDocument = doc =>
doc &&
doc.visibleFields.length &&
!doc.visibleFields.every(field => field.questionType === "FormQuestion");

const buildParams = (section, subSections) => {
if (!section) {
return [];
}

return [
{ section: section.question.slug, subSection: undefined },
...subSections.map(s => ({
section: section.question.slug,
subSection: s.question.slug
}))
];
};

const buildTree = (rootDocument, documents, forms) => {
if (rootDocument.__typename === "Document") {
rootDocument.form = forms.find(
form => form.slug === rootDocument.form.slug
);
}

rootDocument.answers.edges.forEach(answer => {
if (answer.node.__typename === "FormAnswer") {
const childDocument = documents.find(
doc => doc.form.slug === answer.node.question.subForm.slug
);

assert(
`Document for form "${answer.node.question.subForm.slug}" not found`,
childDocument
);

answer.node.formValue = buildTree(childDocument, documents, forms);
}
});

return rootDocument;
};

/**
* Component to render a form with navigation.
*
@@ -77,15 +36,30 @@ const buildTree = (rootDocument, documents, forms) => {
* ```
*
* @class CfContentComponent
* @yield {Object} content
* @yield {Document} content.document
* @yield {CfFormComponent} content.form
* @yield {CfNavigationComponent} content.navigation
* @yield {CfPaginationComponent} content.pagination
*/
export default Component.extend(ComponentQueryManager, {
layout,

documentStore: service(),
router: service(),
calumaStore: service(),

init() {
this._super(...arguments);

assert(
"A `documentId` must be passed to `{{cf-content}}`",
this.documentId
);
},

/**
* The ID of the nested document to display the navigation for
* The uuid of the document to display
*
* @argument {String} documentId
*/
documentId: null,
@@ -99,27 +73,51 @@ export default Component.extend(ComponentQueryManager, {
context: null,

/**
* Form slug of currently visible section
* Whether the form renders in disabled state
*
* @argument {String} section
* @readonly
* @argument {Boolean} disabled
*/
section: reads("router.currentRoute.queryParams.section"),
disabled: false,

/**
* Form slug of currently visible sub-section
* The document to display
*
* @argument {String} subSection
* @readonly
* @property {Document} document
*/
subSection: reads("router.currentRoute.queryParams.subSection"),
document: reads("data.lastSuccessful.value"),

/**
* Whether the form renders in disabled state
*
* @argument {Boolean} disabled
*/
disabled: false,
navigation: computed("document", function() {
if (!this.document) return;

return this.calumaStore.push(
Navigation.create(getOwner(this).ownerInjection(), {
document: this.document
})
);
}),

fieldset: computed(
"document.{fieldsets.[],raw.form.slug}",
"router.currentRoute.queryParams.displayedForm",
function() {
if (!this.document) return;

const slug =
this.get("router.currentRoute.queryParams.displayedForm") ||
this.get("document.raw.form.slug");

const fieldset = this.document.fieldsets.find(
fieldset => fieldset.form.slug === slug
);

assert(
`The fieldset \`${slug}\` does not exist in this document`,
fieldset
);

return fieldset;
}
),

data: computed("documentId", function() {
const task = this.get("dataTask");
@@ -130,181 +128,30 @@ export default Component.extend(ComponentQueryManager, {
}),

dataTask: task(function*() {
if (!this.documentId) return null;

const rootId = window.btoa(`Document:${this.documentId}`);
if (!this.documentId) return;

const documents = (yield this.apollo.query(
const [answerDocument] = (yield this.apollo.query(
{
query: getNavigationDocumentsQuery,
variables: { rootDocument: rootId },
fetchPolicy: "network-only"
query: getDocumentAnswersQuery,
networkPolicy: "network-only",
variables: { id: this.documentId }
},
"allDocuments.edges"
)).map(({ node }) => node);

const forms = (yield this.apollo.query(
const [formDocument] = (yield this.apollo.query(
{
query: getNavigationFormsQuery,
variables: {
slugs: documents.map(doc => doc.form.slug).sort()
},
fetchPolicy: "cache-first"
query: getDocumentFormsQuery,
networkPolicy: "cache-first",
variables: { id: this.documentId }
},
"allForms.edges"
"allDocuments.edges"
)).map(({ node }) => node);

return this.documentStore.find(
buildTree(documents.find(doc => doc.id === rootId), documents, forms)
return this.calumaStore.push(
Document.create(getOwner(this).ownerInjection(), {
raw: parseDocument({ ...answerDocument, ...formDocument })
})
);
}),

rootDocument: reads("data.lastSuccessful.value"),

displayedDocument: computed(
"section",
"subSection",
"rootDocument",
function() {
try {
if (!this.get("rootDocument")) {
return null;
}
if (!this.get("section")) {
return this.get("rootDocument");
}
const section = this.get("rootDocument.fields").find(
field => field.question.slug === this.get("section")
);

if (!this.get("subSection")) {
return section.childDocument;
}
return section.childDocument.fields.find(
field => field.question.slug === this.get("subSection")
).childDocument;
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
return null;
}
}
),

_sections: filterBy(
"rootDocument.visibleFields",
"visibleInNavigation",
true
),

_currentSection: computed("_sections.[]", "section", function() {
return this._sections.find(s => s.question.slug === this.section);
}),

_currentSubSection: computed(
"_currentSubSections.[]",
"subSection",
function() {
return this._currentSubSections.find(
s => s.question.slug === this.subSection
);
}
),

_currentSubSections: filterBy(
"_currentSection.childDocument.visibleFields",
"visibleInNavigation",
true
),

_currentSectionIndex: computed("_sections.[]", "section", function() {
return this._sections.indexOf(this._currentSection);
}),

_previousSection: computed("_currentSectionIndex", function() {
return this._currentSectionIndex > 0
? this._sections[this._currentSectionIndex - 1]
: null;
}),

_nextSection: computed(
"_currentSectionIndex",
"_sections.length",
function() {
return this._currentSectionIndex < this._sections.length
? this._sections[this._currentSectionIndex + 1]
: null;
}
),

_previousSubSections: filterBy(
"_previousSection.childDocument.visibleFields",
"visibleInNavigation",
true
),

_nextSubSections: filterBy(
"_nextSection.childDocument.visibleFields",
"visibleInNavigation",
true
),

adjacentSections: computed(
"_previousSubSections.[]",
"_currentSubSections.[]",
"_nextSubSections.[]",
function() {
return [
...buildParams(this._previousSection, this._previousSubSections),
...buildParams(this._currentSection, this._currentSubSections),
...buildParams(this._nextSection, this._nextSubSections)
];
}
),

sectionIndex: computed(
"adjacentSections.[]",
"section",
"subSection",
function() {
return this.adjacentSections.findIndex(
s => s.section === this.section && s.subSection === this.subSection
);
}
),

previousSection: computed("adjacentSections.[]", "sectionIndex", function() {
return this.sectionIndex > 0
? this.adjacentSections[this.sectionIndex - 1]
: null;
}),

nextSection: computed("adjacentSections.[]", "sectionIndex", function() {
return this.sectionIndex < this.adjacentSections.length
? this.adjacentSections[this.sectionIndex + 1]
: null;
}),

// eslint-disable-next-line ember/no-observers
_displayedDocumentChanged: observer(
"displayedDocument",
"nextSection",
function() {
if (isDisplayableDocument(this.displayedDocument) || !this.nextSection) {
return;
}

later(this, () => once(this, "_transitionToNextSection"));
}
),

_transitionToNextSection() {
if (isDisplayableDocument(this.displayedDocument) || !this.nextSection) {
return;
}

this.router.replaceWith({ queryParams: this.nextSection }).then(() => {
this.element.scrollIntoView(true);
});
}
})
});
@@ -18,13 +18,12 @@ import { task, timeout } from "ember-concurrency";
export default Component.extend({
layout,
classNames: ["uk-margin"],
classNameBindings: ["field.question.hidden:uk-hidden"],
classNameBindings: ["field.hidden:uk-hidden"],

/**
* Task to save a field. This will set the passed value to the answer and
* save the field to the API after a timeout off 500 milliseconds.
*
* @todo Validate the value
* @method save
* @param {String|Number|String[]} value
*/

0 comments on commit b602056

Please sign in to comment.
You can’t perform that action at this time.