ecoportal-api-graphql 1.3.16 → 2.0.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 +4 -4
- data/CHANGELOG.md +384 -6
- 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 +26 -3
- data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +47 -8
- 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/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 +136 -1
- data/lib/ecoportal/api/graphql/base/register.rb +5 -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 +18 -0
- 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.rb +5 -0
- data/lib/ecoportal/api/graphql/diff/pairing/signals.rb +21 -1
- data/lib/ecoportal/api/graphql/file_upload/client.rb +199 -140
- 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/pages/common_page_union.rb +21 -0
- data/lib/ecoportal/api/graphql/fragment/permissions.rb +98 -0
- data/lib/ecoportal/api/graphql/fragment/person_member_changelog.rb +33 -0
- data/lib/ecoportal/api/graphql/fragment.rb +7 -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/update_command.rb +6 -2
- 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_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 +12 -2
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_public_config.rb +19 -0
- 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 +27 -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/cross_reference.rb +3 -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 +3 -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/move_field.rb +3 -2
- 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 +43 -5
- data/lib/ecoportal/api/graphql/input.rb +1 -0
- data/lib/ecoportal/api/graphql/interface/base_page.rb +36 -3
- data/lib/ecoportal/api/graphql/interface/location_structure/command.rb +2 -1
- 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 +27 -0
- data/lib/ecoportal/api/graphql/model/permissions.rb +179 -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.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/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.rb +6 -0
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +73 -12
- 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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# A single "System Access Log" entry, returned by
|
|
6
|
+
# `currentOrganization.activityLogs` (ActivityLogUnionConnection). Read-only.
|
|
7
|
+
#
|
|
8
|
+
# `ActivityLogUnion` is a UNION over 9 members:
|
|
9
|
+
# DashboardAccess, DashboardCreate, DashboardDelete, DashboardUpdate,
|
|
10
|
+
# DeniedDashboardAccess, DeniedPageAccess, FileDownload, PageAccess,
|
|
11
|
+
# ScimIntegrationAccess.
|
|
12
|
+
# This models the field set COMMON to ALL members and passes the concrete
|
|
13
|
+
# `__typename` through. The Dashboard* members additionally expose
|
|
14
|
+
# `parentResource*` + `resource` (see Model::ActivityLog); page/file/scim
|
|
15
|
+
# members do not, so those live on the Model subclass as nullable embeds.
|
|
16
|
+
#
|
|
17
|
+
# Enum-valued fields are exposed as plain strings:
|
|
18
|
+
# - resourceType: ResourceTypesEnum
|
|
19
|
+
class ActivityLog < Logic::BaseModel
|
|
20
|
+
read_only!
|
|
21
|
+
|
|
22
|
+
passkey :id
|
|
23
|
+
# Concrete member type (e.g. 'DashboardAccess', 'PageAccess', 'FileDownload').
|
|
24
|
+
passthrough :__typename
|
|
25
|
+
passthrough :activityType
|
|
26
|
+
passthrough :source
|
|
27
|
+
passthrough :userType
|
|
28
|
+
passthrough :accountStatus
|
|
29
|
+
passthrough :becomingEmail
|
|
30
|
+
# content: Hash! — free-form activity payload; passed through as a Hash.
|
|
31
|
+
passthrough :content
|
|
32
|
+
passthrough :resourceId, :resourceName, :resourceType
|
|
33
|
+
embeds_one :createdAt, klass: Base::DateTime
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
module API
|
|
3
3
|
class GraphQL
|
|
4
4
|
module Base
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# One generation version of an AI page summary — the `item` of `generatePageSummary`.
|
|
6
|
+
#
|
|
7
|
+
# ★ Corrected 2026-07-30 against the live schema. The previous declarations (`prompt`,
|
|
8
|
+
# `inputTokens`, `outputTokens`, `totalTokens`, `resourceType`, `ratings`) do not exist on
|
|
9
|
+
# `AiSummaryVersion` and always read nil. The real fields are: aiSummary, alreadyRated,
|
|
10
|
+
# createdAt, dataFieldLogs, generationErrors, id, pagePatchVer, status.
|
|
11
|
+
#
|
|
12
|
+
# `dataFieldLogs` / `generationErrors` / `createdAt` are left unmodelled until their own
|
|
13
|
+
# shapes are verified — select and model them deliberately rather than by assumption.
|
|
7
14
|
class AiSummaryVersion < Logic::BaseModel
|
|
8
|
-
passkey
|
|
9
|
-
passthrough :aiSummary, :status, :
|
|
10
|
-
|
|
11
|
-
passthrough :resourceType # "field" | "stage" | "page"
|
|
12
|
-
passarray :ratings # [[user_id, score], ...]
|
|
15
|
+
passkey :id
|
|
16
|
+
passthrough :aiSummary, :status, :pagePatchVer
|
|
17
|
+
passboolean :alreadyRated
|
|
13
18
|
end
|
|
14
19
|
end
|
|
15
20
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# System access log entry (SCIM / security auditing), returned by
|
|
6
|
+
# `Organization.authLogs`. Read-only.
|
|
7
|
+
#
|
|
8
|
+
# Enum-valued fields are exposed as plain strings:
|
|
9
|
+
# - event: 'LOGIN' | 'LOGOUT' | 'PASSWORD_CHANGE' | 'PASSWORD_RESET_REQUEST'
|
|
10
|
+
# - loginMethod: 'PASSWORD' | 'SAML' | 'MAGIC_LINK' | 'REDIRECT' | 'OAUTH'
|
|
11
|
+
# - source: 'WEB' | 'MOBILE'
|
|
12
|
+
class AuthLog < Logic::BaseModel
|
|
13
|
+
read_only!
|
|
14
|
+
|
|
15
|
+
passkey :id
|
|
16
|
+
passthrough :event, :source
|
|
17
|
+
passthrough :loginMethod
|
|
18
|
+
passthrough :host, :ipAddress, :referrer, :userAgent
|
|
19
|
+
embeds_one :createdAt, klass: Base::DateTime
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# AuthorizationResult — the result of a permission / ability check (a `canXXX` field).
|
|
6
|
+
# value: Boolean! — whether the action is allowed
|
|
7
|
+
# message: String — human-readable reason when denied
|
|
8
|
+
# reasons: FailureReasons — structured reasons (not modelled here; omitted from the
|
|
9
|
+
# :AuthorizationResult fragment to avoid pulling that subtree).
|
|
10
|
+
# Reusable node type behind the schema's many `canXXX: AuthorizationResult` fields.
|
|
11
|
+
class AuthorizationResult < Logic::BaseModel
|
|
12
|
+
read_only!
|
|
13
|
+
|
|
14
|
+
passboolean :value
|
|
15
|
+
passthrough :message
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -3,7 +3,13 @@ module Ecoportal
|
|
|
3
3
|
class GraphQL
|
|
4
4
|
module Base
|
|
5
5
|
class ContractorEntity
|
|
6
|
+
# Convenience mutators over the read-model `associatedPeopleIds` / `leadContractorIds`
|
|
7
|
+
# arrays. They push/delete plain ids so that `Input::ContractorEntity::Update.from_model`
|
|
8
|
+
# can later emit the IdDiff (additions/removals) against `original_doc`.
|
|
6
9
|
module MemberChanges
|
|
10
|
+
# Associates one or more people. When `lead: true`, also marks them as leads.
|
|
11
|
+
# @param values [Array<String, Hash, Person, Enumerable>] ids (or things resolvable to ids).
|
|
12
|
+
# @param lead [Boolean] whether the associated people are also lead contractors.
|
|
7
13
|
def associate(*values, lead: false)
|
|
8
14
|
ids = to_person_ids(*values)
|
|
9
15
|
|
|
@@ -15,7 +21,9 @@ module Ecoportal
|
|
|
15
21
|
end
|
|
16
22
|
end
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
# Disassociates one or more people (also removing them as leads).
|
|
25
|
+
# @param values [Array<String, Hash, Person, Enumerable>] ids (or resolvable to ids).
|
|
26
|
+
def disassociate(*values)
|
|
19
27
|
ids = to_person_ids(*values)
|
|
20
28
|
|
|
21
29
|
associatedPeopleIds.tap do |associated|
|
|
@@ -23,7 +31,10 @@ module Ecoportal
|
|
|
23
31
|
associated.delete!(*ids)
|
|
24
32
|
end
|
|
25
33
|
end
|
|
34
|
+
# @deprecated Backwards-compatible alias for the original misspelling.
|
|
35
|
+
alias_method :dissassociate, :disassociate
|
|
26
36
|
|
|
37
|
+
# Marks one or more people as lead contractors (associating them if not already).
|
|
27
38
|
def lead!(*values)
|
|
28
39
|
ids = to_person_ids(*values)
|
|
29
40
|
|
|
@@ -33,6 +44,7 @@ module Ecoportal
|
|
|
33
44
|
end
|
|
34
45
|
end
|
|
35
46
|
|
|
47
|
+
# Removes one or more people from the lead contractors list.
|
|
36
48
|
def unlead!(*values)
|
|
37
49
|
ids = to_person_ids(*values)
|
|
38
50
|
|
|
@@ -44,7 +56,7 @@ module Ecoportal
|
|
|
44
56
|
private
|
|
45
57
|
|
|
46
58
|
def to_person_ids(*values)
|
|
47
|
-
to_person_id(values.flatten)
|
|
59
|
+
Array(to_person_id(values.flatten)).flatten.compact
|
|
48
60
|
end
|
|
49
61
|
|
|
50
62
|
def to_person_id(value)
|
|
@@ -54,9 +66,9 @@ module Ecoportal
|
|
|
54
66
|
when Ecoportal::API::V1::Person
|
|
55
67
|
value.id
|
|
56
68
|
when Hash
|
|
57
|
-
|
|
58
|
-
when
|
|
59
|
-
value.map {|val| to_person_id(val)}.compact
|
|
69
|
+
value['id'] || value[:id]
|
|
70
|
+
when Enumerable
|
|
71
|
+
value.map { |val| to_person_id(val) }.compact
|
|
60
72
|
end
|
|
61
73
|
end
|
|
62
74
|
end
|
|
@@ -2,11 +2,19 @@ module Ecoportal
|
|
|
2
2
|
module API
|
|
3
3
|
class GraphQL
|
|
4
4
|
module Base
|
|
5
|
+
# Verified against the live schema 2026-07-30: activeFileAttachment, activeName,
|
|
6
|
+
# activeVersion, archive, canDownload, canShow, draft, fileAttachments, fileSize, id,
|
|
7
|
+
# locations, name, otherTags, tags, token.
|
|
8
|
+
#
|
|
9
|
+
# ★ `activeVersionId` was declared here and does NOT exist on the type (it is
|
|
10
|
+
# `activeVersion`) — it always read nil. `otherTags` was missing, which matters because
|
|
11
|
+
# `UploadInput.otherTags` is the only way to tag a container.
|
|
5
12
|
class FileContainer < Logic::BaseModel
|
|
6
|
-
passkey
|
|
7
|
-
passthrough :name, :activeName
|
|
8
|
-
passthrough :fileSize, :token
|
|
9
|
-
|
|
13
|
+
passkey :id
|
|
14
|
+
passthrough :name, :activeName, :activeVersion
|
|
15
|
+
passthrough :fileSize, :token
|
|
16
|
+
passboolean :draft
|
|
17
|
+
passarray :tags, :otherTags
|
|
10
18
|
end
|
|
11
19
|
end
|
|
12
20
|
end
|
|
@@ -11,6 +11,9 @@ module Ecoportal
|
|
|
11
11
|
passkey :id
|
|
12
12
|
passthrough :name, :weight
|
|
13
13
|
passboolean :archived, :published, :visitorManagementEnabled
|
|
14
|
+
# AI-generated structure support (2026-07 schema).
|
|
15
|
+
passthrough :aiPrompt, read_only: true
|
|
16
|
+
passboolean :generating, read_only: true
|
|
14
17
|
embeds_one :updatedAt, klass: Base::DateTime
|
|
15
18
|
|
|
16
19
|
embeds_many :drafts, klass: :draft_class, order_key: :createdAt
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
# Lightweight dashboard descriptor returned by the `dashboardSearch` connection.
|
|
6
|
+
# Read-only. For the full dashboard (components, filters, permissions) the
|
|
7
|
+
# `Dashboard` type is not yet modelled — see docs/worklog.md.
|
|
8
|
+
#
|
|
9
|
+
# Enum-valued fields are exposed as plain strings:
|
|
10
|
+
# - permissionType: 'GLOBAL' | 'RESTRICTED' | 'PRIVATE'
|
|
11
|
+
# - parentResourceType: DashboardParentResource (REGISTER, REPORTING, TASKS, ...)
|
|
12
|
+
class MinimalDashboard < Logic::BaseModel
|
|
13
|
+
read_only!
|
|
14
|
+
|
|
15
|
+
passkey :id
|
|
16
|
+
passthrough :name
|
|
17
|
+
passthrough :parentResource, :parentResourceId, :parentResourceType
|
|
18
|
+
passthrough :permissionType
|
|
19
|
+
passthrough :weight
|
|
20
|
+
passboolean :global
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -8,6 +8,8 @@ module Ecoportal
|
|
|
8
8
|
passkey :id
|
|
9
9
|
passthrough :name, :companyName
|
|
10
10
|
passthrough :logoUrl, read_only: true
|
|
11
|
+
# Whether contractor admins may add other contractor admins (2026-07 schema).
|
|
12
|
+
passboolean :allowContractorAdminsToAddAdmins, read_only: true
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
module Ecoportal::API::GraphQL::Base::Page
|
|
2
2
|
class DataField
|
|
3
3
|
# Contractor entity selector field.
|
|
4
4
|
# Stores references to ContractorEntity records.
|
|
5
5
|
class ContractorEntities < DataField
|
|
6
|
-
passarray :contractors #
|
|
6
|
+
passarray :contractors # staged writes; the READ response uses `contractorEntities`
|
|
7
7
|
|
|
8
|
+
# Current contractor-entity ids. The GraphQL response carries them under
|
|
9
|
+
# `contractorEntities` (CommonPageUnion's contractorEntitiesField), NOT `contractors`
|
|
10
|
+
# — so read from there. After a contractor_entity_ids= write, prefer the staged value.
|
|
8
11
|
def contractor_entity_ids
|
|
9
|
-
|
|
12
|
+
contractor_ids_from(doc)
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
def contractor_entity_ids=(ids)
|
|
@@ -18,11 +21,31 @@
|
|
|
18
21
|
self.contractor_entity_ids = ids
|
|
19
22
|
end
|
|
20
23
|
|
|
24
|
+
# Dirty only when the effective id SET actually changed — same phantom-dirty class as
|
|
25
|
+
# CrossReference (READ one key, WRITE another): the default leaf diff flags the field
|
|
26
|
+
# the moment the `contractors` key appears, even for a no-op restatement or a clear on
|
|
27
|
+
# an already-empty field.
|
|
28
|
+
def dirty?
|
|
29
|
+
contractor_ids_from(doc).sort != contractor_ids_from(original_doc).sort
|
|
30
|
+
end
|
|
31
|
+
|
|
21
32
|
def as_input
|
|
22
33
|
return nil unless dirty?
|
|
23
34
|
|
|
24
35
|
{ contractorEntities: { id: id, contractorEntityIds: contractor_entity_ids } }
|
|
25
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Prefer the staged `contractors` (write shape), fall back to `contractorEntities`
|
|
41
|
+
# (read shape). Shape-agnostic for both `doc` and `original_doc`, so the comparison
|
|
42
|
+
# stays correct after `consolidate!` copies the staged shape into the baseline.
|
|
43
|
+
def contractor_ids_from(source_doc)
|
|
44
|
+
return [] unless source_doc
|
|
45
|
+
|
|
46
|
+
src = source_doc.key?('contractors') ? source_doc['contractors'] : source_doc['contractorEntities']
|
|
47
|
+
Array(src).map { |c| c.is_a?(Hash) ? c['id'] : c }.compact
|
|
48
|
+
end
|
|
26
49
|
end
|
|
27
50
|
end
|
|
28
51
|
end
|
|
@@ -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
|
|
|
@@ -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
|