Is it possible to optionally include parameters when creating a Relay query?
See below I'm querying resources, and I'm hard coding the params here.
I would like to conditionally include some of these arguments, for example date_gt and date_lt. At the moment I have to set initial values for these, but that falls apart as I need to query for records without dates at all.
If I can't do that is it possible to send null as a value to a param here because I'm not having much luck with that either at the moment.
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
resources(
first: $pageSize
q: $q
type: $types
license: $licenses
order: $order
access_rights: "published"
orphan: true
date_gt: $dateFrom
date_lt: $dateTo
)
{
total
edges {
node {
${ArticleResult.getFragment('resource')}
}
}
pageInfo {
hasNextPage
}
}
}
`
},
Yes, we can provide null as the value for an optional argument in Relay (client-side).
However, we can also provide default values for those optional arguments. For example, in the GraphQL schema, the field resources can be like:
resources: {
type: ResourceConnection,
args: {
// other args go here
date_gt: {
type: GraphQLString,
defaultValue: '1970-01-01'
},
date_lt: {
type: GraphQLString,
defaultValue: '2030-12-31'
},
...connectionArgs
},
resolve: async (root, {...otherArgs, date_gt, date_lt, ...args}) => {
// Check date_gt and/or date_lt values. If they equal the default
// invalid values, ignore them while calculating output. Sometimes it
// is possible that the default values are good enough to be equivalent
// of user-provided values.
return output;
},
},
If we do not provide a default value for an optional argument, we can set value of the optional argument to null. In that case, undefined value is received on the server side:
resources: {
type: ResourceConnection,
args: {
// other args go here
date_gt: {
type: GraphQLString,
},
date_lt: {
type: GraphQLString,
},
...connectionArgs
},
resolve: async (root, {...otherArgs, date_gt, date_lt, ...args}) => {
console.log(`date_gt: ${date_gt}, date_lt: ${date_lt}`);
// Check date_gt and/or date_lt values. If they are not provided,
// date_gt and date_lt are `undefined`. Calculate output accordingly.
return output;
},
},
Related
Cant figure out what I'm missing, and I havent had this issue before on any of my other updates. I expanded a collection and want to be able to update certain fields depending on where in the app the user is interacting. I've had no issue working with subdocs using separate calls, but with this particular nested field I'm getting no errors, and getting the correct document returned without the update. (I have another nested field that is updating fine - "personalInfo" while the "medical" field is the one giving me trouble)
The model looks like this:
const clientSchema = new Schema({
fullName: String,
firstName: String,
lastName: String,
enrollment: {
enrolled: Boolean,
enrollDates: [
{
begin: Date,
end: Date
}
]
},
personalInfo: {
dateOfBirth: Date,
phone: String,
email: String,
address: {
addressLineOne: String,
addressLineTwo: String,
city: String,
state: String,
zip: String
},
regionalCenter: String,
serviceCoordinator: String,
serviceCoordinatorPhone: String,
rcId: String,
emergencyContact: String,
livingSituation: String,
ihss: {
provider: String,
hours: Number,
services: String
}
},
medical: {
primaryIns: String,
primaryInsId: String,
secondaryIns: String,
secondaryInsId: String,
hasMediCal: Boolean,
mediCalId: String,
mediCalEnroll: Date,
hasMedicare: Boolean,
medicareId: String,
medicareEnroll: Date,
logs: {type: [logSchema], default: []},
},
contracts: {type: [contractSchema], default: []},
visits: [{ type: Schema.Types.ObjectId, ref:'Visit' }],
users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
servicePlans: [{ type: Schema.Types.ObjectId, ref: 'ServicePlan'}],
currentPlan: String,
income: {type: [incomeSchema], default: []},
contacts: {type: [contactSchema], default: []}
}
The route:
router.route("/clients/:clientId").patch(updateClient)
And the controller... since I want to keep the controller as restful as possible, but conditionally set the fields depending on the api call, I conditionally set the different aspects and then pass in the body an additional field to tell the controller which aspect to update (so the personalInfo section has a field "personalInfo": "personalInfo" and the medicalInfo field has its own. The personalInfo object updates fine (I commented out the initial line since it was stated in another post that these calls work better doing a findOneAndUpdate- but that hasnt yielded any progress, and the personalInfo update worked without issue).
exports.updateClient = async (req, res) => {
try {
//const client = await Client.findOne({ _id: req.params.clientId })
if (req.body.firstName) {
client.firstName = req.body.firstName
}
if (req.body.lastName) {
client.lastName = req.body.lastName
}
if (req.body.personalInfo === 'personalInfo') {
client.updateOne({$set: {personalInfo: req.body}}, {new: true}, function(err, updatedDoc){
if(err){
console.log("error updating personal info: ", err)
}
})
}
if (req.body.enrollment === 'enrollment') {
client.updateOne({$set: {enrollment: req.body}}, {new: true}, function(err, updatedDoc){
if(err){
console.log("error updating personal info: ", err)
}
})
}
if(req.body.medicalInfo === 'medicalInfo'){
console.log("medInfo: ", req.body)
let clientId = req.params.clientId
// const client = await Client.findById(clientId)
// console.log("Client ", client)
// client.medical.set(req.body)
Client.findById(clientId)
.then((client) => {
client.medical.set(req.body
// hasMediCal: req.body.hasMediCal,
// hasMedicare: req.body.hasMedicare,
// mediCalId: req.body.mediCalId,
// medicareId: req.body.medicareId,
// mediCalEnroll: req.body.mediCalEnroll,
// medicareEnroll: req.body.medicareEnroll,
// primaryIns: req.body.primaryIns,
// primaryInsId: req.body.primaryInsId,
// secondaryIns: req.body.secondaryIns,
// secondaryInsId: req.body.secondaryInsId
);
client.save();
res.send(client)
})
// Client.findOneAndUpdate(
// { _id: req.params.clientId},
// {$set: {medical: req.body}},
// {new: true},
// function(err, updatedDoc){
// if(err){
// console.log("error updating personal info: ", err)
// }
// })
// client.markModified('medical');
}
// await client.save()
// res.send(client)
} catch (error) {
res.status(404)
res.send({ error: "Client not updated: ", error})
}
}
Finally, the body being sent:
{
"hasMediCal": false,
"hasMedicare": false,
"mediCalEnroll": "2005-04-22T08:00:00",
"mediCalId": "91234567A",
"medicalInfo": "medicalInfo",
"medicareEnroll": "2005-04-03T08:00:00",
"medicareId": "9FHS-ASU-95F8",
"primaryIns": "Molina",
"primaryInsId": "91234567A",
"secondaryIns": "SilverScript - Rx",
"secondaryInsId": "08dfA8d8"
}
Whether I've tried findOneAndUpdate, or findOne and then setting the field on the result, or setting each subfield in the object specifically, I keep getting the correct document returned, just not updated, and with no errors. I thought possibly it was because I was attempting to set the update within the conditionals, so I created a separate update controller but that got the same results as well. Really lost as how else to pursue this.
Please let me know if you see anything missing or where I'm going wrong. Much appreciated.
So after running around on this for hours, I came to a working solution, which essentially is no different, other than setting the query as a variable rather than writing it out. If anyone has any guess as to why this works when the multiple other methods didnt, I'd be grateful for your thoughts.
if(req.body.medicalInfo === 'medicalInfo'){
console.log("medInfo: ", req.body)
let clientId = req.params.clientId
let query = {_id: clientId};
Client.findOneAndUpdate(query, {$set: {medical: req.body}}, {new: true, upsert: true}, function(err, doc){
if(err) return res.status(500).send( {error:err});
return res.send(doc)
})
}
We use multiple rollup-plugins that parse their input to an AST. As they run on the same files, each file is parsed multiple times. Can this be optimized, so that each file is parsed only once? Minimal example:
// rollup.config.js
import {createFilter} from '#rollup/pluginutils';
import {simple} from 'acorn-walk';
import {attachComments} from 'astravel';
import {generate} from 'astring';
export default {
input: 'src/main.js',
output: {file: 'bundle.js', format: 'cjs'},
plugins: [{
name: 'plugin1',
transform(code, id) {
const comments = [];
const ast = this.parse(code, {onComment: comments});
attachComments(ast, comments);
simple(ast, {
Identifier(n) {
// rewrite wrong to right
if (n.name === 'wrong') n.name = 'right';
}
});
return {
code: generate(ast, {comments: true}),
ast,
map: null /* minimal example, won't create a source map here */
};
}
}, {
name: 'plugin2',
transform(code, id) {
const comments = [];
const ast = this.parse(code, {onComment: comments});
attachComments(ast, comments);
simple(ast, {
CallExpression(n) {
// rewrite mylog(...) to console.log(...)
if (n.callee.type === 'Identifier' && n.callee.name === 'mylog') {
n.callee = {
type: 'MemberExpression',
object: {type: 'Identifier', name: 'console', start: n.start, end: n.end},
property: {type: 'Identifier', name: 'log', start: n.start, end: n.end},
computed: false,
start: n.start,
end: n.end
}
}
}
});
return {
code: generate(ast, {comments: true}),
ast,
map: null /* minimal example, won't create a source map here */
};
}
}]
};
Now I understand that transform() can return an AST, so that parsing doesn't have to happen twice. And I understand that this.parse() uses the rollup-internal acorn instance. My simple mind thought that this.parse() could return the AST created by previous transform() calls, if available. But I assume that all sorts of demons await on that road, e.g. when this.parse() was called with different options.
Is there a different way achieve what I described? A different hook maybe?
I would love to not have all plugins in one and switching them on and off via options (I see that this would be a solution, but a really cumbersome one).
I couldn't find the answer anywhere.
Let's say we have Vuex store with the following data:
Vuex store
state: {
dialogs: {
dialogName1: {
value: false,
data: {
fileName: '',
isValid: false,
error: '',
... 10 more properties
}
},
dialogName2: {
value: false,
data: {
type: '',
isValid: false,
error: '',
... 10 more properties
}
}
}
}
Dialogs.vue
<div v-if="dialogName1Value">
<input
v-model="dialogName1DataFileName"
:error="dialogName1DataIsValid"
:error-text="dialogName1DataError"
>
<v-btn #click="dialogName1Value = false">
close dialog
</v-btn>
</div>
<!-- the other dialogs here -->
Question
Let's say we need to modify some of these properties in Dialogs.vue.
What's the best practices for creating a getter and setter for every dialog property efficiently, without having to do it all manually like this:
computed: {
dialogName1Value: {
get () {
return this.$store.state.dialogs.dialogName1.value
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.value', value: value })
}
},
dialogName1DataFileName: {
get () {
return this.$store.state.dialogs.dialogName1.data.fileName
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.fileName', value: value })
}
},
dialogName1DataIsValid: {
get () {
return this.$store.state.dialogs.dialogName1.data.isValid
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.isValid', value: value })
}
},
dialogName1DataIsError: {
get () {
return this.$store.state.dialogs.dialogName1.data.error
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.error', value: value })
}
},
... 10 more properties
And this is only 4 properties...
I suppose I could generate those computed properties programmatically in created(), but is that really the proper way to do it?
Are there obvious, commonly known solutions for this issue that I'm not aware of?
getters can be made to take a parameter as an argument - this can be the 'part' of the underlying state you want to return. This is known as Method-style access. For example:
getFilename: (state) => (dialogName) => {
return state.dialogs[dialogName].data.fileName
}
You can then call this getter as:
store.getters.getFilename('dialogName1')
Note that method style access doesn't provide the 'computed property' style caching that you get with property-style access.
For setting those things in only one central function you can use something like this:
<input
:value="dialogName1DataFileName"
#input="update_inputs($event, 'fileName')">
// ...
methods:{
update_inputs($event, whichProperty){
this.$store.commit("SET_PROPERTIES", {newVal: $event.target.value, which:"whichProperty"})
}
}
mutation handler:
// ..
mutations:{
SET_PROPERTIES(state, payload){
state.dialogName1.data[payload.which] = payload.newVal
}
}
Let me explain more what we done above. First we change to v-model type to :value and #input base. Basically you can think, :value is getter and #input is setter for that property. Then we didn't commit in first place, we calling update_inputs function to commit because we should determine which inner property we will commit, so then we did send this data as a method parameter (for example above code is 'fileName') then, we commit this changes with new value of data and info for which property will change. You can make this logic into your whole code blocks and it will solved your problem.
And one more, if you want to learn more about this article will help you more:
https://pekcan.dev/v-model-using-vuex/
I always get Syntax Error: Unterminated string when I try to update my database using javascript strapi sdk. this.chapter.content is a html string generated by ckeditor. How can I escape this string to update my database using graphql?
async updateChapter() {
const q = `
mutation {
updateChapter(input: {
where: {
id: "${this.$route.params.chapterId}"
},
data: {
content: "${this.chapter.content.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/(?:\r\n|\r|\n)/g, '\n')}"
title: "${this.chapter.title}"
}
}) {
chapter{
title
id
content
}
}
}
`;
const res = await strapi.request("post", "/graphql", {
data: {
query: q
}
});
this.chapter = res.data.chapter;
}
Technically you could use block string notation to get around this issue. However, you really should supply dynamic input values using variables instead of string interpolation. This way you can easily provide any of sort of values (strings, numbers, objects, etc.) and GraphQL will parse them accordingly -- including strings with line breaks.
const query = `
mutation MyMutation ($chapterId: ID!, $content: String!, $title: String!) {
updateChapter(input: {
where: {
id: $chapterId
},
data: {
content: $content
title: $title
}
}) {
chapter{
title
id
content
}
}
}
`
const variables = {
chapterId: '...',
content: '...',
title: '...',
}
const res = await strapi.request("post", "/graphql", {
data: {
query,
variables,
},
})
Note that $chapterId may need to be of the type String! instead if that's what's called for in the schema. Since variables can also be input object types, instead of providing 3 different variables, you could also provide a single variable to be passed to the input argument instead:
const query = `
mutation MyMutation ($input: SomeInputObjectTypeHere!) {
updateChapter(input: $input) {
chapter{
title
id
content
}
}
}
`
const variables = {
input: {
where: {
id: '...',
},
data: {
content: '...',
title: '...',
},
},
}
Again, just replace SomeInputObjectTypeHere with the appropriate type in your schema.
Another solution maybe help
Code with issue: For example mainReason and actionTaken fields are text inputs and data contains some white spaces. This action give error: Unterminated string
mutation { updateApplicationForm(input:{ where:{id:"${ticketData.id}"}
data:{
mainReason: "${ticketData.mainReason}"
actionTaken: "${ticketData.actionTaken}"
appStatus: ${ticketData.appStatus}
action: "${ticketData.action}"
}
Fix this problem with JSON.stringify method
mutation { updateApplicationForm(input:{ where:{id:"${ticketData.id}"}
data:{
mainReason:${JSON.stringify(ticketData.mainReason)}
actionTaken:${JSON.stringify(ticketData.actionTaken)}
appStatus: ${ticketData.appStatus}
action: "${ticketData.action}"
}
I need to have the name field of a model be virtual, created by concatenating two real fields together. This name is just for display only. I've tried the virtual examples in the doc, no luck. Keystone 4 beta5.
var keystone = require('keystone')
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.schema.virtual('fooname').get(function() {
return this.bar+ ' ' + this.order;
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
When I use this model definition, I don't see the virtual name in the defaultcolumns list. I want to make a virtual name so lookups are easier when this model is used as a relationship.
You don't need a virtual to do this. Keystone allows you to track and recalculate a field every time the document is saved. You can enable those options in order to create a function which concatenates these two values for you (either synchronously or asynchronously, your choice.)
One other thing I noticed is that bar is a Relationship, which means you will need to populate that relationship prior to getting any useful information out of it. That also means your value function will have to be asynchronous, which is as simple as passing a callback function as an argument to that function. Keystone does the rest. If you don't need any information from this bar, and you only need the _id (which the model always has), you can do without the keystone.list('Bar') function that I included.
http://keystonejs.com/docs/database/#fields-watching
The map object also refers to an option on your model, so you'll need a fooname attribute on your model in any scenario, though it gets calculated dynamically.
var keystone = require('keystone'),
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
fooname: { type: Types.Text, watch: true, value: function (cb) {
// Use this if the "bar" that this document refers to has some information that is relevant to the naming of this document.
keystone.list('Bar').model.findOne({_id: this.bar.toString()}).exec(function (err, result) {
if (!err && result) {
// Result now has all the information of the current "bar"
// If you just need the _id of the "bar", and don't need any information from it, uncomment the code underneath the closure of the "keystone.list('Bar')" function.
return cb(this.bar.name + " " + this.order);
}
});
// Use this if you don't need anything out of the "bar" that this document refers to, just its _id.
// return cb(this.bar.toString() + " " + this.order);
} },
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
try this:
Foo.schema.pre('save', function (next) {
this.name = this.bar+ ' '+ this.order;
next();
});
Could you provide more information? What is currently working? How should it work?
Sample Code?
EDIT:
After creating the model Foo, you can access the Mongoose schema using the attribute Foo.schema. (Keystone Concepts)
This schema provides a pre-hook for all methods, which registered hooks. (Mongoose API Schema#pre)
One of those methods is save, which can be used like this:
Foo.schema.pre('save', function(next){
console.log('pre-save');
next();
});