@valtimo/process-management 13.45.1 → 13.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/valtimo-process-management.mjs +222 -11
- package/fesm2022/valtimo-process-management.mjs.map +1 -1
- package/lib/components/process-management-builder/behaviors/auto-id-behavior.d.ts +20 -0
- package/lib/components/process-management-builder/behaviors/auto-id-behavior.d.ts.map +1 -0
- package/lib/components/process-management-builder/behaviors/index.d.ts +2 -0
- package/lib/components/process-management-builder/behaviors/index.d.ts.map +1 -0
- package/lib/components/process-management-builder/panel/valtimo-properties-provider.d.ts.map +1 -1
- package/lib/components/process-management-builder/process-management-builder.component.d.ts.map +1 -1
- package/lib/constants/bpmn.constants.d.ts +2 -1
- package/lib/constants/bpmn.constants.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ import { LoadingModule, DropdownModule, SelectModule, ButtonModule, IconModule,
|
|
|
15
15
|
import * as i1 from '@angular/common/http';
|
|
16
16
|
import { HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
17
17
|
import * as i2 from '@valtimo/shared';
|
|
18
|
-
import { BaseApiService, InterceptorSkipHeader, InterceptorSkip, getCaseManagementRouteParams, getBuildingBlockManagementRouteParams, ROLE_ADMIN } from '@valtimo/shared';
|
|
18
|
+
import { BaseApiService, InterceptorSkipHeader, InterceptorSkip, toKebabCase, getCaseManagementRouteParams, getBuildingBlockManagementRouteParams, ROLE_ADMIN } from '@valtimo/shared';
|
|
19
19
|
import { useService, BpmnPropertiesPanelModule, BpmnPropertiesProviderModule, CamundaPlatformPropertiesProviderModule } from 'bpmn-js-properties-panel';
|
|
20
20
|
import Modeler from 'bpmn-js/lib/Modeler';
|
|
21
21
|
import camundaPlatformBehaviors from 'camunda-bpmn-js-behaviors/lib/camunda-platform';
|
|
@@ -25,7 +25,8 @@ import { filter, BehaviorSubject, Subject, Subscription, combineLatest, switchMa
|
|
|
25
25
|
import { distinctUntilChanged, map, filter as filter$1 } from 'rxjs/operators';
|
|
26
26
|
import * as i2$1 from '@valtimo/form';
|
|
27
27
|
import { toObservable } from '@angular/core/rxjs-interop';
|
|
28
|
-
import { is } from 'bpmn-js/lib/util/ModelUtil';
|
|
28
|
+
import { is, getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
|
|
29
|
+
import { TextFieldEntry } from '@bpmn-io/properties-panel';
|
|
29
30
|
import { html } from 'htm/preact';
|
|
30
31
|
import { View16, ViewOff16, Upload16 } from '@carbon/icons';
|
|
31
32
|
import * as i3 from 'ngx-logger';
|
|
@@ -66,6 +67,8 @@ const EMPTY_BPMN = `
|
|
|
66
67
|
</bpmndi:BPMNDiagram>
|
|
67
68
|
</bpmn:definitions>
|
|
68
69
|
`;
|
|
70
|
+
// process_link.activity_id is varchar(64) — longer ids fail on save
|
|
71
|
+
const MAX_ACTIVITY_ID_LENGTH = 64;
|
|
69
72
|
|
|
70
73
|
/*
|
|
71
74
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -897,6 +900,21 @@ const clearBuildingBlockCalledElement = (editor, activityId) => {
|
|
|
897
900
|
* See the License for the specific language governing permissions and
|
|
898
901
|
* limitations under the License.
|
|
899
902
|
*/
|
|
903
|
+
// Mirrors the QName rules of bpmn-js-properties-panel, which does not expose them
|
|
904
|
+
const SPACE_REGEX = /\s/;
|
|
905
|
+
const QNAME_REGEX = /^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i;
|
|
906
|
+
const ID_REGEX = /^[a-z_][\w-.]*$/i;
|
|
907
|
+
const PROCESS_LINKABLE_TYPES = [
|
|
908
|
+
'bpmn:UserTask',
|
|
909
|
+
'bpmn:StartEvent',
|
|
910
|
+
'bpmn:ServiceTask',
|
|
911
|
+
'bpmn:SendTask',
|
|
912
|
+
'bpmn:ReceiveTask',
|
|
913
|
+
'bpmn:IntermediateThrowEvent',
|
|
914
|
+
'bpmn:IntermediateCatchEvent',
|
|
915
|
+
'bpmn:CallActivity',
|
|
916
|
+
];
|
|
917
|
+
const isProcessLinkable = (element) => PROCESS_LINKABLE_TYPES.some(type => is(element, type));
|
|
900
918
|
class ValtimoPropertiesProvider {
|
|
901
919
|
static { this.$inject = ['propertiesPanel', 'translate']; }
|
|
902
920
|
get processManagementEditorService() {
|
|
@@ -932,6 +950,14 @@ class ValtimoPropertiesProvider {
|
|
|
932
950
|
const generalGroup = groups.find((g) => g.id === 'general');
|
|
933
951
|
if (generalGroup) {
|
|
934
952
|
generalGroup.entries = generalGroup.entries.filter((entry) => entry.id !== 'isExecutable');
|
|
953
|
+
// Same scope as the auto-filled id, so typing and generating share one limit
|
|
954
|
+
if (is(element, 'bpmn:FlowNode')) {
|
|
955
|
+
const idEntry = generalGroup.entries.find((entry) => entry.id === 'id');
|
|
956
|
+
if (idEntry) {
|
|
957
|
+
idEntry.component = LengthLimitedIdElement;
|
|
958
|
+
idEntry.translateService = this.translateService;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
935
961
|
}
|
|
936
962
|
if (elementErrors.length > 0) {
|
|
937
963
|
const errorGroup = {
|
|
@@ -966,14 +992,7 @@ class ValtimoPropertiesProvider {
|
|
|
966
992
|
targetGroup.shouldOpen = true;
|
|
967
993
|
}
|
|
968
994
|
}
|
|
969
|
-
if (
|
|
970
|
-
is(element, 'bpmn:StartEvent') ||
|
|
971
|
-
is(element, 'bpmn:ServiceTask') ||
|
|
972
|
-
is(element, 'bpmn:SendTask') ||
|
|
973
|
-
is(element, 'bpmn:ReceiveTask') ||
|
|
974
|
-
is(element, 'bpmn:IntermediateThrowEvent') ||
|
|
975
|
-
is(element, 'bpmn:IntermediateCatchEvent') ||
|
|
976
|
-
is(element, 'bpmn:CallActivity')) {
|
|
995
|
+
if (isProcessLinkable(element)) {
|
|
977
996
|
const editingAllowed = this.processManagementEditorService.editingAllowed;
|
|
978
997
|
if (editingAllowed || processLink) {
|
|
979
998
|
const customGroup = {
|
|
@@ -1204,6 +1223,56 @@ const CustomRootElement = (props) => {
|
|
|
1204
1223
|
</div>`;
|
|
1205
1224
|
return wrapEntry(processLink ? genericLinkedPanel : genericCreatePanel);
|
|
1206
1225
|
};
|
|
1226
|
+
const LengthLimitedIdElement = (props) => {
|
|
1227
|
+
const { element, translateService } = props;
|
|
1228
|
+
const modeling = useService('modeling');
|
|
1229
|
+
const debounce = useService('debounceInput');
|
|
1230
|
+
const translate = useService('translate');
|
|
1231
|
+
const getValue = () => getBusinessObject(element).id;
|
|
1232
|
+
const setValue = (value, error) => {
|
|
1233
|
+
if (error)
|
|
1234
|
+
return;
|
|
1235
|
+
modeling.updateProperties(element, { id: value });
|
|
1236
|
+
};
|
|
1237
|
+
const validate = (value) => {
|
|
1238
|
+
const businessObject = getBusinessObject(element);
|
|
1239
|
+
const assigned = businessObject.$model.ids.assigned(value);
|
|
1240
|
+
if (!value)
|
|
1241
|
+
return translate('ID must not be empty.');
|
|
1242
|
+
if (assigned && assigned !== businessObject)
|
|
1243
|
+
return translate('ID must be unique.');
|
|
1244
|
+
if (SPACE_REGEX.test(value))
|
|
1245
|
+
return translate('ID must not contain spaces.');
|
|
1246
|
+
if (!ID_REGEX.test(value)) {
|
|
1247
|
+
return QNAME_REGEX.test(value)
|
|
1248
|
+
? translate('ID must not contain prefix.')
|
|
1249
|
+
: translate('ID must be a valid QName.');
|
|
1250
|
+
}
|
|
1251
|
+
if (value.length > MAX_ACTIVITY_ID_LENGTH) {
|
|
1252
|
+
return translateService.instant('processManagement.idTooLong', {
|
|
1253
|
+
max: MAX_ACTIVITY_ID_LENGTH,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
return undefined;
|
|
1257
|
+
};
|
|
1258
|
+
// The panel's text field has no maxLength prop, so cap the rendered input directly
|
|
1259
|
+
const capInputLength = (node) => {
|
|
1260
|
+
const input = node?.querySelector('input');
|
|
1261
|
+
if (input)
|
|
1262
|
+
input.maxLength = MAX_ACTIVITY_ID_LENGTH;
|
|
1263
|
+
};
|
|
1264
|
+
return html `<div ref=${capInputLength}>
|
|
1265
|
+
${TextFieldEntry({
|
|
1266
|
+
element,
|
|
1267
|
+
id: 'id',
|
|
1268
|
+
label: translate('ID'),
|
|
1269
|
+
getValue,
|
|
1270
|
+
setValue,
|
|
1271
|
+
debounce,
|
|
1272
|
+
validate,
|
|
1273
|
+
})}
|
|
1274
|
+
</div>`;
|
|
1275
|
+
};
|
|
1207
1276
|
const ValidationErrorsElement = (props) => {
|
|
1208
1277
|
const getErrorMessage = (error) => {
|
|
1209
1278
|
if (error.errorCode) {
|
|
@@ -1947,6 +2016,145 @@ const ExpressionAutocompleteModule = {
|
|
|
1947
2016
|
* limitations under the License.
|
|
1948
2017
|
*/
|
|
1949
2018
|
|
|
2019
|
+
/*
|
|
2020
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
2021
|
+
*
|
|
2022
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2023
|
+
* you may not use this file except in compliance with the License.
|
|
2024
|
+
* You may obtain a copy of the License at
|
|
2025
|
+
*
|
|
2026
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2027
|
+
*
|
|
2028
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2029
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2030
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2031
|
+
* See the License for the specific language governing permissions and
|
|
2032
|
+
* limitations under the License.
|
|
2033
|
+
*/
|
|
2034
|
+
const MAX_UNIQUE_SUFFIX = 100;
|
|
2035
|
+
const TRAILING_SEPARATOR_REGEX = /[-_]+$/;
|
|
2036
|
+
class AutoIdBehavior {
|
|
2037
|
+
static { this.$inject = ['eventBus', 'modeling', 'elementRegistry']; }
|
|
2038
|
+
constructor(eventBus, modeling, elementRegistry) {
|
|
2039
|
+
this.modeling = modeling;
|
|
2040
|
+
this.elementRegistry = elementRegistry;
|
|
2041
|
+
this._ownedElements = new WeakSet();
|
|
2042
|
+
this._applyingId = false;
|
|
2043
|
+
this._replacedElementWasOwned = false;
|
|
2044
|
+
eventBus.on('commandStack.shape.create.postExecuted', ({ context }) => {
|
|
2045
|
+
const element = context?.shape;
|
|
2046
|
+
if (this.isEligible(element))
|
|
2047
|
+
this._ownedElements.add(element);
|
|
2048
|
+
});
|
|
2049
|
+
// Changing the type swaps in a new element, and carries the id over as a property update
|
|
2050
|
+
eventBus.on('commandStack.shape.replace.preExecute', ({ context }) => {
|
|
2051
|
+
this._replacedElementWasOwned = this._ownedElements.has(context?.oldShape);
|
|
2052
|
+
});
|
|
2053
|
+
eventBus.on('commandStack.shape.replace.postExecuted', ({ context }) => {
|
|
2054
|
+
const { oldShape, newShape } = context ?? {};
|
|
2055
|
+
this._ownedElements.delete(oldShape);
|
|
2056
|
+
if (this._replacedElementWasOwned && this.isEligible(newShape)) {
|
|
2057
|
+
this._ownedElements.add(newShape);
|
|
2058
|
+
}
|
|
2059
|
+
else {
|
|
2060
|
+
this._ownedElements.delete(newShape);
|
|
2061
|
+
}
|
|
2062
|
+
this._replacedElementWasOwned = false;
|
|
2063
|
+
});
|
|
2064
|
+
// Read the previous id before the update is applied
|
|
2065
|
+
eventBus.on('commandStack.element.updateProperties.preExecute', ({ context }) => {
|
|
2066
|
+
if (this._applyingId)
|
|
2067
|
+
return;
|
|
2068
|
+
const newId = context?.properties?.id;
|
|
2069
|
+
if (newId && newId !== context.element?.id)
|
|
2070
|
+
this._ownedElements.delete(context.element);
|
|
2071
|
+
});
|
|
2072
|
+
eventBus.on('commandStack.element.updateLabel.postExecuted', ({ context }) => this.syncIdWithName(context));
|
|
2073
|
+
eventBus.on('commandStack.element.updateProperties.postExecuted', ({ context }) => {
|
|
2074
|
+
// Editing an unrelated property must never move the id
|
|
2075
|
+
if (context?.properties?.name === undefined)
|
|
2076
|
+
return;
|
|
2077
|
+
this.syncIdWithName(context);
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
// A new process starts from a template, so treat what it brings as drawn here
|
|
2081
|
+
adoptAll() {
|
|
2082
|
+
this.elementRegistry.forEach((element) => {
|
|
2083
|
+
if (this.isEligible(element))
|
|
2084
|
+
this._ownedElements.add(element);
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
syncIdWithName(context) {
|
|
2088
|
+
if (this._applyingId)
|
|
2089
|
+
return;
|
|
2090
|
+
const element = context?.element?.labelTarget ?? context?.element;
|
|
2091
|
+
if (!element || !this._ownedElements.has(element))
|
|
2092
|
+
return;
|
|
2093
|
+
const name = getBusinessObject(element)?.name;
|
|
2094
|
+
if (!name)
|
|
2095
|
+
return;
|
|
2096
|
+
const baseId = toKebabCase(name, MAX_ACTIVITY_ID_LENGTH);
|
|
2097
|
+
if (!baseId || baseId === element.id)
|
|
2098
|
+
return;
|
|
2099
|
+
const uniqueId = this.resolveUniqueId(baseId, element);
|
|
2100
|
+
if (!uniqueId || uniqueId === element.id)
|
|
2101
|
+
return;
|
|
2102
|
+
this._applyingId = true;
|
|
2103
|
+
try {
|
|
2104
|
+
this.modeling.updateProperties(element, { id: uniqueId });
|
|
2105
|
+
}
|
|
2106
|
+
finally {
|
|
2107
|
+
this._applyingId = false;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
isEligible(element) {
|
|
2111
|
+
return !!element && !element.labelTarget && is(element, 'bpmn:FlowNode');
|
|
2112
|
+
}
|
|
2113
|
+
isTaken(id, element) {
|
|
2114
|
+
const existingElement = this.elementRegistry.get(id);
|
|
2115
|
+
if (existingElement && existingElement !== element)
|
|
2116
|
+
return true;
|
|
2117
|
+
// Covers moddle elements that are not on the canvas, e.g. the process itself
|
|
2118
|
+
const businessObject = getBusinessObject(element);
|
|
2119
|
+
const assigned = businessObject?.$model?.ids?.assigned(id);
|
|
2120
|
+
return !!assigned && assigned !== businessObject;
|
|
2121
|
+
}
|
|
2122
|
+
resolveUniqueId(baseId, element) {
|
|
2123
|
+
if (!this.isTaken(baseId, element))
|
|
2124
|
+
return baseId;
|
|
2125
|
+
for (let counter = 2; counter <= MAX_UNIQUE_SUFFIX; counter++) {
|
|
2126
|
+
const suffix = `-${counter}`;
|
|
2127
|
+
const trimmedBase = baseId
|
|
2128
|
+
.slice(0, MAX_ACTIVITY_ID_LENGTH - suffix.length)
|
|
2129
|
+
.replace(TRAILING_SEPARATOR_REGEX, '');
|
|
2130
|
+
const candidate = `${trimmedBase}${suffix}`;
|
|
2131
|
+
if (!this.isTaken(candidate, element))
|
|
2132
|
+
return candidate;
|
|
2133
|
+
}
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
const AutoIdBehaviorModule = {
|
|
2138
|
+
__init__: ['autoIdBehavior'],
|
|
2139
|
+
autoIdBehavior: ['type', AutoIdBehavior],
|
|
2140
|
+
};
|
|
2141
|
+
|
|
2142
|
+
/*
|
|
2143
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
2144
|
+
*
|
|
2145
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
2146
|
+
* you may not use this file except in compliance with the License.
|
|
2147
|
+
* You may obtain a copy of the License at
|
|
2148
|
+
*
|
|
2149
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
2150
|
+
*
|
|
2151
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2152
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
2153
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2154
|
+
* See the License for the specific language governing permissions and
|
|
2155
|
+
* limitations under the License.
|
|
2156
|
+
*/
|
|
2157
|
+
|
|
1950
2158
|
/*
|
|
1951
2159
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
1952
2160
|
*
|
|
@@ -2863,6 +3071,7 @@ class ProcessManagementBuilderComponent {
|
|
|
2863
3071
|
camundaPlatformBehaviors,
|
|
2864
3072
|
ValtimoPropertiesProviderModule,
|
|
2865
3073
|
ExpressionAutocompleteModule,
|
|
3074
|
+
AutoIdBehaviorModule,
|
|
2866
3075
|
],
|
|
2867
3076
|
moddleExtensions: { camunda: CamundaBpmnModdle },
|
|
2868
3077
|
propertiesPanel: { parent: this.modelerPanelElementRef.nativeElement },
|
|
@@ -3021,7 +3230,9 @@ class ProcessManagementBuilderComponent {
|
|
|
3021
3230
|
if (this._selectedProcess$.getValue() !== 'create')
|
|
3022
3231
|
return;
|
|
3023
3232
|
this.creatingNewProcess$.next(true);
|
|
3024
|
-
this._bpmnModeler
|
|
3233
|
+
this._bpmnModeler
|
|
3234
|
+
?.importXML(EMPTY_BPMN)
|
|
3235
|
+
.then(() => this._bpmnModeler?.get('autoIdBehavior')?.adoptAll());
|
|
3025
3236
|
this.isReadOnlyProcess$.next(false);
|
|
3026
3237
|
this.isSystemProcess$.next(false);
|
|
3027
3238
|
this.loading$.next(false);
|