-
Notifications
You must be signed in to change notification settings - Fork 13
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
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
…mver-for-releases
backend/src/services/release.ts
Outdated
)[] | ||
} | ||
|
||
export function getQuerySyntax(querySemver: string, modelID: 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 shouldn't need to export this? To make it clear what this function is doing, could we change the name to something like convertSemverQueryToMongoQuery()
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 have changed the name. I have exported as I test it in release.spec.ts test file.
backend/src/services/release.ts
Outdated
@@ -295,6 +335,142 @@ export async function getReleaseBySemver(user: UserInterface, modelId: string, s | |||
return release | |||
} | |||
|
|||
function getSemverQueryBounds(querySemver: 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.
Could we change this to something like parseSemverQuery()
?
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.
Changed
backend/src/services/release.ts
Outdated
const semverRangeStandardised = semver.validRange(querySemver, { includePrerelease: false }) | ||
|
||
if (!semverRangeStandardised) { | ||
throw BadReq(`Semver range, '${querySemver}' is invalid.`) |
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 general with errors, we try and avoid injecting variables into the messages. Instead you can pass in a context object containing any relevant data. Eg throw BadReq(
Semver range is invalid., {semverQuery: querySemver})
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.
Changed
backend/src/services/release.ts
Outdated
//If lower semver | ||
if (expressionA.includes('>')) { | ||
lowerInclusivity = expressionA.includes('>=') | ||
lowerSemver = expressionA.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.
To avoid having multiple variables for the upper and lower semvers and another set of if statements after this one, could you include .replace(/[<>=]/g, '')
at the start of the function semverStringToObject()
and call semverStringToObject()
here? It would also mean you'd only need to have this replace in one place
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. I still believe I need all 3 replaces as I don't know whether expressionA is lower or upper.
async () => { | ||
const testQuery = getQuerySyntax('1.0.x', 'testModelID') | ||
expect(testQuery).toBe({ | ||
modelId: 'testModelID', |
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.
For large expects like this one, I would use snapshots.
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
|
||
expect(releaseModelMocks.match.mock.calls.at(0)).toMatchSnapshot() | ||
expect(releaseModelMocks.sort.mock.calls.at(0)).toMatchSnapshot() | ||
expect(releaseModelMocks.lookup.mock.calls.at(0)).toMatchSnapshot() | ||
expect(releaseModelMocks.append.mock.calls.at(0)).toMatchSnapshot() | ||
}) | ||
|
||
//test good - give good semver range with lower and upper bounds, returns valid query | ||
//test fail - invalid range | ||
test('getQuerySyntax > good'), |
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 think getQuerySyntax
shouldn't be exported so here you would need to test calling getModelReleases with a semverQuery
and verify that the match is what you expect.
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
|
||
expect(releaseModelMocks.match.mock.calls.at(0)).toMatchSnapshot() | ||
expect(releaseModelMocks.sort.mock.calls.at(0)).toMatchSnapshot() | ||
expect(releaseModelMocks.lookup.mock.calls.at(0)).toMatchSnapshot() | ||
expect(releaseModelMocks.append.mock.calls.at(0)).toMatchSnapshot() | ||
}) | ||
|
||
//test good - give good semver range with lower and upper bounds, returns valid query | ||
//test fail - invalid range | ||
test('getQuerySyntax > good'), |
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 don't think your new tests are correctly running. The whole test including the async functions needs to be included in the call to test()
. As such this line should be test('getQuerySyntax > good',
and 432 should have the closing bracket.
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.
Make sure you look at the test coverage to check if there's anywhere you've missed.
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
@@ -56,8 +56,7 @@ async function validateRelease(user: UserInterface, model: ModelDoc, release: Re | |||
const fileNames: Array<string> = [] | |||
|
|||
for (const fileId of release.fileIds) { | |||
let file: Document<unknown, any, FileInterface> & | |||
Omit<FileInterface & Required<{ _id: Schema.Types.ObjectId }>, never> | |||
let file: FileInterface | (undefined & Omit<FileInterface & Required<{ _id: Schema.Types.ObjectId }>, never>) |
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 not sure what's happened here again. let file: FileInterfaceDoc | undefined
should be the correct type
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.
Hmmm... fixed
backend/src/services/release.ts
Outdated
upperSemver = expressionB.replace(/[<=]/g, '') | ||
} | ||
|
||
let lowerSemverObj, 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.
If you create variables without a default value TS will treat them as any
, so these should both be typed. Also defining multiple values in a single line is something we should probably avoid.
let lowerSemverObj: SemverObject | undefined
let upperSemverObj: SemverObject | 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.
Yeah removed the typing because I wanted it to be undefined, forgetting that I can give the possible type of undefined!
backend/src/services/release.ts
Outdated
upperSemver: string = '', | ||
lowerInclusivity = false, | ||
upperInclusivity = false | ||
let lowerInclusivity, lowerSemver, upperInclusivity, upperSemver |
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.
These are all any
, they should be typed. If this was a JS project I wouldn't have a problem with defining multiple vars on a single line like this, but in TS we should either be providing a default value or explicitly typing it as MyType | undefined
. As a result it's much more readable to define them on separate lines.
let var1: MyType | undefined
let var: MyOtherType | undefined
// etc
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.
Changed
backend/src/services/release.ts
Outdated
if (!semver) { | ||
return '' | ||
} | ||
let metadata: 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.
This type isn't strictly true as it's undefined
until you assign a value. Instead I'd suggest let metadata = ''
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.
Changed
WIP PR for advanced semver range queries as stated in https://docs.npmjs.com/cli/v6/using-npm/semver#caret-ranges-123-025-004