-
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 18 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Release from '../models/Release.js' | ||
|
||
export async function up() { | ||
const releases = await Release.find({}) | ||
for (const release of releases) { | ||
const semver = release.get('semver') | ||
if (semver !== undefined && typeof semver === typeof '') { | ||
release.set('semver', semver) | ||
await release.save() | ||
} | ||
} | ||
} | ||
|
||
export async function down() { | ||
/* NOOP */ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
import { Document, Schema } from 'mongoose' | ||
import semver from 'semver' | ||
import { Optional } from 'utility-types' | ||
|
||
import { ReleaseAction } from '../connectors/authorisation/actions.js' | ||
import authorisation from '../connectors/authorisation/index.js' | ||
import { FileInterface } from '../models/File.js' | ||
import { ModelDoc, ModelInterface } from '../models/Model.js' | ||
import Release, { ImageRef, ReleaseDoc, ReleaseInterface } from '../models/Release.js' | ||
import Release, { ImageRef, ReleaseDoc, ReleaseInterface, SemverObject } from '../models/Release.js' | ||
import ResponseModel, { ResponseKind } from '../models/Response.js' | ||
import { UserInterface } from '../models/User.js' | ||
import { WebhookEvent } from '../models/Webhook.js' | ||
|
@@ -55,7 +56,8 @@ | |
const fileNames: Array<string> = [] | ||
|
||
for (const fileId of release.fileIds) { | ||
let file | ||
let file: Document<unknown, any, FileInterface> & | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Omit<FileInterface & Required<{ _id: Schema.Types.ObjectId }>, never> | ||
try { | ||
file = await getFileById(user, fileId) | ||
} catch (e) { | ||
|
@@ -237,9 +239,11 @@ | |
export async function getModelReleases( | ||
user: UserInterface, | ||
modelId: string, | ||
q?: string, | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
): Promise<Array<ReleaseDoc & { model: ModelInterface; files: FileInterface[] }>> { | ||
const query = q === undefined ? { modelId } : getQuerySyntax(q, modelId) | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const results = await Release.aggregate() | ||
.match({ modelId }) | ||
.match(query!) | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.sort({ updatedAt: -1 }) | ||
.lookup({ from: 'v2_models', localField: 'modelId', foreignField: 'id', as: 'model' }) | ||
.append({ $set: { model: { $arrayElemAt: ['$model', 0] } } }) | ||
|
@@ -248,7 +252,9 @@ | |
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 | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.filter((_, i) => auths[i].success) | ||
.map((result) => ({ ...result, semver: semverObjectToString(result.semver) })) | ||
Check failure on line 257 in backend/src/services/release.ts GitHub Actions / unit_testingtest/services/release.spec.ts > services > release > getModelReleases > good
|
||
} | ||
|
||
export async function getReleasesForExport(user: UserInterface, modelId: string, semvers: string[]) { | ||
|
@@ -276,11 +282,36 @@ | |
return releases | ||
} | ||
|
||
export function semverStringToObject(semver: string) { | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const vIdentifierIndex = semver.indexOf('v') | ||
const trimmedSemver = vIdentifierIndex === -1 ? semver : semver.slice(vIdentifierIndex + 1) | ||
const [version, metadata] = trimmedSemver.split('-') | ||
const [major, minor, patch] = version.split('.') | ||
const majorNum: number = Number(major) | ||
const minorNum: number = Number(minor) | ||
const patchNum: number = Number(patch) | ||
return { major: majorNum, minor: minorNum, patch: patchNum, ...(metadata && { metadata }) } | ||
} | ||
|
||
export function semverObjectToString(semver: SemverObject) { | ||
if (!semver) { | ||
return '' | ||
} | ||
let metadata: 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. This type isn't strictly true as it's 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 (semver.metadata != undefined) { | ||
metadata = `-${semver.metadata}` | ||
} else { | ||
metadata = `` | ||
} | ||
return `${semver.major}.${semver.minor}.${semver.patch}${metadata}` | ||
} | ||
|
||
export async function getReleaseBySemver(user: UserInterface, modelId: string, semver: string) { | ||
const model = await getModelById(user, modelId) | ||
const semverObj = semverStringToObject(semver) | ||
const release = await Release.findOne({ | ||
modelId, | ||
semver, | ||
semver: semverObj, | ||
}) | ||
|
||
if (!release) { | ||
|
@@ -295,6 +326,168 @@ | |
return release | ||
} | ||
|
||
function getSemverQueryBounds(querySemver: 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. Could we change this 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. Changed |
||
const semverRangeStandardised = semver.validRange(querySemver, { includePrerelease: false }) | ||
|
||
if (!semverRangeStandardised) { | ||
throw BadReq(`Semver range, '${querySemver}' is invalid `) | ||
} | ||
|
||
const [expressionA, expressionB] = semverRangeStandardised.split(' ') | ||
|
||
let lowerSemver: string = '', | ||
upperSemver: string = '', | ||
lowerInclusivity = false, | ||
upperInclusivity = false | ||
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('<', '') | ||
|
||
} | ||
} | ||
} else { | ||
if (expressionA.includes('=')) { | ||
upperSemver = expressionA.replace('<=', '') | ||
upperInclusivity = true | ||
} else { | ||
upperSemver = expressionA.replace('<', '') | ||
|
||
} | ||
} | ||
|
||
let lowerSemverObj: { metadata?: string | undefined; major: number; minor: number; patch: number } = { | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
major: NaN, | ||
minor: NaN, | ||
patch: NaN, | ||
}, | ||
upperSemverObj: { metadata?: string | undefined; major: number; minor: number; patch: number } = { | ||
ARADDCC012 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
major: NaN, | ||
minor: NaN, | ||
patch: NaN, | ||
} | ||
if (lowerSemver) { | ||
lowerSemverObj = semverStringToObject(lowerSemver) | ||
} | ||
|
||
if (upperSemver) { | ||
upperSemverObj = semverStringToObject(upperSemver) | ||
} | ||
|
||
return { lowerSemverObj, upperSemverObj, lowerInclusivity, upperInclusivity } | ||
} | ||
|
||
function getQuerySyntax(querySemver: string | undefined, modelID: string) { | ||
if (querySemver === undefined) { | ||
return { | ||
modelId: modelID, | ||
} | ||
} | ||
|
||
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[] = [] | ||
|
||
if (lowerSemverObj) { | ||
const lowerQuery: QueryBoundInterface = { | ||
$or: [ | ||
{ | ||
'semver.major': { $gte: lowerSemverObj.major }, | ||
'semver.minor': { $gte: lowerSemverObj.minor }, | ||
'semver.patch': lowerInclusivity ? { $gte: lowerSemverObj.patch } : { $gt: lowerSemverObj.patch }, | ||
}, | ||
{ | ||
'semver.major': { $gt: lowerSemverObj.major }, | ||
}, | ||
{ | ||
'semver.major': { $gte: lowerSemverObj.major }, | ||
'semver.minor': { $gt: lowerSemverObj.minor }, | ||
}, | ||
], | ||
} | ||
queryQueue.push(lowerQuery) | ||
} | ||
|
||
if (upperSemverObj) { | ||
const upperQuery: QueryBoundInterface = { | ||
$or: [ | ||
{ | ||
'semver.major': { $lte: upperSemverObj.major }, | ||
'semver.minor': { $lte: upperSemverObj.minor }, | ||
'semver.patch': upperInclusivity ? { $lte: upperSemverObj.patch } : { $lt: upperSemverObj.patch }, | ||
}, | ||
{ | ||
'semver.major': { $lt: upperSemverObj.major }, | ||
}, | ||
{ | ||
'semver.major': { $lte: upperSemverObj.major }, | ||
'semver.minor': { $lt: upperSemverObj.minor }, | ||
}, | ||
], | ||
} | ||
queryQueue.push(upperQuery) | ||
} | ||
|
||
const combinedQuery = { | ||
modelId: modelID, | ||
$and: queryQueue, | ||
} | ||
|
||
return combinedQuery | ||
} | ||
|
||
export async function deleteRelease(user: UserInterface, modelId: string, semver: string) { | ||
const model = await getModelById(user, modelId) | ||
if (model.settings.mirror.sourceModelId) { | ||
|
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