Skip to content

Commit e8579a8

Browse files
committed
Merge branch '335-petri-net-rendering' into 'master'
fix: poor rendering of Petri net (fix #335) Closes #335 See merge request processm-team/processm!342 Former-commit-id: 5c7f03b23c6ee6d246dd2fbcb7e1d11c02831e92
2 parents 6b85b46 + 92f8464 commit e8579a8

File tree

15 files changed

+125
-104
lines changed

15 files changed

+125
-104
lines changed

processm.core/src/main/kotlin/processm/core/log/hierarchical/Helpers.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal fun ResultSet.to2DIntArray(): List<IntArray> =
3636
var maxSize = 0
3737
this@to2DIntArray.use {
3838
while (it.next()) {
39-
val array = (it.getArray(1).array as Array<Int>).toIntArray()
39+
val array = ((it.getArray(1) ?: continue).array as Array<Int>).toIntArray()
4040
if (array.size > maxSize)
4141
maxSize = array.size
4242
out.add(array)

processm.dbmodels/src/main/kotlin/processm.dbmodels/models/WorkspaceComponentExtensions.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ fun WorkspaceComponent.triggerEvent(
2929
eventData?.let { setStringProperty(WORKSPACE_COMPONENT_EVENT_DATA, it) }
3030
setString(WORKSPACE_COMPONENT_ID, id.value.toString())
3131
if (event == WorkspaceComponentEventType.DataChange) {
32+
requireNotNull(eventData) {
33+
"eventData must be set for DataChange event. It is recommended to use the triggerEvent(producer: Producer, eventData: DataChangeType) overload."
34+
}
3235
setString(WORKSPACE_ID, workspace.id.toString())
3336
}
3437
}
@@ -179,4 +182,4 @@ class ProcessModelComponentData private constructor() {
179182
}
180183
return false
181184
}
182-
}
185+
}

processm.helpers/src/main/kotlin/processm/helpers/AbstractLocalizedException.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ abstract class AbstractLocalizedException(
1616
message: String
1717
) : Exception(message) {
1818

19+
/**
20+
* Reads the formatting string for the given [key] and [locale].
21+
*
22+
* @throws IllegalStateException if the given [key] is not found for the given [locale] and the fallback locale en_US.
23+
*/
1924
protected fun getFormatString(locale: Locale, key: String) =
2025
try {
2126
locale.getErrorMessage(key)
@@ -25,7 +30,7 @@ abstract class AbstractLocalizedException(
2530
Locale.US.getErrorMessage(key)
2631
} catch (e: MissingResourceException) {
2732
logger().warn("Missing translation of {} to en_US (fallback)", key)
28-
key
33+
throw IllegalStateException("Missing translation of $key to $locale.")
2934
}
3035
}
3136

@@ -35,4 +40,4 @@ abstract class AbstractLocalizedException(
3540
abstract fun localizedMessage(locale: Locale): String
3641

3742

38-
}
43+
}

processm.miners/src/main/kotlin/processm/miners/kpi/LogKPIService.kt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import jakarta.jms.Message
55
import org.jetbrains.exposed.sql.and
66
import org.jetbrains.exposed.sql.select
77
import org.quartz.*
8-
import processm.core.communication.Producer
98
import processm.core.esb.AbstractJobService
109
import processm.core.esb.ServiceJob
1110
import processm.core.log.hierarchical.DBHierarchicalXESInputStream
1211
import processm.core.persistence.connection.transactionMain
1312
import processm.core.querylanguage.Query
13+
import processm.dbmodels.afterCommit
1414
import processm.dbmodels.models.*
15+
import processm.helpers.AbstractLocalizedException
1516
import processm.helpers.toUUID
1617
import processm.logging.loggedScope
1718
import java.time.Instant
@@ -90,17 +91,29 @@ class LogKPIService : AbstractJobService(
9091
val stream =
9192
DBHierarchicalXESInputStream(component.dataStoreId.toString(), Query(component.query), false)
9293
val first = stream.take(2).toList()
93-
require(first.size == 1) { "The query must return exactly one log." }
94-
require(first[0].attributes.size == 1) { "The query must return exactly one attribute." }
94+
require(first.size == 1) { "The query returned ${first.size} event logs but exactly one event log is expected." }
95+
require(first[0].attributes.size == 1) { "The query returned ${first[0].attributes.size} attributes but exactly one attribute is expected." }
9596

9697
component.data = first[0].attributes.values.first().toString()
9798
component.dataLastModified = Instant.now()
9899
component.lastError = null
100+
101+
component.afterCommit {
102+
// TODO: add support for versioning of KPI values; for now just send it as initial model to just update value in GUI
103+
component.triggerEvent(eventData = DataChangeType.InitialModel)
104+
}
99105
} catch (e: Exception) {
100-
component.lastError = e.message
106+
// FIXME: drop hardcoded locale in favor of storing error id or serialized exception and formatting
107+
// error messages in processm.services for the given request locale
108+
component.lastError =
109+
if (e is AbstractLocalizedException) e.localizedMessage(Locale.ENGLISH) else e.message
110+
111+
component.afterCommit {
112+
component.triggerEvent(eventData = DataChangeType.LastError)
113+
}
114+
101115
logger.warn("Cannot calculate log-based KPI for component with id $id.", e)
102116
}
103-
component.triggerEvent(Producer(), WorkspaceComponentEventType.DataChange)
104117
}
105118
}
106119
}

processm.miners/src/test/kotlin/processm/miners/kpi/LogKPIServiceTests.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import org.jetbrains.exposed.sql.deleteWhere
66
import org.junit.jupiter.api.AfterAll
77
import org.junit.jupiter.api.BeforeAll
88
import processm.core.DBTestHelper
9-
import processm.core.communication.Producer
109
import processm.core.esb.Artemis
1110
import processm.core.esb.ServiceStatus
1211
import processm.core.persistence.connection.transactionMain
@@ -46,7 +45,7 @@ class LogKPIServiceTests {
4645
query = _query
4746
workspace = Workspace.all().firstOrNull() ?: Workspace.new { name = "test-workspace" }
4847
}
49-
}.triggerEvent(Producer(), WorkspaceComponentEventType.ComponentCreatedOrUpdated)
48+
}.triggerEvent(event = WorkspaceComponentEventType.ComponentCreatedOrUpdated)
5049
}
5150

5251
@AfterTest
@@ -154,7 +153,7 @@ class LogKPIServiceTests {
154153
}.first()
155154
component.query = "select count(^t:name) where l:name='JournalReview'"
156155
component
157-
}.triggerEvent(Producer(), WorkspaceComponentEventType.ComponentCreatedOrUpdated)
156+
}.triggerEvent(event = WorkspaceComponentEventType.ComponentCreatedOrUpdated)
158157

159158

160159
Thread.sleep(1000L) // wait for calculation

processm.services/src/main/kotlin/processm/services/api/WorkspacesApi.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import kotlinx.serialization.encodeToString
1515
import org.koin.ktor.ext.inject
1616
import processm.dbmodels.models.ComponentTypeDto
1717
import processm.dbmodels.models.RoleType
18-
import processm.dbmodels.models.WorkspaceComponent
1918
import processm.dbmodels.models.Workspaces
2019
import processm.helpers.mapToArray
2120
import processm.logging.loggedScope
@@ -51,7 +50,14 @@ fun Route.WorkspacesApi() {
5150

5251
val workspaceId = workspaceService.create(newWorkspace.name, principal.userId, newWorkspace.organizationId)
5352

54-
call.respond(HttpStatusCode.Created, Workspace(newWorkspace.name, workspaceId))
53+
call.respond(
54+
HttpStatusCode.Created,
55+
Workspace(
56+
id = workspaceId,
57+
name = newWorkspace.name,
58+
role = OrganizationRole.owner
59+
)
60+
)
5561
}
5662

5763
delete<Paths.Workspace> { path ->
@@ -66,7 +72,13 @@ fun Route.WorkspacesApi() {
6672
get<Paths.Workspaces> {
6773
val principal = call.authentication.principal<ApiUser>()!!
6874
val workspaces = workspaceService.getUserWorkspaces(principal.userId)
69-
.map { Workspace(it.first.name, it.first.id.value, OrganizationRole.valueOf(it.second.value)) }
75+
.map {
76+
Workspace(
77+
id = it.first.id.value,
78+
name = it.first.name,
79+
role = OrganizationRole.valueOf(it.second.value)
80+
)
81+
}
7082
.toTypedArray()
7183

7284
call.respond(HttpStatusCode.OK, workspaces)

processm.services/src/main/resources/api-spec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2158,7 +2158,7 @@ components:
21582158
type: string
21592159
Workspace:
21602160
type: object
2161-
required: [ name ]
2161+
required: [ id, name ]
21622162
properties:
21632163
id:
21642164
type: string

processm.services/src/test/kotlin/processm/services/api/NotificationsApiTest.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import org.junit.jupiter.api.Timeout
1111
import org.junit.jupiter.params.ParameterizedTest
1212
import org.junit.jupiter.params.provider.ValueSource
1313
import org.koin.test.mock.declareMock
14-
import processm.core.communication.Producer
1514
import processm.core.esb.Artemis
1615
import processm.core.models.metadata.URN
1716
import processm.dbmodels.models.*
@@ -122,7 +121,7 @@ class NotificationsApiTest : BaseApiTest() {
122121
repeat(5) {
123122
delay(200L)
124123
println("Producing")
125-
component1.triggerEvent(Producer(), WorkspaceComponentEventType.DataChange)
124+
component1.triggerEvent(eventData = DataChangeType.Model)
126125
}
127126
}
128127
runBlocking {
@@ -160,8 +159,8 @@ class NotificationsApiTest : BaseApiTest() {
160159
withAuthentication(userId) {
161160
launch(context = Dispatchers.Request) {
162161
sync.receive()
163-
component1.triggerEvent(Producer(), WorkspaceComponentEventType.DataChange)
164-
component2.triggerEvent(Producer(), WorkspaceComponentEventType.DataChange)
162+
component1.triggerEvent(eventData = DataChangeType.Model)
163+
component2.triggerEvent(eventData = DataChangeType.Model)
165164
}
166165
runBlocking {
167166
handleSse("/api/notifications") { channel ->
@@ -199,7 +198,7 @@ class NotificationsApiTest : BaseApiTest() {
199198
}
200199
runBlocking {
201200
repeat(n) { sync.receive() }
202-
component1.triggerEvent(Producer(), WorkspaceComponentEventType.DataChange)
201+
component1.triggerEvent(eventData = DataChangeType.Model)
203202
jobs.forEach { it.join() }
204203
}
205204
}
@@ -233,11 +232,11 @@ class NotificationsApiTest : BaseApiTest() {
233232
}
234233
runBlocking {
235234
repeat(n) { sync.receive() }
236-
component1.triggerEvent(Producer(), WorkspaceComponentEventType.DataChange)
235+
component1.triggerEvent(eventData = DataChangeType.Model)
237236
jobs.forEach { it.join() }
238237
}
239238
}
240239
assertEquals(n, result.size)
241240
assertTrue { result.all { it.equals(componentId1) } }
242241
}
243-
}
242+
}

processm.webui/src/main/frontend-app/src/components/petri-net-editor/PetriNetEditor.vue

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,16 @@
3939
<div v-show="!isDebuggerEnabled" class="fill-height">
4040
<svg ref="editorSvg" height="100%" width="100%">
4141
<defs>
42-
<marker id="arrow" markerHeight="25" markerUnits="userSpaceOnUse" markerWidth="25" orient="auto-start-reverse" refX="0" refY="5" viewBox="0 0 10 10">
42+
<marker
43+
:id="'arrow-' + svgId"
44+
markerHeight="25"
45+
markerUnits="userSpaceOnUse"
46+
markerWidth="25"
47+
orient="auto-start-reverse"
48+
refX="0"
49+
refY="5"
50+
viewBox="0 0 10 10"
51+
>
4352
<path d="M 0 0 L 10 5 L 0 10 z" />
4453
</marker>
4554
</defs>
@@ -77,6 +86,7 @@ import { Place } from "@/components/petri-net-editor/model/Place";
7786
import { Transition } from "@/components/petri-net-editor/model/Transition";
7887
import { Arc } from "@/components/petri-net-editor/model/Arc";
7988
import svgPanZoom from "svg-pan-zoom";
89+
import { waitForRepaint } from "@/utils/waitForRepaint";
8090
8191
@Component({
8292
name: "petri-net-editor",
@@ -94,6 +104,7 @@ export default class PetriNetEditor extends Vue {
94104
contextMenu: ContextMenu;
95105
editorSvg: HTMLElement;
96106
};
107+
private svgId = uuidv4();
97108
private isEditPlaceDialogVisible = false;
98109
private isEditTransitionDialogVisible = false;
99110
private isExportPnmlDialogVisible = false;
@@ -129,15 +140,15 @@ export default class PetriNetEditor extends Vue {
129140
130141
// noinspection JSUnusedGlobalSymbols
131142
mounted() {
132-
const svgId = `editor-${uuidv4()}`;
143+
const editorId = `editor-${this.svgId}`;
133144
134-
this.$refs.editorSvg.setAttribute("id", svgId);
145+
this.$refs.editorSvg.setAttribute("id", editorId);
135146
136-
this.petriNetManager = new PetriNetSvgManager(d3.select(`#${svgId}`), this.enableDragging);
147+
this.petriNetManager = new PetriNetSvgManager(this.svgId, d3.select(`#${editorId}`), this.enableDragging);
137148
138149
this.layouter = new BlockLayouter(this.debug);
139150
140-
this.redraw();
151+
waitForRepaint(this.redraw.bind(this));
141152
}
142153
143154
redraw(forceLayouter: boolean = false) {
@@ -326,4 +337,4 @@ export default class PetriNetEditor extends Vue {
326337
}
327338
}
328339
}
329-
</script>
340+
</script>

0 commit comments

Comments
 (0)