I recently made an integration between Todoist and Notion. The main idea is that every time I do something with a task on Todoist, the changes are reflected on Notion. I'm encountering a problem with the notion.pages.update() function: every time I modify a task, it returns this error:
body failed validation. Fix one: body.properties.body.id should be defined, instead was undefined. body.properties.body.name should be defined, instead was undefined. body.properties.body.start should be defined, instead was undefined.
I don't even have a body property in my database.
Here's my update function, keep in mind that this is a function inside a module I made:
my.updateTask = async function (notion_page_id, name, todoist_project_id, do_date, priority, in_discord) {
var req_body = {
page_id: notion_page_id,
properties: {
Name: {
type: "title",
title: [
{
type: "rich_text",
rich_text: {
content: name
}
}
],
},
Project: {
type: "relation",
relation: [
{
database_id: my.getProject("todoist_id", todoist_project_id).notion_id
}
]
},
Priority: {
number: priority
},
isOnDiscord: {
checkbox: in_discord
}
}
}
if (typeof do_date !== 'undefined' && do_date !== null) {
start_dt = new Date(do_date.date);
end_dt = new Date(do_date.date);
end_dt.setHours(end_dt.getHours() + 24);
req_body.properties.DoDate = {
date: {
start: start_dt.toISOString().replace(/Z$/, '+02:00'),
end: end_dt.toISOString().replace(/Z$/, '+02:00')
}
}
}
const response = await my.api.pages.update(req_body);
return response !== {};
}
Any help is highly appreciated!
I think your Name property should look like this:
Name: {
title: [
{
text: { content: name }
}
]
}
You can see that here in the docs: https://developers.notion.com/reference/patch-page
I solved it, the problem was actually in another function (the getProject function)
Related
Let's say this is my graphql query:
mutation Test ($input: UpdateUserAccountInput!) {
updateUserAccount(input: $input) {
... on UpdateUserAccountPayload {
userAccount {
name
}
}
}
}
I want to modify to have the following fragment:
... on Error {
message
}
I was able to figure out that I can get AST using parse from graphql package, i.e.
import {
parse,
gql,
} from 'graphql';
parse(gql`
mutation Test ($input: UpdateUserAccountInput!) {
updateUserAccount(input: $input) {
... on UpdateUserAccountPayload {
userAccount {
name
}
}
}
}
`)
Now I am trying to figure out how to add ... on Error { message } to this query.
The problem that I am trying to solve is that my tests sometimes quietly fail because mutation returns an error that I did not capturing. I am extending my GraphQL test client to automatically request errors for every mutation and throw if error is returned.
I assume there exists some utilities that allow me to inject fields into AST, but so far I was not able to find them.
I think I figured it out.
Here is my solution:
const appendErrorFieldToMutation = (query: string) => {
const inlineErrorFragmentTemplate = {
kind: 'InlineFragment',
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: {
kind: 'Name',
value: 'message',
},
},
],
},
typeCondition: {
kind: 'NamedType',
name: {
kind: 'Name',
value: 'Error',
},
},
};
const document = parseGraphQL(query);
if (document.definitions.length !== 1) {
throw new Error('Expected only one definition');
}
const definition = document.definitions[0] as OperationDefinitionNode;
if (definition.operation !== 'mutation') {
return query;
}
if (definition.selectionSet.selections.length !== 1) {
throw new Error('Expected only one document selection');
}
const documentSelection = definition.selectionSet.selections[0] as InlineFragmentNode;
const errorField = documentSelection.selectionSet.selections.find((selection) => {
return (selection as InlineFragmentNode).typeCondition?.name.value === 'Error';
});
if (!errorField) {
// #ts-expect-error – Intentionally mutating the AST.
documentSelection.selectionSet.selections.unshift(inlineErrorFragmentTemplate);
}
return printGraphQL(document);
};
I've not used any utilities, so perhaps there is a smarter way to do the same.
I'm new to Apollo (back-end in general) so your patience is appreciated; I've looked through Apollo's docs and I'm not sure where I've gone wrong.
The data I'm receiving from my REST API call is as follows - I'm trying to return the ids and titles and based on my trials, I'm fairly certain the issue is my resolvers? The error I'm receiving is: "Cannot read properties of undefined (reading: 'session')"
{
"selection": {
...other data...
"options": [
{
id: 1
title: "Option 1 Title"
},
{
id: 2
title: "Option 2 Title"
},
]
}
}
My schema:
type Query {
session: Selection
}
type: Selection {
...other data...
optionListing: [Options]
}
type: Options {
id: Int
title: String
}
My resolvers:
{
Query: {
session: async (parent, args, {tokenAuth}) => {
...token auth code....
return tokenAuth;
};
}
Selection: {
optionListing: async ({tokenAuth}) => {
...this is the resolver that triggers the API call...
return optionData;
}
}
Options: {
id: async(parent) => {
const tempID = await parent;
return tempID.id;
}
title: async(parent) => {
const tempTitle = await parent;
return tempTitle.title;
}
}
}
async fetch() {
try {
console.log(await this.$api.events.all(-1, false)); // <-- First log statement
const res = await this.$api.events.all(-1, false); // <-- Assignment
console.log(res); // <-- Second log statement
if (!this.events) {
this.events = []
}
res.data.forEach((event, index) => {
const id = event.hashid;
const existingIndex = this.events.findIndex((other) => {
return other.hashid = id;
});
if (existingIndex == -1) {
this.events.push(events);
} else {
this.events[existingIndex] = event;
}
});
for (var i = this.events.length - 1; i >= 0; --i) {
const id = this.events[i].hashid
const wasRemoved =
res.data.findIndex((event) => {
return event.hashid == id
}) == -1
if (wasRemoved) {
this.events.splice(i, 1)
}
}
this.$store.commit('cache/updateEventData', {
updated_at: new Date(Date.now()),
data: this.events
});
} catch (err) {
console.log(err)
}
}
// The other functions, maybe this somehow helps
async function refreshTokenFirstThen(adminApi, func) {
await adminApi.refreshAsync();
return func();
}
all(count = -1, description = true) {
const func = () => {
return $axios.get(`${baseURL}/admin/event`, {
'params': {
'count': count,
'description': description ? 1 : 0
},
'headers': {
'Authorization': `Bearer ${store.state.admin.token}`
}
});
}
if (store.getters["admin/isTokenExpired"]) {
return refreshTokenFirstThen(adminApi, func);
}
return func();
},
Both log statements are giving slightly different results even though the same result is expected. But this only happens when is use the function in this specific component. When using the same function in other components, everything works as expected.
First data output:
[
{
"name": "First Name",
"hashid": "VQW9xg7j",
// some more correct attributes
},
{
"name": "Second name",
"hashid": "zlWvEgxQ",
// some more correct attributes
}
]
While the second console.log gives the following output:
[
{
"name": "First Name",
"hashid": "zlWvEgxQ",
// some more correct attributes, but this time with reactiveGetter and reactiveSetter
<get hashid()>: reactiveGetter()
length: 0
name: "reactiveGetter"
prototype: Object { … }
<prototype>: function ()
<set hashid()>: reactiveSetter(newVal)
length: 1
name: "reactiveSetter"
prototype: Object { … }
<prototype>: function ()
},
{
"name": "Second name",
"hashid": "zlWvEgxQ",
// some more correct attributes and still without reactiveGetter and reactiveSetter
}
]
As it can be seen, somehow the value of my hashid attribute changes, when assigning the response of the function call.
The next weird behavior happening here, is that the first object where the hashid field changes also gets reactiveGetter and reactiveSetter (but the second object in the array does not get these).
So it looks to me like something is happening with the assignment that I don't know about. Another guess would be that this has something to do with the Vuex store, because I do not change the Vuex tore in the other place where I use the same function.
It is verified that the backend always sends the correct data, as this is dummy data, consisting of an array with two objects with some attributes. So no other data except this two objects is expected.
Can someone explain to me why this behavior occurs?
There are few problems...
Do not use console.log with objects. Browsers tend to show "live view" of object - reference
this.events.findIndex((other) => { return other.hashid = id; }); is wrong, you are using assignment operator (=) instead of identity operator (===). That's why the hashid of the first element changes...
What is the workaround to update the dataLine when using data.Items.map()
I am getting eslint error:
Assignment to property of function parameter 'dataLine'
You can see I am deleting Other property and modifying dataLine.Config
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
console.log(JSON.stringify(newItems, null, 2));
About eslint, I think it's a missing piece, because if you write your function in an equivalent way:
data.Items.map((dataLine) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
you won't receive any warning. Maybe it's the case of open an issue there.
You could pass {props : true}, like GProst said, but this will enforce you to not make the assignment of any property of the parameter, which is a good thing, for example:
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = { // not allowed with props : true
Size: "L"
};
delete dataLine.Other; // not allowed with props : true
}
return dataLine;
});
Why eslint have such a rule?
You are modifying the properties of data.Items, this will cause side effects on the external environment of the callback function on map. In some cases this will put you in bad situation, like not knowing which piece of code removed some property.
A suggestion about how you can deal with this safely is return an entire new object to make your data.Items immutable in your case:
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
const dataLineCopy = JSON.parse(JSON.stringify(dataLine))
if (data.Type == "API") {
dataLineCopy.Config = {
Size: "L"
};
delete dataLineCopy.Other;
}
return dataLineCopy;
});
console.log(JSON.stringify(newItems, null, 2));
Edit no-param-reassign rule in eslint config, set option props to false:
"no-param-reassign": ["error", { "props": false }]
I want to create a new variable line and make it contain nested data.
What I am expecting as result:
{
description: "descriptionString",
vatInfo : {
vatAccount: {
vatCode : "vatCode"
}
}
}
How I am doing it:
export function changeProductOnLine(originalLine, product, customer, invoice) {
let line = { ...originalLine, product }
if (product) {
const vatCode = getProductVatCode(line, invoice)
line.description = buildLineDescription(product.name, product.description)
line.vatInfo.vatAccount.vatCode = "v10"
return line
}
Is it correct what I am doing ? Can it work ?
What you're doing is close, but you need to keep in mind that when you use a spreader like that, what you'll end up with is this:
{
description: "descriptionString",
vatInfo : {
vatAccount: {
vatCode : "vatCode"
}
},
product: {
description: "descriptionString",
vatInfo : {
vatAccount: {
vatCode : "vatCode"
}
}
}
}
What I think you want is for product to overwrite the values, but not end up with a 'product' property in your object, based on what you explained you wanted. Try this:
export function changeProductOnLine(originalLine, product, customer, invoice) {
if (product) {
return {
...originalLine,
// since these are the last thing in the new obj it will overwrite anything in the obj
description: buildLineDescription(product.name, product.description),
vatInfo: {
vatAccount : {
vatCode: getProductVatCode(line, invoice)
}
}
}
} else {
return line
}
}
This way, you also don't end up creating a new variable in memory just to modify it slightly then return it. It all happens at once, and ONLY if you have a arg 'product' value other than undefined or null