Dictionary Migration - Get migration endpoints#200
Conversation
joneubank
left a comment
There was a problem hiding this comment.
Minor changes here. Good work.
| retries: | ||
| type: number | ||
| description: Number of retries of the migration |
There was a problem hiding this comment.
Do we explain "retries" anywhere? this is a confusing data point.
There was a problem hiding this comment.
I've updated the description for this field "Number of times the migration has run. When a migration fails, it can be retried by registering again the same dictionary."
However, a proper documentation is added in the following PR, where the "retry" functionality is implemented
| const defaultPage = 1; | ||
| const defaultPageSize = 20; |
There was a problem hiding this comment.
Maybe should be global constants defined with the pagination types?
There was a problem hiding this comment.
moved this constants to a new shared file.
| try { | ||
| const migrationId = Number(req.params.migrationId); | ||
|
|
||
| logger.info(LOG_MODULE, `Request Migration id '${migrationId}'`); |
There was a problem hiding this comment.
should probably be debug level
There was a problem hiding this comment.
This comment applies this to all similar request detail logging in this controller.
There was a problem hiding this comment.
we add a info log for every request in every controller because we're not using express request logger.
| if (!submission) { | ||
| throw new NotFound('Submission not found'); |
There was a problem hiding this comment.
should we be logging that the migration was not found?
There was a problem hiding this comment.
👍 code changed as suggested
| if (submissions.result.length === 0) { | ||
| throw new NotFound('Submissions not found'); | ||
| } |
There was a problem hiding this comment.
If the category ID exists, this should return an empty list.
If the category ID does not exist, then it should return NotFound.
There was a problem hiding this comment.
code changed to return NotFound when category does not exists, also changed to return same error in other endpoints of this controller
| } | ||
| }; | ||
|
|
||
| const getMigrationById = async (migrationId: number): Promise<MigrationRecordWithRelations | undefined> => { |
There was a problem hiding this comment.
Similar comment as previous... this needs to explain why it sometimes returns undefined instead of the returned type.
While I prefer using undefined for missing values, the rest of the Service functions return null instead. It is confusing to have both values used in an ad-hoc way. Let's consistently use either null or undefined
There was a problem hiding this comment.
for consistency, will return null when migration is not found, and will update tsdocs
| const getMigrationsByCategoryId = async ( | ||
| categoryId: number, | ||
| paginationOptions: PaginationOptions, | ||
| ): Promise<{ metadata: { totalRecords: number; errorMessage?: string }; result: MigrationRecordWithRelations[] }> => { |
There was a problem hiding this comment.
Might be useful to create a type alias for this return and export it for use where this function is consumed.
There was a problem hiding this comment.
added a type PaginatedResult<T> to be used in this and other functions
| } catch (error) { | ||
| logger.error(LOG_MODULE, `Error retrieving migrations for categoryId '${categoryId}'`, error); | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
This can be a troubling pattern to fall into, where every function that could throw an error just logs the error then throws it. You can get a long log where every function in the call stack logs the same error with no new information added. Generally, we want to log errors where we catch and handle them.
We could instead make a design decision here, or in any function within this call stack, that the caught error can be handled here instead of thrown again. We can replace our return value either with void return type if there is exactly only one expected failure condition, or replace the return type with a Result that has a list of known failure conditions.
There was a problem hiding this comment.
Removed the try-catch here as the error is unhandled in this function, it should be handled by the upper function (controller) that uses this function, repeated this logic in other functions in this Service.
| organizations?: string | string[]; | ||
| isInvalid?: boolean; | ||
| }, | ||
| ): Promise<{ result: AuditDataResponse[]; metadata: { totalRecords: number; errorMessage?: string } }> => { |
There was a problem hiding this comment.
Another place for a helpful type alias.
| * @param migration | ||
| * @returns | ||
| */ | ||
| export const formatMigrationSummary = (migration: MigrationSummary): MigrationSummary => ({ |
There was a problem hiding this comment.
I assume this is to make the return type from the route nicely formatted for presentation. If this is important to enforce, there should be a unit test for this on the route.
Don't spend much time on this next comment, but: this may be a good use for branded types. You can create a Branded type MigrationSummaryFormatted and have the controller require that it returns this branded type, that will enforce that this function is called before returning. This will enforce the rule through the type system, but since its only used for the route return type there's not a lot gained for the amount of effort here, so its not worth doin. Writing a test is the best way to enforce this.
There was a problem hiding this comment.
for the purpose of this PR, I added unit test for this function to verify it, I would considerate using branded types as suggested in future PRs
There was a problem hiding this comment.
unit tests added for migration formatter functions
|
PR has been updated based on the feedback received, it is now ready for code review. |
| records: migrationsResult.result.map(formatMigrationSummary), | ||
| }; | ||
|
|
||
| return res.send(response); |
There was a problem hiding this comment.
We should send a status 200
There was a problem hiding this comment.
added .status(200) in the response
| * @param {string} filterOptions.systemId | ||
| * and additional filters | ||
| * @param {number} categoryId Category ID to filter the Audit records | ||
| * @param {object} filterOptions Additional filters and pagination options |
There was a problem hiding this comment.
Instead of {object} AuditFilterOptions might be better for the tsDocs
There was a problem hiding this comment.
Responding to James' comment:
Instead of {object} AuditFilterOptions might be better for the tsDocs
Alternately, TS captures our function interfaces. It is entirely acceptable to remove the param annotations, or just the type information, from the TSDoc. These properties are useful if there is something about the parameter that needs to be described - like maybe it is used in a confusing way by the function logic and the caller needs to know that.
There was a problem hiding this comment.
I just removed the type in the tsdocs to avoid unnecessary information in the docs
| status: Extract<MigrationStatus, 'COMPLETED' | 'FAILED'>; | ||
| userName: string; | ||
| }): Promise<Result<null, string>> => { | ||
| }): AsyncResult<null, string> => { |
There was a problem hiding this comment.
Should we use the AsyncResult pattern for the remainder of the services in this file? Or is this only used for create/update actions?
There was a problem hiding this comment.
Long term, I would like to. Not required for scope for this PR.
There was a problem hiding this comment.
for simplicity just using the AsyncResult pattern in the actionables functions: initiate, perform and finalize migrations.
|
|
||
| /** | ||
| * Order the properties of the Migration Summary Record | ||
| * @param migration |
There was a problem hiding this comment.
| * @param migration | |
| * @param {MigrationSummary} migration - The migration summary to format. |
There was a problem hiding this comment.
docs updated as suggested
joneubank
left a comment
There was a problem hiding this comment.
This looks very good. Thanks!
| Pagination: | ||
| type: object | ||
| properties: | ||
| currentPage: | ||
| type: number | ||
| pageSize: | ||
| type: number | ||
| totalPages: | ||
| type: number | ||
| totalRecords: | ||
| type: number |
| status: | ||
| type: string | ||
| description: Status of the Migration | ||
| enum: ['IN-PROGRESS', 'COMPLETED', 'FAILED'] |
There was a problem hiding this comment.
status reports that the migration completed... is there anythign that indicates if any records have errors after the migration?
There was a problem hiding this comment.
there is invalidRecords I missed updating the openapi docs, now it reflects the right response
| records: migrationsResult.result.map(formatMigrationSummary), | ||
| }; | ||
|
|
||
| return res.send(response); |
There was a problem hiding this comment.
Really minor point, but we should be using res.json(response) instead of .send.
They will be almost identical, or even exactly identical, in most cases. The point though is to indicate that we INTEND to be responding with JSON. If our response is not an object (like a string) then .send(someString) will happily send a response that does not have Content-Type application/json, and TS will not prevent us from returning this value.
There's also some weird cases where if you do send(400) it will set the 400 as the http status and send an empty body. Its best to avoid the "smart" logic in .send and use .json when you intend to return json.
There was a problem hiding this comment.
code change to respond with .json(response) as suggested.
| if (organization && !isEmptyString(organization)) { | ||
| filterArray.push(eq(auditSubmittedData.organization, organization)); | ||
| } | ||
|
|
||
| if (submissionId) { | ||
| filterArray.push(eq(auditSubmittedData.submissionId, submissionId)); | ||
| } | ||
|
|
||
| if (newIsValid !== undefined) { | ||
| filterArray.push(eq(auditSubmittedData.newDataIsValid, newIsValid)); | ||
| } |
There was a problem hiding this comment.
Responding to:
The integration test is broken due to a previous change in the worker pool, it requires additional work out of the scope of this PR and should be fixed in a separate PR and extended to cover this function
Can you create an issue and link it?
| * @param {string} filterOptions.systemId | ||
| * and additional filters | ||
| * @param {number} categoryId Category ID to filter the Audit records | ||
| * @param {object} filterOptions Additional filters and pagination options |
There was a problem hiding this comment.
Responding to James' comment:
Instead of {object} AuditFilterOptions might be better for the tsDocs
Alternately, TS captures our function interfaces. It is entirely acceptable to remove the param annotations, or just the type information, from the TSDoc. These properties are useful if there is something about the parameter that needs to be described - like maybe it is used in a confusing way by the function logic and the caller needs to know that.
| }; | ||
| } | ||
|
|
||
| const newIsValid = options.isInvalid !== undefined ? !options.isInvalid : undefined; |
There was a problem hiding this comment.
I forgot this. Thanks for clarification.
| status: Extract<MigrationStatus, 'COMPLETED' | 'FAILED'>; | ||
| userName: string; | ||
| }): Promise<Result<null, string>> => { | ||
| }): AsyncResult<null, string> => { |
There was a problem hiding this comment.
Long term, I would like to. Not required for scope for this PR.
Description
This PR creates 3 new GET endpoints to retrieve migrations information.
Related tickets:
This PR depends on: