Skip to content

Commit

Permalink
Merge pull request #2094 from flowforge/2075-application-pipelines
Browse files Browse the repository at this point in the history
Application Pipelines
  • Loading branch information
Pezmc authored May 30, 2023
2 parents 21dbda3 + 441aeb8 commit 2d5d952
Show file tree
Hide file tree
Showing 45 changed files with 2,925 additions and 15 deletions.
2 changes: 1 addition & 1 deletion config/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Vue from '@vitejs/plugin-vue'

export default defineConfig({
plugins: [
Vue()
Vue(),
],
test: {
globals: true,
Expand Down
26 changes: 24 additions & 2 deletions forge/auditLog/formatters.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const isObject = (obj) => {
* @param {{ error?, team?, project?, sourceProject?, targetProject?, device?, user?, stack?, billingSession?, subscription?, license?, updates?, snapshot?, role?, projectType?, info? } == {}} objects objects to include in body
* @returns {{ error?, team?, project?, sourceProject?, targetProject?, device?, user?, stack?, billingSession?, subscription?, license?, updates?, snapshot?, role?, projectType? info? }
*/
const generateBody = ({ error, team, application, project, sourceProject, targetProject, device, user, stack, billingSession, subscription, license, updates, snapshot, role, projectType, info } = {}) => {
const generateBody = ({ error, team, application, project, sourceProject, targetProject, device, user, stack, billingSession, subscription, license, updates, snapshot, pipeline, pipelineStage, role, projectType, info } = {}) => {
const body = {}

if (isObject(error) || typeof error === 'string') {
Expand Down Expand Up @@ -57,6 +57,12 @@ const generateBody = ({ error, team, application, project, sourceProject, target
if (isObject(snapshot)) {
body.snapshot = snapshotObject(snapshot)
}
if (isObject(pipeline)) {
body.pipeline = pipelineObject(pipeline)
}
if (isObject(pipelineStage)) {
body.pipelineStage = pipelineStageObject(pipelineStage)
}
if (isObject(role) || typeof role === 'number') {
body.role = roleObject(role)
}
Expand Down Expand Up @@ -127,7 +133,9 @@ const formatLogEntry = (auditLogDbRow) => {
updates: body?.updates,
device: body?.device,
projectType: body?.projectType,
info: body?.info
info: body?.info,
pipeline: body?.pipeline,
pipelineStage: body?.pipelineStage
})
const roleObj = body?.role && roleObject(body.role)
if (roleObj) {
Expand Down Expand Up @@ -238,6 +246,20 @@ const snapshotObject = (snapshot) => {
name: snapshot?.name || null
}
}
const pipelineObject = (pipeline) => {
return {
id: pipeline?.id || null,
hashid: pipeline?.hashid || null,
name: pipeline?.name || null
}
}
const pipelineStageObject = (stage) => {
return {
id: stage?.id || null,
hashid: stage?.hashid || null,
name: stage?.name || null
}
}
const roleObject = (role) => {
if (typeof role === 'number') {
if (RoleNames[role]) {
Expand Down
3 changes: 3 additions & 0 deletions forge/auditLog/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ module.exports = {
async flowImported (actionedBy, error, project) {
await log('project.flow-imported', actionedBy, project?.id, generateBody({ error, project }))
},
async assignedToPipelineStage (actionedBy, error, project, pipeline, pipelineStage) {
await log('project.assigned-to-pipeline-stage', actionedBy, project?.id, generateBody({ error, project, pipeline, pipelineStage }))
},
device: {
async unassigned (actionedBy, error, project, device) {
await log('project.device.unassigned', actionedBy, project?.id, generateBody({ error, project, device }))
Expand Down
14 changes: 14 additions & 0 deletions forge/auditLog/team.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ module.exports = {
},
async deleted (actionedBy, error, team, application) {
await log('application.deleted', actionedBy, team?.id, generateBody({ error, team, application }))
},
pipeline: {
async created (actionedBy, error, team, application, pipeline) {
await log('application.pipeline.created', actionedBy, team?.id, generateBody({ error, team, application, pipeline }))
},
async updated (actionedBy, error, team, application, pipeline, updates) {
await log('application.pipeline.updated', actionedBy, team?.id, generateBody({ error, team, application, pipeline, updates }))
},
async deleted (actionedBy, error, team, application, pipeline) {
await log('application.pipeline.deleted', actionedBy, team?.id, generateBody({ error, team, application, pipeline }))
},
async stageAdded (actionedBy, error, team, application, pipeline, pipelineStage) {
await log('application.pipeline.stage-added', actionedBy, team?.id, generateBody({ error, team, application, pipeline, pipelineStage }))
}
}
}

Expand Down
74 changes: 74 additions & 0 deletions forge/db/migrations/20230504-01-add-pipelines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Add the ProjectStacks table
*/
const { DataTypes } = require('sequelize')

module.exports = {
up: async (context) => {
await context.createTable('Pipelines', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: { type: DataTypes.STRING, allowNull: false },

createdAt: { type: DataTypes.DATE },
updatedAt: { type: DataTypes.DATE },

ApplicationId: {
type: DataTypes.INTEGER,
references: { model: 'Applications', key: 'id' },
onDelete: 'cascade',
onUpdate: 'cascade'
}
})

await context.createTable('PipelineStages', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: { type: DataTypes.STRING, allowNull: false },

NextStageId: {
type: DataTypes.INTEGER,
allowNull: true,
references: { model: 'PipelineStages', key: 'id' },
onDelete: 'SET NULL ',
onUpdate: 'SET NULL '
},

PipelineId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'Pipelines', key: 'id' },
onDelete: 'cascade',
onUpdate: 'cascade'
},

createdAt: { type: DataTypes.DATE },
updatedAt: { type: DataTypes.DATE }
})

await context.createTable('PipelineStageInstances', {
InstanceId: {
type: DataTypes.UUID,
allowNull: false,
references: { model: 'Projects', key: 'id' },
onDelete: 'cascade',
onUpdate: 'cascade'
},
PipelineStageId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'PipelineStages', key: 'id' },
onDelete: 'cascade',
onUpdate: 'cascade'
}
})
},
down: async (context) => {
}
}
27 changes: 27 additions & 0 deletions forge/ee/db/controllers/Pipeline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

module.exports = {
addPipelineStage: async function (app, pipeline, options) {
if (!options.instanceId) {
throw new Error('Param instanceId is required when creating a new pipeline stage')
}

let source
options.PipelineId = pipeline.id
if (options.source) {
// this gives us the input stage to this new stage.
// we store "targets", so need to update the source to point to this new stage
source = options.source
delete options.source
}
const stage = await app.db.models.PipelineStage.create(options)
await stage.addInstanceId(options.instanceId)

if (source) {
const sourceStage = await app.db.models.PipelineStage.byId(source)
sourceStage.NextStageId = stage.id
await sourceStage.save()
}

return stage
}
}
3 changes: 2 additions & 1 deletion forge/ee/db/controllers/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const modelTypes = [
'Subscription',
'UserBillingCode'
'UserBillingCode',
'Pipeline'
]

async function init (app) {
Expand Down
48 changes: 48 additions & 0 deletions forge/ee/db/models/Pipeline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const {
DataTypes
} = require('sequelize')

module.exports = {
name: 'Pipeline',
schema: {
name: {
type: DataTypes.STRING,
allowNull: false
}
},
associations: function (M) {
this.belongsTo(M.Application)
this.hasMany(M.PipelineStage)
},
finders: function (M) {
const self = this
return {
instance: {
stages: async function () {
return await M.PipelineStage.byPipeline(this.id)
}
},
static: {
byId: async function (idOrHash) {
let id = idOrHash
if (typeof idOrHash === 'string') {
id = M.Pipeline.decodeHashid(idOrHash)
}
return this.findOne({
where: { id }
})
},
byApplicationId: async function (applicationId) {
if (typeof applicationId === 'string') {
applicationId = M.Application.decodeHashid(applicationId)
}
return self.findAll({
where: {
ApplicationId: applicationId
}
})
}
}
}
}
}
97 changes: 97 additions & 0 deletions forge/ee/db/models/PipelineStage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const {
DataTypes
} = require('sequelize')

module.exports = {
name: 'PipelineStage',
schema: {
name: {
type: DataTypes.STRING,
allowNull: false
},

NextStageId: {
type: DataTypes.INTEGER,
allowNull: true
}
},
options: {
validate: {
async instancesHaveSameApplication () {
const instancesPromise = this.getInstances()
const pipelinePromise = this.getPipeline()

const instances = await instancesPromise
const pipeline = await pipelinePromise

instances.forEach((instance) => {
if (instance.ApplicationId !== pipeline.ApplicationId) {
throw new Error(`All instances on a pipeline stage, must be a member of the same application as the pipeline. ${instance.name} is not a member of application ${pipeline.ApplicationId}.`)
}
})
}
}
},
associations: function (M) {
this.belongsTo(M.Pipeline)
this.belongsToMany(M.Project, { through: M.PipelineStageInstance, as: 'Instances', otherKey: 'InstanceId' })
this.hasOne(M.PipelineStage, { as: 'NextStage', foreignKey: 'NextStageId', allowNull: true })
},
finders: function (M) {
const self = this
return {
instance: {
async addInstanceId (instanceId) {
const instance = await M.Project.byId(instanceId)
if (!instance) {
throw new Error('instanceId not found')
}

await this.addInstance(instance)
}
},
static: {
byId: async function (idOrHash) {
let id = idOrHash
if (typeof idOrHash === 'string') {
id = M.PipelineStage.decodeHashid(idOrHash)
}
return this.findOne({
where: { id },
include: [
{
association: 'Instances',
attributes: ['hashid', 'id', 'name', 'url', 'updatedAt']
}
]
})
},
byPipeline: async function (pipelineId) {
if (typeof pipelineId === 'string') {
pipelineId = M.Pipeline.decodeHashid(pipelineId)
}
return await self.findAll({
where: {
PipelineId: pipelineId
},
include: [
{
association: 'Instances',
attributes: ['hashid', 'id', 'name', 'url', 'updatedAt']
}
]
})
},
byNextStage: async function (idOrHash) {
let id = idOrHash
if (typeof idOrHash === 'string') {
id = M.PipelineStage.decodeHashid(idOrHash)
}
return this.findOne({
where: { NextStageId: id }
})
}
}
}
}
}
20 changes: 20 additions & 0 deletions forge/ee/db/models/PipelineStageInstance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* This is the many:1 association model between a Project and a PipelineStage
* @namespace forge.db.models.PipelineStageInstance
*/

module.exports = {
name: 'PipelineStageInstance',
options: {
timestamps: false
},
associations: function (M) {
this.belongsTo(M.PipelineStage)
this.belongsTo(M.Project, { as: 'Instance' }) // @TODO: need to guard that the instance is part of the same application that owns the stage
},
meta: {
slug: false,
hashid: false,
links: false
}
}
Loading

0 comments on commit 2d5d952

Please sign in to comment.