-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Fix] Introducing Pagination Support for Notion API in Page and Database Responses #59
Conversation
Signed-off-by: brf153 <153hsb@gmail.com>
Signed-off-by: brf153 <153hsb@gmail.com>
Signed-off-by: brf153 <153hsb@gmail.com>
Signed-off-by: brf153 <153hsb@gmail.com>
Signed-off-by: brf153 <153hsb@gmail.com>
Signed-off-by: brf153 <153hsb@gmail.com>
Signed-off-by: brf153 <153hsb@gmail.com>
@Nabhag8848 I have resolved the merge conflicts, but unfortunately, I mistakenly force-pushed the changes, leading to the dissolution of my previous pull request. However, upon further inspection, I can confirm that the changes are still present. |
The ChangesWhatsApp.Video.2024-01-23.at.10.13.26.mp4WhatsApp.Video.2024-01-24.at.10.30.00.mp4WhatsApp.Video.2024-01-24.at.10.04.29.mp4 |
@brf153 Reviewing this right now, Sorry for So much delay on this. 🙏🏻 |
@Nabhag8848 No problem at all! I appreciate you getting back to me. Take your time with the review, and let me know if there are any further changes required. Looking forward to hearing your thoughts! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brf153 Added examples and comments for changes, lemme know once you are done, will review back, quickly.
src/lib/NotionSDK.ts
Outdated
|
||
private async recursiveSearchPages(token: string, cursor: string): Promise<Array<IPage>> { | ||
const response = await this.http.post(NotionApi.SEARCH, { | ||
data: { | ||
start_cursor: cursor, | ||
}, | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
"Content-Type": NotionApi.CONTENT_TYPE, | ||
"User-Agent": NotionApi.USER_AGENT, | ||
"Notion-Version": this.NotionVersion, | ||
}, | ||
}); | ||
|
||
if (response.statusCode.toString().startsWith("2")) { | ||
|
||
|
||
let results: Array<IPage> = response.data.results; | ||
|
||
if (response.data.has_more === true) { | ||
|
||
const recursiveResults = await this.recursiveSearchPages(token, response.data.next_cursor); | ||
|
||
results.push(...recursiveResults); | ||
} | ||
|
||
return results; | ||
} else { | ||
throw new AppsEngineException(`Error While Searching Pages: ${response.content}`); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private async recursiveSearchPages(token: string, cursor: string): Promise<Array<IPage>> { | |
const response = await this.http.post(NotionApi.SEARCH, { | |
data: { | |
start_cursor: cursor, | |
}, | |
headers: { | |
Authorization: `Bearer ${token}`, | |
"Content-Type": NotionApi.CONTENT_TYPE, | |
"User-Agent": NotionApi.USER_AGENT, | |
"Notion-Version": this.NotionVersion, | |
}, | |
}); | |
if (response.statusCode.toString().startsWith("2")) { | |
let results: Array<IPage> = response.data.results; | |
if (response.data.has_more === true) { | |
const recursiveResults = await this.recursiveSearchPages(token, response.data.next_cursor); | |
results.push(...recursiveResults); | |
} | |
return results; | |
} else { | |
throw new AppsEngineException(`Error While Searching Pages: ${response.content}`); | |
} | |
} | |
- Better Approach:
public async searchPages(
token: string,
cursor?: string
): Promise<Array<IPage> | Error> {
try {
const data = {
filter: {
value: NotionObjectTypes.PAGE,
property: NotionObjectTypes.PROPERTY,
},
start_cursor: cursor,
};
const response = await this.http.post(NotionApi.SEARCH, {
data,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": NotionApi.CONTENT_TYPE,
"User-Agent": NotionApi.USER_AGENT,
"Notion-Version": this.NotionVersion,
},
});
if (!response.statusCode.toString().startsWith("2")) {
return this.handleErrorResponse(
response.statusCode,
`Error While Searching Pages: `,
response.content
);
}
const { results, next_cursor, has_more } = response.data;
if (has_more === true) {
const recursiveResults = await this.searchPages(
token,
next_cursor
);
if (recursiveResults instanceof Error) {
return recursiveResults;
}
results.push(...recursiveResults);
}
if (cursor) return results;
const result: Array<IPage> = [];
results.forEach(async (item) => {
const pageObject = await this.getPageObjectFromResults(item);
if (pageObject) {
result.push(pageObject);
}
})
return result;
} catch (err) {
throw new AppsEngineException(err as string);
}
}
-
Hey @brf153 Can't we have instead implement recursion without using the helper extra function. similar to above, can we change
THIS ONE and REST OF THE Implementation USING this apporach
make sure we don't need to use extra method and avoid having duplicacy of code. -
Also make sure about this case what if during the paginated requests one of the requests get error from server in that case we need to assume that whole request is Error. above example solves that. Your implementation doesn't include that.
-
lets use forEach loops and lets stick to code already have to maintain consistency.
Also there is a bug in getPageObjectFromResults:
private async getPageObjectFromResults(
item,
emoji: boolean = false
): Promise<IPage | null> {
const typesWithTitleProperty = [
NotionObjectTypes.WORKSPACE.toString(),
NotionObjectTypes.PAGE_ID.toString(),
];
const parentType: string = item.parent.type;
const properties = item.properties;
const pageId: string = item.id;
if (typesWithTitleProperty.includes(parentType)) {
const pageName: string =
properties.title.title[0]?.text?.content ||
NotionObjectTypes.UNTITLED;
return this.returnPage(pageName, pageId, emoji);
}
// title property either be at first or last position
const columns = Object.keys(properties);
const firstColumn = columns[0];
const lastColumn = columns[columns.length - 1];
// title at first position and has subpage
if (properties[firstColumn].title) { <- Removed the length statement here this is updated version, remove in your PR code as well
const name: string =
properties[firstColumn].title[0]?.text?.content ||
NotionObjectTypes.UNTITLED;
return this.returnPage(name, pageId, emoji);
}
//title at last position and has subpage
if (properties[lastColumn].title) { <- Removed the length statement here this is updated version as well.
const name: string =
properties[lastColumn].title[0]?.text?.content ||
NotionObjectTypes.UNTITLED;
return this.returnPage(name, pageId, emoji);
}
return null;
}
- So when we get the page from the api: we need to make sure we get all the pages, removing that length statement solves the Untitled pages not being shown:
- parent page
- sub pages
- database row (this are also the pages)
- page without titled should get
Untitled
title
If you currently will request there are 140
pages, in your workspace you shared
src/lib/NotionSDK.ts
Outdated
private getNotionApiHeaders(token: string): Record<string, string> { | ||
return { | ||
Authorization: `Bearer ${token}`, | ||
"Content-Type": NotionApi.CONTENT_TYPE, | ||
"User-Agent": NotionApi.USER_AGENT, | ||
"Notion-Version": this.NotionVersion, | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
As you have Introduce this method, Lets change in entire file, where we are passing this exact same header and lets make the lines of code less and call this method instead of hard coding
-
INCLUDING the code changes i shared.
Eg:
const response = await this.http.post(NotionApi.SEARCH, {
headers: this.getNotionApiHeaders(token)
});
src/lib/NotionSDK.ts
Outdated
public async searchDatabases( | ||
token: string | ||
): Promise<Array<IDatabase> | Error> { | ||
public async recursiveSearchDatabases(token: string, startCursor?: string): Promise<Array<IDatabase>> { | ||
try { | ||
const response = await this.http.post(NotionApi.SEARCH, { | ||
data: { | ||
filter: { | ||
value: NotionObjectTypes.DATABASE, | ||
property: NotionObjectTypes.PROPERTY, | ||
let response; | ||
|
||
if (startCursor) { | ||
response = await this.http.post(NotionApi.SEARCH, { | ||
data: { | ||
filter: { | ||
value: NotionObjectTypes.DATABASE, | ||
property: NotionObjectTypes.PROPERTY, | ||
}, | ||
start_cursor: startCursor, | ||
}, | ||
}, | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
"Content-Type": NotionApi.CONTENT_TYPE, | ||
"User-Agent": NotionApi.USER_AGENT, | ||
"Notion-Version": this.NotionVersion, | ||
}, | ||
}); | ||
headers: this.getNotionApiHeaders(token), | ||
}); | ||
} else { | ||
response = await this.http.post(NotionApi.SEARCH, { | ||
data: { | ||
filter: { | ||
value: NotionObjectTypes.DATABASE, | ||
property: NotionObjectTypes.PROPERTY, | ||
}, | ||
}, | ||
headers: this.getNotionApiHeaders(token), | ||
}); | ||
} | ||
|
||
if (!response.statusCode.toString().startsWith("2")) { | ||
return this.handleErrorResponse( | ||
response.statusCode, | ||
`Error While Searching Databases: `, | ||
response.content | ||
); | ||
throw new AppsEngineException(`Error While Searching Databases: ${response.content}`); | ||
} | ||
|
||
const { results } = response.data; | ||
|
||
const { results, has_more, next_cursor } = response.data; | ||
const result: Array<IDatabase> = []; | ||
|
||
results.forEach(async (item) => { | ||
const objectType: string = item?.[NotionObjectTypes.OBJECT]; | ||
const databaseObject = await this.getDatabaseObjectFromResults( | ||
item | ||
); | ||
|
||
const databaseObject = await this.getDatabaseObjectFromResults(item); | ||
result.push(databaseObject); | ||
}); | ||
|
||
if (has_more) { | ||
const recursiveResults = await this.recursiveSearchDatabases(token, next_cursor); | ||
result.push(...recursiveResults); | ||
} | ||
|
||
return result; | ||
} catch (err) { | ||
throw new AppsEngineException(err as string); | ||
} | ||
} | ||
|
||
public async searchDatabases(token: string): Promise<Array<IDatabase> | Error> { | ||
try { | ||
const results = await this.recursiveSearchDatabases(token); | ||
return results; | ||
} catch (err) { | ||
throw new AppsEngineException(err as string); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Implement making sure We have just one method and don't have extra helper method to avoid duplicacy as approached shared above.
-
Also make sure about this case what if during the paginated requests one of the requests get error from server in that case we need to assume that whole set of request is Error. above example solves that.
-
Applies to all three: searchDatabases, searchPages, searchPagesAndDatabases
@@ -492,31 +533,34 @@ export class NotionSDK implements INotionSDK { | |||
} | |||
} | |||
|
|||
public async searchPagesAndDatabases( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Change according to approach similar to shared.
-
Also make sure about this case what if during the paginated requests one of the requests get error from server in that case we need to assume that whole request is Error. above example solves that.
src/lib/NotionSDK.ts
Outdated
if (startCursor) { | ||
response = await this.http.post(NotionApi.SEARCH, { | ||
data: { | ||
start_cursor: startCursor, | ||
}, | ||
headers: this.getNotionApiHeaders(token), | ||
}); | ||
} else { | ||
response = await this.http.post(NotionApi.SEARCH, { | ||
headers: this.getNotionApiHeaders(token), | ||
}); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- You can use conditional statments in object instead of making if else statement for request. implement in new implementation take reference from example code shared.
src/lib/NotionSDK.ts
Outdated
`Error While Searching Databases: `, | ||
response.content | ||
); | ||
throw new AppsEngineException(`Error While Searching Databases: ${response.content}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw new AppsEngineException(`Error While Searching Databases: ${response.content}`); | |
return this.handleErrorResponse( | |
response.statusCode, | |
`Error While Searching Databases: `, | |
response.content | |
); |
- never throw an error for an usual error response, we are handling this error to later don't open a modal if we don't get the database
@Nabhag8848 working on this. Sorry for the delay |
No Worries, Thanks ! @brf153 Looking forward 🚀 |
… in the original function as recommended Signed-off-by: brf153 <153hsb@gmail.com>
@Nabhag8848 I have made the changes, and your suggestions have greatly helped me. Thank you for the recommendations. |
@brf153 🙏 will review it, by tomorrow thanks! Try checking all the features once changing that and see if nothing is getting break. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @brf153 Added some comments, few more Changes, we can merge this after. Lemme know once you are done.
Thanks !
src/lib/NotionSDK.ts
Outdated
|
||
const response = await this.http.post(NotionApi.SEARCH, { | ||
data, | ||
headers: this.getNotionApiHeaders(token), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indent this header
src/lib/NotionSDK.ts
Outdated
return this.handleErrorResponse( | ||
err.statusCode, | ||
`Error While Searching Databases: `, | ||
err.content | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you will have a look on previous comments again I meant Usual Errors
, Errors which will go into Catch are not usual which means those errors are not known and weren't expected anytime.
return this.handleErrorResponse( | |
err.statusCode, | |
`Error While Searching Databases: `, | |
err.content | |
); | |
throw new AppsEngineException(err as string); |
src/lib/NotionSDK.ts
Outdated
"User-Agent": NotionApi.USER_AGENT, | ||
"Notion-Version": this.NotionVersion, | ||
}, | ||
headers: this.getNotionApiHeaders(token), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indent
src/lib/NotionSDK.ts
Outdated
"User-Agent": NotionApi.USER_AGENT, | ||
"Notion-Version": this.NotionVersion, | ||
}, | ||
headers: this.getNotionApiHeaders(token), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent
src/lib/NotionSDK.ts
Outdated
"Notion-Version": this.NotionVersion, | ||
}, | ||
data, | ||
headers: this.getNotionApiHeaders(token), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent
src/lib/NotionSDK.ts
Outdated
"User-Agent": NotionApi.USER_AGENT, | ||
"Notion-Version": this.NotionVersion, | ||
}, | ||
headers: this.getNotionApiHeaders(token), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent
src/lib/NotionSDK.ts
Outdated
"User-Agent": NotionApi.USER_AGENT, | ||
"Notion-Version": this.NotionVersion, | ||
}, | ||
headers: this.getNotionApiHeaders(token), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent
src/lib/NotionSDK.ts
Outdated
return this.handleErrorResponse( | ||
err.statusCode, | ||
`Error While Searching Databases: `, | ||
err.content | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return this.handleErrorResponse( | |
err.statusCode, | |
`Error While Searching Databases: `, | |
err.content | |
); | |
throw new AppsEngineException(err as string); |
Reason mentioned above.
src/lib/NotionSDK.ts
Outdated
return this.handleErrorResponse( | ||
err.statusCode, | ||
`Error While Searching Databases: `, | ||
err.content | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return this.handleErrorResponse( | |
err.statusCode, | |
`Error While Searching Databases: `, | |
err.content | |
); | |
throw new AppsEngineException(err as string); |
Reason mentiond above.
src/lib/NotionSDK.ts
Outdated
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove vertical spaces. 🙏🏻
…in catch errors Signed-off-by: brf153 <153hsb@gmail.com>
@Nabhag8848 made the changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🔥 @brf153 Very Thanks, Do you have any feature in mind to built ? / or have some suggestion on improvement around this app feel free to open issue.
Sure I will open an issue as soon I get some idea for a feature |
Issue(s)
closes #38
Acceptance Criteria fulfillment
Proposed changes (including videos or screenshots):
Made changes to searchPage method. Now, we are getting all the pages made in a workspace.
Screenshot of the length of the final results after concatenating all the response.data.results