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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'zip'
|
|
7
|
+
|
|
8
|
+
module Mxrb
|
|
9
|
+
# Fail-closed evidence gate for core and pluggable widgets used by an MPR.
|
|
10
|
+
# rubocop:disable Metrics
|
|
11
|
+
class WidgetCertification
|
|
12
|
+
CORE_WIDGET_TYPES = %w[
|
|
13
|
+
Forms$DivContainer Forms$LayoutGrid Forms$LayoutGridRow Forms$LayoutGridColumn Forms$Table
|
|
14
|
+
Forms$DynamicText Forms$Title Forms$ActionButton Forms$DataView Forms$ListView Forms$TextBox
|
|
15
|
+
Forms$TextArea Forms$DatePicker Forms$CheckBox Forms$RadioButtonGroup Forms$FileManager
|
|
16
|
+
Forms$GroupBox Forms$SnippetCallWidget Forms$Label Forms$TabControl Forms$StaticImageViewer
|
|
17
|
+
Forms$ScrollContainer Forms$Placeholder Forms$SidebarToggleButton Forms$Header
|
|
18
|
+
Forms$NavigationTree Forms$MenuBar Forms$SimpleMenuBar
|
|
19
|
+
].freeze
|
|
20
|
+
FAILURE_MARKERS = [
|
|
21
|
+
'Could not render widget',
|
|
22
|
+
'An error occurred, please contact your system administrator.',
|
|
23
|
+
'Executing runtime operation failed for security reasons',
|
|
24
|
+
'Out of context', 'out of context', 'must be placed inside',
|
|
25
|
+
'should be placed within', 'failed to load'
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
def initialize(path, browser_report: nil, build: true, mendix_home: nil)
|
|
29
|
+
@path = File.expand_path(path)
|
|
30
|
+
@browser_report = browser_report && File.expand_path(browser_report)
|
|
31
|
+
@build = build
|
|
32
|
+
@mendix_home = mendix_home
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run
|
|
36
|
+
started = monotonic
|
|
37
|
+
source = Compiler::SourceModel.read(@path)
|
|
38
|
+
inventory = widget_inventory(source)
|
|
39
|
+
compilation = compile_pages(source)
|
|
40
|
+
package_inventory = packages(inventory.fetch(:custom).map { _1.fetch(:id) })
|
|
41
|
+
native_build = @build ? build_web(source.version) : { status: 'not_run' }
|
|
42
|
+
browser = browser_evidence(inventory)
|
|
43
|
+
failures = failures(compilation, package_inventory, native_build, browser)
|
|
44
|
+
{
|
|
45
|
+
path: @path, mendix_version: source.version,
|
|
46
|
+
status: failures.empty? ? 'pass' : 'fail', failures:,
|
|
47
|
+
inventory:, packages: package_inventory, compilation:, native_build:, browser:,
|
|
48
|
+
elapsed_seconds: (monotonic - started).round(3)
|
|
49
|
+
}
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
{
|
|
52
|
+
path: @path, status: 'fail', failures: [e.message],
|
|
53
|
+
error: { class: e.class.name, message: e.message },
|
|
54
|
+
elapsed_seconds: (monotonic - started).round(3)
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def widget_inventory(source)
|
|
61
|
+
found = web_units(source).flat_map do |unit|
|
|
62
|
+
nested_widgets(unit.document).map do |widget|
|
|
63
|
+
custom = widget['$Type'] == 'CustomWidgets$CustomWidget'
|
|
64
|
+
{
|
|
65
|
+
page: "#{unit.module_name}.#{unit.document['Name']}", name: widget['Name'].to_s,
|
|
66
|
+
type: widget['$Type'].to_s, id: (custom ? custom_widget_id(source, widget) : nil)
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
core = found.reject { _1[:id] }.group_by { _1.fetch(:type) }.map do |type, entries|
|
|
71
|
+
{ type:, occurrences: entries.length, pages: entries.map { _1.fetch(:page) }.uniq.sort }
|
|
72
|
+
end
|
|
73
|
+
core.sort_by! { _1.fetch(:type) }
|
|
74
|
+
custom = found.select { _1[:id] }.group_by { _1.fetch(:id) }.map do |id, entries|
|
|
75
|
+
{ id:, occurrences: entries.length, pages: entries.map { _1.fetch(:page) }.uniq.sort }
|
|
76
|
+
end
|
|
77
|
+
custom.sort_by! { _1.fetch(:id) }
|
|
78
|
+
{ total: found.length, core:, custom: }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def nested_widgets(value, found = [])
|
|
82
|
+
case value
|
|
83
|
+
when Hash
|
|
84
|
+
type = value['$Type'].to_s
|
|
85
|
+
found << value if CORE_WIDGET_TYPES.include?(type) || type == 'CustomWidgets$CustomWidget'
|
|
86
|
+
value.each_value { nested_widgets(_1, found) }
|
|
87
|
+
when Array then value.each { nested_widgets(_1, found) }
|
|
88
|
+
end
|
|
89
|
+
found
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def custom_widget_id(source, widget)
|
|
93
|
+
direct = widget.dig('Type', 'WidgetId').to_s
|
|
94
|
+
return direct unless direct.empty?
|
|
95
|
+
|
|
96
|
+
pointer = IO::BsonCodec.extract_id(widget.dig('Object', 'TypePointer'))
|
|
97
|
+
source.document_index.values.find do |document|
|
|
98
|
+
IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == pointer
|
|
99
|
+
end&.fetch('WidgetId', '').to_s
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def compile_pages(source)
|
|
103
|
+
pages = web_units(source).map do |unit|
|
|
104
|
+
bundle = if unit.document['$Type'] == 'Forms$Layout'
|
|
105
|
+
Compiler::PageBundleCompiler.new(source).compile_layout(unit)
|
|
106
|
+
else
|
|
107
|
+
Compiler::PageBundleCompiler.new(source).compile(unit)
|
|
108
|
+
end
|
|
109
|
+
{
|
|
110
|
+
name: bundle.qualified_name, bytes: bundle.source.bytesize,
|
|
111
|
+
unsupported: bundle.unsupported_widgets,
|
|
112
|
+
unsupported_custom: bundle.unsupported_custom_widgets
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
{ status: pages.all? { _1[:unsupported].empty? && _1[:unsupported_custom].empty? } ? 'pass' : 'fail', pages: }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def packages(widget_ids)
|
|
119
|
+
paths = Dir.glob(File.join(File.dirname(@path), 'widgets', '*.mpk')).sort
|
|
120
|
+
entries = paths.map do |path|
|
|
121
|
+
modules = Zip::File.open(path) { _1.map(&:name).grep(/\.mjs\z/) }
|
|
122
|
+
ids = widget_ids.select { modules.include?("#{_1.tr('.', '/')}.mjs") }
|
|
123
|
+
next if ids.empty?
|
|
124
|
+
|
|
125
|
+
{ file: File.basename(path), sha256: Digest::SHA256.file(path).hexdigest, widget_ids: ids.sort }
|
|
126
|
+
rescue Zip::Error => e
|
|
127
|
+
{ file: File.basename(path), error: e.message, widget_ids: [] }
|
|
128
|
+
end.compact
|
|
129
|
+
covered = entries.flat_map { _1.fetch(:widget_ids) }.uniq
|
|
130
|
+
{
|
|
131
|
+
status: (widget_ids - covered).empty? ? 'pass' : 'fail', entries:,
|
|
132
|
+
missing_widget_ids: (widget_ids - covered).sort
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def build_web(version)
|
|
137
|
+
root = @mendix_home || File.join(Dir.home, '.local', 'share', 'mendix', version)
|
|
138
|
+
started = monotonic
|
|
139
|
+
Dir.mktmpdir('mxrb-widget-certification-') do |directory|
|
|
140
|
+
deployment = File.join(directory, 'deployment')
|
|
141
|
+
stages = Compiler::DeploymentMaterializer.new(
|
|
142
|
+
@path, deployment:, mendix_home: root
|
|
143
|
+
).materialize.stages.length
|
|
144
|
+
web = Compiler::WebBundleBuilder.new(
|
|
145
|
+
@path, deployment:, mendix_home: root
|
|
146
|
+
).build
|
|
147
|
+
return {
|
|
148
|
+
status: 'pass', stages:, files: web.files, bytes: web.bytes,
|
|
149
|
+
elapsed_ms: ((monotonic - started) * 1000).round(1)
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def browser_evidence(inventory)
|
|
155
|
+
required_ids = inventory.fetch(:custom).map { _1.fetch(:id) }
|
|
156
|
+
required_types = inventory.fetch(:core).map { _1.fetch(:type) }
|
|
157
|
+
return { status: 'missing', missing_widget_ids: required_ids, missing_widget_types: required_types } unless
|
|
158
|
+
@browser_report && File.file?(@browser_report)
|
|
159
|
+
|
|
160
|
+
report = JSON.parse(File.read(@browser_report))
|
|
161
|
+
declared = Array(report.dig('certification', 'widget_ids')).map(&:to_s).uniq
|
|
162
|
+
declared_types = Array(report.dig('certification', 'widget_types')).map(&:to_s).uniq
|
|
163
|
+
visible_failures = browser_failure_markers(report)
|
|
164
|
+
missing = required_ids - declared
|
|
165
|
+
missing_types = required_types - declared_types
|
|
166
|
+
status = if report['passed'] == true && visible_failures.empty? && missing.empty? && missing_types.empty?
|
|
167
|
+
'pass'
|
|
168
|
+
else
|
|
169
|
+
'fail'
|
|
170
|
+
end
|
|
171
|
+
{
|
|
172
|
+
status:, passed: report['passed'] == true, scenario: report['scenario'],
|
|
173
|
+
widget_ids: declared.sort, missing_widget_ids: missing.sort,
|
|
174
|
+
widget_types: declared_types.sort, missing_widget_types: missing_types.sort,
|
|
175
|
+
visible_failures:, console_errors: Array(report['console_errors']),
|
|
176
|
+
report: @browser_report
|
|
177
|
+
}
|
|
178
|
+
rescue JSON::ParserError => e
|
|
179
|
+
{ status: 'fail', error: "invalid browser report: #{e.message}" }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def browser_failure_markers(report)
|
|
183
|
+
elements = Array(report['pages']).flat_map { Array(_1.dig('snapshot', 'elements')) } +
|
|
184
|
+
Array(report.dig('failure_snapshot', 'elements'))
|
|
185
|
+
elements.filter_map do |element|
|
|
186
|
+
text = element['text'].to_s
|
|
187
|
+
text if FAILURE_MARKERS.any? { text.include?(_1) }
|
|
188
|
+
end.uniq
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def failures(compilation, package_inventory, native_build, browser)
|
|
192
|
+
[].tap do |result|
|
|
193
|
+
result << 'page compiler reported unsupported widgets' unless compilation[:status] == 'pass'
|
|
194
|
+
result << 'one or more pluggable widget modules are missing' unless package_inventory[:status] == 'pass'
|
|
195
|
+
result << 'native web bundle did not pass' unless native_build[:status] == 'pass'
|
|
196
|
+
result << 'passing browser evidence does not cover every widget contract' unless browser[:status] == 'pass'
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def web_units(source)
|
|
201
|
+
source.units_of('Forms$Page').select { source.web_page?(_1) } +
|
|
202
|
+
source.units_of('Forms$Layout').select { source.web_layout?(_1) }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
206
|
+
end
|
|
207
|
+
# rubocop:enable Metrics
|
|
208
|
+
end
|
data/lib/mxrb/writer.rb
CHANGED
|
@@ -47,7 +47,9 @@ module Mxrb
|
|
|
47
47
|
mpr = IO::MprFile.open(@path)
|
|
48
48
|
mpr.transaction do
|
|
49
49
|
apply(mpr, native_units)
|
|
50
|
+
write_native_compatibility(mpr, native_units)
|
|
50
51
|
mpr.write_architecture_definition(@definition)
|
|
52
|
+
mpr.write_ruby_app_sources(ruby_app_source_files) if @definition[:ruby_app_sources_path]
|
|
51
53
|
end
|
|
52
54
|
progress.advance(detail: "model transaction")
|
|
53
55
|
materialize_project_assets(asset_manifest)
|
|
@@ -62,6 +64,22 @@ module Mxrb
|
|
|
62
64
|
|
|
63
65
|
private
|
|
64
66
|
|
|
67
|
+
def ruby_app_source_files
|
|
68
|
+
path = @definition.fetch(:ruby_app_sources_path)
|
|
69
|
+
manifest = JSON.parse(File.read(path))
|
|
70
|
+
manifest.fetch("files").map do |file|
|
|
71
|
+
relative = safe_asset_path(file.fetch("path"))
|
|
72
|
+
contents = Base64.strict_decode64(file.fetch("contents"))
|
|
73
|
+
checksum = Digest::SHA256.hexdigest(contents)
|
|
74
|
+
raise SerializationError, "Ruby source checksum mismatch: #{relative}" \
|
|
75
|
+
unless checksum == file.fetch("sha256")
|
|
76
|
+
|
|
77
|
+
{ path: relative, contents:, sha256: checksum, mode: file.fetch("mode", 0o644) }
|
|
78
|
+
end
|
|
79
|
+
rescue JSON::ParserError, KeyError, ArgumentError => e
|
|
80
|
+
raise SerializationError, "invalid Ruby source manifest: #{e.message}"
|
|
81
|
+
end
|
|
82
|
+
|
|
65
83
|
def materialize_design_system
|
|
66
84
|
design_system = @definition[:design_system]
|
|
67
85
|
return unless design_system
|
|
@@ -352,7 +370,8 @@ module Mxrb
|
|
|
352
370
|
if existing
|
|
353
371
|
current = mpr.parse_contents(existing)
|
|
354
372
|
preserved = current.merge(doc).merge(
|
|
355
|
-
'$ID' => current['$ID'] || existing.fetch('UnitID'),
|
|
373
|
+
'$ID' => current['$ID'] || existing.fetch('UnitID'),
|
|
374
|
+
'$Type' => doc['$Type'] || doc[:'$Type'] || document.fetch(:type)
|
|
356
375
|
)
|
|
357
376
|
mpr.update_unit(existing.fetch('UnitID'), preserved)
|
|
358
377
|
next
|
|
@@ -431,26 +450,59 @@ module Mxrb
|
|
|
431
450
|
def upsert_native_unit(mpr, container_id, unit)
|
|
432
451
|
doc = unit.fetch("doc")
|
|
433
452
|
name = doc["Name"] || doc["name"]
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
453
|
+
requested_id = unit["unit_id"].to_s
|
|
454
|
+
requested_id = nil if requested_id.empty?
|
|
455
|
+
stable = mpr.unit(requested_id) if requested_id
|
|
456
|
+
existing = stable if stable && mpr.parse_contents(stable)["$Type"] == doc["$Type"]
|
|
457
|
+
candidates = mpr.children_of(container_id)
|
|
458
|
+
existing ||= if name.to_s.empty?
|
|
459
|
+
candidates.find { mpr.parse_contents(_1)["$Type"] == doc["$Type"] }
|
|
460
|
+
else
|
|
461
|
+
candidates.find do |raw|
|
|
462
|
+
existing_doc = mpr.parse_contents(raw)
|
|
463
|
+
(existing_doc["Name"] || existing_doc["name"]) == name &&
|
|
464
|
+
existing_doc["$Type"] == doc["$Type"]
|
|
465
|
+
end
|
|
466
|
+
end
|
|
442
467
|
if existing
|
|
443
468
|
current = mpr.parse_contents(existing)
|
|
444
469
|
preserved = {
|
|
445
470
|
"$ID" => current["$ID"] || existing.fetch("UnitID"),
|
|
446
471
|
"$Type" => doc["$Type"]
|
|
447
472
|
}.merge(current).merge(doc)
|
|
473
|
+
preserved["$ID"] = current["$ID"] || existing.fetch("UnitID") unless existing.equal?(stable)
|
|
448
474
|
mpr.update_unit(existing.fetch("UnitID"), preserved)
|
|
449
475
|
existing.fetch("UnitID")
|
|
450
476
|
else
|
|
451
477
|
containment = unit.fetch("containment")
|
|
452
|
-
|
|
478
|
+
inserted_id = stable ? SecureRandom.uuid : requested_id
|
|
479
|
+
inserted_doc = stable ? doc.merge("$ID" => inserted_id) : doc
|
|
480
|
+
mpr.insert_unit(
|
|
481
|
+
container_uuid: container_id,
|
|
482
|
+
containment_name: containment,
|
|
483
|
+
contents_doc: inserted_doc,
|
|
484
|
+
unit_uuid: inserted_id
|
|
485
|
+
)
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def write_native_compatibility(mpr, native_units)
|
|
490
|
+
mismatches = native_units.filter_map do |unit|
|
|
491
|
+
unit_id = unit["unit_id"].to_s
|
|
492
|
+
doc = unit.fetch("doc")
|
|
493
|
+
content_id = IO::BsonCodec.extract_id(doc["$ID"]).to_s
|
|
494
|
+
next if unit_id.empty? || content_id.empty? || unit_id == content_id
|
|
495
|
+
|
|
496
|
+
raw = mpr.unit(unit_id)
|
|
497
|
+
next unless raw
|
|
498
|
+
|
|
499
|
+
written = mpr.parse_contents(raw)
|
|
500
|
+
written_id = IO::BsonCodec.extract_id(written["$ID"]).to_s
|
|
501
|
+
next unless written_id == content_id && written["$Type"] == doc["$Type"]
|
|
502
|
+
|
|
503
|
+
{ unit_id: unit_id, content_id: content_id, type: doc["$Type"] }
|
|
453
504
|
end
|
|
505
|
+
mpr.write_legacy_unit_identity_mismatches(mismatches)
|
|
454
506
|
end
|
|
455
507
|
|
|
456
508
|
def write_project_security(mpr, root_id, security)
|
|
@@ -899,6 +951,7 @@ module Mxrb
|
|
|
899
951
|
doc.delete("__mxrb_preserve_native_body")
|
|
900
952
|
doc.delete("__mxrb_deep_structure_declared")
|
|
901
953
|
doc.delete("__mxrb_allow_concurrent_execution_declared")
|
|
954
|
+
doc.delete("__mxrb_apply_entity_access_declared")
|
|
902
955
|
doc.delete("__mxrb_mark_as_used_declared")
|
|
903
956
|
doc.delete("__mxrb_excluded_declared")
|
|
904
957
|
strip_nested_internal_keys(doc)
|
|
@@ -935,15 +988,61 @@ module Mxrb
|
|
|
935
988
|
def configure_pluggable_widget!(widget, options)
|
|
936
989
|
properties = custom_widget_properties(widget)
|
|
937
990
|
options.fetch(:properties, {}).each do |key, configured|
|
|
938
|
-
|
|
939
|
-
|
|
991
|
+
property = properties[key.to_s]
|
|
992
|
+
configure_custom_widget_value!(property, configured) if property
|
|
993
|
+
end
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
def configure_custom_widget_value!(property, configured)
|
|
997
|
+
value = property.fetch('Value')
|
|
998
|
+
unless configured.is_a?(Hash)
|
|
999
|
+
value['PrimitiveValue'] = configured.to_s unless configured.nil?
|
|
1000
|
+
return value
|
|
1001
|
+
end
|
|
1002
|
+
return value.merge!(stringify_keys(configured)) unless semantic_widget_value?(configured)
|
|
1003
|
+
|
|
1004
|
+
configured = configured.to_h { |key, item| [key.to_sym, item] }
|
|
1005
|
+
value['PrimitiveValue'] = configured[:primitive].to_s if configured.key?(:primitive)
|
|
1006
|
+
value['Expression'] = configured[:expression].to_s if configured.key?(:expression)
|
|
1007
|
+
value['Selection'] = configured[:selection].to_s if configured.key?(:selection)
|
|
1008
|
+
value['TextTemplate'] = client_template_doc(configured[:text]) if configured.key?(:text)
|
|
1009
|
+
value['AttributeRef'] = attribute_ref_doc(configured[:attribute]) if configured.key?(:attribute)
|
|
1010
|
+
value['EntityRef'] = indirect_entity_ref_doc(configured[:association]) if configured.key?(:association)
|
|
1011
|
+
configure_widget_data_source!(value, configured[:data_source]) if configured.key?(:data_source)
|
|
1012
|
+
configure_widget_children!(value, configured[:widgets]) if configured.key?(:widgets)
|
|
1013
|
+
configure_widget_objects!(property, value, configured[:objects]) if configured.key?(:objects)
|
|
1014
|
+
value
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
def semantic_widget_value?(value)
|
|
1018
|
+
value.is_a?(Hash) && (value.keys.map(&:to_sym) & %i[
|
|
1019
|
+
primitive expression selection text attribute association data_source widgets objects
|
|
1020
|
+
]).any?
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
def configure_widget_data_source!(value, configured)
|
|
1024
|
+
options = configured.is_a?(Hash) ? configured : { entity: configured }
|
|
1025
|
+
value['DataSource'] = custom_xpath_source_doc(
|
|
1026
|
+
options.fetch(:entity), xpath: options.fetch(:xpath, '')
|
|
1027
|
+
)
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
def configure_widget_children!(value, widgets)
|
|
1031
|
+
children = (widgets.is_a?(Array) ? widgets : [widgets]).compact.map { widget_doc(_1) }
|
|
1032
|
+
value['Widgets'] = IO::BsonCodec.build_array(children, marker: 2)
|
|
1033
|
+
end
|
|
940
1034
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1035
|
+
def configure_widget_objects!(property, value, configurations)
|
|
1036
|
+
object_type = property.dig('ValueType', 'ObjectType')
|
|
1037
|
+
objects = Array(configurations).map do |configuration|
|
|
1038
|
+
object = custom_widget_object_doc(object_type)
|
|
1039
|
+
nested = widget_object_properties(object_type, object)
|
|
1040
|
+
configuration.each do |key, configured|
|
|
1041
|
+
configure_custom_widget_value!(nested.fetch(key.to_s), configured)
|
|
945
1042
|
end
|
|
1043
|
+
object
|
|
946
1044
|
end
|
|
1045
|
+
value['Objects'] = IO::BsonCodec.build_array(objects, marker: 2)
|
|
947
1046
|
end
|
|
948
1047
|
|
|
949
1048
|
def stringify_keys(value)
|
|
@@ -987,6 +1086,15 @@ module Mxrb
|
|
|
987
1086
|
if options[:entity] && properties["datasource"]
|
|
988
1087
|
properties["datasource"]["Value"]["DataSource"] = custom_xpath_source_doc(options[:entity])
|
|
989
1088
|
end
|
|
1089
|
+
if options[:selection] && properties['itemSelection']
|
|
1090
|
+
properties.dig('itemSelection', 'Value')['Selection'] = options[:selection].to_s
|
|
1091
|
+
end
|
|
1092
|
+
if properties['filtersPlaceholder'] && options[:filters]
|
|
1093
|
+
configured_filters = options[:filters].is_a?(Array) ? options[:filters] : [options[:filters]]
|
|
1094
|
+
filters = configured_filters.map { widget_doc(_1) }
|
|
1095
|
+
properties.dig('filtersPlaceholder', 'Value')['Widgets'] =
|
|
1096
|
+
IO::BsonCodec.build_array(filters, marker: 2)
|
|
1097
|
+
end
|
|
990
1098
|
return unless properties["columns"] && options[:columns]
|
|
991
1099
|
|
|
992
1100
|
column_type = properties["columns"].dig("ValueType", "ObjectType")
|
|
@@ -1052,16 +1160,20 @@ module Mxrb
|
|
|
1052
1160
|
end
|
|
1053
1161
|
|
|
1054
1162
|
def custom_widget_properties(widget)
|
|
1055
|
-
|
|
1163
|
+
widget_object_properties(widget.dig('Type', 'ObjectType'), widget['Object'])
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
def widget_object_properties(object_type, object)
|
|
1167
|
+
types = array_items(object_type&.fetch('PropertyTypes', nil)).to_h do |type|
|
|
1056
1168
|
[IO::BsonCodec.extract_id(type["$ID"]), type]
|
|
1057
1169
|
end
|
|
1058
|
-
array_items(
|
|
1170
|
+
array_items(object&.fetch('Properties', nil)).to_h do |property|
|
|
1059
1171
|
type = types[IO::BsonCodec.extract_id(property["TypePointer"])]
|
|
1060
1172
|
[type && type["PropertyKey"], property.merge("ValueType" => type && type["ValueType"])]
|
|
1061
1173
|
end.compact
|
|
1062
1174
|
end
|
|
1063
1175
|
|
|
1064
|
-
def custom_xpath_source_doc(entity)
|
|
1176
|
+
def custom_xpath_source_doc(entity, xpath: '')
|
|
1065
1177
|
{
|
|
1066
1178
|
"$ID" => SecureRandom.uuid, "$Type" => "CustomWidgets$CustomWidgetXPathSource",
|
|
1067
1179
|
"EntityRef" => {
|
|
@@ -1073,19 +1185,24 @@ module Mxrb
|
|
|
1073
1185
|
"$ID" => SecureRandom.uuid, "$Type" => "Forms$GridSortBar",
|
|
1074
1186
|
"SortItems" => IO::BsonCodec.build_array([], marker: 2)
|
|
1075
1187
|
},
|
|
1076
|
-
"SourceVariable" => nil, "XPathConstraint" =>
|
|
1188
|
+
"SourceVariable" => nil, "XPathConstraint" => xpath.to_s
|
|
1077
1189
|
}
|
|
1078
1190
|
end
|
|
1079
1191
|
|
|
1080
1192
|
def data_grid2_column_doc(object_type, column, entity: nil)
|
|
1081
1193
|
attribute = column[:attribute]
|
|
1082
1194
|
attribute = "#{entity}.#{attribute}" if entity && attribute && !attribute.include?('.')
|
|
1195
|
+
filters = column[:filter].is_a?(Array) ? column[:filter] : [column[:filter]].compact
|
|
1083
1196
|
overrides = {
|
|
1084
1197
|
"showContentAs" => { "PrimitiveValue" => "attribute" },
|
|
1085
1198
|
"attribute" => { "AttributeRef" => attribute_ref_doc(attribute) },
|
|
1086
1199
|
"header" => { "TextTemplate" => client_template_doc(column[:caption] || column[:name]) },
|
|
1087
1200
|
"tooltip" => { "TextTemplate" => empty_client_template_doc },
|
|
1088
|
-
"filter" => {
|
|
1201
|
+
"filter" => {
|
|
1202
|
+
"Widgets" => IO::BsonCodec.build_array(
|
|
1203
|
+
filters.map { widget_doc(_1) }, marker: 2
|
|
1204
|
+
)
|
|
1205
|
+
}
|
|
1089
1206
|
}
|
|
1090
1207
|
custom_widget_object_doc(object_type, overrides)
|
|
1091
1208
|
end
|
|
@@ -1131,6 +1248,7 @@ module Mxrb
|
|
|
1131
1248
|
def preserve_flow_metadata(merged, existing, generated)
|
|
1132
1249
|
{
|
|
1133
1250
|
"AllowConcurrentExecution" => "__mxrb_allow_concurrent_execution_declared",
|
|
1251
|
+
"ApplyEntityAccess" => "__mxrb_apply_entity_access_declared",
|
|
1134
1252
|
"MarkAsUsed" => "__mxrb_mark_as_used_declared",
|
|
1135
1253
|
"Excluded" => "__mxrb_excluded_declared"
|
|
1136
1254
|
}.each do |field, declaration|
|
|
@@ -1459,7 +1577,7 @@ module Mxrb
|
|
|
1459
1577
|
end
|
|
1460
1578
|
|
|
1461
1579
|
def attribute_doc(attr, previous)
|
|
1462
|
-
storage_type = Model::Attribute::TYPE_MAP.fetch(attr.fetch(:type))
|
|
1580
|
+
storage_type = Model::Attribute::TYPE_MAP.fetch(attr.fetch(:type).to_sym)
|
|
1463
1581
|
type_key = native_existing_key(previous, "type", "Type", "newType", "NewType") || "NewType"
|
|
1464
1582
|
value_key = native_existing_key(previous, "value", "Value") || "Value"
|
|
1465
1583
|
name_key = native_key(previous, "name", "Name")
|
|
@@ -1471,9 +1589,11 @@ module Mxrb
|
|
|
1471
1589
|
{ "$ID" => SecureRandom.uuid, "$Type" => storage_type }
|
|
1472
1590
|
end
|
|
1473
1591
|
type_doc = type_doc.merge("Enumeration" => attr[:enumeration].to_s) if attr[:enumeration]
|
|
1474
|
-
|
|
1592
|
+
string_type = storage_type == Model::Attribute::TYPE_MAP[:string]
|
|
1593
|
+
if attr[:length] || (string_type && !previous_type&.key?('length') && !previous_type&.key?('Length'))
|
|
1475
1594
|
length_key = native_key(previous_type, 'length', 'Length')
|
|
1476
|
-
|
|
1595
|
+
length = attr[:length] || Model::Attribute::DEFAULT_STRING_LENGTH
|
|
1596
|
+
type_doc = type_doc.merge(length_key => Integer(length))
|
|
1477
1597
|
end
|
|
1478
1598
|
if attr.key?(:localize_date)
|
|
1479
1599
|
localize_key = native_key(previous_type, 'localizeDate', 'LocalizeDate')
|
|
@@ -2259,6 +2379,48 @@ module Mxrb
|
|
|
2259
2379
|
}
|
|
2260
2380
|
end
|
|
2261
2381
|
|
|
2382
|
+
# A project created outside Studio Pro may not have the pluggable-widget
|
|
2383
|
+
# package available yet. These standard-shaped fallback fields preserve
|
|
2384
|
+
# the concise Ruby projection until Studio Pro can hydrate the widget.
|
|
2385
|
+
def configure_fallback_data_grid!(doc, options)
|
|
2386
|
+
entity = options[:entity].to_s
|
|
2387
|
+
columns = Array(options[:columns]).map do |column|
|
|
2388
|
+
attribute = column[:attribute].to_s
|
|
2389
|
+
attribute = "#{entity}.#{attribute}" if !entity.empty? && !attribute.empty? && !attribute.include?('.')
|
|
2390
|
+
{
|
|
2391
|
+
'$ID' => SecureRandom.uuid, '$Type' => 'Forms$DataGridColumn',
|
|
2392
|
+
'Name' => column[:name].to_s,
|
|
2393
|
+
'AttributeRef' => attribute_ref_doc(attribute),
|
|
2394
|
+
'Caption' => text_doc(column[:caption] || column[:name])
|
|
2395
|
+
}
|
|
2396
|
+
end
|
|
2397
|
+
toolbar = Array(options.dig(:toolbar, :buttons)).map do |button|
|
|
2398
|
+
kind = { new: 'New', delete: 'Delete', search: 'Search', export: 'ExportToExcel' }
|
|
2399
|
+
.fetch(button[:type].to_sym)
|
|
2400
|
+
{
|
|
2401
|
+
'$ID' => SecureRandom.uuid, '$Type' => "Forms$Grid#{kind}Button",
|
|
2402
|
+
'Caption' => text_doc(button[:caption].to_s)
|
|
2403
|
+
}
|
|
2404
|
+
end
|
|
2405
|
+
doc['DataSource'] = {
|
|
2406
|
+
'$ID' => SecureRandom.uuid, '$Type' => 'Forms$DatabaseSource', 'Entity' => entity
|
|
2407
|
+
}
|
|
2408
|
+
doc['Columns'] = IO::BsonCodec.build_array(columns)
|
|
2409
|
+
doc['ToolBar'] = {
|
|
2410
|
+
'$ID' => SecureRandom.uuid, '$Type' => 'Forms$GridControlBar',
|
|
2411
|
+
'Buttons' => IO::BsonCodec.build_array(toolbar)
|
|
2412
|
+
}
|
|
2413
|
+
doc
|
|
2414
|
+
end
|
|
2415
|
+
|
|
2416
|
+
def configure_fallback_combo_box!(doc, options)
|
|
2417
|
+
doc['AttributePath'] = options[:attribute].to_s if options[:attribute]
|
|
2418
|
+
doc['LabelText'] = text_doc(options[:caption]) if options.key?(:caption)
|
|
2419
|
+
doc['SelectorType'] = options[:__kind].to_sym == :reference_selector ? 'Reference' : 'Enumeration'
|
|
2420
|
+
doc['DisplayAttribute'] = options[:display_attribute].to_s if options[:display_attribute]
|
|
2421
|
+
doc
|
|
2422
|
+
end
|
|
2423
|
+
|
|
2262
2424
|
def combo_box_descriptor
|
|
2263
2425
|
{
|
|
2264
2426
|
id: "com.mendix.widget.web.combobox.Combobox", name: "Combo box",
|
|
@@ -2305,6 +2467,15 @@ module Mxrb
|
|
|
2305
2467
|
configure_data_grid2!(doc, options) if descriptor.fetch(:id) == data_grid2_descriptor[:id]
|
|
2306
2468
|
configure_combo_box!(doc, options) if descriptor.fetch(:id) == combo_box_descriptor[:id]
|
|
2307
2469
|
configure_pluggable_widget!(doc, options) if widget.fetch(:type) == :pluggable_widget
|
|
2470
|
+
if definition.nil? && descriptor.fetch(:id) == data_grid2_descriptor[:id]
|
|
2471
|
+
configure_fallback_data_grid!(doc, options)
|
|
2472
|
+
end
|
|
2473
|
+
if definition.nil? && descriptor.fetch(:id) == combo_box_descriptor[:id]
|
|
2474
|
+
configure_fallback_combo_box!(doc, options)
|
|
2475
|
+
end
|
|
2476
|
+
widget.fetch(:events, []).each do |event|
|
|
2477
|
+
doc[event_property(event.fetch(:event))] = client_action_doc(event)
|
|
2478
|
+
end
|
|
2308
2479
|
doc
|
|
2309
2480
|
end
|
|
2310
2481
|
|
|
@@ -2465,6 +2636,8 @@ module Mxrb
|
|
|
2465
2636
|
case event.fetch(:kind).to_sym
|
|
2466
2637
|
when :action
|
|
2467
2638
|
native_action_doc(event.fetch(:handler))
|
|
2639
|
+
when :page
|
|
2640
|
+
form_action_doc(event.fetch(:handler))
|
|
2468
2641
|
when :nanoflow
|
|
2469
2642
|
{
|
|
2470
2643
|
"$ID" => SecureRandom.uuid,
|
|
@@ -2584,6 +2757,7 @@ module Mxrb
|
|
|
2584
2757
|
body_declared = !flow[:body].nil? || !flow[:return_expression].nil?
|
|
2585
2758
|
return_var_name = flow[:return_variable_name] || "ReturnValue"
|
|
2586
2759
|
allow_concurrent = flow[:allow_concurrent_execution]
|
|
2760
|
+
apply_entity_access = flow[:apply_entity_access]
|
|
2587
2761
|
mark_as_used = flow[:mark_as_used]
|
|
2588
2762
|
excluded = flow[:excluded]
|
|
2589
2763
|
|
|
@@ -2601,6 +2775,7 @@ module Mxrb
|
|
|
2601
2775
|
"Name" => flow_name, "Documentation" => flow.fetch(:documentation, ""),
|
|
2602
2776
|
"ReturnVariableName" => return_var_name,
|
|
2603
2777
|
"AllowConcurrentExecution" => allow_concurrent.nil? ? true : allow_concurrent,
|
|
2778
|
+
"ApplyEntityAccess" => apply_entity_access.nil? ? false : apply_entity_access,
|
|
2604
2779
|
"MarkAsUsed" => mark_as_used.nil? ? false : mark_as_used,
|
|
2605
2780
|
"Excluded" => excluded.nil? ? false : excluded,
|
|
2606
2781
|
"AllowedModuleRoles" => IO::BsonCodec.build_array(Array(flow[:allowed_roles]), marker: 1),
|
|
@@ -2608,6 +2783,7 @@ module Mxrb
|
|
|
2608
2783
|
"__mxrb_body_declared" => body_declared,
|
|
2609
2784
|
"__mxrb_preserve_native_body" => flow[:preserve_native_body] == true,
|
|
2610
2785
|
"__mxrb_allow_concurrent_execution_declared" => !allow_concurrent.nil?,
|
|
2786
|
+
"__mxrb_apply_entity_access_declared" => !apply_entity_access.nil?,
|
|
2611
2787
|
"__mxrb_mark_as_used_declared" => !mark_as_used.nil?,
|
|
2612
2788
|
"__mxrb_excluded_declared" => !excluded.nil?,
|
|
2613
2789
|
"MicroflowReturnType" => microflow_data_type_doc(flow[:return_type], module_name),
|
data/lib/mxrb.rb
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "mxrb/version"
|
|
4
4
|
require_relative "mxrb/errors"
|
|
5
|
+
require_relative "mxrb/http/server"
|
|
5
6
|
require_relative "mxrb/progress"
|
|
6
7
|
require_relative "mxrb/doctor"
|
|
7
8
|
require_relative "mxrb/benchmark"
|
|
8
9
|
require_relative "mxrb/project_lifecycle"
|
|
10
|
+
require_relative "mxrb/environment"
|
|
9
11
|
require_relative "mxrb/scaffold/templates"
|
|
10
12
|
require_relative "mxrb/scaffold/page_templates"
|
|
11
13
|
require_relative "mxrb/initializer"
|
|
@@ -16,8 +18,11 @@ require_relative "mxrb/scaffold/recipes"
|
|
|
16
18
|
require_relative "mxrb/scaffold/generator"
|
|
17
19
|
require_relative "mxrb/scaffold/help"
|
|
18
20
|
require_relative "mxrb/scaffold/cli"
|
|
21
|
+
require_relative "mxrb/cli/help"
|
|
22
|
+
require_relative "mxrb/cli/release"
|
|
19
23
|
require_relative "mxrb/io/bson_codec"
|
|
20
24
|
require_relative "mxrb/widget_package"
|
|
25
|
+
require_relative "mxrb/widget_certification"
|
|
21
26
|
require_relative "mxrb/frontend/migrator"
|
|
22
27
|
require_relative "mxrb/io/mxunit_codec"
|
|
23
28
|
require_relative "mxrb/io/mpr_file"
|
|
@@ -36,6 +41,13 @@ require_relative "mxrb/model/design_migration"
|
|
|
36
41
|
require_relative "mxrb/model/module"
|
|
37
42
|
require_relative "mxrb/model/connector"
|
|
38
43
|
require_relative "mxrb/model/project"
|
|
44
|
+
require_relative "mxrb/modeler/catalog"
|
|
45
|
+
require_relative "mxrb/domain_diagram"
|
|
46
|
+
require_relative "mxrb/domain_diagram/lifecycle"
|
|
47
|
+
require_relative "mxrb/uml/class_diagram"
|
|
48
|
+
require_relative "mxrb/uml/activity_diagram"
|
|
49
|
+
require_relative "mxrb/uml/sequence_diagram"
|
|
50
|
+
require_relative "mxrb/uml/server"
|
|
39
51
|
require_relative "mxrb/protocols"
|
|
40
52
|
require_relative "mxrb/oql"
|
|
41
53
|
require_relative "mxrb/oql/plan_analyzer"
|
|
@@ -116,6 +128,7 @@ require_relative "mxrb/compiler/image_bundle_compiler"
|
|
|
116
128
|
require_relative "mxrb/compiler/generic_widget_bundle_compiler"
|
|
117
129
|
require_relative "mxrb/compiler/combo_box_bundle_compiler"
|
|
118
130
|
require_relative "mxrb/compiler/legacy_data_grid_compiler"
|
|
131
|
+
require_relative "mxrb/compiler/legacy_custom_widget_compiler"
|
|
119
132
|
require_relative "mxrb/compiler/legacy_page_builder"
|
|
120
133
|
require_relative "mxrb/compiler/page_bundle_compiler"
|
|
121
134
|
require_relative "mxrb/compiler/compatibility_analyzer"
|
|
@@ -126,6 +139,14 @@ require_relative "mxrb/widget_synchronizer"
|
|
|
126
139
|
require_relative "mxrb/runtime/docker_workspace"
|
|
127
140
|
require_relative "mxrb/runtime/executor"
|
|
128
141
|
require_relative "mxrb/runtime/native"
|
|
142
|
+
require_relative "mxrb/runtime/schema_migrator"
|
|
143
|
+
require_relative "mxrb/runtime/sqlite_store"
|
|
144
|
+
require_relative "mxrb/runtime/access_control"
|
|
145
|
+
require_relative "mxrb/runtime/shared_store"
|
|
146
|
+
require_relative "mxrb/runtime/scheduler"
|
|
147
|
+
require_relative "mxrb/ruby_app"
|
|
148
|
+
require_relative "mxrb/ruby_app/preset"
|
|
149
|
+
require_relative "mxrb/ruby_app/exporter"
|
|
129
150
|
require_relative "mxrb/runtime/docker_executor"
|
|
130
151
|
require_relative "mxrb/runtime/clipboard"
|
|
131
152
|
require_relative "mxrb/runtime/database_workspace"
|
|
@@ -169,9 +190,13 @@ module Mxrb
|
|
|
169
190
|
#
|
|
170
191
|
def self.define(path, &block)
|
|
171
192
|
output = ENV.fetch("MXRB_OUTPUT_PATH", path)
|
|
172
|
-
Dsl::Builder.new(output).tap { _1.instance_eval(&block) }.build!
|
|
193
|
+
builder = Dsl::Builder.new(output).tap { _1.instance_eval(&block) }.build!
|
|
194
|
+
@last_defined_path = builder.path
|
|
195
|
+
builder
|
|
173
196
|
end
|
|
174
197
|
|
|
198
|
+
def self.last_defined_path = @last_defined_path
|
|
199
|
+
|
|
175
200
|
def self.validate(path)
|
|
176
201
|
Integrity::Validator.new(path).validate
|
|
177
202
|
end
|