ecoportal-api-graphql 1.3.10 → 1.3.12

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-assistance/code/diff_pairing_engine.md +243 -0
  3. data/.ai-assistance/code/filter_contract_matrix.md +177 -0
  4. data/.ai-assistance/code/graphql_domain_knowledge.md +20 -10
  5. data/.ai-assistance/code/template_diff_pairing_domain.md +175 -0
  6. data/.ai-assistance/code/workflow-command-guide.md +28 -0
  7. data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +136 -0
  8. data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +6 -1
  9. data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +93 -0
  10. data/.ai-assistance/projects/qa-services-delivery/INTENT.md +76 -0
  11. data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +115 -0
  12. data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +99 -0
  13. data/.ai-assistance/projects/qa-services-delivery/TODO.md +81 -0
  14. data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +87 -0
  15. data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +108 -0
  16. data/.ai-assistance/projects/template-diff-deploy/INTENT.md +12 -0
  17. data/.ai-assistance/projects/template-diff-deploy/TODO.md +9 -0
  18. data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +93 -0
  19. data/.ai-assistance/projects/template-maintenance/README.md +14 -0
  20. data/.ai-assistance/skills/procedural-memory/SKILL.md +319 -0
  21. data/.ai-assistance/standards-version.json +21 -20
  22. data/CHANGELOG.md +119 -0
  23. data/CLAUDE.md +10 -0
  24. data/docs/self-docs/ARCHITECTURE.md +88 -0
  25. data/docs/self-docs/CHANGES.jsonl +7 -0
  26. data/docs/self-docs/CONVENTIONS.md +74 -0
  27. data/docs/self-docs/INTEGRATIONS.md +63 -0
  28. data/docs/self-docs/OVERVIEW.md +51 -0
  29. data/docs/self-docs/STATUS.md +69 -0
  30. data/docs/self-docs/self-docs-index.json +39 -0
  31. data/docs/worklog.md +256 -0
  32. data/ecoportal-api-graphql.gemspec +1 -1
  33. data/lib/ecoportal/api/common/graphql/model/diffable.rb +54 -54
  34. data/lib/ecoportal/api/graphql/base/action.rb +43 -43
  35. data/lib/ecoportal/api/graphql/base/contractor_entity/member_changes.rb +67 -67
  36. data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +7 -0
  37. data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +28 -28
  38. data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +25 -25
  39. data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +24 -24
  40. data/lib/ecoportal/api/graphql/base/page/data_field.rb +1 -1
  41. data/lib/ecoportal/api/graphql/base/page/section_collection.rb +85 -79
  42. data/lib/ecoportal/api/graphql/base/preset_view.rb +17 -17
  43. data/lib/ecoportal/api/graphql/base/register.rb +18 -18
  44. data/lib/ecoportal/api/graphql/builder/template_builder.rb +174 -0
  45. data/lib/ecoportal/api/graphql/builder.rb +17 -16
  46. data/lib/ecoportal/api/graphql/compat/filter_translator.rb +63 -28
  47. data/lib/ecoportal/api/graphql/concerns/page_compat.rb +51 -51
  48. data/lib/ecoportal/api/graphql/concerns.rb +14 -14
  49. data/lib/ecoportal/api/graphql/diff/change.rb +59 -0
  50. data/lib/ecoportal/api/graphql/diff/command_synthesizer.rb +329 -0
  51. data/lib/ecoportal/api/graphql/diff/cross_object_diff.rb +165 -0
  52. data/lib/ecoportal/api/graphql/diff/deploy.rb +121 -0
  53. data/lib/ecoportal/api/graphql/diff/id_resolver.rb +64 -0
  54. data/lib/ecoportal/api/graphql/diff/pairing/candidate.rb +32 -0
  55. data/lib/ecoportal/api/graphql/diff/pairing/engine.rb +173 -0
  56. data/lib/ecoportal/api/graphql/diff/pairing/ledger.rb +119 -0
  57. data/lib/ecoportal/api/graphql/diff/pairing/signals.rb +104 -0
  58. data/lib/ecoportal/api/graphql/diff/strategy.rb +113 -0
  59. data/lib/ecoportal/api/graphql/diff/version_diff.rb +332 -0
  60. data/lib/ecoportal/api/graphql/diff.rb +34 -0
  61. data/lib/ecoportal/api/graphql/file_upload/client.rb +181 -181
  62. data/lib/ecoportal/api/graphql/fragment/page.rb +85 -85
  63. data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +1 -0
  64. data/lib/ecoportal/api/graphql/input/action/update.rb +14 -14
  65. data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +41 -41
  66. data/lib/ecoportal/api/graphql/input/location_structure/apply_commands.rb +47 -47
  67. data/lib/ecoportal/api/graphql/input/location_structure/draft/add_commands.rb +49 -49
  68. data/lib/ecoportal/api/graphql/input/location_structure/update_command.rb +27 -27
  69. data/lib/ecoportal/api/graphql/input/search_conf.rb +436 -367
  70. data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +27 -18
  71. data/lib/ecoportal/api/graphql/interface/location_structure/command.rb +30 -30
  72. data/lib/ecoportal/api/graphql/logic/input.rb +26 -26
  73. data/lib/ecoportal/api/graphql/mutation/action/archive.rb +1 -1
  74. data/lib/ecoportal/api/graphql/mutation/action/create.rb +1 -1
  75. data/lib/ecoportal/api/graphql/mutation/action/update.rb +1 -1
  76. data/lib/ecoportal/api/graphql/mutation/contractor_entity/create.rb +1 -1
  77. data/lib/ecoportal/api/graphql/mutation/contractor_entity/destroy.rb +1 -1
  78. data/lib/ecoportal/api/graphql/mutation/contractor_entity/update.rb +1 -1
  79. data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +1 -1
  80. data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +1 -1
  81. data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +1 -1
  82. data/lib/ecoportal/api/graphql.rb +1 -0
  83. data/lib/ecoportal/api/graphql_version.rb +1 -1
  84. data/tests/dump_template_model.rb +90 -0
  85. data/tests/validate_queries.rb +31 -9
  86. metadata +40 -3
@@ -1,181 +1,181 @@
1
- require 'net/http'
2
- require 'uri'
3
- require 'json'
4
- require 'mime/types'
5
-
6
- module Ecoportal
7
- module API
8
- class GraphQL
9
- module FileUpload
10
- # Orchestrates the full file upload workflow:
11
- #
12
- # Step 1 — Get S3 pre-signed credentials via GraphQL
13
- # Step 2 — Upload file directly to S3 (multipart form POST)
14
- # Step 3 — Register the upload with EcoPortal (REST POST /v2/s3/files)
15
- # Step 4 — Poll until the FileContainer is created
16
- #
17
- # Returns a fileContainerId string on success, suitable for use in
18
- # DataField::FileField#file_container_ids= or DataField::ImageGallery#file_container_ids=
19
- #
20
- # Usage:
21
- # container_id = api.file_upload.upload('/path/to/report.pdf')
22
- # page.components.get_by_name('Attachment').file_container_ids = [container_id]
23
- # api.pages.update(page)
24
- #
25
- # Requires the gem to have been initialised with email/pass (OAuth Bearer token).
26
- class Client
27
- POLL_INTERVAL = 2 # seconds between poll attempts
28
- POLL_MAX_WAIT = 120 # seconds before giving up
29
-
30
- def initialize(graphql_client)
31
- @graphql_client = graphql_client
32
- @http_client = graphql_client.client.http_client
33
- @org_id = @http_client.org_id
34
- @host = @http_client.host
35
- end
36
-
37
- # Upload a file and return the fileContainerId.
38
- # Raises RuntimeError on failure.
39
- def upload(file_path)
40
- raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path)
41
-
42
- creds = fetch_s3_credentials
43
- s3_key = direct_upload_to_storage(file_path, creds)
44
- poll_id = register_upload(file_path, s3_key)
45
- poll_for_container_id(poll_id)
46
- end
47
-
48
- private
49
-
50
- # Step 1: GraphQL query → S3 presigned credentials
51
- def fetch_s3_credentials
52
- Query::FileUploadSignature.new(@graphql_client.client).query
53
- end
54
-
55
- # Step 2: Direct multipart POST to S3
56
- def direct_upload_to_storage(file_path, creds)
57
- filename = File.basename(file_path)
58
- s3_key = "#{creds.upload_prefix}#{filename}"
59
- mime_type = detect_mime_type(file_path)
60
- endpoint = URI(creds.endpoint)
61
-
62
- File.open(file_path, 'rb') do |file|
63
- Net::HTTP.start(endpoint.host, endpoint.port,
64
- use_ssl: endpoint.scheme == 'https') do |http|
65
- boundary = "EcoPortalUpload#{SecureRandom.hex(8)}"
66
- body = build_multipart(boundary, {
67
- s3_key: s3_key, access_key: creds.access_key,
68
- policy: creds.policy, signature: creds.signature,
69
- mime_type: mime_type, filename: filename
70
- }, file)
71
- request = Net::HTTP::Post.new(endpoint.path.empty? ? '/' : endpoint.path)
72
- request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
73
- request.body = body
74
- response = http.request(request)
75
- raise "S3 upload failed (#{response.code}): #{response.body}" unless response.code.to_i < 300
76
- end
77
- end
78
-
79
- s3_key
80
- end
81
-
82
- # Step 3: Register with EcoPortal backend (REST v2)
83
- def register_upload(file_path, s3_key)
84
- filename = File.basename(file_path)
85
- mime_type = detect_mime_type(file_path)
86
- filesize = File.size(file_path)
87
-
88
- body = { filename: filename, filetype: mime_type, filesize: filesize, path: s3_key }
89
- response = upload_post('/s3/files', body)
90
- raise "File registration failed: #{response.body}" unless response.code.to_i < 300
91
-
92
- JSON.parse(response.body)['id']
93
- end
94
-
95
- # Step 4: Poll until FileContainer is created; return fileContainerId
96
- def poll_for_container_id(poll_id)
97
- deadline = Time.now + POLL_MAX_WAIT
98
- loop do
99
- raise 'File upload timed out' if Time.now > deadline
100
-
101
- response = upload_get("/s3/files/poll/#{poll_id}")
102
- raise "Poll failed: #{response.body}" unless response.code.to_i < 300
103
-
104
- data = JSON.parse(response.body)
105
- status = data['status'] || data.dig('file', 'status')
106
-
107
- return data.dig('file', 'file_container_id') if data['status'] == 'success'
108
- raise "File upload failed: #{data.inspect}" if status == 'failed'
109
-
110
- sleep POLL_INTERVAL
111
- end
112
- end
113
-
114
- # --- REST helpers (v2 endpoints, Bearer token auth) ---
115
-
116
- def bearer_token
117
- @bearer_token ||= @http_client.send(:key_token)
118
- end
119
-
120
- def upload_base_url
121
- "https://#{@host}/api/v2/#{@org_id}"
122
- end
123
-
124
- def upload_get(path)
125
- uri = URI("#{upload_base_url}#{path}")
126
- request = Net::HTTP::Get.new(uri)
127
- request['Authorization'] = "Bearer #{bearer_token}"
128
- request['Accept'] = 'application/json'
129
- Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
130
- end
131
-
132
- def upload_post(path, body)
133
- uri = URI("#{upload_base_url}#{path}")
134
- request = Net::HTTP::Post.new(uri)
135
- request['Authorization'] = "Bearer #{bearer_token}"
136
- request['Content-Type'] = 'application/json'
137
- request['Accept'] = 'application/json'
138
- request.body = JSON.generate(body)
139
- Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
140
- end
141
-
142
- # --- Multipart builder ---
143
-
144
- def build_multipart(boundary, opts, file_io)
145
- parts = {
146
- 'key' => opts[:s3_key],
147
- 'AWSAccessKeyId' => opts[:access_key],
148
- 'policy' => opts[:policy],
149
- 'signature' => opts[:signature],
150
- 'x-amz-server-side-encryption' => 'AES256',
151
- 'Content-Type' => opts[:mime_type]
152
- }
153
- filename = opts[:filename]
154
- mime_type = opts[:mime_type]
155
-
156
- body = +''
157
- parts.each do |name, value|
158
- body << "--#{boundary}\r\n"
159
- body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n"
160
- body << "#{value}\r\n"
161
- end
162
- # File field last
163
- body << "--#{boundary}\r\n"
164
- body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
165
- body << "Content-Type: #{mime_type}\r\n\r\n"
166
- body << file_io.read
167
- body << "\r\n--#{boundary}--\r\n"
168
- body
169
- end
170
-
171
- def detect_mime_type(file_path)
172
- types = MIME::Types.type_for(file_path)
173
- types.first&.to_s || 'application/octet-stream'
174
- rescue StandardError
175
- 'application/octet-stream'
176
- end
177
- end
178
- end
179
- end
180
- end
181
- end
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'mime/types'
5
+
6
+ module Ecoportal
7
+ module API
8
+ class GraphQL
9
+ module FileUpload
10
+ # Orchestrates the full file upload workflow:
11
+ #
12
+ # Step 1 — Get S3 pre-signed credentials via GraphQL
13
+ # Step 2 — Upload file directly to S3 (multipart form POST)
14
+ # Step 3 — Register the upload with EcoPortal (REST POST /v2/s3/files)
15
+ # Step 4 — Poll until the FileContainer is created
16
+ #
17
+ # Returns a fileContainerId string on success, suitable for use in
18
+ # DataField::FileField#file_container_ids= or DataField::ImageGallery#file_container_ids=
19
+ #
20
+ # Usage:
21
+ # container_id = api.file_upload.upload('/path/to/report.pdf')
22
+ # page.components.get_by_name('Attachment').file_container_ids = [container_id]
23
+ # api.pages.update(page)
24
+ #
25
+ # Requires the gem to have been initialised with email/pass (OAuth Bearer token).
26
+ class Client
27
+ POLL_INTERVAL = 2 # seconds between poll attempts
28
+ POLL_MAX_WAIT = 120 # seconds before giving up
29
+
30
+ def initialize(graphql_client)
31
+ @graphql_client = graphql_client
32
+ @http_client = graphql_client.client.http_client
33
+ @org_id = @http_client.org_id
34
+ @host = @http_client.host
35
+ end
36
+
37
+ # Upload a file and return the fileContainerId.
38
+ # Raises RuntimeError on failure.
39
+ def upload(file_path)
40
+ raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path)
41
+
42
+ creds = fetch_s3_credentials
43
+ s3_key = direct_upload_to_storage(file_path, creds)
44
+ poll_id = register_upload(file_path, s3_key)
45
+ poll_for_container_id(poll_id)
46
+ end
47
+
48
+ private
49
+
50
+ # Step 1: GraphQL query → S3 presigned credentials
51
+ def fetch_s3_credentials
52
+ Query::FileUploadSignature.new(@graphql_client.client).query
53
+ end
54
+
55
+ # Step 2: Direct multipart POST to S3
56
+ def direct_upload_to_storage(file_path, creds)
57
+ filename = File.basename(file_path)
58
+ s3_key = "#{creds.upload_prefix}#{filename}"
59
+ mime_type = detect_mime_type(file_path)
60
+ endpoint = URI(creds.endpoint)
61
+
62
+ File.open(file_path, 'rb') do |file|
63
+ Net::HTTP.start(endpoint.host, endpoint.port,
64
+ use_ssl: endpoint.scheme == 'https') do |http|
65
+ boundary = "EcoPortalUpload#{SecureRandom.hex(8)}"
66
+ body = build_multipart(boundary, {
67
+ s3_key: s3_key, access_key: creds.access_key,
68
+ policy: creds.policy, signature: creds.signature,
69
+ mime_type: mime_type, filename: filename
70
+ }, file)
71
+ request = Net::HTTP::Post.new(endpoint.path.empty? ? '/' : endpoint.path)
72
+ request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
73
+ request.body = body
74
+ response = http.request(request)
75
+ raise "S3 upload failed (#{response.code}): #{response.body}" unless response.code.to_i < 300
76
+ end
77
+ end
78
+
79
+ s3_key
80
+ end
81
+
82
+ # Step 3: Register with EcoPortal backend (REST v2)
83
+ def register_upload(file_path, s3_key)
84
+ filename = File.basename(file_path)
85
+ mime_type = detect_mime_type(file_path)
86
+ filesize = File.size(file_path)
87
+
88
+ body = { filename: filename, filetype: mime_type, filesize: filesize, path: s3_key }
89
+ response = upload_post('/s3/files', body)
90
+ raise "File registration failed: #{response.body}" unless response.code.to_i < 300
91
+
92
+ JSON.parse(response.body)['id']
93
+ end
94
+
95
+ # Step 4: Poll until FileContainer is created; return fileContainerId
96
+ def poll_for_container_id(poll_id)
97
+ deadline = Time.now + POLL_MAX_WAIT
98
+ loop do
99
+ raise 'File upload timed out' if Time.now > deadline
100
+
101
+ response = upload_get("/s3/files/poll/#{poll_id}")
102
+ raise "Poll failed: #{response.body}" unless response.code.to_i < 300
103
+
104
+ data = JSON.parse(response.body)
105
+ status = data['status'] || data.dig('file', 'status')
106
+
107
+ return data.dig('file', 'file_container_id') if data['status'] == 'success'
108
+ raise "File upload failed: #{data.inspect}" if status == 'failed'
109
+
110
+ sleep POLL_INTERVAL
111
+ end
112
+ end
113
+
114
+ # --- REST helpers (v2 endpoints, Bearer token auth) ---
115
+
116
+ def bearer_token
117
+ @bearer_token ||= @http_client.send(:key_token)
118
+ end
119
+
120
+ def upload_base_url
121
+ "https://#{@host}/api/v2/#{@org_id}"
122
+ end
123
+
124
+ def upload_get(path)
125
+ uri = URI("#{upload_base_url}#{path}")
126
+ request = Net::HTTP::Get.new(uri)
127
+ request['Authorization'] = "Bearer #{bearer_token}"
128
+ request['Accept'] = 'application/json'
129
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
130
+ end
131
+
132
+ def upload_post(path, body)
133
+ uri = URI("#{upload_base_url}#{path}")
134
+ request = Net::HTTP::Post.new(uri)
135
+ request['Authorization'] = "Bearer #{bearer_token}"
136
+ request['Content-Type'] = 'application/json'
137
+ request['Accept'] = 'application/json'
138
+ request.body = JSON.generate(body)
139
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
140
+ end
141
+
142
+ # --- Multipart builder ---
143
+
144
+ def build_multipart(boundary, opts, file_io)
145
+ parts = {
146
+ 'key' => opts[:s3_key],
147
+ 'AWSAccessKeyId' => opts[:access_key],
148
+ 'policy' => opts[:policy],
149
+ 'signature' => opts[:signature],
150
+ 'x-amz-server-side-encryption' => 'AES256',
151
+ 'Content-Type' => opts[:mime_type]
152
+ }
153
+ filename = opts[:filename]
154
+ mime_type = opts[:mime_type]
155
+
156
+ body = +''
157
+ parts.each do |name, value|
158
+ body << "--#{boundary}\r\n"
159
+ body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n"
160
+ body << "#{value}\r\n"
161
+ end
162
+ # File field last
163
+ body << "--#{boundary}\r\n"
164
+ body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
165
+ body << "Content-Type: #{mime_type}\r\n\r\n"
166
+ body << file_io.read
167
+ body << "\r\n--#{boundary}--\r\n"
168
+ body
169
+ end
170
+
171
+ def detect_mime_type(file_path)
172
+ types = MIME::Types.type_for(file_path)
173
+ types.first&.to_s || 'application/octet-stream'
174
+ rescue StandardError
175
+ 'application/octet-stream'
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -1,85 +1,85 @@
1
- module Ecoportal
2
- module API
3
- class GraphQL
4
- class Fragment
5
- # Shared fields for both BasicPage and PhasedPage (spread on BasePageInterface).
6
- # Always include __typename so PageUnion can dispatch to the right concrete class.
7
- fragment :PageFields, <<~GRAPHQL
8
- fragment PageFields on BasePageInterface {
9
- __typename
10
- id
11
- name
12
- uid
13
- icon
14
- patchVer
15
- state
16
- archived
17
- archivedAt { dateTime }
18
- draft
19
- externalId
20
- timeZone
21
- otherTags
22
- taskPriority
23
- sourceTemplateId
24
- createdAt { dateTime }
25
- updatedAt { dateTime }
26
- creatorName
27
- locations {
28
- ...LocationNode
29
- }
30
- workflow {
31
- autoPageCreationEnabled
32
- fields { key name type total missing }
33
- }
34
- }
35
- GRAPHQL
36
-
37
- # BasicPage-only fields — spread alongside PageFields for basic pages.
38
- # BasicPage has sections directly (PhasedPage sections live under stages).
39
- fragment :BasicPageFields, <<~GRAPHQL
40
- fragment BasicPageFields on BasicPage {
41
- sections {
42
- __typename
43
- ... on ContentSection {
44
- id
45
- heading
46
- }
47
- ... on SplitSection {
48
- id
49
- heading
50
- }
51
- }
52
- }
53
- GRAPHQL
54
-
55
- # PhasedPage-only fields — spread alongside PageFields for phased pages.
56
- # Provides lightweight stage index, current stage, and active stages.
57
- # For full stage content (sections, data fields), use CommonPageUnion.
58
- fragment :PhasedPageFields, <<~GRAPHQL
59
- fragment PhasedPageFields on PhasedPage {
60
- stagesIndex {
61
- id
62
- name
63
- ordering
64
- state
65
- started
66
- hasCompleteTask
67
- hasReviewTask
68
- }
69
- currentStage {
70
- id
71
- name
72
- ordering
73
- state
74
- }
75
- activeStages {
76
- id
77
- name
78
- ordering
79
- }
80
- }
81
- GRAPHQL
82
- end
83
- end
84
- end
85
- end
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ class Fragment
5
+ # Shared fields for both BasicPage and PhasedPage (spread on BasePageInterface).
6
+ # Always include __typename so PageUnion can dispatch to the right concrete class.
7
+ fragment :PageFields, <<~GRAPHQL
8
+ fragment PageFields on BasePageInterface {
9
+ __typename
10
+ id
11
+ name
12
+ uid
13
+ icon
14
+ patchVer
15
+ state
16
+ archived
17
+ archivedAt { dateTime }
18
+ draft
19
+ externalId
20
+ timeZone
21
+ otherTags
22
+ taskPriority
23
+ sourceTemplateId
24
+ createdAt { dateTime }
25
+ updatedAt { dateTime }
26
+ creatorName
27
+ locations {
28
+ ...LocationNode
29
+ }
30
+ workflow {
31
+ autoPageCreationEnabled
32
+ fields { key name type total missing }
33
+ }
34
+ }
35
+ GRAPHQL
36
+
37
+ # BasicPage-only fields — spread alongside PageFields for basic pages.
38
+ # BasicPage has sections directly (PhasedPage sections live under stages).
39
+ fragment :BasicPageFields, <<~GRAPHQL
40
+ fragment BasicPageFields on BasicPage {
41
+ sections {
42
+ __typename
43
+ ... on ContentSection {
44
+ id
45
+ heading
46
+ }
47
+ ... on SplitSection {
48
+ id
49
+ heading
50
+ }
51
+ }
52
+ }
53
+ GRAPHQL
54
+
55
+ # PhasedPage-only fields — spread alongside PageFields for phased pages.
56
+ # Provides lightweight stage index, current stage, and active stages.
57
+ # For full stage content (sections, data fields), use CommonPageUnion.
58
+ fragment :PhasedPageFields, <<~GRAPHQL
59
+ fragment PhasedPageFields on PhasedPage {
60
+ stagesIndex {
61
+ id
62
+ name
63
+ ordering
64
+ state
65
+ started
66
+ hasCompleteTask
67
+ hasReviewTask
68
+ }
69
+ currentStage {
70
+ id
71
+ name
72
+ ordering
73
+ state
74
+ }
75
+ activeStages {
76
+ id
77
+ name
78
+ ordering
79
+ }
80
+ }
81
+ GRAPHQL
82
+ end
83
+ end
84
+ end
85
+ end
@@ -119,6 +119,7 @@ module Ecoportal
119
119
  __typename
120
120
  id @skip(if: $only_content)
121
121
  label
122
+ genomeSignature @skip(if: $only_content)
122
123
  deindex @include(if: $content)
123
124
  linkedFieldConfig @skip(if: $only_content) {
124
125
  crossReferenceId
@@ -1,14 +1,14 @@
1
- module Ecoportal
2
- module API
3
- class GraphQL
4
- module Input
5
- module Action
6
- class Update < Ecoportal::API::GraphQL::Base::Action
7
- passthrough :clientMutationId
8
- passthrough :updateMode
9
- end
10
- end
11
- end
12
- end
13
- end
14
- end
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Input
5
+ module Action
6
+ class Update < Ecoportal::API::GraphQL::Base::Action
7
+ passthrough :clientMutationId
8
+ passthrough :updateMode
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,41 +1,41 @@
1
- module Ecoportal
2
- module API
3
- class GraphQL
4
- module Input
5
- module ContractorEntity
6
- class Update < Input::ContractorEntity::Create
7
- passthrough :clientMutationId
8
- class_resolver :id_diff_input, "Ecoportal::API::GraphQL::Input::IdDiff"
9
- embeds_one :associatedPeopleIds, klass: :id_diff_input
10
- embeds_one :leadContractorIds, klass: :id_diff_input
11
-
12
- ID_DIFF_FIELDS = %i[associatedPeopleIds leadContractorIds].freeze
13
-
14
- class << self
15
- # Overrides Logic::Input.from_model to reshape array fields into
16
- # IdDiffInput format: { additions: [...], removals: [...] }
17
- # Array fields are stripped from the flat diff, so we compare
18
- # doc vs original_doc directly rather than relying on the diff hash.
19
- def from_model(model, **kargs)
20
- diff = Logic::Input.from_model(model, **kargs)
21
-
22
- ID_DIFF_FIELDS.each do |field|
23
- key = field.to_s
24
- current = Array(model.doc[key])
25
- original = Array(model.original_doc[key])
26
- next if current == original
27
-
28
- diff ||= {}
29
- diff[:id] ||= model.id
30
- diff[field] = { additions: current - original, removals: original - current }
31
- end
32
-
33
- diff
34
- end
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Input
5
+ module ContractorEntity
6
+ class Update < Input::ContractorEntity::Create
7
+ passthrough :clientMutationId
8
+ class_resolver :id_diff_input, "Ecoportal::API::GraphQL::Input::IdDiff"
9
+ embeds_one :associatedPeopleIds, klass: :id_diff_input
10
+ embeds_one :leadContractorIds, klass: :id_diff_input
11
+
12
+ ID_DIFF_FIELDS = %i[associatedPeopleIds leadContractorIds].freeze
13
+
14
+ class << self
15
+ # Overrides Logic::Input.from_model to reshape array fields into
16
+ # IdDiffInput format: { additions: [...], removals: [...] }
17
+ # Array fields are stripped from the flat diff, so we compare
18
+ # doc vs original_doc directly rather than relying on the diff hash.
19
+ def from_model(model, **kargs)
20
+ diff = Logic::Input.from_model(model, **kargs)
21
+
22
+ ID_DIFF_FIELDS.each do |field|
23
+ key = field.to_s
24
+ current = Array(model.doc[key])
25
+ original = Array(model.original_doc[key])
26
+ next if current == original
27
+
28
+ diff ||= {}
29
+ diff[:id] ||= model.id
30
+ diff[field] = { additions: current - original, removals: original - current }
31
+ end
32
+
33
+ diff
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end