-
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
Changes from 13 commits
3ad2e86
1c421d7
9e10ab3
ae35fc8
82bb578
2d8b532
a7b05b8
725eb87
da9110c
6df897e
d050140
1e56d7b
ce0f95e
8e7f254
235f1f3
47106bd
ffc7b03
0215e0e
3915659
b5658be
f2ff99f
9604ff1
f532454
223dbad
fcf9a0a
0bc56a7
84dfdac
9f6eb68
d15fe16
0546f95
5118315
ae80725
8f31592
f8ca356
d6f486f
8d0cb76
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,4 +1,4 @@ | ||
import { Document, Schema } from 'mongoose' | ||
import { Schema } from 'mongoose' | ||
import semver from 'semver' | ||
import { Optional } from 'utility-types' | ||
|
||
|
@@ -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>) | ||
try { | ||
file = await getFileById(user, fileId) | ||
} catch (e) { | ||
|
@@ -198,7 +197,10 @@ export async function updateRelease(user: UserInterface, modelId: string, semver | |
}) | ||
} | ||
|
||
const updatedRelease = await Release.findOneAndUpdate({ modelId, semver }, { $set: release }) | ||
const updatedRelease = await Release.findOneAndUpdate( | ||
{ modelId, semver: semverStringToObject(semver) }, | ||
{ $set: release }, | ||
) | ||
|
||
if (!updatedRelease) { | ||
throw NotFound(`The requested release was not found.`, { modelId, semver }) | ||
|
@@ -239,11 +241,11 @@ export async function newReleaseComment(user: UserInterface, modelId: string, se | |
export async function getModelReleases( | ||
user: UserInterface, | ||
modelId: string, | ||
q?: string, | ||
querySemver?: string, | ||
): Promise<Array<ReleaseDoc & { model: ModelInterface; files: FileInterface[] }>> { | ||
const query = q === undefined ? { modelId } : getQuerySyntax(q, modelId) | ||
const query = querySemver === undefined ? { modelId } : getQuerySyntax(querySemver, modelId) | ||
const results = await Release.aggregate() | ||
.match(query!) | ||
.match(query) | ||
.sort({ updatedAt: -1 }) | ||
.lookup({ from: 'v2_models', localField: 'modelId', foreignField: 'id', as: 'model' }) | ||
.append({ $set: { model: { $arrayElemAt: ['$model', 0] } } }) | ||
|
@@ -252,9 +254,16 @@ 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) | ||
.map((result) => ({ ...result, semver: semverObjectToString(result.semver) })) | ||
return results.reduce<Array<ReleaseDoc & { model: ModelInterface; files: FileInterface[] }>>( | ||
(updatedResults, result, index) => { | ||
if (auths[index].success) { | ||
updatedResults.push({ ...result, semver: semverObjectToString(result.semver) }) | ||
} | ||
|
||
return updatedResults | ||
}, | ||
[], | ||
) | ||
} | ||
|
||
export async function getReleasesForExport(user: UserInterface, modelId: string, semvers: string[]) { | ||
|
@@ -282,7 +291,7 @@ export async function getReleasesForExport(user: UserInterface, modelId: string, | |
return releases | ||
} | ||
|
||
export function semverStringToObject(semver: string) { | ||
export function semverStringToObject(semver: string): SemverObject { | ||
const vIdentifierIndex = semver.indexOf('v') | ||
const trimmedSemver = vIdentifierIndex === -1 ? semver : semver.slice(vIdentifierIndex + 1) | ||
const [version, metadata] = trimmedSemver.split('-') | ||
|
@@ -293,7 +302,7 @@ export function semverStringToObject(semver: string) { | |
return { major: majorNum, minor: minorNum, patch: patchNum, ...(metadata && { metadata }) } | ||
} | ||
|
||
export function semverObjectToString(semver: SemverObject) { | ||
export function semverObjectToString(semver: SemverObject): string { | ||
if (!semver) { | ||
return '' | ||
} | ||
|
@@ -330,50 +339,29 @@ function getSemverQueryBounds(querySemver: string) { | |
const semverRangeStandardised = semver.validRange(querySemver, { includePrerelease: false }) | ||
|
||
if (!semverRangeStandardised) { | ||
throw BadReq(`Semver range, '${querySemver}' is invalid `) | ||
throw BadReq(`Semver range, '${querySemver}' is invalid.`) | ||
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. 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 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. Changed |
||
} | ||
|
||
const [expressionA, expressionB] = semverRangeStandardised.split(' ') | ||
|
||
let lowerSemver: string = '', | ||
upperSemver: string = '', | ||
lowerInclusivity = false, | ||
upperInclusivity = false | ||
let lowerInclusivity, lowerSemver, upperInclusivity, upperSemver | ||
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. These are all
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. Changed |
||
|
||
//If lower semver | ||
if (expressionA.includes('>')) { | ||
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. When an if statement becomes this complex it's always worth seeing if it could be simplified.
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. 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 |
||
if (expressionA.includes('=')) { | ||
lowerSemver = expressionA.replace('>=', '') | ||
lowerInclusivity = true | ||
} else { | ||
lowerSemver = expressionA.replace('>', '') | ||
} | ||
//do check for expressionB | ||
if (expressionB) { | ||
if (expressionB.includes('=')) { | ||
upperSemver = expressionB.replace('<=', '') | ||
upperInclusivity = true | ||
} else { | ||
upperSemver = expressionB.replace('<', '') | ||
} | ||
} | ||
lowerInclusivity = expressionA.includes('>=') | ||
lowerSemver = expressionA.replace(/[<>=]/g, '') | ||
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. To avoid having multiple variables for the upper and lower semvers and another set of if statements after this one, could you include 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. Done. I still believe I need all 3 replaces as I don't know whether expressionA is lower or upper. |
||
} else { | ||
if (expressionA.includes('=')) { | ||
upperSemver = expressionA.replace('<=', '') | ||
upperInclusivity = true | ||
} else { | ||
upperSemver = expressionA.replace('<', '') | ||
} | ||
//upper semver | ||
upperInclusivity = expressionA.includes('<=') | ||
upperSemver = expressionA.replace(/[<=]/g, '') | ||
} | ||
|
||
let lowerSemverObj: { metadata?: string | undefined; major: number; minor: number; patch: number } = { | ||
major: NaN, | ||
minor: NaN, | ||
patch: NaN, | ||
}, | ||
upperSemverObj: { metadata?: string | undefined; major: number; minor: number; patch: number } = { | ||
major: NaN, | ||
minor: NaN, | ||
patch: NaN, | ||
} | ||
if (expressionB) { | ||
upperInclusivity = expressionB.includes('<=') | ||
upperSemver = expressionB.replace(/[<=]/g, '') | ||
} | ||
|
||
let lowerSemverObj, upperSemverObj | ||
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. If you create variables without a default value TS will treat them as
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. Yeah removed the typing because I wanted it to be undefined, forgetting that I can give the possible type of undefined! |
||
if (lowerSemver) { | ||
lowerSemverObj = semverStringToObject(lowerSemver) | ||
} | ||
|
@@ -385,58 +373,53 @@ function getSemverQueryBounds(querySemver: string) { | |
return { lowerSemverObj, upperSemverObj, lowerInclusivity, upperInclusivity } | ||
} | ||
|
||
function getQuerySyntax(querySemver: string | undefined, modelID: string) { | ||
if (querySemver === undefined) { | ||
return { | ||
modelId: modelID, | ||
} | ||
} | ||
interface QueryBoundInterface { | ||
$or: ( | ||
| { | ||
'semver.major': | ||
| { | ||
$lte: number | ||
} | ||
| { | ||
$gte: number | ||
} | ||
'semver.minor': | ||
| { | ||
$lte: number | ||
} | ||
| { $gte: number } | ||
'semver.patch': | ||
| { | ||
$gte: number | ||
} | ||
| { $gt: number } | ||
| { $lte: number } | ||
| { $lt: number } | ||
} | ||
| { | ||
'semver.major': | ||
| { | ||
$gt: number | ||
} | ||
| { $lt: number } | ||
} | ||
| { | ||
'semver.major': | ||
| { | ||
$gte: number | ||
} | ||
| { $lte: number } | ||
'semver.minor': | ||
| { | ||
$gt: number | ||
} | ||
| { $lt: number } | ||
} | ||
)[] | ||
} | ||
|
||
export function getQuerySyntax(querySemver: string, modelID: string) { | ||
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. We shouldn't need to export this? To make it clear what this function is doing, could we change the name to something like 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. I have changed the name. I have exported as I test it in release.spec.ts test file. |
||
const { lowerSemverObj, upperSemverObj, lowerInclusivity, upperInclusivity } = getSemverQueryBounds(querySemver) | ||
interface QueryBoundInterface { | ||
$or: ( | ||
| { | ||
'semver.major': | ||
| { | ||
$lte: number | ||
} | ||
| { | ||
$gte: number | ||
} | ||
'semver.minor': | ||
| { | ||
$lte: number | ||
} | ||
| { $gte: number } | ||
'semver.patch': | ||
| { | ||
$gte: number | ||
} | ||
| { $gt: number } | ||
| { $lte: number } | ||
| { $lt: number } | ||
} | ||
| { | ||
'semver.major': | ||
| { | ||
$gt: number | ||
} | ||
| { $lt: number } | ||
} | ||
| { | ||
'semver.major': | ||
| { | ||
$gte: number | ||
} | ||
| { $lte: number } | ||
'semver.minor': | ||
| { | ||
$gt: number | ||
} | ||
| { $lt: number } | ||
} | ||
)[] | ||
} | ||
|
||
const queryQueue: QueryBoundInterface[] = [] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,9 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`services > release > getModelReleases > good 1`] = ` | ||
[ | ||
{ | ||
"modelId": "modelId", | ||
}, | ||
] | ||
`; | ||
exports[`services > release > getModelReleases > good 1`] = `undefined`; | ||
|
||
exports[`services > release > getModelReleases > good 2`] = ` | ||
[ | ||
{ | ||
"updatedAt": -1, | ||
}, | ||
] | ||
`; | ||
exports[`services > release > getModelReleases > good 2`] = `undefined`; | ||
|
||
exports[`services > release > getModelReleases > good 3`] = ` | ||
[ | ||
{ | ||
"as": "model", | ||
"foreignField": "id", | ||
"from": "v2_models", | ||
"localField": "modelId", | ||
}, | ||
] | ||
`; | ||
exports[`services > release > getModelReleases > good 3`] = `undefined`; | ||
|
||
exports[`services > release > getModelReleases > good 4`] = ` | ||
[ | ||
{ | ||
"$set": { | ||
"model": { | ||
"$arrayElemAt": [ | ||
"$model", | ||
0, | ||
], | ||
}, | ||
}, | ||
}, | ||
] | ||
`; | ||
exports[`services > release > getModelReleases > good 4`] = `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.
I'm not sure what's happened here again.
let file: FileInterfaceDoc | undefined
should be the correct typeThere 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