Skip to content

Commit 31e0f9a

Browse files
authored
[WTF-2406] Generate types for single object data sources (#173)
## Checklist - Contains unit tests ✅ - Contains breaking changes ❌ - Did you update version and changelog? ✅ ❌ - PR title properly formatted (`[XX-000]: description`)? ✅ ❌ ## This PR contains - [X] Feature What is the purpose of this PR? It will be possible to define data sources with `isList` flag set to false. This PR updates the types accordingly. Relevant changes Type generation will updates types based on `isList` flag
2 parents d9fdeb8 + 41dc8fd commit 31e0f9a

6 files changed

Lines changed: 228 additions & 12 deletions

File tree

packages/pluggable-widgets-tools/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
99
### Added
1010

1111
- We added support for the `allowUpload` attribute on image properties in native widgets, generating `EditableImageValue<NativeImage>` when enabled, introduced in Mendix 11.11.
12+
- We added support for single object datasource properties, introduced in Mendix 11.11.
1213

1314
### Changed
1415

packages/pluggable-widgets-tools/src/typings-generator/__tests__/index.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import {listActionWithVariablesInput, listActionWithVariablesInputNative} from "
4747
import {listActionWithVariablesOutput, listActionWithVariablesOutputNative} from "./outputs/list-action-with-variables";
4848
import {imageWebInput, imageNativeInput} from "./inputs/image";
4949
import {imageWebOutput, imageNativeOutput} from "./outputs/image";
50+
import { singleObjectDatasourceInput, singleObjectDatasourceInputNative } from "./inputs/single-object-datasource";
51+
import { singleObjectDatasourceNativeOutput, singleObjectDatasourceWebOutput } from "./outputs/single-object-datasource";
5052

5153
describe("Generating tests", () => {
5254
it("Generates a parsed typing from XML for native", () => {
@@ -248,6 +250,16 @@ describe("Generating tests", () => {
248250
const newContent = generateNativeTypesFor(imageNativeInput);
249251
expect(newContent).toBe(imageNativeOutput);
250252
});
253+
254+
it("Generates a parsed typing from XML for web using single object datasource", () => {
255+
const newContent = generateFullTypesFor(singleObjectDatasourceInput);
256+
expect(newContent).toBe(singleObjectDatasourceWebOutput);
257+
});
258+
259+
it("Generates a parsed typing from XML for native using single object datasource", () => {
260+
const newContent = generateNativeTypesFor(singleObjectDatasourceInputNative);
261+
expect(newContent).toBe(singleObjectDatasourceNativeOutput);
262+
});
251263
});
252264

253265
function generateFullTypesFor(xml: string) {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
export const singleObjectDatasourceInput = `<?xml version="1.0" encoding="utf-8"?>
2+
<widget id="mendix.mywidget.MyWidget" needsEntityContext="true" offlineCapable="true" pluginWidget="true"
3+
xmlns="http://www.mendix.com/widget/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../xsd/widget.xsd">
5+
<properties>
6+
<propertyGroup caption="General">
7+
<property key="singleSource" type="datasource" isList="false">
8+
<caption>Single object data source</caption>
9+
<description />
10+
</property>
11+
<property key="optionalSingleSource" type="datasource" isList="false" required="false">
12+
<caption>Optional single object data source</caption>
13+
<description />
14+
</property>
15+
<property key="listSource" type="datasource" isList="true">
16+
<caption>List data source</caption>
17+
<description />
18+
</property>
19+
<property key="singleContent" type="widgets" dataSource="singleSource">
20+
<caption>Single Content</caption>
21+
<description />
22+
</property>
23+
<property key="singleAttribute" type="attribute" dataSource="singleSource">
24+
<caption>Single Attribute</caption>
25+
<description />
26+
<attributeTypes>
27+
<attributeType name="String"/>
28+
<attributeType name="Boolean"/>
29+
<attributeType name="Decimal"/>
30+
</attributeTypes>
31+
</property>
32+
<property key="singleAction" type="action" dataSource="singleSource">
33+
<caption>Single Action</caption>
34+
<description />
35+
</property>
36+
<property key="singleTextTemplate" type="textTemplate" dataSource="singleSource">
37+
<caption>Single Text Template</caption>
38+
<description />
39+
</property>
40+
<property key="singleExpression" type="expression" dataSource="singleSource">
41+
<caption>Single Expression</caption>
42+
<description />
43+
<returnType type="Decimal"/>
44+
</property>
45+
<property key="optionalSingleAttribute" type="attribute" dataSource="optionalSingleSource">
46+
<caption>Optional Single Attribute</caption>
47+
<description />
48+
<attributeTypes>
49+
<attributeType name="String"/>
50+
</attributeTypes>
51+
</property>
52+
<property key="optionalSingleAction" type="action" dataSource="optionalSingleSource">
53+
<caption>Optional Single Action</caption>
54+
<description />
55+
</property>
56+
<property key="listContent" type="widgets" dataSource="listSource">
57+
<caption>List Content</caption>
58+
<description />
59+
</property>
60+
<property key="listAttribute" type="attribute" dataSource="listSource">
61+
<caption>List Attribute</caption>
62+
<description />
63+
<attributeTypes>
64+
<attributeType name="String"/>
65+
</attributeTypes>
66+
</property>
67+
<property key="listAction" type="action" dataSource="listSource">
68+
<caption>List Action</caption>
69+
<description />
70+
</property>
71+
</propertyGroup>
72+
<propertyGroup caption="System Properties">
73+
<systemProperty key="Label"></systemProperty>
74+
<systemProperty key="TabIndex"></systemProperty>
75+
</propertyGroup>
76+
</properties>
77+
</widget>`;
78+
79+
export const singleObjectDatasourceInputNative = `<?xml version="1.0" encoding="utf-8"?>
80+
<widget id="mendix.mywidget.MyWidget" needsEntityContext="true" offlineCapable="true" pluginWidget="true" supportedPlatform="Native"
81+
xmlns="http://www.mendix.com/widget/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
82+
xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../xsd/widget.xsd">
83+
<properties>
84+
<propertyGroup caption="General">
85+
<property key="singleSource" type="datasource" isList="false">
86+
<caption>Single object data source</caption>
87+
<description />
88+
</property>
89+
<property key="listSource" type="datasource" isList="true">
90+
<caption>List data source</caption>
91+
<description />
92+
</property>
93+
<property key="singleContent" type="widgets" dataSource="singleSource">
94+
<caption>Single Content</caption>
95+
<description />
96+
</property>
97+
<property key="singleAttribute" type="attribute" dataSource="singleSource">
98+
<caption>Single Attribute</caption>
99+
<description />
100+
<attributeTypes>
101+
<attributeType name="String"/>
102+
<attributeType name="Boolean"/>
103+
<attributeType name="Decimal"/>
104+
</attributeTypes>
105+
</property>
106+
<property key="singleAction" type="action" dataSource="singleSource">
107+
<caption>Single Action</caption>
108+
<description />
109+
</property>
110+
<property key="singleTextTemplate" type="textTemplate" dataSource="singleSource">
111+
<caption>Single Text Template</caption>
112+
<description />
113+
</property>
114+
<property key="singleExpression" type="expression" dataSource="singleSource">
115+
<caption>Single Expression</caption>
116+
<description />
117+
<returnType type="Decimal"/>
118+
</property>
119+
<property key="listContent" type="widgets" dataSource="listSource">
120+
<caption>List Content</caption>
121+
<description />
122+
</property>
123+
<property key="listAttribute" type="attribute" dataSource="listSource">
124+
<caption>List Attribute</caption>
125+
<description />
126+
<attributeTypes>
127+
<attributeType name="String"/>
128+
</attributeTypes>
129+
</property>
130+
<property key="listAction" type="action" dataSource="listSource">
131+
<caption>List Action</caption>
132+
<description />
133+
</property>
134+
</propertyGroup>
135+
</properties>
136+
</widget>`;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
export const singleObjectDatasourceWebOutput = `/**
2+
* This file was generated from MyWidget.xml
3+
* WARNING: All changes made to this file will be overwritten
4+
* @author Mendix Widgets Framework Team
5+
*/
6+
import { ActionValue, DynamicValue, EditableValue, ListActionValue, ListAttributeValue, ListValue, ListWidgetValue, ObjectItem } from "mendix";
7+
import { ComponentType, ReactNode } from "react";
8+
import { Big } from "big.js";
9+
10+
export interface MyWidgetContainerProps {
11+
name: string;
12+
tabIndex?: number;
13+
id: string;
14+
singleSource: DynamicValue<ObjectItem>;
15+
optionalSingleSource?: DynamicValue<ObjectItem>;
16+
listSource: ListValue;
17+
singleContent: ReactNode;
18+
singleAttribute: EditableValue<string | boolean | Big>;
19+
singleAction?: ActionValue;
20+
singleTextTemplate: DynamicValue<string>;
21+
singleExpression: DynamicValue<Big>;
22+
optionalSingleAttribute?: EditableValue<string>;
23+
optionalSingleAction?: ActionValue;
24+
listContent: ListWidgetValue;
25+
listAttribute: ListAttributeValue<string>;
26+
listAction?: ListActionValue;
27+
}
28+
29+
export interface MyWidgetPreviewProps {
30+
readOnly: boolean;
31+
renderMode: "design" | "xray" | "structure";
32+
translate: (text: string) => string;
33+
singleSource: {} | { caption: string } | { type: string } | null;
34+
optionalSingleSource: {} | { caption: string } | { type: string } | null;
35+
listSource: {} | { caption: string } | { type: string } | null;
36+
singleContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
37+
singleAttribute: string;
38+
singleAction: {} | null;
39+
singleTextTemplate: string;
40+
singleExpression: string;
41+
optionalSingleAttribute: string;
42+
optionalSingleAction: {} | null;
43+
listContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
44+
listAttribute: string;
45+
listAction: {} | null;
46+
}
47+
`;
48+
49+
export const singleObjectDatasourceNativeOutput = `export interface MyWidgetProps<Style> {
50+
name: string;
51+
style: Style[];
52+
singleSource: DynamicValue<ObjectItem>;
53+
listSource: ListValue;
54+
singleContent: ReactNode;
55+
singleAttribute: EditableValue<string | boolean | Big>;
56+
singleAction?: ActionValue;
57+
singleTextTemplate: DynamicValue<string>;
58+
singleExpression: DynamicValue<Big>;
59+
listContent: ListWidgetValue;
60+
listAttribute: ListAttributeValue<string>;
61+
listAction?: ListActionValue;
62+
}`;

packages/pluggable-widgets-tools/src/typings-generator/generate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const importableModules = [
2525
"ListWidgetValue",
2626
"NativeIcon",
2727
"NativeImage",
28+
"ObjectItem",
2829
"Option",
2930
"ReferenceSetValue",
3031
"ReferenceValue",

packages/pluggable-widgets-tools/src/typings-generator/generateClientTypes.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ export function hasOptionalDataSource(prop: Property, resolveProp: (key: string)
100100
return prop.$.dataSource && resolveProp(prop.$.dataSource)?.$.required === "false";
101101
}
102102

103+
function isLinkedToListDataSource(prop: Property, resolveProp: (key: string) => Property | undefined): boolean {
104+
return prop.$.dataSource != null && resolveProp(prop.$.dataSource)?.$.isList === "true";
105+
}
106+
103107
function toActionVariablesOutputType(actionVariables?: ActionVariableTypes[]) {
104108
const types = actionVariables?.flatMap(av => av.actionVariable)
105109
.map(avt => `${avt.$.key}: ${toOption(toAttributeClientType(avt.$.type))}`)
@@ -121,9 +125,9 @@ function toClientPropType(
121125
return "string";
122126
case "action":
123127
const variableTypes = toActionVariablesOutputType(prop.actionVariables);
124-
return (prop.$.dataSource ? "ListActionValue" : "ActionValue") + variableTypes;
128+
return (isLinkedToListDataSource(prop, resolveProp) ? "ListActionValue" : "ActionValue") + variableTypes;
125129
case "textTemplate":
126-
return prop.$.dataSource ? "ListExpressionValue<string>" : "DynamicValue<string>";
130+
return isLinkedToListDataSource(prop, resolveProp) ? "ListExpressionValue<string>" : "DynamicValue<string>";
127131
case "integer":
128132
return "number";
129133
case "decimal":
@@ -136,7 +140,7 @@ function toClientPropType(
136140
case "file":
137141
return prop.$.allowUpload ? "EditableFileValue" : "DynamicValue<FileValue>";
138142
case "datasource":
139-
return "ListValue";
143+
return prop.$.isList === "true" ? "ListValue" : "DynamicValue<ObjectItem>";
140144
case "attribute": {
141145
if (!prop.attributeTypes?.length) {
142146
throw new Error("[XML] Attribute property requires attributeTypes element");
@@ -145,22 +149,22 @@ function toClientPropType(
145149
.flatMap(ats => ats.attributeType)
146150
.map(at => toAttributeClientType(at.$.name));
147151
const unionType = toUniqueUnionType(types);
148-
const linkedToDataSource = !!prop.$.dataSource;
152+
const linkedToListDS = isLinkedToListDataSource(prop, resolveProp);
149153

150154
if (prop.$.isMetaData === "true") {
151-
if (!linkedToDataSource) {
155+
if (!prop.$.dataSource) {
152156
throw new Error(`[XML] Attribute property can only have isMetaData="true" when linked to a datasource`);
153157
}
154158
return `AttributeMetaData<${unionType}>`;
155159
}
156160

157161
if (!prop.associationTypes?.length) {
158-
return toAttributeOutputType("Reference", linkedToDataSource, unionType);
162+
return toAttributeOutputType("Reference", linkedToListDS, unionType);
159163
}
160164
else {
161165
const reftypes = prop.associationTypes
162166
.flatMap(ats => ats.associationType)
163-
.map(at => toAttributeOutputType(at.$.name, linkedToDataSource, unionType));
167+
.map(at => toAttributeOutputType(at.$.name, linkedToListDS, unionType));
164168
return toUniqueUnionType(reftypes);
165169
}
166170
}
@@ -169,25 +173,25 @@ function toClientPropType(
169173
throw new Error("[XML] Association property requires associationTypes element");
170174
}
171175

172-
const linkedToDataSource = !!prop.$.dataSource;
176+
const linkedToListDS = isLinkedToListDataSource(prop, resolveProp);
173177
if (prop.$.isMetaData === "true") {
174-
if (!linkedToDataSource) {
178+
if (!prop.$.dataSource) {
175179
throw new Error(`[XML] Association property can only have isMetaData="true" when linked to a datasource`);
176180
}
177181
return "AssociationMetaData";
178182
}
179183

180184
const types = prop.associationTypes
181185
.flatMap(ats => ats.associationType)
182-
.map(at => toAssociationOutputType(at.$.name, linkedToDataSource));
186+
.map(at => toAssociationOutputType(at.$.name, linkedToListDS));
183187
return toUniqueUnionType(types);
184188
}
185189
case "expression":
186190
if (!prop.returnType || prop.returnType.length === 0) {
187191
throw new Error("[XML] Expression property requires returnType element");
188192
}
189193
const type = toExpressionClientType(prop.returnType[0], resolveProp);
190-
return prop.$.dataSource ? `ListExpressionValue<${type}>` : `DynamicValue<${type}>`;
194+
return isLinkedToListDataSource(prop, resolveProp) ? `ListExpressionValue<${type}>` : `DynamicValue<${type}>`;
191195
case "enumeration":
192196
const typeName = capitalizeFirstLetter(prop.$.key) + "Enum";
193197
generatedTypes.push(generateEnum(typeName, prop));
@@ -209,7 +213,7 @@ ${generateClientTypeBody(childProperties, isNative, generatedTypes, resolveChild
209213
);
210214
return prop.$.isList === "true" ? `${childType}[]` : childType;
211215
case "widgets":
212-
return prop.$.dataSource ? "ListWidgetValue" : "ReactNode";
216+
return isLinkedToListDataSource(prop, resolveProp) ? "ListWidgetValue" : "ReactNode";
213217
case "selection":
214218
if (!prop.selectionTypes?.length) {
215219
throw new Error("[XML] Selection property requires selectionTypes element");

0 commit comments

Comments
 (0)