Skip to content

Commit

Permalink
lots of changes around publishing when changes happen for a app strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
rvowles committed Dec 7, 2024
1 parent a8ebd68 commit 9696113
Show file tree
Hide file tree
Showing 27 changed files with 351 additions and 211 deletions.
12 changes: 6 additions & 6 deletions backend/mr-api/application-strategies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,6 @@ components:
description: "unique id of the strategy"
type: string
format: uuid
usage:
description: "usage of the strategy"
type: array
nullable: true
items:
$ref: "#/components/schemas/ApplicationRolloutStrategyEnvironment"
ListApplicationRolloutStrategyItemUser:
type: object
properties:
Expand All @@ -335,6 +329,12 @@ components:
format: date-time
updatedBy:
$ref: "#/components/schemas/ListApplicationRolloutStrategyItemUser"
usage:
description: "usage of the strategy"
type: array
nullable: true
items:
$ref: "#/components/schemas/ApplicationRolloutStrategyEnvironment"
ApplicationRolloutStrategyEnvironment:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface ApplicationRolloutStrategyApi {
fun updateStrategy(
appId: UUID,
strategyId: UUID,
rolloutStrategy: UpdateApplicationRolloutStrategy,
update: UpdateApplicationRolloutStrategy,
person: UUID,
opts: Opts
): ApplicationRolloutStrategy?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ open class ConvertUtils @Inject constructor(
if (opts!!.contains(FillOpts.Members)) {
val org = if (dbg.owningOrganization == null) dbg.owningPortfolio.organization else dbg.owningOrganization
group.members = QDbPerson()
.order().name.asc().whenArchived.isNull.groupMembers.group.eq(dbg).findList()
.orderBy().name.asc().whenArchived.isNull.groupMembers.group.eq(dbg).findList()
.map { p: DbPerson? ->
this.toPerson(
p, org, opts.minus(FillOpts.Members, FillOpts.Acls, FillOpts.Groups)
Expand Down Expand Up @@ -594,23 +594,6 @@ open class ConvertUtils @Inject constructor(

val info = rs.strategy

opts?.contains(FillOpts.Usage)?.let {
val envs = mutableMapOf<UUID,ApplicationRolloutStrategyEnvironment>()

QDbFeatureValue()
.sharedRolloutStrategies.rolloutStrategy.id.eq(rs.id)
.environment.fetch(QDbEnvironment.Alias.id, QDbEnvironment.Alias.name)
.select(QDbFeatureValue.Alias.id).findList().forEach { fv ->
envs.getOrPut(fv.environment.id) { ApplicationRolloutStrategyEnvironment().featuresCount(0) }.let { env ->
env.id = fv.environment.id
env.name = fv.environment.name
env.featuresCount += 1
}
}

info.usage = envs.values.toMutableList()
}

// if (opts!!.contains(FillOpts.SimplePeople)) {
// info.changedBy(toPerson(rs.whoChanged)!!)
// }
Expand Down Expand Up @@ -649,14 +632,14 @@ open class ConvertUtils @Inject constructor(
}
if (opts.contains(FillOpts.Groups)) {
portfolio.groups =
QDbGroup().whenArchived.isNull.owningPortfolio.eq(p).order().name.asc().findList()
QDbGroup().whenArchived.isNull.owningPortfolio.eq(p).orderBy().name.asc().findList()
.map { g: DbGroup? -> toGroup(g, opts) }
}
if (opts.contains(FillOpts.Applications)) {
var appFinder = QDbApplication()
.whenArchived.isNull
.portfolio.eq(p)
.order().name.asc()
.orderBy().name.asc()

personId?.let {
val portAdmin = isPersonMemberOfPortfolioAdminGroup(portfolio.id, personId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package io.featurehub.db.services

import io.ebean.annotation.Transactional
import io.featurehub.dacha.model.PublishAction
import io.featurehub.db.api.FillOpts
import io.featurehub.db.api.Opts
import io.featurehub.db.api.ApplicationRolloutStrategyApi
import io.featurehub.db.model.DbApplicationRolloutStrategy
import io.featurehub.db.model.query.QDbApplicationRolloutStrategy
import io.featurehub.mr.events.common.CacheSource
import io.featurehub.db.model.query.QDbEnvironment
import io.featurehub.db.model.query.QDbFeatureValue
import io.featurehub.db.services.ArchiveStrategy.Companion.isoDate
import io.featurehub.mr.model.*
import jakarta.inject.Inject
import org.apache.commons.lang3.RandomStringUtils
Expand All @@ -17,7 +18,8 @@ import java.time.ZoneOffset
import java.util.*

class ApplicationRolloutStrategySqlApi @Inject constructor(
private val conversions: Conversions, private val cacheSource: CacheSource
private val conversions: Conversions,
private val archiveStrategy: ArchiveStrategy, private val internalFeatureApi: InternalFeatureApi
) : ApplicationRolloutStrategyApi {

override fun createStrategy(
Expand Down Expand Up @@ -114,6 +116,8 @@ class ApplicationRolloutStrategySqlApi @Inject constructor(

val strategy = byStrategy(appId, strategyId, Opts.empty()).findOne() ?: return null
if (strategy.application.id == app.id) {
var notifyAttachedFeatures = false

// check if we are renaming it and if so, are we using a duplicate name
update.name?.let { newName ->
if (!strategy.name.equals(newName, ignoreCase = true)) {
Expand All @@ -135,15 +139,18 @@ class ApplicationRolloutStrategySqlApi @Inject constructor(

update.percentage?.let { percent ->
strategy.strategy.percentage = percent
notifyAttachedFeatures = true
}

update.percentageAttributes?.let { percentAttrs ->
strategy.strategy.percentageAttributes = percentAttrs
notifyAttachedFeatures = true
}

update.attributes?.let { attr ->
rationaliseAttributeIds(attr)
strategy.strategy.attributes = attr
notifyAttachedFeatures = true
}

update.avatar?.let { strategy.strategy.avatar = it }
Expand All @@ -154,7 +161,16 @@ class ApplicationRolloutStrategySqlApi @Inject constructor(

return try {
save(strategy)
cacheSource.publishApplicationRolloutStrategyChange(PublishAction.UPDATE, strategy)

if (notifyAttachedFeatures) {
// now we have to update all of the tagged features, add an additional history element, force republishing of everything associated
strategy.sharedRolloutStrategies?.let { strategies ->
strategies.forEach { strategyForFeatureValue ->
internalFeatureApi.updatedApplicationStrategy(strategyForFeatureValue, p)
}
}
}

conversions.toApplicationRolloutStrategy(strategy, opts)!!
} catch (e: Exception) {
throw ApplicationRolloutStrategyApi.DuplicateNameException()
Expand Down Expand Up @@ -185,13 +201,34 @@ class ApplicationRolloutStrategySqlApi @Inject constructor(
val count = qRS.findCount()

return ApplicationRolloutStrategyList().max(count).page(page)
.items(strategies.mapNotNull {
ListApplicationRolloutStrategyItem()
.strategy(conversions.toApplicationRolloutStrategy(it, opts)!!)
.whenUpdated(it.whenUpdated.atOffset(ZoneOffset.UTC))
.whenCreated(it.whenCreated.atOffset(ZoneOffset.UTC))
.updatedBy(ListApplicationRolloutStrategyItemUser().name(it.whoChanged.name).email(it.whoChanged.email))
})
.items(strategies.mapNotNull { toListApplicationRolloutStrategyItem(it, opts) })
}

fun toListApplicationRolloutStrategyItem(rs: DbApplicationRolloutStrategy, opts: Opts): ListApplicationRolloutStrategyItem {
val info = ListApplicationRolloutStrategyItem()
.strategy(conversions.toApplicationRolloutStrategy(rs, opts)!!)
.whenUpdated(rs.whenUpdated.atOffset(ZoneOffset.UTC))
.whenCreated(rs.whenCreated.atOffset(ZoneOffset.UTC))
.updatedBy(ListApplicationRolloutStrategyItemUser().name(rs.whoChanged.name).email(rs.whoChanged.email))

opts.contains(FillOpts.Usage).let {
val envs = mutableMapOf<UUID,ApplicationRolloutStrategyEnvironment>()

QDbFeatureValue()
.sharedRolloutStrategies.rolloutStrategy.id.eq(rs.id)
.environment.fetch(QDbEnvironment.Alias.id, QDbEnvironment.Alias.name)
.select(QDbFeatureValue.Alias.id).findList().forEach { fv ->
envs.getOrPut(fv.environment.id) { ApplicationRolloutStrategyEnvironment().featuresCount(0) }.let { env ->
env.id = fv.environment.id
env.name = fv.environment.name
env.featuresCount += 1
}
}

info.usage = envs.values.toMutableList()
}

return info
}

@Transactional(readOnly = true)
Expand All @@ -213,14 +250,26 @@ class ApplicationRolloutStrategySqlApi @Inject constructor(
val p = conversions.byPerson(person) ?: return false
val strategy = byStrategy(appId, strategyId, Opts.empty()).findOne() ?: return false

// only update and publish if it _actually_ changed
// only update and publish if it _actually_ changed. We do this here instead of in ArchiveStrategy
// because its not a simple publish. That class normally orchestrates it, but its simply too complex.
if (strategy.whenArchived == null) {
strategy.whoChanged = p
strategy.whenArchived = LocalDateTime.now()
save(strategy)
cacheSource.publishApplicationRolloutStrategyChange(PublishAction.DELETE, strategy)
strategy.name = (strategy.name + Conversions.archivePrefix + isoDate.format(strategy.whenArchived)).take(150)
strategy.whoChanged = p
strategy.save()

strategy.sharedRolloutStrategies?.let { attachedStrategies ->
// we are going go and detach all of these, which will require us to create new audit records for this
val copy = attachedStrategies.toList()

copy.forEach { strategyForFeatureValue ->
// this needs to remove the connection, create an audit trail, and publish a new record to Edge, and trigger webhooks
internalFeatureApi.detachApplicationStrategy(strategyForFeatureValue, strategy, p)
}
}
}


return true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package io.featurehub.db.services

import io.featurehub.db.model.*
import java.time.format.DateTimeFormatter

interface ArchiveStrategy {
companion object {
val isoDate = DateTimeFormatter.ISO_DATE_TIME
}

fun archivePortfolio(portfolio: DbPortfolio)
fun archiveApplication(application: DbApplication)
fun archiveEnvironment(environment: DbEnvironment)
Expand All @@ -17,4 +22,5 @@ interface ArchiveStrategy {
fun archiveApplicationFeature(feature: DbApplicationFeature)
fun featureListener(listener: (DbApplicationFeature) -> Unit)
fun archivePerson(person: DbPerson)
fun archiveApplicationStrategy(strategy: DbApplicationRolloutStrategy, personWhoArchived: DbPerson)
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
package io.featurehub.db.services

import io.ebean.Database
import io.ebean.annotation.Transactional
import io.featurehub.dacha.model.PublishAction
import io.featurehub.db.model.*
import io.featurehub.db.model.DbApplication
import io.featurehub.db.model.DbApplicationFeature
import io.featurehub.db.model.DbApplicationRolloutStrategy
import io.featurehub.db.model.DbEnvironment
import io.featurehub.db.model.DbGroup
import io.featurehub.db.model.DbOrganization
import io.featurehub.db.model.DbPerson
import io.featurehub.db.model.DbPortfolio
import io.featurehub.db.model.DbServiceAccount
import io.featurehub.db.model.query.QDbEnvironment
import io.featurehub.db.services.ArchiveStrategy.Companion.isoDate
import io.featurehub.mr.events.common.CacheSource
import jakarta.inject.Inject
import org.slf4j.LoggerFactory
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

class DbArchiveStrategy @Inject constructor(private val database: Database, private val cacheSource: CacheSource) :
class DbArchiveStrategy @Inject constructor(private val cacheSource: CacheSource) :
ArchiveStrategy {
private val isoDate = DateTimeFormatter.ISO_DATE_TIME

@Transactional
override fun archivePortfolio(portfolio: DbPortfolio) {
portfolio.whenArchived = LocalDateTime.now()
portfolio.name = portfolio.name + Conversions.archivePrefix + isoDate.format(portfolio.whenArchived)
database.save(portfolio)
portfolio.name = (portfolio.name + Conversions.archivePrefix + isoDate.format(portfolio.whenArchived)).take(250)
portfolio.save()
portfolio.applications.forEach { application: DbApplication -> archiveApplication(application) }
portfolio.groups.forEach { group: DbGroup -> archiveGroup(group) }
portfolio.serviceAccounts.forEach { serviceAccount: DbServiceAccount ->
Expand All @@ -32,16 +38,16 @@ class DbArchiveStrategy @Inject constructor(private val database: Database, priv
@Transactional
override fun archiveApplication(application: DbApplication) {
application.whenArchived = LocalDateTime.now()
database.save(application)
application.save()
application.environments.forEach { environment: DbEnvironment -> archiveEnvironment(environment) }
application.features.forEach { feature: DbApplicationFeature -> archiveApplicationFeature(feature) }
}

@Transactional
override fun archiveEnvironment(environment: DbEnvironment) {
environment.whenArchived = LocalDateTime.now()
environment.name = environment.name + Conversions.archivePrefix + isoDate.format(environment.whenArchived)
database.save(environment)
environment.name = (environment.name + Conversions.archivePrefix + isoDate.format(environment.whenArchived)).take(250)
environment.save()
cacheSource.deleteEnvironment(environment.id)
QDbEnvironment().priorEnvironment.eq(environment).findList().forEach { e: DbEnvironment ->
if (environment.priorEnvironment != null) {
Expand All @@ -50,7 +56,7 @@ class DbArchiveStrategy @Inject constructor(private val database: Database, priv
} else {
e.priorEnvironment = environment.priorEnvironment
}
database.save(e)
e.save()
}
}

Expand All @@ -68,25 +74,25 @@ class DbArchiveStrategy @Inject constructor(private val database: Database, priv
@Transactional
override fun archiveOrganization(organization: DbOrganization) {
organization.whenArchived = LocalDateTime.now()
database.save(organization)
organization.save()
organization.portfolios.forEach { portfolio: DbPortfolio -> archivePortfolio(portfolio) }
}

@Transactional
override fun archiveServiceAccount(serviceAccount: DbServiceAccount) {
serviceAccount.whenArchived = LocalDateTime.now()
serviceAccount.name =
serviceAccount.name + Conversions.archivePrefix + isoDate.format(serviceAccount.whenArchived)
database.save(serviceAccount)
(serviceAccount.name + Conversions.archivePrefix + isoDate.format(serviceAccount.whenArchived)).take(250)
serviceAccount.save()
cacheSource.deleteServiceAccount(serviceAccount.id)
}

@Transactional
override fun archiveGroup(group: DbGroup) {
group.whenArchived = LocalDateTime.now()
group.name =
group.name + Conversions.archivePrefix + isoDate.format(group.whenArchived)
database.save(group)
(group.name + Conversions.archivePrefix + isoDate.format(group.whenArchived)).take(250)
group.save()
}

@Transactional
Expand All @@ -95,8 +101,8 @@ class DbArchiveStrategy @Inject constructor(private val database: Database, priv
// key is unique
val originalKey = feature.key
feature.key =
feature.key + Conversions.archivePrefix + isoDate.format(feature.whenArchived)
database.save(feature)
(feature.key + Conversions.archivePrefix + isoDate.format(feature.whenArchived)).take(250)
feature.save()

featureListeners.forEach {
try {
Expand All @@ -118,7 +124,11 @@ class DbArchiveStrategy @Inject constructor(private val database: Database, priv
@Transactional
override fun archivePerson(person: DbPerson) {
person.whenArchived = LocalDateTime.now()
database.save(person)
person.save()
}

@Transactional
override fun archiveApplicationStrategy(strategy: DbApplicationRolloutStrategy, personWhoArchived: DbPerson) {
}

companion object {
Expand Down
Loading

0 comments on commit 9696113

Please sign in to comment.