mxrb 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.de-DE.md +108 -0
- data/README.md +172 -0
- data/README.pt-BR.md +109 -0
- data/bin/mxrb +1613 -0
- data/docs/README.md +10 -0
- data/docs/de-DE/README.md +24 -0
- data/docs/de-DE/architectural-patterns.md +26 -0
- data/docs/de-DE/architectural-standard.md +48 -0
- data/docs/de-DE/architecture.md +76 -0
- data/docs/de-DE/compiler.md +162 -0
- data/docs/de-DE/conventions.md +19 -0
- data/docs/de-DE/design-system.md +52 -0
- data/docs/de-DE/entity-dsl.md +53 -0
- data/docs/de-DE/native-runtime-quality-report.md +74 -0
- data/docs/de-DE/oql-sql.md +211 -0
- data/docs/de-DE/platform-api-integration.md +36 -0
- data/docs/de-DE/platform-operations.md +243 -0
- data/docs/de-DE/project-structure.md +32 -0
- data/docs/de-DE/ruby-first-roadmap.md +116 -0
- data/docs/de-DE/scaffolds.md +59 -0
- data/docs/de-DE/semantic-refactoring.md +56 -0
- data/docs/de-DE/team-server.md +33 -0
- data/docs/de-DE/validation-matrix.md +64 -0
- data/docs/de-DE/vetclinic-acceptance.md +24 -0
- data/docs/de-DE/writing.md +138 -0
- data/docs/en-US/README.md +24 -0
- data/docs/en-US/architectural-patterns.md +151 -0
- data/docs/en-US/architectural-standard.md +89 -0
- data/docs/en-US/architecture.md +170 -0
- data/docs/en-US/compiler.md +157 -0
- data/docs/en-US/conventions.md +82 -0
- data/docs/en-US/design-system.md +163 -0
- data/docs/en-US/entity-dsl.md +53 -0
- data/docs/en-US/native-runtime-quality-report.md +73 -0
- data/docs/en-US/oql-sql.md +201 -0
- data/docs/en-US/platform-api-integration.md +36 -0
- data/docs/en-US/platform-operations.md +244 -0
- data/docs/en-US/project-structure.md +49 -0
- data/docs/en-US/ruby-first-roadmap.md +186 -0
- data/docs/en-US/scaffolds.md +57 -0
- data/docs/en-US/semantic-refactoring.md +198 -0
- data/docs/en-US/team-server.md +32 -0
- data/docs/en-US/validation-matrix.md +257 -0
- data/docs/en-US/vetclinic-acceptance.md +23 -0
- data/docs/en-US/writing.md +655 -0
- data/docs/pt-BR/README.md +24 -0
- data/docs/pt-BR/architectural-patterns.md +25 -0
- data/docs/pt-BR/architectural-standard.md +533 -0
- data/docs/pt-BR/architecture.md +300 -0
- data/docs/pt-BR/compiler.md +162 -0
- data/docs/pt-BR/conventions.md +19 -0
- data/docs/pt-BR/design-system.md +65 -0
- data/docs/pt-BR/entity-dsl.md +116 -0
- data/docs/pt-BR/native-runtime-quality-report.md +73 -0
- data/docs/pt-BR/oql-sql.md +208 -0
- data/docs/pt-BR/platform-api-integration.md +36 -0
- data/docs/pt-BR/platform-operations.md +260 -0
- data/docs/pt-BR/project-structure.md +32 -0
- data/docs/pt-BR/ruby-first-roadmap.md +239 -0
- data/docs/pt-BR/scaffolds.md +104 -0
- data/docs/pt-BR/semantic-refactoring.md +55 -0
- data/docs/pt-BR/team-server.md +59 -0
- data/docs/pt-BR/validation-matrix.md +86 -0
- data/docs/pt-BR/vetclinic-acceptance.md +50 -0
- data/docs/pt-BR/writing.md +177 -0
- data/examples/sudoku_evaluation.rb +14 -0
- data/examples/sudoku_functional_test.rb +28 -0
- data/lib/mxrb/architecture/graph.rb +124 -0
- data/lib/mxrb/architecture/validator.rb +98 -0
- data/lib/mxrb/benchmark.rb +33 -0
- data/lib/mxrb/compare.rb +390 -0
- data/lib/mxrb/compiler/adapter.rb +96 -0
- data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
- data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
- data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
- data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
- data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
- data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
- data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
- data/lib/mxrb/compiler/constants_materializer.rb +111 -0
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
- data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
- data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
- data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
- data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
- data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
- data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
- data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
- data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
- data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
- data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
- data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
- data/lib/mxrb/compiler/mda.rb +99 -0
- data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
- data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
- data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
- data/lib/mxrb/compiler/model_package.rb +116 -0
- data/lib/mxrb/compiler/model_values.rb +67 -0
- data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
- data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
- data/lib/mxrb/compiler/packager.rb +100 -0
- data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
- data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
- data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
- data/lib/mxrb/compiler/portable_packager.rb +356 -0
- data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
- data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
- data/lib/mxrb/compiler/project_materializer.rb +139 -0
- data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
- data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
- data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
- data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
- data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
- data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
- data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
- data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
- data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
- data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
- data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
- data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
- data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
- data/lib/mxrb/compiler/security_materializer.rb +104 -0
- data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
- data/lib/mxrb/compiler/settings_materializer.rb +25 -0
- data/lib/mxrb/compiler/source_model.rb +128 -0
- data/lib/mxrb/compiler/system_model_seed.rb +88 -0
- data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
- data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
- data/lib/mxrb/compiler/translation_materializer.rb +95 -0
- data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
- data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
- data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
- data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
- data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
- data/lib/mxrb/doctor.rb +116 -0
- data/lib/mxrb/dsl/builder.rb +1636 -0
- data/lib/mxrb/errors.rb +18 -0
- data/lib/mxrb/evaluation.rb +131 -0
- data/lib/mxrb/exporter.rb +1716 -0
- data/lib/mxrb/frontend/migrator.rb +775 -0
- data/lib/mxrb/functional.rb +307 -0
- data/lib/mxrb/github/annotator.rb +161 -0
- data/lib/mxrb/initializer.rb +257 -0
- data/lib/mxrb/integrity/validator.rb +136 -0
- data/lib/mxrb/io/bson_codec.rb +145 -0
- data/lib/mxrb/io/mpr_file.rb +558 -0
- data/lib/mxrb/io/mxunit_codec.rb +42 -0
- data/lib/mxrb/marketplace.rb +380 -0
- data/lib/mxrb/model/association.rb +98 -0
- data/lib/mxrb/model/attribute.rb +100 -0
- data/lib/mxrb/model/connector.rb +17 -0
- data/lib/mxrb/model/design_materializer.rb +133 -0
- data/lib/mxrb/model/design_migration.rb +126 -0
- data/lib/mxrb/model/design_system.rb +111 -0
- data/lib/mxrb/model/domain_model.rb +51 -0
- data/lib/mxrb/model/entity.rb +180 -0
- data/lib/mxrb/model/menu.rb +38 -0
- data/lib/mxrb/model/microflow.rb +100 -0
- data/lib/mxrb/model/module.rb +142 -0
- data/lib/mxrb/model/navigation.rb +127 -0
- data/lib/mxrb/model/page.rb +410 -0
- data/lib/mxrb/model/project.rb +187 -0
- data/lib/mxrb/model/unit.rb +48 -0
- data/lib/mxrb/module_initializer.rb +75 -0
- data/lib/mxrb/official_marketplace/content_api.rb +292 -0
- data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
- data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
- data/lib/mxrb/official_marketplace.rb +720 -0
- data/lib/mxrb/oql/analyzer.rb +155 -0
- data/lib/mxrb/oql/index_advisor.rb +85 -0
- data/lib/mxrb/oql/plan_analyzer.rb +185 -0
- data/lib/mxrb/oql/server.rb +132 -0
- data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
- data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
- data/lib/mxrb/oql/workload_analyzer.rb +175 -0
- data/lib/mxrb/oql/workload_baseline.rb +60 -0
- data/lib/mxrb/oql.rb +424 -0
- data/lib/mxrb/project_lifecycle.rb +76 -0
- data/lib/mxrb/protocols/plan.rb +86 -0
- data/lib/mxrb/protocols.rb +123 -0
- data/lib/mxrb/runtime/database_workspace.rb +677 -0
- data/lib/mxrb/runtime/docker_executor.rb +41 -0
- data/lib/mxrb/runtime/docker_workspace.rb +29 -0
- data/lib/mxrb/runtime/executor.rb +169 -0
- data/lib/mxrb/runtime/java_locator.rb +26 -0
- data/lib/mxrb/runtime/native.rb +474 -0
- data/lib/mxrb/runtime/sql_server_database.rb +146 -0
- data/lib/mxrb/runtime/toolchain.rb +82 -0
- data/lib/mxrb/scaffold/cli.rb +113 -0
- data/lib/mxrb/scaffold/generator.rb +246 -0
- data/lib/mxrb/scaffold/help.rb +80 -0
- data/lib/mxrb/scaffold/page_templates.rb +54 -0
- data/lib/mxrb/scaffold/recipes.rb +198 -0
- data/lib/mxrb/scaffold/registry.rb +82 -0
- data/lib/mxrb/scaffold/templates.rb +723 -0
- data/lib/mxrb/scaffold/transaction.rb +84 -0
- data/lib/mxrb/schema/tables.rb +55 -0
- data/lib/mxrb/semantic/analyzer.rb +473 -0
- data/lib/mxrb/semantic/batch_plan.rb +68 -0
- data/lib/mxrb/semantic/domain_mutator.rb +419 -0
- data/lib/mxrb/semantic/embedder.rb +51 -0
- data/lib/mxrb/semantic/extractor.rb +290 -0
- data/lib/mxrb/semantic/index.rb +623 -0
- data/lib/mxrb/semantic/inliner.rb +216 -0
- data/lib/mxrb/semantic/mover.rb +153 -0
- data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
- data/lib/mxrb/semantic/remover.rb +70 -0
- data/lib/mxrb/semantic/renamer.rb +211 -0
- data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
- data/lib/mxrb/semantic/vec_store.rb +75 -0
- data/lib/mxrb/team_server.rb +390 -0
- data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
- data/lib/mxrb/templates/project/11.12.1.json +32 -0
- data/lib/mxrb/templates/project/6.10.8.json +50 -0
- data/lib/mxrb/templates/project/7.17.0.json +50 -0
- data/lib/mxrb/templates/project/7.5.0.json +50 -0
- data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
- data/lib/mxrb/version.rb +5 -0
- data/lib/mxrb/widget_package.rb +338 -0
- data/lib/mxrb/widget_synchronizer.rb +31 -0
- data/lib/mxrb/writer.rb +3775 -0
- data/lib/mxrb.rb +188 -0
- data/marketplace/catalog.json +10 -0
- data/marketplace/modules/shared-kernel/domain/README.md +4 -0
- data/marketplace/modules/shared-kernel/module.rb +5 -0
- data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
- metadata +447 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
# Compiles Java and JavaScript action signatures consumed by the Runtime model.
|
|
6
|
+
class CodeActionDocumentCompiler
|
|
7
|
+
include ModelValues
|
|
8
|
+
include CodeActionTypeCompiler
|
|
9
|
+
|
|
10
|
+
TYPES = %w[JavaActions$JavaAction JavaScriptActions$JavaScriptAction].freeze
|
|
11
|
+
|
|
12
|
+
def compile(unit)
|
|
13
|
+
source = unit.document
|
|
14
|
+
case source['$Type']
|
|
15
|
+
when 'JavaActions$JavaAction' then java_action(source, unit.module_name)
|
|
16
|
+
when 'JavaScriptActions$JavaScriptAction' then javascript_action(source, unit.module_name)
|
|
17
|
+
else raise CompilationError, "unsupported code action #{source['$Type']}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def java_action(source, module_name)
|
|
24
|
+
{
|
|
25
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'],
|
|
26
|
+
'Parameters' => array(source['Parameters']).map { java_parameter(_1) },
|
|
27
|
+
'Name' => source['Name'], 'QualifiedName' => qualified_name(module_name, source['Name']),
|
|
28
|
+
'ReturnType' => code_action_type(source['JavaReturnType'])
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def java_parameter(source)
|
|
33
|
+
{
|
|
34
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'], 'Name' => source['Name'],
|
|
35
|
+
'Type' => java_parameter_type(source['ParameterType'])
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def java_parameter_type(source)
|
|
40
|
+
return 'String' if source['$Type'] == 'JavaActions$MicroflowJavaActionParameterType'
|
|
41
|
+
|
|
42
|
+
code_action_type(source['Type'])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def javascript_action(source, module_name)
|
|
46
|
+
{
|
|
47
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'],
|
|
48
|
+
'Parameters' => array(source['Parameters']).map { javascript_parameter(_1) },
|
|
49
|
+
'Name' => source['Name'], 'QualifiedName' => qualified_name(module_name, source['Name'])
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def javascript_parameter(source)
|
|
54
|
+
{ '$ID' => source['$ID'], '$Type' => source['$Type'], 'Name' => source['Name'] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def qualified_name(module_name, name)
|
|
58
|
+
raise CompilationError, "code action #{name} is outside a module" unless module_name
|
|
59
|
+
|
|
60
|
+
"#{module_name}.#{name}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
CodeActionMaterialization = Data.define(:model_path, :java_actions, :javascript_actions)
|
|
6
|
+
|
|
7
|
+
# Materializes server and client action signatures into model.mdp.
|
|
8
|
+
class CodeActionMaterializer
|
|
9
|
+
def initialize(mpr_path, deployment:)
|
|
10
|
+
@mpr_path = File.expand_path(mpr_path)
|
|
11
|
+
@deployment = File.expand_path(deployment)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def materialize
|
|
15
|
+
source = SourceModel.read(@mpr_path)
|
|
16
|
+
path = File.join(@deployment, 'model', 'model.mdp')
|
|
17
|
+
documents = compile_documents(source)
|
|
18
|
+
documents.reduce(ModelPackage.read(path)) { |package, document| package.upsert(document) }.write(path)
|
|
19
|
+
counts = documents.map { _1['$Type'] }.tally
|
|
20
|
+
CodeActionMaterialization.new(
|
|
21
|
+
model_path: path, java_actions: counts.fetch('JavaActions$JavaAction', 0),
|
|
22
|
+
javascript_actions: counts.fetch('JavaScriptActions$JavaScriptAction', 0)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def compile_documents(source)
|
|
29
|
+
compiler = CodeActionDocumentCompiler.new
|
|
30
|
+
source.units.select { CodeActionDocumentCompiler::TYPES.include?(_1.document['$Type']) }
|
|
31
|
+
.map { compiler.compile(_1) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
# Converts CodeActions type trees into Runtime type identifiers.
|
|
6
|
+
module CodeActionTypeCompiler
|
|
7
|
+
SCALARS = {
|
|
8
|
+
'CodeActions$VoidType' => 'Void', 'CodeActions$StringType' => 'String',
|
|
9
|
+
'CodeActions$BooleanType' => 'Boolean', 'CodeActions$IntegerType' => 'Integer',
|
|
10
|
+
'CodeActions$LongType' => 'Integer', 'CodeActions$DecimalType' => 'Decimal',
|
|
11
|
+
'CodeActions$FloatType' => 'Decimal', 'CodeActions$DateTimeType' => 'DateTime'
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def code_action_type(source)
|
|
17
|
+
return 'Void' unless source
|
|
18
|
+
|
|
19
|
+
type = source['$Type'].to_s.sub(/\AJavaActions\$/, 'CodeActions$')
|
|
20
|
+
return SCALARS.fetch(type) if SCALARS.key?(type)
|
|
21
|
+
|
|
22
|
+
case type
|
|
23
|
+
when 'CodeActions$ConcreteEntityType' then source['Entity'].to_s
|
|
24
|
+
when 'CodeActions$EnumerationType' then "##{source['Enumeration']}"
|
|
25
|
+
when 'CodeActions$ParameterizedEntityType' then 'Unknown'
|
|
26
|
+
when 'CodeActions$ListType' then "[#{code_action_type(source['Parameter'])}]"
|
|
27
|
+
else raise CompilationError, "unsupported code-action type #{source['$Type']}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Mxrb
|
|
7
|
+
module Compiler
|
|
8
|
+
# Compiles association- and database-backed instances of the official React Combo box widget.
|
|
9
|
+
# rubocop:disable Metrics
|
|
10
|
+
class ComboBoxBundleCompiler
|
|
11
|
+
include ModelValues
|
|
12
|
+
|
|
13
|
+
WIDGET_ID = 'com.mendix.widget.web.combobox.Combobox'
|
|
14
|
+
|
|
15
|
+
def initialize(source, page_name, widget, scope:, entity:)
|
|
16
|
+
@source = source
|
|
17
|
+
@page_name = page_name
|
|
18
|
+
@widget = widget
|
|
19
|
+
@scope = scope
|
|
20
|
+
@entity = entity
|
|
21
|
+
@index = document_index
|
|
22
|
+
@values = property_values(widget['Object'])
|
|
23
|
+
@data_source = WebListDataSource.new(source, widget)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def supported?
|
|
27
|
+
widget_type&.fetch('WidgetId', nil) == WIDGET_ID && supported_source? && resolved_scope
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def render # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
31
|
+
properties = primitive_properties.merge(
|
|
32
|
+
key: widget_key, '$widgetId': widget_key, class: css_class, id: widget_key,
|
|
33
|
+
optionsSourceStaticDataSource: [],
|
|
34
|
+
ariaRequired: raw(expression(false))
|
|
35
|
+
)
|
|
36
|
+
properties.merge!(source_properties)
|
|
37
|
+
control = "React.createElement($Combobox, #{javascript(properties)})"
|
|
38
|
+
caption = translated_text(@widget['LabelTemplate'])
|
|
39
|
+
group = {
|
|
40
|
+
key: "#{widget_key}$formGroup", '$widgetId': "#{widget_key}$formGroup",
|
|
41
|
+
class: "#{css_class} mx-combobox", control: raw("[#{control}]"),
|
|
42
|
+
width: 3, orientation: 'horizontal', labelFor: widget_key,
|
|
43
|
+
caption: raw(expression(caption)), hasError: raw(expression(false))
|
|
44
|
+
}
|
|
45
|
+
"React.createElement($FormGroup, #{javascript(group)})"
|
|
46
|
+
end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def supported_source?
|
|
51
|
+
return database_source_supported? if primitive('source') == 'database'
|
|
52
|
+
return enumeration_source_supported? if primitive('optionsSourceType') == 'enumeration'
|
|
53
|
+
|
|
54
|
+
association_source_supported?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def database_source_supported?
|
|
58
|
+
target_attribute && database_caption_attribute && database_value_attribute && list_source_supported?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def association_source_supported?
|
|
62
|
+
primitive('source') == 'context' && primitive('optionsSourceType') == 'association' &&
|
|
63
|
+
association_step && (association_caption_attribute || association_caption_expression) &&
|
|
64
|
+
list_source_supported?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def enumeration_source_supported?
|
|
68
|
+
primitive('source') == 'context' && enumeration_attribute
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def list_source_supported?
|
|
72
|
+
@data_source.supported? && !@data_source.entity.to_s.empty?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def source_properties
|
|
76
|
+
return database_properties if primitive('source') == 'database'
|
|
77
|
+
return enumeration_properties if primitive('optionsSourceType') == 'enumeration'
|
|
78
|
+
|
|
79
|
+
association_properties
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def enumeration_properties
|
|
83
|
+
{ attributeEnumeration: raw(attribute_property(enumeration_attribute)) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def database_properties
|
|
87
|
+
return database_association_properties if target_steps.any?
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
databaseAttributeString: raw(attribute_property(target_attribute)),
|
|
91
|
+
optionsSourceDatabaseCaptionAttribute: raw(list_attribute_property(database_caption_attribute)),
|
|
92
|
+
optionsSourceDatabaseValueAttribute: raw(list_attribute_property(database_value_attribute)),
|
|
93
|
+
optionsSourceDatabaseDataSource: raw(list_property),
|
|
94
|
+
optionsSourceDatabaseItemSelection: raw(selection_property)
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def database_association_properties
|
|
99
|
+
{
|
|
100
|
+
source: 'context',
|
|
101
|
+
optionsSourceAssociationCaptionAttribute: raw(list_attribute_property(database_caption_attribute)),
|
|
102
|
+
attributeAssociation: raw(association_property(target_steps.first)),
|
|
103
|
+
optionsSourceAssociationDataSource: raw(list_property)
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def association_properties
|
|
108
|
+
properties = {
|
|
109
|
+
attributeAssociation: raw(association_property(association_step)),
|
|
110
|
+
optionsSourceAssociationDataSource: raw(list_property)
|
|
111
|
+
}
|
|
112
|
+
if association_caption_attribute
|
|
113
|
+
properties[:optionsSourceAssociationCaptionAttribute] = raw(
|
|
114
|
+
list_attribute_property(association_caption_attribute)
|
|
115
|
+
)
|
|
116
|
+
else
|
|
117
|
+
properties[:optionsSourceAssociationCaptionExpression] = raw(list_expression_property)
|
|
118
|
+
end
|
|
119
|
+
properties
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def list_expression_property
|
|
123
|
+
source = value('optionsSourceAssociationCaptionExpression')&.fetch('Expression', '').to_s
|
|
124
|
+
path = source[%r{\A\$currentObject/(.+)\z}, 1]
|
|
125
|
+
"ListExpressionProperty(#{javascript(
|
|
126
|
+
expression: {
|
|
127
|
+
expr: { type: 'variable', variable: 'currentObject', path: },
|
|
128
|
+
args: { currentObject: { widget: widget_key, source: 'object' } }
|
|
129
|
+
},
|
|
130
|
+
dataSourceId: data_source_id
|
|
131
|
+
)})"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def association_property(step)
|
|
135
|
+
"AssociationProperty(#{javascript(
|
|
136
|
+
type: 'Reference', entity: resolved_entity, path: '', attribute: step['Association'],
|
|
137
|
+
endpointEntity: step['DestinationEntity'], selectableObjectsId: data_source_id,
|
|
138
|
+
scope: resolved_scope, onChange: do_nothing
|
|
139
|
+
)})"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def list_property
|
|
143
|
+
config = {
|
|
144
|
+
dataSourceId: data_source_id, entity: @data_source.entity, scope: resolved_scope,
|
|
145
|
+
operationId: WebOperationCompiler.operation_id(@page_name, @widget['Name'])
|
|
146
|
+
}
|
|
147
|
+
if @data_source.xpath?
|
|
148
|
+
"DatabaseObjectListProperty(#{javascript(config.merge(sort: []))})"
|
|
149
|
+
else
|
|
150
|
+
"MicroflowObjectListProperty(#{javascript(config.merge(argMap: {}, fetchOnlyWithAllParams: false))})"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def selection_property
|
|
155
|
+
selection = value('optionsSourceDatabaseItemSelection')&.fetch('Selection', 'Single') || 'Single'
|
|
156
|
+
"SelectionProperty(#{javascript(selectionType: selection, dataSourceId: data_source_id)})"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def attribute_property(attribute)
|
|
160
|
+
entity, name = split_attribute(attribute.fetch('Attribute'))
|
|
161
|
+
path = entity_steps(attribute).flat_map do |step|
|
|
162
|
+
[step['Association'], step['DestinationEntity']]
|
|
163
|
+
end.join('/')
|
|
164
|
+
config = {
|
|
165
|
+
scope: resolved_scope, path:, entity:, attribute: name, onChange: do_nothing,
|
|
166
|
+
isList: false, validation: nil, formatting: {}
|
|
167
|
+
}
|
|
168
|
+
"AttributeProperty(#{javascript(config)})"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def list_attribute_property(attribute)
|
|
172
|
+
entity, name = split_attribute(attribute.fetch('Attribute'))
|
|
173
|
+
"ListAttributeProperty(#{javascript(
|
|
174
|
+
path: '', entity:, attribute: name, attributeType: 'String', sortable: true,
|
|
175
|
+
filterable: true, dataSourceId: data_source_id, isList: false
|
|
176
|
+
)})"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def split_attribute(qualified)
|
|
180
|
+
entity, separator, name = qualified.to_s.rpartition('.')
|
|
181
|
+
raise CompilationError, "invalid Combo box attribute #{qualified.inspect}" unless separator == '.'
|
|
182
|
+
|
|
183
|
+
[entity, name]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def resolved_scope
|
|
187
|
+
@resolved_scope ||= begin
|
|
188
|
+
parameter = target_value&.dig('SourceVariable', 'PageParameter').to_s
|
|
189
|
+
parameter.empty? ? @scope : "$#{parameter}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def resolved_entity
|
|
194
|
+
return @entity unless @entity.to_s.empty?
|
|
195
|
+
|
|
196
|
+
parameter = target_value&.dig('SourceVariable', 'PageParameter').to_s
|
|
197
|
+
page_parameter_entity(parameter)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def page_parameter_entity(name)
|
|
201
|
+
module_name, document_name = @page_name.split('.', 2)
|
|
202
|
+
page = @source.units_of('Forms$Page').find do |unit|
|
|
203
|
+
unit.module_name == module_name && unit.document['Name'] == document_name
|
|
204
|
+
end
|
|
205
|
+
parameter = array(page&.document&.fetch('Parameters', nil)).find { _1['Name'] == name }
|
|
206
|
+
parameter&.dig('ParameterType', 'Entity').to_s
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def target_value = value('databaseAttributeString')
|
|
210
|
+
def target_attribute = target_value&.fetch('AttributeRef', nil)
|
|
211
|
+
def enumeration_attribute = value('attributeEnumeration')&.fetch('AttributeRef', nil)
|
|
212
|
+
def database_caption_attribute = value('optionsSourceDatabaseCaptionAttribute')&.fetch('AttributeRef', nil)
|
|
213
|
+
def database_value_attribute = value('optionsSourceDatabaseValueAttribute')&.fetch('AttributeRef', nil)
|
|
214
|
+
def association_caption_attribute = value('optionsSourceAssociationCaptionAttribute')&.fetch('AttributeRef', nil)
|
|
215
|
+
def association_caption_expression = value('optionsSourceAssociationCaptionExpression')&.fetch('Expression', nil)
|
|
216
|
+
|
|
217
|
+
def association_step
|
|
218
|
+
entity_steps(value('attributeAssociation')).first
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def target_steps = entity_steps(target_attribute)
|
|
222
|
+
|
|
223
|
+
def entity_steps(value)
|
|
224
|
+
array(value&.dig('EntityRef', 'Steps'))
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def primitive_properties
|
|
228
|
+
@values.each_with_object({}) do |(key, (type, property)), result|
|
|
229
|
+
compiled = compile_primitive(type, property)
|
|
230
|
+
result[key.to_sym] = compiled unless compiled.nil?
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def compile_primitive(type, property)
|
|
235
|
+
case type
|
|
236
|
+
when 'Boolean' then property['PrimitiveValue'] == 'true'
|
|
237
|
+
when 'Integer' then property['PrimitiveValue'].to_i
|
|
238
|
+
when 'Enumeration' then property['PrimitiveValue'].to_s
|
|
239
|
+
when 'TextTemplate' then raw(expression(translated_text(property['TextTemplate'])))
|
|
240
|
+
when 'Widgets' then [] if array(property['Widgets']).empty?
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def translated_text(template)
|
|
245
|
+
items = array(template&.dig('Template', 'Items'))
|
|
246
|
+
items.find { _1['LanguageCode'] == 'en_US' }&.fetch('Text', '') ||
|
|
247
|
+
items.first&.fetch('Text', '') || ''
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def expression(value)
|
|
251
|
+
"ExpressionProperty(#{javascript(expression: { expr: { type: 'literal', value: }, args: {} })})"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def do_nothing
|
|
255
|
+
{ type: 'doNothing', argMap: {}, config: {}, disabledDuringExecution: false }
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def data_source_id = "p.#{Digest::SHA256.hexdigest(widget_key)[0, 6].to_i(16)}"
|
|
259
|
+
def widget_key = "p.#{@page_name}.#{@widget['Name']}"
|
|
260
|
+
|
|
261
|
+
def css_class
|
|
262
|
+
["mx-name-#{@widget['Name']}", @widget.dig('Appearance', 'Class')]
|
|
263
|
+
.map(&:to_s).reject(&:empty?).uniq.join(' ')
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def primitive(key) = value(key)&.fetch('PrimitiveValue', nil)
|
|
267
|
+
def value(key) = @values[key]&.last
|
|
268
|
+
def raw(value) = { '$raw' => value }
|
|
269
|
+
|
|
270
|
+
def javascript(value)
|
|
271
|
+
return value['$raw'] if value.is_a?(Hash) && value.key?('$raw')
|
|
272
|
+
return "[#{value.map { javascript(_1) }.join(', ')}]" if value.is_a?(Array)
|
|
273
|
+
if value.is_a?(Hash)
|
|
274
|
+
return "{ #{value.map { |key, item| "#{JSON.generate(key)}: #{javascript(item)}" }.join(', ')} }"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
JSON.generate(value)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def property_values(object)
|
|
281
|
+
array(object&.fetch('Properties', nil)).filter_map do |property|
|
|
282
|
+
type = @index[IO::BsonCodec.extract_id(property['TypePointer'])]
|
|
283
|
+
next unless type
|
|
284
|
+
|
|
285
|
+
[type.fetch('PropertyKey'), [type.dig('ValueType', 'Type'), property['Value']]]
|
|
286
|
+
end.to_h
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def widget_type
|
|
290
|
+
object_type_id = IO::BsonCodec.extract_id(@widget.dig('Object', 'TypePointer'))
|
|
291
|
+
@index.values.find do |document|
|
|
292
|
+
IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == object_type_id
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def document_index
|
|
297
|
+
return @source.document_index if @source.is_a?(SourceModel)
|
|
298
|
+
|
|
299
|
+
{}.tap { |index| @source.documents.each { index_document(_1, index) } }
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def index_document(value, index)
|
|
303
|
+
case value
|
|
304
|
+
when Hash
|
|
305
|
+
id = IO::BsonCodec.extract_id(value['$ID'])
|
|
306
|
+
index[id] = value if id
|
|
307
|
+
value.each_value { index_document(_1, index) }
|
|
308
|
+
when Array then value.each { index_document(_1, index) }
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
# rubocop:enable Metrics
|
|
313
|
+
end
|
|
314
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
CompatibilityFinding = Data.define(:severity, :category, :type, :location, :message, :count) do
|
|
6
|
+
def to_h = { severity:, category:, type:, location:, message:, count: }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
CompatibilityReport = Data.define(:path, :mendix_version, :findings, :stats) do
|
|
10
|
+
def compatible? = findings.none? { _1.severity == :error }
|
|
11
|
+
def errors = findings.select { _1.severity == :error }
|
|
12
|
+
def warnings = findings.select { _1.severity == :warning }
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
{
|
|
16
|
+
path:, mendix_version:, compatible: compatible?, stats:,
|
|
17
|
+
findings: findings.map(&:to_h)
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Read-only preflight for features consumed by the native compiler and web runtime.
|
|
23
|
+
# rubocop:disable Metrics
|
|
24
|
+
class CompatibilityAnalyzer
|
|
25
|
+
include ModelValues
|
|
26
|
+
|
|
27
|
+
def initialize(path, source: nil)
|
|
28
|
+
@path = File.expand_path(path)
|
|
29
|
+
@source = source || SourceModel.read(@path)
|
|
30
|
+
@findings = []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def analyze
|
|
34
|
+
analyze_version
|
|
35
|
+
analyze_features if @adapter
|
|
36
|
+
CompatibilityReport.new(
|
|
37
|
+
path: @path, mendix_version: @source.version, findings: collapsed_findings.freeze,
|
|
38
|
+
stats: statistics.freeze
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def analyze_features
|
|
45
|
+
if @adapter.web_profiles.include?(:react)
|
|
46
|
+
analyze_pages
|
|
47
|
+
analyze_layouts
|
|
48
|
+
analyze_nanoflows
|
|
49
|
+
else
|
|
50
|
+
analyze_legacy_pages
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def analyze_version
|
|
55
|
+
@adapter = Adapter.for(@source.version, source: @source)
|
|
56
|
+
rescue UnsupportedVersion => e
|
|
57
|
+
add(:error, :version, "Mendix #{@source.version}", 'project', e.message)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def analyze_legacy_pages
|
|
61
|
+
LegacyPageBuilder.new(@source, '', profiles: @adapter.web_profiles).audit.each do |location, types|
|
|
62
|
+
types.each do |type|
|
|
63
|
+
add(:error, page_category(type), type, location,
|
|
64
|
+
"#{type} is not compiled by the native legacy web renderer")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
rescue CompilationError, KeyError, TypeError => e
|
|
68
|
+
add(:error, :page, 'Forms$Page', 'project', e.message)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def analyze_pages
|
|
72
|
+
@source.units_of('Forms$Page').select { web_page?(_1) }.each do |unit|
|
|
73
|
+
location = qualified_name(unit)
|
|
74
|
+
bundle = PageBundleCompiler.new(@source).compile(unit)
|
|
75
|
+
bundle.unsupported_widgets.each do |type|
|
|
76
|
+
if type == 'CustomWidgets$CustomWidget'
|
|
77
|
+
bundle.unsupported_custom_widgets.each do |identifier|
|
|
78
|
+
add(:error, :custom_widget, identifier, location,
|
|
79
|
+
"#{identifier} is not compiled by the native web renderer")
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
add(:error, page_category(type), type, location,
|
|
83
|
+
"#{type} is not compiled by the native web renderer")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
rescue CompilationError, KeyError, TypeError => e
|
|
87
|
+
add(:error, :page, 'Forms$Page', location, e.message)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def analyze_nanoflows
|
|
92
|
+
compiler = NanoflowProgramCompiler.new(@source)
|
|
93
|
+
@source.units_of('Microflows$Nanoflow').each { compiler.reference(qualified_name(_1)) }
|
|
94
|
+
compiler.unsupported.each do |entry|
|
|
95
|
+
location, type = entry.split(':', 2)
|
|
96
|
+
add(:error, :nanoflow, type, location, "#{type} is not compiled by the native nanoflow runtime")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def analyze_layouts
|
|
101
|
+
@source.units_of('Forms$Layout').select { web_layout?(_1) }.each do |unit|
|
|
102
|
+
location = qualified_name(unit)
|
|
103
|
+
bundle = PageBundleCompiler.new(@source).compile_layout(unit)
|
|
104
|
+
add_bundle_findings(bundle, location)
|
|
105
|
+
rescue CompilationError, KeyError, TypeError => e
|
|
106
|
+
add(:error, :layout, 'Forms$Layout', location, e.message)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def add_bundle_findings(bundle, location)
|
|
111
|
+
bundle.unsupported_widgets.each do |type|
|
|
112
|
+
if type == 'CustomWidgets$CustomWidget'
|
|
113
|
+
bundle.unsupported_custom_widgets.each do |identifier|
|
|
114
|
+
add(:error, :custom_widget, identifier, location,
|
|
115
|
+
"#{identifier} is not compiled by the native web renderer")
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
add(:error, page_category(type), type, location,
|
|
119
|
+
"#{type} is not compiled by the native web renderer")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def page_category(type)
|
|
125
|
+
return :client_action if type.end_with?('Action')
|
|
126
|
+
return :custom_widget if type == 'CustomWidgets$CustomWidget'
|
|
127
|
+
|
|
128
|
+
:widget
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def web_page?(unit) = !@source.is_a?(SourceModel) || @source.web_page?(unit)
|
|
132
|
+
def web_layout?(unit) = !@source.is_a?(SourceModel) || @source.web_layout?(unit)
|
|
133
|
+
|
|
134
|
+
def add(severity, category, type, location, message)
|
|
135
|
+
@findings << CompatibilityFinding.new(
|
|
136
|
+
severity:, category:, type: type.to_s, location: location.to_s, message:, count: 1
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def collapsed_findings
|
|
141
|
+
grouped = @findings.group_by { finding_key(_1) }
|
|
142
|
+
findings = grouped.map { |key, matches| collapsed_finding(key, matches.size) }
|
|
143
|
+
findings.sort_by { [_1.severity.to_s, _1.category.to_s, _1.location, _1.type] }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def finding_key(finding)
|
|
147
|
+
[finding.severity, finding.category, finding.type, finding.location, finding.message]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def collapsed_finding(key, count)
|
|
151
|
+
severity, category, type, location, message = key
|
|
152
|
+
CompatibilityFinding.new(severity:, category:, type:, location:, message:, count:)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def statistics
|
|
156
|
+
{
|
|
157
|
+
units: @source.units.size,
|
|
158
|
+
pages: @source.units_of('Forms$Page').size,
|
|
159
|
+
layouts: @source.units_of('Forms$Layout').size,
|
|
160
|
+
nanoflows: @source.units_of('Microflows$Nanoflow').size,
|
|
161
|
+
microflows: @source.units_of('Microflows$Microflow').size
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def qualified_name(unit) = [unit.module_name, unit.document['Name']].compact.join('.')
|
|
166
|
+
end
|
|
167
|
+
# rubocop:enable Metrics
|
|
168
|
+
end
|
|
169
|
+
end
|