-
Notifications
You must be signed in to change notification settings - Fork 41
Refactor pagination #58
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
Changes from 5 commits
a34699e
7cf8234
4885d46
c846552
dab81ac
c66b104
91144a9
faafa4f
7f7e65a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,183 @@ | ||
| const { URL } = require('url'); | ||
|
|
||
| /** | ||
| * Class for paging Bundles. | ||
| * @private | ||
| * | ||
| */ | ||
| class Pagination { | ||
| /** | ||
| * Add pagination functionality for the provided client. | ||
| * | ||
| * @private | ||
| * FHIR-conforming servers that support pagination will, at minimum, | ||
| * allow for the use of `currentPage()`, `prevPage()`, and `nextPage()`. | ||
| * (see https://www.hl7.org/fhir/http.html#paging) | ||
| * | ||
| * Some servers will also have page offset and/or unique search IDs, | ||
| * which enable `goToPage()`. The parameter names can optionally be renamed | ||
| * here if needed for variations in server parameter names. | ||
| * | ||
| * @param {HttpClient} httpClient a configured instance of http client. | ||
| * @param {Object} paramNames pagination-related search parameter names, optional. | ||
| * @param {Object} [paramNames.searchIdParam] server-based unique ID for search session, optional. | ||
| * @param {Object} [paramNames.offsetParam] server-based page offset for search, optional. | ||
| * @param {Object} [paramNames.countParam] server-based result count per page, optional. | ||
| */ | ||
| constructor(httpClient) { | ||
| constructor(httpClient, paramNames = {}) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The client constructor doesn't provide a way to pass |
||
| this.httpClient = httpClient; | ||
| this.baseUrl = httpClient.axiosInstance.defaults.baseURL; | ||
| this.currentResults = null; | ||
|
|
||
| const defaultParamNames = { | ||
| searchIdParam: '_getpages', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you explain what this param actually is? and those make obvious sense, but when I look at |
||
| offsetParam: '_getpagesoffset', | ||
| countParam: '_count', | ||
| }; | ||
|
|
||
| this.paramNames = Object.assign({}, defaultParamNames, paramNames); | ||
| } | ||
|
|
||
| /** | ||
| * Return the next page of results. | ||
| * Initialize pagination functionality based on current bundle results. | ||
| * | ||
| * @param {object} results a bundle result of a FHIR search | ||
| * | ||
| * @returns {void} | ||
| */ | ||
| initialize(results) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If results is always a Bundle, I think it should be called |
||
| this.currentResults = results; | ||
|
|
||
| const availableLink = this.nextLink() || this.prevLink() || this.selfLink(); | ||
| if (availableLink) { this.parseUrl(availableLink); } | ||
| } | ||
|
|
||
| /** | ||
| * Detect and save search parameters based on provided or default paramNames. | ||
| * | ||
| * @param {string} link a link containing search parameters matching indicated paramNames. | ||
| * | ||
| * @returns {void} | ||
| */ | ||
| parseUrl(link) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this could have a more descriptive name. Something like |
||
| const linkUrl = new URL(link.url); | ||
|
|
||
| Object.keys(this.paramNames).forEach((paramName) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like it should use |
||
| const param = this.paramNames[paramName]; | ||
| this[param] = linkUrl.searchParams.get(param); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Return the specified page of results based on available search parameters. | ||
| * | ||
| * @param {number} page the page number to navigate to. | ||
| * | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| goToPage(page) { | ||
| const selectedPage = this.goToPageLink(page); | ||
|
|
||
| this.currentResults = selectedPage ? this.httpClient.get(selectedPage) : undefined; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really just |
||
| return this.currentResults; | ||
| } | ||
|
|
||
| /** | ||
| * Return the current page of results. | ||
| * | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| nextPage(results) { | ||
| const nextLink = results.link.find(link => link.relation === 'next'); | ||
| return nextLink ? this.httpClient.get(nextLink.url) : undefined; | ||
| currentPage() { | ||
| return this.getAndSetCurrent(this.selfLink()); | ||
| } | ||
|
|
||
| /** | ||
| * Return the next page of results. | ||
| * | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| nextPage() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
| return this.getAndSetCurrent(this.nextLink()); | ||
| } | ||
|
|
||
| /** | ||
| * Return the previous page of results. | ||
| * | ||
| * @param {object} results a bundle result of a FHIR search | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| prevPage() { | ||
| return this.getAndSetCurrent(this.prevLink()); | ||
| } | ||
|
|
||
| /** | ||
| * Return the link for the next page of results. | ||
| * | ||
| * @return {String} The link for the next page of results. | ||
| */ | ||
| nextLink() { | ||
| return this.getLink(/next/); | ||
| } | ||
|
|
||
| /** | ||
| * Return the link for the previous page of results. | ||
| * | ||
| * @return {String} link for the previous page of results. | ||
| */ | ||
| prevLink() { | ||
| return this.getLink(/^prev(ious)?$/); | ||
| } | ||
|
|
||
| /** | ||
| * Return the link for the current page of results. | ||
| * | ||
| * @return {String} link for the current page of results. | ||
| */ | ||
| selfLink() { | ||
| return this.getLink(/self/); | ||
| } | ||
|
|
||
| /** | ||
| * Return a link for the specified page of results based on available search parameters. | ||
| * | ||
| * @param {number} page the page number to navigate to. | ||
| * | ||
| * @return {String} link for the specified page of results. | ||
| */ | ||
| goToPageLink(page) { | ||
| const { countParam, offsetParam } = this.paramNames; | ||
|
|
||
| this[offsetParam] = (page * this[countParam]) - this[countParam]; | ||
| const paramNames = Object.keys(this.paramNames); | ||
|
|
||
| const pageLinkUrl = new URL(`${this.baseUrl}`); | ||
|
|
||
| paramNames.forEach((paramName) => { | ||
| const param = this.paramNames[paramName]; | ||
| pageLinkUrl.searchParams.append(param, this[param]); | ||
| }); | ||
|
|
||
| return pageLinkUrl.href; | ||
| } | ||
|
|
||
| /** | ||
| * Return results and set them to the current results. | ||
| * | ||
| * @param {String} link a link referring to a page of search results. | ||
| * | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| prevPage(results) { | ||
| const prevLink = results.link.find(link => link.relation.match(/^prev(ious)?$/)); | ||
| return prevLink ? this.httpClient.get(prevLink.url) : undefined; | ||
| getAndSetCurrent(link) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rename this |
||
| this.currentResults = link ? this.httpClient.get(link.url) : undefined; | ||
| return this.currentResults; | ||
| } | ||
|
|
||
| /** | ||
| * Return a link from the current results bundle. | ||
| * | ||
| * @param {String} regex to match specific link text. | ||
| * | ||
| * @return {String} link to specified page based on regex. | ||
| */ | ||
| getLink(regex) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe |
||
| return this.currentResults.link.find(link => link.relation.match(regex)); | ||
| } | ||
| } | ||
|
|
||
|
|
||
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.
Could we discuss some alternative names for
paramNames? Probably easier to do face to face.