Skip to content

Commit

Permalink
Merge pull request #4535 from FlowFuse/4534-bom-api-use-project-model…
Browse files Browse the repository at this point in the history
…-versions

 Update BOM API use project model versions
  • Loading branch information
Steve-Mcl authored Sep 23, 2024
2 parents 45c992a + 594e0c7 commit 452a16a
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 63 deletions.
37 changes: 17 additions & 20 deletions forge/db/models/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,28 +214,25 @@ module.exports = {
const child = children.get(instance)
const deps = {}
deps['node-red'] = {
semver: instance.ProjectStack?.properties?.nodered,
installed: instance.ProjectStack?.properties?.nodered
}
if (instance.ProjectStack?.replacedBy) {
const replacementStack = await app.db.models.ProjectStack.byId(instance.ProjectStack.replacedBy)
deps['node-red'].semver = replacementStack?.properties?.nodered
wanted: instance.versions?.['node-red']?.wanted,
current: instance.versions?.['node-red']?.current
}

const settings = await instance.getSetting(KEY_SETTINGS)
if (Array.isArray(settings?.palette?.modules)) {
settings.palette.modules.forEach(m => {
deps[m.name] = {
semver: m.version
wanted: m.version
}
})
}

const projectModules = await storageController.getProjectModules(child.model) || []
projectModules.forEach(m => {
deps[m.name] = deps[m.name] || {}
deps[m.name].installed = m.version
if (!deps[m.name].semver) {
deps[m.name].semver = m.version
deps[m.name].current = m.version
if (!deps[m.name].wanted) {
deps[m.name].wanted = m.version
}
})
child.dependencies = deps
Expand Down Expand Up @@ -272,41 +269,41 @@ module.exports = {
if (activeModulesInstalled?.length) {
activeModulesInstalled.forEach(m => {
deps[m.name] = deps[m.name] || {}
deps[m.name].installed = m.version
deps[m.name].current = m.version
})
}
if (targetModulesSemver?.length) {
targetModulesSemver.forEach(m => {
deps[m.name] = deps[m.name] || {}
deps[m.name].semver = m.version
deps[m.name].wanted = m.version
})
} else if (activeModulesSemver?.length) {
activeModulesSemver.forEach(m => {
deps[m.name] = deps[m.name] || {}
deps[m.name].semver = m.version
deps[m.name].wanted = m.version
})
} else if (device.ownerType === 'application' && !targetSnapshot && !activeSnapshot) {
// if the device has no snapshots, use the default snapshot data
Object.entries(defaultModules).forEach(([name, version]) => {
deps[name] = deps[name] || {}
deps[name].semver = version
deps[name].wanted = version
})
}

// some devices dont get informed of the @flowfuse/nr-project-nodes or '@flowfuse/nr-assistant' to install due being included
// via nodesdir or other means. In this case, we will use the installed version as the semver
if (deps['@flowfuse/nr-project-nodes'] && deps['@flowfuse/nr-project-nodes'].installed && !deps['@flowfuse/nr-project-nodes'].semver) {
deps['@flowfuse/nr-project-nodes'].semver = deps['@flowfuse/nr-project-nodes'].installed
if (deps['@flowfuse/nr-project-nodes'] && deps['@flowfuse/nr-project-nodes'].current && !deps['@flowfuse/nr-project-nodes'].wanted) {
deps['@flowfuse/nr-project-nodes'].wanted = deps['@flowfuse/nr-project-nodes'].current
}
if (deps['@flowfuse/nr-assistant'] && deps['@flowfuse/nr-assistant'].installed && !deps['@flowfuse/nr-assistant'].semver) {
deps['@flowfuse/nr-assistant'].semver = deps['@flowfuse/nr-assistant'].installed
if (deps['@flowfuse/nr-assistant'] && deps['@flowfuse/nr-assistant'].current && !deps['@flowfuse/nr-assistant'].wanted) {
deps['@flowfuse/nr-assistant'].wanted = deps['@flowfuse/nr-assistant'].current
}

const noderedVersionInstalled = await getDeviceNodeRedVersion(device, activeModulesInstalled) || '*'
const noderedVersionSemver = await getDeviceNodeRedVersion(device, targetModulesSemver) || '*'
deps['node-red'] = {
semver: noderedVersionSemver,
installed: noderedVersionInstalled
wanted: noderedVersionSemver,
current: noderedVersionInstalled
}

child.dependencies = deps
Expand Down
10 changes: 5 additions & 5 deletions forge/db/views/BOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ module.exports = {
version: {
type: 'object',
properties: {
semver: { type: 'string' },
installed: { type: 'string', nullable: true }
wanted: { type: 'string' },
current: { type: 'string', nullable: true }
}
}
}
Expand Down Expand Up @@ -42,8 +42,8 @@ module.exports = {
const result = {
name,
version: {
semver: semverVersion,
installed: installedVersion || null
wanted: semverVersion,
current: installedVersion || null
}
}
return result
Expand All @@ -52,7 +52,7 @@ module.exports = {
dependant (model, dependencies) {
const type = model instanceof app.db.models.Project ? 'instance' : model instanceof app.db.models.Device ? 'device' : null
if (type !== null) {
const dependenciesArray = Object.entries(dependencies || {}).map(([name, version]) => app.db.views.BOM.dependency(name, version?.semver, version?.installed))
const dependenciesArray = Object.entries(dependencies || {}).map(([name, version]) => app.db.views.BOM.dependency(name, version?.wanted, version?.current))
if (type === 'device') {
const { hashid, name, ownerType } = model
let ownerId = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default {
.reduce((acc, currentInstance) => {
currentInstance.dependencies.forEach(dep => {
const searchTerm = this.searchTerm.trim()
const installedDependencyVersion = dep.version?.installed ?? dep.version?.semver ?? 'N/A'
const installedDependencyVersion = dep.version?.current ?? dep.version?.wanted ?? 'N/A'
const dependencyNameMatchesSearch = dep.name.toLowerCase().includes(searchTerm.toLowerCase())
const dependencyVersionMatchesSearch = installedDependencyVersion.toLowerCase().includes(searchTerm.toLowerCase())
const matchesInstanceName = currentInstance.name.toLowerCase().includes(searchTerm.toLowerCase())
Expand Down
52 changes: 26 additions & 26 deletions test/e2e/frontend/cypress/fixtures/applications/dependencies.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,22 @@
{
"name": "@package/dep-1",
"version": {
"semver": "1.1.1",
"installed": "1.1.1"
"wanted": "1.1.1",
"current": "1.1.1"
}
},
{
"name": "@package/var-2",
"version": {
"semver": "~1.15.0",
"installed": "1.15.0"
"wanted": "~1.15.0",
"current": "1.15.0"
}
},
{
"name": "@external/sem-3",
"version": {
"semver": "~2.1.1",
"installed": "2.1.1"
"wanted": "~2.1.1",
"current": "2.1.1"
}
}
]
Expand All @@ -73,22 +73,22 @@
{
"name": "@package/dep-1",
"version": {
"semver": "2.2.2",
"installed": "2.2.2"
"wanted": "2.2.2",
"current": "2.2.2"
}
},
{
"name": "@package/var-2",
"version": {
"semver": "3.3.3",
"installed": "3.3.3"
"wanted": "3.3.3",
"current": "3.3.3"
}
},
{
"name": "@external/sem-3",
"version": {
"semver": "4.4.4",
"installed": "4.4.4"
"wanted": "4.4.4",
"current": "4.4.4"
}
}
]
Expand All @@ -101,29 +101,29 @@
{
"name": "@package/dep-1",
"version": {
"semver": "5.5.5",
"installed": "5.5.5"
"wanted": "5.5.5",
"current": "5.5.5"
}
},
{
"name": "@package/var-2",
"version": {
"semver": "6.6.6",
"installed": "7.7.7"
"wanted": "6.6.6",
"current": "7.7.7"
}
},
{
"name": "@external/sem-3",
"version": {
"semver": "8.8.8",
"installed": "8.8.8"
"wanted": "8.8.8",
"current": "8.8.8"
}
},
{
"name": "@lonely/package-1",
"version": {
"semver": "1.0.0",
"installed": "1.0.0"
"wanted": "1.0.0",
"current": "1.0.0"
}
}
]
Expand All @@ -136,8 +136,8 @@
{
"name": "@package/var-2",
"version": {
"semver": "6.6.6",
"installed": "7.7.7"
"wanted": "6.6.6",
"current": "7.7.7"
}
}
]
Expand All @@ -150,8 +150,8 @@
{
"name": "@external/sem-3",
"version": {
"semver": "8.8.8",
"installed": "8.8.8"
"wanted": "8.8.8",
"current": "8.8.8"
}
}
]
Expand All @@ -164,8 +164,8 @@
{
"name": "@some-package/var",
"version": {
"semver": "1.2.0",
"installed": "1.1.1"
"wanted": "1.2.0",
"current": "1.1.1"
}
}
]
Expand Down
20 changes: 10 additions & 10 deletions test/unit/forge/db/models/Application_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Application model', function () {
after(async function () {
await app.close()
})
let Application, Project, Team, Device
let Application, Project, Team
const nameGenerator = (name) => `${name} ${Math.random().toString(36).substring(7)}`
const newProject = async (applicationId, teamId, projectTypeId, projectStackId, projectTemplateId) => {
const name = nameGenerator('Test Project')
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('Application model', function () {
describe('Relations', function () {
beforeEach(async () => {
app.license.defaults.instances = 20; // override default
({ Application, Project, Team, Device } = app.db.models)
({ Application, Project, Team } = app.db.models)
})

it('should delete Application when team is deleted', async () => {
Expand Down Expand Up @@ -87,17 +87,17 @@ describe('Application model', function () {
const device2 = children.find(c => c.type === 'device' && c.model.id === instDevice.id)

instance.should.be.an.Object()
instance.model.should.be.an.instanceOf(Project)
instance.model.should.be.an.instanceOf(app.db.models.Project)
instance.should.not.have.property('dependencies')

device1.should.be.an.Object()
device1.model.should.be.an.instanceOf(Device)
device1.model.should.be.an.instanceOf(app.db.models.Device)
device1.should.have.property('ownerId', application.id)
device1.should.have.property('ownerType', 'application')
device1.should.not.have.property('dependencies')

device2.should.be.an.Object()
device2.model.should.be.an.instanceOf(Device)
device2.model.should.be.an.instanceOf(app.db.models.Device)
device2.should.have.property('ownerType', 'instance')
device2.should.have.property('ownerId', project.id)
device2.should.not.have.property('dependencies')
Expand All @@ -121,23 +121,23 @@ describe('Application model', function () {
Object.entries(item.dependencies).forEach(([key, value]) => {
key.should.be.a.String()
value.should.be.an.Object()
value.should.have.property('semver')
value.should.have.property('installed')
value.should.have.property('wanted')
value.should.have.property('current')
})
}

instance.should.be.an.Object()
instance.model.should.be.an.instanceOf(Project)
instance.model.should.be.an.instanceOf(app.db.models.Project)
checkDeps(instance)

device1.should.be.an.Object()
device1.model.should.be.an.instanceOf(Device)
device1.model.should.be.an.instanceOf(app.db.models.Device)
device1.should.have.property('ownerId', application.id)
device1.should.have.property('ownerType', 'application')
checkDeps(device1)

device2.should.be.an.Object()
device2.model.should.be.an.instanceOf(Device)
device2.model.should.be.an.instanceOf(app.db.models.Device)
device2.should.have.property('ownerType', 'instance')
device2.should.have.property('ownerId', project.id)
checkDeps(device2)
Expand Down
6 changes: 5 additions & 1 deletion test/unit/forge/ee/routes/api/application_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ describe('Application API', function () {
TestObjects.instance = await factory.createInstance({ name: generateName('B-team-instance') }, TestObjects.application, app.stack, app.template, app.projectType, { start: false })
TestObjects.device1 = await factory.createDevice({ name: generateName('device') }, TestObjects.ATeam, TestObjects.instance, null)
TestObjects.device2 = await factory.createDevice({ name: generateName('device') }, TestObjects.BTeam, null, TestObjects.application)

// fake the instance `versions` to test BOM
await TestObjects.instance.update({ versions: { 'node-red': { wanted: '4.0.3', current: '4.0.2' } } })
})

async function login (username, password) {
Expand Down Expand Up @@ -115,7 +118,7 @@ describe('Application API', function () {
item.dependencies.forEach(dep => {
should(dep).be.an.Object()
dep.should.have.properties('name', 'version')
dep.version.should.have.properties('semver', 'installed')
dep.version.should.have.properties('wanted', 'current')
})
}

Expand All @@ -130,6 +133,7 @@ describe('Application API', function () {
should(instance).be.an.Object()
instance.should.have.property('name', TestObjects.instance.name)
instance.should.have.property('type', 'instance')
instance.dependencies.should.matchAny({ name: 'node-red', version: { wanted: '4.0.3', current: '4.0.2' } })
depsCheck(instance)

const device1 = result.children.find(c => c.type === 'device' && c.id === TestObjects.device1.hashid)
Expand Down

0 comments on commit 452a16a

Please sign in to comment.