ecoportal-api-graphql 1.3.13 → 1.3.16
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 +87 -0
- data/lib/ecoportal/api/common/graphql/client.rb +0 -2
- data/lib/ecoportal/api/common/graphql/http_client.rb +6 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -17
- data/lib/ecoportal/api/graphql/base/file_container.rb +14 -14
- data/lib/ecoportal/api/graphql/base/location_classification_type.rb +15 -15
- data/lib/ecoportal/api/graphql/base/location_structure.rb +27 -27
- data/lib/ecoportal/api/graphql/base/organization.rb +15 -15
- data/lib/ecoportal/api/graphql/base/page/task.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page.rb +17 -16
- data/lib/ecoportal/api/graphql/base/template.rb +35 -0
- data/lib/ecoportal/api/graphql/base.rb +37 -35
- data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +41 -41
- data/lib/ecoportal/api/graphql/compat/response.rb +35 -35
- data/lib/ecoportal/api/graphql/connection/action.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/action_category.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/person_member.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection.rb +17 -17
- data/lib/ecoportal/api/graphql/file_upload/client.rb +21 -10
- data/lib/ecoportal/api/graphql/fragment/field_configuration.rb +38 -0
- data/lib/ecoportal/api/graphql/fragment/force.rb +14 -10
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +26 -26
- data/lib/ecoportal/api/graphql/fragment/page_task.rb +39 -0
- data/lib/ecoportal/api/graphql/fragment/template.rb +51 -0
- data/lib/ecoportal/api/graphql/fragment.rb +3 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -16
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -21
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -28
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -23
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -24
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -251
- data/lib/ecoportal/api/graphql/input.rb +24 -24
- data/lib/ecoportal/api/graphql/logic/connection.rb +34 -34
- data/lib/ecoportal/api/graphql/model/page/task.rb +16 -0
- data/lib/ecoportal/api/graphql/model/page.rb +16 -15
- data/lib/ecoportal/api/graphql/model/person_member.rb +15 -15
- data/lib/ecoportal/api/graphql/model/template.rb +15 -0
- data/lib/ecoportal/api/graphql/model.rb +32 -30
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -45
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +7 -7
- data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +8 -7
- data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +34 -35
- data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -35
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -36
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -13
- data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -46
- data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation.rb +20 -20
- data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/payload/ok_payload.rb +36 -21
- data/lib/ecoportal/api/graphql/payload/page/draft.rb +26 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task_batch.rb +23 -0
- data/lib/ecoportal/api/graphql/payload/page.rb +20 -19
- data/lib/ecoportal/api/graphql/payload/preset_view.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/register.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/payload.rb +23 -23
- data/lib/ecoportal/api/graphql/query/location_structures.rb +57 -57
- data/lib/ecoportal/api/graphql/query/page.rb +45 -45
- data/lib/ecoportal/api/graphql/query/page_with_forces.rb +9 -3
- data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +9 -3
- data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -78
- data/lib/ecoportal/api/graphql/query.rb +27 -27
- data/lib/ecoportal/api/graphql.rb +9 -5
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +9 -180
- data/.ai-assistance/bridge/CLAUDE.md +0 -338
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +0 -29
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +0 -18
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +0 -42
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +0 -115
- data/.ai-assistance/bridge/context/gemini-review-prompt.txt +0 -48
- data/.ai-assistance/bridge/context/gemini-review-response.md +0 -104
- data/.ai-assistance/bridge/context/project.md +0 -42
- data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +0 -48
- data/.ai-assistance/bridge/queue/.gitkeep +0 -1
- data/.ai-assistance/capabilities/CLAUDE.md +0 -27
- data/.ai-assistance/capabilities/assumptions-log.md +0 -80
- data/.ai-assistance/capabilities/code.md +0 -47
- data/.ai-assistance/capabilities/connectors.md +0 -37
- data/.ai-assistance/capabilities/cowork.md +0 -55
- data/.ai-assistance/code/OVERVIEW.md +0 -155
- data/.ai-assistance/code/data_fields.md +0 -242
- data/.ai-assistance/code/dependencies.md +0 -151
- data/.ai-assistance/code/diff_as_input.md +0 -234
- data/.ai-assistance/code/diff_pairing_engine.md +0 -243
- data/.ai-assistance/code/diff_service_deep_dive.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +0 -55
- data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +0 -181
- data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +0 -147
- data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +0 -277
- data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +0 -200
- data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +0 -228
- data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +0 -197
- data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +0 -243
- data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +0 -196
- data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +0 -132
- data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +0 -187
- data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +0 -236
- data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +0 -183
- data/.ai-assistance/code/ecoportal_schema_reference.md +0 -240
- data/.ai-assistance/code/filter_contract_matrix.md +0 -177
- data/.ai-assistance/code/graphql_domain_knowledge.md +0 -240
- data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +0 -71
- data/.ai-assistance/code/refactoring/opportunities.md +0 -251
- data/.ai-assistance/code/schema_analysis.md +0 -321
- data/.ai-assistance/code/search_filters.md +0 -868
- data/.ai-assistance/code/spec_coverage.md +0 -73
- data/.ai-assistance/code/template_diff_pairing_domain.md +0 -175
- data/.ai-assistance/code/workflow-command-guide.md +0 -466
- data/.ai-assistance/code/workflow-space.md +0 -353
- data/.ai-assistance/conventions/CLAUDE.md +0 -30
- data/.ai-assistance/conventions/code-working-tree-protocol.md +0 -199
- data/.ai-assistance/conventions/gitignore-rules.md +0 -42
- data/.ai-assistance/conventions/permission-guidance.md +0 -120
- data/.ai-assistance/integrations/README.md +0 -70
- data/.ai-assistance/integrations/gitkraken-mcp.md +0 -107
- data/.ai-assistance/integrations/gitlab-mcp.md +0 -123
- data/.ai-assistance/integrations/local-git.md +0 -60
- data/.ai-assistance/local_paths.example.md +0 -17
- data/.ai-assistance/projects/TODO.md +0 -118
- data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +0 -168
- data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +0 -60
- data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +0 -267
- data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +0 -53
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +0 -102
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +0 -139
- data/.ai-assistance/projects/compat-layer-audit/COMPAT_AUDIT.md +0 -244
- data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +0 -93
- data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +0 -244
- data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +0 -266
- data/.ai-assistance/projects/eco-helpers-compat/TODO.md +0 -86
- data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +0 -101
- data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +0 -177
- data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +0 -161
- data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +0 -125
- data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +0 -136
- data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +0 -126
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +0 -261
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +0 -122
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +0 -148
- data/.ai-assistance/projects/page-model/DECISIONS.md +0 -245
- data/.ai-assistance/projects/page-model/TODO.md +0 -190
- data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +0 -93
- data/.ai-assistance/projects/qa-services-delivery/INTENT.md +0 -76
- data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +0 -115
- data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +0 -99
- data/.ai-assistance/projects/qa-services-delivery/TODO.md +0 -81
- data/.ai-assistance/projects/search-filter-builder/INTENT.md +0 -107
- data/.ai-assistance/projects/search-filter-builder/TODO.md +0 -131
- data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +0 -87
- data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +0 -108
- data/.ai-assistance/projects/template-diff-deploy/INTENT.md +0 -12
- data/.ai-assistance/projects/template-diff-deploy/TODO.md +0 -9
- data/.ai-assistance/projects/template-maintenance/DESIGN.md +0 -134
- data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +0 -93
- data/.ai-assistance/projects/template-maintenance/README.md +0 -14
- data/.ai-assistance/projects/template-model-logic/CATEGORY_CATALOG.md +0 -236
- data/.ai-assistance/projects/template-model-logic/CLASSIFICATION_SPIKE.md +0 -243
- data/.ai-assistance/projects/template-model-logic/DESIGN_NOTE.md +0 -154
- data/.ai-assistance/projects/workflow-space/TODO.md +0 -213
- data/.ai-assistance/reinstall-claude-desktop-windows.md +0 -136
- data/.ai-assistance/scripts/CLAUDE.md +0 -150
- data/.ai-assistance/scripts/bridge-inbox-check.sh +0 -75
- data/.ai-assistance/scripts/bridge-init.sh +0 -86
- data/.ai-assistance/scripts/bridge-status.sh +0 -44
- data/.ai-assistance/scripts/capabilities-check.ts +0 -104
- data/.ai-assistance/scripts/check-outbox.sh +0 -43
- data/.ai-assistance/scripts/dep_graph.rb +0 -91
- data/.ai-assistance/scripts/lock-acquire.sh +0 -103
- data/.ai-assistance/scripts/lock-multi.sh +0 -124
- data/.ai-assistance/scripts/lock-queue.sh +0 -94
- data/.ai-assistance/scripts/setup-mcps.test.ts +0 -188
- data/.ai-assistance/scripts/setup-mcps.ts +0 -234
- data/.ai-assistance/scripts/task-complete.ts +0 -74
- data/.ai-assistance/scripts/task-create.ts +0 -75
- data/.ai-assistance/scripts/task-read.ts +0 -125
- data/.ai-assistance/scripts/token-logger.js +0 -220
- data/.ai-assistance/scripts/token-report.ts +0 -158
- data/.ai-assistance/scripts/token-session-start.js +0 -66
- data/.ai-assistance/skills/ai-instructions/SKILL.md +0 -48
- data/.ai-assistance/skills/code-specs/SKILL.md +0 -69
- data/.ai-assistance/skills/corporate-policies/SKILL.md +0 -201
- data/.ai-assistance/skills/dep-graph/SKILL.md +0 -139
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +0 -417
- data/.ai-assistance/skills/gemini-assist/SKILL.md +0 -63
- data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +0 -205
- data/.ai-assistance/skills/gemini-assist/gemini_ask.py +0 -1
- data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +0 -240
- data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +0 -25
- data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +0 -261
- data/.ai-assistance/skills/procedural-memory/SKILL.md +0 -319
- data/.ai-assistance/skills/project-cycle/SKILL.md +0 -177
- data/.ai-assistance/skills/project-self-docs/SKILL.md +0 -181
- data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +0 -378
- data/.ai-assistance/skills/refactor/SKILL.md +0 -62
- data/.ai-assistance/skills/rubocop/SKILL.md +0 -93
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +0 -215
- data/.ai-assistance/skills/spec-generation/SKILL.md +0 -72
- data/.ai-assistance/standards-version.json +0 -22
- data/.ai-assistance/token-budget.json +0 -32
- data/.ai-assistance/version.json +0 -39
- data/.claude/settings.json +0 -150
- data/.env.example +0 -18
- data/.gitattributes +0 -15
- data/.gitignore +0 -37
- data/.gitlab-ci.yml +0 -45
- data/.markdownlint.json +0 -4
- data/.rspec +0 -3
- data/.rubocop.yml +0 -121
- data/.ruby-version +0 -1
- data/.yardopts +0 -10
- data/CLAUDE.md +0 -243
- data/Gemfile +0 -30
- data/Rakefile +0 -90
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/setup.ps1 +0 -6
- data/docs/self-docs/ARCHITECTURE.md +0 -88
- data/docs/self-docs/CHANGES.jsonl +0 -12
- data/docs/self-docs/COMPLIANCE.md +0 -79
- data/docs/self-docs/CONVENTIONS.md +0 -74
- data/docs/self-docs/INTEGRATIONS.md +0 -65
- data/docs/self-docs/OPERATIONS.md +0 -76
- data/docs/self-docs/OVERVIEW.md +0 -64
- data/docs/self-docs/STATUS.md +0 -73
- data/docs/self-docs/self-docs-index.json +0 -51
- data/docs/worklog.md +0 -977
- data/ecoportal-api-graphql.gemspec +0 -40
- data/scripts/auto-worker-scheduler.sh +0 -386
- data/tests/actions_get.rb +0 -7
- data/tests/contractor_entities_get.rb +0 -20
- data/tests/contractor_entity_create.rb +0 -19
- data/tests/contractor_entity_udpate.rb +0 -20
- data/tests/dump_page_model.rb +0 -74
- data/tests/dump_template_model.rb +0 -90
- data/tests/loc_structure_get.rb +0 -10
- data/tests/loc_structure_update.rb +0 -51
- data/tests/loc_structures_get.rb +0 -15
- data/tests/local_libs.rb +0 -20
- data/tests/validate_queries.rb +0 -127
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
# Project: eco-helpers GraphQL Compat Layer
|
|
2
|
-
|
|
3
|
-
**Created:** 2026-06-07
|
|
4
|
-
**Status:** Starting — URGENT (APIv2 retires in ~3 weeks)
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Goal
|
|
9
|
-
|
|
10
|
-
Existing eco-helpers scripts work against `ecoportal-api-v2` (REST). They must continue
|
|
11
|
-
to work against `ecoportal-api-graphql` (GraphQL) **without changing one line of script code**
|
|
12
|
-
related to pages.
|
|
13
|
-
|
|
14
|
-
The compat layer wraps `Ecoportal::API::GraphQL` and exposes the same interface that
|
|
15
|
-
eco-helpers scripts currently call against `Ecoportal::API::V2`.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## eco-helpers API Surface (audited 2026-06-07)
|
|
20
|
-
|
|
21
|
-
### Pages
|
|
22
|
-
|
|
23
|
-
| v2 call | Description | GraphQL equivalent |
|
|
24
|
-
|---|---|---|
|
|
25
|
-
| `apiv2.pages.get(id)` | Fetch page by ID | `Query::Page.new(client).query(id: id)` |
|
|
26
|
-
| `apiv2.pages.get_new(template_id)` | Build draft from template | `Mutation::Page::BuildFromTemplate` (via `buildPageFromTemplate`) |
|
|
27
|
-
| `apiv2.pages.create(page, from: template_id)` | Create from filled draft | `Mutation::Page::CreateFromTemplate` |
|
|
28
|
-
| `apiv2.pages.update(page)` | Update changed page | `Mutation::Page::Update` with `Input::Page::Update.from_model` |
|
|
29
|
-
| `apiv2.pages.get_body(page)` | Get patch doc of changes | `page.as_update` |
|
|
30
|
-
|
|
31
|
-
### Registers / Search
|
|
32
|
-
|
|
33
|
-
| v2 call | Description | GraphQL equivalent |
|
|
34
|
-
|---|---|---|
|
|
35
|
-
| `apiv2.registers.search(register_id, opts) { \|r\| }` | Paginated search | `Query::RegisterPreviewPages` (fast/ES) for existence; org search for full data |
|
|
36
|
-
|
|
37
|
-
Search options v2 → GraphQL mapping:
|
|
38
|
-
- `sort: 'created_at'`, `dir: 'asc'` → `SearchConf.new.sort(:created_at, :asc)`
|
|
39
|
-
- `query: text` → `SearchConf.new.query(text)`
|
|
40
|
-
- `filters: [...]` → `SearchConf` with appropriate filter objects
|
|
41
|
-
|
|
42
|
-
### Page object API
|
|
43
|
-
|
|
44
|
-
| v2 accessor | v2 type | GraphQL equivalent |
|
|
45
|
-
|---|---|---|
|
|
46
|
-
| `.id` | String | `Model::Page::Basic#id` |
|
|
47
|
-
| `.state` | String | `#state` |
|
|
48
|
-
| `.template_id` | String | `#sourceTemplateId` |
|
|
49
|
-
| `.created_at` | String | `#createdAt` |
|
|
50
|
-
| `.updated_at` | String | `#updatedAt` |
|
|
51
|
-
| `.stages?` | Boolean | `#stages.any?` |
|
|
52
|
-
| `.current_stage_id` | String | `Model::Page::Phased#currentStage.id` |
|
|
53
|
-
| `.stages` | Collection | `#stages` |
|
|
54
|
-
| `.stages.get_by_name(name)` | Stage | filtered stages |
|
|
55
|
-
| `.sections` | Collection | stage `.sections` or BasicPage `.sections` |
|
|
56
|
-
| `.components` | Collection | sections → dataFields (APIv2 name for fields) |
|
|
57
|
-
| `.tags` | Array | `#otherTags` (or `#baseTags` for base tags) |
|
|
58
|
-
| `.base_tags` | Array | `#baseTags` |
|
|
59
|
-
| `.external_id` | String | `#externalId` |
|
|
60
|
-
| `.validate` | String or nil | custom validation (not in GraphQL schema) |
|
|
61
|
-
| `page.consolidate!` | void | `page.original_doc = page.doc.dup` (reset dirty tracking) |
|
|
62
|
-
|
|
63
|
-
### Dirty state tracking
|
|
64
|
-
|
|
65
|
-
v2 uses `dirty?(page)` which checks if `patch_doc(page)['page']` is non-empty.
|
|
66
|
-
GraphQL uses `page.as_update` — returns nil if no changes, hash if changed.
|
|
67
|
-
|
|
68
|
-
Equivalence:
|
|
69
|
-
```ruby
|
|
70
|
-
# v2
|
|
71
|
-
dirty?(page)
|
|
72
|
-
# GraphQL compat
|
|
73
|
-
!page.as_update.nil?
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Response objects
|
|
77
|
-
|
|
78
|
-
| v2 | GraphQL |
|
|
79
|
-
|---|---|
|
|
80
|
-
| `response.success?` | `payload.success?` |
|
|
81
|
-
| `response.status` | `payload.error? ? :error : :ok` |
|
|
82
|
-
| `response.body` | `payload.error_doc` |
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## Design: CompatPages and CompatRegisters
|
|
87
|
-
|
|
88
|
-
Two wrapper objects that expose the v2-compatible interface:
|
|
89
|
-
|
|
90
|
-
```ruby
|
|
91
|
-
module Ecoportal::API::GraphQL::Compat
|
|
92
|
-
class Pages
|
|
93
|
-
def initialize(client); @client = client; end
|
|
94
|
-
def get(id, **kargs) = ...
|
|
95
|
-
def get_new(template_id) = ...
|
|
96
|
-
def create(page, from:) = ...
|
|
97
|
-
def update(page) = ...
|
|
98
|
-
def get_body(page) = page.as_update
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
class Registers
|
|
102
|
-
def initialize(client); @client = client; end
|
|
103
|
-
def search(register_id, options = {}, &block) = ...
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
And on the top-level `Ecoportal::API::GraphQL` class:
|
|
109
|
-
```ruby
|
|
110
|
-
def pages = Compat::Pages.new(client)
|
|
111
|
-
def registers = Compat::Registers.new(client)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**Key constraints:**
|
|
115
|
-
- `pages.get` must return an object that responds to all page accessors above
|
|
116
|
-
- `pages.create` must return a reference object with `.page_id` and `.active_stage_id`
|
|
117
|
-
- `page.consolidate!` must reset dirty state
|
|
118
|
-
- `registers.search` must yield results in the same shape as v2 (block with `.id`)
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Architectural Differences: APIv2 vs GraphQL (critical for compat)
|
|
123
|
-
|
|
124
|
-
### Stage rendering and updating
|
|
125
|
-
|
|
126
|
-
**APIv2:** Instance pages (pages with stages) are rendered ONE STAGE PER REQUEST.
|
|
127
|
-
Every `pages.get(id, stage_id:)` call fetches a single stage view. Updating is also
|
|
128
|
-
done per-stage. This is a known limitation.
|
|
129
|
-
|
|
130
|
-
**GraphQL:** Fetches the full page in one request. Can update all stages at once via
|
|
131
|
-
`updatePage`. Can also do stage-specific updates via `stageId` in `UpdatePageInput`.
|
|
132
|
-
|
|
133
|
-
**Implication for compat:**
|
|
134
|
-
- eco-helpers `RegisterUpdate` loops through stages by fetching each one. In GraphQL
|
|
135
|
-
compat, we fetch the full page once and let the script work on a client-side stage view.
|
|
136
|
-
- Stage-specific submit (e.g. close-out stage N to trigger fill-in task on stage N+1)
|
|
137
|
-
STILL requires `stageId` in the GraphQL mutation. The submit trigger (creating/starting
|
|
138
|
-
the next fill-in task) is server-side logic keyed on `stageId`.
|
|
139
|
-
- `Compat::Pages#get(id, stage_id:)` currently ignores `stage_id` — this is a GAP.
|
|
140
|
-
A future improvement: if `stage_id` is provided, filter the returned page to show only
|
|
141
|
-
that stage's fields. But the mutation must still include `stageId`.
|
|
142
|
-
|
|
143
|
-
### Stage permissions
|
|
144
|
-
|
|
145
|
-
**APIv2:** Per-stage permissions enforced at fetch time — requesting a forbidden stage
|
|
146
|
-
returns no data or an error.
|
|
147
|
-
|
|
148
|
-
**GraphQL:** Stage data is included or excluded silently based on permissions — no
|
|
149
|
-
access errors, just reduced data. A stage the user can't access simply won't appear in
|
|
150
|
-
`stages` or `stagesIndex`. (Needs double-check with Engineering.)
|
|
151
|
-
|
|
152
|
-
**Implication:** Scripts that rely on access errors from APIv2 to detect permission
|
|
153
|
-
boundaries may not get the same signal from GraphQL. Scripts should check whether the
|
|
154
|
-
stage they expect is present in the response.
|
|
155
|
-
|
|
156
|
-
### `as_update` usage in eco-helpers
|
|
157
|
-
|
|
158
|
-
`as_update` is called in eco-helpers for two purposes:
|
|
159
|
-
1. **Simulate/dry_run mode:** Print what would change without sending the request.
|
|
160
|
-
2. **Dirty detection:** `dirty?(page)` = `!page.as_update.nil?` (v2 pattern).
|
|
161
|
-
|
|
162
|
-
Our compat `dirty?` already delegates to `as_update` via `PageCompat`. ✓
|
|
163
|
-
|
|
164
|
-
For simulate mode: when `graphql.pages.update(page)` is called in dry_run mode,
|
|
165
|
-
eco-helpers should call `pages.get_body(page)` (= `page.as_update`) and display the
|
|
166
|
-
diff without making the HTTP request.
|
|
167
|
-
|
|
168
|
-
### `add_component` / `add_field` gap
|
|
169
|
-
|
|
170
|
-
APIv2 allows adding NEW fields to a page via `components.add(...)`. In GraphQL,
|
|
171
|
-
`updatePage` has `dataFields: { additions: [DataFieldInput], updates: [...], deletions: [ID] }`.
|
|
172
|
-
We implemented `updates` only. `additions` is not yet supported in the compat layer.
|
|
173
|
-
|
|
174
|
-
**Consequence:** Scripts that call `add_field` or `components.add` will not work yet.
|
|
175
|
-
This is a known gap. Document it for the integration test phase.
|
|
176
|
-
|
|
177
|
-
### `apiv2.pages.create|update` redirect
|
|
178
|
-
|
|
179
|
-
The eco-helpers `Eco::API::Session` manages an `apiv2` object. Redirecting to GraphQL
|
|
180
|
-
requires swapping the `apiv2` session backend without changing scripts. Two approaches:
|
|
181
|
-
|
|
182
|
-
1. **Session-level swap (eco-helpers):** Override `apiv2.pages` and `apiv2.registers`
|
|
183
|
-
in the eco-helpers session to return the GraphQL compat objects. Scripts are unchanged.
|
|
184
|
-
This is the cleanest approach for the 3-week deadline.
|
|
185
|
-
|
|
186
|
-
2. **Proxy class:** Create `Ecoportal::API::GraphQL::V2Proxy` that mimics the full V2
|
|
187
|
-
API surface. Assign this as the session backend when GraphQL mode is active.
|
|
188
|
-
|
|
189
|
-
The GraphQL gem already provides `graphql.pages` and `graphql.registers`. The eco-helpers
|
|
190
|
-
change is only in how the session wires the backend — NOT in the scripts themselves.
|
|
191
|
-
|
|
192
|
-
### Template updates
|
|
193
|
-
|
|
194
|
-
Engineering does NOT want template updates via GraphQL currently — templates are still
|
|
195
|
-
managed via the old AngularJS front-end. Do NOT expose template mutation endpoints from
|
|
196
|
-
the compat layer. `Query::Templates` (read-only list) is fine. Mutations against templates
|
|
197
|
-
should be blocked or at minimum undocumented.
|
|
198
|
-
|
|
199
|
-
### Forces (AngularJS front-end snippets)
|
|
200
|
-
|
|
201
|
-
Forces are embedded front-end code snippets that attach to specific stages based on
|
|
202
|
-
their bindings. They are being REPLACED by the Workflow Builder (see below). Low priority
|
|
203
|
-
for scripting integration. Forces are not queryable/mutable via GraphQL in a meaningful way.
|
|
204
|
-
|
|
205
|
-
### Workflow Builder (in progress, high change velocity)
|
|
206
|
-
|
|
207
|
-
The Workflow Builder (`pagesWorkflow`) allows configuring automated behaviours:
|
|
208
|
-
- Deployed: notifications, auto page creation, task assignments
|
|
209
|
-
- Not yet deployed: conditional field/section workflows (show/hide sections based on
|
|
210
|
-
field values, computed risk ratings, gauge/select interdependencies)
|
|
211
|
-
- Recently refactored (quick fixes via CI/CD for new customers)
|
|
212
|
-
|
|
213
|
-
**Implication for scripting:** Workflow Builder operations are not part of the
|
|
214
|
-
eco-helpers compat scope. However, scripts must be aware that workflow triggers
|
|
215
|
-
(task creation, stage transitions) can fire as side effects of `updatePage`.
|
|
216
|
-
|
|
217
|
-
### DSL location decision (confirmed 2026-06-07)
|
|
218
|
-
|
|
219
|
-
The compat DSL (page accessors, `components`, `stages[name]`, `consolidate!`, etc.)
|
|
220
|
-
sits in `ecoportal-api-graphql` as concerns included on page models. This is correct
|
|
221
|
-
because these are upstream-dependency concerns of this gem.
|
|
222
|
-
|
|
223
|
-
The eco-helpers `OozeBase` / `RegisterUpdate` inheritance chain will transparently
|
|
224
|
-
use the GraphQL-backed models once the session backend is swapped. No script code changes.
|
|
225
|
-
|
|
226
|
-
However: some DSL methods (`add_field`, `add_component`, stage-specific submit) may
|
|
227
|
-
need to live in eco-helpers itself if they cannot be cleanly expressed at the gem level.
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## Open Questions (Resolved 2026-06-07)
|
|
232
|
-
|
|
233
|
-
1. ✓ Filter format: translated in `Compat::Registers` (date_filter lbound/ubound → gte/lte)
|
|
234
|
-
2. ✓ Components naming: `components` aliased to `field_collection` via `DataFieldAccess`
|
|
235
|
-
3. ✓ `get_new` shape: `buildFromTemplate` returns full page with field IDs via `CommonPageUnion`
|
|
236
|
-
4. ✓ Stage access: `StageCollection` wrapper provides `[]`, `get_by_name`, `ordered`
|
|
237
|
-
5. Script repo access: pending `.claude/settings.json` rules configuration
|
|
238
|
-
|
|
239
|
-
## Remaining Gaps
|
|
240
|
-
|
|
241
|
-
- `stage_id` in `pages.get` currently ignored — stage-specific view not filtered client-side
|
|
242
|
-
- `stageId` in `updatePage` not yet passed through compat — needed for stage submit/close-out
|
|
243
|
-
- `add_field` / `add_component` (dataFields.additions) not implemented
|
|
244
|
-
- Stage-specific permissions may behave differently (silent omission vs error)
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
# eco-helpers → GraphQL Migration Guide
|
|
2
|
-
|
|
3
|
-
*Migrating eco-helpers scripts from `ecoportal-api-v2` to `ecoportal-api-graphql`.*
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## TL;DR
|
|
8
|
-
|
|
9
|
-
For most page scripts: **change one line** (the API initialiser). Everything else stays the same.
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
# Before (APIv2)
|
|
13
|
-
api = Ecoportal::API::V2.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG'])
|
|
14
|
-
|
|
15
|
-
# After (GraphQL)
|
|
16
|
-
api = Ecoportal::API::GraphQL.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG'])
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
`api.pages` and `api.registers` expose the same interface — the same method names,
|
|
20
|
-
the same argument shapes, the same block and return-value patterns.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## What Changes
|
|
25
|
-
|
|
26
|
-
### Initialiser
|
|
27
|
-
|
|
28
|
-
| | APIv2 | GraphQL |
|
|
29
|
-
|---|---|---|
|
|
30
|
-
| Class | `Ecoportal::API::V2` | `Ecoportal::API::GraphQL` |
|
|
31
|
-
| Gem | `ecoportal-api-v2` | `ecoportal-api-graphql` |
|
|
32
|
-
| `email:` | ✓ | ✓ |
|
|
33
|
-
| `pass:` | ✓ | ✓ |
|
|
34
|
-
| `org_id:` | ✓ | ✓ |
|
|
35
|
-
| `host:` | ✓ (default `live.ecoportal.com`) | ✓ (same default) |
|
|
36
|
-
|
|
37
|
-
### Gem dependency in your script's Gemfile
|
|
38
|
-
|
|
39
|
-
```ruby
|
|
40
|
-
# Remove:
|
|
41
|
-
gem 'ecoportal-api-v2'
|
|
42
|
-
|
|
43
|
-
# Add:
|
|
44
|
-
gem 'ecoportal-api-graphql'
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## What Stays the Same
|
|
50
|
-
|
|
51
|
-
### `pages` interface
|
|
52
|
-
|
|
53
|
-
| Method | APIv2 signature | GraphQL compat | Notes |
|
|
54
|
-
|---|---|---|---|
|
|
55
|
-
| `get(id)` | `pages.get(id)` | ✓ identical | |
|
|
56
|
-
| `get(id, stage_id:)` | `pages.get(id, stage_id: sid)` | ✓ identical | Returns `StageView` |
|
|
57
|
-
| `get_new(template_id)` | `pages.get_new(tid)` | ✓ identical | Returns built PageUnion |
|
|
58
|
-
| `create(page, from:)` | `pages.create(page, from: tid)` | ✓ identical | Returns `PageReference` |
|
|
59
|
-
| `update(page)` | `pages.update(page)` | ✓ identical | Returns `Response` |
|
|
60
|
-
| `get_body(page)` | `pages.get_body(page)` | ✓ identical | Returns change hash |
|
|
61
|
-
|
|
62
|
-
### Page model accessors
|
|
63
|
-
|
|
64
|
-
| Accessor | Works? | Notes |
|
|
65
|
-
|---|---|---|
|
|
66
|
-
| `page.id` | ✓ | |
|
|
67
|
-
| `page.name` | ✓ | |
|
|
68
|
-
| `page.external_id` | ✓ | maps to `externalId` |
|
|
69
|
-
| `page.template_id` | ✓ | maps to `sourceTemplateId` |
|
|
70
|
-
| `page.stages?` | ✓ | |
|
|
71
|
-
| `page.stages['Stage Name']` | ✓ | via `Compat::StageCollection` |
|
|
72
|
-
| `page.stages.get_by_name(n)` | ✓ | |
|
|
73
|
-
| `page.stages.ordered.map(&:id)` | ✓ | |
|
|
74
|
-
| `page.current_stage_id` | ✓ | on PhasedPage only |
|
|
75
|
-
| `page.components` | ✓ | returns `DataField::Collection` |
|
|
76
|
-
| `page.components.get_by_name(l)` | ✓ | case-insensitive |
|
|
77
|
-
| `page.components.get_by_type(t)` | ✓ | symbol or string |
|
|
78
|
-
| `page.dirty?` | ✓ | |
|
|
79
|
-
| `page.consolidate!` | ✓ | resets change tracking |
|
|
80
|
-
| `page.validate` | ✓ (always nil) | server validates on mutation |
|
|
81
|
-
| `stage.components` | ✓ | |
|
|
82
|
-
| `stage.sections` | ✓ | |
|
|
83
|
-
| `stage.active?` / `stage.started?` | ✓ | |
|
|
84
|
-
|
|
85
|
-
### Page reference (after create)
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
ref = api.pages.create(draft, from: template_id)
|
|
89
|
-
ref.page_id # ✓
|
|
90
|
-
ref.active_stage_id # ✓
|
|
91
|
-
ref.success? # ✓
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### `registers` interface
|
|
95
|
-
|
|
96
|
-
| Method | APIv2 | GraphQL compat | Notes |
|
|
97
|
-
|---|---|---|---|
|
|
98
|
-
| `search(id, opts) { \|r\| }` | ✓ | ✓ | Cursor-paginated |
|
|
99
|
-
| `result.id` in block | ✓ | ✓ | |
|
|
100
|
-
| No-block → `.total` | ✓ | ✓ | via `SearchResults` |
|
|
101
|
-
| `.total_before_filtering` | ✓ | ✓ | |
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## What Requires Review
|
|
106
|
-
|
|
107
|
-
### 1. Filter format in `registers.search`
|
|
108
|
-
|
|
109
|
-
Scripts that pass v2-style filter hashes are **automatically translated** by
|
|
110
|
-
`Compat::FilterTranslator`. You only need to review if your script passes filters
|
|
111
|
-
in a non-standard or custom format.
|
|
112
|
-
|
|
113
|
-
**Automatic translation (no action needed):**
|
|
114
|
-
|
|
115
|
-
```ruby
|
|
116
|
-
# v2 date_filter with lbound/ubound — automatically converted to gte/lte
|
|
117
|
-
{ 'type' => 'date_filter', 'key' => 'updated_at', 'lbound' => '2025-01-01', 'ubound' => '2025-12-31' }
|
|
118
|
-
# → { operation: 'date_filter', params: { key: 'updated_at', gte: '2025-01-01', lte: '2025-12-31' } }
|
|
119
|
-
|
|
120
|
-
# tag_filter — passed through unchanged (same ES backend)
|
|
121
|
-
{ 'type' => 'tag_filter', 'tags' => [...] }
|
|
122
|
-
|
|
123
|
-
# register_filter — passed through unchanged
|
|
124
|
-
{ 'type' => 'register_filter', 'params' => { 'ids' => [...] } }
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
**Requires review:** if your script builds filters with keys like `extact_filter`
|
|
128
|
-
(typo), non-standard operation names, or custom filter shapes not listed above —
|
|
129
|
-
those will pass through unchanged and may return empty results silently.
|
|
130
|
-
See `search_filters.md` for the full list of valid operation names (all snake_case).
|
|
131
|
-
|
|
132
|
-
**Quick search to run on your script repo:**
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
grep -rn "date_filter\|tag_filter\|filters.*type\|type.*filter" scripts/
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### 2. `pages.update` — always fetch before update
|
|
139
|
-
|
|
140
|
-
The GraphQL path requires `patchVer` for concurrency control.
|
|
141
|
-
`Input::Page::Update.from_model` injects it automatically **if you fetched the page first**.
|
|
142
|
-
|
|
143
|
-
```ruby
|
|
144
|
-
# Correct — always fetch first
|
|
145
|
-
page = api.pages.get(page_id)
|
|
146
|
-
page.name = 'New Name'
|
|
147
|
-
api.pages.update(page)
|
|
148
|
-
|
|
149
|
-
# Incorrect — updating a page you constructed manually (no patchVer)
|
|
150
|
-
page = MyManualPageObject.new(...)
|
|
151
|
-
api.pages.update(page) # ⚠ patchVer will be nil — server may reject
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
If your script fetches pages via `get` before updating, no change needed.
|
|
155
|
-
If your script constructs page objects manually without fetching — review those paths.
|
|
156
|
-
|
|
157
|
-
### 3. `get_new` — template ID must be the live template ID
|
|
158
|
-
|
|
159
|
-
`pages.get_new(template_id)` calls `buildPageFromTemplate` on the GraphQL API.
|
|
160
|
-
The `template_id` must be the live MongoDB ObjectId, same as in APIv2.
|
|
161
|
-
|
|
162
|
-
The return value has the same structure as in APIv2 — `page.components`,
|
|
163
|
-
`page.stages`, etc. Field IDs are server-assigned instance IDs, not template IDs.
|
|
164
|
-
You must use these IDs when creating the page (the compat layer handles this
|
|
165
|
-
automatically via `page.data_fields_updates`).
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## Known Limitations
|
|
170
|
-
|
|
171
|
-
### 1. Field types — read access only for some types
|
|
172
|
-
|
|
173
|
-
The following field types are **readable** but cannot be written via `as_input`:
|
|
174
|
-
|
|
175
|
-
| Type | Status | Notes |
|
|
176
|
-
|---|---|---|
|
|
177
|
-
| `Signature` | Read only | Captured via UI |
|
|
178
|
-
| `Mailbox` | Read only | System-managed |
|
|
179
|
-
| `ActionsList` | Read only | Manage via `api.action` builder |
|
|
180
|
-
| `Law` | Read only | Legal snippets, not editable via script |
|
|
181
|
-
| `AiSummary` | Read only | AI-generated |
|
|
182
|
-
| `Table` | Read only | Complex structure |
|
|
183
|
-
|
|
184
|
-
The following types **are writable** but require a file upload step first:
|
|
185
|
-
|
|
186
|
-
| Type | Status | Notes |
|
|
187
|
-
|---|---|---|
|
|
188
|
-
| `File` | Writable* | Requires REST upload → fileContainerIds |
|
|
189
|
-
| `ImageGallery` | Writable* | Same as File |
|
|
190
|
-
|
|
191
|
-
\* File upload support is on the roadmap. For now, `file_container_ids=` will work
|
|
192
|
-
if you already have `fileContainerIds` from a prior upload.
|
|
193
|
-
|
|
194
|
-
### 2. `pages.search` — not implemented on `Compat::Pages`
|
|
195
|
-
|
|
196
|
-
APIv2's `pages.search(...)` is not part of the compat layer.
|
|
197
|
-
Use `api.registers.search(register_id, ...)` for register-scoped search,
|
|
198
|
-
or use `Model::Organization.pages(searchConf: conf)` for org-wide search.
|
|
199
|
-
|
|
200
|
-
### 3. Performance — org search is ~3s/page
|
|
201
|
-
|
|
202
|
-
The GraphQL `pages.get` (and `pages.get_new`) fetches full DB-rendered page data.
|
|
203
|
-
This is ~3s per page for large pages, compared to ~200ms in APIv2 for the same data.
|
|
204
|
-
|
|
205
|
-
Mitigation: batch fetches, or use `api.registers.search` (ES-backed, fast) to
|
|
206
|
-
get page IDs first, then fetch only the pages you actually need to update.
|
|
207
|
-
|
|
208
|
-
### 4. `simulate?` / dry-run mode — no change
|
|
209
|
-
|
|
210
|
-
The compat layer honours `get_body` / `dirty?` / `consolidate!` as before.
|
|
211
|
-
Dry-run logic that checks `dirty?` and skips `update` if not dirty continues to work.
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## Quick Verification Checklist
|
|
216
|
-
|
|
217
|
-
Before switching a script to the GraphQL gem:
|
|
218
|
-
|
|
219
|
-
- [ ] Grep for `API::V2.new` — replace with `API::GraphQL.new`
|
|
220
|
-
- [ ] Grep for custom filter formats — verify they translate correctly
|
|
221
|
-
- [ ] Grep for manual page construction (without `pages.get`) — check patchVer supply
|
|
222
|
-
- [ ] Grep for `pages.search` — replace with `registers.search` or org search
|
|
223
|
-
- [ ] Run the script in `simulate` mode against a staging org
|
|
224
|
-
|
|
225
|
-
---
|
|
226
|
-
|
|
227
|
-
## Example: minimal diff for a typical update script
|
|
228
|
-
|
|
229
|
-
```ruby
|
|
230
|
-
# Before
|
|
231
|
-
require 'ecoportal/api-v2'
|
|
232
|
-
api = Ecoportal::API::V2.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG'])
|
|
233
|
-
|
|
234
|
-
api.registers.search('REG_ID', filters: [date_range_filter('updated_at', from: '2025-01-01')]) do |result|
|
|
235
|
-
page = api.pages.get(result.id)
|
|
236
|
-
next unless page.dirty?
|
|
237
|
-
page.components.get_by_name('Status')&.select_option('Active')
|
|
238
|
-
api.pages.update(page)
|
|
239
|
-
page.consolidate!
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
# After (change highlighted)
|
|
243
|
-
require 'ecoportal/api-graphql' # ← changed gem
|
|
244
|
-
api = Ecoportal::API::GraphQL.new(email: ENV['EMAIL'], pass: ENV['PASS'], org_id: ENV['ORG']) # ← changed class
|
|
245
|
-
|
|
246
|
-
api.registers.search('REG_ID', filters: [date_range_filter('updated_at', from: '2025-01-01')]) do |result|
|
|
247
|
-
page = api.pages.get(result.id)
|
|
248
|
-
next unless page.dirty?
|
|
249
|
-
page.components.get_by_name('Status')&.select_option('Active')
|
|
250
|
-
api.pages.update(page)
|
|
251
|
-
page.consolidate!
|
|
252
|
-
end
|
|
253
|
-
# Everything else is identical.
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## Rollback
|
|
259
|
-
|
|
260
|
-
If a script needs to revert to APIv2:
|
|
261
|
-
|
|
262
|
-
1. Change the require back to `ecoportal/api-v2`
|
|
263
|
-
2. Change the initialiser class back to `Ecoportal::API::V2`
|
|
264
|
-
3. No other code changes needed
|
|
265
|
-
|
|
266
|
-
The compat layer was designed so the rollback is always a one-line change.
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# TODOs — eco-helpers GraphQL Compat Layer
|
|
2
|
-
|
|
3
|
-
> URGENT — APIv2 retires in ~3 weeks.
|
|
4
|
-
> Natural order: 1 → 2 → 3 → 4 → 5 (resolve open questions before implementing)
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Step 1 — Resolve open questions (Done 2026-06-07)
|
|
9
|
-
|
|
10
|
-
- [x] **1.1** Filter format confirmed: scripts use `tags_filter` and `date_range_filter`
|
|
11
|
-
helpers producing `type: "date_filter"` with `lbound`/`ubound` keys.
|
|
12
|
-
Translation: date_filter lbound→gte, ubound→lte. Other types pass through.
|
|
13
|
-
|
|
14
|
-
- [x] **1.2** `registers.search` block result: yields individual PreviewPage objects (.id etc).
|
|
15
|
-
No-block form returns an object with `.total` and `.total_before_filtering`.
|
|
16
|
-
Implemented: `Compat::SearchResults` wraps connection with both properties.
|
|
17
|
-
|
|
18
|
-
- [ ] **1.3** `pages.get_new` draft shape — still waiting on end-script repo access.
|
|
19
|
-
For now: `buildPageFromTemplate` returns PageUnion with field IDs and stagesIndex.
|
|
20
|
-
|
|
21
|
-
- [x] **1.4** `pages.create` reference: only `.page_id` and `.active_stage_id` used.
|
|
22
|
-
`Compat::PageReference` implements both correctly.
|
|
23
|
-
|
|
24
|
-
- [x] **1.5** Stage access: `stages[id_name]` AND `stages.get_by_name(id_name)` AND
|
|
25
|
-
`stages.ordered.map(&:id)`. Implemented via `Compat::StageCollection` wrapper
|
|
26
|
-
included via `Model::Page::Phased#stages` override.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Step 2 — Scaffold `Compat::Pages` and `Compat::Registers` (Done 2026-06-07)
|
|
31
|
-
|
|
32
|
-
- [x] **2.1** `lib/ecoportal/api/graphql/compat/pages.rb`: `get`, `get_new`, `create`, `update`, `get_body`.
|
|
33
|
-
*Done 2026-06-07*
|
|
34
|
-
|
|
35
|
-
- [x] **2.2** `lib/ecoportal/api/graphql/compat/registers.rb`: `search` with cursor pagination
|
|
36
|
-
and v2 filter translation (date_filter lbound/ubound → gte/lte).
|
|
37
|
-
*Done 2026-06-07*
|
|
38
|
-
|
|
39
|
-
- [x] **2.3** Wired from top-level `Ecoportal::API::GraphQL`: `pages` → `Compat::Pages`, `registers` → `Compat::Registers`.
|
|
40
|
-
*Done 2026-06-07*
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Step 3 — Page model compatibility wrapper (Done 2026-06-07)
|
|
45
|
-
|
|
46
|
-
- [x] **3.1** `Concerns::PageCompat` included in `Interface::BasePage`:
|
|
47
|
-
`external_id`, `template_id`, `components`, `stages?`, `consolidate!`, `validate`, `dirty?`.
|
|
48
|
-
`current_stage_id` defined on `Model::Page::Phased` (reads in-progress stage from doc).
|
|
49
|
-
19 specs: `spec/ecoportal/api/graphql/concerns/page_compat_spec.rb`.
|
|
50
|
-
*Done 2026-06-07*
|
|
51
|
-
|
|
52
|
-
- [x] **3.2** `Compat::StageCollection`: `stages[id_or_name]`, `get_by_name`, `ordered`.
|
|
53
|
-
Wrapped via `Model::Page::Phased#stages` override.
|
|
54
|
-
Specs: `spec/ecoportal/api/graphql/compat/stage_collection_spec.rb`.
|
|
55
|
-
*Done 2026-06-07*
|
|
56
|
-
|
|
57
|
-
- [x] **3.3** `Compat::PageReference`: `.page_id` and `.active_stage_id` (via `current_stage_id`).
|
|
58
|
-
Fixed: was using `currentStage` (undefined accessor); now uses `current_stage_id`.
|
|
59
|
-
Specs: `spec/ecoportal/api/graphql/compat/page_reference_spec.rb`.
|
|
60
|
-
*Done 2026-06-07*
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Step 4 — Search/filter translation
|
|
65
|
-
|
|
66
|
-
- [x] **4.1** `Compat::FilterTranslator.to_graphql(v2_filter)` — translates v2 filter hashes
|
|
67
|
-
to `SearchConf` objects. Handles `date_filter` (lbound→gte, ubound→lte), `tag_filter`,
|
|
68
|
-
`exact_filter`, `register_filter`; extracted from `Compat::Registers`.
|
|
69
|
-
*Done 2026-06-07 — 12 specs in `spec/ecoportal/api/graphql/compat/filter_translator_spec.rb`*
|
|
70
|
-
|
|
71
|
-
- [x] **4.2** `Logic::BaseQuery#normalize_variables` auto-calls `.to_h` on value objects
|
|
72
|
-
(e.g. `SearchConf`) before passing as GraphQL variables — callers no longer need explicit `.to_h`.
|
|
73
|
-
*Done 2026-06-07 (commit cc72902)*
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## Step 5 — Integration test + migration guide
|
|
78
|
-
|
|
79
|
-
- [ ] **5.1** Run a real end-script against the compat layer (requires end-script repo access).
|
|
80
|
-
User will provide once `.claude/settings.json` rules are set.
|
|
81
|
-
|
|
82
|
-
- [x] **5.2** `.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md` written.
|
|
83
|
-
Covers: one-line initialiser change, API compatibility table, filter review checklist,
|
|
84
|
-
patchVer requirement, known limitations (read-only field types, no pages.search,
|
|
85
|
-
~3s/page performance), example diff, rollback instructions.
|
|
86
|
-
*Done 2026-06-08*
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# Project: DoubleModel Review & Stabilisation (`ecoportal-api-v2`)
|
|
2
|
-
|
|
3
|
-
**Created:** 2026-06-05
|
|
4
|
-
**Status:** idea — not started
|
|
5
|
-
**Primary repo:** `ecoportal-api-v2`
|
|
6
|
-
**Affects:** `ecoportal-api-graphql` (consumers of `as_update` / `as_input` cascade)
|
|
7
|
-
**Blocks:** `dynamic-model-generation` project
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Goal
|
|
12
|
-
|
|
13
|
-
Review and stabilise the `DoubleModel` implementation in `ecoportal-api-v2`, with particular
|
|
14
|
-
focus on:
|
|
15
|
-
|
|
16
|
-
1. The `doc` / `original_doc` lookup chain through nested and Array-indexed models.
|
|
17
|
-
2. The `root!` / `read_only!` memoisation flags and their interaction with the cascaded diff.
|
|
18
|
-
3. Ensuring that memoised `@doc` cannot silently go out of sync with a parent's diff.
|
|
19
|
-
|
|
20
|
-
The outcome must be a `DoubleModel` contract that is reliable enough to underpin
|
|
21
|
-
`ecoportal-api-graphql`'s `as_update` / `as_input` pipeline and, eventually, auto-generated
|
|
22
|
-
model classes.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Background
|
|
27
|
-
|
|
28
|
-
The `DoubleModel` stores the canonical document (`@doc`) only at the root object. All
|
|
29
|
-
nested descendant objects resolve their own `doc` by walking up the ownership chain and
|
|
30
|
-
extracting their slice of the root `@doc` by key path.
|
|
31
|
-
|
|
32
|
-
This design is correct for Hash-keyed paths (keys are deterministic). It breaks down for
|
|
33
|
-
Array-indexed paths (`CollectionModel` and similar), because Array indexes are positional —
|
|
34
|
-
if order changes, a nested object's key path no longer resolves to the right element.
|
|
35
|
-
|
|
36
|
-
The current workaround is to look up the element by its own key from the parent at access
|
|
37
|
-
time, rather than by stored index. This lookup is performed every time `doc` or
|
|
38
|
-
`original_doc` is called on a nested object inside an Array, making it operationally costly:
|
|
39
|
-
|
|
40
|
-
- The lookup re-scans the parent array on each call.
|
|
41
|
-
- Objects under a `CollectionModel` may be instantiated multiple times during a single render
|
|
42
|
-
as a side effect of these repeated resolutions.
|
|
43
|
-
|
|
44
|
-
To mitigate this, `root!` and `read_only!` class-level flags were introduced. They allow
|
|
45
|
-
a model class to memoise `@doc` locally (breaking the chain) when it is known to be stable.
|
|
46
|
-
These flags are used extensively in `ecoportal-api-graphql` model and payload classes.
|
|
47
|
-
|
|
48
|
-
### Known risk
|
|
49
|
-
|
|
50
|
-
`root!` also signals to the cascaded diff (`DiffService`) that the object should be
|
|
51
|
-
excluded from the parent's diff. This is correct for read-only lookup objects (e.g. a
|
|
52
|
-
referenced Location node). But if `root!` is applied at a level where the object *is*
|
|
53
|
-
expected to contribute changes to the parent diff, the memoised `@doc` will silently
|
|
54
|
-
go out of sync — changes to that sub-model will not surface in `as_update`. This is
|
|
55
|
-
a subtle correctness risk that needs to be audited across all uses of `root!` and
|
|
56
|
-
`read_only!` in `ecoportal-api-graphql`.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Scope
|
|
61
|
-
|
|
62
|
-
- Audit all uses of `root!` and `read_only!` in `ecoportal-api-graphql` model and payload
|
|
63
|
-
classes: confirm each is correctly applied (genuinely read-only / excluded from diff).
|
|
64
|
-
- Review the Array-indexed lookup in `CollectionModel` (and related): document the current
|
|
65
|
-
behaviour, identify cases where it causes multiple object instantiation.
|
|
66
|
-
- Propose and implement a more efficient resolution strategy for Array-indexed nested models
|
|
67
|
-
that does not require rescanning on every `doc` / `original_doc` call.
|
|
68
|
-
- Ensure `patch_ver` propagation through the chain is correct after any changes.
|
|
69
|
-
- Confirm that the `initial_doc` / `original_doc` / `doc` three-state contract is
|
|
70
|
-
preserved correctly after any refactor.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Spec-first requirement
|
|
75
|
-
|
|
76
|
-
Specs related to any modification **must be written before the change is applied** to
|
|
77
|
-
`ecoportal-api-v2`, to lock in current behaviour and guard backwards compatibility.
|
|
78
|
-
Specs must also be maintained and extended after the change to prevent future regressions.
|
|
79
|
-
|
|
80
|
-
This applies both to the `ecoportal-api-v2` spec suite and to any `ecoportal-api-graphql`
|
|
81
|
-
specs that exercise the cascaded diff path.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## Open Questions
|
|
86
|
-
|
|
87
|
-
- Is the Array-indexed lookup fixable with a simple cache keyed by object identity
|
|
88
|
-
(`object_id`) that is invalidated on `doc=` / `reset!` calls?
|
|
89
|
-
- Are there cases in `ecoportal-api-graphql` where `root!` is applied incorrectly
|
|
90
|
-
(object should contribute to diff but doesn't)?
|
|
91
|
-
- What is the right `read_only!` vs `root!` semantic split? (`read_only!` = never
|
|
92
|
-
accept changes; `root!` = store doc locally + exclude from parent diff — are these
|
|
93
|
-
always the same thing, or should they be separable?)
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## Notes
|
|
98
|
-
|
|
99
|
-
This project is primarily work in `ecoportal-api-v2`, but the review must happen with
|
|
100
|
-
`ecoportal-api-graphql` open alongside it, since the usage patterns there drive what
|
|
101
|
-
needs to be fixed.
|