Commit 4b88a27f authored by Rich's avatar Rich
Browse files

Sorting on reporting

parent 5e0ff470
......@@ -95,7 +95,7 @@ class Extractor {
];
}
public function addPart(string $partID, ?string $ref = NULL, $label = NULL, $raw = FALSE) :Extractor {
public function addPart(string $partID, ?string $ref = NULL, $label = NULL, $useLabel = FALSE, $raw = FALSE) :Extractor {
$part = $this->survey->findQuestion($partID);
......@@ -151,11 +151,13 @@ class Extractor {
if (!$raw) {
if ($part['dataType'] === 'options') {
$this->work['sqlToColumns'][] = [
'method' => 'sqlResultToOptions',
'params' => [
'options' => $part['ask']['options'],
'ref' => $ref,
'useLabel' => $useLabel,
],
];
}
......@@ -172,10 +174,10 @@ class Extractor {
return $this;
}
public function addPartFromCodeName(string $codeName, ?string $ref = NULL, $label = NULL) :Extractor {
public function addPartFromCodeName(string $codeName, ?string $ref = NULL, $label = NULL, $useLabel = FALSE) :Extractor {
$part = $this->survey->findPartByCodeName($codeName);
$ref = $ref ?? $codeName;
return $this->addPart($part['id'], $ref, $label);
return $this->addPart($part['id'], $ref, $label, $useLabel);
}
public function addExpression(string $expression, string $ref, $label = NULL) :Extractor {
......@@ -242,13 +244,14 @@ class Extractor {
foreach ($definition['columns'] as $columnDef) {
$ref = $columnDef['ref'] ?? NULL;
$label = $columnDef['label'] ?? NULL;
$useLabel = $columnDef['useLabel'] ?? FALSE;
switch ($columnDef['type']) {
case 'partID':
$extractor->addPart($columnDef['partID'], $ref, $label);
$extractor->addPart($columnDef['partID'], $ref, $label, $useLabel);
break;
case 'codeName':
$extractor->addPartFromCodeName($columnDef['codeName'], $ref, $label);
$extractor->addPartFromCodeName($columnDef['codeName'], $ref, $label, $useLabel);
break;
case 'expression':
......@@ -296,22 +299,32 @@ class Extractor {
$options[$option['value']] = $option['label'];
}
$ref = $params['ref'];
$useLabel = $params['useLabel'] ?? FALSE;
foreach ($results as &$row) {
$value = $row[$ref];
$vals = [];
$values = [];
if ($value && isset($value['presets'])) {
foreach ($value['presets'] ?? [] as $presetID) {
if (isset($options[$presetID])) {
$val = $options[$presetID];
if (preg_match('/^_other_/', $presetID) && !empty($value['other'][$presetID])) {
$val .= ": " . $value['other'][$presetID];
if ($useLabel) {
foreach ($value['presets'] ?? [] as $presetID) {
if (isset($options[$presetID])) {
$val = $options[$presetID];
if (preg_match('/^_other_/', $presetID) && !empty($value['other'][$presetID])) {
$val .= ": " . $value['other'][$presetID];
}
$values[] = $val;
}
}
}
else {
foreach ($value['presets'] ?? [] as $presetID) {
if (isset($options[$presetID])) {
$values[] = $presetID;
}
$vals[] = $val;
}
}
}
$row[$ref] = implode("\n", $vals);
$row[$ref] = implode("\n", $values);
}
}
/**
......
This diff is collapsed.
<template>
<div>
<h2>{{title}}</h2>
<tabs :tabs="[
<tabs :tabs="[
{name: 'reports', label: 'Saved Reports'},
{name: 'dataset', label: 'Edit data sets'},
{name: 'presentations', label: 'Edit presentations'},
......@@ -199,14 +199,22 @@ export default {
/**
* This is an array of objects detailing the info we are requesting.
*
* Nb. we also have reportData.headers which is a map of { ref: { ref, label } }
* Each item is an object with at least ref, label and weight, but plus whatever the query item is.
* The weight for codeName and partID types is the position of that part in the survey, and is used
* in the UI for sorting columns.
*
* (Nb. we also have reportData.headers which is a map of { ref: { ref, label } })
*/
availableData() {
let d = [
{ ref: 'participantID', label: 'Participant ID' },
{ ref: 'participantLabel', label: 'Participant' }
{ ref: 'participantID', label: 'Participant ID', weight: 0},
{ ref: 'participantLabel', label: 'Participant', weight: 1}
];
return d.concat(this.query);
this.query.forEach((item, idx) => {
let weight = idx + 2;
d.push(Object.assign({weight}, item));
});
return d;
}
},
methods: {
......@@ -286,10 +294,12 @@ export default {
type: 'codeName',
ref: part.codeName,
codeName: part.codeName,
useLabel : false,
label: this.getPartLabel(part)
});
}
});
this.sortQuery();
},
editDataItem(i) {
this.datasetBeingEdited = i;
......@@ -305,12 +315,14 @@ export default {
this.query[this.datasetBeingEdited] = Object.assign({}, e);
}
this.dirty = true;
this.sortQuery();
}
this.datasetBeingEdited = null;
},
deleteDataItem(i) {
this.query.splice(i, 1);
this.dirty = true;
this.sortQuery();
},
/**
* Returns a promise
......@@ -370,12 +382,40 @@ export default {
// Load this report's data.
this.reportID = reportID;
this.query = savedReport.query || [];
this.sortQuery();
this.presentations = savedReport.presentations || [];
this.reportTitle = savedReport.title;
// Show the report data and run report.
this.selectedTab = 'report';
this.runReport();
},
sortQuery() {
let partIdx = {}, codeNameIdx = {}, atTheEnd = 2,
parts = this.survey ? JSON.parse(this.survey.definition).parts : [];
parts.forEach((part, idx) => {
partIdx[part.id] = idx + 2;
if (part.codeName) {
codeNameIdx[part.codeName] = idx + 2;
}
});
atTheEnd = parts.length + 2;
const itemToWeight = item => {
if (item.type === 'partID') {
return partIdx[item.partID] || 0;
}
else if (item.type === 'codeName') {
return codeNameIdx[item.codeName] || 0;
}
return atTheEnd++;
};
this.query.sort((aItem, bItem) => {
let a = itemToWeight(aItem),
b = itemToWeight(bItem);
return (a - b);
});
},
addPresentation(type) {
this.presentationNewType = type;
this.presentationBeingEdited = -1;
......
......@@ -20,6 +20,13 @@
<searchable-select :options="allCodeNames" v-model="codeName" @update:modelValue="codeNameChanged" label="Choose part by codename" />
</div>
<div class="input-wrapper" v-if="selectedPartUsesOptions">
<label>
<input type=checkbox v-model="useLabel" /> For option-based parts, use the label instead of the stored value
</label>
</div>
<div class="input-wrapper" v-if="type === 'expression'">
<label :for="myId + 'expression'">Expression</label>
<input v-model="expression" :id="myId + 'expression'" @input="dirty=true" type="text" required />
......@@ -71,12 +78,26 @@ export default {
partID: this.item.partID || null,
expression: this.item.expression || null,
codeName: this.item.codeName || null,
useLabel: this.item.useLabel || false,
ref: this.item.ref || null,
label: this.item.label || null,
};
},
emits: ['updated'],
computed: {
selectedPart() {
let part;
if (this.type === 'partID') {
part = this.surveyDef.value.parts.find(p => p.id === this.partID);
}
else if (this.type === 'codeName') {
part = this.surveyDef.value.parts.find(p => p.codeName === this.codeName);
}
return part || null;
},
selectedPartUsesOptions() {
return this.selectedPart ? this.selectedPart.dataType === 'options' : null;
},
expressionErrors() {
if (!this.expression) {
return;
......@@ -149,23 +170,21 @@ export default {
ref: this.ref,
label: this.label,
};
let part = null;
if (d.type === 'partID') {
d.partID = this.partID;
part = this.surveyDef.value.parts.find(p => p.id === d.partID);
}
else if (d.type === 'codeName') {
d.codeName = this.codeName;
part = this.surveyDef.value.parts.find(p => p.codeName === d.codeName);
}
else if (d.type === 'expression') {
d.expression = this.expression;
}
if (part && part.dataType === 'options') {
if (this.selectedPartUsesOptions) {
d.options = true;
d.multi = part.ask.inputType === 'checkboxes';
d.useLabel = this.useLabel;
d.multi = this.selectedPart.ask.inputType === 'checkboxes';
}
this.$emit('updated', d);
......
......@@ -21,9 +21,9 @@
</span>
<span class="actions">
&nbsp;
<a v-if="i > 0" href @click.prevent="columns.splice(i-1, 0, columns.splice(i, 1)[0]);dirty=true;" title="move up"></a>
<a v-if="i > 0" href @click.prevent="moveColumn(i, -1)" title="move up"></a>
&nbsp;
<a v-if="i < columns.length-1" href @click.prevent="columns.splice(i+1, 0, columns.splice(i, 1)[0]);dirty=true;" title="move down"></a>
<a v-if="i < columns.length-1" href @click.prevent="moveColumn(i, 1)" title="move down"></a>
&nbsp;
<a href @click.prevent="columns.splice(i, 1);dirty=true;" >Delete</a>
</span>
......@@ -33,6 +33,7 @@
<ul>
<li v-for="r in refsExcept(columns)" :key="r"><a href @click.prevent="columns.push(r.ref);dirty=true;" ><code>{{r.ref}}</code> {{r.label}}</a></li>
</ul>
<a href @click.prevent="sortCols" title="Sort columns by their position in the survey">Sort columns</a>
</div>
<div v-if="type === 'piechart'">
......@@ -127,7 +128,7 @@ export default {
inject: ['getNextId'],
props: {
config: Object,
/** Array of objects with { ref, label } */
/** Array of objects with { ref, label, weight } */
availableRefs: Object,
survey: Object,
},
......@@ -163,7 +164,6 @@ export default {
let x = [];
this.availableRefs.forEach(item => {
x.push(item);
console.log(item);
if (item.options) {
x.push({
ref: item.ref + ':options',
......@@ -196,6 +196,17 @@ export default {
},
},
methods: {
moveColumn(i, dir) {
this.columns.splice(i + dir, 0, this.columns.splice(i, 1)[0]);
this.dirty = true;
},
sortCols() {
this.columns.sort((aRef, bRef) => {
let a = this.availableRefsIndex[aRef],
b = this.availableRefsIndex[bRef];
return (a.weight || 0) - (b.weight || 0);
});
},
handleChangingBarchartX() {
if (this.x.match(/:options$/)) {
this.y = ':count';
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment