Skip to content

Commit ee58db9

Browse files
ENHANCEMENT-473: add SemanticLink component, fix DataReference refresh logic
- Add SemanticLink component support - Fix DataReference refresh: use hasAssociatedViewConfigured check (matching web implementation) instead of blanket-skipping picklist types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 187e696 commit ee58db9

14 files changed

Lines changed: 1369 additions & 8 deletions

File tree

core/src/commonMain/kotlin/com/pega/constellation/sdk/kmp/core/components/ComponentRegistry.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.Phone
2828
import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.RadioButtons
2929
import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.Region
3030
import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.RichText
31+
import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.SemanticLink
3132
import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.RootContainer
3233
import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.SimpleComboBox
3334
import com.pega.constellation.sdk.kmp.core.components.ComponentTypes.SimpleTable
@@ -71,6 +72,7 @@ import com.pega.constellation.sdk.kmp.core.components.fields.PhoneComponent
7172
import com.pega.constellation.sdk.kmp.core.components.fields.RadioButtonsComponent
7273
import com.pega.constellation.sdk.kmp.core.components.fields.RichTextComponent
7374
import com.pega.constellation.sdk.kmp.core.components.fields.SimpleComboBoxComponent
75+
import com.pega.constellation.sdk.kmp.core.components.fields.SemanticLinkComponent
7476
import com.pega.constellation.sdk.kmp.core.components.fields.TextAreaComponent
7577
import com.pega.constellation.sdk.kmp.core.components.fields.TextInputComponent
7678
import com.pega.constellation.sdk.kmp.core.components.fields.TimeComponent
@@ -118,6 +120,7 @@ object ComponentRegistry {
118120
Def(RadioButtons) { RadioButtonsComponent(it) },
119121
Def(Region) { RegionComponent(it) },
120122
Def(RootContainer) { RootContainerComponent(it) },
123+
Def(SemanticLink) { SemanticLinkComponent(it) },
121124
Def(SimpleTable) { SimpleTableComponent(it) },
122125
Def(SimpleTableManual) { SimpleTableManualComponent(it) },
123126
Def(SimpleTableSelect) { SimpleTableSelectComponent(it) },

core/src/commonMain/kotlin/com/pega/constellation/sdk/kmp/core/components/ComponentTypes.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ object ComponentTypes {
4242
val RadioButtons = ComponentType("RadioButtons")
4343
val RichText = ComponentType("RichText")
4444
val SimpleComboBox = ComponentType("SimpleComboBox")
45+
val SemanticLink = ComponentType("SemanticLink")
4546
val TextArea = ComponentType("TextArea")
4647
val TextInput = ComponentType("TextInput")
4748
val Time = ComponentType("Time")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.pega.constellation.sdk.kmp.core.components.fields
2+
3+
import androidx.compose.runtime.getValue
4+
import androidx.compose.runtime.mutableStateOf
5+
import androidx.compose.runtime.setValue
6+
import com.pega.constellation.sdk.kmp.core.api.BaseComponent
7+
import com.pega.constellation.sdk.kmp.core.api.ComponentContext
8+
import com.pega.constellation.sdk.kmp.core.api.HideableComponent
9+
import com.pega.constellation.sdk.kmp.core.components.getString
10+
import com.pega.constellation.sdk.kmp.core.components.optBoolean
11+
import kotlinx.serialization.json.JsonObject
12+
13+
class SemanticLinkComponent(context: ComponentContext) : BaseComponent(context), HideableComponent {
14+
var value: String by mutableStateOf("")
15+
private set
16+
var label: String by mutableStateOf("")
17+
private set
18+
override var visible: Boolean by mutableStateOf(false)
19+
private set
20+
21+
override fun applyProps(props: JsonObject) {
22+
with(props) {
23+
value = getString("value")
24+
label = getString("label")
25+
visible = optBoolean("visible", true)
26+
}
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.cases
2+
3+
import androidx.compose.ui.test.ExperimentalTestApi
4+
import androidx.compose.ui.test.onNodeWithText
5+
import androidx.compose.ui.test.performClick
6+
import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.ComposeTest
7+
import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.runAndroidTest
8+
import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.waitForNode
9+
import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.waitForNodes
10+
import com.pega.constellation.sdk.kmp.test.mock.PegaVersion
11+
import kotlin.test.Test
12+
13+
@OptIn(ExperimentalTestApi::class)
14+
class DataRefSemanticLinkTest : ComposeTest(PegaVersion.v25_1) {
15+
16+
@Test
17+
fun test_data_reference_form_and_semantic_link() = runAndroidTest {
18+
setupApp("OI1OYV-Marco2-Work-DataReferenceTest-FieldOnly")
19+
20+
// create case
21+
onNodeWithText("New Service").performClick()
22+
23+
// Step 1: verify DataReference form with Dropdown appears
24+
waitForNode("DataReferenceSingleCars")
25+
26+
// Select "Focus" from the dropdown
27+
onNodeWithText("DataReferenceSingleCars").performClick()
28+
waitForNode("Focus")
29+
onNodeWithText("Focus").performClick()
30+
31+
// Verify refresh populated the subview with Brand and Model
32+
waitForNodes("Focus", 2)
33+
waitForNode("Ford")
34+
35+
// Submit step 1 → step 2 loads with SemanticLink
36+
onNodeWithText("Next").performClick()
37+
38+
// Step 2: verify SemanticLink renders the selected car model
39+
waitForNode("Focus")
40+
}
41+
}

scripts/dxcomponents/components/containers/templates/data-reference.component.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -279,26 +279,38 @@ export class DataReferenceComponent extends ContainerBaseComponent {
279279
#handleSelection(event) {
280280
const caseKey = this.pConn.getCaseInfo().getKey();
281281
const refreshOptions = { autoDetectRefresh: true };
282+
// AutoComplete sets value on event.id whereas Dropdown sets it on event.target.value
283+
const selectionValue = event?.id || event?.target?.value;
282284

283285
const children = this.pConn.getRawMetadata()?.children;
284286
if (children?.length > 0 && children[0].config?.value) {
285287
refreshOptions.propertyName = children[0].config.value;
286288
refreshOptions.classID = this.pConn.getRawMetadata().classID;
287289
}
288290

291+
// Skip manual refreshCaseView for picklist-based children (Dropdown, AutoComplete, Checkbox)
292+
// as they don't have an associated view configured and trigger refresh automatically
293+
const hasAssociatedViewConfigured = this.rawViewMetadata.children?.[1]?.children?.length;
294+
289295
if (this.canBeChangedInReviewMode && this.pConn.getValue("__currentPageTabViewName")) {
290296
this.pConn
291297
.getActionsApi()
292-
.refreshCaseView(caseKey, this.pConn.getValue("__currentPageTabViewName"), "", refreshOptions);
298+
.refreshCaseView(caseKey, this.pConn.getValue("__currentPageTabViewName"), "", refreshOptions)
299+
?.catch((error) => {
300+
console.warn(`${TAG} refreshCaseView failed (review mode)`, error?.name, error?.message);
301+
});
293302
PCore.getDeferLoadManager().refreshActiveComponents(this.pConn.getContextName());
294-
} else {
303+
} else if (hasAssociatedViewConfigured) {
295304
const pgRef = this.pConn.getPageReference().replace("caseInfo.content", "");
296-
this.pConn.getActionsApi().refreshCaseView(caseKey, this.viewName, pgRef, refreshOptions);
305+
this.pConn
306+
.getActionsApi()
307+
.refreshCaseView(caseKey, this.viewName, pgRef, refreshOptions)
308+
?.catch((error) => {
309+
console.warn(`${TAG} refreshCaseView failed`, error?.name, error?.message);
310+
});
297311
}
298312

299-
// AutoComplete sets value on event.id whereas Dropdown sets it on event.target.value
300-
const propValue = event?.id || event?.target?.value;
301-
if (propValue && this.canBeChangedInReviewMode && this.isDisplayModeEnabled) {
313+
if (selectionValue && this.canBeChangedInReviewMode && this.isDisplayModeEnabled) {
302314
PCore.getDataApiUtils()
303315
.getCaseEditLock(caseKey, "")
304316
.then((caseResponse) => {
@@ -317,7 +329,7 @@ export class DataReferenceComponent extends ContainerBaseComponent {
317329
const propArr = this.propName.split(".");
318330
propArr.forEach((element, idx) => {
319331
if (idx + 1 === propArr.length) {
320-
curr[element] = propValue;
332+
curr[element] = selectionValue;
321333
} else {
322334
curr[element] = {};
323335
curr = curr[element];
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { BaseComponent } from "../base.component.js";
2+
3+
export class SemanticLinkComponent extends BaseComponent {
4+
jsComponentPConnectData = {};
5+
6+
props = {
7+
value: "",
8+
label: "",
9+
visible: true,
10+
};
11+
12+
init() {
13+
this.jsComponentPConnectData = this.jsComponentPConnect.registerAndSubscribeComponent(
14+
this,
15+
this.checkAndUpdate
16+
);
17+
this.componentsManager.onComponentAdded(this);
18+
this.checkAndUpdate();
19+
}
20+
21+
destroy() {
22+
super.destroy();
23+
this.jsComponentPConnectData.unsubscribeFn?.();
24+
this.componentsManager.onComponentRemoved(this);
25+
}
26+
27+
update(pConn) {
28+
if (this.pConn !== pConn) {
29+
this.pConn = pConn;
30+
this.checkAndUpdate();
31+
}
32+
}
33+
34+
checkAndUpdate() {
35+
if (this.jsComponentPConnect.shouldComponentUpdate(this)) {
36+
this.#updateSelf();
37+
}
38+
}
39+
40+
#updateSelf() {
41+
const configProps = this.pConn.resolveConfigProps(this.pConn.getConfigProps());
42+
this.props.label = configProps.label ?? "";
43+
this.props.value = configProps.text ?? configProps.value ?? "";
44+
this.props.visible = this.utils.getBooleanValue(configProps.visibility ?? this.props.visible);
45+
this.componentsManager.onComponentPropsUpdate(this);
46+
}
47+
}

scripts/dxcomponents/mappings/sdk-pega-component-map.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { IntegerComponent } from "../components/fields/integer.component.js";
3434
// import { PercentageComponent } from './_components/field/percentage/percentage.component';
3535
import { PhoneComponent } from "../components/fields/phone.component.js";
3636
import { RadioButtonsComponent } from "../components/fields/radio-buttons.component.js";
37-
// import { SemanticLinkComponent } from './_components/field/semantic-link/semantic-link.component';
37+
import { SemanticLinkComponent } from "../components/fields/semantic-link.component.js";
3838
import { TextAreaComponent } from "../components/fields/text-area.component.js";
3939
// import { TextComponent } from './_components/field/text/text.component';
4040
// import { TextContentComponent } from './_components/field/text-content/text-content.component';
@@ -219,6 +219,7 @@ const pegaSdkComponentMap = {
219219
// ScalarList: ScalarListComponent,
220220
// SemanticLink: SemanticLinkComponent,
221221
SimpleComboBox: SimpleComboBoxComponent,
222+
SemanticLink: SemanticLinkComponent,
222223
SimpleTable: SimpleTableComponent,
223224
SimpleTableManual: SimpleTableManualComponent,
224225
SimpleTableSelect: SimpleTableSelectComponent,

0 commit comments

Comments
 (0)