Skip to content

Commit 83f9ea8

Browse files
committed
fix: labels in MultiLanguage OmniScript are not added properly to the OmniScript definition
1 parent 595bba7 commit 83f9ea8

File tree

8 files changed

+197
-60
lines changed

8 files changed

+197
-60
lines changed

packages/omniscript/src/omniScriptDefinitionBuilder.ts

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ export class OmniScriptDefinitionBuilder implements Iterable<OmniScriptElementDe
3434
constructor(private readonly scriptDef: OmniScriptDefinition) {
3535
}
3636

37+
/**
38+
* Determines whether the script definition supports multiple languages.
39+
* Returns `true` if the `bpLang` property matches "MultiLanguage" or "Multi-Language" (case-insensitive).
40+
*/
41+
public get isMultiLanguage() {
42+
return /^Multi-?Language$/i.test(this.scriptDef.bpLang);
43+
}
44+
3745
/**
3846
* True if picklists are to be loaded at script startup; otherwise false and the generator is expected to load the picklists at design time.
3947
* In which case picklists labels are in the same language as the activating user
@@ -57,26 +65,37 @@ export class OmniScriptDefinitionBuilder implements Iterable<OmniScriptElementDe
5765
this.embeddedTemplates.set(template.name, template);
5866
}
5967

68+
public addRuntimePicklistSource(
69+
optionSource: { type: 'SObject' | 'Custom' | 'Manual' | 'Image', source: string },
70+
controllingField?: { type?: 'SObject' | 'Custom' | 'None', source: string, element: string } | undefined
71+
) {
72+
this.setPicklistValues(optionSource, controllingField, '');
73+
}
74+
6075
/**
61-
* Add a custom picklist controller to the script definition. This controller is invoked at the start of the script to load the picklist values.
62-
* @param controller Name of the controller to add in the following format: `<controller>.<method>`
76+
* Sets the design time picklist values for the specified option source.
77+
* This is used to set the picklist values for the script definition at design time.
78+
* @param optionSource
79+
* @param values
80+
* @returns
6381
*/
64-
public addRuntimePicklistSource(
82+
public setPicklistValues(
6583
optionSource: { type: 'SObject' | 'Custom' | 'Manual' | 'Image', source: string },
66-
controllingField?: { type?: 'SObject' | 'Custom' | 'None', source: string, element: string})
67-
{
84+
controllingField?: { type?: 'SObject' | 'Custom' | 'None', source: string, element: string } | undefined,
85+
values?: { value: string, name: string }[] | ''
86+
) {
6887
if (!optionSource.source) {
6988
return;
7089
}
7190

7291
if (controllingField?.type === 'SObject' && controllingField.source) {
73-
this.scriptDef.depSOPL[`${optionSource.source}/${controllingField.source}`] = '';
92+
this.scriptDef.depSOPL[`${optionSource.source}/${controllingField.source}`] = values ?? '';
7493
} else if (controllingField?.type === 'Custom' && controllingField.source) {
75-
this.scriptDef.depCusPL[`${controllingField?.element}/${controllingField.source}`] = '';
94+
this.scriptDef.depCusPL[`${controllingField.element}/${controllingField.source}`] = values ?? '';
7695
} else if (optionSource.type === 'Custom') {
77-
this.scriptDef.cusPL[optionSource.source] = '';
96+
this.scriptDef.cusPL[optionSource.source] = values ?? '';
7897
} else if (optionSource.type === 'SObject') {
79-
this.scriptDef.sobjPL[optionSource.source] = '';
98+
this.scriptDef.sobjPL[optionSource.source] = values ?? '';
8099
}
81100
}
82101

@@ -102,6 +121,10 @@ export class OmniScriptDefinitionBuilder implements Iterable<OmniScriptElementDe
102121
public addElement(id: string, element: OmniScriptElementDefinition, options?: AddElementOptions) {
103122
// Validate if the element can be added
104123
this.validateElement(element, options);
124+
125+
// Add labels to the script definition
126+
this.addElementLabels(element);
127+
105128
this.elements.set(id, element);
106129

107130
if (element.propSetMap?.HTMLTemplateId) {
@@ -136,10 +159,6 @@ export class OmniScriptDefinitionBuilder implements Iterable<OmniScriptElementDe
136159
element.offSet = 0;
137160
}
138161

139-
if (element.propSetMap?.label) {
140-
this.scriptDef.labelKeyMap[element.propSetMap.label] = element.propSetMap.label;
141-
}
142-
143162
if (options?.parentElementId) {
144163
try {
145164
this.group(options.parentElementId).addElement(element);
@@ -163,6 +182,39 @@ export class OmniScriptDefinitionBuilder implements Iterable<OmniScriptElementDe
163182
this.addElementHandlers[element.type]?.(id, element, options);
164183
}
165184

185+
private addElementLabels(element: OmniScriptElementDefinition) {
186+
if (element.propSetMap) {
187+
for (const [key, value] of Object.entries(element.propSetMap)) {
188+
if (!value || typeof value !== 'string') {
189+
// Skip null and non-string values
190+
continue;
191+
}
192+
if (/(label|message)$/i.test(key)) {
193+
// All label and message keys are registered in the label key map
194+
this.addLabel(value);
195+
}
196+
}
197+
}
198+
199+
if (element.type === 'Step' && element.propSetMap.instruction) {
200+
this.addLabel(element.propSetMap.instruction);
201+
}
202+
203+
if (element.type === 'Validation' && element.propSetMap.messages) {
204+
for (const msg of Object.values(element.propSetMap.messages)) {
205+
this.addLabel(msg.text);
206+
}
207+
}
208+
}
209+
210+
private addLabel(labelName: string | undefined) {
211+
if (!labelName || labelName.includes(' ')) {
212+
// If a label key includes a space, it is not a valid label key and should not be added
213+
return;
214+
}
215+
this.scriptDef.labelKeyMap[labelName] = labelName;
216+
}
217+
166218
/**
167219
* Validates if the specified element can be added to the script definition.
168220
* Throws an error when the element is invalid or not supported.

packages/omniscript/src/omniScriptDefinitionGenerator.ts

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NamespaceService, SalesforceLabels, SalesforceSchemaService } from '@vl
33
import { groupBy, sortBy } from '@vlocode/util';
44
import { VlocityDatapack, VlocityInterfaceInvoker } from '@vlocode/vlocity';
55

6-
import { OmniScriptDefinition, OmniScriptSpecification, isChoiceScriptElement, OmniScriptChoiceElementDefinition } from './types';
6+
import { OmniScriptDefinition, OmniScriptSpecification, isChoiceScriptElement, OmniScriptChoiceElementDefinition, OmniScriptPickListOption } from './types';
77
import { OmniScriptDefinitionFactory } from './omniScriptDefinitionFactory';
88
import { OmniScriptDefinitionBuilder } from './omniScriptDefinitionBuilder';
99
import { OmniScriptDefinitionProvider } from './omniScriptDefinitionProvider';
@@ -61,9 +61,16 @@ export class OmniScriptDefinitionGenerator implements OmniScriptDefinitionProvid
6161
}
6262
}
6363

64-
// Add Labels and translate them to the current user language
64+
// Resolve labels
6565
const labels = await this.labels.getCustomLabels(Object.keys(scriptDef.labelKeyMap));
66-
scriptDef.labelKeyMap = Object.fromEntries(Object.values(labels).map(label => ([label.name, label.value])));
66+
67+
if (scriptBuilder.isMultiLanguage) {
68+
// For multi-language scripts, do not resolve labels here, but rather let the script builder handle it
69+
scriptDef.labelKeyMap = Object.fromEntries(Object.values(labels).map(label => [label.name, '']));
70+
} else {
71+
// For none multi-language scripts, resolve labels immediately to the user's active language
72+
scriptDef.labelKeyMap = Object.fromEntries(Object.values(labels).map(label => ([label.name, label.value])));
73+
}
6774

6875
return scriptBuilder.build();
6976
}
@@ -102,13 +109,7 @@ export class OmniScriptDefinitionGenerator implements OmniScriptDefinitionProvid
102109
}
103110

104111
if (isChoiceScriptElement(definition)) {
105-
if (builder.realtimePicklistSeed) {
106-
// Add ru time picklists to definition so they can be resolved at runtime
107-
builder.addRuntimePicklistSource(definition.propSetMap.optionSource, definition.propSetMap.controllingField);
108-
} else {
109-
// Loading of picklist options can fail
110-
await this.loadOptions(builder, definition);
111-
}
112+
await this.setPicklistValues(builder, definition);
112113
}
113114

114115
try {
@@ -138,37 +139,60 @@ export class OmniScriptDefinitionGenerator implements OmniScriptDefinitionProvid
138139
}
139140
}
140141

141-
private async loadOptions(builder: OmniScriptDefinitionBuilder, element: OmniScriptChoiceElementDefinition) {
142+
private async setPicklistValues(builder: OmniScriptDefinitionBuilder, element: OmniScriptChoiceElementDefinition) {
142143
if (!element.propSetMap.optionSource.source) {
143144
return;
144145
}
145146

147+
if (builder.realtimePicklistSeed) {
148+
// Add runtime picklists to definition so they can be resolved at runtime
149+
builder.setPicklistValues(element.propSetMap.optionSource, element.propSetMap.controllingField, '');
150+
return;
151+
}
152+
153+
const picklistValues = await this.loadPicklistValues(builder, element);
154+
if (!picklistValues) {
155+
builder.setPicklistValues(element.propSetMap.optionSource, element.propSetMap.controllingField, picklistValues);
156+
return;
157+
}
158+
159+
if (element.propSetMap.controllingField.type && element.propSetMap.controllingField.element) {
160+
element.propSetMap.dependency = picklistValues;
161+
} else {
162+
element.propSetMap.options = picklistValues;
163+
}
164+
165+
// Stor as Global Picklist otherwise it will not render in standard runtime
166+
builder.setPicklistValues(element.propSetMap.optionSource, element.propSetMap.controllingField, picklistValues);
167+
}
168+
169+
private async loadPicklistValues(builder: OmniScriptDefinitionBuilder, element: OmniScriptChoiceElementDefinition) {
146170
if (element.propSetMap.optionSource.type === 'SObject') {
147171
this.logger.debug(`Loading picklist options for SObject field: ${element.propSetMap.optionSource.source}`);
148172
if (element.propSetMap.controllingField.type === 'SObject' && element.propSetMap.controllingField.element) {
149-
element.propSetMap.dependency = await this.getDependentPicklistOptions(element.propSetMap.optionSource.source);
150-
} else {
151-
element.propSetMap.options = await this.getPicklistOptions(element.propSetMap.optionSource.source);
152-
}
173+
return this.getDependentPicklistOptions(element.propSetMap.optionSource.source);
174+
}
175+
return this.getPicklistOptions(element.propSetMap.optionSource.source);
153176
}
154177

155178
if (element.propSetMap.optionSource.type === 'Custom') {
156-
if (element.propSetMap.controllingField.type === 'Custom' && element.propSetMap.controllingField.element) {
157-
// Let Vlocity handle custom dependent picklist sources at runtime
158-
builder.addRuntimePicklistSource(element.propSetMap.optionSource, element.propSetMap.controllingField);
159-
} else if (element.propSetMap.optionSource.source?.includes('.')) {
179+
if (element.propSetMap.controllingField.type === 'Custom' &&
180+
element.propSetMap.controllingField.element
181+
) {
182+
return undefined;
183+
}
184+
185+
if (element.propSetMap.optionSource.source?.includes('.')) {
160186
this.logger.debug(`Loading picklist options from Custom source: ${element.propSetMap.optionSource.source}`);
161187
try {
162188
const response = await this.genericInvoker.invoke(element.propSetMap.optionSource.source);
163-
if (!Array.isArray(response.options)) {
164-
element.propSetMap.options = [ { value: `Error ${element.propSetMap.optionSource.source} returned no options`, name: 'ERR' } ];
165-
} else {
166-
element.propSetMap.options = response.options;
167-
}
189+
if (!Array.isArray(response?.options)) {
190+
return [ { value: `Error ${element.propSetMap.optionSource.source} returned no options`, name: 'ERR' } ];
191+
}
192+
return response.options;
168193
} catch(err) {
169194
// When loading of picklist elements fails instead delegate loading to be done at runtime
170195
// to avoid blocking the script builder from generating the rest of the script definition
171-
builder.addRuntimePicklistSource(element.propSetMap.optionSource);
172196
this.logger.warn(`Unable to load custom picklist options for script element "${element.name}":`, err);
173197
}
174198
}
@@ -185,12 +209,12 @@ export class OmniScriptDefinitionGenerator implements OmniScriptDefinitionProvid
185209
return true;
186210
}
187211

188-
private async getPicklistOptions(picklist: string) {
212+
private async getPicklistOptions(picklist: string) : Promise<OmniScriptPickListOption[]> {
189213
const entries = await this.getActivePicklistEntries(picklist);
190214
return entries.map(entry => ({ value: entry.label ?? entry.value, name: entry.value }));
191215
}
192216

193-
private async getDependentPicklistOptions(picklist: string) {
217+
private async getDependentPicklistOptions(picklist: string) : Promise<Record<string, OmniScriptPickListOption[]>> {
194218
const entries = await this.getActivePicklistEntries(picklist);
195219
return groupBy(entries, 'validFor', entry => ({ value: entry.label ?? entry.value, name: entry.value }));
196220
}

packages/omniscript/src/types/omniScriptDefinition.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ export interface OmniScriptProperties {
8686
}
8787
}
8888

89+
export interface OmniScriptPickListOption {
90+
name: string;
91+
value: string;
92+
}
93+
8994
export interface OmniScriptDefinition {
9095
userTimeZone: null,
9196
userProfile: string,
@@ -109,7 +114,8 @@ export interface OmniScriptDefinition {
109114
errorMsg: string;
110115
error: string;
111116
dMap: object;
112-
sobjPL: object;
117+
/** Picklist values by SObject type, key is the SObject.Field, value is an array of possible picklist values */
118+
sobjPL: Record<string, OmniScriptPickListOption[] | ''>;
113119
depSOPL: object;
114120
/**
115121
* Record keyed by the name of the custom APEX method that loads the picklist values. Custom APEX classes listed in this array will be invoked at the script
@@ -121,7 +127,7 @@ export interface OmniScriptDefinition {
121127
* }
122128
* ```
123129
*/
124-
cusPL: Record<string, ''>;
130+
cusPL: Record<string, OmniScriptPickListOption[] | ''>;
125131
/**
126132
* Record keyed by the name of the custom APEX method that loads the picklist values. Custom APEX classes listed in this array will be invoked at the script
127133
* start to load picklist values instead of caching the values in the script definition at design time.
@@ -132,7 +138,7 @@ export interface OmniScriptDefinition {
132138
* }
133139
* ```
134140
*/
135-
depCusPL: Record<string, ''>;
141+
depCusPL: Record<string, OmniScriptPickListOption[] | ''>;
136142
children: Array<OmniScriptElementDefinition>;
137143
bReusable: boolean;
138144
bpVersion: number;

packages/omniscript/src/types/omniScriptElementDefinition.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { OmniScriptBaseElementType, OmniScriptElementType, OmniScriptGroupElementType, OmniScriptInputChoiceElementType, OmniScriptInputElementType } from "./omniScriptElementType";
2-
import { OmniScriptBaseElementPropertySet } from "./omniScriptElementPropertySet";
1+
import { OmniScriptBaseElementType, OmniScriptElementType, OmniScriptGroupElementType, OmniScriptInputChoiceElementType, OmniScriptInputElementType, OmniScriptStepElementType, OmniScriptValidationElementType } from "./omniScriptElementType";
2+
import { OmniScriptBaseElementPropertySet, OmniScriptStepElementPropertySet, OmniScriptValidationElementPropertySet } from "./omniScriptElementPropertySet";
33

44
export interface OmniScriptBaseElementDefinition<T extends OmniScriptElementType = OmniScriptBaseElementType> {
55
type: T;
@@ -24,7 +24,7 @@ export interface OmniScriptBaseElementDefinition<T extends OmniScriptElementType
2424
}>
2525
}
2626

27-
export interface OmniScriptGroupElementDefinition extends OmniScriptBaseElementDefinition<OmniScriptGroupElementType> {
27+
export interface OmniScriptGroupElementDefinition<T extends OmniScriptElementType = OmniScriptGroupElementType> extends OmniScriptBaseElementDefinition<T> {
2828
children: Array<{
2929
bHasAttachment: boolean,
3030
eleArray: Array<OmniScriptElementDefinition>;
@@ -37,6 +37,14 @@ export interface OmniScriptGroupElementDefinition extends OmniScriptBaseElementD
3737
bAccordionActive: boolean,
3838
}
3939

40+
export interface OmniScriptStepElementDefinition extends OmniScriptGroupElementDefinition<OmniScriptStepElementType> {
41+
propSetMap: OmniScriptStepElementPropertySet;
42+
}
43+
44+
export interface OmniScriptValidationElementDefinition extends OmniScriptBaseElementDefinition<OmniScriptValidationElementType> {
45+
propSetMap: OmniScriptValidationElementPropertySet;
46+
}
47+
4048
export interface OmniScriptEmbeddedScriptPropertySet extends OmniScriptBaseElementPropertySet {
4149
Type: string;
4250
'Sub Type': string,
@@ -84,10 +92,12 @@ export interface OmniScriptChoiceElementDefinition extends OmniScriptInputElemen
8492
{ }
8593

8694
export type OmniScriptElementDefinition =
95+
OmniScriptStepElementDefinition |
8796
OmniScriptGroupElementDefinition |
8897
OmniScriptEmbeddedScriptElementDefinition |
8998
OmniScriptInputElementDefinition |
9099
OmniScriptChoiceElementDefinition |
100+
OmniScriptValidationElementDefinition |
91101
OmniScriptBaseElementDefinition
92102

93103
export function isChoiceScriptElement(def: OmniScriptElementDefinition): def is OmniScriptChoiceElementDefinition {

packages/omniscript/src/types/omniScriptElementPropertySet.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,48 @@ export interface OmniScriptBaseElementPropertySet extends Record<string, any> {
1515
HTMLTemplateId?: string;
1616
rpe?: boolean;
1717
bus?: boolean;
18-
}
18+
controlWidth?: number;
19+
}
20+
21+
export interface OmniScriptStepElementPropertySet extends OmniScriptBaseElementPropertySet {
22+
allowSaveForLater?: boolean;
23+
businessCategory?: string;
24+
businessEvent?: string;
25+
cancelLabel?: string;
26+
cancelMessage?: string | null;
27+
chartLabel?: string | null;
28+
completeLabel?: string | null;
29+
completeMessage?: string | null;
30+
conditionType?: 'Hide if False' | 'Show if True';
31+
errorMessage?: Record<string, any>;
32+
instruction?: string;
33+
instructionKey?: string | null;
34+
knowledgeOptions?: Record<string, any>;
35+
label: string;
36+
message?: Record<string, any>;
37+
nextLabel: string;
38+
nextWidth?: string | number;
39+
previousLabel: string;
40+
previousWidth?: number;
41+
pubsub?: boolean;
42+
remoteClass?: string;
43+
remoteMethod?: string;
44+
remoteOptions?: Record<string, any>;
45+
remoteTimeout?: number;
46+
saveLabel: string;
47+
saveMessage: string;
48+
showPersistentComponent?: Array<boolean>;
49+
}
50+
51+
52+
export interface OmniScriptValidationElementPropertySet extends OmniScriptBaseElementPropertySet {
53+
hideLabel: boolean;
54+
disOnTplt: boolean;
55+
messages?: Array<{
56+
active: boolean;
57+
text?: string;
58+
type: 'Error' | 'Warning' | 'Requirement';
59+
value: boolean;
60+
}>;
61+
validateExpression?: string;
62+
}

0 commit comments

Comments
 (0)