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
|
@@ -1,24 +1,63 @@
|
|
|
1
1
|
module Ecoportal::API::GraphQL::Base::Page
|
|
2
2
|
class DataField
|
|
3
3
|
# File attachment field.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# READ: the fragment selects `items { id fileContainer { id ... } }` — the container id
|
|
5
|
+
# lives at items[].fileContainer.id, NOT in a `fileContainers` array (live-verified;
|
|
6
|
+
# the previous reader keyed on a doc key the API never returns).
|
|
7
|
+
# WRITE: live `FileInput` accepts `items: [ItemInput]` ({id, fileContainerId, position}),
|
|
8
|
+
# NOT `fileContainerIds`. The backend applies `items` as FULL-REPLACE — any existing item
|
|
9
|
+
# whose id is not echoed back is destroyed — so `as_input` sends kept items as `{id:}`
|
|
10
|
+
# and new containers as `{fileContainerId:}`.
|
|
11
|
+
# `items` is deliberately a plain doc reader, not `passarray`: materialising an ArrayModel
|
|
12
|
+
# writes `items: []` into the doc of a field read without $content, and a phantom-dirty
|
|
13
|
+
# empty `items` here doesn't no-op — it DELETES every attachment on the next update.
|
|
14
|
+
# Container ids come from a prior upload — see FileUpload::Client#upload_all.
|
|
7
15
|
class FileField < DataField
|
|
8
|
-
|
|
16
|
+
# each: { 'id' =>, 'fileContainer' => { 'id' =>, 'activeFileAttachment' => {...} } }
|
|
17
|
+
def items
|
|
18
|
+
Array(doc['items'])
|
|
19
|
+
end
|
|
9
20
|
|
|
10
21
|
def file_container_ids
|
|
11
|
-
|
|
22
|
+
items.map do |item|
|
|
23
|
+
item.is_a?(Hash) ? item.dig('fileContainer', 'id') : item
|
|
24
|
+
end.compact
|
|
12
25
|
end
|
|
13
26
|
|
|
27
|
+
# Full-replace: containers already on the field keep their item identity;
|
|
28
|
+
# ids not present in `ids` are removed server-side by omission.
|
|
14
29
|
def file_container_ids=(ids)
|
|
15
|
-
|
|
30
|
+
kept = {}
|
|
31
|
+
items.each do |item|
|
|
32
|
+
next unless item.is_a?(Hash) && (fc_id = item.dig('fileContainer', 'id'))
|
|
33
|
+
|
|
34
|
+
kept[fc_id] ||= item['id']
|
|
35
|
+
end
|
|
36
|
+
doc['items'] = Array(ids).map do |fc_id|
|
|
37
|
+
new_item = { 'fileContainer' => { 'id' => fc_id } }
|
|
38
|
+
new_item['id'] = kept[fc_id] if kept[fc_id]
|
|
39
|
+
new_item
|
|
40
|
+
end
|
|
16
41
|
end
|
|
17
42
|
|
|
18
43
|
def as_input
|
|
19
44
|
return nil unless dirty?
|
|
20
45
|
|
|
21
|
-
{ file: { id: id,
|
|
46
|
+
{ file: { id: id, items: items_input } }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def items_input
|
|
52
|
+
items.map do |item|
|
|
53
|
+
next unless item.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
if (item_id = item['id'])
|
|
56
|
+
{ id: item_id }
|
|
57
|
+
else
|
|
58
|
+
{ fileContainerId: item.dig('fileContainer', 'id') }
|
|
59
|
+
end
|
|
60
|
+
end.compact
|
|
22
61
|
end
|
|
23
62
|
end
|
|
24
63
|
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
module Ecoportal::API::GraphQL::Base::Page
|
|
2
2
|
class DataField
|
|
3
3
|
# Image gallery field.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# ★ WRITE PATH BROKEN (schema-invalid, live-verified 2026-08): `ImageGalleryInput` has no
|
|
5
|
+
# `fileContainerIds` — it takes `images: [ImageInput]`, attaching via `sourceId`, which is
|
|
6
|
+
# an `Enzyme::ImageReference::TempImage` (a DIFFERENT upload pipeline than file containers).
|
|
7
|
+
# Fixing this needs its own design — see the DataField shape-asymmetry review project.
|
|
8
|
+
# The reader is equally stale: live responses carry `images`, not `fileContainers`.
|
|
6
9
|
class ImageGallery < DataField
|
|
7
10
|
passarray :fileContainers # each: { id:, fileName:, url:, ... }
|
|
8
11
|
|
|
@@ -4,20 +4,16 @@ module Ecoportal::API::GraphQL::Base::Page
|
|
|
4
4
|
passarray :peopleIds
|
|
5
5
|
|
|
6
6
|
def people_ids
|
|
7
|
-
|
|
8
|
-
return ids unless ids.empty?
|
|
9
|
-
|
|
10
|
-
# Fallback: the read fragment populates `peopleIds`, but if a response ever returns it
|
|
11
|
-
# null/empty while the expanded `people { id }` nodes ARE present (a partial / non-
|
|
12
|
-
# $content read), seed from the nodes. The backend write replaces peopleIds wholesale,
|
|
13
|
-
# so a `people_ids << x` append must start from the existing set or it would WIPE the
|
|
14
|
-
# field. Mirrors CrossReference's references.nodes fallback. Read key == write key
|
|
15
|
-
# (peopleIds), so this only widens the read; it never creates phantom-dirty.
|
|
16
|
-
Array(doc['people']).map { |p| p.is_a?(Hash) ? p['id'] : p }.compact
|
|
7
|
+
people_ids_from(doc)
|
|
17
8
|
end
|
|
18
9
|
|
|
19
10
|
def people_ids=(ids)
|
|
20
|
-
|
|
11
|
+
# An explicit write makes `peopleIds` authoritative for THIS instance: without the
|
|
12
|
+
# flag, a deliberate clear (`people_ids = []`) on a `peopleIds: null` baseline would
|
|
13
|
+
# fall back to the `people` nodes on the doc side too, compare equal, and be silently
|
|
14
|
+
# dropped. The flag confines the fallback to un-written docs.
|
|
15
|
+
@people_ids_written = true
|
|
16
|
+
doc['peopleIds'] = Array(ids).compact
|
|
21
17
|
end
|
|
22
18
|
|
|
23
19
|
# v2 compat: scripts may set via people_ids= or direct assignment
|
|
@@ -25,10 +21,52 @@ module Ecoportal::API::GraphQL::Base::Page
|
|
|
25
21
|
self.people_ids = ids
|
|
26
22
|
end
|
|
27
23
|
|
|
24
|
+
# Dirty only when the effective people-id SET actually changed.
|
|
25
|
+
#
|
|
26
|
+
# The read response can carry `peopleIds: null` while still populating the expanded
|
|
27
|
+
# `people { id }` (and `cachedPeople`) nodes (a partial / non-$content read). The
|
|
28
|
+
# default key-level leaf diff baselines on `original_doc['peopleIds']` (null), so
|
|
29
|
+
# writing the SAME id set as already attached looks like a brand-new key and flags
|
|
30
|
+
# a change — a phantom-dirty update that sends `peopleIds` for a no-op. Compare id
|
|
31
|
+
# SETS (falling back to `people[].id` on both sides) instead. Mirrors CrossReference's
|
|
32
|
+
# `references.nodes` fallback / `dirty?` override for the identical disease.
|
|
33
|
+
def dirty?
|
|
34
|
+
effective_people_ids.sort != people_ids_from(original_doc).sort
|
|
35
|
+
end
|
|
36
|
+
|
|
28
37
|
def as_input
|
|
29
38
|
return nil unless dirty?
|
|
30
39
|
|
|
31
|
-
{ people: { id: id, peopleIds:
|
|
40
|
+
{ people: { id: id, peopleIds: effective_people_ids } }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# The id set the client currently means: after an explicit `people_ids=` write the raw
|
|
46
|
+
# `peopleIds` doc key is authoritative (so `[]` means CLEAR, no fallback); before any
|
|
47
|
+
# write it is the shape-agnostic read (fallback to `people[].id` nodes). Keeping
|
|
48
|
+
# `as_input` on the same source as `dirty?` also prevents an explicit clear from
|
|
49
|
+
# re-serialising fallback-seeded node ids.
|
|
50
|
+
def effective_people_ids
|
|
51
|
+
return Array(doc['peopleIds']).compact if @people_ids_written
|
|
52
|
+
|
|
53
|
+
people_ids_from(doc)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Extract the people id set from a doc snapshot. `original_doc` is the server baseline
|
|
57
|
+
# (the diff prev_doc — see Diffable::ClassicDiffService), `doc` carries client changes.
|
|
58
|
+
# Prefer `peopleIds` when it is present and non-empty (the write shape, and the normal
|
|
59
|
+
# read shape); fall back to the expanded `people[].id` nodes (the read-only lookup
|
|
60
|
+
# shape) when `peopleIds` is absent/null/empty — so the comparison is shape-agnostic on
|
|
61
|
+
# both sides and stays correct after `consolidate!` copies `doc` into `original_doc`.
|
|
62
|
+
# `people` nodes are a lookup only, never written.
|
|
63
|
+
def people_ids_from(source_doc)
|
|
64
|
+
return [] unless source_doc
|
|
65
|
+
|
|
66
|
+
ids = Array(source_doc['peopleIds']).compact
|
|
67
|
+
return ids unless ids.empty?
|
|
68
|
+
|
|
69
|
+
Array(source_doc['people']).map { |p| p.is_a?(Hash) ? p['id'] : p }.compact
|
|
32
70
|
end
|
|
33
71
|
end
|
|
34
72
|
end
|
|
@@ -10,24 +10,12 @@ module Ecoportal
|
|
|
10
10
|
#
|
|
11
11
|
# A DataField's array attributes (`options`, `peopleIds`, `contractors`,
|
|
12
12
|
# `pages`, `fileContainers`, `items`, ...) are declared with `passarray`,
|
|
13
|
-
# which registers them as *cascaded* attributes
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
# unaffected). For a leaf model we want every doc key diffed inline, so this
|
|
20
|
-
# service keeps cascaded keys in place.
|
|
21
|
-
class LeafDiffService < Ecoportal::API::Common::GraphQL::Model::Diffable::DiffService
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
# Keep all doc keys: leaf-field arrays are data, not nested models.
|
|
25
|
-
def doc_with_non_cascaded_attributes(json)
|
|
26
|
-
json
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
DIFF_CLASS = LeafDiffService
|
|
13
|
+
# which registers them as *cascaded* attributes invisible to the default
|
|
14
|
+
# `DiffService` (see `Diffable::LeafDiffService` for the full explanation).
|
|
15
|
+
# A DataField is a pure leaf model (no nested `GraphQL::Model` children), so
|
|
16
|
+
# it diffs `flat: true` — the promoted shared `LeafDiffService` keeps the
|
|
17
|
+
# array doc keys inline.
|
|
18
|
+
DIFF_CLASS = Ecoportal::API::Common::GraphQL::Model::Diffable::LeafDiffService
|
|
31
19
|
# Map GraphQL __typename values to subclass names.
|
|
32
20
|
TYPE_MAP = {
|
|
33
21
|
'PlainText' => 'PlainText',
|
|
@@ -55,6 +43,12 @@ module Ecoportal
|
|
|
55
43
|
|
|
56
44
|
passkey :id
|
|
57
45
|
passthrough :label, :deindex, :genomeSignature
|
|
46
|
+
# `read_only: true` is load-bearing, not documentation: `description` is readable on
|
|
47
|
+
# `DataFieldsInterface` but ABSENT from `DataFieldInput` and every per-type member of
|
|
48
|
+
# it, so a writable accessor would make the field dirty and push a key `updatePage`
|
|
49
|
+
# cannot accept. The only writer is the `editFieldConfiguration` workflow command
|
|
50
|
+
# (`Input::WorkflowCommand::EditFieldConfiguration`).
|
|
51
|
+
passthrough :description, read_only: true
|
|
58
52
|
# __typename is stored under the 'type' key after mapping
|
|
59
53
|
passthrough :type
|
|
60
54
|
|
|
@@ -10,6 +10,9 @@ module Ecoportal
|
|
|
10
10
|
passkey :id
|
|
11
11
|
passthrough :name, :ordering, :state
|
|
12
12
|
passboolean :active, :started
|
|
13
|
+
# Per-stage tag set (Stage.tags in the schema). Fetched by the stageAttrs
|
|
14
|
+
# fragment; a component of #all_tags. Empty array when not queried.
|
|
15
|
+
passarray :tags
|
|
13
16
|
|
|
14
17
|
class_resolver :callback_type_class, 'Ecoportal::API::GraphQL::Base::PagesWorkflow::CallbackType'
|
|
15
18
|
|
|
@@ -17,6 +20,54 @@ module Ecoportal
|
|
|
17
20
|
# canonical field objects (filtered to this stage) rather than fresh dups.
|
|
18
21
|
attr_writer :page_field_collection
|
|
19
22
|
|
|
23
|
+
# The owning page, resolved via the DoubleModel's NATIVE nesting reference rather
|
|
24
|
+
# than a manually-injected back-ref. A stage is embedded under the page as
|
|
25
|
+
# page -> stages (CollectionModel) -> stage, so `_parent` is the intermediate
|
|
26
|
+
# collection, NOT the page. `root` walks the whole `_parent` chain to the top of
|
|
27
|
+
# the doc model (the page), which is robust to that intermediate nesting.
|
|
28
|
+
#
|
|
29
|
+
# Returns the page for an embedded stage, or nil for a standalone stage whose root
|
|
30
|
+
# is itself (i.e. not a page — has no baseTags/otherTags/location_ids to fold in).
|
|
31
|
+
def page
|
|
32
|
+
top = root
|
|
33
|
+
top.equal?(self) ? nil : top
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# location_ids redirect: the GraphQL Stage type has NO location field — locations
|
|
37
|
+
# are assigned at the PAGE level (page.locations { id }). So a stage's location ids
|
|
38
|
+
# are the page's location ids. Empty array for a standalone stage.
|
|
39
|
+
def location_ids
|
|
40
|
+
Array(page&.location_ids)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The stage's fully-compiled tag set (client-side build of the backend-only
|
|
44
|
+
# "compiled_tags"), per the domain formula:
|
|
45
|
+
#
|
|
46
|
+
# Stage.all_tags = page.baseTags + page.location_ids + page.otherTags + stage.tags
|
|
47
|
+
#
|
|
48
|
+
# (page.location_ids — surfaced here via #location_ids — stands in for the per-stage
|
|
49
|
+
# "location_ids" of the formula: the GraphQL schema does NOT expose locations on the
|
|
50
|
+
# Stage type, they are assigned at the PAGE level.)
|
|
51
|
+
#
|
|
52
|
+
# Returns a de-duplicated Array of tag strings. For a standalone stage (no owning
|
|
53
|
+
# page) folds in only the stage's own tags.
|
|
54
|
+
def all_tags
|
|
55
|
+
(page_base_tags + location_ids + page_other_tags + Array(tags)).
|
|
56
|
+
compact.uniq
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def page_base_tags
|
|
62
|
+
page ? Array(page.baseTags) : []
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def page_other_tags
|
|
66
|
+
page ? Array(page.otherTags) : []
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
public
|
|
70
|
+
|
|
20
71
|
def workflow_callbacks
|
|
21
72
|
@workflow_callbacks ||= Array(doc['workflowCallbacks'] || []).map { |cb| callback_type_class.from_doc(cb) }
|
|
22
73
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# Permissions ? the org-level ability set (Organization.permissions).
|
|
6
|
+
# Every field is a `canXXX: AuthorizationResult!` permission check. Read-only.
|
|
7
|
+
# The concrete embeds_one declarations live on Model::Permissions (they reference
|
|
8
|
+
# Model::AuthorizationResult).
|
|
9
|
+
class Permissions < Logic::BaseModel
|
|
10
|
+
read_only!
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# A single audit entry in a person member's changelog history, returned by
|
|
6
|
+
# `PersonMember.changelogs` / `Organization.personMemberChangelogs`. Read-only.
|
|
7
|
+
#
|
|
8
|
+
# Enum-valued fields are exposed as plain strings:
|
|
9
|
+
# - operation / accountOperation: 'CREATE' | 'UPDATE' | 'DESTROY'
|
|
10
|
+
class PersonMemberChangelog < Logic::BaseModel
|
|
11
|
+
read_only!
|
|
12
|
+
|
|
13
|
+
passkey :id
|
|
14
|
+
passthrough :operation, :accountOperation
|
|
15
|
+
passthrough :personMemberId, :apiKeyPrefix
|
|
16
|
+
passboolean :changedByApi
|
|
17
|
+
passarray :humanReadableDifferences
|
|
18
|
+
embeds_one :createdAt, klass: Base::DateTime
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -5,11 +5,146 @@ module Ecoportal
|
|
|
5
5
|
# A register preset view — saved column/sort/filter configuration for previewPages.
|
|
6
6
|
# Scripts use the id as `presetViewId` in register.previewPages queries.
|
|
7
7
|
class PresetView < Logic::BaseModel
|
|
8
|
+
# `fieldConfigurations` is a passarray (leaf) invisible to the default
|
|
9
|
+
# DiffService; LeafDiffService detects it as a full-set replace, which is what
|
|
10
|
+
# makes a columns-only change reach `#as_input` at all (before it, the array was
|
|
11
|
+
# dropped from the flat diff and no mutation was issued). The read-key/shape
|
|
12
|
+
# conversion that change then needs is handled below — the GAP-P1 that comment
|
|
13
|
+
# referred to as a separate pending converter is closed by it.
|
|
14
|
+
# See Diffable::LeafDiffService.
|
|
15
|
+
DIFF_CLASS = Ecoportal::API::Common::GraphQL::Model::Diffable::LeafDiffService
|
|
16
|
+
|
|
17
|
+
# READ key vs WRITE key — and, on update, read SHAPE vs write SHAPE.
|
|
18
|
+
#
|
|
19
|
+
# The API RETURNS the collection as `fieldConfigurations`, a plain list (see the
|
|
20
|
+
# `passarray` below — that stays as-is, consumers read it). The mutations call it
|
|
21
|
+
# `fieldConfigs`, and the two mutations do NOT agree on its shape. Schema-verified
|
|
22
|
+
# against the live introspection dump (20260730):
|
|
23
|
+
#
|
|
24
|
+
# CreatePresetViewInput.fieldConfigs : [FieldConfigurationInput!]!
|
|
25
|
+
# UpdatePresetViewInput.fieldConfigs : FieldConfigurationOneToManyInput!
|
|
26
|
+
# { additions:, deletions:, updates: }
|
|
27
|
+
#
|
|
28
|
+
# Both are NON_NULL with no default value — i.e. REQUIRED on every call — while the
|
|
29
|
+
# `FieldConfigurationOneToManyInput` members each default to `[]`, so `{}` is a
|
|
30
|
+
# valid no-op value for an update that does not touch the columns.
|
|
31
|
+
#
|
|
32
|
+
# Emitting the read key `fieldConfigurations` is an unknown-argument error, and
|
|
33
|
+
# emitting the read LIST under `fieldConfigs` is a type error: either one rejects
|
|
34
|
+
# the WHOLE mutation, a `name`-only edit included.
|
|
35
|
+
#
|
|
36
|
+
# `#as_input` emits the UPDATE shape. `Logic::Mutation#as_input` hands a read model
|
|
37
|
+
# straight to `model.as_input` without telling it which mutation it feeds, and the
|
|
38
|
+
# only model-driven path is `register.preset_view.update`: a read model carries an
|
|
39
|
+
# `id`, which `CreatePresetViewInput` does not even accept. Create is built from a
|
|
40
|
+
# caller-supplied Hash (see Builder::Register::PresetView) whose `fieldConfigs` is
|
|
41
|
+
# already the plain array the schema wants — it needs no conversion.
|
|
42
|
+
READ_KEY = 'fieldConfigurations'.freeze
|
|
43
|
+
|
|
44
|
+
# The fields `FieldConfigurationInput` accepts (same dump). The read nodes also
|
|
45
|
+
# carry `__typename` — the read side is a union, see Fragment::FieldConfiguration —
|
|
46
|
+
# which the input type does NOT accept, so nodes are whitelisted through this list
|
|
47
|
+
# rather than passed along verbatim.
|
|
48
|
+
FIELD_CONFIG_INPUT_KEYS = %i[
|
|
49
|
+
id key name weight dataFieldLabel type showTime valueToShow mode displayUid
|
|
50
|
+
].freeze
|
|
51
|
+
|
|
8
52
|
passkey :id
|
|
9
53
|
passthrough :name, :registerId
|
|
10
54
|
passboolean :global
|
|
11
55
|
passarray :fieldConfigurations
|
|
12
56
|
passthrough :sorter # { key:, direction:, missing: }
|
|
57
|
+
|
|
58
|
+
class << self
|
|
59
|
+
# Turns a raw mutation-input diff into a valid `UpdatePresetViewInput`: drops the
|
|
60
|
+
# read key and sets `fieldConfigs` to the one-to-many delta.
|
|
61
|
+
#
|
|
62
|
+
# Idempotent — the read key is deleted and the delta is recomputed from
|
|
63
|
+
# `doc`/`original_doc`, so a second application yields the same Hash. That is what
|
|
64
|
+
# makes it safe for both seams (`#as_input` and `Input::PresetView::Update
|
|
65
|
+
# .from_model`) to call it on the same Hash.
|
|
66
|
+
#
|
|
67
|
+
# @param model [PresetView] the subject (read) model.
|
|
68
|
+
# @param input [Hash, nil] the mutation input hash so far (nil when nothing in the
|
|
69
|
+
# flat diff changed).
|
|
70
|
+
# @return [Hash, nil] +input+ with a schema-valid `fieldConfigs`, or nil when
|
|
71
|
+
# neither the flat diff nor the columns changed (no mutation to send).
|
|
72
|
+
def build_update_input(model, input)
|
|
73
|
+
return input unless model.respond_to?(:field_configs_delta)
|
|
74
|
+
|
|
75
|
+
delta = model.field_configs_delta
|
|
76
|
+
return input if input.nil? && delta.empty?
|
|
77
|
+
|
|
78
|
+
input ||= {}
|
|
79
|
+
input.delete(:fieldConfigurations)
|
|
80
|
+
input.delete(READ_KEY)
|
|
81
|
+
input[:id] ||= model.id if model.respond_to?(:id)
|
|
82
|
+
input[:fieldConfigs] = delta
|
|
83
|
+
input
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @param node [Hash] one `fieldConfigurations` read node.
|
|
87
|
+
# @return [Hash] the node reduced to the keys `FieldConfigurationInput` accepts,
|
|
88
|
+
# symbol-keyed. Keys the read selection did not ask for are simply absent.
|
|
89
|
+
def field_config_input(node)
|
|
90
|
+
FIELD_CONFIG_INPUT_KEYS.each_with_object({}) do |key, out|
|
|
91
|
+
str = key.to_s
|
|
92
|
+
out[key] = node[str] if node.key?(str)
|
|
93
|
+
out[key] = node[key] if node.key?(key)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The `FieldConfigurationOneToManyInput` delta between the server baseline
|
|
99
|
+
# (`original_doc`) and the current `doc`, matched on the config `id`:
|
|
100
|
+
# `additions` = nodes with no known id, `deletions` = baseline ids that are gone,
|
|
101
|
+
# `updates` = known nodes whose (input-visible) content changed. Empty members are
|
|
102
|
+
# omitted, so an untouched collection yields `{}` — the valid no-op.
|
|
103
|
+
#
|
|
104
|
+
# Computed DIRECTLY from the docs rather than from the flat diff, for the same
|
|
105
|
+
# reason as {Logic::Input::IdDiffFields}: `passarray` leaves are dropped by the
|
|
106
|
+
# cascaded DiffService.
|
|
107
|
+
#
|
|
108
|
+
# @return [Hash] `{ additions:, deletions:, updates: }`, empty members omitted.
|
|
109
|
+
def field_configs_delta
|
|
110
|
+
current = field_config_nodes(doc)
|
|
111
|
+
original = field_config_nodes(original_doc)
|
|
112
|
+
known = original.filter_map {|node| node[:id]}
|
|
113
|
+
|
|
114
|
+
additions = current.reject {|node| node[:id] && known.include?(node[:id])}
|
|
115
|
+
updates = (current - additions) - original
|
|
116
|
+
deletions = known - current.filter_map {|node| node[:id]}
|
|
117
|
+
|
|
118
|
+
{}.tap do |delta|
|
|
119
|
+
delta[:additions] = additions unless additions.empty?
|
|
120
|
+
delta[:deletions] = deletions unless deletions.empty?
|
|
121
|
+
delta[:updates] = updates unless updates.empty?
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Mutation input for this preset view, shaped for `updatePresetView`.
|
|
126
|
+
# @see .build_update_input
|
|
127
|
+
def as_input(target_class: nil, clientMutationId: '')
|
|
128
|
+
input = self.class.build_update_input(self, super)
|
|
129
|
+
return input if input.nil? || target_class
|
|
130
|
+
|
|
131
|
+
# `super` merges it only when it built the Hash itself; a columns-only change
|
|
132
|
+
# synthesises the Hash here, so keep the wire payload identical either way.
|
|
133
|
+
input[:clientMutationId] = clientMutationId.to_s unless input.key?(:clientMutationId)
|
|
134
|
+
input
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
# @param source_doc [Hash, nil] `doc` or `original_doc`.
|
|
140
|
+
# @return [Array<Hash>] the read nodes, each reduced to input-accepted keys.
|
|
141
|
+
def field_config_nodes(source_doc)
|
|
142
|
+
return [] if source_doc.nil?
|
|
143
|
+
|
|
144
|
+
Array(source_doc[READ_KEY]).
|
|
145
|
+
grep(Hash).
|
|
146
|
+
map {|node| self.class.field_config_input(node)}
|
|
147
|
+
end
|
|
13
148
|
end
|
|
14
149
|
end
|
|
15
150
|
end
|
|
@@ -3,6 +3,11 @@ module Ecoportal
|
|
|
3
3
|
class GraphQL
|
|
4
4
|
module Base
|
|
5
5
|
class Register < Logic::BaseModel
|
|
6
|
+
# `filterTags` is a passarray (leaf) that the default DiffService cannot
|
|
7
|
+
# diff; LeafDiffService detects it as a full-set replace. See
|
|
8
|
+
# Diffable::LeafDiffService.
|
|
9
|
+
DIFF_CLASS = Ecoportal::API::Common::GraphQL::Model::Diffable::LeafDiffService
|
|
10
|
+
|
|
6
11
|
passkey :id
|
|
7
12
|
passthrough :name, :iconName, :moduleType, :registerStatus
|
|
8
13
|
passthrough :descriptionText, :descriptionImageUrl
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
module Template
|
|
6
|
+
# One entry of a TEMPLATE's change log — a single command the platform recorded
|
|
7
|
+
# against the template. Read-only.
|
|
8
|
+
#
|
|
9
|
+
# ── Where it comes from (verified in the Rails backend on 2026-08-19) ────────────
|
|
10
|
+
# `app/graphql/types/pages/template_type.rb` declares
|
|
11
|
+
# field :commands, Types::Pages::Interfaces::WorkflowCommandInterface.connection_type
|
|
12
|
+
# and resolves it as
|
|
13
|
+
# NewEp::Pages::Workflows::Commands::Base.where(page: object.page)
|
|
14
|
+
# .from_template_builder.order(_id: :desc)
|
|
15
|
+
#
|
|
16
|
+
# The narrow `Template` type itself is reachable as `template { ... }` on any page,
|
|
17
|
+
# because `app/graphql/types/pages/interfaces/base_page_interface.rb:27` declares
|
|
18
|
+
# field :template, Types::Pages::TemplateType, null: true
|
|
19
|
+
#
|
|
20
|
+
# ── ★ TWO ACCESS CAVEATS — both are silent, neither raises ─────────────────────
|
|
21
|
+
# 1. `commands` returns **nil**, not an empty connection, unless the caller satisfies
|
|
22
|
+
# `Pages::TemplatePolicy#update_information?` (`return unless allowed_to?(...)`).
|
|
23
|
+
# A read-only account therefore sees NO change log and gets no error saying so.
|
|
24
|
+
# Treat a nil connection as "not permitted", never as "nothing changed".
|
|
25
|
+
# 2. The scope is `.from_template_builder` — only `TEMPLATE_BUILDER`-sourced commands
|
|
26
|
+
# appear. `WORKFLOW_BUILDER` commands against the same page are EXCLUDED
|
|
27
|
+
# (`app/models/new_ep/pages/workflows/commands/base.rb`, `enum_field :feature_source`
|
|
28
|
+
# with values WORKFLOW_BUILDER / TEMPLATE_BUILDER). For the workflow-builder side of
|
|
29
|
+
# the same log see `Query::PagesWorkflowCommands`.
|
|
30
|
+
# Ordering is `_id: :desc` — newest first, server-side, not configurable.
|
|
31
|
+
#
|
|
32
|
+
# ── ★ WHY THIS LOG BEATS POLLING A WATERMARK ──────────────────────────────────
|
|
33
|
+
# `app/services/new_ep/pages/run_merged_forces.rb:17` writes with
|
|
34
|
+
# @page.timeless.skip_patch_incr.rewrite(only_if: { updated_at: @page.updated_at.utc })
|
|
35
|
+
# so a forces run bumps NEITHER `patchVer` NOR `updatedAt`. Any change tracker that
|
|
36
|
+
# polls a watermark is structurally blind to force-driven change; the command log is
|
|
37
|
+
# per-change and cannot be skipped that way.
|
|
38
|
+
# (OPEN: whether forces run against TEMPLATES specifically is NOT confirmed — do not
|
|
39
|
+
# assume either way.)
|
|
40
|
+
#
|
|
41
|
+
# ── Field set ─────────────────────────────────────────────────────────────────
|
|
42
|
+
# These are the fields of the INTERFACE `PagesWorkflowCommandInterface` only
|
|
43
|
+
# (`app/graphql/types/pages/interfaces/workflow_command_interface.rb`). The concrete
|
|
44
|
+
# implementing types add their own (`PagesWorkflowCommandChange.changes`,
|
|
45
|
+
# `PagesWorkflowCommandChangeMessage.changeMessages`,
|
|
46
|
+
# `PagesWorkflowCommandEsChange.changes`); `__typename` is passed through so a caller
|
|
47
|
+
# can branch, and those extras can be selected with an inline fragment or by reusing
|
|
48
|
+
# `Fragment :PagesWorkflowCommandFields`.
|
|
49
|
+
#
|
|
50
|
+
# Enum-valued fields are exposed as plain strings:
|
|
51
|
+
# - commandAction: WorkflowCommandActionEnum
|
|
52
|
+
class ChangeLogEntry < Logic::BaseModel
|
|
53
|
+
read_only!
|
|
54
|
+
|
|
55
|
+
passkey :id
|
|
56
|
+
# Concrete type name: 'PagesWorkflowCommandChange' | 'PagesWorkflowCommandChangeMessage'
|
|
57
|
+
# | 'PagesWorkflowCommandEsChange'.
|
|
58
|
+
passthrough :__typename
|
|
59
|
+
passthrough :commandAction
|
|
60
|
+
# Optimistic-concurrency token the command was recorded against (Int!).
|
|
61
|
+
passthrough :patchVer
|
|
62
|
+
# `owner` (the UserType embed) is added in Model::Template::ChangeLogEntry — Base
|
|
63
|
+
# loads before Model.
|
|
64
|
+
passthrough :ownerId
|
|
65
|
+
passthrough :stageId, :stageName
|
|
66
|
+
passthrough :taskId, :taskType
|
|
67
|
+
# What the command acted on. Resolved server-side from `targeted_resource` and
|
|
68
|
+
# nil for commands that target nothing.
|
|
69
|
+
passthrough :targetedResourceId, :targetedResourceName, :targetedResourceType
|
|
70
|
+
|
|
71
|
+
# `createdAt`/`updatedAt` are `DateTimeType` OBJECTS ({ dateTime, timeZone }), not
|
|
72
|
+
# scalars — hence an embed rather than `passdate`. Use `entry.createdAt.dateTime`.
|
|
73
|
+
embeds_one :createdAt, klass: Base::DateTime
|
|
74
|
+
embeds_one :updatedAt, klass: Base::DateTime
|
|
75
|
+
|
|
76
|
+
# ── What actually changed (only populated by the DETAIL selection) ──────────
|
|
77
|
+
# Present only when the node was selected with `Fragment :TemplateChangeLogEntryDetail`
|
|
78
|
+
# (see `Query::TemplateChangeLog.detail_block` / `api.template.change_log(detail: true)`).
|
|
79
|
+
# With the default light selection both readers are empty — empty means
|
|
80
|
+
# "not selected", NOT "nothing changed", exactly like the nil-connection caveat above.
|
|
81
|
+
#
|
|
82
|
+
# Deliberately raw hashes rather than a typed embed: the two `changes`-bearing types
|
|
83
|
+
# do not share a shape (`ChangeType.from/to` are Strings;
|
|
84
|
+
# `StoredEsFilterChangeType.from/to` are objects), so one typed model would have to
|
|
85
|
+
# lie about one of them. This mirrors `Base::PagesWorkflow::CommandChange#changes`,
|
|
86
|
+
# which does the same for the workflow-builder side of the same interface.
|
|
87
|
+
def changes
|
|
88
|
+
Array(doc['changes'])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
passarray :changeMessages
|
|
92
|
+
|
|
93
|
+
# Which of the three concrete command types this entry is, as a symbol:
|
|
94
|
+
# :changes | :change_messages | :es_changes | nil (unknown/unselected `__typename`).
|
|
95
|
+
# Use it instead of string-matching `__typename` at call sites.
|
|
96
|
+
DETAIL_KINDS = {
|
|
97
|
+
'PagesWorkflowCommandChange' => :changes,
|
|
98
|
+
'PagesWorkflowCommandChangeMessage' => :change_messages,
|
|
99
|
+
'PagesWorkflowCommandEsChange' => :es_changes
|
|
100
|
+
}.freeze
|
|
101
|
+
|
|
102
|
+
def detail_kind
|
|
103
|
+
DETAIL_KINDS[__typename]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# True when this entry carries field-level detail. False both when the light
|
|
107
|
+
# selection was used and when a command genuinely recorded no changes -- those two
|
|
108
|
+
# are indistinguishable from the response alone, which is why the caller should know
|
|
109
|
+
# which selection it asked for.
|
|
110
|
+
def detail?
|
|
111
|
+
!changes.empty? || !changeMessages.to_a.empty?
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -20,13 +20,22 @@ require_relative 'base/page'
|
|
|
20
20
|
require_relative 'base/resource'
|
|
21
21
|
require_relative 'base/action_category'
|
|
22
22
|
require_relative 'base/action'
|
|
23
|
+
require_relative 'base/action_event_page'
|
|
24
|
+
require_relative 'base/action_event'
|
|
23
25
|
require_relative 'base/contractor_entity'
|
|
24
26
|
require_relative 'base/preview_page'
|
|
25
27
|
require_relative 'base/delta_result'
|
|
28
|
+
require_relative 'base/authorization_result'
|
|
29
|
+
require_relative 'base/permissions'
|
|
30
|
+
require_relative 'base/activity'
|
|
31
|
+
require_relative 'base/activity_log'
|
|
32
|
+
require_relative 'base/auth_log'
|
|
33
|
+
require_relative 'base/person_member_changelog'
|
|
26
34
|
require_relative 'base/organization'
|
|
27
35
|
|
|
28
36
|
require_relative 'base/register'
|
|
29
37
|
require_relative 'base/preset_view'
|
|
38
|
+
require_relative 'base/minimal_dashboard'
|
|
30
39
|
|
|
31
40
|
require_relative 'base/ai_summary_version'
|
|
32
41
|
|
|
@@ -21,6 +21,12 @@ module Ecoportal
|
|
|
21
21
|
destroyMutation.query(**kargs, &block)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# updateContractorManagerSettings — auto-approval settings + whether
|
|
25
|
+
# contractor admins may add other admins. Returns the updated Organization.
|
|
26
|
+
def update_manager_settings(**kargs, &block)
|
|
27
|
+
updateManagerSettingsMutation.query(**kargs, &block)
|
|
28
|
+
end
|
|
29
|
+
|
|
24
30
|
private
|
|
25
31
|
|
|
26
32
|
def createMutation
|
|
@@ -34,6 +40,10 @@ module Ecoportal
|
|
|
34
40
|
def destroyMutation
|
|
35
41
|
Mutation::ContractorEntity::Destroy.new(client)
|
|
36
42
|
end
|
|
43
|
+
|
|
44
|
+
def updateManagerSettingsMutation
|
|
45
|
+
Mutation::ContractorEntity::UpdateManagerSettings.new(client)
|
|
46
|
+
end
|
|
37
47
|
end
|
|
38
48
|
end
|
|
39
49
|
end
|