Adjusted process check. Now checking if there is a produced derived event and if it can create more.
This commit is contained in:
parent
e4e972f36b
commit
82cb245639
@ -0,0 +1,43 @@
|
|||||||
|
package no.iktdev.mediaprocessing
|
||||||
|
|
||||||
|
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||||
|
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
|
||||||
|
import no.iktdev.mediaprocessing.shared.common.contract.jsonToEvent
|
||||||
|
import org.json.JSONArray
|
||||||
|
|
||||||
|
enum class Files(val fileName: String) {
|
||||||
|
MultipleLanguageBased("Events.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Files.getContent(): String? {
|
||||||
|
return this.javaClass.classLoader.getResource(this.fileName)?.readText()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Files.databaseJsonToEvents(): List<Event> {
|
||||||
|
val content = this.getContent();
|
||||||
|
try {
|
||||||
|
val jarr = JSONArray(content)
|
||||||
|
for (i in 0 until jarr.length()) {
|
||||||
|
val o = jarr.getJSONObject(i)
|
||||||
|
if (o.has("type") && o.getString("type") == "table") {
|
||||||
|
val dataArray = o.getJSONArray("data")
|
||||||
|
|
||||||
|
val events: MutableList<Event> = mutableListOf()
|
||||||
|
for (x in 0 until dataArray.length()) {
|
||||||
|
|
||||||
|
val obj = dataArray.getJSONObject(x)
|
||||||
|
val eventType = obj.getString("event")
|
||||||
|
val dataString = obj.getString("data")
|
||||||
|
dataString.jsonToEvent(eventType).also {
|
||||||
|
events.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package no.iktdev.mediaprocessing.coordinator.tasksV2.listeners
|
||||||
|
|
||||||
|
import no.iktdev.eventi.data.referenceId
|
||||||
|
import no.iktdev.mediaprocessing.Files
|
||||||
|
import no.iktdev.mediaprocessing.databaseJsonToEvents
|
||||||
|
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
class ConvertWorkTaskListenerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateParsingOfEvents() {
|
||||||
|
val content = Files.MultipleLanguageBased.databaseJsonToEvents()
|
||||||
|
assertThat(content).isNotEmpty()
|
||||||
|
val referenceId = content.firstOrNull()?.referenceId()
|
||||||
|
assertThat(referenceId).isNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateCreationOfConvertTasks() {
|
||||||
|
val listener: ConvertWorkTaskListener = ConvertWorkTaskListener()
|
||||||
|
val content = Files.MultipleLanguageBased.databaseJsonToEvents().filter { it.eventType in listOf( Events.WorkExtractPerformed, Events.ProcessStarted, Events.WorkConvertCreated, Events.WorkConvertPerformed) }
|
||||||
|
assertThat(listener).isNotNull()
|
||||||
|
val success = content.map { listener.shouldIProcessAndHandleEvent(it, content) to it }
|
||||||
|
assertThat(success.filter { it.first }.size).isGreaterThan(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@ package no.iktdev.mediaprocessing.coordinator.tasksV2.listeners
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import no.iktdev.eventi.data.dataAs
|
import no.iktdev.eventi.data.dataAs
|
||||||
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||||
import no.iktdev.mediaprocessing.shared.common.contract.fromJsonWithDeserializer
|
import no.iktdev.mediaprocessing.shared.common.contract.jsonToEvent
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ class ParseMediaFileStreamsTaskListenerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testParse() {
|
fun testParse() {
|
||||||
val event = data.fromJsonWithDeserializer(Events.ReadStreamPerformed).dataAs<JsonObject>()
|
val event = data.jsonToEvent(Events.ReadStreamPerformed.event).dataAs<JsonObject>()
|
||||||
|
|
||||||
val parser = ParseMediaFileStreamsTaskListener()
|
val parser = ParseMediaFileStreamsTaskListener()
|
||||||
val result = parser.parseStreams(event)
|
val result = parser.parseStreams(event)
|
||||||
|
|||||||
@ -6,14 +6,14 @@ import no.iktdev.mediaprocessing.shared.common.contract.data.az
|
|||||||
import no.iktdev.mediaprocessing.shared.common.contract.ffmpeg.AudioPreference
|
import no.iktdev.mediaprocessing.shared.common.contract.ffmpeg.AudioPreference
|
||||||
import no.iktdev.mediaprocessing.shared.common.contract.ffmpeg.EncodingPreference
|
import no.iktdev.mediaprocessing.shared.common.contract.ffmpeg.EncodingPreference
|
||||||
import no.iktdev.mediaprocessing.shared.common.contract.ffmpeg.VideoPreference
|
import no.iktdev.mediaprocessing.shared.common.contract.ffmpeg.VideoPreference
|
||||||
import no.iktdev.mediaprocessing.shared.common.contract.fromJsonWithDeserializer
|
import no.iktdev.mediaprocessing.shared.common.contract.jsonToEvent
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class EncodeWorkArgumentsMappingTest {
|
class EncodeWorkArgumentsMappingTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parse() {
|
fun parse() {
|
||||||
val event = data.fromJsonWithDeserializer(Events.ParseStreamPerformed)
|
val event = data.jsonToEvent(Events.ParseStreamPerformed.event)
|
||||||
val parser = EncodeWorkArgumentsMapping(
|
val parser = EncodeWorkArgumentsMapping(
|
||||||
"potato.mkv",
|
"potato.mkv",
|
||||||
"potato.mp4",
|
"potato.mp4",
|
||||||
|
|||||||
23
apps/coordinator/src/test/resources/Events.json
Normal file
23
apps/coordinator/src/test/resources/Events.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{"type":"header","version":"5.2.1","comment":"Export to JSON plugin for PHPMyAdmin"},
|
||||||
|
{"type":"database","name":"eventsV4"},
|
||||||
|
{"type":"table","name":"events","database":"eventsV4","data":
|
||||||
|
[
|
||||||
|
{"id":"15349","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"8ca447c5-aad9-40cc-a561-6669e1248d77","event":"event:media-process:started","data":"{\"metadata\":{\"eventId\":\"8ca447c5-aad9-40cc-a561-6669e1248d77\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:26:46.029447375\",\"source\":\"Coordinator\"},\"data\":{\"type\":\"FLOW\",\"operations\":[\"ENCODE\",\"EXTRACT\",\"CONVERT\"],\"file\":\"\/src\/input\/completed\/standalone\/Potetmos\/Potetmos.mkv\"},\"eventType\":\"ProcessStarted\"}","created":"2025-02-26 22:26:46.047704"},
|
||||||
|
{"id":"15359","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"fc1d6871-b5e3-47ad-96ef-6e63e611dec2","event":"event:work-extract:created","data":"{\"metadata\":{\"derivedFromEventId\":\"17f5ba56-7155-4fac-b787-3371480a08ac\",\"eventId\":\"fc1d6871-b5e3-47ad-96ef-6e63e611dec2\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:26:57.668593271\",\"source\":\"ExtractWorkTaskListener\"},\"eventType\":\"WorkExtractCreated\",\"data\":{\"arguments\":[\"-c:s\",\"copy\",\"-map\",\"0:s:0\"],\"language\":\"eng\",\"storeFileName\":\"Potetmos\",\"outputFileName\":\"Potetmos.eng.srt\",\"inputFile\":\"\/src\/input\/completed\/standalone\/Potetmos\/Potetmos.mkv\"}}","created":"2025-02-26 22:26:57.695234"},
|
||||||
|
{"id":"15360","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"11abadee-73f5-4b7a-ad28-5ec76ae5e095","event":"event:work-extract:created","data":"{\"metadata\":{\"derivedFromEventId\":\"17f5ba56-7155-4fac-b787-3371480a08ac\",\"eventId\":\"11abadee-73f5-4b7a-ad28-5ec76ae5e095\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:26:57.668599001\",\"source\":\"ExtractWorkTaskListener\"},\"eventType\":\"WorkExtractCreated\",\"data\":{\"arguments\":[\"-c:s\",\"copy\",\"-map\",\"0:s:1\"],\"language\":\"dan\",\"storeFileName\":\"Potetmos\",\"outputFileName\":\"Potetmos.dan.srt\",\"inputFile\":\"\/src\/input\/completed\/standalone\/Potetmos\/Potetmos.mkv\"}}","created":"2025-02-26 22:26:57.727437"},
|
||||||
|
{"id":"15361","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"9f929171-91be-460f-b6db-0243603eb222","event":"event:work-extract:created","data":"{\"metadata\":{\"derivedFromEventId\":\"17f5ba56-7155-4fac-b787-3371480a08ac\",\"eventId\":\"9f929171-91be-460f-b6db-0243603eb222\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:26:57.668600721\",\"source\":\"ExtractWorkTaskListener\"},\"eventType\":\"WorkExtractCreated\",\"data\":{\"arguments\":[\"-c:s\",\"copy\",\"-map\",\"0:s:2\"],\"language\":\"swe\",\"storeFileName\":\"Potetmos\",\"outputFileName\":\"Potetmos.swe.srt\",\"inputFile\":\"\/src\/input\/completed\/standalone\/Potetmos\/Potetmos.mkv\"}}","created":"2025-02-26 22:26:57.762233"},
|
||||||
|
{"id":"15362","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"abbba9e0-0491-48a1-bf48-dd0f201cef7f","event":"event:work-extract:created","data":"{\"metadata\":{\"derivedFromEventId\":\"17f5ba56-7155-4fac-b787-3371480a08ac\",\"eventId\":\"abbba9e0-0491-48a1-bf48-dd0f201cef7f\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:26:57.668602091\",\"source\":\"ExtractWorkTaskListener\"},\"eventType\":\"WorkExtractCreated\",\"data\":{\"arguments\":[\"-c:s\",\"copy\",\"-map\",\"0:s:3\"],\"language\":\"nob\",\"storeFileName\":\"Potetmos\",\"outputFileName\":\"Potetmos.nob.srt\",\"inputFile\":\"\/src\/input\/completed\/standalone\/Potetmos\/Potetmos.mkv\"}}","created":"2025-02-26 22:26:57.792813"},
|
||||||
|
{"id":"15363","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"fe594289-b2c5-4e3e-8827-4ba2988184b6","event":"event:work-extract:created","data":"{\"metadata\":{\"derivedFromEventId\":\"17f5ba56-7155-4fac-b787-3371480a08ac\",\"eventId\":\"fe594289-b2c5-4e3e-8827-4ba2988184b6\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:26:57.668603331\",\"source\":\"ExtractWorkTaskListener\"},\"eventType\":\"WorkExtractCreated\",\"data\":{\"arguments\":[\"-c:s\",\"copy\",\"-map\",\"0:s:4\"],\"language\":\"fin\",\"storeFileName\":\"Potetmos\",\"outputFileName\":\"Potetmos.fin.srt\",\"inputFile\":\"\/src\/input\/completed\/standalone\/Potetmos\/Potetmos.mkv\"}}","created":"2025-02-26 22:26:57.827601"},
|
||||||
|
{"id":"15364","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"99692869-ba77-4976-8544-02fdc841a06a","event":"event:work-encode:created","data":"{\"metadata\":{\"derivedFromEventId\":\"fb06297e-e5ab-4e2f-bf20-b624d37c2f8b\",\"eventId\":\"99692869-ba77-4976-8544-02fdc841a06a\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:26:57.842844507\",\"source\":\"EncodeWorkTaskListener\"},\"eventType\":\"WorkEncodeCreated\",\"data\":{\"arguments\":[\"-c:v\",\"copy\",\"-vbsf\",\"hevc_mp4toannexb\",\"-acodec\",\"copy\",\"-map\",\"0:v:0\",\"-map\",\"0:a:0\"],\"outputFileName\":\"Potetmos.mp4\",\"inputFile\":\"\/src\/input\/completed\/standalone\/Potetmos\/Potetmos.mkv\"}}","created":"2025-02-26 22:26:57.871043"},
|
||||||
|
{"id":"15365","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"59d1bcda-0a34-48fc-83e3-34547d63abb5","event":"event:work-extract:performed","data":"{\"metadata\":{\"derivedFromEventId\":\"fc1d6871-b5e3-47ad-96ef-6e63e611dec2\",\"eventId\":\"59d1bcda-0a34-48fc-83e3-34547d63abb5\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:15.577829445\",\"source\":\"ExtractService\"},\"eventType\":\"WorkExtractPerformed\",\"data\":{\"language\":\"eng\",\"storeFileName\":\"Potetmos\",\"outputFile\":\"\/src\/cache\/Potetmos.eng.srt\"}}","created":"2025-02-26 22:27:15.581941"},
|
||||||
|
{"id":"15366","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"5a0419e6-572f-4b73-95b8-11af31e6002e","event":"event:work-encode:performed","data":"{\"metadata\":{\"derivedFromEventId\":\"99692869-ba77-4976-8544-02fdc841a06a\",\"eventId\":\"5a0419e6-572f-4b73-95b8-11af31e6002e\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:15.598866765\",\"source\":\"EncodeService\"},\"eventType\":\"WorkEncodePerformed\",\"data\":{\"outputFile\":\"\/src\/cache\/Potetmos.mp4\"}}","created":"2025-02-26 22:27:15.603074"},
|
||||||
|
{"id":"15367","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"4191b02e-1d85-49f3-8cd3-2ca3a424e645","event":"event:work-convert:created","data":"{\"metadata\":{\"derivedFromEventId\":\"59d1bcda-0a34-48fc-83e3-34547d63abb5\",\"eventId\":\"4191b02e-1d85-49f3-8cd3-2ca3a424e645\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:16.058203558\",\"source\":\"ConvertWorkTaskListener\"},\"eventType\":\"WorkConvertCreated\",\"data\":{\"inputFile\":\"\/src\/cache\/Potetmos.eng.srt\",\"language\":\"eng\",\"outputDirectory\":\"\/src\/cache\",\"outputFileName\":\"Potetmos.eng\",\"storeFileName\":\"Potetmos\",\"formats\":[],\"allowOverwrite\":true}}","created":"2025-02-26 22:27:16.091175"},
|
||||||
|
{"id":"15368","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"79f23190-8696-4b4f-b037-98c0a9cd6314","event":"event:work-extract:performed","data":"{\"metadata\":{\"derivedFromEventId\":\"11abadee-73f5-4b7a-ad28-5ec76ae5e095\",\"eventId\":\"79f23190-8696-4b4f-b037-98c0a9cd6314\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:21.73082975\",\"source\":\"ExtractService\"},\"eventType\":\"WorkExtractPerformed\",\"data\":{\"language\":\"dan\",\"storeFileName\":\"Potetmos\",\"outputFile\":\"\/src\/cache\/Potetmos.dan.srt\"}}","created":"2025-02-26 22:27:21.734862"},
|
||||||
|
{"id":"15369","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"aa66dda3-dbb0-4738-9b38-0f10ec6e3c07","event":"event:work-convert:performed","data":"{\"metadata\":{\"derivedFromEventId\":\"4191b02e-1d85-49f3-8cd3-2ca3a424e645\",\"eventId\":\"aa66dda3-dbb0-4738-9b38-0f10ec6e3c07\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:22.029604937\",\"source\":\"ConvertService\"},\"eventType\":\"WorkConvertPerformed\",\"data\":{\"language\":\"eng\",\"baseName\":\"Potetmos\",\"outputFiles\":[\"\/src\/cache\/Potetmos.eng.vtt\",\"\/src\/cache\/Potetmos.eng.smi\"]}}","created":"2025-02-26 22:27:22.033352"},
|
||||||
|
{"id":"15370","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"ec07bb50-1cc1-45b2-a5d7-73b84bfd29d4","event":"event:work-extract:performed","data":"{\"metadata\":{\"derivedFromEventId\":\"9f929171-91be-460f-b6db-0243603eb222\",\"eventId\":\"ec07bb50-1cc1-45b2-a5d7-73b84bfd29d4\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:26.803119861\",\"source\":\"ExtractService\"},\"eventType\":\"WorkExtractPerformed\",\"data\":{\"language\":\"swe\",\"storeFileName\":\"Potetmos\",\"outputFile\":\"\/src\/cache\/Potetmos.swe.srt\"}}","created":"2025-02-26 22:27:26.807279"},
|
||||||
|
{"id":"15371","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"7b6663e4-cc38-4b5e-b0af-32bd1ed3039d","event":"event:work-extract:performed","data":"{\"metadata\":{\"derivedFromEventId\":\"abbba9e0-0491-48a1-bf48-dd0f201cef7f\",\"eventId\":\"7b6663e4-cc38-4b5e-b0af-32bd1ed3039d\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:31.834864505\",\"source\":\"ExtractService\"},\"eventType\":\"WorkExtractPerformed\",\"data\":{\"language\":\"nob\",\"storeFileName\":\"Potetmos\",\"outputFile\":\"\/src\/cache\/Potetmos.nob.srt\"}}","created":"2025-02-26 22:27:31.839143"},
|
||||||
|
{"id":"15372","referenceId":"7aed4397-e286-4787-966e-7d375f43b1e5","eventId":"4b3c17b8-c624-40fb-94ca-33e3657bc6d3","event":"event:work-extract:performed","data":"{\"metadata\":{\"derivedFromEventId\":\"fe594289-b2c5-4e3e-8827-4ba2988184b6\",\"eventId\":\"4b3c17b8-c624-40fb-94ca-33e3657bc6d3\",\"referenceId\":\"7aed4397-e286-4787-966e-7d375f43b1e5\",\"status\":\"Success\",\"created\":\"2025-02-26T22:27:37.017421899\",\"source\":\"ExtractService\"},\"eventType\":\"WorkExtractPerformed\",\"data\":{\"language\":\"fin\",\"storeFileName\":\"Potetmos\",\"outputFile\":\"\/src\/cache\/Potetmos.fin.srt\"}}","created":"2025-02-26 22:27:37.021717"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1,7 +1,18 @@
|
|||||||
package no.iktdev.mediaprocessing.ui.dto
|
package no.iktdev.mediaprocessing.ui.dto
|
||||||
|
|
||||||
|
data class EventHolder(
|
||||||
|
val referenceId: String,
|
||||||
|
val fileName: String?,
|
||||||
|
val events: List<EventChain>,
|
||||||
|
val created: Long
|
||||||
|
)
|
||||||
|
|
||||||
data class EventChain(
|
data class EventChain(
|
||||||
val eventId: String,
|
val eventId: String,
|
||||||
val eventName: String,
|
val eventName: String,
|
||||||
val elements: MutableList<EventChain> = mutableListOf()
|
val created: Long,
|
||||||
|
val success: Boolean,
|
||||||
|
val failure: Boolean,
|
||||||
|
val skipped: Boolean,
|
||||||
|
val events: MutableList<EventChain> = mutableListOf()
|
||||||
)
|
)
|
||||||
@ -1,49 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,27 +1,34 @@
|
|||||||
package no.iktdev.mediaprocessing.ui.socket
|
package no.iktdev.mediaprocessing.ui.socket
|
||||||
|
|
||||||
import no.iktdev.eventi.data.derivedFromEventId
|
import no.iktdev.eventi.data.*
|
||||||
import no.iktdev.eventi.data.eventId
|
import no.iktdev.eventi.database.toEpochSeconds
|
||||||
import no.iktdev.eventi.data.referenceId
|
import no.iktdev.mediaprocessing.shared.common.contract.Events
|
||||||
import no.iktdev.mediaprocessing.shared.common.contract.data.Event
|
import no.iktdev.mediaprocessing.shared.common.contract.data.*
|
||||||
import no.iktdev.mediaprocessing.ui.dto.EventChain
|
import no.iktdev.mediaprocessing.ui.dto.EventChain
|
||||||
|
import no.iktdev.mediaprocessing.ui.dto.EventHolder
|
||||||
import no.iktdev.mediaprocessing.ui.eventsManager
|
import no.iktdev.mediaprocessing.ui.eventsManager
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class ChainedEventsTopic(
|
class ChainedEventsTopic(
|
||||||
@Autowired private val template: SimpMessagingTemplate?
|
@Autowired private val template: SimpMessagingTemplate?,
|
||||||
) {
|
) {
|
||||||
@MessageMapping("/chained/all")
|
@MessageMapping("/chained/all")
|
||||||
fun sendAllChainedEvents() {
|
fun sendAllChainedEvents() {
|
||||||
val collections: MutableMap<String, List<EventChain>> = mutableMapOf()
|
val holders: MutableList<EventHolder> = mutableListOf()
|
||||||
eventsManager.getAllEvents().onEach { events ->
|
eventsManager.getAllEvents().onEach { events ->
|
||||||
collections[events.first().referenceId()] = events.chained()
|
holders.add(EventHolder(
|
||||||
|
referenceId = events.first().referenceId(),
|
||||||
|
fileName = events.findFirstOf(Events.ProcessStarted)?.dataAs<StartEventData>()?.file?.let { File(it).name },
|
||||||
|
events = events.chained(),
|
||||||
|
created = events.chained().firstOrNull()?.created ?: 0
|
||||||
|
))
|
||||||
}
|
}
|
||||||
template?.convertAndSend("/topic/chained/all",collections)
|
template?.convertAndSend("/topic/chained/all", holders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -33,17 +40,23 @@ class ChainedEventsTopic(
|
|||||||
this.forEach { event ->
|
this.forEach { event ->
|
||||||
val eventId = event.metadata.eventId
|
val eventId = event.metadata.eventId
|
||||||
val derivedFromEventId = event.metadata.derivedFromEventId
|
val derivedFromEventId = event.metadata.derivedFromEventId
|
||||||
val chain = chains.getOrPut(eventId) { EventChain(eventId, event.eventType.toString()) }
|
val created = event.metadata.created.toEpochSeconds() * 1000L
|
||||||
|
val chain = chains.getOrPut(eventId) {
|
||||||
|
EventChain(eventId, event.eventType.toString(), created, success = event.isSuccessful(), skipped = event.isSkipped(), failure = event.isFailed())
|
||||||
|
}
|
||||||
|
|
||||||
if (derivedFromEventId != null && eventMap.containsKey(derivedFromEventId)) {
|
if (derivedFromEventId != null && eventMap.containsKey(derivedFromEventId)) {
|
||||||
val parentChain = chains.getOrPut(derivedFromEventId) {
|
val parentChain = chains.getOrPut(derivedFromEventId) {
|
||||||
EventChain(derivedFromEventId, eventMap[derivedFromEventId]!!.eventType.toString())
|
EventChain(derivedFromEventId, eventMap[derivedFromEventId]!!.eventType.toString(), created, success = event.isSuccessful(), skipped = event.isSkipped(), failure = event.isFailed())
|
||||||
}
|
}
|
||||||
parentChain.elements.add(chain)
|
parentChain.events.add(chain)
|
||||||
children.add(eventId)
|
children.add(eventId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chains.values.forEach { chain -> chain.events.sortBy { it.created }}
|
||||||
|
|
||||||
return chains.values.filter { it.eventId !in children }
|
return chains.values.filter { it.eventId !in children }
|
||||||
|
.sortedBy { it.created }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,7 +6,7 @@ import org.springframework.messaging.simp.SimpMessagingTemplate
|
|||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class EventsTableTopic(
|
class PersistentEventsTableTopic(
|
||||||
@Autowired private val template: SimpMessagingTemplate?,
|
@Autowired private val template: SimpMessagingTemplate?,
|
||||||
//@Autowired private val persistentEventsTableService: PersistentEventsTableService
|
//@Autowired private val persistentEventsTableService: PersistentEventsTableService
|
||||||
) {
|
) {
|
||||||
2451
apps/ui/web/package-lock.json
generated
2451
apps/ui/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
|||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/styled-components": "^5.1.27",
|
"@types/styled-components": "^5.1.27",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-d3-tree": "^3.6.5",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "^8.1.1",
|
"react-redux": "^8.1.1",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
|
|||||||
@ -1,6 +1,19 @@
|
|||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
|
|
||||||
|
|
||||||
|
export function toUnixTimestamp({ timestamp }: { timestamp?: number }) {
|
||||||
|
if (!timestamp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const day = date.getDate();
|
||||||
|
const month = date.toLocaleString('default', { month: 'short' });
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
return `${day}.${month}.${year} ${hours}.${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function UnixTimestamp({ timestamp }: { timestamp?: number }) {
|
export function UnixTimestamp({ timestamp }: { timestamp?: number }) {
|
||||||
if (!timestamp) {
|
if (!timestamp) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,141 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { RootState } from "../store";
|
|
||||||
import IconArrowUp from '@mui/icons-material/ArrowUpward';
|
|
||||||
import IconArrowDown from '@mui/icons-material/ArrowDownward';
|
|
||||||
import { Table, TableHead, TableRow, TableCell, TableBody, Typography, Box, useTheme, TableContainer } from "@mui/material";
|
|
||||||
|
|
||||||
export interface TablePropetyConfig {
|
|
||||||
label: string
|
|
||||||
accessor: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TableCellCustomizer<T> {
|
|
||||||
(accessor: string, data: T): JSX.Element | null
|
|
||||||
}
|
|
||||||
|
|
||||||
type NullableTableRowActionEvents<T> = TableRowActionEvents<T> | null;
|
|
||||||
export interface TableRowActionEvents<T> {
|
|
||||||
click: (row: T) => void;
|
|
||||||
doubleClick: (row: T) => void;
|
|
||||||
contextMenu?: (row: T, x: number, y: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default function SimpleTable<T>({ items, columns, customizer, onRowClickedEvent }: { items: Array<T>, columns: Array<TablePropetyConfig>, customizer?: TableCellCustomizer<T>, onRowClickedEvent?: TableRowActionEvents<T> }) {
|
|
||||||
const muiTheme = useTheme();
|
|
||||||
|
|
||||||
const [order, setOrder] = useState<'asc' | 'desc'>('asc');
|
|
||||||
const [orderBy, setOrderBy] = useState<string>('');
|
|
||||||
const [selectedRow, setSelectedRow] = useState<T | null>(null);
|
|
||||||
|
|
||||||
const tableRowSingleClicked = (row: T | null) => {
|
|
||||||
setSelectedRow(row);
|
|
||||||
if (row && onRowClickedEvent) {
|
|
||||||
onRowClickedEvent.click(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const tableRowDoubleClicked = (row: T | null) => {
|
|
||||||
setSelectedRow(row);
|
|
||||||
if (row && onRowClickedEvent) {
|
|
||||||
onRowClickedEvent.doubleClick(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableRowContextMenu = (e: React.MouseEvent<HTMLTableRowElement, MouseEvent> , row: T | null) => {
|
|
||||||
if (row && onRowClickedEvent && onRowClickedEvent.contextMenu) {
|
|
||||||
e.preventDefault()
|
|
||||||
onRowClickedEvent.contextMenu(row, e.pageX, e.pageY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSort = (property: string) => {
|
|
||||||
const isAsc = orderBy === property && order === 'asc';
|
|
||||||
setOrder(isAsc ? 'desc' : 'asc');
|
|
||||||
setOrderBy(property);
|
|
||||||
};
|
|
||||||
|
|
||||||
const compareValues = (a: any, b: any, orderBy: string) => {
|
|
||||||
if (typeof a[orderBy] === 'string') {
|
|
||||||
return a[orderBy].localeCompare(b[orderBy]);
|
|
||||||
} else if (typeof a[orderBy] === 'number') {
|
|
||||||
return a[orderBy] - b[orderBy];
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedData = items.slice().sort((a, b) => {
|
|
||||||
if (order === 'asc') {
|
|
||||||
return compareValues(a, b, orderBy);
|
|
||||||
} else {
|
|
||||||
return compareValues(b, a, orderBy);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handleSort(columns[0].accessor)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column", // Bruk column-fleksretning
|
|
||||||
height: "100%",
|
|
||||||
overflow: "hidden"
|
|
||||||
}}>
|
|
||||||
<TableContainer sx={{
|
|
||||||
flex: 1,
|
|
||||||
overflowY: "auto",
|
|
||||||
position: "relative", // Legg til denne linjen for å justere layout
|
|
||||||
maxHeight: "100%" // Legg til denne linjen for å begrense høyden
|
|
||||||
}}>
|
|
||||||
<Table>
|
|
||||||
<TableHead sx={{
|
|
||||||
position: "sticky",
|
|
||||||
top: 0,
|
|
||||||
backgroundColor: muiTheme.palette.background.paper,
|
|
||||||
}}>
|
|
||||||
<TableRow>
|
|
||||||
{columns.map((column) => (
|
|
||||||
<TableCell key={column.accessor} onClick={() => handleSort(column.accessor)} sx={{ cursor: "pointer" }}>
|
|
||||||
<Box display="flex">
|
|
||||||
{orderBy === column.accessor ?
|
|
||||||
(order === "asc" ? (<IconArrowDown />) : (<IconArrowUp />)) : (
|
|
||||||
<IconArrowDown sx={{ color: "transparent" }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<Typography>{column.label}</Typography>
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody sx={{
|
|
||||||
overflowY: "scroll"
|
|
||||||
}}>
|
|
||||||
{sortedData?.map((row: T, rowIndex: number) => (
|
|
||||||
<TableRow key={rowIndex}
|
|
||||||
onClick={() => tableRowSingleClicked(row)}
|
|
||||||
onDoubleClick={() => tableRowDoubleClicked(row)}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
tableRowContextMenu(e, row);
|
|
||||||
tableRowSingleClicked(row);
|
|
||||||
}}
|
|
||||||
style={{ cursor: "pointer", backgroundColor: selectedRow === row ? muiTheme.palette.action.selected : '' }}
|
|
||||||
>
|
|
||||||
{columns.map((column) => (
|
|
||||||
<TableCell key={column.accessor}>
|
|
||||||
{customizer && customizer(column.accessor, row) !== null
|
|
||||||
? customizer(column.accessor, row)
|
|
||||||
: <Typography variant='body1'>{(row as any)[column.accessor]}</Typography>}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,41 +1,68 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useStompClient } from 'react-stomp-hooks';
|
import { useStompClient } from 'react-stomp-hooks';
|
||||||
import { RootState } from "../store";
|
import { RootState } from "../store";
|
||||||
import { useWsSubscription } from "../ws/subscriptions";
|
import { useWsSubscription } from "../ws/subscriptions";
|
||||||
import { set } from "../store/persistent-events-slice";
|
|
||||||
import { EventsObjectListResponse } from "../../types";
|
import { EventsObjectListResponse } from "../../types";
|
||||||
import IconRefresh from '@mui/icons-material/Refresh'
|
import IconRefresh from '@mui/icons-material/Refresh'
|
||||||
import { Button } from "@mui/material";
|
import { Box, Button, Typography, useTheme } from "@mui/material";
|
||||||
|
import { Tree } from "react-d3-tree";
|
||||||
|
import { EventChain, EventGroup, EventGroups, set } from "../store/chained-events-slice";
|
||||||
|
import { toUnixTimestamp, UnixTimestamp } from "../features/UxTc";
|
||||||
|
import { TableCellCustomizer, TablePropetyConfig } from "../features/table/table";
|
||||||
|
import ExpandableTable, { ExpandableItem } from "../features/table/expandableTable";
|
||||||
|
import { CustomNodeElementProps } from 'react-d3-tree';
|
||||||
|
|
||||||
|
|
||||||
export default function EventsChainPage() {
|
interface RawNodeDatum {
|
||||||
const dispatch = useDispatch();
|
name: string;
|
||||||
const client = useStompClient();
|
attributes?: Record<string, string | number | boolean>;
|
||||||
const cursor = useSelector((state: RootState) => state.persistentEvents)
|
children?: RawNodeDatum[];
|
||||||
|
fill?: string;
|
||||||
function log(data: any) {
|
|
||||||
console.log(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useWsSubscription("/topic/chained/all", (response) => {
|
// Transformasjonsfunksjon for EventChain
|
||||||
console.log(response)
|
const transformEventChain = (eventChain: EventChain): RawNodeDatum => ({
|
||||||
|
name: eventChain.eventName,
|
||||||
|
attributes: {
|
||||||
|
//eventId: eventChain.eventId,
|
||||||
|
created: toUnixTimestamp({ timestamp: eventChain.created }) ?? "",
|
||||||
|
success: eventChain.success,
|
||||||
|
skipped: eventChain.skipped,
|
||||||
|
failure: eventChain.failure,
|
||||||
|
childrens: eventChain.events.length
|
||||||
|
},
|
||||||
|
fill: "white",
|
||||||
|
children: eventChain.events.map(transformEventChain),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Transformasjonsfunksjon for EventGroups
|
||||||
|
const transformEventGroups = (group: EventGroup): RawNodeDatum[] => {
|
||||||
|
return group.events.map(transformEventChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventGroupToTreeView extends EventGroup {
|
||||||
|
underView: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EventsChainPage() {
|
||||||
|
const muiTheme = useTheme();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const client = useStompClient();
|
||||||
|
const cursor = useSelector((state: RootState) => state.chained)
|
||||||
|
const [useReferenceId, setUseReferenceId] = useState<string | null>(null);
|
||||||
|
const [treeData, setTreeData] = useState<RawNodeDatum[] | null>(null);
|
||||||
|
|
||||||
|
useWsSubscription<Array<EventGroup>>("/topic/chained/all", (response) => {
|
||||||
|
dispatch(set(response))
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (Object.keys(cursor).length === 0) {
|
||||||
|
|
||||||
// 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({
|
client?.publish({
|
||||||
destination: "/app/chained/all"
|
destination: "/app/chained/all"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Alternativt, du kan dispatche en Redux handling her
|
|
||||||
// dispatch(fetchDataAction()); // Eksempel på å dispatche en handling
|
|
||||||
}
|
}
|
||||||
}, [cursor, client, dispatch]);
|
}, [cursor, client, dispatch]);
|
||||||
|
|
||||||
@ -45,6 +72,104 @@ export default function EventsChainPage() {
|
|||||||
"body": "Potato"
|
"body": "Potato"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (useReferenceId) {
|
||||||
|
const eventGroup = cursor.groups.find((group) => group.referenceId === useReferenceId);
|
||||||
|
if (eventGroup) {
|
||||||
|
const data = transformEventGroups(eventGroup);
|
||||||
|
console.log({
|
||||||
|
"info": "Tree data",
|
||||||
|
"data": data
|
||||||
|
})
|
||||||
|
setTreeData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [useReferenceId, cursor])
|
||||||
|
|
||||||
|
const createTableCell: TableCellCustomizer<EventGroup> = (accessor, data) => {
|
||||||
|
switch (accessor) {
|
||||||
|
case "created": {
|
||||||
|
if (typeof data[accessor] === "number") {
|
||||||
|
const timestampObject = { timestamp: data[accessor] as number }; // Opprett et objekt med riktig struktur
|
||||||
|
return UnixTimestamp(timestampObject);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: Array<TablePropetyConfig> = [
|
||||||
|
{ label: "ReferenceId", accessor: "referenceId" },
|
||||||
|
{ label: "File", accessor: "fileName" },
|
||||||
|
{ label: "Created", accessor: "created" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
function renderCustomNodeElement(nodeData: CustomNodeElementProps): JSX.Element {
|
||||||
|
const attr = nodeData.nodeDatum.attributes;
|
||||||
|
const nodeColor = attr?.success ? "green" : attr?.failure ? "red" : "yellow";
|
||||||
|
return (<>
|
||||||
|
<g>
|
||||||
|
<circle r="15" fill={nodeColor} onClick={nodeData.toggleNode} />
|
||||||
|
<text fill="white" stroke="white" strokeWidth="0" x="20">
|
||||||
|
{nodeData.nodeDatum.name}
|
||||||
|
</text>
|
||||||
|
{attr?.created && (
|
||||||
|
<text fill="lightgray" x="20" dy="20" strokeWidth="0">
|
||||||
|
Created: {attr?.created}
|
||||||
|
</text>
|
||||||
|
)}
|
||||||
|
{attr?.success && (
|
||||||
|
<text fill="lightgray" x="20" dy="40" strokeWidth="0">
|
||||||
|
Success
|
||||||
|
</text>
|
||||||
|
)}
|
||||||
|
{attr?.failure && (
|
||||||
|
<text fill="lightgray" x="20" dy="40" strokeWidth="0">
|
||||||
|
Failure
|
||||||
|
</text>
|
||||||
|
)}
|
||||||
|
{attr?.skipped && (
|
||||||
|
<text fill="lightgray" x="20" dy="40" strokeWidth="0">
|
||||||
|
Skipped
|
||||||
|
</text>
|
||||||
|
)}
|
||||||
|
{attr?.childrens && (
|
||||||
|
<text fill="lightgray" x="20" dy="60" strokeWidth="0">
|
||||||
|
{attr?.childrens} derived {Number(attr?.childrens) > 1 ? "events" : "event"}
|
||||||
|
</text>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderExpandableItem(item: EventGroup): ExpandableItem<EventGroup> | null {
|
||||||
|
return {
|
||||||
|
tag: item.referenceId,
|
||||||
|
expandElement: (() => {
|
||||||
|
const data = transformEventGroups(item);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(data) ? (
|
||||||
|
<div id="treeWrapper" style={{ width: '100%', height: '60vh', }}>
|
||||||
|
<Tree data={data} orientation="vertical" separation={{
|
||||||
|
nonSiblings: 3,
|
||||||
|
siblings: 2
|
||||||
|
}}
|
||||||
|
renderCustomNodeElement={renderCustomNodeElement}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : <Typography>Tree data not available</Typography>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@ -52,7 +177,19 @@ export default function EventsChainPage() {
|
|||||||
onClick={onRefresh} sx={{
|
onClick={onRefresh} sx={{
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
textTransform: 'none'
|
textTransform: 'none'
|
||||||
}}>Refresh</Button >
|
}}>Refresh
|
||||||
|
</Button>
|
||||||
|
<Box display="block">
|
||||||
|
<Box sx={{
|
||||||
|
display: "block",
|
||||||
|
height: "calc(100% - 120px)",
|
||||||
|
overflow: "hidden",
|
||||||
|
position: "absolute",
|
||||||
|
width: "100%"
|
||||||
|
}}>
|
||||||
|
<ExpandableTable items={cursor?.groups ?? []} columns={columns} cellCustomizer={createTableCell} expandableRender={renderExpandableItem} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3,7 +3,7 @@ import { UnixTimestamp } from '../features/UxTc';
|
|||||||
import { Box, Button, Typography, useTheme } from '@mui/material';
|
import { Box, Button, Typography, useTheme } from '@mui/material';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { RootState } from '../store';
|
import { RootState } from '../store';
|
||||||
import SimpleTable, { TableCellCustomizer, TablePropetyConfig, TableRowActionEvents } from '../features/table';
|
import { TableCellCustomizer, TablePropetyConfig, TableRowActionEvents } from '../features/table/table';
|
||||||
import { useStompClient } from 'react-stomp-hooks';
|
import { useStompClient } from 'react-stomp-hooks';
|
||||||
import { useWsSubscription } from '../ws/subscriptions';
|
import { useWsSubscription } from '../ws/subscriptions';
|
||||||
import { updateItems } from '../store/explorer-slice';
|
import { updateItems } from '../store/explorer-slice';
|
||||||
@ -14,6 +14,7 @@ import IconHome from '@mui/icons-material/Home';
|
|||||||
import { ExplorerItem, ExplorerCursor, ExplorerItemType } from '../../types';
|
import { ExplorerItem, ExplorerCursor, ExplorerItemType } from '../../types';
|
||||||
import ContextMenu, { ContextMenuActionEvent, ContextMenuItem } from '../features/ContextMenu';
|
import ContextMenu, { ContextMenuActionEvent, ContextMenuItem } from '../features/ContextMenu';
|
||||||
import { canConvert, canEncode, canExtract } from '../../fileUtil';
|
import { canConvert, canEncode, canExtract } from '../../fileUtil';
|
||||||
|
import SimpleTable from '../features/table/sortableTable';
|
||||||
|
|
||||||
|
|
||||||
const createTableCell: TableCellCustomizer<ExplorerItem> = (accessor, data) => {
|
const createTableCell: TableCellCustomizer<ExplorerItem> = (accessor, data) => {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import SimpleTable, { TableCellCustomizer, TablePropetyConfig } from "../features/table"
|
|
||||||
import { RootState } from "../store";
|
import { RootState } from "../store";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useStompClient } from "react-stomp-hooks";
|
import { useStompClient } from "react-stomp-hooks";
|
||||||
@ -7,6 +6,8 @@ import { Box, Button, IconButton, Typography, useTheme } from "@mui/material";
|
|||||||
import IconRefresh from '@mui/icons-material/Refresh'
|
import IconRefresh from '@mui/icons-material/Refresh'
|
||||||
import IconCompleted from '@mui/icons-material/Check'
|
import IconCompleted from '@mui/icons-material/Check'
|
||||||
import IconWorking from '@mui/icons-material/Engineering';
|
import IconWorking from '@mui/icons-material/Engineering';
|
||||||
|
import { TablePropetyConfig } from "../features/table/table";
|
||||||
|
import SimpleTable from "../features/table/sortableTable";
|
||||||
|
|
||||||
const columns: Array<TablePropetyConfig> = [
|
const columns: Array<TablePropetyConfig> = [
|
||||||
{ label: "Title", accessor: "givenTitle" },
|
{ label: "Title", accessor: "givenTitle" },
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import explorerSlice from './store/explorer-slice';
|
|||||||
import kafkaItemsFlatSlice from './store/kafka-items-flat-slice';
|
import kafkaItemsFlatSlice from './store/kafka-items-flat-slice';
|
||||||
import contextMenuSlice from './store/context-menu-slice';
|
import contextMenuSlice from './store/context-menu-slice';
|
||||||
import persistentEventsSlice from './store/persistent-events-slice';
|
import persistentEventsSlice from './store/persistent-events-slice';
|
||||||
|
import chainedEventsSlice from './store/chained-events-slice';
|
||||||
|
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
@ -12,7 +13,8 @@ export const store = configureStore({
|
|||||||
explorer: explorerSlice,
|
explorer: explorerSlice,
|
||||||
kafkaComposedFlat: kafkaItemsFlatSlice,
|
kafkaComposedFlat: kafkaItemsFlatSlice,
|
||||||
contextMenu: contextMenuSlice,
|
contextMenu: contextMenuSlice,
|
||||||
persistentEvents: persistentEventsSlice
|
persistentEvents: persistentEventsSlice,
|
||||||
|
chained: chainedEventsSlice,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const root = ReactDOM.createRoot(
|
|||||||
|
|
||||||
const protocol = window.location.protocol;
|
const protocol = window.location.protocol;
|
||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
const wsUrl = `${protocol}//${host}/ws`;
|
const wsUrl = "http://localhost:8080/ws" //`${protocol}//${host}/ws`;
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|||||||
@ -21,6 +21,10 @@ inline fun <reified T: Event> List<Event>.findFirstEventOf(): T? {
|
|||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun List<Event>.findFirstOf(events: Events): Event? {
|
||||||
|
return this.firstOrNull { it.eventType == events }
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <reified T: Event> List<Event>.findEventsOf(): List<T> {
|
inline fun <reified T: Event> List<Event>.findEventsOf(): List<T> {
|
||||||
return this.filterIsInstance<T>().map { it }
|
return this.filterIsInstance<T>().map { it }
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user