I'm using knex and this is my code to get a question_info object after requesting from the database.
let question_info = knex.select('id', 'question', 'pre_question', 'response_options_id').from('_questions').whereNot({'progress': 100}).andWhere({'school_id': school_id}).orderBy('date_created', 'asc')
.map((question) => {
//going through all possible questions
helpers.debug("Question", question);
//checking if there are responses
return knex('_responses').where({'question_id': question.id, 'student_id': event.user.id}).first()
.then((response) => {
//if no responses, return question_info object
if(!response){
return knex.select('options_json').from('_response_options').where({'id': question.response_options_id}).first()
.then((options) => {
return {
question_id: question.id,
question: question.question,
pre_question: question.pre_question,
response_options: JSON.parse(options.options_json)
}
});
}
});
}).catch((e) => {
console.error('Error', e);
});
When the first question has already been answered, my return value shows up as null. How do I just have the returned array without null using knex correctly.
[
null,
{
"question_id": "2",
"question": "What is second question?",
"pre_question": "Hey!!",
"response_options": [
{
"title": "Yes",
"value": "Yes"
},
{
"title": "Maybe",
"value": "Maybe"
},
{
"title": "No",
"value": "No"
}
]
},
{
"question_id": "3",
"question": "Third Question?",
"pre_question": "Yo",
"response_options": [
{
"title": "Yes",
"value": "Yes"
},
{
"title": "Maybe",
"value": "Maybe"
},
{
"title": "No",
"value": "No"
}
]
}
]
Looks like your problem is that if second query indeed did return response you are not returning anything.
// this part of your code seems to be wrong
if (!response) {
// return choices because there was no response found
return knex('choices')...
}
It should be something like this:
// this part of your code seems to be wrong
if (!response) {
// return choices because there was no response found
return knex('choices')...
} else {
return response;
}
EDIT this would be more or less how to do the same stuff with single query and joins (I didn't test this so it probably doesn't work, but it should give general idea how to achieve it):
let question_info = knex('_questions as q')
.select(
'id',
'question',
'pre_question',
'response_options_id',
'r.id as r_id',
'options_json'
)
.join('_responses as r', builder => {
builder
.on('q.id', 'r.question_id')
.onVal('r.student_id', event.user.id);
})
.join('_response_options as ro', 'q.response_options_id', 'ro.id')
.where('q.school_id', school_id)
.whereNot('q.progress', 100)
.whereNull('r.id')
.orderBy('q.date_created', 'asc')
.map(notAnsweredQuestion => {
return {
question_id: notAnsweredQuestion.id,
question: notAnsweredQuestion.question,
pre_question: notAnsweredQuestion.pre_question,
response_options: JSON.parse(notAnsweredQuestion.options_json)
};
}).catch((e) => {
console.error('Error', e);
});
Related
I have a source.json file, and then another file of changes which I parse, and need to write out the array back to the source file. Here is the source.json structure:
{
"users" : [
{
"id" : "1",
"name" : "Albin Jaye"
}
],
"playlists" : [
{
"id" : "1",
"owner_id" : "2",
"song_ids" : [
"8",
"32"
]
}
],
"songs": [
{
"id" : "1",
"artist": "Camila Cabello",
"title": "Never Be the Same"
}
]
}
Here is the changes.json structure. I have a CRUD like "add" directive and the "payload" which is the entry to be added to the source file:
{
"users": [{
"action": "add",
"payload": [{
"id": "1",
"name": "Albin Jaye"
},
{
"id": "2",
"name": "Dave Mustaine"
}
]
}],
"playlist": [{
"action": "add",
"payload": [{
"id": "1",
"owner_id": "2",
"song_ids": [
"8",
"32"
]
}]
}],
"songs": [{
"action": "add",
"payload": [{
"id": "1",
"artist": "Camila Cabello",
"title": "Never Be the Same"
}]
}]
}
I have written this code in Node to read in the source file and parse and then read in the changes file. Once the changes file is read, I iterate over and see what "CRUD" directive is there. When I find "add", I then push the payload to an array. After I get finished, when I console log out I get this:
[
[ 'users', '[object Object],[object Object]' ],
[ 'playlists', '[object Object]' ],
[ 'songs', '[object Object]' ]
]
This is swell. BUT, I need now to take this result and Stringify it I think and place it back into the source JSON file in the appropriate place. I think it will be some type of ```fs.appendFile()`` function, but not sure. Here is my code so far:
// Stream in source file
fs.readFile('./' + inputFile, (err, data) => {
if (err) throw err;
let input = JSON.parse(data);
//console.log(input);
});
// Stream in changes file
fs.readFile('./' + changesFile, 'utf-8', (err, jsonString) => {
if (err) {
console.log(err);
} else {
try {
const data = JSON.parse(jsonString);
const array = [];
Object.entries(data).map(([key, [{ action, payload }]]) => {
switch (key) {
case 'users': {
if (action === 'add') {
console.log("it's an add");
array.push([`${key}`, `${payload}`]);
}
break;
}
case 'playlists': {
if (action === 'add') {
console.log("it's an add");
array.push([`${key}`, `${payload}`]);
}
break;
}
case 'songs': {
if (action === 'add') {
console.log("it's an add");
array.push([`${key}`, `${payload}`]);
}
break;
}
}
});
console.log(array);
} catch (err) {
console.log('Error parsing JSON', err);
}
}
});
// after we have merged changes and source we need to write out
// Don't know how to merge the changes array I created above back into the
// source.json file?
fs.appendFile('./' + outputFile, JSON.stringify(array, null, 2), err => {
if (err) {
console.log(err);
} else {
console.log('File sucessfully written');
}
});
I'm not 100% sure where you're going with your implementation, but I think a better way to go about this would be to update the original data object with the changes, and then write the changes out to a file:
const data = JSON.parse(fs.readFileSync(inputFile));
const changes = JSON.parse(fs.readFileSync(changesFile));
for(const [table, actions] of Object.entries(changes)){
if(!(table in data))continue;
for(const actionDetail of actions){
if(actionDetail.action === 'add'){
data[table].push(...actionDetail.payload);
}
}
}
fs.writeFileSync(inputFile, JSON.stringify(data));
there is a list of users
filterData = [
{
"position":"lawyer",
"department_positions":[],
"group_positions":[
{"group":{"id":2,"code":"234","name":"group1"},"lead":false},
{"group":{"id":1,"code":"123","name":"group12"},"lead":true}
]
},
{
"position":"director",
"department_positions":[
{"department":{"id":3,"code":"333","name":"subDep"},"lead":false}
],
"group_positions":[
{"group":{"id":2,"code":"234","name":"group1"},"lead":false},
{"group":{"id":1,"code":"123","name":"group12"},"lead":true}
]
},
{
"position":"director",
"department_positions":[],
"group_positions":[]
}
]
and list of filters
categories = {
"position":["lawyer","director"],
"group_positions":["group1","group12"],
"department_positions":["generalDep", "subDep"]
}
It is necessary to filter users taking into account the fact that several filters can be selected at the same time. For example, i want to find user with position = "director" and AND group_positions = "group1" AND department_positions = "subDep"
my code doesn't allow filtering by multiple conditions. how can i fix it?
this.filter = this.filterData.filter(item => {
for (let key in this.categories) {
if (item[key].find(el =>
this.categories[key].includes(
el.group?.name || el.department?.name
)
)) {
return true
}
}
return false
})}
This is a good place to employ an es6 class to give behavior to the object being filtered. Augment each object to determine if it matches the "category" object.
(from the example data, this assumes the OP is looking for a "product of sums" match: for all of the category keys match at least one of the category values)
class FilterMe {
constructor(item) {
Object.assign(this, item);
}
namesForKey(key) {
switch (key) {
case 'position':
return [this.position]; // always answer an array
case 'group_positions':
return this.group_positions.map(gp => gp.group.name);
case 'department_positions':
return this.department_positions.map(dp => dp.department.name);
default:
return [];
}
}
// return true if a single filter key-value pair is matched
matchesFilterKeyValue(filterKey, filterOptions) {
const myNames = this.namesForKey(filterKey);
const matches = filterOptions.filter(e => myNames.includes(e));
return matches.length > 0;
}
// return true if all filter key-values pairs are matched
matchesFilter(filter) {
return Object.entries(filter).every(keyValue => {
return this.matchesFilterKeyValue(...keyValue);
})
}
}
const filterData = [{
"position": "lawyer",
"department_positions": [],
"group_positions": [{
"group": {
"id": 2,
"code": "234",
"name": "group1"
},
"lead": false
}, {
"group": {
"id": 1,
"code": "123",
"name": "group12"
},
"lead": true
}]
},
{
"position": "director",
"department_positions": [{
"department": {
"id": 3,
"code": "333",
"name": "subDep"
},
"lead": false
}],
"group_positions": [{
"group": {
"id": 2,
"code": "234",
"name": "group1"
},
"lead": false
}, {
"group": {
"id": 1,
"code": "123",
"name": "group12"
},
"lead": true
}]
},
{
"position": "director",
"department_positions": [],
"group_positions": []
}
]
const categories = {
"position": ["lawyer", "director"],
"group_positions": ["group1", "group12"],
"department_positions": ["generalDep", "subDep"]
}
// convert the filterData to the objects and test them...
let objects = filterData.map(d => new FilterMe(d));
let matches = objects.filter(o => o.matchesFilter(categories))
console.log(matches)
You can try something like this:
let filtered = example.filter(item => {
let valid = false
if (item.includes('something')) {
valid = true
}
if (!valid) {
// check second condition
}
return valid
})
Use a temporary placeholder so you don't immediately have to return true/false.
I have a bit of a problem with building an object in my Next.JS application. And it's most likely because of my lacking JavaScript and React competences.
I have a form that is rendered from an api call. It returns an array of objects like so:
[
{
"formId": "uuid",
"description": "Description of form",
"questions": [
{
"questionId": "uuid",
"text": "question 1?",
"alternatives": [
{
"text": "No",
"ratingValue": 1
},
{
"text": "Yes",
"ratingValue": 5
}
]
}
]
}]
The object will contain multiple forms and each form can contain multiple questions.
I pass the value of each question into a function called pushAnswer in my component like this:
<Form.Check
type="radio"
name={question.questionId}
value={alternative.ratingValue}
onChange={event => {
pushAnswer(form.formId, question.questionId, event.target.value)
}} />
I have state variable const [formBody, setFormBody] = useState({form:[]})
And the pushAnswer function looks like this:
const pushAnswer = (formId, questionId, answerValue) => {
const currentForm = formBody;
let answeredForm = currentForm.forms.find(form => form.formId === formId);
// if the form ID doesn't exist, add it
if (!answeredForm) {
answeredForm = {
formId,
answers: []
}
}
// check if the questionId exists
let answeredQuestion = answeredForm.answers.find(answer => answer.questionId === questionId)
if (!answeredQuestion) {
answeredQuestion = {
questionId,
rating: answerValue
}
}
answeredQuestion.rating = answerValue;
setFormBody(oldForm => ({
...oldForm,
forms: [
{
formId: answeredForm.formId,
questions: [
{
questionId: answeredQuestion.questionId,
rating: answeredQuestion.rating
}
]
}
]
}))
}
I want to produce an answer like this:
{
"forms": [
{
"formId": "2b945644-a9c3-473e-afac-1236bc1575ce",
"questions": [
{
"questionId": "289a9e8a-a607-464a-8939-48223819f413",
"rating": "1"
},
{
"questionId": "another uuid",
"rating": "5"
}
]
},
{
"formId": "another uuid",
"questions": [
{
"questionId": "another uuid",
"rating": "5"
}
]
}
]
}
The first question gets added to formBody, but when I try to get the values of another question I get an error at let answeredQuestion = answeredForm.answers.find(answer => answer.questionId === questionId) that says Cannot read property 'find' of undefined.
Can anyone advice how I can solve this?
You are not saving the correct value in the state, it should be
const [formBody, setFormBody] = useState({forms:[]})
setFormBody(oldForm => ({forms: [
...oldForm.forms,
[
{
formId: answeredForm.formId,
questions: [
{
questionId: answeredQuestion.questionId,
rating: answeredQuestion.rating
}
]
}
]
]}))
I want to return true if all the product contain Done in the statusLog array. If any product does not contain Done in the statusLog then it should return false. Some product may not have statusLog property which mean it should return false.
The code seem to work fine, but I felt there must be a better way to refactor this. If "Done" not found from the first product then it should skip the loop as it no need to keep looping. How can that be done?
data={"id":123,"products":[{"id":1,"name":"Item 1","statusLog":[{"name":"New"},{"name":"Done"}]},{"id":2,"name":"Item 2","statusLog":[{"name":"New"},{"name":"Done"}]},{"id":3,"name":"Item 3","statusLog":[{"name":"Pending"},{"name":"Dones"}]},]}
var hasDone = 0;
data.products.forEach((product) => {
if (product.statusLog) {
if (product.statusLog.some((status) => {
return status.name == "Done"
})) {
hasDone++
}
}
});
if (hasDone != data.products.length) {
console.log("All products has Done Status")
}
Demo: https://jsfiddle.net/bdrve3xs/18/
You can use Array.prototype.every
const allHaveDone = data.products.every(
product => (product.statusLog || []).some( status => status.name === "Done" )
);
if ( allHaveDone ) {
console.log("All products has Done Status")
}
You could use filter and check its length.
When done like this, you also get the "not done" ones in return.
Stack snippet
data = {
"id": 123,
"products": [{
"id": 1,
"name": "Item 1",
"statusLog": [{ "name": "New" }, { "name": "Done" }]
},
{
"id": 2,
"name": "Item 2",
"statusLog": [{ "name": "New" }, { "name": "Done" }]
},
{
"id": 3,
"name": "Item 3",
"statusLog": [{ "name": "Pending" }, { "name": "Dones" }]
},
]
}
var not_done = data.products.filter(p => {
if (!p.statusLog || !p.statusLog.some(s => s.name === "Done")) return p;
});
if (not_done.length > 0) {
console.log('These/This is not "Done" yet', not_done)
}
I'm working with a form built dynamically in JavaScript from a JSON schema that looks like this:
{
"questionSets": [
{
"questionSetId": "example-fields",
"questions": [
{
"questionId": "text",
"question": "Text Field",
"input": {
"type": "textInput",
"default": ""
},
},
{
"questionId": "textarea",
"question": "Text Area",
"input": {
"type": "textareaInput",
"default": ""
}
}
]
}
]
}
When the form is submitted it just returns the updated values that look like this:
{
text: "some entered text",
textarea: "some more entered text"
}
The keys of this resulting JSON array correspond with the questionId and the value with the default key in the first array.
What's the best way to go about merging these 2 arrays so the result is:
{
"questionSets": [
{
"questionSetId": "example-fields",
"questions": [
{
"questionId": "text",
"question": "Text Field",
"input": {
"type": "textInput",
"default": "some entered text"
},
},
{
"questionId": "textarea",
"question": "Text Area",
"input": {
"type": "textareaInput",
"default": "some more entered text"
}
}
]
}
]
}
That's a tricky one. The simplest way is using underscore. Let reply be your input object, and defaultInputs the object with the default inputs to be filled out in the JSON.
'use strict';
let _ = require('underscore');
module.exports = function (defaultInputs, reply) {
reply.questionSets = _.map(reply.questionSets, questionSet => {
questionSet.questions = _.map(questionSet.questions, question => {
question.input.default = _.find(defaultInputs,(item, key) => (
new RegExp(`${key}Input`).test(question.input.type) && item
) || false) || '';
return question;
});
return questionSet;
});
return reply;
};
A proper code solution (including the test) can be found here.
UPDATE (07/01/2018)
Now it's possible to achieve the same via the new Array prototype functions like map and filter
'use strict';
module.exports = function (defaultInputs, reply) {
reply.questionSets = reply.questionSets.map(questionSet => {
questionSet.questions = questionSet.questions.map(question => {
question.input.default = defaultInputs.filter((item, key) => (
new RegExp(`${key}Input`).test(question.input.type) && item
) || false) || '';
return question;
});
return questionSet;
});
return reply;
};