Update ui
This commit is contained in:
parent
9c7e42ae29
commit
4d21d06781
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
@ -14,7 +15,6 @@
|
||||
<option value="$PROJECT_DIR$/apps/ui" />
|
||||
<option value="$PROJECT_DIR$/shared" />
|
||||
<option value="$PROJECT_DIR$/shared/common" />
|
||||
<option value="$PROJECT_DIR$/shared/contract" />
|
||||
<option value="$PROJECT_DIR$/shared/eventi" />
|
||||
</set>
|
||||
</option>
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
1078
.idea/workspace.xml
generated
1078
.idea/workspace.xml
generated
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ import no.iktdev.exfl.coroutines.CoroutinesIO
|
||||
import no.iktdev.exfl.observable.Observables
|
||||
import no.iktdev.mediaprocessing.shared.common.*
|
||||
import no.iktdev.eventi.database.MySqlDataSource
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.RunnerManager
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.TasksManager
|
||||
import no.iktdev.streamit.library.db.tables.*
|
||||
|
||||
@ -1,178 +0,0 @@
|
||||
package no.iktdev.mediaprocessing.coordinator
|
||||
|
||||
import no.iktdev.eventi.core.PersistentMessageHelper
|
||||
import no.iktdev.eventi.data.eventId
|
||||
import no.iktdev.eventi.data.referenceId
|
||||
import no.iktdev.eventi.data.toJson
|
||||
import no.iktdev.eventi.database.DataSource
|
||||
import no.iktdev.eventi.database.isCausedByDuplicateError
|
||||
import no.iktdev.eventi.database.isExposedSqlException
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.allEvents
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.events
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.EventsManagerContract
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.fromJsonWithDeserializer
|
||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
|
||||
class EventsManager(dataSource: DataSource) : EventsManagerContract(dataSource) {
|
||||
|
||||
override fun storeEvent(event: Event): Boolean {
|
||||
|
||||
no.iktdev.eventi.database.withTransaction(dataSource.database) {
|
||||
allEvents.insert {
|
||||
it[referenceId] = event.referenceId()
|
||||
it[eventId] = event.eventId()
|
||||
it[events.event] = event.eventType.event
|
||||
it[data] = event.toJson()
|
||||
}
|
||||
}
|
||||
|
||||
val existing = getEventsWith(event.referenceId())
|
||||
|
||||
val derivedId = event.metadata.derivedFromEventId
|
||||
if (derivedId != null) {
|
||||
val isNewEventOrphan = existing.none { it.eventId() == derivedId }
|
||||
if (isNewEventOrphan) {
|
||||
log.warn { "Message not saved! ${event.referenceId()} with eventId(${event.eventId()}) for event ${event.eventType} has derivedEventId($derivedId) which does not exist!" }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val exception = no.iktdev.eventi.database.executeOrException(dataSource.database) {
|
||||
events.insert {
|
||||
it[referenceId] = event.referenceId()
|
||||
it[eventId] = event.eventId()
|
||||
it[events.event] = event.eventType.event
|
||||
it[data] = event.toJson()
|
||||
}
|
||||
}
|
||||
val success = if (exception != null) {
|
||||
if (exception.isExposedSqlException()) {
|
||||
if ((exception as ExposedSQLException).isCausedByDuplicateError()) {
|
||||
log.debug { "Error is of SQLIntegrityConstraintViolationException" }
|
||||
log.error { exception.message }
|
||||
exception.printStackTrace()
|
||||
} else {
|
||||
log.debug { "Error code is: ${exception.errorCode}" }
|
||||
log.error { exception.message }
|
||||
exception.printStackTrace()
|
||||
}
|
||||
} else {
|
||||
log.error { exception.message }
|
||||
exception.printStackTrace()
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
if (success) {
|
||||
//deleteSupersededEvents(referenceId = event.referenceId(), eventId = event.eventId(), event = event.eventType, derivedFromId = event.derivedFromEventId())
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
|
||||
private val exemptedFromSingleEvent = listOf(
|
||||
Events.EventWorkConvertCreated,
|
||||
Events.EventWorkExtractCreated,
|
||||
Events.EventWorkConvertPerformed,
|
||||
Events.EventWorkExtractPerformed
|
||||
)
|
||||
|
||||
private fun isExempted(event: Events): Boolean {
|
||||
return event in exemptedFromSingleEvent
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun readAvailableEvents(): List<List<Event>> {
|
||||
return no.iktdev.eventi.database.withTransaction(dataSource.database) {
|
||||
events.selectAll()
|
||||
.groupBy { it[events.referenceId] }
|
||||
.mapNotNull { it.value.mapNotNull { v -> v.toEvent() } }.filter { it.none { e -> e.eventType == Events.EventMediaProcessCompleted } }
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
override fun readAvailableEventsFor(referenceId: String): List<Event> {
|
||||
val events = no.iktdev.eventi.database.withTransaction(dataSource.database) {
|
||||
events.select { events.referenceId eq referenceId }
|
||||
.mapNotNull { it.toEvent() }
|
||||
} ?: emptyList()
|
||||
return if (events.any { it.eventType == Events.EventMediaProcessCompleted }) emptyList() else events
|
||||
}
|
||||
|
||||
override fun getAllEvents(): List<List<Event>> {
|
||||
val events = no.iktdev.eventi.database.withTransaction(dataSource.database) {
|
||||
events.selectAll()
|
||||
.groupBy { it[events.referenceId] }
|
||||
.mapNotNull { it.value.mapNotNull { v -> v.toEvent() } }
|
||||
} ?: emptyList()
|
||||
return events.filter { it.none { it.eventType == Events.EventMediaProcessCompleted } }
|
||||
}
|
||||
|
||||
|
||||
override fun getEventsWith(referenceId: String): List<Event> {
|
||||
return no.iktdev.eventi.database.withTransaction(dataSource.database) {
|
||||
events.select {
|
||||
(events.referenceId eq referenceId)
|
||||
}
|
||||
.orderBy(events.created, SortOrder.ASC)
|
||||
.mapNotNull { it.toEvent() }
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param referenceId Reference
|
||||
* @param eventId Current eventId for the message, required to prevent deletion of itself
|
||||
* @param event Current event for the message
|
||||
*/
|
||||
private fun deleteSupersededEvents(referenceId: String, eventId: String, event: Events, derivedFromId: String?) {
|
||||
val forRemoval = mutableListOf<Event>()
|
||||
|
||||
val present = getEventsWith(referenceId).filter { it.metadata.derivedFromEventId != null }
|
||||
val helper = PersistentMessageHelper<Event>(present)
|
||||
|
||||
val replaced = if (!isExempted(event)) present.find { it.eventId() != eventId && it.eventType == event } else null
|
||||
val orphaned = replaced?.let { helper.getEventsRelatedTo(it.eventId()) }?.toMutableSet() ?: mutableSetOf()
|
||||
//orphaned.addAll(helper.findOrphanedEvents())
|
||||
|
||||
forRemoval.addAll(orphaned)
|
||||
|
||||
deleteSupersededEvents(forRemoval)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes the events
|
||||
*/
|
||||
private fun deleteSupersededEvents(superseded: List<Event>) {
|
||||
no.iktdev.eventi.database.withTransaction(dataSource) {
|
||||
superseded.forEach { duplicate ->
|
||||
events.deleteWhere {
|
||||
(referenceId eq duplicate.referenceId()) and
|
||||
(eventId eq duplicate.eventId()) and
|
||||
(event eq duplicate.eventType.event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun ResultRow.toEvent(): Event? {
|
||||
val kev = try {
|
||||
Events.toEvent(this[events.event])
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}?: return null
|
||||
return this[events.data].fromJsonWithDeserializer(kev)
|
||||
}
|
||||
|
||||
}
|
||||
@ -44,9 +44,8 @@ dependencies {
|
||||
implementation ("mysql:mysql-connector-java:8.0.29")
|
||||
|
||||
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
|
||||
implementation(project(mapOf("path" to ":shared")))
|
||||
implementation(project(mapOf("path" to ":shared:eventi")))
|
||||
implementation(project(mapOf("path" to ":shared:common")))
|
||||
implementation(project(mapOf("path" to ":shared:contract")))
|
||||
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:5.9.1"))
|
||||
|
||||
@ -2,9 +2,6 @@ package no.iktdev.mediaprocessing.ui
|
||||
|
||||
import no.iktdev.mediaprocessing.shared.common.Defaults
|
||||
import no.iktdev.mediaprocessing.shared.common.socket.SocketImplementation
|
||||
import no.iktdev.mediaprocessing.shared.kafka.core.CoordinatorProducer
|
||||
import no.iktdev.mediaprocessing.shared.kafka.core.DefaultMessageListener
|
||||
import no.iktdev.mediaprocessing.shared.kafka.core.KafkaImplementation
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer
|
||||
@ -67,8 +64,3 @@ class SocketImplemented: SocketImplementation() {
|
||||
|
||||
@Configuration
|
||||
class DefaultConfiguration: Defaults()
|
||||
|
||||
@Configuration
|
||||
@Import(CoordinatorProducer::class, DefaultMessageListener::class)
|
||||
class KafkaLocalInit: KafkaImplementation() {
|
||||
}
|
||||
@ -2,40 +2,45 @@ package no.iktdev.mediaprocessing.ui
|
||||
|
||||
|
||||
import mu.KotlinLogging
|
||||
import no.iktdev.eventi.database.MySqlDataSource
|
||||
import no.iktdev.exfl.coroutines.CoroutinesDefault
|
||||
import no.iktdev.exfl.coroutines.CoroutinesIO
|
||||
import no.iktdev.exfl.observable.ObservableMap
|
||||
import no.iktdev.exfl.observable.Observables
|
||||
import no.iktdev.exfl.observable.observableMapOf
|
||||
import no.iktdev.mediaprocessing.shared.common.DatabaseEnvConfig
|
||||
import no.iktdev.mediaprocessing.shared.common.datasource.MySqlDataSource
|
||||
import no.iktdev.mediaprocessing.shared.common.persistance.PersistentDataStore
|
||||
import no.iktdev.mediaprocessing.shared.common.database.EventsDatabase
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.TasksManager
|
||||
import no.iktdev.mediaprocessing.shared.common.toEventsDatabase
|
||||
import no.iktdev.mediaprocessing.ui.dto.ExplorerItem
|
||||
import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerItem
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.context.ApplicationContext
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import org.springframework.context.annotation.Bean
|
||||
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
val ioCoroutine = CoroutinesIO()
|
||||
val defaultCoroutine = CoroutinesDefault()
|
||||
lateinit var eventsManager: EventsManager
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
class UIApplication {
|
||||
|
||||
@Bean
|
||||
fun eventManager(): EventsManager {
|
||||
return eventsManager
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var eventsDatabase: MySqlDataSource
|
||||
fun getEventsDatabase(): MySqlDataSource {
|
||||
return eventsDatabase
|
||||
}
|
||||
private lateinit var eventsDatabase: EventsDatabase
|
||||
|
||||
lateinit var taskManager: TasksManager
|
||||
|
||||
lateinit var persistentReader: PersistentDataReader
|
||||
lateinit var persistentWriter: PersistentDataStore
|
||||
|
||||
private var context: ApplicationContext? = null
|
||||
private val kafkaClearedLatch = CountDownLatch(1)
|
||||
|
||||
@Suppress("unused")
|
||||
fun getContext(): ApplicationContext? {
|
||||
@ -46,11 +51,10 @@ val fileRegister: ObservableMap<String, ExplorerItem> = observableMapOf()
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
eventsDatabase = DatabaseEnvConfig.toEventsDatabase()
|
||||
eventsDatabase.connect()
|
||||
eventsDatabase = EventsDatabase().also {
|
||||
eventsManager = EventsManager(it.database)
|
||||
}
|
||||
|
||||
persistentReader = PersistentDataReader(eventsDatabase)
|
||||
persistentWriter = PersistentDataStore(eventsDatabase)
|
||||
|
||||
|
||||
ioCoroutine.addListener(listener = object: Observables.ObservableValue.ValueListener<Throwable> {
|
||||
@ -63,32 +67,6 @@ fun main(args: Array<String>) {
|
||||
value.printStackTrace()
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
/*val admincli = AdminClient.create(mapOf(
|
||||
AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG to KafkaEnv.servers,
|
||||
AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG to "1000",
|
||||
AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG to "5000"
|
||||
))
|
||||
val go = admincli.listConsumerGroupOffsets("${KafkaEnv.consumerId}:UIDataComposer")
|
||||
go.partitionsToOffsetAndMetadata().whenComplete { result, throwable ->
|
||||
val partitions = result.entries.filter { it.key.topic() == SharedConfig.kafkaTopic }
|
||||
.map { it.key }
|
||||
val deleteResult = admincli.deleteConsumerGroupOffsets("${KafkaEnv.consumerId}:UIDataComposer", partitions.toSet())
|
||||
deleteResult.all().whenComplete { result, throwable ->
|
||||
kafkaClearedLatch.countDown()
|
||||
}
|
||||
}*/
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// kafkaClearedLatch.countDown()
|
||||
}
|
||||
|
||||
// logger.info { "Waiting for kafka to clear offset!" }
|
||||
// kafkaClearedLatch.await(5, TimeUnit.MINUTES)
|
||||
// logger.info { "Offset cleared!" }
|
||||
// Thread.sleep(10000)
|
||||
context = runApplication<UIApplication>(*args)
|
||||
|
||||
}
|
||||
|
||||
@ -3,8 +3,6 @@ package no.iktdev.mediaprocessing.ui
|
||||
import java.io.File
|
||||
|
||||
object UIEnv {
|
||||
var storedContent: File = if (!System.getenv("DIRECTORY_CONTENT_STORED").isNullOrBlank()) File(System.getenv("DIRECTORY_CONTENT_STORED")) else File("/src/output")
|
||||
val socketEncoder: String = if (System.getenv("EncoderWs").isNullOrBlank()) System.getenv("EncoderWs") else "ws://encoder:8080"
|
||||
|
||||
val coordinatorUrl: String = if (System.getenv("Coordinator").isNullOrBlank()) System.getenv("Coordinator") else "http://coordinator"
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package no.iktdev.mediaprocessing.ui.dto
|
||||
|
||||
data class EventChain(
|
||||
val eventId: String,
|
||||
val eventName: String,
|
||||
val elements: MutableList<EventChain> = mutableListOf()
|
||||
)
|
||||
@ -1,12 +1,12 @@
|
||||
package no.iktdev.mediaprocessing.ui.dto
|
||||
|
||||
import no.iktdev.mediaprocessing.shared.kafka.core.KafkaEvents
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||
|
||||
data class EventSummary(
|
||||
val referenceId: String,
|
||||
val baseName: String? = null,
|
||||
val collection: String? = null,
|
||||
val events: List<KafkaEvents> = emptyList(),
|
||||
val events: List<Events> = emptyList(),
|
||||
val status: SummaryState = SummaryState.Started,
|
||||
val activeEvens: Map<String, EventSummarySubItem>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package no.iktdev.mediaprocessing.ui.dto
|
||||
package no.iktdev.mediaprocessing.ui.dto.explore
|
||||
|
||||
interface ExplorerAttr {
|
||||
val created: Long
|
||||
@ -1,4 +1,4 @@
|
||||
package no.iktdev.mediaprocessing.ui.dto
|
||||
package no.iktdev.mediaprocessing.ui.dto.explore
|
||||
|
||||
data class ExplorerCursor (
|
||||
val name: String,
|
||||
@ -1,11 +1,10 @@
|
||||
package no.iktdev.mediaprocessing.ui.explorer
|
||||
|
||||
import no.iktdev.mediaprocessing.shared.common.SharedConfig
|
||||
import no.iktdev.mediaprocessing.ui.UIEnv
|
||||
import no.iktdev.mediaprocessing.ui.dto.ExplorerAttributes
|
||||
import no.iktdev.mediaprocessing.ui.dto.ExplorerCursor
|
||||
import no.iktdev.mediaprocessing.ui.dto.ExplorerItem
|
||||
import no.iktdev.mediaprocessing.ui.dto.ExplorerItemType
|
||||
import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerAttributes
|
||||
import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerCursor
|
||||
import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerItem
|
||||
import no.iktdev.mediaprocessing.ui.dto.explore.ExplorerItemType
|
||||
import java.io.File
|
||||
import java.io.FileFilter
|
||||
import java.nio.file.Files
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package no.iktdev.mediaprocessing.ui.service
|
||||
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
|
||||
import no.iktdev.mediaprocessing.ui.eventsManager
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
@EnableScheduling
|
||||
class AvailableEventsService(
|
||||
@Autowired eventsManager: EventsManager
|
||||
) {
|
||||
|
||||
fun pullAvailableEvents() {
|
||||
eventsManager.readAvailableEvents().onEach {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package no.iktdev.mediaprocessing.ui.service
|
||||
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
@EnableScheduling
|
||||
class CompletedEventsService(
|
||||
@Autowired eventsManager: EventsManager
|
||||
) {
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package no.iktdev.mediaprocessing.ui.service
|
||||
|
||||
import no.iktdev.eventi.data.derivedFromEventId
|
||||
import no.iktdev.eventi.data.eventId
|
||||
import no.iktdev.eventi.data.referenceId
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
|
||||
import no.iktdev.mediaprocessing.shared.common.database.cal.EventsManager
|
||||
import no.iktdev.mediaprocessing.ui.dto.EventChain
|
||||
import no.iktdev.mediaprocessing.ui.eventsManager
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
@EnableScheduling
|
||||
class EventExecutionOrderService(
|
||||
@Autowired eventsManager: EventsManager
|
||||
) {
|
||||
|
||||
val collections: MutableMap<String, List<EventChain>> = mutableMapOf()
|
||||
|
||||
@Scheduled(fixedDelay = 5_000)
|
||||
fun pullAvailableEvents() {
|
||||
eventsManager.getAllEvents().onEach { events ->
|
||||
collections[events.first().referenceId()] = events.chained()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun List<Event>.chained(): List<EventChain> {
|
||||
val eventMap = this.associateBy { it.eventId() }
|
||||
val chains = mutableMapOf<String, EventChain>()
|
||||
|
||||
this.forEach { event ->
|
||||
val chain = EventChain(eventId = event.eventId(), eventName = event.eventType.name)
|
||||
chains[event.eventId()] = chain
|
||||
|
||||
if (event.derivedFromEventId() != null && eventMap.containsKey(event.derivedFromEventId())) {
|
||||
val parentChain = chains[event.derivedFromEventId()]
|
||||
parentChain?.elements?.add(chain)
|
||||
}
|
||||
}
|
||||
return chains.values.filter { it.elements.isNotEmpty() }.toList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package no.iktdev.mediaprocessing.ui.socket
|
||||
|
||||
import no.iktdev.eventi.data.derivedFromEventId
|
||||
import no.iktdev.eventi.data.eventId
|
||||
import no.iktdev.eventi.data.referenceId
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
|
||||
import no.iktdev.mediaprocessing.ui.dto.EventChain
|
||||
import no.iktdev.mediaprocessing.ui.eventsManager
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.stereotype.Controller
|
||||
|
||||
@Controller
|
||||
class ChainedEventsTopic(
|
||||
@Autowired private val template: SimpMessagingTemplate?
|
||||
) {
|
||||
@MessageMapping("/chained/all")
|
||||
fun sendAllChainedEvents() {
|
||||
val collections: MutableMap<String, List<EventChain>> = mutableMapOf()
|
||||
eventsManager.getAllEvents().onEach { events ->
|
||||
collections[events.first().referenceId()] = events.chained()
|
||||
}
|
||||
template?.convertAndSend("/topic/chained/all",collections)
|
||||
}
|
||||
|
||||
|
||||
fun List<Event>.chained(): List<EventChain> {
|
||||
val eventMap = this.associateBy { it.eventId() }
|
||||
val chains = mutableMapOf<String, EventChain>()
|
||||
val children = mutableSetOf<String>()
|
||||
|
||||
this.forEach { event ->
|
||||
val eventId = event.metadata.eventId
|
||||
val derivedFromEventId = event.metadata.derivedFromEventId
|
||||
val chain = chains.getOrPut(eventId) { EventChain(eventId, event.eventType.toString()) }
|
||||
|
||||
if (derivedFromEventId != null && eventMap.containsKey(derivedFromEventId)) {
|
||||
val parentChain = chains.getOrPut(derivedFromEventId) {
|
||||
EventChain(derivedFromEventId, eventMap[derivedFromEventId]!!.eventType.toString())
|
||||
}
|
||||
parentChain.elements.add(chain)
|
||||
children.add(eventId)
|
||||
}
|
||||
}
|
||||
|
||||
return chains.values.filter { it.eventId !in children }
|
||||
}
|
||||
}
|
||||
@ -8,12 +8,12 @@ import org.springframework.stereotype.Controller
|
||||
@Controller
|
||||
class EventsTableTopic(
|
||||
@Autowired private val template: SimpMessagingTemplate?,
|
||||
@Autowired private val persistentEventsTableService: PersistentEventsTableService
|
||||
): TopicSupport() {
|
||||
//@Autowired private val persistentEventsTableService: PersistentEventsTableService
|
||||
) {
|
||||
|
||||
@MessageMapping("/persistent/events")
|
||||
fun readbackEvents() {
|
||||
template?.convertAndSend("/topic/persistent/events", persistentEventsTableService.cachedEvents)
|
||||
//template?.convertAndSend("/topic/persistent/events", persistentEventsTableService.cachedEvents)
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package no.iktdev.mediaprocessing.ui.socket
|
||||
|
||||
import mu.KotlinLogging
|
||||
import no.iktdev.mediaprocessing.shared.contract.dto.EventRequest
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.dto.EventRequest
|
||||
import no.iktdev.mediaprocessing.ui.UIEnv
|
||||
import no.iktdev.mediaprocessing.ui.explorer.ExplorerCore
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@ -17,7 +17,7 @@ class ExplorerTopic(
|
||||
@Autowired private val template: SimpMessagingTemplate?,
|
||||
@Autowired private val coordinatorTemplate: RestTemplate,
|
||||
val explorer: ExplorerCore = ExplorerCore()
|
||||
): TopicSupport() {
|
||||
) {
|
||||
|
||||
@MessageMapping("/explorer/home")
|
||||
fun goHome() {
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
package no.iktdev.mediaprocessing.ui.socket
|
||||
|
||||
import com.google.gson.Gson
|
||||
|
||||
abstract class TopicSupport {
|
||||
|
||||
fun toJson(item: Any?): String? {
|
||||
return if (item != null) Gson().toJson(item) else null
|
||||
}
|
||||
}
|
||||
58
apps/ui/web/package-lock.json
generated
58
apps/ui/web/package-lock.json
generated
@ -30,6 +30,7 @@
|
||||
"react-scripts": "5.0.1",
|
||||
"react-stomp": "^5.1.0",
|
||||
"react-stomp-hooks": "^2.1.0",
|
||||
"react-tree-graph": "^8.0.2",
|
||||
"react-use-websocket": "^4.4.0",
|
||||
"redux": "^4.2.1",
|
||||
"sockjs-client": "^1.6.1",
|
||||
@ -2084,9 +2085,9 @@
|
||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
|
||||
"integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
||||
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -7142,6 +7143,16 @@
|
||||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz",
|
||||
"integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ=="
|
||||
},
|
||||
"node_modules/d3-hierarchy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
|
||||
"integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw=="
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@ -15370,6 +15381,20 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-tree-graph": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-tree-graph/-/react-tree-graph-8.0.2.tgz",
|
||||
"integrity": "sha512-w3DWXisWvAvESAKO5WMCSIdqUb+LLXKX32ePwuVab98QUDUjrwoHSGsWG10ip6bsxuCyUTmP0YXwNyI8fSiOaQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.5",
|
||||
"d3-ease": "^2.0.0",
|
||||
"d3-hierarchy": "^2.0.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-use-websocket": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.4.0.tgz",
|
||||
@ -19838,9 +19863,9 @@
|
||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz",
|
||||
"integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
||||
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -23432,6 +23457,16 @@
|
||||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"d3-ease": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz",
|
||||
"integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ=="
|
||||
},
|
||||
"d3-hierarchy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
|
||||
"integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw=="
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@ -29215,6 +29250,17 @@
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-tree-graph": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-tree-graph/-/react-tree-graph-8.0.2.tgz",
|
||||
"integrity": "sha512-w3DWXisWvAvESAKO5WMCSIdqUb+LLXKX32ePwuVab98QUDUjrwoHSGsWG10ip6bsxuCyUTmP0YXwNyI8fSiOaQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.24.5",
|
||||
"d3-ease": "^2.0.0",
|
||||
"d3-hierarchy": "^2.0.0",
|
||||
"prop-types": "^15.8.1"
|
||||
}
|
||||
},
|
||||
"react-use-websocket": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.4.0.tgz",
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
"react-scripts": "5.0.1",
|
||||
"react-stomp": "^5.1.0",
|
||||
"react-stomp-hooks": "^2.1.0",
|
||||
"react-tree-graph": "^8.0.2",
|
||||
"react-use-websocket": "^4.4.0",
|
||||
"redux": "^4.2.1",
|
||||
"sockjs-client": "^1.6.1",
|
||||
|
||||
@ -14,6 +14,7 @@ import { ThemeProvider } from '@mui/material';
|
||||
import theme from './theme';
|
||||
import { simpleEventsUpdate } from './app/store/kafka-items-flat-slice';
|
||||
import { EventDataObject, SimpleEventDataObject } from './types';
|
||||
import EventsChainPage from './app/page/EventsChainPage';
|
||||
|
||||
function App() {
|
||||
const client = useStompClient();
|
||||
@ -62,6 +63,7 @@ function App() {
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path='/files' element={<ExplorePage />} />
|
||||
<Route path='/events' element={<EventsChainPage />} />
|
||||
<Route path='/' element={<LaunchPage />} />
|
||||
</Routes>
|
||||
<Footer />
|
||||
|
||||
58
apps/ui/web/src/app/page/EventsChainPage.tsx
Normal file
58
apps/ui/web/src/app/page/EventsChainPage.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useStompClient } from 'react-stomp-hooks';
|
||||
import { RootState } from "../store";
|
||||
import { useWsSubscription } from "../ws/subscriptions";
|
||||
import { set } from "../store/persistent-events-slice";
|
||||
import { EventsObjectListResponse } from "../../types";
|
||||
import IconRefresh from '@mui/icons-material/Refresh'
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
|
||||
export default function EventsChainPage() {
|
||||
const dispatch = useDispatch();
|
||||
const client = useStompClient();
|
||||
const cursor = useSelector((state: RootState) => state.persistentEvents)
|
||||
|
||||
function log(data: any) {
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
useWsSubscription("/topic/chained/all", (response) => {
|
||||
console.log(response)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
// Kjør din funksjon her når komponenten lastes inn for første gang
|
||||
// Sjekk om cursor er null
|
||||
if (cursor.items === null && client !== null) {
|
||||
console.log(cursor)
|
||||
// Kjør din funksjon her når cursor er null og client ikke er null
|
||||
client?.publish({
|
||||
destination: "/app/chained/all"
|
||||
});
|
||||
|
||||
// Alternativt, du kan dispatche en Redux handling her
|
||||
// dispatch(fetchDataAction()); // Eksempel på å dispatche en handling
|
||||
}
|
||||
}, [cursor, client, dispatch]);
|
||||
|
||||
const onRefresh = () => {
|
||||
client?.publish({
|
||||
"destination": "/app/chained/all",
|
||||
"body": "Potato"
|
||||
})
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
startIcon={ <IconRefresh /> }
|
||||
onClick={onRefresh} sx={{
|
||||
borderRadius: 5,
|
||||
textTransform: 'none'
|
||||
}}>Refresh</Button >
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -58,6 +58,7 @@ function getSegmentedNaviagatablePath(navigateTo: (path: string | null) => void,
|
||||
console.log(path);
|
||||
const parts: Array<string> = path?.split(/\\|\//).map((value: string, index: number) => value.replaceAll(":", "")) ?? [];
|
||||
const segments = parts.map((name: string, index: number) => {
|
||||
console.log(name)
|
||||
return (
|
||||
<Box key={index} sx={{
|
||||
display: "flex",
|
||||
@ -65,7 +66,8 @@ function getSegmentedNaviagatablePath(navigateTo: (path: string | null) => void,
|
||||
alignItems: "center"
|
||||
}}>
|
||||
<Button sx={{
|
||||
borderRadius: 5
|
||||
borderRadius: 5,
|
||||
textTransform: 'none'
|
||||
}} onClick={() => navigateTo(getPartFor(path!, index))}>
|
||||
<Typography>{name}</Typography>
|
||||
</Button>
|
||||
|
||||
@ -3,9 +3,10 @@ import SimpleTable, { TableCellCustomizer, TablePropetyConfig } from "../feature
|
||||
import { RootState } from "../store";
|
||||
import { useEffect } from "react";
|
||||
import { useStompClient } from "react-stomp-hooks";
|
||||
import { Box, Button, useTheme } from "@mui/material";
|
||||
import { Box, Button, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import IconRefresh from '@mui/icons-material/Refresh'
|
||||
|
||||
import IconCompleted from '@mui/icons-material/Check'
|
||||
import IconWorking from '@mui/icons-material/Engineering';
|
||||
|
||||
const columns: Array<TablePropetyConfig> = [
|
||||
{ label: "Title", accessor: "givenTitle" },
|
||||
@ -53,11 +54,24 @@ export default function LaunchPage() {
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
}}>
|
||||
<Button onClick={onRefresh} sx={{
|
||||
borderRadius: 5
|
||||
}}>
|
||||
<IconRefresh />
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={ <IconRefresh /> }
|
||||
onClick={onRefresh} sx={{
|
||||
borderRadius: 5,
|
||||
textTransform: 'none'
|
||||
}}>Refresh</Button >
|
||||
<Button
|
||||
startIcon={ <IconCompleted /> }
|
||||
onClick={onRefresh} sx={{
|
||||
borderRadius: 5,
|
||||
textTransform: 'none'
|
||||
}}>Completed</Button >
|
||||
<Button
|
||||
startIcon={ <IconWorking /> }
|
||||
onClick={onRefresh} sx={{
|
||||
borderRadius: 5,
|
||||
textTransform: 'none'
|
||||
}}>Working</Button >
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
|
||||
@ -27,7 +27,7 @@ object EventToClazzTable {
|
||||
Events.EventMediaReadStreamPerformed to MediaFileStreamsReadEvent::class.java,
|
||||
Events.EventMediaMetadataSearchPerformed to MediaMetadataReceivedEvent::class.java,
|
||||
Events.EventMediaReadOutNameAndType to MediaOutInformationConstructedEvent::class.java,
|
||||
Events.EventMediaWorkProceedPermitted to no.iktdev.mediaprocessing.shared.common.contract.data.PermitWorkCreationEvent::class.java,
|
||||
Events.EventMediaWorkProceedPermitted to PermitWorkCreationEvent::class.java,
|
||||
Events.EventMediaProcessCompleted to MediaProcessCompletedEvent::class.java
|
||||
)
|
||||
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package no.iktdev.mediaprocessing.shared.common.database
|
||||
|
||||
import no.iktdev.mediaprocessing.shared.common.DatabaseEnvConfig
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.allEvents
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.events
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.runners
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.tasks
|
||||
import no.iktdev.mediaprocessing.shared.common.toEventsDatabase
|
||||
|
||||
class EventsDatabase() {
|
||||
val database = DatabaseEnvConfig.toEventsDatabase()
|
||||
val tables = listOf(
|
||||
events, // For kafka
|
||||
allEvents,
|
||||
tasks,
|
||||
runners
|
||||
)
|
||||
|
||||
init {
|
||||
database.createDatabase()
|
||||
database.createTables(*tables.toTypedArray())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -8,20 +8,19 @@ import no.iktdev.eventi.data.toJson
|
||||
import no.iktdev.eventi.database.DataSource
|
||||
import no.iktdev.eventi.database.isCausedByDuplicateError
|
||||
import no.iktdev.eventi.database.isExposedSqlException
|
||||
import no.iktdev.eventi.implementations.EventsManagerImpl
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.allEvents
|
||||
import no.iktdev.mediaprocessing.shared.common.database.tables.events
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.EventsManagerContract
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
|
||||
import no.iktdev.mediaprocessing.shared.common.contract.fromJsonWithDeserializer
|
||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
|
||||
open class EventsManager(dataSource: DataSource) : EventsManagerContract(dataSource) {
|
||||
class EventsManager(dataSource: DataSource) : EventsManagerImpl<Event>(dataSource) {
|
||||
val log = KotlinLogging.logger {}
|
||||
|
||||
|
||||
override fun storeEvent(event: Event): Boolean {
|
||||
|
||||
no.iktdev.eventi.database.withTransaction(dataSource.database) {
|
||||
@ -114,7 +113,7 @@ open class EventsManager(dataSource: DataSource) : EventsManagerContract(dataSou
|
||||
.groupBy { it[events.referenceId] }
|
||||
.mapNotNull { it.value.mapNotNull { v -> v.toEvent() } }
|
||||
} ?: emptyList()
|
||||
return events.filter { it.none { it.eventType == Events.EventMediaProcessCompleted } }
|
||||
return events
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -185,7 +185,6 @@ abstract class EventCoordinator<T : EventImpl, E : EventsManagerImpl<T>> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Ikke implementert enda
|
||||
enum class ActiveMode {
|
||||
Active,
|
||||
Passive
|
||||
|
||||
Loading…
Reference in New Issue
Block a user