ecoportal-api-graphql 1.3.17 → 2.2.1
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/CHANGELOG.md +763 -11
- data/lib/ecoportal/api/common/graphql/http_client.rb +85 -2
- data/lib/ecoportal/api/common/graphql/model/diffable/leaf_diff_service.rb +97 -0
- data/lib/ecoportal/api/common/graphql/model/diffable.rb +1 -0
- data/lib/ecoportal/api/graphql/base/action.rb +1 -1
- data/lib/ecoportal/api/graphql/base/action_event.rb +23 -0
- data/lib/ecoportal/api/graphql/base/action_event_page.rb +16 -0
- data/lib/ecoportal/api/graphql/base/activity.rb +38 -0
- data/lib/ecoportal/api/graphql/base/activity_log.rb +38 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +12 -7
- data/lib/ecoportal/api/graphql/base/auth_log.rb +24 -0
- data/lib/ecoportal/api/graphql/base/authorization_result.rb +20 -0
- data/lib/ecoportal/api/graphql/base/contractor_entity/member_changes.rb +17 -5
- data/lib/ecoportal/api/graphql/base/file_container.rb +12 -4
- data/lib/ecoportal/api/graphql/base/location_classification_type.rb +2 -0
- data/lib/ecoportal/api/graphql/base/location_structure.rb +3 -0
- data/lib/ecoportal/api/graphql/base/minimal_dashboard.rb +25 -0
- data/lib/ecoportal/api/graphql/base/organization.rb +2 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +25 -2
- data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +46 -7
- data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +5 -2
- data/lib/ecoportal/api/graphql/base/page/data_field/people.rb +50 -12
- data/lib/ecoportal/api/graphql/base/page/data_field.rb +12 -18
- data/lib/ecoportal/api/graphql/base/page/phased/stage.rb +51 -0
- data/lib/ecoportal/api/graphql/base/permissions.rb +15 -0
- data/lib/ecoportal/api/graphql/base/person_member_changelog.rb +23 -0
- data/lib/ecoportal/api/graphql/base/preset_view.rb +135 -0
- data/lib/ecoportal/api/graphql/base/register.rb +5 -0
- data/lib/ecoportal/api/graphql/base/template/change_log_entry.rb +118 -0
- data/lib/ecoportal/api/graphql/base/template.rb +2 -0
- data/lib/ecoportal/api/graphql/base.rb +9 -0
- data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +10 -0
- data/lib/ecoportal/api/graphql/builder/page.rb +0 -16
- data/lib/ecoportal/api/graphql/builder/template.rb +46 -0
- data/lib/ecoportal/api/graphql/builder/template_builder.rb +21 -2
- data/lib/ecoportal/api/graphql/compat/pages.rb +7 -2
- data/lib/ecoportal/api/graphql/compat/response.rb +14 -0
- data/lib/ecoportal/api/graphql/concerns/threadable.rb +58 -0
- data/lib/ecoportal/api/graphql/concerns.rb +1 -0
- data/lib/ecoportal/api/graphql/connection/action.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/action_category.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/activity.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/activity_log.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/auth_log.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/minimal_dashboard.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/page.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/pages_workflow_command.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/person_member.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/person_member_changelog.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +1 -1
- data/lib/ecoportal/api/graphql/connection/template_change_log.rb +19 -0
- data/lib/ecoportal/api/graphql/connection.rb +6 -0
- data/lib/ecoportal/api/graphql/diff/change.rb +7 -2
- data/lib/ecoportal/api/graphql/diff/command_synthesizer.rb +19 -1
- data/lib/ecoportal/api/graphql/diff/pairing/signals.rb +21 -1
- data/lib/ecoportal/api/graphql/diff/version_diff.rb +13 -6
- data/lib/ecoportal/api/graphql/file_upload/client.rb +198 -139
- data/lib/ecoportal/api/graphql/fragment/activity.rb +84 -0
- data/lib/ecoportal/api/graphql/fragment/activity_log.rb +75 -0
- data/lib/ecoportal/api/graphql/fragment/auth_log.rb +29 -0
- data/lib/ecoportal/api/graphql/fragment/authorization_result.rb +14 -0
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +1 -0
- data/lib/ecoportal/api/graphql/fragment/minimal_dashboard.rb +31 -0
- data/lib/ecoportal/api/graphql/fragment/page.rb +1 -0
- data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +23 -0
- data/lib/ecoportal/api/graphql/fragment/permissions.rb +96 -0
- data/lib/ecoportal/api/graphql/fragment/person_member_changelog.rb +33 -0
- data/lib/ecoportal/api/graphql/fragment/template_change_log.rb +102 -0
- data/lib/ecoportal/api/graphql/fragment.rb +8 -0
- data/lib/ecoportal/api/graphql/input/action/update.rb +12 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity/manager_settings.rb +20 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +6 -27
- data/lib/ecoportal/api/graphql/input/contractor_entity.rb +1 -0
- data/lib/ecoportal/api/graphql/input/date_range.rb +16 -0
- data/lib/ecoportal/api/graphql/input/location_structure/apply_commands.rb +2 -2
- data/lib/ecoportal/api/graphql/input/location_structure/draft/add_commands.rb +2 -2
- data/lib/ecoportal/api/graphql/input/location_structure/insert_command.rb +8 -0
- data/lib/ecoportal/api/graphql/input/location_structure/update_command.rb +6 -2
- data/lib/ecoportal/api/graphql/input/page/review_task.rb +14 -1
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -1
- data/lib/ecoportal/api/graphql/input/workflow_command/add_checklist_field_item.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +10 -9
- data/lib/ecoportal/api/graphql/input/workflow_command/add_select_field_option.rb +11 -5
- data/lib/ecoportal/api/graphql/input/workflow_command/add_smart_fill_config.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/add_viewable_field.rb +45 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/archive_public_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/create_public_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_checklist_field_item.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +17 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_force.rb +5 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_public_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_select_field_option.rb +6 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_smart_fill_config.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_advanced_settings.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_basic_settings.rb +32 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_container_uid.rb +45 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/{edit_page_creator_permissions.rb → edit_template_creator_permissions.rb} +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_template_descriptions.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/action_list.rb +31 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/contractor_entities.rb +6 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +9 -1
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +15 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/file.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +27 -3
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +9 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/manage_copy_page_configuration.rb +47 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/move_section_to_stage.rb +33 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_viewable_field.rb +33 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_checklist_field_item.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_smart_fill_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_viewable_field.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +3 -2
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +48 -5
- data/lib/ecoportal/api/graphql/input.rb +1 -0
- data/lib/ecoportal/api/graphql/interface/base_page.rb +50 -3
- data/lib/ecoportal/api/graphql/interface/location_structure/command.rb +2 -1
- data/lib/ecoportal/api/graphql/logic/base_query.rb +72 -5
- data/lib/ecoportal/api/graphql/logic/connection.rb +17 -0
- data/lib/ecoportal/api/graphql/logic/input.rb +127 -0
- data/lib/ecoportal/api/graphql/model/action_event.rb +14 -0
- data/lib/ecoportal/api/graphql/model/action_event_page.rb +10 -0
- data/lib/ecoportal/api/graphql/model/activity.rb +14 -0
- data/lib/ecoportal/api/graphql/model/activity_log.rb +31 -0
- data/lib/ecoportal/api/graphql/model/auth_log.rb +11 -0
- data/lib/ecoportal/api/graphql/model/authorization_result.rb +12 -0
- data/lib/ecoportal/api/graphql/model/minimal_dashboard.rb +14 -0
- data/lib/ecoportal/api/graphql/model/organization.rb +36 -0
- data/lib/ecoportal/api/graphql/model/page/phased.rb +17 -0
- data/lib/ecoportal/api/graphql/model/permissions.rb +185 -0
- data/lib/ecoportal/api/graphql/model/person_member.rb +7 -0
- data/lib/ecoportal/api/graphql/model/person_member_changelog.rb +15 -0
- data/lib/ecoportal/api/graphql/model/template/change_log_entry.rb +19 -0
- data/lib/ecoportal/api/graphql/model/template.rb +4 -1
- data/lib/ecoportal/api/graphql/model.rb +9 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +20 -10
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +25 -10
- data/lib/ecoportal/api/graphql/mutation/contractor_entity/update_manager_settings.rb +37 -0
- data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +1 -0
- data/lib/ecoportal/api/graphql/mutation/file_container/upload.rb +78 -0
- data/lib/ecoportal/api/graphql/mutation/file_container.rb +15 -0
- data/lib/ecoportal/api/graphql/mutation/template/update_related_page.rb +79 -0
- data/lib/ecoportal/api/graphql/mutation/template.rb +1 -0
- data/lib/ecoportal/api/graphql/mutation.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/contractor_entity/manager_settings.rb +20 -0
- data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +1 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb +2 -2
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb +2 -2
- data/lib/ecoportal/api/graphql/payload/template/update_related_page.rb +15 -0
- data/lib/ecoportal/api/graphql/payload/template.rb +1 -0
- data/lib/ecoportal/api/graphql/payload/upload.rb +12 -0
- data/lib/ecoportal/api/graphql/payload.rb +1 -0
- data/lib/ecoportal/api/graphql/query/activity_logs.rb +78 -0
- data/lib/ecoportal/api/graphql/query/auth_logs.rb +51 -0
- data/lib/ecoportal/api/graphql/query/dashboard_search.rb +57 -0
- data/lib/ecoportal/api/graphql/query/location_structure.rb +2 -0
- data/lib/ecoportal/api/graphql/query/location_structures.rb +2 -0
- data/lib/ecoportal/api/graphql/query/page.rb +6 -1
- data/lib/ecoportal/api/graphql/query/page_activities.rb +56 -0
- data/lib/ecoportal/api/graphql/query/permissions.rb +35 -0
- data/lib/ecoportal/api/graphql/query/person_member_changelogs.rb +53 -0
- data/lib/ecoportal/api/graphql/query/person_members.rb +76 -0
- data/lib/ecoportal/api/graphql/query/template_change_log.rb +118 -0
- data/lib/ecoportal/api/graphql/query.rb +8 -0
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +83 -14
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +0 -28
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +0 -36
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +0 -40
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +0 -13
|
@@ -105,14 +105,6 @@ module Ecoportal
|
|
|
105
105
|
submitAiSummaryFeedbackMutation.query(**kargs, &block)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
def generate_smart_fill(**kargs, &block)
|
|
109
|
-
generateSmartFillMutation.query(**kargs, &block)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def submit_smart_fill_feedback(**kargs, &block)
|
|
113
|
-
submitSmartFillFeedbackMutation.query(**kargs, &block)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
108
|
private
|
|
117
109
|
|
|
118
110
|
# Blank the page's externalId via updatePage (no patchVer needed — cleanup path).
|
|
@@ -184,14 +176,6 @@ module Ecoportal
|
|
|
184
176
|
Mutation::AiSummary::SubmitFeedback.new(client)
|
|
185
177
|
end
|
|
186
178
|
|
|
187
|
-
def generateSmartFillMutation
|
|
188
|
-
Mutation::SmartFill::Generate.new(client)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def submitSmartFillFeedbackMutation
|
|
192
|
-
Mutation::SmartFill::SubmitFeedback.new(client)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
179
|
def executeWorkflowCommandsMutation
|
|
196
180
|
Mutation::Page::ExecuteWorkflowCommands.new(client)
|
|
197
181
|
end
|
|
@@ -40,10 +40,48 @@ module Ecoportal
|
|
|
40
40
|
)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# Edits an existing related-pages entry. Only the keys you pass are sent — absent keys
|
|
44
|
+
# are left alone server-side, so this is a partial update, not a replace.
|
|
45
|
+
def update_related_page(template_id:, id:, patch_ver:, title: nil, register_id: nil, filters: nil, &block)
|
|
46
|
+
updateRelatedPageMutation.query(
|
|
47
|
+
template_id: template_id,
|
|
48
|
+
id: id,
|
|
49
|
+
patch_ver: patch_ver,
|
|
50
|
+
title: title,
|
|
51
|
+
register_id: register_id,
|
|
52
|
+
filters: filters,
|
|
53
|
+
&block
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
43
57
|
def destroy_related_page(template_id:, id:, patch_ver:, &block)
|
|
44
58
|
destroyRelatedPageMutation.query(template_id: template_id, id: id, patch_ver: patch_ver, &block)
|
|
45
59
|
end
|
|
46
60
|
|
|
61
|
+
# TEMPLATE CHANGE LOG — who changed this template, what, and when. Newest first
|
|
62
|
+
# (server-side `_id: :desc`), paginated.
|
|
63
|
+
#
|
|
64
|
+
# api.template.change_log(id: template_page_id, first: 50) # => Connection
|
|
65
|
+
# api.template.change_log.each(id: template_page_id) { |e| ... } # auto-paginates
|
|
66
|
+
#
|
|
67
|
+
# ★ Two caveats, both silent — read `Base::Template::ChangeLogEntry` before relying on
|
|
68
|
+
# this: the connection is nil (not empty) unless the caller satisfies
|
|
69
|
+
# `Pages::TemplatePolicy#update_information?`, and only TEMPLATE_BUILDER-sourced
|
|
70
|
+
# commands appear (workflow-builder ones are on `Query::PagesWorkflowCommands`).
|
|
71
|
+
#
|
|
72
|
+
# Called with no arguments it returns the query object itself, so `#each` /`#map` and
|
|
73
|
+
# the rest of Enumerable are available — same idiom as the QueryIntegration queries on
|
|
74
|
+
# `Model::Organization`.
|
|
75
|
+
# @param detail [Boolean] when true, also select FIELD-LEVEL detail (what changed).
|
|
76
|
+
# Ignored when an explicit block is given -- the block wins, since it is the more
|
|
77
|
+
# specific instruction. Off by default so the common who/when read stays cheap.
|
|
78
|
+
def change_log(detail: false, **kargs, &block)
|
|
79
|
+
selection = block || (detail ? Query::TemplateChangeLog.detail_block : nil)
|
|
80
|
+
return changeLogQuery if kargs.empty? && !selection
|
|
81
|
+
|
|
82
|
+
changeLogQuery.query(**kargs, &selection)
|
|
83
|
+
end
|
|
84
|
+
|
|
47
85
|
private
|
|
48
86
|
|
|
49
87
|
def createMutation
|
|
@@ -70,9 +108,17 @@ module Ecoportal
|
|
|
70
108
|
Mutation::Template::CreateRelatedPage.new(client)
|
|
71
109
|
end
|
|
72
110
|
|
|
111
|
+
def updateRelatedPageMutation
|
|
112
|
+
Mutation::Template::UpdateRelatedPage.new(client)
|
|
113
|
+
end
|
|
114
|
+
|
|
73
115
|
def destroyRelatedPageMutation
|
|
74
116
|
Mutation::Template::DestroyRelatedPage.new(client)
|
|
75
117
|
end
|
|
118
|
+
|
|
119
|
+
def changeLogQuery
|
|
120
|
+
Query::TemplateChangeLog.new(client)
|
|
121
|
+
end
|
|
76
122
|
end
|
|
77
123
|
end
|
|
78
124
|
end
|
|
@@ -24,7 +24,12 @@ module Ecoportal
|
|
|
24
24
|
# { field_type: 'PlainText', label: 'Description', column: 0,
|
|
25
25
|
# description: 'ID:token' }, # addField (+ editFieldConfiguration for description)
|
|
26
26
|
# { field_type: 'Select', label: 'Severity',
|
|
27
|
-
# options: [ { label: 'Low', weight: 0
|
|
27
|
+
# options: [ { label: 'Low', weight: 0, value: 'low' },
|
|
28
|
+
# { label: 'High', weight: 10, value: 'high' } ], # addSelectFieldOption
|
|
29
|
+
# # `value:` is REQUIRED per option (backend: AddSelectFieldOptionInput,
|
|
30
|
+
# # required: true) — it is the option's unique key (duplicate values within one
|
|
31
|
+
# # field are rejected), always sent as a String even for numeric select fields
|
|
32
|
+
# # (stored as an Integer server-side). A missing `value` raises ArgumentError.
|
|
28
33
|
# config: { select: { multiple: false } } }, # editFieldConfiguration(byType:)
|
|
29
34
|
# { field_type: 'Gauge', label: 'Risk',
|
|
30
35
|
# stops: [ { threshold: 0, color: '#0f0' }, ... ], # addGaugeFieldStop
|
|
@@ -125,9 +130,23 @@ module Ecoportal
|
|
|
125
130
|
add(:editFieldConfiguration, data_field_id: field_ph, description: description)
|
|
126
131
|
end
|
|
127
132
|
|
|
133
|
+
# addSelectFieldOption requires `value` (backend: AddSelectFieldOptionInput, required: true
|
|
134
|
+
# — a real field distinct from label/weight, cast to a number server-side for numeric
|
|
135
|
+
# select fields). The DSL has no natural default for it (unlike label/weight, it is
|
|
136
|
+
# business data this builder cannot infer), so it MUST be supplied per option in the spec;
|
|
137
|
+
# a missing one raises rather than silently falling back to the label/weight (which would
|
|
138
|
+
# risk persisting the wrong stored value for a scored/numeric select field).
|
|
128
139
|
def emit_options(field, field_ph)
|
|
129
140
|
Array(field[:options]).each do |opt|
|
|
130
|
-
|
|
141
|
+
value = opt[:value]
|
|
142
|
+
if value.nil?
|
|
143
|
+
raise ArgumentError,
|
|
144
|
+
"TemplateBuilder: option '#{opt[:label]}' on field '#{field[:label]}' is " \
|
|
145
|
+
'missing `value` (addSelectFieldOption requires it) — supply ' \
|
|
146
|
+
'options: [{ label:, weight:, value: }, ...] in the spec'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
add(:addSelectFieldOption, data_field_id: field_ph, label: opt[:label], value: value, weight: opt[:weight])
|
|
131
150
|
end
|
|
132
151
|
end
|
|
133
152
|
|
|
@@ -18,8 +18,13 @@ module Ecoportal
|
|
|
18
18
|
# Fetch a page by ID with full data — metadata, stages, sections, data fields.
|
|
19
19
|
# When stage_id is provided, wraps the result in a StageView that filters
|
|
20
20
|
# sections and components to that stage only — matching v2 per-stage behaviour.
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
# @param variables [Hash] optional CommonPageUnion document-variable overrides
|
|
22
|
+
# (`showHiddenData:`, `content:`, `only_content:`, `fields:`). `content: false`
|
|
23
|
+
# skips field values AND `attachedFiles` — the one selection preauthorized on the
|
|
24
|
+
# `files: download` ability — so structure-only readers (e.g. template tooling on a
|
|
25
|
+
# least-privilege account) can read pages without that grant.
|
|
26
|
+
def get(id, stage_id: nil, **variables)
|
|
27
|
+
page = Query::Page.new(@client).query(id: id, **variables) do
|
|
23
28
|
spread :CommonPageUnion
|
|
24
29
|
end
|
|
25
30
|
stage_id ? StageView.new(page, stage_id) : page
|
|
@@ -5,7 +5,21 @@ module Ecoportal
|
|
|
5
5
|
# Wraps a GraphQL mutation payload to expose the v2-compatible response interface.
|
|
6
6
|
# Scripts check .success? and .status after update/create operations.
|
|
7
7
|
class Response
|
|
8
|
+
# The payload contract this wrapper delegates to. Guarded at construction so a
|
|
9
|
+
# payload of the wrong shape fails LOUD here, instead of silently downstream.
|
|
10
|
+
# This is the class of drift behind the eco-helpers 3.2.19 KPI-counter
|
|
11
|
+
# regression, where a Compat::Response (duck-typed, not a Common::Response)
|
|
12
|
+
# slipped through an `is_a?` check and every GraphQL update went uncounted.
|
|
13
|
+
PAYLOAD_CONTRACT = %i[success? error? error_doc item].freeze
|
|
14
|
+
|
|
8
15
|
def initialize(payload)
|
|
16
|
+
missing = PAYLOAD_CONTRACT.reject { |meth| payload.respond_to?(meth) }
|
|
17
|
+
unless missing.empty?
|
|
18
|
+
raise ArgumentError,
|
|
19
|
+
"Compat::Response payload #{payload.class} does not honour the expected " \
|
|
20
|
+
"response contract; missing: #{missing.join(', ')}"
|
|
21
|
+
end
|
|
22
|
+
|
|
9
23
|
@payload = payload
|
|
10
24
|
end
|
|
11
25
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Concerns
|
|
5
|
+
# Bounded-concurrency helpers for batch operations.
|
|
6
|
+
#
|
|
7
|
+
# Ported from `ecoportal-api-v2`'s `Common::Concerns::Threadable` rather than required
|
|
8
|
+
# from it: that gem is being decommissioned, and this is exactly the kind of small,
|
|
9
|
+
# stable primitive that should not create a new dependency on it.
|
|
10
|
+
#
|
|
11
|
+
# `new_thread` keeps at most `max` threads alive by joining the oldest before starting
|
|
12
|
+
# a new one — a deliberately simple back-pressure valve, not a pool. `max: 1` runs
|
|
13
|
+
# inline (no threads at all), which keeps single-file paths and specs deterministic.
|
|
14
|
+
module Threadable
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# @param threads [Array<Thread>] the caller's thread accumulator (joined by the caller).
|
|
18
|
+
# @param max [Integer] maximum concurrent threads; `1` means run inline.
|
|
19
|
+
# @return [Array<Thread>, void]
|
|
20
|
+
def new_thread(threads, max: 20, &block)
|
|
21
|
+
if max <= 1
|
|
22
|
+
yield
|
|
23
|
+
return []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
threads.shift.join if threads.count >= max
|
|
27
|
+
threads << Thread.new(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Named mutexes, created on demand. Use one key per shared resource so unrelated
|
|
31
|
+
# critical sections never serialise against each other.
|
|
32
|
+
def mutex(key = :general)
|
|
33
|
+
key = key.to_sym
|
|
34
|
+
@mutex_map ||= {}
|
|
35
|
+
# The map itself must be created atomically the first time.
|
|
36
|
+
@mutex_map[key] ||= Mutex.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Batch work must not be killed by one file raising in a thread, and must not spam
|
|
40
|
+
# the caller's stderr with per-thread reports — the caller gets the error in its
|
|
41
|
+
# result object instead. Restores both globals afterwards.
|
|
42
|
+
def with_preserved_thread_globals(abort: false, report: false)
|
|
43
|
+
mutex(:thread_globals).synchronize do
|
|
44
|
+
prev_abort = Thread.abort_on_exception
|
|
45
|
+
prev_report = Thread.report_on_exception
|
|
46
|
+
Thread.abort_on_exception = abort
|
|
47
|
+
Thread.report_on_exception = report
|
|
48
|
+
yield
|
|
49
|
+
ensure
|
|
50
|
+
Thread.abort_on_exception = prev_abort
|
|
51
|
+
Thread.report_on_exception = prev_report
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -5,7 +5,7 @@ module Ecoportal
|
|
|
5
5
|
class PagesWorkflowCommand < Logic::Connection
|
|
6
6
|
SCHEMA_VERSION = '20260605'.freeze
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
nodes_of 'Ecoportal::API::GraphQL::Base::PagesWorkflow::CommandInterface'
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Connection
|
|
5
|
+
# `Template.commands` — a `PagesWorkflowCommandInterfaceConnection` carrying
|
|
6
|
+
# `totalCount` (added by `Types::CustomConnections::BaseConnection`), `pageInfo`
|
|
7
|
+
# and `nodes`.
|
|
8
|
+
#
|
|
9
|
+
# ★ A nil connection here means NOT PERMITTED, not "no changes" — see
|
|
10
|
+
# `Base::Template::ChangeLogEntry` for the two access caveats.
|
|
11
|
+
class TemplateChangeLog < Logic::Connection
|
|
12
|
+
# `nodes_of` (not `class_resolver :node_class`) — see Logic::Connection for why the
|
|
13
|
+
# shared dynamic collection poisons sibling connections otherwise.
|
|
14
|
+
nodes_of Model::Template::ChangeLogEntry
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -15,3 +15,9 @@ require_relative 'connection/page'
|
|
|
15
15
|
require_relative 'connection/preview_page'
|
|
16
16
|
require_relative 'connection/preset_view'
|
|
17
17
|
require_relative 'connection/pages_workflow_command'
|
|
18
|
+
require_relative 'connection/activity'
|
|
19
|
+
require_relative 'connection/activity_log'
|
|
20
|
+
require_relative 'connection/auth_log'
|
|
21
|
+
require_relative 'connection/person_member_changelog'
|
|
22
|
+
require_relative 'connection/minimal_dashboard'
|
|
23
|
+
require_relative 'connection/template_change_log'
|
|
@@ -23,13 +23,18 @@ module Ecoportal
|
|
|
23
23
|
# that parent_id alone cannot carry. Set for an ADDED :field (parent_id = its section id,
|
|
24
24
|
# owner_id = its stage id) because addField requires BOTH sectionId and stageId (both
|
|
25
25
|
# NON_NULL in the schema). nil for every other kind/op.
|
|
26
|
+
#
|
|
27
|
+
# value: for an ADDED :option, the select option's `value` (the schema's addSelectFieldOption
|
|
28
|
+
# requires it — see AddSelectFieldOptionInput, backend authority). Read from the source doc's
|
|
29
|
+
# option.value (VersionDiff#index_options); nil when the source doc did not carry one. NEVER
|
|
30
|
+
# defaulted to label/weight by the synthesiser — see CommandSynthesizer#option_command.
|
|
26
31
|
Change = Struct.new(:op, :kind, :id, :label, :path, :attribute, :before, :after, :parent_id,
|
|
27
|
-
:by_type, :owner_id, keyword_init: true) do
|
|
32
|
+
:by_type, :owner_id, :value, keyword_init: true) do
|
|
28
33
|
def to_h
|
|
29
34
|
{
|
|
30
35
|
op: op, kind: kind, id: id, label: label, path: path,
|
|
31
36
|
attribute: attribute, before: before, after: after, parent_id: parent_id,
|
|
32
|
-
by_type: by_type, owner_id: owner_id
|
|
37
|
+
by_type: by_type, owner_id: owner_id, value: value
|
|
33
38
|
}.compact
|
|
34
39
|
end
|
|
35
40
|
|
|
@@ -277,13 +277,31 @@ module Ecoportal
|
|
|
277
277
|
|
|
278
278
|
field_ref = ref(field_id)
|
|
279
279
|
case change.op
|
|
280
|
-
when :added then
|
|
280
|
+
when :added then add_option(change, field_ref)
|
|
281
281
|
when :removed then build(:removeSelectFieldOption, data_field_id: field_ref, option_id: change.id)
|
|
282
282
|
when :changed then edit_option(change, field_ref)
|
|
283
283
|
else unsupported!(change)
|
|
284
284
|
end
|
|
285
285
|
end
|
|
286
286
|
|
|
287
|
+
# addSelectFieldOption requires `value` (backend: AddSelectFieldOptionInput, required: true
|
|
288
|
+
# — a real, distinct field from label/weight; see OptionType#value). NEVER default it to
|
|
289
|
+
# the label/weight (a numeric select field stores `value` as a number, so a label stand-in
|
|
290
|
+
# would silently persist wrong data). When the source doc's option carried no `value`
|
|
291
|
+
# (Change#value nil — see VersionDiff#index_options), raise loudly instead of guessing.
|
|
292
|
+
def add_option(change, field_ref)
|
|
293
|
+
if change.value.nil?
|
|
294
|
+
raise ArgumentError,
|
|
295
|
+
"CommandSynthesizer: cannot build addSelectFieldOption for option " \
|
|
296
|
+
"'#{change.label}' (field #{field_ref}) — Change#value is nil. The source doc's " \
|
|
297
|
+
"option is missing `value` (VersionDiff#index_options reads it from " \
|
|
298
|
+
"option['value']); supply it on the source doc, or on the Change directly when " \
|
|
299
|
+
"hand-assembling one."
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
build(:addSelectFieldOption, data_field_id: field_ref, label: change.label, value: change.value)
|
|
303
|
+
end
|
|
304
|
+
|
|
287
305
|
def edit_option(change, field_id)
|
|
288
306
|
case change.attribute
|
|
289
307
|
when 'label' then build(:editSelectFieldOption, data_field_id: field_id, option_id: change.id, label: change.after)
|
|
@@ -25,13 +25,33 @@ module Ecoportal
|
|
|
25
25
|
# MISMATCH scores 0 rather than vetoing (the field may have been re-purposed keeping its
|
|
26
26
|
# genome, or genome may be absent on newer field types). Returns nil when neither side
|
|
27
27
|
# carries a genome — the signal simply does not apply and is excluded from the average.
|
|
28
|
+
#
|
|
29
|
+
# LINEAGE (verified live 2026-08-05, same-org backup restore of a full template): a
|
|
30
|
+
# restore does NOT equal-copy the signature — it APPENDS one segment to it, for every
|
|
31
|
+
# membrane AND force (e.g. `2zwxkb6gns` → `2zwxkb6gns53jp`, `ktnrya` → `ktnrya877l`;
|
|
32
|
+
# 28/28 fields + 4/4 forces followed the rule). So an ancestor's signature is a strict
|
|
33
|
+
# PREFIX of its descendant's, and equality-only scoring would give the exact
|
|
34
|
+
# cross-object case this engine exists for (UAT backup → PROD restore) a 0.0 — worse
|
|
35
|
+
# than no signal. Prefix ancestry therefore scores just below an exact match.
|
|
28
36
|
def genome(source, target)
|
|
29
37
|
a = source['genomeSignature']
|
|
30
38
|
b = target['genomeSignature']
|
|
31
39
|
return nil if blank?(a) && blank?(b)
|
|
32
40
|
return 0.0 if blank?(a) || blank?(b)
|
|
41
|
+
return 1.0 if a == b
|
|
33
42
|
|
|
34
|
-
a
|
|
43
|
+
lineage?(a, b) ? 0.9 : 0.0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# True when one signature is a strict prefix of the other (restore/copy appends a
|
|
47
|
+
# lineage segment — see #genome). Direction is unknowable at a pairing call (either
|
|
48
|
+
# side may be the older document), so both directions count. Very short signatures
|
|
49
|
+
# are rejected as ancestry evidence — a trivial prefix can collide by chance.
|
|
50
|
+
def lineage?(sig_a, sig_b)
|
|
51
|
+
short, long = [sig_a.to_s, sig_b.to_s].minmax_by(&:length)
|
|
52
|
+
return false if short.length < 4
|
|
53
|
+
|
|
54
|
+
short != long && long.start_with?(short)
|
|
35
55
|
end
|
|
36
56
|
|
|
37
57
|
# Field type (__typename) equality. A type change is a strong DISQUALIFIER for a data
|
|
@@ -91,8 +91,11 @@ module Ecoportal
|
|
|
91
91
|
# addStageSection.stageId), threading through a placeholder when the parent is created in
|
|
92
92
|
# the same batch. nil (default) means no parent id is known/recorded — the historical
|
|
93
93
|
# behaviour, unchanged.
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
# `value:` (optional proc) is recorded on ADDED changes only — currently used for :option,
|
|
95
|
+
# whose addSelectFieldOption command requires the source doc's option.value (see
|
|
96
|
+
# Change#value). nil (default) means the kind has no `value` concept.
|
|
97
|
+
def diff_index(kind, before_idx, after_idx, label:, path:, parent: nil, owner: nil, value: nil, &compare)
|
|
98
|
+
procs = { label: label, path: path, parent: parent, owner: owner, value: value }
|
|
96
99
|
removed = (before_idx.keys - after_idx.keys).map { |id| removed_change(kind, id, before_idx[id], procs) }
|
|
97
100
|
added = (after_idx.keys - before_idx.keys).map { |id| added_change(kind, id, after_idx[id], procs) }
|
|
98
101
|
matched = (before_idx.keys & after_idx.keys).flat_map { |id| Array(compare.call(id, before_idx[id], after_idx[id])) }
|
|
@@ -105,7 +108,8 @@ module Ecoportal
|
|
|
105
108
|
|
|
106
109
|
def added_change(kind, id, info, procs)
|
|
107
110
|
Change.new(op: :added, kind: kind, id: id, label: procs[:label].call(info), path: procs[:path].call(info),
|
|
108
|
-
parent_id: procs[:parent]&.call(info), owner_id: procs[:owner]&.call(info)
|
|
111
|
+
parent_id: procs[:parent]&.call(info), owner_id: procs[:owner]&.call(info),
|
|
112
|
+
value: procs[:value]&.call(info))
|
|
109
113
|
end
|
|
110
114
|
|
|
111
115
|
def stage_changes
|
|
@@ -153,7 +157,7 @@ module Ecoportal
|
|
|
153
157
|
|
|
154
158
|
def options_diff(field_id, path, before_field, after_field)
|
|
155
159
|
diff_index(:option, index_options(before_field), index_options(after_field),
|
|
156
|
-
label: ->(o) { o[:label] }, path: ->(_o) { path }) do |k, b, a|
|
|
160
|
+
label: ->(o) { o[:label] }, path: ->(_o) { path }, value: ->(o) { o[:value] }) do |k, b, a|
|
|
157
161
|
cmp = []
|
|
158
162
|
cmp << changed(:option, k, a[:label], path, 'label', b[:label], a[:label], field_id) if b[:label] != a[:label]
|
|
159
163
|
cmp << changed(:option, k, a[:label], path, 'weight', b[:weight], a[:weight], field_id) if b[:weight] != a[:weight]
|
|
@@ -294,13 +298,16 @@ module Ecoportal
|
|
|
294
298
|
end
|
|
295
299
|
|
|
296
300
|
# Options keyed by id when present, else by value (primary) / name (auxiliary) — the
|
|
297
|
-
# correct select-option identity (NOT genome).
|
|
301
|
+
# correct select-option identity (NOT genome). `value` is carried through separately from
|
|
302
|
+
# `label` (they are distinct schema fields — OptionType#value vs #label/#name) so an ADDED
|
|
303
|
+
# option's Change can supply the real value addSelectFieldOption requires, never a
|
|
304
|
+
# label/weight stand-in.
|
|
298
305
|
def index_options(field)
|
|
299
306
|
Array(field['options']).each_with_object({}) do |o, h|
|
|
300
307
|
next unless o.is_a?(Hash)
|
|
301
308
|
|
|
302
309
|
key = o['id'] || o['value'] || o['label'] || o['name']
|
|
303
|
-
h[key] = { label: o['label'] || o['name'] || o['value'], weight: o['weight'] }
|
|
310
|
+
h[key] = { label: o['label'] || o['name'] || o['value'], weight: o['weight'], value: o['value'] }
|
|
304
311
|
end
|
|
305
312
|
end
|
|
306
313
|
|