-
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 all 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,193 @@ | ||
| 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); | ||
| } | ||
|
|
||
| /** | ||
| * Initialize pagination functionality based on current bundle results. | ||
| * | ||
| * @param {object} bundle a results bundle of a FHIR search. | ||
| * | ||
| * @returns {void} | ||
| */ | ||
| initialize(bundle) { | ||
| this.currentResults = bundle; | ||
|
|
||
| const availableLink = this.nextLink() || this.prevLink() || this.selfLink(); | ||
| if (availableLink) { this.extractSearchParams(availableLink); } | ||
| } | ||
|
|
||
| /** | ||
| * Extract each type of param (searchIdParam, offsetParam, & countParam) from link. | ||
| * | ||
| * @param {string} link a link containing search parameters matching indicated paramNames. | ||
| * | ||
| * @returns {void} | ||
| */ | ||
| extractSearchParams(link) { | ||
| const linkUrl = new URL(link.url); | ||
|
|
||
| Object.values(this.paramNames).forEach((paramName) => { | ||
| this[paramName] = linkUrl.searchParams.get(paramName); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Return the specified page of results based on available search parameters. | ||
| * | ||
| * @param {number} page the page number to navigate to. | ||
| * @param {object} [bundle] a results bundle of a FHIR search, optional. | ||
| * | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| goToPage(page, bundle) { | ||
| if (bundle) { this.initialize(bundle); } | ||
| const selectedPage = this.goToPageLink(page); | ||
|
|
||
| this.currentResults = selectedPage ? this.httpClient.get(selectedPage) : undefined; | ||
| return this.currentResults; | ||
| } | ||
|
|
||
| /** | ||
| * Return the current page of results. | ||
| * | ||
| * @param {object} [bundle] a results bundle of a FHIR search, optional. | ||
| * | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| currentPage(bundle) { | ||
| if (bundle) { this.initialize(bundle); } | ||
| return this.updateCurrent(this.selfLink()); | ||
| } | ||
|
|
||
| /** | ||
| * Return the next page of results. | ||
| * | ||
| * @param {object} results a bundle result of a FHIR search | ||
| * @param {object} [bundle] a results bundle of a FHIR search, optional. | ||
| * | ||
| * @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; | ||
| nextPage(bundle) { | ||
| if (bundle) { this.initialize(bundle); } | ||
| return this.updateCurrent(this.nextLink()); | ||
| } | ||
|
|
||
| /** | ||
| * Return the previous page of results. | ||
| * | ||
| * @param {object} results a bundle result of a FHIR search | ||
| * @param {object} [bundle] a results bundle of a FHIR search, optional. | ||
| * | ||
| * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. | ||
| */ | ||
| prevPage(bundle) { | ||
| if (bundle) { this.initialize(bundle); } | ||
| return this.updateCurrent(this.prevLink()); | ||
| } | ||
|
|
||
| /** | ||
| * Return the link for the next page of results. | ||
| * | ||
| * @return {String} link for the next page of results. | ||
| */ | ||
| nextLink() { | ||
| return this.findLink(/next/); | ||
| } | ||
|
|
||
| /** | ||
| * Return the link for the previous page of results. | ||
| * | ||
| * @return {String} link for the previous page of results. | ||
| */ | ||
| prevLink() { | ||
| return this.findLink(/^prev(ious)?$/); | ||
| } | ||
|
|
||
| /** | ||
| * Return the link for the current page of results. | ||
| * | ||
| * @return {String} link for the current page of results. | ||
| */ | ||
| selfLink() { | ||
| return this.findLink(/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; | ||
| updateCurrent(link) { | ||
| 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. | ||
| */ | ||
| findLink(regex) { | ||
| 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.