Commit a1481ce2 authored by Kevin Robert's avatar Kevin Robert
Browse files

Merge branch 'feature/expression_items' into 'develop'

UNOTOPLYS-237 : Début de la mise en place de la saisie des conditions d'affichage des items.

See merge request !86
parents 323abd64 d46e1cee
......@@ -31,4 +31,9 @@ class AdvancedCondition extends DisplayCondition {
public String toSpel() {
return condition;
}
@Override
public boolean isValid() {
return StringUtils.isNotBlank(condition);
}
}
package com.unantes.orientactive.condition;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
......@@ -7,14 +9,14 @@ import java.util.stream.Collectors;
/**
* Représentation d'un test sur la valeur d'une réponse.
*/
class AnswerCondition extends DisplayCondition {
class AnswersCondition extends ReferencedCondition {
/**
* La liste des réponses de la forme "questionReference_answerReference".
*/
private List<String> answers = new ArrayList<>();
public AnswerCondition() {
public AnswersCondition() {
super("answers");
}
......@@ -28,6 +30,11 @@ class AnswerCondition extends DisplayCondition {
@Override
public String toSpel() {
return "(" + answers.stream().map(answer -> "answers[" + answer + "]").collect(Collectors.joining(" or ")) + ")";
return "(" + answers.stream().map(answer -> "answers[" + reference + Expression.VARIABLE_PART_SEPARATOR + answer + "]").collect(Collectors.joining(" or ")) + ")";
}
@Override
public boolean isValid() {
return super.isValid() && CollectionUtils.isNotEmpty(answers);
}
}
package com.unantes.orientactive.condition;
import org.apache.commons.lang3.StringUtils;
/**
* Représentation d'une comparaison simple.
* Compare une référence de variable avec une valeur.
*/
class ComparisonCondition extends DisplayCondition {
class ComparisonCondition extends ReferencedCondition {
private String operator;
private String leftOperand;
private String rightOperand;
public ComparisonCondition() {
......@@ -23,14 +24,6 @@ class ComparisonCondition extends DisplayCondition {
this.operator = operator;
}
public String getLeftOperand() {
return leftOperand;
}
public void setLeftOperand(String leftOperand) {
this.leftOperand = leftOperand;
}
public String getRightOperand() {
return rightOperand;
}
......@@ -41,6 +34,11 @@ class ComparisonCondition extends DisplayCondition {
@Override
public String toSpel() {
return leftOperand + " " + operator + " " + rightOperand;
return "variables[" + reference + "] " + operator + " " + rightOperand;
}
@Override
public boolean isValid() {
return super.isValid() && StringUtils.isNotBlank(operator) && StringUtils.isNotBlank(rightOperand);
}
}
package com.unantes.orientactive.condition;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apache.commons.lang3.builder.EqualsBuilder;
......@@ -11,7 +12,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, visible = true, property = "type")
@JsonSubTypes(
{
@JsonSubTypes.Type(value = AnswerCondition.class, name = "answers"), // Réponses sélectionnées.
@JsonSubTypes.Type(value = AnswersCondition.class, name = "answers"), // Réponses sélectionnées.
@JsonSubTypes.Type(value = ComparisonCondition.class, name = "comparison"), // Comparaison simple.
@JsonSubTypes.Type(value = HasAnswerCondition.class, name = "hasAnswer"), // Question répondue.
@JsonSubTypes.Type(value = HasNoAnswerCondition.class, name = "hasNoAnswer"), // Question non répondue.
......@@ -37,6 +38,14 @@ public abstract class DisplayCondition {
public abstract String toSpel();
/**
* Permet de savoir si la condition est valide, afin de ne pas tenter de la transformer en spel si elle ne l'est pas.
*
* @return Vrai si la condition est valide, faux sinon.
*/
@JsonIgnore
public abstract boolean isValid();
@Override
public boolean equals(final Object o) {
if (this == o) {
......
......@@ -52,7 +52,7 @@ public class DisplayConditions {
(leftOperand instanceof CompoundExpression || leftOperand instanceof Literal) &&
(rightOperand instanceof CompoundExpression || rightOperand instanceof Literal)
) {
AnswerCondition answerCondition = new AnswerCondition();
AnswersCondition answerCondition = new AnswersCondition();
displayConditions.add(answerCondition);
add(leftOperand, answerCondition);
add(rightOperand, answerCondition);
......@@ -78,7 +78,7 @@ public class DisplayConditions {
) {
ComparisonCondition comparisonCondition = new ComparisonCondition();
displayConditions.add(comparisonCondition);
comparisonCondition.setLeftOperand(leftOperand.toStringAST());
comparisonCondition.setReference(extractVariableName(leftOperand.toStringAST()));
comparisonCondition.setRightOperand(rightOperand.toStringAST());
comparisonCondition.setOperator(relationalOperator.getOperatorName());
} else {
......@@ -91,27 +91,39 @@ public class DisplayConditions {
if (method.getName().equals("hasAnswer")) {
HasAnswerCondition hasAnswerCondition = new HasAnswerCondition();
displayConditions.add(hasAnswerCondition);
hasAnswerCondition.setQuestionReference(method.getChild(0).toStringAST());
String reference = method.getChild(0).toStringAST();
hasAnswerCondition.setReference(reference.replace("'", ""));
} else if (method.getName().equals("hasNoAnswer")) {
HasNoAnswerCondition hasNoAnswerCondition = new HasNoAnswerCondition();
displayConditions.add(hasNoAnswerCondition);
hasNoAnswerCondition.setQuestionReference(method.getChild(0).toStringAST());
String reference = method.getChild(0).toStringAST();
hasNoAnswerCondition.setReference(reference.replace("'", ""));
}
} else {
AnswerCondition answerCondition = new AnswerCondition();
answerCondition.addAnswers(extractVariableName(astNode.toStringAST()));
AnswersCondition answerCondition = new AnswersCondition();
String variableName = extractVariableName(astNode.toStringAST());
answerCondition.addAnswers(extractAnswerReference(variableName));
answerCondition.setReference(extractQuestionReference(variableName));
displayConditions.add(answerCondition);
}
}
public void add(SpelNode astNode, AnswerCondition answerCondition) {
public void add(SpelNode astNode, AnswersCondition answerCondition) {
if (astNode instanceof OpOr) {
OpOr or = (OpOr) astNode;
add(or.getLeftOperand(), answerCondition);
add(or.getRightOperand(), answerCondition);
} else {
System.out.println(astNode.toStringAST());
answerCondition.addAnswers(extractVariableName(astNode.toStringAST()));
String variableName = extractVariableName(astNode.toStringAST());
if (variableName.contains(Expression.VARIABLE_PART_SEPARATOR)) {
String[] infosAnswer = variableName.split(Expression.VARIABLE_PART_SEPARATOR);
String questionReference = infosAnswer[0];
String answerValue = infosAnswer[1];
answerCondition.setReference(questionReference);
answerCondition.addAnswers(answerValue);
} else {
answerCondition.addAnswers(variableName);
}
}
}
......@@ -123,18 +135,34 @@ public class DisplayConditions {
return variable[0];
}
private String extractQuestionReference(String variableName) {
String[] infos = variableName.split(Expression.VARIABLE_PART_SEPARATOR);
if (infos.length != 2) {
throw new IllegalStateException(variableName);
}
return infos[0];
}
private String extractAnswerReference(String variableName) {
String[] infos = variableName.split(Expression.VARIABLE_PART_SEPARATOR);
if (infos.length != 2) {
throw new IllegalStateException(variableName);
}
return infos[1];
}
public void joinComparison() {
//@formatter:off
final Map<String, List<ComparisonCondition>> groupedRange = displayConditions.stream()
.filter(ComparisonCondition.class::isInstance)
.map(ComparisonCondition.class::cast)
.collect(groupingBy(ComparisonCondition::getLeftOperand));
.collect(groupingBy(ComparisonCondition::getReference));
//@formatter:on
for (List<ComparisonCondition> value : groupedRange.values()) {
if (value.size() == 2) {
RangeCondition rangeCondition = new RangeCondition();
ComparisonCondition left = value.get(0);
rangeCondition.setComparedObject(left.getLeftOperand());
rangeCondition.setComparedObject(left.getReference());
rangeCondition.setLeftBoundary(left.getRightOperand());
rangeCondition.setLeftOperator(left.getOperator());
ComparisonCondition right = value.get(1);
......@@ -147,8 +175,13 @@ public class DisplayConditions {
}
}
/**
* Traduction en SPEL des expresions valides.
*
* @return L'expression SPEL.
*/
public String toSpEL() {
return displayConditions.stream().map(DisplayCondition::toSpel).collect(Collectors.joining(" and "));
return displayConditions.stream().filter(DisplayCondition::isValid).map(DisplayCondition::toSpel).collect(Collectors.joining(" and "));
}
public List<DisplayCondition> getDisplayConditions() {
......
......@@ -36,14 +36,14 @@ public class Expression implements IExpression {
private static final Logger LOGGER = LoggerFactory.getLogger(Expression.class);
/**
* Le séparateur entre la référence de la question et sa valeur.
* Le séparateur entre les différentes parties d'une variable
*/
private static final String QUESTION_VALUE_SEPARATOR = "__";
public static final String VARIABLE_PART_SEPARATOR = "__";
/**
* Le séparateur entre la référence du screen et la référence de la question.
* Le séparateur entre la référence d'un écran et la référence d'une question.
*/
private static final String SCREEN_QUESTION_SEPARATOR = "_";
public static final String SCREEN_QUESTION_SEPARATOR = "_";
/**
* Lors du calcul des variables {@link #addVariables(List)}, une boucle infi peut se produire. Cette constante en est le seuil de détection.
......@@ -333,14 +333,14 @@ public class Expression implements IExpression {
}
/**
* Construit la référence à une réponse : referenceEcran_referenceQuestion_valeurReponse
* Construit la référence à une réponse : referenceEcran_referenceQuestion__valeurReponse
* @param screenReference reference de l'écran contenant la question
* @param questionReference reference d'une question de l'écran
* @param value reference d'une réponse de la question
* @return
*/
protected String getAnswerReference(final String screenReference, final String questionReference, final String value) {
return getQuestionReference(screenReference, questionReference) + QUESTION_VALUE_SEPARATOR + value;
return getQuestionReference(screenReference, questionReference) + VARIABLE_PART_SEPARATOR + value;
}
/**
......
......@@ -3,24 +3,15 @@ package com.unantes.orientactive.condition;
/**
* Représentation d'un test permettant de savoir si une question est répondue.
*/
class HasAnswerCondition extends DisplayCondition {
class HasAnswerCondition extends ReferencedCondition {
private String questionReference;
public HasAnswerCondition() {
super("hasAnswer");
}
public String getQuestionReference() {
return questionReference;
}
public void setQuestionReference(String questionReference) {
this.questionReference = questionReference;
}
@Override
public String toSpel() {
return "hasAnswer(" + questionReference + ")";
return "hasAnswer('" + reference + "')";
}
}
......@@ -3,24 +3,15 @@ package com.unantes.orientactive.condition;
/**
* Représentation d'un test permettant de savoir si une question n'est pas répondue.
*/
class HasNoAnswerCondition extends DisplayCondition {
class HasNoAnswerCondition extends ReferencedCondition {
private String questionReference;
public HasNoAnswerCondition() {
super("hasNoAnswer");
}
public String getQuestionReference() {
return questionReference;
}
public void setQuestionReference(String questionReference) {
this.questionReference = questionReference;
}
@Override
public String toSpel() {
return "hasNoAnswer(" + questionReference + ")";
return "hasNoAnswer('" + reference + "')";
}
}
package com.unantes.orientactive.condition;
/**
* IMPORTANT : Normalement non supporté par l'application !
* <p>
* Représentation d'une double comparaison.
*/
class RangeCondition extends DisplayCondition {
......@@ -59,4 +61,14 @@ class RangeCondition extends DisplayCondition {
public void setComparedObject(final String comparedObject) {
this.comparedObject = comparedObject;
}
/**
* Toujours faux car cette condition n'est pas supportée.
*
* @return faux.
*/
@Override
public boolean isValid() {
return false;
}
}
package com.unantes.orientactive.condition;
import org.apache.commons.lang3.StringUtils;
public abstract class ReferencedCondition extends DisplayCondition {
/**
* La référence.
*/
protected String reference = StringUtils.EMPTY;
/**
* Constructeur minimal.
*
* @param type Le type de la condition d'affichage.
*/
public ReferencedCondition(String type) {
super(type);
}
public String getReference() {
return reference;
}
public void setReference(String reference) {
this.reference = reference;
}
@Override
public boolean isValid() {
return StringUtils.isNotBlank(reference);
}
}
......@@ -67,10 +67,9 @@ export default class Autocomplete extends AbstractEditableComponent {
this.results = this.items.filter((item: any) => item.label.toLowerCase().indexOf(this.search.toLowerCase()) > -1);
}
public onChange() {
public onChange(evt) {
this.filterResults();
this.displayList = true;
this.$emit('selected', {});
}
public onArrowDown() {
......
import Vue from 'vue';
import Component from 'vue-class-component';
import { mixins } from 'vue-class-component';
import ConditionMixin from '@/components/screen-item/conditions/condition.mixin';
import OaTextarea from '@/components/forms/textarea/oa-textarea.vue';
import {Prop} from "vue-property-decorator";
@Component({
components: {
OaTextarea,
},
})
export default class AdvancedCondition extends mixins(ConditionMixin) {}
export default class AdvancedCondition extends Vue {
/**
* La condition d'affichage simplifiée de l'item.
*/
@Prop()
public condition;
/**
* Mise à jour de la condition suite à la sélection d'une nouvelle question et sauvegarde.
*
* @param selection La sélection.
*/
public updateCondition(selection: any) {
this.condition.condition = selection;
this.$emit('update');
}
}
......@@ -3,7 +3,7 @@
:label="$t('screen.item.conditions.advanced.condition.label')"
:helpText="$t('screen.item.conditions.advanced.condition.helpText')"
:value="condition.condition"
@update="updateCondition({ condition: $event })"
@update="updateCondition"
/>
</template>
......
......@@ -9,7 +9,7 @@ export default class AnswerConditionMixin extends mixins(QuestionConditionMixin)
this.selectedQuestion = selection;
if (this.selectedQuestion) {
const validCondition = {
questionReference: this.selectedQuestion.value.reference,
reference: this.selectedQuestion.id,
};
this.updateCondition(validCondition);
}
......
import { mixins } from 'vue-class-component';
import { Component } from 'vue-property-decorator';
import QuestionConditionMixin from '@/components/screen-item/conditions/question-condition.mixin';
@Component
export default class AnswerCondition extends mixins(QuestionConditionMixin) {
public answers: [];
constructor() {
super();
this.answers = [];
}
public selectQuestion(selection: any) {
this.selectedQuestion = selection;
this.answers = this.questions.filter(q => q === this.selectedQuestion).flatMap(q => q.value.choices);
}
}
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';
import { uuid } from 'vue-uuid';
import Autocomplete from "@/components/forms/autocomplete/autocomplete.vue";
@Component({
components: {
Autocomplete,
},
})
export default class AnswersCondition extends Vue {
/**
* La condition d'affichage simplifiée de l'item.
*/
@Prop()
public condition;
/**
* Les réponses possibles de la question.
*/
public answers: any[] = [];
/**
* Les valeurs sélectionnées par l'utilisateur.
*/
public values: string[] = [];
/**
* UUID pour générer les identifiants des choix.
*/
public uuid: string = uuid.v1();
/**
* La question sélectionnée.
*/
public selectedQuestion;
/**
* Sélection d'une question, on va chercher les réponses associées.
*
* @param selection La question sélectionnée.
*/
public selectQuestion(selection: any) {
this.selectedQuestion = selection;
this.answers = this.questions.filter(q => q === this.selectedQuestion).flatMap(q => q.value.choices);
if (this.condition.answers) {
this.values = this.condition.answers;
} else {
this.values = [];
}
}
/**
* Génération d'un identifiant à partir d'une réponse pour la validité du HTML.
*
* @param value La réponse.
*/
public getChoiceId({ value }) {
return `${value}_${this.uuid}`;
}
/**
* Lors de la sélection d'une réponse on va modifier la condition et sauvegarder les information.
*/
public updateAnswer() {
if (this.selectedQuestion && this.values.length > 0) {
this.condition.reference = this.selectedQuestion.id;
this.condition.answers = this.values;
this.$emit('update', this.condition);
}
}
/**
* Récupération de la liste des questions possible.
* Mise en forme des questions pour l'autocomplete.
*/
public get questions(): any {
return this.$store.getters.questions.map((item: any) => {
return {
id: `${item.screenReference}_${item.reference}`,
label: `${item.idxScreen}. ${item.question}`,
value: item,
};
});
}
/**
* Récupération du libellé de la question sélectionnée pour l'affichage de l'autocomplete.
*/
public get selectedQuestionLabel() {