mxrb 0.1.3 → 0.1.5
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.
- checksums.yaml +4 -4
- data/bin/mxrb +455 -10
- data/docs/de-DE/README.md +1 -0
- data/docs/de-DE/oql-sql.md +42 -0
- data/docs/de-DE/project-structure.md +201 -2
- data/docs/de-DE/runtime-ruby.md +104 -0
- data/docs/de-DE/runtime-schema-migrations.md +44 -0
- data/docs/de-DE/validation-matrix.md +42 -4
- data/docs/en-US/README.md +1 -0
- data/docs/en-US/oql-sql.md +41 -0
- data/docs/en-US/project-structure.md +192 -2
- data/docs/en-US/runtime-ruby.md +100 -0
- data/docs/en-US/runtime-schema-migrations.md +41 -0
- data/docs/en-US/validation-matrix.md +39 -4
- data/docs/pt-BR/README.md +1 -0
- data/docs/pt-BR/oql-sql.md +41 -0
- data/docs/pt-BR/project-structure.md +197 -2
- data/docs/pt-BR/runtime-ruby.md +108 -0
- data/docs/pt-BR/runtime-schema-migrations.md +40 -0
- data/docs/pt-BR/validation-matrix.md +42 -5
- data/lib/mxrb/cli/help.rb +426 -0
- data/lib/mxrb/cli/release.rb +137 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +1 -1
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +8 -1
- data/lib/mxrb/compiler/domain_document_compiler.rb +3 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +11 -1
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +76 -19
- data/lib/mxrb/compiler/legacy_custom_widget_compiler.rb +159 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +43 -7
- data/lib/mxrb/compiler/legacy_page_builder.rb +1248 -23
- data/lib/mxrb/compiler/page_bundle_compiler.rb +13 -4
- data/lib/mxrb/compiler/web_bundle_builder.rb +5 -1
- data/lib/mxrb/compiler/web_shell_materializer.rb +42 -2
- data/lib/mxrb/compiler/widget_package_extractor.rb +3 -0
- data/lib/mxrb/domain_diagram/lifecycle.rb +272 -0
- data/lib/mxrb/domain_diagram.rb +409 -0
- data/lib/mxrb/dsl/builder.rb +43 -9
- data/lib/mxrb/environment.rb +226 -0
- data/lib/mxrb/exporter.rb +89 -15
- data/lib/mxrb/http/server.rb +107 -0
- data/lib/mxrb/initializer.rb +80 -9
- data/lib/mxrb/integrity/validator.rb +14 -0
- data/lib/mxrb/io/bson_codec.rb +42 -1
- data/lib/mxrb/io/mpr_file.rb +146 -3
- data/lib/mxrb/model/attribute.rb +3 -0
- data/lib/mxrb/model/entity.rb +28 -2
- data/lib/mxrb/model/microflow.rb +3 -1
- data/lib/mxrb/model/page.rb +257 -17
- data/lib/mxrb/model/project.rb +3 -0
- data/lib/mxrb/modeler/catalog.rb +178 -0
- data/lib/mxrb/oql/reverse_translator.rb +394 -0
- data/lib/mxrb/oql/server.rb +4 -8
- data/lib/mxrb/oql.rb +23 -1
- data/lib/mxrb/project_lifecycle.rb +5 -2
- data/lib/mxrb/ruby_app/exporter.rb +2508 -0
- data/lib/mxrb/ruby_app/preset.rb +506 -0
- data/lib/mxrb/ruby_app/session_manager.rb +122 -0
- data/lib/mxrb/ruby_app.rb +1369 -0
- data/lib/mxrb/runtime/access_control.rb +408 -0
- data/lib/mxrb/runtime/native.rb +1015 -93
- data/lib/mxrb/runtime/scheduler.rb +396 -0
- data/lib/mxrb/runtime/schema_migrator.rb +598 -0
- data/lib/mxrb/runtime/shared_store.rb +335 -0
- data/lib/mxrb/runtime/sqlite_store.rb +729 -0
- data/lib/mxrb/scaffold/generator.rb +11 -3
- data/lib/mxrb/uml/activity_diagram.rb +118 -0
- data/lib/mxrb/uml/class_diagram.rb +118 -0
- data/lib/mxrb/uml/sequence_diagram.rb +112 -0
- data/lib/mxrb/uml/server.rb +146 -0
- data/lib/mxrb/uml/support.rb +35 -0
- data/lib/mxrb/version.rb +1 -1
- data/lib/mxrb/web_ui/assets/abnfDiagram-N423BO3Z-C4JD3cEC.js +1 -0
- data/lib/mxrb/web_ui/assets/arc-3Z53kgFp.js +1 -0
- data/lib/mxrb/web_ui/assets/architecture-TIHT7OUA-BStBTLea.js +1 -0
- data/lib/mxrb/web_ui/assets/architectureDiagram-T3A2C74G-DBwqnM4J.js +36 -0
- data/lib/mxrb/web_ui/assets/array-BifhSqXX.js +1 -0
- data/lib/mxrb/web_ui/assets/blockDiagram-VBNYF7ZC-DLtuHt-j.js +132 -0
- data/lib/mxrb/web_ui/assets/c4Diagram-5PPSVZJV-ConSk_pe.js +10 -0
- data/lib/mxrb/web_ui/assets/channel-BhPvD7Dt.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2GRJ4B5K-D2HpWsf_.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-4I5QYGJK-CiIdNwaF.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-5RXB4S5H-Bu_AaEkE.js +231 -0
- data/lib/mxrb/web_ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- data/lib/mxrb/web_ui/assets/chunk-6Q2QTUOP-wQkNmt_X.js +88 -0
- data/lib/mxrb/web_ui/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-GF5L2VYU-G8MmiBzU.js +206 -0
- data/lib/mxrb/web_ui/assets/chunk-I66GZJ75-CulcrLPh.js +127 -0
- data/lib/mxrb/web_ui/assets/chunk-JQJVKLGR-ukjzcdlL.js +156 -0
- data/lib/mxrb/web_ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KBJHAD2P-Wm35lYov.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KEIR6QF5-BICK3FdT.js +161 -0
- data/lib/mxrb/web_ui/assets/chunk-NSK5VX7P-D2nYrCY4.js +2 -0
- data/lib/mxrb/web_ui/assets/chunk-QR6OTTB3-BrdIzg4Q.js +62 -0
- data/lib/mxrb/web_ui/assets/chunk-RYQCIY6F-CCfZMcW6.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-UBXNYLIW-BPEVYsFK.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-W5SLKNZC-2E6OPhA4.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-WRU74C26-CBIvOyWp.js +70 -0
- data/lib/mxrb/web_ui/assets/chunk-XXDRQBXY-pH58XAyl.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-JCYQIIEL-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-v2-OCEON4UE-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/cose-bilkent-JH36ORCC-t_flPpii.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefin-VYW2F7L2-BIqktTEv.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefinDiagram-MW4NZA55-C2d--lut.js +62 -0
- data/lib/mxrb/web_ui/assets/cytoscape.esm-B-NFISlW.js +321 -0
- data/lib/mxrb/web_ui/assets/dagre-Buvkdvvj.js +1 -0
- data/lib/mxrb/web_ui/assets/dagre-VZM6K2ZE-CSz2_tb5.js +4 -0
- data/lib/mxrb/web_ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- data/lib/mxrb/web_ui/assets/diagram-7IWD3JNH-DCt7u7V_.js +30 -0
- data/lib/mxrb/web_ui/assets/diagram-B4RE2ZJO-DqZBqkMu.js +3 -0
- data/lib/mxrb/web_ui/assets/diagram-LBJQPF4R-ClIYbIsI.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-Q27KOJAE-DFxhvthJ.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-UB23O5K3-BSUTXptv.js +41 -0
- data/lib/mxrb/web_ui/assets/dist-D2qOEbeJ.js +1 -0
- data/lib/mxrb/web_ui/assets/domain-mGOZ1KCG.css +1 -0
- data/lib/mxrb/web_ui/assets/domain-qsjXhy4v.js +1 -0
- data/lib/mxrb/web_ui/assets/ebnfDiagram-BXEA7PRR-B5reICEi.js +1 -0
- data/lib/mxrb/web_ui/assets/erDiagram-JOGREHBK-Co5Mxn2p.js +85 -0
- data/lib/mxrb/web_ui/assets/eventmodeling-45OFAUF4-C1ylhSks.js +1 -0
- data/lib/mxrb/web_ui/assets/flowDiagram-UKHOOZJN-CHXaJIaS.js +1 -0
- data/lib/mxrb/web_ui/assets/ganttDiagram-PKOTCBZU-DPPPaWaz.js +292 -0
- data/lib/mxrb/web_ui/assets/gitGraph-TEB2WS4Q-qS77ywA9.js +1 -0
- data/lib/mxrb/web_ui/assets/gitGraphDiagram-DS77QQ5N-DpAv6-BT.js +106 -0
- data/lib/mxrb/web_ui/assets/graphlib-DS17s2tU.js +1 -0
- data/lib/mxrb/web_ui/assets/info-DKCQHKI2-C9VD1Y4e.js +1 -0
- data/lib/mxrb/web_ui/assets/infoDiagram-6WML65LV-Bk1kbcCy.js +2 -0
- data/lib/mxrb/web_ui/assets/init-C-OQMol4.js +1 -0
- data/lib/mxrb/web_ui/assets/ishikawaDiagram-WSZJBQD7-0gcISBFR.js +70 -0
- data/lib/mxrb/web_ui/assets/journeyDiagram-NVQOT4AX-Dt0RQnlh.js +139 -0
- data/lib/mxrb/web_ui/assets/jsx-runtime-DV0a5kSb.js +9 -0
- data/lib/mxrb/web_ui/assets/kanban-definition-27J2QSJJ-DIPQM7w9.js +89 -0
- data/lib/mxrb/web_ui/assets/katex-CXMH3UgJ.js +257 -0
- data/lib/mxrb/web_ui/assets/line-C0rYD-tL.js +1 -0
- data/lib/mxrb/web_ui/assets/linear-BPPxWorj.js +1 -0
- data/lib/mxrb/web_ui/assets/map-BaFkSB1l.js +1 -0
- data/lib/mxrb/web_ui/assets/mermaid-parser.core-B_UPzTxa.js +7 -0
- data/lib/mxrb/web_ui/assets/mindmap-definition-FAOFIHXS-CdJRSMuk.js +96 -0
- data/lib/mxrb/web_ui/assets/modeler-BNqHthwk.css +1 -0
- data/lib/mxrb/web_ui/assets/modeler-CQmqflbg.js +1 -0
- data/lib/mxrb/web_ui/assets/ordinal-BDEzSJ7C.js +1 -0
- data/lib/mxrb/web_ui/assets/packet-7NZHBO7P-CwQ4NSBs.js +1 -0
- data/lib/mxrb/web_ui/assets/path-fybaL0A-.js +1 -0
- data/lib/mxrb/web_ui/assets/pegDiagram-VL7TDLO6-D40S7ppx.js +1 -0
- data/lib/mxrb/web_ui/assets/pie-RZYD4A2V-CJroHMKh.js +1 -0
- data/lib/mxrb/web_ui/assets/pieDiagram-7S7Q4E2Y-QB54__BV.js +39 -0
- data/lib/mxrb/web_ui/assets/quadrantDiagram-CIZ2JOQS-DBhDS6ag.js +7 -0
- data/lib/mxrb/web_ui/assets/radar-I7S5WNFK-DdIUwd2d.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-3IZDKUUU-B1Lw6Xyq.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-abnf-AHOZXSZD-CQgQoeak.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-ebnf-EBAXGLYW-D7qEQ2x8.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-peg-LSFZ7HO6-C005Vxbb.js +1 -0
- data/lib/mxrb/web_ui/assets/railroadDiagram-AXF67PYL-BBP2qk7M.js +1 -0
- data/lib/mxrb/web_ui/assets/requirementDiagram-LRYGKXZP-D2cLsAoI.js +84 -0
- data/lib/mxrb/web_ui/assets/rough.esm-Dy-Kn_BL.js +1 -0
- data/lib/mxrb/web_ui/assets/sankeyDiagram-W5VNT64P-Bpi3FiP2.js +40 -0
- data/lib/mxrb/web_ui/assets/sequenceDiagram-SI44F4Z6-DCRcDlVe.js +162 -0
- data/lib/mxrb/web_ui/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- data/lib/mxrb/web_ui/assets/src-CvxfUak2.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-OKZ733FA-C2GCEoxl.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-v2-UEYNNEHI-D2gWgedh.js +1 -0
- data/lib/mxrb/web_ui/assets/swimlanes-SLNWSIFB-CRI9irEW.js +2 -0
- data/lib/mxrb/web_ui/assets/swimlanesDiagram-ULZ7WXOC-C5Wb_-hZ.js +8 -0
- data/lib/mxrb/web_ui/assets/theme-_mxc7BdY.css +1 -0
- data/lib/mxrb/web_ui/assets/timeline-definition-Z64GVDOM-B1-mNg-h.js +120 -0
- data/lib/mxrb/web_ui/assets/treeView-QDETBFTQ-7CTvIwKq.js +1 -0
- data/lib/mxrb/web_ui/assets/treemap-6X3UGDF4-CtL22pq5.js +1 -0
- data/lib/mxrb/web_ui/assets/uml-C3KAFGTq.js +51 -0
- data/lib/mxrb/web_ui/assets/uml-Dn-qd01X.css +1 -0
- data/lib/mxrb/web_ui/assets/vennDiagram-T6HMQDX7-BzVnUEAy.js +34 -0
- data/lib/mxrb/web_ui/assets/wardley-OPB4EBWU-JNi-QNag.js +1 -0
- data/lib/mxrb/web_ui/assets/wardleyDiagram-T6FBY63Y-BUwmAQ-B.js +78 -0
- data/lib/mxrb/web_ui/assets/xychartDiagram-ELKLHX3M-DEQS8sKp.js +7 -0
- data/lib/mxrb/web_ui/domain.html +15 -0
- data/lib/mxrb/web_ui/modeler.html +16 -0
- data/lib/mxrb/web_ui/uml.html +31 -0
- data/lib/mxrb/web_ui.rb +89 -0
- data/lib/mxrb/widget_certification.rb +208 -0
- data/lib/mxrb/writer.rb +200 -24
- data/lib/mxrb.rb +26 -1
- metadata +156 -5
data/lib/mxrb/model/page.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Mxrb
|
|
|
10
10
|
attr_reader :name, :documentation, :url, :layout_id, :title,
|
|
11
11
|
:popup_width, :popup_height, :popup_resizable, :excluded,
|
|
12
12
|
:allowed_module_roles, :parameters, :export_level,
|
|
13
|
-
:widgets, :data_source, :
|
|
13
|
+
:widgets, :data_source, :appearance_class, :appearance_style,
|
|
14
|
+
:raw_document
|
|
14
15
|
|
|
15
16
|
def decode(doc)
|
|
16
17
|
@raw_document = doc
|
|
@@ -25,6 +26,8 @@ module Mxrb
|
|
|
25
26
|
@popup_resizable = doc["PopupResizable"] == true
|
|
26
27
|
@excluded = doc["Excluded"] == true
|
|
27
28
|
@export_level = doc["ExportLevel"] || "Hidden"
|
|
29
|
+
@appearance_class = doc.dig("Appearance", "Class").to_s
|
|
30
|
+
@appearance_style = doc.dig("Appearance", "Style").to_s
|
|
28
31
|
@allowed_module_roles = parse_array(doc["AllowedModuleRoles"] || doc["AllowedRoles"] || doc["allowedModuleRoles"])
|
|
29
32
|
@parameters = parse_array(doc["Parameters"] || doc["parameters"])
|
|
30
33
|
@widgets = []
|
|
@@ -112,7 +115,8 @@ module Mxrb
|
|
|
112
115
|
children = []
|
|
113
116
|
parse_widgets(parse_array(widget["Widgets"]), children)
|
|
114
117
|
target << { type: :container, name: widget["Name"] || "container",
|
|
115
|
-
options: container_options(widget), children: children,
|
|
118
|
+
options: container_options(widget), children: children,
|
|
119
|
+
events: container_events(widget) }
|
|
116
120
|
next
|
|
117
121
|
end
|
|
118
122
|
if layout_container?(widget)
|
|
@@ -171,17 +175,42 @@ module Mxrb
|
|
|
171
175
|
end
|
|
172
176
|
|
|
173
177
|
def widget_options(widget, widget_type)
|
|
174
|
-
options =
|
|
178
|
+
options = appearance_options(widget).merge(
|
|
175
179
|
attribute: attribute_path(widget),
|
|
176
180
|
caption: widget_caption(widget, widget_type)
|
|
177
|
-
|
|
181
|
+
).compact
|
|
182
|
+
parameters = template_parameters(widget)
|
|
183
|
+
options[:parameters] = parameters unless parameters.empty?
|
|
178
184
|
options[:lines] = widget["NumberOfLines"] if widget_type == :text_area && widget["NumberOfLines"]
|
|
179
185
|
options
|
|
180
186
|
end
|
|
181
187
|
|
|
182
188
|
def container_options(widget)
|
|
189
|
+
appearance_options(widget)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def appearance_options(widget)
|
|
183
193
|
class_name = widget.dig("Appearance", "Class") || widget["Class"]
|
|
184
|
-
|
|
194
|
+
style = widget.dig("Appearance", "Style") || widget["Style"]
|
|
195
|
+
dynamic = widget.dig("Appearance", "DynamicClasses")
|
|
196
|
+
visibility = widget.dig("ConditionalVisibilitySettings", "Expression")
|
|
197
|
+
{ class: class_name.to_s, style: style.to_s,
|
|
198
|
+
dynamic_class: dynamic.to_s, visible: visibility.to_s }.reject { |_key, value| value.empty? }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def container_events(widget)
|
|
202
|
+
action = parse_action(widget["OnClickAction"] || widget["Action"])
|
|
203
|
+
action ? [action.merge(event: :on_click)] : []
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def template_parameters(widget)
|
|
207
|
+
template = widget["Content"] || widget["CaptionTemplate"] || widget["LabelTemplate"]
|
|
208
|
+
parse_array(template.is_a?(Hash) ? template["Parameters"] : nil).filter_map do |parameter|
|
|
209
|
+
expression = parameter["Expression"].to_s
|
|
210
|
+
attribute = parameter.dig("AttributeRef", "Attribute").to_s.split('.').last.to_s
|
|
211
|
+
expression = "$currentObject/#{attribute}" if expression.empty? && !attribute.empty?
|
|
212
|
+
expression unless expression.empty?
|
|
213
|
+
end
|
|
185
214
|
end
|
|
186
215
|
|
|
187
216
|
def attribute_path(widget)
|
|
@@ -210,27 +239,187 @@ module Mxrb
|
|
|
210
239
|
options = { entity: grid_entity(widget), columns: columns }.compact
|
|
211
240
|
options[:search_bar] = parse_search_bar(widget["SearchBar"]) if widget["SearchBar"].is_a?(Hash)
|
|
212
241
|
options[:toolbar] = parse_toolbar(widget["ToolBar"]) if widget["ToolBar"].is_a?(Hash)
|
|
213
|
-
{ type: :data_grid, name: widget["Name"], options: options, events:
|
|
242
|
+
{ type: :data_grid, name: widget["Name"], options: options, events: grid_events(widget) }
|
|
214
243
|
end
|
|
215
244
|
|
|
216
245
|
def pluggable_widget(widget)
|
|
217
|
-
|
|
246
|
+
widget_id = widget.dig("Type", "WidgetId").to_s
|
|
247
|
+
return native_widget(widget) if widget_id.empty?
|
|
248
|
+
|
|
249
|
+
case widget_id
|
|
218
250
|
when "com.mendix.widget.web.datagrid.Datagrid" then data_grid2_widget(widget)
|
|
219
251
|
when "com.mendix.widget.web.combobox.Combobox" then combo_box_widget(widget)
|
|
220
|
-
|
|
252
|
+
when "com.mendix.widget.web.gallery.Gallery" then gallery_widget(widget)
|
|
253
|
+
else generic_pluggable_widget(widget)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def generic_pluggable_widget(widget)
|
|
258
|
+
object_type = widget.dig("Type", "ObjectType")
|
|
259
|
+
properties = pluggable_properties(widget["Object"], object_type)
|
|
260
|
+
children = nested_pluggable_widgets(properties)
|
|
261
|
+
properties = strip_pluggable_widgets(properties)
|
|
262
|
+
options = appearance_options(widget).merge(
|
|
263
|
+
widget_id: widget.dig("Type", "WidgetId"),
|
|
264
|
+
widget_name: widget.dig("Type", "WidgetName"),
|
|
265
|
+
platform: widget.dig("Type", "SupportedPlatform"),
|
|
266
|
+
properties:
|
|
267
|
+
).compact
|
|
268
|
+
{
|
|
269
|
+
type: :pluggable_widget, name: widget["Name"] || "widget",
|
|
270
|
+
options:, children:, events: pluggable_events(properties)
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def pluggable_properties(object, object_type)
|
|
275
|
+
return {} unless object.is_a?(Hash) && object_type.is_a?(Hash)
|
|
276
|
+
|
|
277
|
+
types = parse_array(object_type["PropertyTypes"])
|
|
278
|
+
types_by_id = types.to_h { [IO::BsonCodec.extract_id(_1["$ID"]), _1] }
|
|
279
|
+
parse_array(object["Properties"]).each_with_object({}) do |property, result|
|
|
280
|
+
type = types_by_id[IO::BsonCodec.extract_id(property["TypePointer"])]
|
|
281
|
+
next unless type
|
|
282
|
+
|
|
283
|
+
result[type["PropertyKey"].to_s] = pluggable_value(
|
|
284
|
+
property["Value"], type["ValueType"]
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def pluggable_value(value, value_type)
|
|
290
|
+
return nil unless value.is_a?(Hash)
|
|
291
|
+
|
|
292
|
+
type = value_type.is_a?(Hash) ? value_type["Type"].to_s : ""
|
|
293
|
+
widgets = parse_array(value["Widgets"])
|
|
294
|
+
unless widgets.empty?
|
|
295
|
+
parsed = []
|
|
296
|
+
parse_widgets(widgets, parsed)
|
|
297
|
+
return { widgets: parsed }
|
|
298
|
+
end
|
|
299
|
+
objects = parse_array(value["Objects"])
|
|
300
|
+
unless objects.empty?
|
|
301
|
+
object_type = value_type.is_a?(Hash) ? value_type["ObjectType"] : nil
|
|
302
|
+
return { objects: objects.map { pluggable_properties(_1, object_type) } }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
return extract_text(value["TextTemplate"]) if value["TextTemplate"].is_a?(Hash)
|
|
306
|
+
return value.dig("AttributeRef", "Attribute") if value["AttributeRef"].is_a?(Hash)
|
|
307
|
+
return pluggable_data_source(value["DataSource"]) if value["DataSource"].is_a?(Hash)
|
|
308
|
+
return value["Expression"] unless value["Expression"].to_s.empty?
|
|
309
|
+
return value["Image"] unless value["Image"].to_s.empty?
|
|
310
|
+
return value["Form"] unless value["Form"].to_s.empty?
|
|
311
|
+
|
|
312
|
+
primitive = value["PrimitiveValue"]
|
|
313
|
+
return primitive == true || primitive.to_s.casecmp("true").zero? if type == "Boolean"
|
|
314
|
+
return primitive.to_i if type == "Integer"
|
|
315
|
+
return primitive.to_f if %w[Decimal Number].include?(type)
|
|
316
|
+
return primitive unless primitive.nil? || primitive.to_s.empty?
|
|
317
|
+
|
|
318
|
+
action = value["Action"]
|
|
319
|
+
return pluggable_action(action) if action.is_a?(Hash) && action["$Type"] != "Forms$NoAction"
|
|
320
|
+
return value["Selection"] unless value["Selection"].to_s.empty? || value["Selection"] == "None"
|
|
321
|
+
|
|
322
|
+
nil
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def pluggable_data_source(source)
|
|
326
|
+
{
|
|
327
|
+
data_source: source.dig("EntityRef", "Entity") || source["Entity"],
|
|
328
|
+
xpath: source["XPathConstraint"].to_s,
|
|
329
|
+
sort: parse_array(source.dig("SortBar", "SortItems")).map do |item|
|
|
330
|
+
{
|
|
331
|
+
attribute: item.dig("AttributeRef", "Attribute"),
|
|
332
|
+
direction: item["SortDirection"].to_s
|
|
333
|
+
}.compact
|
|
334
|
+
end
|
|
335
|
+
}.compact
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def pluggable_action(action)
|
|
339
|
+
candidates = {
|
|
340
|
+
"nanoflow" => action["Nanoflow"],
|
|
341
|
+
"microflow" => action["Microflow"],
|
|
342
|
+
"page" => action["Form"]
|
|
343
|
+
}
|
|
344
|
+
kind, handler = candidates.find { |_candidate, value| !value.to_s.empty? }
|
|
345
|
+
kind ||= "action"
|
|
346
|
+
handler ||= action["$Type"]
|
|
347
|
+
{ kind:, handler: handler.to_s }
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def nested_pluggable_widgets(value, found = [])
|
|
351
|
+
case value
|
|
352
|
+
when Hash
|
|
353
|
+
widgets = value[:widgets] || value["widgets"]
|
|
354
|
+
found.concat(Array(widgets)) if widgets
|
|
355
|
+
value.each_value { nested_pluggable_widgets(_1, found) }
|
|
356
|
+
when Array then value.each { nested_pluggable_widgets(_1, found) }
|
|
357
|
+
end
|
|
358
|
+
found.uniq { [_1[:name] || _1["name"], _1[:type] || _1["type"]] }
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def strip_pluggable_widgets(value)
|
|
362
|
+
case value
|
|
363
|
+
when Hash
|
|
364
|
+
value.each_with_object({}) do |(key, child), result|
|
|
365
|
+
next if key.to_s == "widgets"
|
|
366
|
+
|
|
367
|
+
result[key] = strip_pluggable_widgets(child)
|
|
368
|
+
end
|
|
369
|
+
when Array then value.map { strip_pluggable_widgets(_1) }
|
|
370
|
+
else value
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def pluggable_events(properties)
|
|
375
|
+
properties.filter_map do |key, value|
|
|
376
|
+
next unless value.is_a?(Hash) && value[:handler]
|
|
377
|
+
|
|
378
|
+
event = key.to_s.match?(/change/i) ? :on_change : :on_click
|
|
379
|
+
value.merge(event:)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def gallery_widget(widget)
|
|
384
|
+
properties = custom_property_map(widget["Object"], widget.dig("Type", "ObjectType"))
|
|
385
|
+
source = properties.dig("datasource", "DataSource") || {}
|
|
386
|
+
children = []
|
|
387
|
+
parse_widgets(parse_array(properties.dig("content", "Widgets")), children)
|
|
388
|
+
sort = parse_array(source.dig("SortBar", "SortItems")).map do |item|
|
|
389
|
+
{
|
|
390
|
+
attribute: item.dig("AttributeRef", "Attribute"),
|
|
391
|
+
direction: item["SortDirection"].to_s
|
|
392
|
+
}.compact
|
|
221
393
|
end
|
|
394
|
+
xpath = source["XPathConstraint"].to_s
|
|
395
|
+
options = appearance_options(widget).merge(
|
|
396
|
+
entity: source.dig("EntityRef", "Entity"), xpath: xpath,
|
|
397
|
+
association: xpath[/\[([\w.]+)\s*=\s*\$\w+\]/, 1],
|
|
398
|
+
context_variable: xpath[/\$([A-Za-z_]\w*)/, 1], sort: sort
|
|
399
|
+
).compact
|
|
400
|
+
{
|
|
401
|
+
type: :gallery, name: widget["Name"], options: options,
|
|
402
|
+
children: children, events: []
|
|
403
|
+
}
|
|
222
404
|
end
|
|
223
405
|
|
|
224
406
|
def native_widget(widget)
|
|
225
407
|
deep = widget.reject { |key, _| %w[$ID $Type Name].include?(key.to_s) }
|
|
408
|
+
widget_id = widget.dig("Type", "WidgetId").to_s
|
|
409
|
+
platform = widget.dig("Type", "SupportedPlatform").to_s
|
|
410
|
+
options = { native_type: widget["$Type"], deep_structure: deep }
|
|
411
|
+
options[:widget_id] = widget_id unless widget_id.empty?
|
|
412
|
+
options[:platform] = platform unless platform.empty?
|
|
226
413
|
{
|
|
227
414
|
type: :native_widget, name: widget["Name"] || "widget",
|
|
228
|
-
options
|
|
415
|
+
options:, events: []
|
|
229
416
|
}
|
|
230
417
|
end
|
|
231
418
|
|
|
232
419
|
def data_grid2_widget(widget)
|
|
233
420
|
properties = custom_property_map(widget["Object"], widget.dig("Type", "ObjectType"))
|
|
421
|
+
return data_grid_widget(widget) if properties.empty? && widget["DataSource"]
|
|
422
|
+
|
|
234
423
|
entity = properties.dig("datasource", "DataSource", "EntityRef", "Entity")
|
|
235
424
|
column_type = custom_property_type(widget.dig("Type", "ObjectType"), "columns")
|
|
236
425
|
&.dig("ValueType", "ObjectType")
|
|
@@ -244,12 +433,35 @@ module Mxrb
|
|
|
244
433
|
end
|
|
245
434
|
{
|
|
246
435
|
type: :data_grid, name: widget["Name"],
|
|
247
|
-
options: { entity: entity, columns: columns }.compact, events:
|
|
436
|
+
options: { entity: entity, columns: columns }.compact, events: grid_events(widget)
|
|
248
437
|
}
|
|
249
438
|
end
|
|
250
439
|
|
|
440
|
+
def grid_events(widget)
|
|
441
|
+
{
|
|
442
|
+
"OnChangeAction" => :on_change,
|
|
443
|
+
"OnClickAction" => :on_click,
|
|
444
|
+
"Action" => :on_click
|
|
445
|
+
}.filter_map do |property, event|
|
|
446
|
+
action = parse_action(widget[property])
|
|
447
|
+
action&.merge(event: event)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
251
451
|
def combo_box_widget(widget)
|
|
252
452
|
properties = custom_property_map(widget["Object"], widget.dig("Type", "ObjectType"))
|
|
453
|
+
if properties.empty? && widget["AttributePath"]
|
|
454
|
+
reference = widget["SelectorType"] == "Reference"
|
|
455
|
+
options = {
|
|
456
|
+
attribute: widget["AttributePath"], caption: extract_text(widget["LabelText"]),
|
|
457
|
+
display_attribute: widget["DisplayAttribute"]
|
|
458
|
+
}.compact
|
|
459
|
+
return {
|
|
460
|
+
type: reference ? :reference_selector : :drop_down,
|
|
461
|
+
name: widget["Name"], options: options, events: grid_events(widget)
|
|
462
|
+
}
|
|
463
|
+
end
|
|
464
|
+
|
|
253
465
|
association = parse_array(properties.dig("attributeAssociation", "EntityRef", "Steps"))
|
|
254
466
|
.first&.fetch("Association", nil)
|
|
255
467
|
reference = properties.dig("optionsSourceType", "PrimitiveValue") == "association"
|
|
@@ -267,7 +479,7 @@ module Mxrb
|
|
|
267
479
|
)
|
|
268
480
|
end
|
|
269
481
|
{ type: reference ? :reference_selector : :drop_down,
|
|
270
|
-
name: widget["Name"], options: options.compact, events:
|
|
482
|
+
name: widget["Name"], options: options.compact, events: grid_events(widget) }
|
|
271
483
|
end
|
|
272
484
|
|
|
273
485
|
def custom_property_map(object, object_type)
|
|
@@ -376,21 +588,49 @@ module Mxrb
|
|
|
376
588
|
def parse_source(source)
|
|
377
589
|
return nil unless source.is_a?(Hash)
|
|
378
590
|
result = if %w[Pages$NanoflowSource Forms$NanoflowSource].include?(source["$Type"])
|
|
379
|
-
{ kind: :nanoflow, name:
|
|
591
|
+
{ kind: :nanoflow, name: source_name(source["Nanoflow"] || source.dig("NanoflowSettings", "Nanoflow")) }
|
|
380
592
|
else
|
|
381
|
-
{ kind: :microflow, name:
|
|
593
|
+
{ kind: :microflow, name: source_name(source["Microflow"] || source.dig("MicroflowSettings", "Microflow")) }
|
|
382
594
|
end
|
|
383
595
|
result[:name].to_s.empty? ? nil : result
|
|
384
596
|
end
|
|
385
597
|
|
|
598
|
+
# Page data sources are architectural references. Unlike widget-local
|
|
599
|
+
# action handlers, their module qualification must survive export so a
|
|
600
|
+
# native Core.Flow reference cannot silently become Portal.Flow.
|
|
601
|
+
def source_name(name)
|
|
602
|
+
name.to_s
|
|
603
|
+
end
|
|
604
|
+
|
|
386
605
|
def parse_action(action)
|
|
387
606
|
return nil unless action.is_a?(Hash)
|
|
388
607
|
if %w[Pages$CallNanoflowClientAction Forms$CallNanoflowClientAction].include?(action["$Type"])
|
|
389
|
-
|
|
390
|
-
handler
|
|
608
|
+
settings = action["NanoflowSettings"] || action
|
|
609
|
+
handler = local_name(action["Nanoflow"] || settings["Nanoflow"])
|
|
610
|
+
return nil if handler.to_s.empty?
|
|
611
|
+
|
|
612
|
+
arguments = parse_array(settings["ParameterMappings"]).to_h do |mapping|
|
|
613
|
+
[local_name(mapping["Parameter"]), mapping["Expression"].to_s]
|
|
614
|
+
end
|
|
615
|
+
{ kind: :nanoflow, handler: handler, arguments: arguments }
|
|
391
616
|
elsif %w[Pages$MicroflowClientAction Forms$MicroflowAction Forms$MicroflowClientAction].include?(action["$Type"])
|
|
392
|
-
|
|
393
|
-
handler
|
|
617
|
+
settings = action["MicroflowSettings"] || action
|
|
618
|
+
handler = local_name(action["Microflow"] || settings["Microflow"])
|
|
619
|
+
return nil if handler.to_s.empty?
|
|
620
|
+
|
|
621
|
+
arguments = parse_array(settings["ParameterMappings"]).to_h do |mapping|
|
|
622
|
+
[local_name(mapping["Parameter"]), mapping["Expression"].to_s]
|
|
623
|
+
end
|
|
624
|
+
{ kind: :microflow, handler: handler, arguments: arguments }
|
|
625
|
+
elsif %w[Pages$FormAction Forms$FormAction].include?(action["$Type"])
|
|
626
|
+
settings = action["FormSettings"] || action
|
|
627
|
+
handler = settings["Form"].to_s
|
|
628
|
+
return nil if handler.empty?
|
|
629
|
+
|
|
630
|
+
arguments = parse_array(settings["ParameterMappings"]).to_h do |mapping|
|
|
631
|
+
[local_name(mapping["Parameter"]), mapping["Expression"].to_s]
|
|
632
|
+
end
|
|
633
|
+
{ kind: :page, handler: handler, arguments: arguments }
|
|
394
634
|
elsif %w[Pages$SaveChangesClientAction Forms$SaveChangesClientAction].include?(action["$Type"])
|
|
395
635
|
{ kind: :action, handler: "save_changes" }
|
|
396
636
|
elsif %w[Pages$CancelChangesClientAction Forms$CancelChangesClientAction].include?(action["$Type"])
|
data/lib/mxrb/model/project.rb
CHANGED
|
@@ -63,6 +63,9 @@ module Mxrb
|
|
|
63
63
|
analyzer = Oql::Analyzer.new(dialect:)
|
|
64
64
|
oql_queries.map { analyzer.analyze(_1) }.freeze
|
|
65
65
|
end
|
|
66
|
+
def sql_to_oql(source, dialect: :postgresql)
|
|
67
|
+
Oql::ReverseTranslator.new(dialect:, project: self).translate(source)
|
|
68
|
+
end
|
|
66
69
|
|
|
67
70
|
# ── Semantic analysis ───────────────────────────────────────────────
|
|
68
71
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Modeler
|
|
5
|
+
# Read-only, JSON-safe projection used by the React project modeler.
|
|
6
|
+
# rubocop:disable Metrics
|
|
7
|
+
class Catalog
|
|
8
|
+
SETTINGS_PATTERN = /(?:Settings|Configuration)\$|(?:Settings|Configuration)\z/
|
|
9
|
+
|
|
10
|
+
def initialize(path)
|
|
11
|
+
@path = File.expand_path(path)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
project = Model::Project.open(@path)
|
|
16
|
+
documents = project_documents(project)
|
|
17
|
+
modules = project.modules.map { module_payload(_1) }
|
|
18
|
+
{
|
|
19
|
+
project: project_payload(project),
|
|
20
|
+
summary: summary(modules),
|
|
21
|
+
modules:,
|
|
22
|
+
navigation: project.navigation.to_h,
|
|
23
|
+
security: security_payload(documents),
|
|
24
|
+
settings: settings_payload(documents)
|
|
25
|
+
}
|
|
26
|
+
ensure
|
|
27
|
+
project&.close
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def project_payload(project)
|
|
33
|
+
{
|
|
34
|
+
name: project.name,
|
|
35
|
+
mendix_version: project.mendix_version,
|
|
36
|
+
format_version: project.format_version
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def summary(modules)
|
|
41
|
+
keys = %i[entities pages microflows nanoflows integrations configurations]
|
|
42
|
+
{ modules: modules.size }.merge(
|
|
43
|
+
keys.to_h { |key| [key, modules.sum { Array(_1[key]).size }] }
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def module_payload(mod)
|
|
48
|
+
documents = catalog_documents(mod)
|
|
49
|
+
integration_groups = %w[endpoints integrations mappings]
|
|
50
|
+
{
|
|
51
|
+
id: mod.id,
|
|
52
|
+
name: mod.name,
|
|
53
|
+
marketplace: mod.from_app_store == true,
|
|
54
|
+
marketplace_guid: blank_to_nil(mod.app_store_guid),
|
|
55
|
+
marketplace_version: blank_to_nil(mod.app_store_version),
|
|
56
|
+
entities: mod.entities.map { entity_payload(_1, mod.name) },
|
|
57
|
+
pages: mod.pages.map { page_payload(_1, mod.name) },
|
|
58
|
+
microflows: mod.microflows.map { flow_payload(_1, mod.name, 'microflow') },
|
|
59
|
+
nanoflows: mod.nanoflows.map { flow_payload(_1, mod.name, 'nanoflow') },
|
|
60
|
+
module_roles: mod.module_roles,
|
|
61
|
+
integrations: documents.select { integration_groups.include?(_1[:group]) },
|
|
62
|
+
configurations: documents.reject { integration_groups.include?(_1[:group]) }
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def entity_payload(entity, module_name)
|
|
67
|
+
{
|
|
68
|
+
id: entity.id,
|
|
69
|
+
name: entity.name,
|
|
70
|
+
qualified_name: entity.qualified_name || "#{module_name}.#{entity.name}",
|
|
71
|
+
persistent: entity.persistable == true,
|
|
72
|
+
attributes: entity.attributes.map do |attribute|
|
|
73
|
+
{ name: attribute.name, type: attribute.type.to_s, required: attribute.required == true }
|
|
74
|
+
end
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def page_payload(page, module_name)
|
|
79
|
+
widgets = flatten_widgets(page.widgets)
|
|
80
|
+
{
|
|
81
|
+
id: page.id,
|
|
82
|
+
name: page.name,
|
|
83
|
+
qualified_name: "#{module_name}.#{page.name}",
|
|
84
|
+
title: page.title,
|
|
85
|
+
url: page.url,
|
|
86
|
+
layout_id: page.layout_id,
|
|
87
|
+
excluded: page.excluded == true,
|
|
88
|
+
allowed_module_roles: page.allowed_module_roles.map(&:to_s),
|
|
89
|
+
widget_count: widgets.size,
|
|
90
|
+
widget_types: widgets.filter_map { _1[:type]&.to_s }.tally
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def flow_payload(flow, module_name, kind)
|
|
95
|
+
{
|
|
96
|
+
id: flow.id,
|
|
97
|
+
name: flow.name,
|
|
98
|
+
qualified_name: "#{module_name}.#{flow.name}",
|
|
99
|
+
kind:,
|
|
100
|
+
documentation: flow.documentation.to_s,
|
|
101
|
+
allowed_module_roles: flow.allowed_module_roles.map(&:to_s),
|
|
102
|
+
parameter_count: flow.parameters.size,
|
|
103
|
+
object_count: flow.objects.size,
|
|
104
|
+
flow_count: flow.flows.size
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def flatten_widgets(widgets)
|
|
109
|
+
Array(widgets).flat_map do |widget|
|
|
110
|
+
next [] unless widget.is_a?(Hash)
|
|
111
|
+
|
|
112
|
+
[widget, *flatten_widgets(widget[:children] || widget['children'])]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def catalog_documents(mod)
|
|
117
|
+
documents = mod.infrastructure_documents + mod.application_documents + mod.domain_documents
|
|
118
|
+
documents.map do |document|
|
|
119
|
+
{
|
|
120
|
+
id: document[:id],
|
|
121
|
+
name: document[:name].to_s,
|
|
122
|
+
type: document[:type],
|
|
123
|
+
group: document[:route].to_s.split('/').first
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def project_documents(project)
|
|
129
|
+
project.all_units.filter_map do |unit|
|
|
130
|
+
document = project.parse_bson(unit)
|
|
131
|
+
type = document['$Type'].to_s
|
|
132
|
+
next if type.empty?
|
|
133
|
+
|
|
134
|
+
{ id: unit['UnitID'], type:, document: }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def security_payload(documents)
|
|
139
|
+
source = documents.find { _1[:type] == 'Security$ProjectSecurity' }&.fetch(:document)
|
|
140
|
+
return { configured: false, level: nil, user_roles: [] } unless source
|
|
141
|
+
|
|
142
|
+
roles = array(source['UserRoles']).map do |role|
|
|
143
|
+
{
|
|
144
|
+
name: role['Name'].to_s,
|
|
145
|
+
description: role['Description'].to_s,
|
|
146
|
+
module_roles: array(role['ModuleRoles']).map(&:to_s)
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
{ configured: true, level: source['SecurityLevel'], user_roles: roles }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def settings_payload(documents)
|
|
153
|
+
documents.filter { _1[:type].match?(SETTINGS_PATTERN) }.map do |entry|
|
|
154
|
+
document = entry[:document]
|
|
155
|
+
{
|
|
156
|
+
id: entry[:id],
|
|
157
|
+
type: entry[:type],
|
|
158
|
+
name: document['Name'].to_s,
|
|
159
|
+
values: scalar_values(document)
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def scalar_values(document)
|
|
165
|
+
document.each_with_object({}) do |(key, value), result|
|
|
166
|
+
next if key.start_with?('$') || value.is_a?(Hash) || value.is_a?(Array)
|
|
167
|
+
|
|
168
|
+
result[key] = value
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def array(value) = IO::BsonCodec.parse_array(value)[:items]
|
|
173
|
+
|
|
174
|
+
def blank_to_nil(value) = value.to_s.empty? ? nil : value
|
|
175
|
+
end
|
|
176
|
+
# rubocop:enable Metrics
|
|
177
|
+
end
|
|
178
|
+
end
|