Skip to content

Surveys is a plain java library to provide a base for questionnaires. It also provides a function to generate diagrams and to measure answer times.

License

Notifications You must be signed in to change notification settings

YunaBraska/surveys

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Surveys

Surveys is a plain java library to provide a base for nested questionnaires. It also provides a function to generate and import diagrams using graphviz-java and to measure answer times.

Build Maintainable Coverage Issues Commit Dependencies License Central Tag Javadoc Size Label Label

Motivation

The goal of this project was to build a simple, solid core workflow/state machine library with a minimalistic style. Means everyone can build easily on top of it while providing already basic functions like import/export diagrams. A survey is easy to modify and store in a database as its just a simple ordered list.

Requirements

  • Library graphviz (e.g. brew install graphviz, sudo apt-get install graphviz) is needed for import and export diagrams. (graphviz-java is used)

Diagram example

On this example:

  • Green = Answered
  • Orange = Current
  • Blue = Transitioned back path

Diagram example

Data Structure

Diagram example

Define a flow

  Question flow = Question.of("START")
        .targetGet(Question.of("Q1"))
        .targetGet(Question.of("Q2"))
        .targetGet(Question.of("Q3"))
        .targetGet(Question.of("END"));

Define a condition

  QuestionBool flow =  QuestionBool.of("START");
        flow.target(Question.of("OPTION_01"), answer->answer==true);
        flow.target(Question.of("OPTION_02"), new MyCondition());

Define a back event with condition

  • Back events are functions. They will be triggered on any back transition which needs to step over an associated FlowItem
  • Back conditions can block the backward transitions
    Question flow = Question.of("Q1");
        flow.onBack(answer->answer==true);
        flow.onBack(new MyCondition());

Define custom condition

public class CustomChoice extends Choice<String> {

    //Label for diagram - nullable
    public CustomChoice() {
        super("If equals 1");
    }

    //Return true if transition to target is allowed
    @Override
    public boolean apply(final String answer) {
        return answer.equals("1");
    }
}

Define custom flowItem

  • Default FlowItems/Examples can be found in the JavaDoc
import java.util.Optional;

public class MyFlowItem extends FlowItem<Boolean, MyFlowItem> {

    //Parse answer to defined type which will be used to match a condition
    @Override
    public Optional<String> parse(final ContextExchange exchange) {
        return exchange.payload(String.class);
    }
}

Start a survey

  • Surveys are used for
    • Answering the flow
    • Tracking the answer history
    • Soring the flow config/behavior
    Question flow = Question.of(START);
    Survey mySurvey = Survey.init(myFlow);

Answer a survey

  • Surveys answers always the current FlowItem in the flow
"    Question flow = Question.of(Q1).target(Question.of(Q2));
        Survey survey = Survey.init(myFlow);
        survey.answer("Yes") //Answers the first question (Q1)
        survey.answer("Yes") //Answers the second question (Q2)
        survey.answer("Yes", "My Context object") //Answers the third question with a custom context object

Export a survey

  • Export a survey can be useful to save the current state like to a DB
        Survey survey = Survey.init(Question.of(MYFLOW));
        List<HistoryItem> history = survey.getHistory(); //The order is important - time is UTC
        List<HistoryItemJson> history = survey.getHistoryJson(); //converts answers to json for easier database storage

Import a survey

  • Importing a survey can be useful to continue a previous survey
        List<HistoryItem> history = [...]
        Survey survey=Survey.init(history);

Transition backward or forward

  • Transitioning back and forth won't lose the answer history
""        Survey survey = [...]
        boolean success = survey.transitTo("Q2")
        boolean success = survey.transitTo("Q1", "My custom context object")

Disable back transitions without condition

  • On default back transitions without conditions are allowed - this can be disabled by autoBackTransition
        Survey survey = [...]
        boolean success = survey.autoBackTransition(false)

Render a diagram

  • A diagram can be easily rendered of any survey or flowItem (default target = javaTmpDir)
  • Java lambda functions are not exportable!
    final File path=survey.diagram().save(survey, "/optional/target/file.svg", Format.SVG)

Diagram direction

  • Directions (TOP_TO_BOTTOM, BOTTOM_TO_TOP, LEFT_TO_RIGHT, RIGHT_TO_LEFT)
    final DiagramExporter exporter = survey.diagram();
    exporter.config().direction(LEFT_TO_RIGHT);

Diagram size

     final DiagramExporter exporter = survey.diagram();
    exporter.config().width(800).height(600);

Diagram styling

  • Graphviz diagram Attributes (e.g. Color, Shape,...) can additionally for each ElementType [ITEM_DRAFT, ITEM_CHOICE, ITEM_CURRENT, ITEM_ANSWERED, ITEM_DEFAULT]
    final DiagramExporter exporter = survey.diagram();
    exporter.config()
        .add(ITEM_CURRENT, Color.RED)
        .add(ITEM_ANSWERED, Shape.START);

Disable autogenerated choice

  • Disable autogenerated choice elements
    final DiagramExporter exporter = survey.diagram();
    exporter.config().add(ITEM_CHOICE, Shape.NONE);

Answer duration metrics

  • Surveys can output the time a user spent to answer the questions
        Survey survey = [...]
        Map<String, Long> durations=survey.getDurationsMS()

Import from a diagram

  • Format must be DOT
  • Import can be imported by [File, String, InputStream, MutableGraph]
  • Its required to define possible flowItems (Child's of FlowItem) and conditions (Child' of Condition) since the library doesn't use reflections (except of the export to json function)
    final FlowItem<?,?> flow = new DiagramImporter().read(file)

Create a diagram manually

  • Diagrams can be manually created like with GraphvizOnline
  • To detect the FlowItems and Conditions, it's important to add meta attributes
  • Link Attributes
    • DiagramExporter.CONFIG_KEY_SOURCE = configures the "from" flowItem
    • DiagramExporter.CONFIG_KEY_TARGET = configures the "to" flowItem
    • DiagramExporter.CONFIG_KEY_CONDITION = Condition class name
  • Element/Node Attributes
    • DiagramExporter.CONFIG_KEY_SOURCE = Label for flowItem
    • DiagramExporter.CONFIG_KEY_CLASS = FlowItem class name
    • DiagramExporter.CONFIG_KEY_CONDITION = Comma separated list of condition class names for back transitions

Full example

class SurveyExampleTest {

  @Test
  void testSurvey() {
    final QuestionBool flow = QuestionBool.of("START");
    final AtomicBoolean question2BackTriggered = new AtomicBoolean(false);

    //DEFINE FLOW
    flow.target(Question.of("Q1_TRUE"), answer -> answer == true);
    flow.targetGet(Question.of("Q1_FALSE"), answer -> answer == false)
            .targetGet(Question.of("Q2")).onBack(oldAnswer -> {
              question2BackTriggered.set(true);
              return true;
            })
            .targetGet(Question.of("Q3"))
            .targetGet(Question.of("END"));

    //CREATE survey that manages the history / context
    final Survey survey01 = Survey.init(flow);

    //EXECUTE survey flow
    assertThat(survey01.get(), is(equalTo(QuestionBool.of("START"))));
    assertThat(survey01.answer("Yes").get(), is(equalTo(Question.of("Q1_TRUE"))));
    assertThat(survey01.transitTo("START"), is(true));

    //TRANSITION NO BACK TRIGGERED
    assertThat(question2BackTriggered.get(), is(false));
    assertThat(survey01.get(), is(equalTo(QuestionBool.of("START"))));
    assertThat(survey01.answer("No").get(), is(equalTo(Question.of("Q1_FALSE"))));

    //EXPORT / IMPORT
    final List<HistoryItemJson> export = survey01.getHistoryJson();
    final Survey survey02 = Survey.init(flow, export);
    assertThat(export, is(equalTo(survey02.getHistoryJson())));
    assertThat(survey02.get(), is(equalTo(survey01.get())));
    assertThat(survey02.answer("next").get(), is(equalTo(Question.of("Q2"))));
    assertThat(survey02.answer("next").get(), is(equalTo(Question.of("Q3"))));
    assertThat(survey02.answer("next").get(), is(equalTo(Question.of("END"))));
    assertThat(survey02.answer("next").get(), is(equalTo(Question.of("END"))));
    assertThat(survey02.isEnded(), is(true));

    //TRANSITION BACK TRIGGERED
    assertThat(survey02.transitTo("START"), is(true));
    assertThat(question2BackTriggered.get(), is(true));
    assertThat(survey02.isEnded(), is(false));

    //TRANSITION FORWARD
    assertThat(survey02.transitTo("END"), is(true));
    assertThat(survey02.get(), is(Question.of("END")));
    assertThat(survey02.isEnded(), is(true));

    //IMPORT FINISHED FLOW
    assertThat(Survey.init(flow, survey02.getHistory()).isEnded(), is(true));
  }
}

TODOs

  • Core: Implement custom exceptions
  • Feature: Custom error handler concept
  • Feature: Add more question examples like radio, checkbox, list, map,...
  • Diagram: Generate heat map from List of Surveys for e.g. most, longest, never taken answers
  • Diagram: Custom styling also for links
  • Diagram: Import / Export from UML

About

Surveys is a plain java library to provide a base for questionnaires. It also provides a function to generate diagrams and to measure answer times.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages