-
Notifications
You must be signed in to change notification settings - Fork 12
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
Bai 1111 allow semver matching and latest semver for releases #1582
base: main
Are you sure you want to change the base?
Bai 1111 allow semver matching and latest semver for releases #1582
Conversation
backend/src/services/release.ts
Outdated
{ | ||
$or: [ | ||
{ | ||
'semver.major': { $lte: upperSemverObj.major }, |
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 upperSemverObj
doesn't exist, this part of both queries will 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.
Checks added for upperSemver, etc
backend/src/services/release.ts
Outdated
upperSemverObj = semverStringToObject(upperSemverTrimmed) | ||
} | ||
|
||
if (inclusivity) |
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.
This function is a lot simpler and more readable, good job!
To improve upon this a bit more I would move all the logic needed to convert the semverRangeStandardised
content into a form that can be used to generate this mongo query into a single function. At the moment this function contains multiple variable variables and function calls to convert the various semver formats when all you really need at this point is just the lowerSemverObj and upperSemverObj. Also your exclusivity logic doesn't currently work as it doesn't allow for half the semver query being inclusive.
Once you've got your semverRangeStandardised
and checked it's not null you could capture all the logic up to this point of the mongo query in a single function, something like:
function parseSemverQuery(semverRangeStandardised: string): {
upper: { major: string; minor: string; patch: string; inclusive: boolean }
lower: { major: string; minor: string; patch: string; inclusive: boolean }
} {
//parse semverRangeStandardised
}
Then to reduce duplication in the inclusivity logic I would use if statements to set either $gte
or $gt
and $lse
or $ls
rather than repeat all the other bits of the mongo query in all the conditions. Eg:
if (semverQuery.upper.inclusive) {
lowerComparator = '$gte'
} else {
lowerComparator = '$gt'
}
if (semverQuery.lower.inclusive) {
uppercomparator = '$lse'
} else {
uppercomparator = '$ls'
}
Then you should be able to use this two variable in the relevant places in your single mongo query object Eg 'semver.major': { [lowerComparator]: semverQuery.lower.major }
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.
Changes to semverRangeStandardised added, also added dynamic query stuff for inclusivity.
upperSemver: string = '', | ||
lowerInclusivity = false, | ||
upperInclusivity = false | ||
if (expressionA.includes('>')) { |
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.
When an if statement becomes this complex it's always worth seeing if it could be simplified.
Have you considered:
const lowerInclusivity = expressionA.includes('>=')
const upperInclusivity = expressionB.includes('<=') | expressionA.includes('<=')
const lowerSemver = expressionA.replace(/[<>=]/g, '')
const upperSemver = expressionB.replace(/[<>=]/g, '')
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.
Done, still required some if statements as expressionA can be the upperSemver as the lower/upper parts of the variables state wether they are upper or lower bounds, not first or second in query
backend/src/services/release.ts
Outdated
} | ||
} | ||
|
||
let lowerSemverObj: { metadata?: string | undefined; major: number; minor: number; patch: number } = { |
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.
In a situation where two things are the exact same type, we should avoid redefining types. This allows us to have a single source of truth. If we ever updated that type for any reason, we'd only have to update it in one place and TS would guide us through updating all variables of that type.
In this scenario we should be using the SemverObject
type you've already created in backend/src/models/Release.ts
for both lowerSemverObj
and upperSemverObj
.
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.
Removed as was causing bugs and didn't seem strictly necessary.
backend/src/services/release.ts
Outdated
minor: NaN, | ||
patch: NaN, | ||
}, | ||
upperSemverObj: { metadata?: string | undefined; major: number; minor: number; patch: number } = { |
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.
Although JS allows us to define multiple variables on one line like you're doing here, I would strongly advise against it as it's simply less readable than defining multiple variables separately.
let lowerSemverObj: SemverObject = { ... }
let upperSemverObj: SemverObject = { ... }
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.
Fixed above
backend/src/services/release.ts
Outdated
@@ -237,9 +239,11 @@ export async function newReleaseComment(user: UserInterface, modelId: string, se | |||
export async function getModelReleases( | |||
user: UserInterface, | |||
modelId: string, | |||
q?: 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.
I'm never a fan of single character variable names like this. It's more confusing than simply giving it a proper name.
Consider calling this querySemver
as you have later on.
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.
True. Fixed
backend/src/services/release.ts
Outdated
): Promise<Array<ReleaseDoc & { model: ModelInterface; files: FileInterface[] }>> { | ||
const query = q === undefined ? { modelId } : getQuerySyntax(q, modelId) |
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.
This can be simplified to const query = getQuerySyntax(q, modelId)
as you've already covered off the case of q === undefined
inside of getQuerySyntax
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.
Removed the check in getquerysyntax, dealing with it earlier makes more sense to me, but willing to change
backend/src/services/release.ts
Outdated
const results = await Release.aggregate() | ||
.match({ modelId }) | ||
.match(query!) |
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.
By using !
we're telling TS that this can't possibly be undefined
/null
. This is very rarely the correct way to manage types and is likely indicative of a flaw in your getQuerySyntax()
logic.
I'd suggest removing this assertion and seeing where undefined
/null
are possibilities in earlier logic. You should fix it there instead of asserting here.
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.
Turns out I didn't even need it anymore so just removed
@@ -17,6 +17,9 @@ export const getReleasesSchema = z.object({ | |||
required_error: 'Must specify model id as URL parameter', | |||
}), | |||
}), | |||
query: z.object({ | |||
q: z.string().optional(), |
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 you include an example and description here for the API docs please
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.
Done
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.
Done
backend/src/services/release.ts
Outdated
@@ -248,7 +252,9 @@ export async function getModelReleases( | |||
const model = await getModelById(user, modelId) | |||
|
|||
const auths = await authorisation.releases(user, model, results, ReleaseAction.View) | |||
return results.filter((_, i) => auths[i].success) | |||
return results |
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 we care about performance here, we can combine this filter
and map
into a single reduce
:
return results.reduce<(ReleaseInterface & { model: ModelInterface })[]>((updatedResults, result, index) => {
if (auths[index].success) {
updatedResults.push({ ...result, semver: semverObjectToString(result.semver) })
}
return updatedResults
}, [])
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.
Added, but wasn't sure on how to implement the updatedResults as I can't push to it as its not an array, etc
backend/src/services/release.ts
Outdated
@@ -276,11 +282,36 @@ export async function getReleasesForExport(user: UserInterface, modelId: string, | |||
return releases | |||
} | |||
|
|||
export function semverStringToObject(semver: 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.
We can give this an explicit return type to ensure it's not broken by accident:
export function semverStringToObject(semver: string): SemverObject { ...
You could do the same for semverObjectToString
, but that's less of a concern as it only has to return a string instead of an object of a defined shape.
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.
Added
backend/src/services/release.ts
Outdated
@@ -55,7 +56,8 @@ async function validateRelease(user: UserInterface, model: ModelDoc, release: Re | |||
const fileNames: Array<string> = [] | |||
|
|||
for (const fileId of release.fileIds) { | |||
let file | |||
let file: Document<unknown, any, FileInterface> & |
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.
This can be simplified to let file: FileInterfaceDoc | undefined
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.
Don't know why its saying I've done this, but I have changed anyway!
WIP PR for advanced semver range queries as stated in https://docs.npmjs.com/cli/v6/using-npm/semver#caret-ranges-123-025-004