Skip to content

Dictionary Migration - Get migration endpoints#200

Open
leoraba wants to merge 39 commits intofeat/dictionary_migrationfrom
feat/get_migration_endpoints
Open

Dictionary Migration - Get migration endpoints#200
leoraba wants to merge 39 commits intofeat/dictionary_migrationfrom
feat/get_migration_endpoints

Conversation

@leoraba
Copy link
Copy Markdown
Contributor

@leoraba leoraba commented Apr 16, 2026

Description

This PR creates 3 new GET endpoints to retrieve migrations information.

  • GET /migration/category/{categoryId}:
  • GET /migration/{migrationId}
  • GET /migration/{migrationId}/records

Related tickets:

This PR depends on:

@leoraba leoraba changed the base branch from main to feat/migration_implementation April 16, 2026 17:26
@leoraba leoraba linked an issue Apr 16, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

@joneubank joneubank left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor changes here. Good work.

Comment thread apps/server/swagger/schemas.yml Outdated
Comment on lines +498 to +500
retries:
type: number
description: Number of retries of the migration
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we explain "retries" anywhere? this is a confusing data point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +15 to +16
const defaultPage = 1;
const defaultPageSize = 20;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe should be global constants defined with the pagination types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this constants to a new shared file.

try {
const migrationId = Number(req.params.migrationId);

logger.info(LOG_MODULE, `Request Migration id '${migrationId}'`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be debug level

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment applies this to all similar request detail logging in this controller.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we add a info log for every request in every controller because we're not using express request logger.

Comment on lines +27 to +28
if (!submission) {
throw new NotFound('Submission not found');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be logging that the migration was not found?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 code changed as suggested

Comment on lines +45 to +47
if (submissions.result.length === 0) {
throw new NotFound('Submissions not found');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the category ID exists, this should return an empty list.

If the category ID does not exist, then it should return NotFound.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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> => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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[] }> => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to create a type alias for this return and export it for use where this function is consumed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a type PaginatedResult<T> to be used in this and other functions

Comment on lines +116 to +119
} catch (error) {
logger.error(LOG_MODULE, `Error retrieving migrations for categoryId '${categoryId}'`, error);
throw error;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 } }> => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another place for a helpful type alias.

* @param migration
* @returns
*/
export const formatMigrationSummary = (migration: MigrationSummary): MigrationSummary => ({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Base automatically changed from feat/migration_implementation to feat/dictionary_migration April 30, 2026 04:22
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unit tests added for migration formatter functions

@leoraba
Copy link
Copy Markdown
Contributor Author

leoraba commented May 4, 2026

PR has been updated based on the feedback received, it is now ready for code review.

@leoraba leoraba requested a review from joneubank May 4, 2026 13:43
records: migrationsResult.result.map(formatMigrationSummary),
};

return res.send(response);
Copy link
Copy Markdown
Contributor

@JamesTLopez JamesTLopez May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should send a status 200

* @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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of {object} AuditFilterOptions might be better for the tsDocs

status: Extract<MigrationStatus, 'COMPLETED' | 'FAILED'>;
userName: string;
}): Promise<Result<null, string>> => {
}): AsyncResult<null, string> => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the AsyncResult pattern for the remainder of the services in this file? Or is this only used for create/update actions?


/**
* Order the properties of the Migration Summary Record
* @param migration
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param migration
* @param {MigrationSummary} migration - The migration summary to format.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Part 2 - Data migration] Get Migrations endpoints

3 participants