Skip to content
This repository has been archived by the owner on Oct 30, 2023. It is now read-only.

Commit

Permalink
Merge pull request #2 from clint-tseng/cxlt/162b
Browse files Browse the repository at this point in the history
convert/new: add support for outputting cascading selects.
  • Loading branch information
issa-tseng authored Jul 28, 2017
2 parents 349d441 + ce79597 commit 94c3966
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 7 deletions.
54 changes: 50 additions & 4 deletions lib/convert.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 107 additions & 0 deletions spec/src/convert-form-spec.ls
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,113 @@ describe 'row generation:' ->
[ \choices_testselect, \delta, undefined, undefined ]
])

test 'single cascading select', ->
result = convert-form(
metadata:
activeLanguages: { 0: \en, _counter: 0 }
controls: [
{ type: \inputSelectOne, name: \universe, cascading: true, options: [
{ val: \known, cascade: [], text: { 0: 'Known' } }
] }
{ type: \inputSelectOne, name: \galaxy, cascading: true, options: [
{ val: \milkyway, cascade: [ \known ], text: { 0: 'Milky Way' } }
{ val: \andromeda, cascade: [ \known ], text: { 0: 'Andromeda' } }
] }
{ type: \inputSelectOne, name: \star, options: [
{ val: \sol, cascade: [ \known, \milkyway ], text: { 0: 'Sol' } }
{ val: \an, cascade: [ \known, \andromeda ], text: { 0: 'AN And' } }
] }
]
)

expect(result[1].data).toEqual([
[ 'list name', \name, \label::en, \universe, \galaxy ]
[ \choices_universe, \known, 'Known', undefined, undefined ]
[ \choices_galaxy, \milkyway, 'Milky Way', \known, undefined ]
[ \choices_galaxy, \andromeda, 'Andromeda', \known, undefined ]
[ \choices_star, \sol, 'Sol', \known, \milkyway ]
[ \choices_star, \an, 'AN And', \known, \andromeda ]
])

test 'single multilingual cascading select', ->
result = convert-form(
metadata:
activeLanguages: { 0: \en, 1: \nyan, _counter: 0 }
controls: [
{ type: \inputSelectOne, name: \universe, cascading: true, options: [
{ val: \known, cascade: [], text: { 0: 'Known', 1: 'Nyown' } }
] }
{ type: \inputSelectOne, name: \galaxy, cascading: true, options: [
{ val: \milkyway, cascade: [ \known ], text: { 0: 'Milky Way', 1: 'Milky Nyan' } }
{ val: \andromeda, cascade: [ \known ], text: { 0: 'Andromeda', 1: 'Nyandromeda' } }
] }
{ type: \inputSelectOne, name: \star, options: [
{ val: \sol, cascade: [ \known, \milkyway ], text: { 0: 'Sol', 1: 'Nyan Sol' } }
{ val: \an, cascade: [ \known, \andromeda ], text: { 0: 'AN And', 1: 'NyAN NyAnd' } }
] }
]
)

expect(result[1].data).toEqual([
[ 'list name', \name, \label::en, \label::nyan, \universe, \galaxy ]
[ \choices_universe, \known, 'Known', 'Nyown', undefined, undefined ]
[ \choices_galaxy, \milkyway, 'Milky Way', 'Milky Nyan', \known, undefined ]
[ \choices_galaxy, \andromeda, 'Andromeda', 'Nyandromeda', \known, undefined ]
[ \choices_star, \sol, 'Sol', 'Nyan Sol', \known, \milkyway ]
[ \choices_star, \an, 'AN And', 'NyAN NyAnd', \known, \andromeda ]
])

test 'multiple cascading selects', ->
result = convert-form(
metadata:
activeLanguages: { 0: \en, _counter: 0 }
controls: [
{ type: \inputSelectOne, name: \universe, cascading: true, options: [
{ val: \known, cascade: [], text: { 0: 'Known' } }
] }
{ type: \inputSelectOne, name: \galaxy, cascading: true, options: [
{ val: \milkyway, cascade: [ \known ], text: { 0: 'Milky Way' } }
{ val: \andromeda, cascade: [ \known ], text: { 0: 'Andromeda' } }
] }
{ type: \inputSelectOne, name: \star, options: [
{ val: \sol, cascade: [ \known, \milkyway ], text: { 0: 'Sol' } }
{ val: \an, cascade: [ \known, \andromeda ], text: { 0: 'AN And' } }
] }

{ type: \inputSelectOne, name: \kingdom, cascading: true, options: [
{ val: \animalia, cascade: [], text: { 0: 'Animalia' } }
{ val: \plantae, cascade: [], text: { 0: 'Plantae' } }
] }
{ type: \inputSelectOne, name: \phylum, cascading: true, options: [
{ val: \arthropoda, cascade: [ \animalia ], text: { 0: 'Arthropoda' } }
{ val: \chordata, cascade: [ \animalia ], text: { 0: 'Chordata' } }
{ val: \magnoliophyta, cascade: [ \plantae ], text: { 0: 'Magnoliophyta' } }
] }
{ type: \inputSelectOne, name: \class, options: [
{ val: \insecta, cascade: [ \animalia, \arthropoda ], text: { 0: 'Insecta' } }
{ val: \mammalia, cascade: [ \animalia, \chordata ], text: { 0: 'Mammalia' } }
{ val: \magnoliopsida, cascade: [ \plantae, \magnoliophyta ], text: { 0: 'Magnoliopsida' } }
] }
]
)

expect(result[1].data).toEqual([
[ 'list name', \name, \label::en, \universe, \galaxy, \kingdom, \phylum ]
[ \choices_universe, \known, 'Known', undefined, undefined, undefined, undefined ]
[ \choices_galaxy, \milkyway, 'Milky Way', \known, undefined, undefined, undefined ]
[ \choices_galaxy, \andromeda, 'Andromeda', \known, undefined, undefined, undefined ]
[ \choices_star, \sol, 'Sol', \known, \milkyway, undefined, undefined ]
[ \choices_star, \an, 'AN And', \known, \andromeda, undefined, undefined ]
[ \choices_kingdom, \animalia, 'Animalia', undefined, undefined, undefined, undefined ]
[ \choices_kingdom, \plantae, 'Plantae', undefined, undefined, undefined, undefined ]
[ \choices_phylum, \arthropoda, 'Arthropoda', undefined, undefined, \animalia, undefined ]
[ \choices_phylum, \chordata, 'Chordata', undefined, undefined, \animalia, undefined ]
[ \choices_phylum, \magnoliophyta, 'Magnoliophyta', undefined, undefined, \plantae, undefined ]
[ \choices_class, \insecta, 'Insecta', undefined, undefined, \animalia, \arthropoda ]
[ \choices_class, \mammalia, 'Mammalia', undefined, undefined, \animalia, \chordata ]
[ \choices_class, \magnoliopsida, 'Magnoliopsida', undefined, undefined, \plantae, \magnoliophyta ]
])

describe 'complex row generation' ->
test 'generates begin and end rows for groups' ->
result = convert-form(
Expand Down
31 changes: 31 additions & 0 deletions spec/src/convert-question-spec.ls
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,37 @@ describe 'options' ->

# TODO: no test for choice id collision.

describe 'cascading' ->
test 'cascading selects generate choice_filters' ->
context = new-context()
first = convert-question({ type: \inputSelectOne, name: \universe, cascading: true, options: [] }, context)
expect(first.choice_filter).toBe('')
second = convert-question({ type: \inputSelectOne, name: \galaxy, cascading: true, options: [] }, context)
expect(second.choice_filter).toBe('universe = ${universe}')
third = convert-question({ type: \inputSelectOne, name: \star, options: [] }, context)
expect(third.choice_filter).toBe('universe = ${universe} and galaxy = ${galaxy}')

test 'cascade ends appropriately' ->
context = new-context()
convert-question({ type: \inputSelectOne, name: \universe, cascading: true, options: [] }, context)
convert-question({ type: \inputSelectOne, name: \galaxy, cascading: true, options: [] }, context)
convert-question({ type: \inputSelectOne, name: \star, options: [] }, context)
innocent = convert-question({ type: \inputSelectOne, name: \something_else, options: [] }, context)
expect(innocent.choice_filter).toBe(undefined)

test 'cascade options are reformatted to dict lookups' ->
context = new-context()
convert-question({ type: \inputSelectOne, name: \universe, cascading: true, options: [] }, context)
convert-question({ type: \inputSelectOne, name: \galaxy, cascading: true, options: [] }, context)
convert-question({ type: \inputSelectOne, name: \star, options: [
{ val: \sol, cascade: [ \known, \milkyway ], text: {} }
{ val: \alphacentauri, cascade: [ \known, \milkyway ], text: {} }
] }, context)
expect(context.choices.choices_star).toEqual([
{ val: \sol, cascade: { universe: \known, galaxy: \milkyway }, text: {} }
{ val: \alphacentauri, cascade: { universe: \known, galaxy: \milkyway }, text: {} }
])

# questions nested in groups are recursively processed:
describe 'group children' ->
# use required flag mutation as a sign that processing happened.
Expand Down
31 changes: 28 additions & 3 deletions src/convert.ls
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ expr-value = (value) ->
| otherwise => value

# conversion constants.
survey-fields = <[ type name label hint required read_only default constraint constraint_message relevant calculation parameters appearance ]>
survey-fields = <[ type name label hint required read_only default constraint constraint_message relevant calculation choice_filter parameters appearance ]>
choices-fields = [ 'list name', \name, \label ]

multilingual-fields = <[ label hint constraint_message ]> # these fields have ::lang syntax/support.
Expand Down Expand Up @@ -124,6 +124,23 @@ convert-question = (question, context, prefix = []) ->
if (other = delete question.other)?
context.successor-relevance = other |> map(-> "selected(#{question.name}, '#it')") |> join(' or ')

# deal with cascades.
if (question.cascading is true) or context.cascade?
context.cascade ?= []

# add a choice filter column value.
question.choice_filter = [ "#name = ${#name}" for name in context.cascade ].join(' and ')

# munge our options to have cascade dicts rather than arrays.
for option in question.options
option.cascade = { [ context.cascade[idx], value ] for value, idx in option.cascade }

# push our context now that we are done. drop the whole thing if we are at the tail.
context.cascade.push(question.name)
if question.cascading isnt true
delete context.cascade
delete question.cascading

# deal with choices. life is hard.
if question.options?
context.warnings ++= [ "Multiple choice lists have the ID '#choice-id'. The last one encountered is used." ] if context.choices[choice-id]?
Expand Down Expand Up @@ -246,8 +263,16 @@ convert-form = (form) ->
[ [ (if field in multilingual-fields then gen-lang(question[field]) else question[field]) for field in survey-simple-fields ] |> flatten ]
survey-rows = gen-rows(intermediate)

# choices serialize straight out.
choices-rows = [ [ name, entry.val ] ++ gen-lang(entry.text) for name, entries of choices for entry in entries ]
# choices might gain columns if cascades are involved.
additional-choice-cols = []
for _, entries of choices when entries[0]?.cascade?
for key of entries[0].cascade when key not in additional-choice-cols
additional-choice-cols.push(key)
choices-schema ++= additional-choice-cols

# once we know the additional fields we can send them all out.
pull-cascade-values = (entry) -> [ entry.cascade[col] for col in additional-choice-cols ]
choices-rows = [ [ name, entry.val ] ++ gen-lang(entry.text) ++ pull-cascade-values(entry) for name, entries of choices for entry in entries ]

# return sheets.
[
Expand Down

0 comments on commit 94c3966

Please sign in to comment.