Skip to content

Commit

Permalink
[fix] ordering of questions and answer values by a property and alpha…
Browse files Browse the repository at this point in the history
…betical order
  • Loading branch information
blcham committed Apr 23, 2017
1 parent 29687b5 commit c867d6f
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 17 deletions.
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
"email": "martin.ledvinka@fel.cvut.cz",
"url": "https://kbss.felk.cvut.cz/web/portal/people"
},
"contributors": [{
"name": "Miroslav Blasko",
"email": "miroslav.blasko@fel.cvut.cz",
"url": "https://kbss.felk.cvut.cz/web/portal/people"
}],
"contributors": [
{
"name": "Miroslav Blasko",
"email": "miroslav.blasko@fel.cvut.cz",
"url": "https://kbss.felk.cvut.cz/web/portal/people"
}
],
"repository": {
"type": "git",
"url": "https://kbss.felk.cvut.cz/git/semforms.git"
Expand All @@ -32,7 +34,8 @@
"react-bootstrap-typeahead": "https://kbss.felk.cvut.cz/dist/react-bootstrap-typeahead-0.0.11.tgz",
"jsonld-utils": "https://kbss.felk.cvut.cz/dist/jsonld-utils-0.0.6.tgz",
"inputmask-core": "^2.1.1",
"babel-runtime": "^6.9.2"
"babel-runtime": "^6.9.2",
"tsort": "0.0.1"
},
"peerDependencies": {
"react": ">= 15.3.2",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Question.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export default class Question extends React.Component {
question[Constants.HAS_SUBQUESTION].sort(JsonLdObjectUtils.getCompareLocalizedLabelFunction(Configuration.intl));

// sort by property
JsonLdObjectUtils.toplogicalSort(question[Constants.HAS_SUBQUESTION], Constants.HAS_PRECEDING_QUESTION);
JsonLdObjectUtils.orderPreservingToplogicalSort(question[Constants.HAS_SUBQUESTION], Constants.HAS_PRECEDING_QUESTION);

return question[Constants.HAS_SUBQUESTION];
}
Expand Down
19 changes: 16 additions & 3 deletions src/components/answer/TypeaheadAnswer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Configuration from '../../model/Configuration';
import Constants from '../../constants/Constants';
import FormUtils from '../../util/FormUtils';
import Utils from '../../util/Utils';
import JsonLdObjectUtils from "../../util/JsonLdObjectUtils";

export default class TypeaheadAnswer extends React.Component {
static propTypes = {
Expand All @@ -23,7 +24,7 @@ export default class TypeaheadAnswer extends React.Component {
super(props);
this._queryHash = Utils.getStringHash(FormUtils.getPossibleValuesQuery(this.props.question));
this.state = {
options: this._queryHash ? JsonLdUtils.processTypeaheadOptions(Configuration.optionsStore.getOptions(this._queryHash)) : []
options: this._queryHash ? this._processTypeaheadOptions(Configuration.optionsStore.getOptions(this._queryHash)) : []
}
}

Expand All @@ -32,7 +33,7 @@ export default class TypeaheadAnswer extends React.Component {
if (!question[Constants.HAS_OPTION] && FormUtils.getPossibleValuesQuery(question)) {
Configuration.actions.loadFormOptions(this._queryHash, FormUtils.getPossibleValuesQuery(question));
} else {
this.setState({options: JsonLdUtils.processTypeaheadOptions(question[Constants.HAS_OPTION])});
this.setState({options: this._processTypeaheadOptions(question[Constants.HAS_OPTION])});
}
}

Expand All @@ -48,7 +49,7 @@ export default class TypeaheadAnswer extends React.Component {
if (type !== this._queryHash) {
return;
}
options = JsonLdUtils.processTypeaheadOptions(options);
options = this._processTypeaheadOptions(options);
const value = FormUtils.resolveValue(this.props.answer),
selected = options.find((item) => {
return item.id === value;
Expand All @@ -63,6 +64,18 @@ export default class TypeaheadAnswer extends React.Component {
this.props.onChange(option ? option.id : null);
};

_processTypeaheadOptions(options) {
if (!options) {
return [];
}
// sort by label
options.sort(JsonLdObjectUtils.getCompareLocalizedLabelFunction(Configuration.intl));

// sort by property
JsonLdObjectUtils.orderPreservingToplogicalSort(options, Constants.HAS_PRECEDING_VALUE);
return JsonLdUtils.processTypeaheadOptions(options);
}

render() {
const value = Utils.idToName(this.state.options, this.props.value),
question = this.props.question,
Expand Down
5 changes: 5 additions & 0 deletions src/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const FORM = 'http://onto.fel.cvut.cz/ontologies/documentation/form',
REQUIRES_ANSWER_VALUE = 'http://onto.fel.cvut.cz/ontologies/form/requires-answer-value',
REQUIRES_DESCRIPTION = 'http://onto.fel.cvut.cz/ontologies/form/requires-description',
HAS_PRECEDING_QUESTION = 'http://onto.fel.cvut.cz/ontologies/form/has-preceding-question',
HAS_PRECEDING_VALUE = 'http://onto.fel.cvut.cz/ontologies/form/has-preceding-value',

CONDITION = 'http://onto.fel.cvut.cz/ontologies/form/condition',

Expand Down Expand Up @@ -256,6 +257,10 @@ export default class Constants {
return HAS_PRECEDING_QUESTION;
}

static get HAS_PRECEDING_VALUE() {
return HAS_PRECEDING_VALUE;
}

static get CONDITION() {
return CONDITION;
}
Expand Down
2 changes: 1 addition & 1 deletion src/model/WizardGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export default class WizardGenerator {
stepQuestions.sort(JsonLdObjectUtils.getCompareLocalizedLabelFunction(Configuration.intl));

// sort by property
JsonLdObjectUtils.toplogicalSort(stepQuestions, Constants.HAS_PRECEDING_QUESTION);
JsonLdObjectUtils.orderPreservingToplogicalSort(stepQuestions, Constants.HAS_PRECEDING_QUESTION);


steps = stepQuestions.map(
Expand Down
49 changes: 43 additions & 6 deletions src/util/JsonLdObjectUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import jsonld from "jsonld";
import JsonLdUtils from "jsonld-utils";
import Utils from "./Utils";
import tsort from 'tsort';

export default class JsonLdObjectUtils {

Expand Down Expand Up @@ -36,12 +38,15 @@ export default class JsonLdObjectUtils {
swapped = false;
for (var i = 0, len = data.length; i < len; i++) {
for (var j = i; j < len; j++) {
if (data[i][gtProperty] && data[i][gtProperty]['@id'] === data[j]['@id']) {
var tmp = data[i];
data[i] = data[j];
data[j] = tmp;
swapped = true;
break;
if (data[i][gtProperty]) {
let gtId = (typeof data[i][gtProperty] === 'object') ? data[i][gtProperty]['@id'] : data[i][gtProperty];
if (gtId === data[j]['@id']) {
var tmp = data[i];
data[i] = data[j];
data[j] = tmp;
swapped = true;
break;
}
}
}
}
Expand All @@ -50,6 +55,38 @@ export default class JsonLdObjectUtils {
return data;
}

/**
* Sorts the specified JSON-LD data using a topological sort over partially ordered set defined by gtProperty,
* while preserving original order.
*
* This is useful for situations where each item only knows its immediate neighbour in the list.
* @param data The data to sort, should be an array
* @param gtProperty Property specifying that an item is greater than another item. It is used for comparison.
*
*/
static orderPreservingToplogicalSort(data, gtProperty) {
let graph = tsort(),
id2ObjectMap = {};

for (let i = 0, len = data.length; i < len; i++) {
let currentId = data[i]['@id'];
graph.add(currentId);
id2ObjectMap[currentId] = data[i];

Utils.asArray(data[i][gtProperty])
.map(val => (typeof val === 'object') ? val['@id'] : val)
.map(val => [val, currentId])
.forEach(edge => graph.add(edge));
}

let sortedIds = graph.sort();
for (let i = 0, len = sortedIds.length; i < len; i++) {
data[i] = id2ObjectMap[sortedIds[i]];

}
return data;
}

static getCompareLocalizedLabelFunction(intl) {
return (a, b) => {
var aLabel = JsonLdUtils.getLocalized(a[JsonLdUtils.RDFS_LABEL], intl),
Expand Down
37 changes: 37 additions & 0 deletions test/__tests__/TypeaheadAnswerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,41 @@ describe('TypeaheadAnswer', () => {
TestUtils.Simulate.click(resetButton);
expect(onChange).toHaveBeenCalledWith(null);
});

it('orders options using partial ordering with alphabetical ordering', () => {
// create options
const options = createOptionsWithPartialOrder(['3', '2', '1', 'before2'], ['before2<2']),
query = 'SELECT * WHERE { ?x ?y ?z .}';

optionsStore.getOptions.and.returnValue(options);
question[Constants.LAYOUT_CLASS].push(Constants.LAYOUT.QUESTION_TYPEAHEAD);
question[Constants.HAS_OPTIONS_QUERY] = query;

const component = Environment.render(<TypeaheadAnswer answer={{}} question={question} onChange={onChange} label="TestLabel"/>),
typeahead = TestUtils.findRenderedComponentWithType(component, require('react-bootstrap-typeahead'));

expect(optionsStore.getOptions).toHaveBeenCalled();
expect(actions.loadFormOptions).toHaveBeenCalled();
expect(component).not.toBeNull();
expect(component.state.options.map(i => i['id'])).toEqual(['1', 'before2', '2', '3']);
});

function createOptionsWithPartialOrder(ids, orderingRules) {
let options = ids
.map((i) => {
let obj = {'@id': i};
obj[JsonLdUtils.RDFS_LABEL] = i + '. value';
return obj;
});
orderingRules.forEach(
rule => {
const firstId = rule.substring(0, rule.indexOf('<'));
const secondId = rule.substring(rule.indexOf('<')+1);
const secondIndex = ids.indexOf(secondId);

options[secondIndex][Constants.HAS_PRECEDING_VALUE] = firstId;
}
);
return options;
}
});

0 comments on commit c867d6f

Please sign in to comment.