ecoportal-api-graphql 1.3.5 → 1.3.9
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/.ai-assistance/bridge/CLAUDE.md +338 -0
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +29 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +18 -0
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +42 -0
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +115 -0
- data/.ai-assistance/bridge/context/gemini-review-prompt.txt +48 -0
- data/.ai-assistance/bridge/context/gemini-review-response.md +104 -0
- data/.ai-assistance/bridge/context/project.md +42 -0
- 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 +48 -0
- data/.ai-assistance/bridge/queue/.gitkeep +1 -0
- data/.ai-assistance/capabilities/CLAUDE.md +27 -0
- data/.ai-assistance/capabilities/assumptions-log.md +80 -0
- data/.ai-assistance/capabilities/code.md +47 -0
- data/.ai-assistance/capabilities/connectors.md +37 -0
- data/.ai-assistance/capabilities/cowork.md +55 -0
- data/.ai-assistance/code/OVERVIEW.md +155 -0
- data/.ai-assistance/code/data_fields.md +242 -0
- data/.ai-assistance/code/dependencies.md +151 -0
- data/.ai-assistance/code/diff_as_input.md +234 -0
- data/.ai-assistance/code/diff_service_deep_dive.md +192 -0
- data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +55 -0
- data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +181 -0
- data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +192 -0
- data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +147 -0
- data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +277 -0
- data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +200 -0
- data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +228 -0
- data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +197 -0
- data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +243 -0
- data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +196 -0
- data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +132 -0
- data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +187 -0
- data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +236 -0
- data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +183 -0
- data/.ai-assistance/code/ecoportal_schema_reference.md +240 -0
- data/.ai-assistance/code/graphql_domain_knowledge.md +230 -0
- data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +71 -0
- data/.ai-assistance/code/refactoring/opportunities.md +251 -0
- data/.ai-assistance/code/schema_analysis.md +321 -0
- data/.ai-assistance/code/search_filters.md +868 -0
- data/.ai-assistance/code/spec_coverage.md +73 -0
- data/.ai-assistance/code/workflow-command-guide.md +438 -0
- data/.ai-assistance/code/workflow-space.md +353 -0
- data/.ai-assistance/conventions/CLAUDE.md +30 -0
- data/.ai-assistance/conventions/code-working-tree-protocol.md +199 -0
- data/.ai-assistance/conventions/gitignore-rules.md +42 -0
- data/.ai-assistance/conventions/permission-guidance.md +120 -0
- data/.ai-assistance/integrations/README.md +70 -0
- data/.ai-assistance/integrations/gitkraken-mcp.md +107 -0
- data/.ai-assistance/integrations/gitlab-mcp.md +123 -0
- data/.ai-assistance/integrations/local-git.md +60 -0
- data/.ai-assistance/local_paths.example.md +17 -0
- data/.ai-assistance/projects/TODO.md +97 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +168 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +60 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +267 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +53 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +102 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +139 -0
- data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +93 -0
- data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +244 -0
- data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +266 -0
- data/.ai-assistance/projects/eco-helpers-compat/TODO.md +86 -0
- data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +101 -0
- data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +177 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +161 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +125 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +126 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +256 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +122 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +148 -0
- data/.ai-assistance/projects/page-model/DECISIONS.md +245 -0
- data/.ai-assistance/projects/page-model/TODO.md +190 -0
- data/.ai-assistance/projects/search-filter-builder/INTENT.md +107 -0
- data/.ai-assistance/projects/search-filter-builder/TODO.md +131 -0
- data/.ai-assistance/projects/template-maintenance/DESIGN.md +134 -0
- data/.ai-assistance/projects/workflow-space/TODO.md +213 -0
- data/.ai-assistance/reinstall-claude-desktop-windows.md +136 -0
- data/.ai-assistance/scripts/CLAUDE.md +150 -0
- data/.ai-assistance/scripts/bridge-init.sh +86 -0
- data/.ai-assistance/scripts/bridge-status.sh +44 -0
- data/.ai-assistance/scripts/capabilities-check.ts +104 -0
- data/.ai-assistance/scripts/check-outbox.sh +43 -0
- data/.ai-assistance/scripts/dep_graph.rb +91 -0
- data/.ai-assistance/scripts/lock-acquire.sh +103 -0
- data/.ai-assistance/scripts/lock-multi.sh +124 -0
- data/.ai-assistance/scripts/lock-queue.sh +94 -0
- data/.ai-assistance/scripts/setup-mcps.test.ts +188 -0
- data/.ai-assistance/scripts/setup-mcps.ts +234 -0
- data/.ai-assistance/scripts/task-complete.ts +74 -0
- data/.ai-assistance/scripts/task-create.ts +75 -0
- data/.ai-assistance/scripts/task-read.ts +125 -0
- data/.ai-assistance/scripts/token-logger.js +220 -0
- data/.ai-assistance/scripts/token-report.ts +158 -0
- data/.ai-assistance/scripts/token-session-start.js +66 -0
- data/.ai-assistance/skills/ai-instructions/SKILL.md +48 -0
- data/.ai-assistance/skills/code-specs/SKILL.md +69 -0
- data/.ai-assistance/skills/corporate-policies/SKILL.md +201 -0
- data/.ai-assistance/skills/dep-graph/SKILL.md +139 -0
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +417 -0
- data/.ai-assistance/skills/gemini-assist/SKILL.md +63 -0
- data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +205 -0
- data/.ai-assistance/skills/gemini-assist/gemini_ask.py +1 -0
- data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +240 -0
- data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +25 -0
- data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +261 -0
- data/.ai-assistance/skills/project-cycle/SKILL.md +177 -0
- data/.ai-assistance/skills/refactor/SKILL.md +62 -0
- data/.ai-assistance/skills/rubocop/SKILL.md +93 -0
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +215 -0
- data/.ai-assistance/skills/spec-generation/SKILL.md +72 -0
- data/.ai-assistance/standards-version.json +21 -0
- data/.ai-assistance/token-budget.json +32 -0
- data/.ai-assistance/version.json +39 -0
- data/.claude/settings.json +146 -0
- data/.env.example +18 -0
- data/.gitattributes +15 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +121 -97
- data/CHANGELOG.md +673 -477
- data/CLAUDE.md +232 -0
- data/Gemfile +30 -6
- data/Rakefile +90 -38
- data/docs/worklog.md +574 -0
- data/ecoportal-api-graphql.gemspec +40 -40
- data/lib/ecoportal/api/common/graphql/CLAUDE.md +36 -0
- data/lib/ecoportal/api/common/graphql/client.rb +1 -1
- data/lib/ecoportal/api/common/graphql/http_client.rb +35 -3
- data/lib/ecoportal/api/common/graphql/model/CLAUDE.md +28 -0
- data/lib/ecoportal/api/common/graphql/model/as_input.rb +8 -5
- data/lib/ecoportal/api/common/graphql/model/diffable/classic_diff_service.rb +3 -1
- data/lib/ecoportal/api/common/graphql/model/diffable/diff_service.rb +31 -23
- data/lib/ecoportal/api/common/graphql/model/diffable/hash_diff_nesting.rb +54 -1
- data/lib/ecoportal/api/graphql/CLAUDE.md +37 -0
- data/lib/ecoportal/api/graphql/base/CLAUDE.md +50 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -0
- data/lib/ecoportal/api/graphql/base/delta_result.rb +16 -0
- data/lib/ecoportal/api/graphql/base/force/binding.rb +19 -0
- data/lib/ecoportal/api/graphql/base/force/binding_collection.rb +62 -0
- data/lib/ecoportal/api/graphql/base/force/collection.rb +47 -0
- data/lib/ecoportal/api/graphql/base/force.rb +65 -0
- data/lib/ecoportal/api/graphql/base/kickstand/job.rb +17 -0
- data/lib/ecoportal/api/graphql/base/kickstand/workflow.rb +18 -0
- data/lib/ecoportal/api/graphql/base/kickstand.rb +13 -0
- data/lib/ecoportal/api/graphql/base/page/basic.rb +38 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/actions_list.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/ai_summary.rb +18 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/checklist.rb +29 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +112 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +28 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/cross_reference.rb +54 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/date_field.rb +23 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +25 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/gauge.rb +19 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/geo.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/law.rb +8 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/mailbox.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/number.rb +20 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/people.rb +35 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/plain_text.rb +17 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/rich_text.rb +26 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/select.rb +41 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/signature.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/smart_fill.rb +17 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/table.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/tag_field.rb +21 -0
- data/lib/ecoportal/api/graphql/base/page/data_field.rb +137 -12
- data/lib/ecoportal/api/graphql/base/page/phased/stage.rb +68 -14
- data/lib/ecoportal/api/graphql/base/page/phased.rb +1 -0
- data/lib/ecoportal/api/graphql/base/page/section.rb +36 -0
- data/lib/ecoportal/api/graphql/base/page/section_collection.rb +79 -0
- data/lib/ecoportal/api/graphql/base/page.rb +2 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/action_type_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/callback_type.rb +28 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_change.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_change_message.rb +15 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_es_change.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_interface.rb +36 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/email_config.rb +18 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/escalation_level.rb +19 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/in_system_config.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/mailbox_field_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operation_interface.rb +39 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/assign_to.rb +27 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/create_page.rb +21 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/send_notification.rb +30 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/people_field_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/recipient_config.rb +34 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/register_field.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/task_config_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/time_delay_config.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/trigger_interface.rb +26 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/triggers/conditional_logic.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/user_selection.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow.rb +35 -0
- data/lib/ecoportal/api/graphql/base/preset_view.rb +17 -0
- data/lib/ecoportal/api/graphql/base/preview_page.rb +23 -0
- data/lib/ecoportal/api/graphql/base/register.rb +18 -0
- data/lib/ecoportal/api/graphql/base.rb +11 -0
- data/lib/ecoportal/api/graphql/builder/CLAUDE.md +65 -0
- data/lib/ecoportal/api/graphql/builder/kickstand.rb +73 -0
- data/lib/ecoportal/api/graphql/builder/page.rb +210 -41
- data/lib/ecoportal/api/graphql/builder/register/preset_view.rb +84 -0
- data/lib/ecoportal/api/graphql/builder/register.rb +27 -19
- data/lib/ecoportal/api/graphql/builder/template.rb +80 -0
- data/lib/ecoportal/api/graphql/builder.rb +2 -0
- data/lib/ecoportal/api/graphql/compat/filter_translator.rb +107 -0
- data/lib/ecoportal/api/graphql/compat/page_reference.rb +23 -0
- data/lib/ecoportal/api/graphql/compat/pages.rb +212 -0
- data/lib/ecoportal/api/graphql/compat/registers.rb +84 -0
- data/lib/ecoportal/api/graphql/compat/response.rb +35 -0
- data/lib/ecoportal/api/graphql/compat/search_results.rb +33 -0
- data/lib/ecoportal/api/graphql/compat/stage_collection.rb +70 -0
- data/lib/ecoportal/api/graphql/compat/stage_view.rb +76 -0
- data/lib/ecoportal/api/graphql/compat.rb +17 -0
- data/lib/ecoportal/api/graphql/concerns/data_field_access.rb +71 -0
- data/lib/ecoportal/api/graphql/concerns/deprecation.rb +20 -0
- data/lib/ecoportal/api/graphql/concerns/fragment_definitions.rb +21 -28
- data/lib/ecoportal/api/graphql/concerns/page_compat.rb +51 -0
- data/lib/ecoportal/api/graphql/concerns/snake_camel_access.rb +60 -0
- data/lib/ecoportal/api/graphql/concerns.rb +4 -0
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/pages_workflow_command.rb +13 -0
- data/lib/ecoportal/api/graphql/connection/preset_view.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -0
- data/lib/ecoportal/api/graphql/connection.rb +4 -0
- data/lib/ecoportal/api/graphql/file_upload/client.rb +181 -0
- data/lib/ecoportal/api/graphql/file_upload.rb +10 -0
- data/lib/ecoportal/api/graphql/fragment/action.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/action_category.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/contractor_entity.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/force.rb +30 -0
- data/lib/ecoportal/api/graphql/fragment/location_draft.rb +2 -2
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/locations_error.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/page.rb +85 -0
- data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +395 -0
- data/lib/ecoportal/api/graphql/fragment/pages.rb +15 -0
- data/lib/ecoportal/api/graphql/fragment/pages_workflow.rb +172 -0
- data/lib/ecoportal/api/graphql/fragment/pagination.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment.rb +37 -27
- data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +25 -0
- data/lib/ecoportal/api/graphql/input/delta_input.rb +16 -0
- data/lib/ecoportal/api/graphql/input/page/archive.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/build_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/create_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/create_from_template.rb +18 -0
- data/lib/ecoportal/api/graphql/input/page/delete_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/publish_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/review_task.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/unarchive.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/update.rb +140 -0
- data/lib/ecoportal/api/graphql/input/page.rb +26 -0
- data/lib/ecoportal/api/graphql/input/preset_view/create.rb +18 -0
- data/lib/ecoportal/api/graphql/input/preset_view/permission.rb +16 -0
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -0
- data/lib/ecoportal/api/graphql/input/preset_view.rb +14 -0
- data/lib/ecoportal/api/graphql/input/register/create.rb +18 -0
- data/lib/ecoportal/api/graphql/input/register/update.rb +15 -0
- data/lib/ecoportal/api/graphql/input/register.rb +13 -0
- data/lib/ecoportal/api/graphql/input/search_conf/ai_generator.rb +234 -0
- data/lib/ecoportal/api/graphql/input/search_conf.rb +367 -0
- data/lib/ecoportal/api/graphql/input/variable_binding.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_action_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_comment_tagging_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_default_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_gauge_field_stop.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_field_config.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_action_type.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_filter.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_people_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_task_config.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback_action.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_select_field_option.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_task.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_task_assignment_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_workflow_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/collapse_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_creator_permissions.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_gauge_field_stop.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_field_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_reminder.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_required_sign_offs.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_comment_tagging.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_task_assignment.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_scheduled_callback.rb +22 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_section_header.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_select_field_option.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_task_due.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_trigger.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/expand_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/contractor_entities.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/gauge.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/location_field.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/signature.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/table.rb +25 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_action_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_comment_tagging_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_gauge_field_stop.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_field_config.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_action_type.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_filter.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_people_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_task_config.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_scheduled_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_select_field_option.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_assignment_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_due.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_priority_level.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -0
- data/lib/ecoportal/api/graphql/input.rb +8 -0
- data/lib/ecoportal/api/graphql/interface/base_page.rb +100 -58
- data/lib/ecoportal/api/graphql/interface/location_structure/nodes.rb +27 -28
- data/lib/ecoportal/api/graphql/logic/base_model.rb +2 -0
- data/lib/ecoportal/api/graphql/logic/base_query.rb +45 -2
- data/lib/ecoportal/api/graphql/logic/input.rb +15 -0
- data/lib/ecoportal/api/graphql/model/ai_summary_version.rb +10 -0
- data/lib/ecoportal/api/graphql/model/organization.rb +65 -55
- data/lib/ecoportal/api/graphql/model/page/basic.rb +14 -0
- data/lib/ecoportal/api/graphql/model/page/phased.rb +82 -20
- data/lib/ecoportal/api/graphql/model/page.rb +1 -0
- data/lib/ecoportal/api/graphql/model/page_union.rb +21 -0
- data/lib/ecoportal/api/graphql/model/pages_workflow.rb +20 -0
- data/lib/ecoportal/api/graphql/model/preset_view.rb +10 -0
- data/lib/ecoportal/api/graphql/model/preview_page.rb +10 -0
- data/lib/ecoportal/api/graphql/model/register.rb +10 -0
- data/lib/ecoportal/api/graphql/model.rb +8 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary.rb +13 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_jobs.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_workflows.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/fail_job.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/start_job.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand.rb +18 -0
- data/lib/ecoportal/api/graphql/mutation/location_structure/apply_commands.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/add_commands.rb +2 -2
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/create.rb +2 -2
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/drop_bad_commands.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/publish.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/archive.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/build_from_template.rb +50 -0
- data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/create_from_template.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +69 -0
- data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +51 -0
- data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/unarchive.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/update.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/update_variable_bindings.rb +44 -0
- data/lib/ecoportal/api/graphql/mutation/page.rb +28 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/create.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/permission.rb +37 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/update.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view.rb +15 -0
- data/lib/ecoportal/api/graphql/mutation/register/create.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register/update.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register.rb +14 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -0
- data/lib/ecoportal/api/graphql/mutation/template/create.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -0
- data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template/publish.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/unpublish.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/update.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template.rb +18 -0
- data/lib/ecoportal/api/graphql/mutation.rb +8 -0
- data/lib/ecoportal/api/graphql/payload/ai_summary_generate.rb +12 -0
- data/lib/ecoportal/api/graphql/payload/execute_workflow_commands.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/force_commands.rb +31 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/job.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/workflow.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/kickstand.rb +15 -0
- data/lib/ecoportal/api/graphql/payload/location_structure/draft/create.rb +33 -34
- data/lib/ecoportal/api/graphql/payload/ok_payload.rb +21 -0
- data/lib/ecoportal/api/graphql/payload/page/archive.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/build_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/create_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/draft.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/unarchive.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/update.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/update_variable_bindings.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page.rb +19 -0
- data/lib/ecoportal/api/graphql/payload/preset_view.rb +11 -0
- data/lib/ecoportal/api/graphql/payload/register.rb +11 -0
- data/lib/ecoportal/api/graphql/payload/template/create.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/publish.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/unpublish.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/update.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/update_information.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template.rb +18 -0
- data/lib/ecoportal/api/graphql/payload.rb +11 -0
- data/lib/ecoportal/api/graphql/query/action.rb +1 -1
- data/lib/ecoportal/api/graphql/query/action_categories.rb +1 -1
- data/lib/ecoportal/api/graphql/query/actions.rb +2 -2
- data/lib/ecoportal/api/graphql/query/contractor_entities.rb +1 -1
- data/lib/ecoportal/api/graphql/query/file_upload_signature.rb +76 -0
- data/lib/ecoportal/api/graphql/query/location_structure/draft.rb +2 -2
- data/lib/ecoportal/api/graphql/query/location_structure.rb +1 -1
- data/lib/ecoportal/api/graphql/query/location_structures.rb +1 -1
- data/lib/ecoportal/api/graphql/query/page.rb +45 -0
- data/lib/ecoportal/api/graphql/query/page_delta.rb +47 -0
- data/lib/ecoportal/api/graphql/query/page_with_forces.rb +43 -0
- data/lib/ecoportal/api/graphql/query/pages.rb +59 -0
- data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +59 -0
- data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -0
- data/lib/ecoportal/api/graphql/query/register_preview_pages.rb +83 -0
- data/lib/ecoportal/api/graphql/query/templates.rb +53 -0
- data/lib/ecoportal/api/graphql/query.rb +11 -0
- data/lib/ecoportal/api/graphql.rb +60 -2
- data/lib/ecoportal/api/graphql_version.rb +5 -5
- data/scripts/auto-worker-scheduler.sh +386 -0
- data/tests/contractor_entity_create.rb +19 -19
- data/tests/contractor_entity_udpate.rb +20 -20
- data/tests/dump_page_model.rb +74 -0
- data/tests/loc_structure_get.rb +1 -2
- data/tests/loc_structure_update.rb +51 -51
- data/tests/loc_structures_get.rb +15 -15
- metadata +436 -5
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Data Fields — Code Spec
|
|
2
|
+
|
|
3
|
+
*All 20 DataField types, access patterns, mutation input serialisation, and Collection API.*
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Data fields are the form inputs within a page (rich content — not structural metadata).
|
|
10
|
+
Each field is a live instance with a MongoDB ObjectId (`id`) assigned on page creation.
|
|
11
|
+
|
|
12
|
+
All field types inherit from `Base::Page::DataField < Logic::BaseModel` and live in
|
|
13
|
+
`lib/ecoportal/api/graphql/base/page/data_field/`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## All 20 Field Types
|
|
18
|
+
|
|
19
|
+
| GraphQL `__typename` | Ruby class | Writable | Input key |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `PlainText` | `DataField::PlainText` | ✓ | `plainText` |
|
|
22
|
+
| `RichText` | `DataField::RichText` | ✓ | `richText` |
|
|
23
|
+
| `Date` | `DataField::DateField` | ✓ | `date` |
|
|
24
|
+
| `Number` | `DataField::Number` | ✓ | `number` |
|
|
25
|
+
| `Gauge` | `DataField::Gauge` | ✓ | `gauge` |
|
|
26
|
+
| `Select` | `DataField::Select` | ✓ | `select` |
|
|
27
|
+
| `Checklist` | `DataField::Checklist` | ✓ | `checklist` |
|
|
28
|
+
| `TagField` | `DataField::TagField` | ✓ | `locationField` |
|
|
29
|
+
| `People` | `DataField::People` | ✓ | `people` |
|
|
30
|
+
| `Geo` | `DataField::Geo` | ✓ | `geo` |
|
|
31
|
+
| `ContractorEntities` | `DataField::ContractorEntities` | ✓ | `contractorEntities` |
|
|
32
|
+
| `CrossReference` | `DataField::CrossReference` | ✓ | `crossReference` |
|
|
33
|
+
| `File` | `DataField::FileField` | ✓* | `file` |
|
|
34
|
+
| `ImageGallery` | `DataField::ImageGallery` | ✓* | `imageGallery` |
|
|
35
|
+
| `Signature` | `DataField::Signature` | — | — |
|
|
36
|
+
| `Mailbox` | `DataField::Mailbox` | — | — |
|
|
37
|
+
| `ActionsList` | `DataField::ActionsList` | — | — |
|
|
38
|
+
| `Law` | `DataField::Law` | — | — |
|
|
39
|
+
| `AiSummary` | `DataField::AiSummary` | — | — |
|
|
40
|
+
| `Table` | `DataField::Table` | — | — |
|
|
41
|
+
|
|
42
|
+
\* File and ImageGallery require a prior file upload to the REST upload endpoint to obtain
|
|
43
|
+
`fileContainerIds`. See `projects/TODO.md` — "File upload" item.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Dispatch
|
|
48
|
+
|
|
49
|
+
`DataField.from_doc(raw_hash)` reads `__typename` (or `type` if already normalised) and
|
|
50
|
+
returns the right subclass instance. Falls back to base `DataField` for unknown types.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
field = Base::Page::DataField.from_doc({ '__typename' => 'PlainText', 'id' => 'f1', 'value' => 'X' })
|
|
54
|
+
# => #<DataField::PlainText ...>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Field Access on Page Models
|
|
60
|
+
|
|
61
|
+
`Interface::BasePage` includes `Concerns::DataFieldAccess`, so all concrete page models have:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
page.field_collection # → DataField::Collection (memoized)
|
|
65
|
+
page.components # → same (v2 compat alias)
|
|
66
|
+
page.data_fields_updates # → [DataFieldInput, ...] for dirty fields
|
|
67
|
+
page.data_fields_additions # → [DataFieldInput, ...] for added fields
|
|
68
|
+
page.data_fields_deletions # → [field_id, ...] queued for deletion
|
|
69
|
+
page.data_fields_dirty? # → Boolean
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`Base::Page::Phased::Stage` also exposes `components` (via `sections.components`) for
|
|
73
|
+
stage-level field access.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## DataField::Collection API
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
coll = page.components
|
|
81
|
+
|
|
82
|
+
# Read
|
|
83
|
+
coll.get_by_type(:plain_text) # → [DataField::PlainText, ...]
|
|
84
|
+
coll.get_by_type('PlainText') # → same (string also works)
|
|
85
|
+
coll.get_by_name('Summary') # → DataField::PlainText (case-insensitive)
|
|
86
|
+
|
|
87
|
+
# Iterate
|
|
88
|
+
coll.each { |f| puts f.label }
|
|
89
|
+
coll.map(&:label)
|
|
90
|
+
|
|
91
|
+
coll.doc # → [raw_hash, ...] (v2 compat)
|
|
92
|
+
coll.empty? # → Boolean
|
|
93
|
+
coll.length # → Integer
|
|
94
|
+
|
|
95
|
+
# Mutations — updates (existing fields)
|
|
96
|
+
field = coll.get_by_name('Summary')
|
|
97
|
+
field.value = 'New text'
|
|
98
|
+
coll.dirty_inputs # → [{ plainText: { id: 'f1', value: 'New text' } }]
|
|
99
|
+
|
|
100
|
+
# Mutations — additions (new field instances from buildFromTemplate)
|
|
101
|
+
coll.add(id: 'server_id', label: 'New Field', type: 'PlainText') { |f| f.value = 'X' }
|
|
102
|
+
coll.dirty_additions # → [{ plainText: { id: 'server_id', value: 'X' } }]
|
|
103
|
+
|
|
104
|
+
# Mutations — deletions (remove field instances)
|
|
105
|
+
coll.mark_for_deletion('f1') # by ID string
|
|
106
|
+
coll.mark_for_deletion(field_object) # by field object (uses field.id)
|
|
107
|
+
coll.dirty_deletions # → ['f1']
|
|
108
|
+
|
|
109
|
+
coll.dirty? # → true if any of the above have changes
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Setter API per Type
|
|
115
|
+
|
|
116
|
+
### PlainText
|
|
117
|
+
```ruby
|
|
118
|
+
field.value = 'text string'
|
|
119
|
+
field.as_input # → { plainText: { id:, value: } }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### RichText
|
|
123
|
+
```ruby
|
|
124
|
+
field.content = '<p>HTML</p>'
|
|
125
|
+
field.value = '<p>HTML</p>' # alias
|
|
126
|
+
field.as_input # → { richText: { id:, content: } }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Date
|
|
130
|
+
```ruby
|
|
131
|
+
field.value = '2025-06-30T09:00:00+12:00' # ISO8601
|
|
132
|
+
field.as_input # → { date: { id:, value: } }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Number / Gauge
|
|
136
|
+
```ruby
|
|
137
|
+
field.value = 42.5
|
|
138
|
+
field.as_input # → { number: { id:, value: } } or { gauge: { id:, value: } }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Select
|
|
142
|
+
```ruby
|
|
143
|
+
field.select_option('Active') # select by name (deselects all others)
|
|
144
|
+
field.select_option('o1') # select by option id
|
|
145
|
+
field.select_option('opt1', 'opt2') # multi-select by id
|
|
146
|
+
field.clear_selection
|
|
147
|
+
field.selected_options # → [{ 'id' =>..., 'name' =>..., 'selected' => true }]
|
|
148
|
+
field.as_input # → { select: { id:, options: [{ id:, selected: }] } }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Checklist
|
|
152
|
+
```ruby
|
|
153
|
+
field.check('Step A') # check by label
|
|
154
|
+
field.check('i1', checked: false) # uncheck by id
|
|
155
|
+
field.as_input # → { checklist: { id:, items: [{ id:, checked: }] } }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### TagField (LocationField)
|
|
159
|
+
```ruby
|
|
160
|
+
field.location_ids = ['loc1', 'loc2']
|
|
161
|
+
field.as_input # → { locationField: { id:, locationIds: [...] } }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### People
|
|
165
|
+
```ruby
|
|
166
|
+
field.people_ids = ['person1', 'person2']
|
|
167
|
+
field.value = ['person1'] # alias
|
|
168
|
+
field.as_input # → { people: { id:, peopleIds: [...] } }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Geo
|
|
172
|
+
```ruby
|
|
173
|
+
field.address = '123 Main St, Auckland'
|
|
174
|
+
field.coordinates = { 'lat' => -36.86, 'lon' => 174.76 }
|
|
175
|
+
field.as_input # → { geo: { id:, address:, coordinates: } }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### ContractorEntities
|
|
179
|
+
```ruby
|
|
180
|
+
field.contractor_entity_ids = ['ce1', 'ce2']
|
|
181
|
+
field.value = ['ce1'] # alias
|
|
182
|
+
field.as_input # → { contractorEntities: { id:, contractorEntityIds: [...] } }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### CrossReference
|
|
186
|
+
```ruby
|
|
187
|
+
field.page_ids = ['page1', 'page2']
|
|
188
|
+
field.as_input # → { crossReference: { id:, referenceIds: [...] } }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### File / ImageGallery
|
|
192
|
+
```ruby
|
|
193
|
+
# Requires prior file upload — see TODO "File upload"
|
|
194
|
+
field.file_container_ids = ['fc1', 'fc2']
|
|
195
|
+
field.as_input # → { file: { id:, fileContainerIds: [...] } }
|
|
196
|
+
# or { imageGallery: { id:, fileContainerIds: [...] } }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## DataFieldOneToManyInput — Wiring to Mutations
|
|
202
|
+
|
|
203
|
+
`Input::Page::Update.from_model(page)` automatically collects field changes:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
input = Input::Page::Update.from_model(page)
|
|
207
|
+
# input[:dataFields] may contain:
|
|
208
|
+
# {
|
|
209
|
+
# updates: [{ plainText: { id:, value: } }, ...], # dirty existing fields
|
|
210
|
+
# additions: [{ plainText: { id:, value: } }, ...], # newly added fields
|
|
211
|
+
# deletions: ['field_id_1', 'field_id_2'] # fields queued for deletion
|
|
212
|
+
# }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`build_data_fields` only includes non-empty keys — if only `updates` exist, only
|
|
216
|
+
`updates` is present in the hash.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Stage-Level Field Access
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
page = graphql.pages.get(page_id)
|
|
224
|
+
stage = page.stages['Risk Assessment']
|
|
225
|
+
|
|
226
|
+
# All fields in the stage (flat, across sections)
|
|
227
|
+
stage.components.get_by_name('Hazard Description').value = 'Exposure to...'
|
|
228
|
+
|
|
229
|
+
# Section-by-section
|
|
230
|
+
stage.sections.each do |section|
|
|
231
|
+
section.components.each { |f| puts "#{f.label}: #{f.value}" }
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Spec Coverage
|
|
238
|
+
|
|
239
|
+
| File | Coverage |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `spec/ecoportal/api/graphql/base/page/data_field_spec.rb` | All 20 type dispatch, dirty tracking, PlainText, RichText, Select, People, Checklist, Collection (get_by_type/name, dirty_inputs, add/additions, deletions) |
|
|
242
|
+
| `spec/ecoportal/api/graphql/input/page/update_spec.rb` | from_model with dataFields.updates, additions, deletions |
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Code Spec: Dependency Map
|
|
2
|
+
|
|
3
|
+
**Scope:** All upstream and downstream gem dependencies for `ecoportal-api-graphql` — remote URLs, fork relationships, collaboration status, and access notes. Local paths are developer-specific; see the section below on local setup.
|
|
4
|
+
**Last updated:** 2026-06-02
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## This Repo
|
|
9
|
+
|
|
10
|
+
| Property | Value |
|
|
11
|
+
|----------|-------|
|
|
12
|
+
| Gem name | `ecoportal-api-graphql` |
|
|
13
|
+
| Remote (origin) | https://gitlab.ecoportal.co.nz/oscar/ecoportal-api-graphql.git |
|
|
14
|
+
| Relationship | Target repo — all work happens here |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Upstream Dependencies
|
|
19
|
+
|
|
20
|
+
### ecoportal-api
|
|
21
|
+
|
|
22
|
+
| Property | Value |
|
|
23
|
+
|----------|-------|
|
|
24
|
+
| Gem name | `ecoportal-api` |
|
|
25
|
+
| Version constraint | `~> 0.10, >= 0.10.15` |
|
|
26
|
+
| Remote | https://gitlab.ecoportal.co.nz/ecoportal/ecoportal-api.git |
|
|
27
|
+
| Collaboration | **Team-managed** — we own this gem |
|
|
28
|
+
| Key deps | `dotenv`, `elastic-apm`, `http`, `rate_throttle_client` |
|
|
29
|
+
| Notes | Provides REST API base, authentication, org context, and the `Ecoportal::API::Common` namespace that this gem extends. |
|
|
30
|
+
|
|
31
|
+
### ecoportal-api-v2
|
|
32
|
+
|
|
33
|
+
| Property | Value |
|
|
34
|
+
|----------|-------|
|
|
35
|
+
| Gem name | `ecoportal-api-v2` |
|
|
36
|
+
| Version constraint | `~> 3.3, >= 3.3.1` |
|
|
37
|
+
| Remote | https://gitlab.ecoportal.co.nz/oscar/ecoportal-api-v2.git |
|
|
38
|
+
| Collaboration | **Team-managed** — we own this gem |
|
|
39
|
+
| Key deps | `ecoportal-api`, `mime-types` |
|
|
40
|
+
| Notes | REST API v2 layer. Required by the entry point (`api-graphql.rb`). Depends on `ecoportal-api` itself. |
|
|
41
|
+
|
|
42
|
+
### graphlient
|
|
43
|
+
|
|
44
|
+
| Property | Value |
|
|
45
|
+
|----------|-------|
|
|
46
|
+
| Gem name | `graphlient` |
|
|
47
|
+
| Version constraint | `>= 0.8.0, < 0.9` |
|
|
48
|
+
| Remote (our fork) | https://github.com/rellampec/graphlient.git |
|
|
49
|
+
| Remote (upstream) | https://github.com/ashkan18/graphlient.git |
|
|
50
|
+
| Collaboration | **Fork (active)** — we drive new features; upstream is `ashkan18/graphlient` |
|
|
51
|
+
| Git workflow | **Feature branches off our fork's working branch** (`feature/directives-dsl-support`). We open PRs upstream when appropriate but are not required to. Do NOT commit directly to `master`. |
|
|
52
|
+
| Key deps | `faraday ~> 2.0`, `graphql-client` |
|
|
53
|
+
| Notes | Friendly wrapper around `graphql-client`. `Common::GraphQL::Client` inherits from `Graphlient::Client`. |
|
|
54
|
+
|
|
55
|
+
### graphql-client
|
|
56
|
+
|
|
57
|
+
| Property | Value |
|
|
58
|
+
|----------|-------|
|
|
59
|
+
| Gem name | `graphql-client` |
|
|
60
|
+
| Version constraint | (via graphlient) |
|
|
61
|
+
| Remote (our fork) | https://github.com/rellampec/graphql-client.git |
|
|
62
|
+
| Remote (upstream) | https://github.com/github-community-projects/graphql-client.git |
|
|
63
|
+
| Collaboration | **Fork (upstream-contribution only)** — we do NOT maintain this gem; upstream is `github-community-projects/graphql-client` |
|
|
64
|
+
| Git workflow | **ALWAYS use a feature branch** (e.g. `feature/allow-named-fragment-access`). Changes are intended as upstream PRs to `github-community-projects/graphql-client`. NEVER commit to `master`. If unsure about the branch name or PR strategy, ask the developer before committing. |
|
|
65
|
+
| Key deps | `activesupport >= 3.0`, `graphql >= 1.13.0` |
|
|
66
|
+
| Notes | Low-level GraphQL HTTP + parsing library. Pulled in transitively via `graphlient` — not a direct dependency. Check `Common::GraphQL::Patches` for any monkey-patches applied on top. |
|
|
67
|
+
|
|
68
|
+
### graphql-ruby
|
|
69
|
+
|
|
70
|
+
| Property | Value |
|
|
71
|
+
|----------|-------|
|
|
72
|
+
| Gem name | `graphql` |
|
|
73
|
+
| Version constraint | `>= 1.13.0` (transitive, via graphql-client) |
|
|
74
|
+
| Remote | https://github.com/rmosolgo/graphql-ruby |
|
|
75
|
+
| Collaboration | **No collaboration** — external, read-only reference |
|
|
76
|
+
| Notes | Schema definition and execution engine. Used server-side by EcoPortal; client-side usage here is indirect. API reference: https://graphql-ruby.org |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Downstream Dependencies
|
|
81
|
+
|
|
82
|
+
### eco-helpers
|
|
83
|
+
|
|
84
|
+
| Property | Value |
|
|
85
|
+
|----------|-------|
|
|
86
|
+
| Gem name | `eco-helpers` |
|
|
87
|
+
| Requires this gem as | `ecoportal-api-graphql ~> 1.3, >= 1.3.4` |
|
|
88
|
+
| Remote | https://gitlab.ecoportal.co.nz/oscar/script_api_helpers.git |
|
|
89
|
+
| Collaboration | **Team-managed** — we own this gem |
|
|
90
|
+
| Notes | Primary consumer of this gem. **Backwards compatibility with `eco-helpers` must be preserved.** When a change could break it, cross-reference its usage before committing. |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Dependency Chain Summary
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
graphql-ruby (rmosolgo — external, read-only)
|
|
98
|
+
↓
|
|
99
|
+
graphql-client (our fork ← github-community-projects/graphql-client)
|
|
100
|
+
↓
|
|
101
|
+
graphlient (our fork ← ashkan18/graphlient)
|
|
102
|
+
↓
|
|
103
|
+
ecoportal-api (team-managed — gitlab.ecoportal.co.nz)
|
|
104
|
+
ecoportal-api-v2 (team-managed — gitlab.ecoportal.co.nz)
|
|
105
|
+
↓
|
|
106
|
+
ecoportal-api-graphql ← THIS REPO
|
|
107
|
+
↓
|
|
108
|
+
eco-helpers (team-managed — gitlab.ecoportal.co.nz)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Local Development Setup
|
|
114
|
+
|
|
115
|
+
Local paths are **developer-specific** — do not hardcode them in shared docs.
|
|
116
|
+
|
|
117
|
+
Each developer who works across multiple gems should clone the relevant repos and record their local paths in `.claude/local_paths.md` (git-ignored). See `.claude/local_paths.example.md` for the format.
|
|
118
|
+
|
|
119
|
+
### Using local gem overrides with Bundler
|
|
120
|
+
|
|
121
|
+
To point Bundler at a local clone instead of the published gem:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bundle config local.ecoportal-api /your/path/to/ecoportal-api
|
|
125
|
+
bundle config local.ecoportal-api-v2 /your/path/to/ecoportal-api-v2
|
|
126
|
+
bundle config local.graphlient /your/path/to/graphlient
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Or add a `path:` entry in `Gemfile` during development (remove before committing):
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
gem 'graphlient', path: '/your/path/to/graphlient'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### For AI agents working with local clones
|
|
136
|
+
|
|
137
|
+
1. Check `.ai-assistance/local_paths.md` for this developer's local paths.
|
|
138
|
+
2. If the file doesn't exist, the repos are not locally available — use the remote URLs for reference only (read via web fetch or ask the developer to clone).
|
|
139
|
+
3. Never assume a specific local path exists — always check first.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Upstream Change Decision Guide
|
|
144
|
+
|
|
145
|
+
If a task requires changes in an upstream gem, the options are (confirm with developer before proceeding):
|
|
146
|
+
|
|
147
|
+
1. **Local monkey-patch** — apply the fix from within `ecoportal-api-graphql` (see `Common::GraphQL::Patches`). Fast, contained, but temporary debt.
|
|
148
|
+
2. **Fork change** — commit to our fork (for `graphlient` or `graphql-client`). Appropriate for lasting fixes we own.
|
|
149
|
+
3. **New version** — for `ecoportal-api` or `ecoportal-api-v2` (team-owned), cut a new version. For external forks, optionally contribute back upstream.
|
|
150
|
+
|
|
151
|
+
Document the decision in the active project's `DECISIONS.md`.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Code Spec: `as_update` / `as_input` Pipeline
|
|
2
|
+
|
|
3
|
+
**Scope:** `Common::GraphQL::Model::Diffable`, `Common::GraphQL::Model::AsInput`,
|
|
4
|
+
`DiffService`, `ClassicDiffService`, `HashDiffNesting`
|
|
5
|
+
**Last updated:** 2026-06-05
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What This Pipeline Does
|
|
10
|
+
|
|
11
|
+
Models that include `AsInput` (via `Model < DoubleModel`) can:
|
|
12
|
+
1. **Diff themselves** against their original document via `#as_update` → returns a Hash
|
|
13
|
+
of only the changed properties.
|
|
14
|
+
2. **Convert that diff** to mutation input via `#as_input` → returns a Hash ready to be
|
|
15
|
+
passed as the `input:` variable to a GraphQL mutation.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Class Hierarchy
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Common::GraphQL::Model < Common::Content::DoubleModel
|
|
23
|
+
includes Diffable → provides #as_update, #dirty?
|
|
24
|
+
includes AsInput → provides #as_input, .as_input(hash)
|
|
25
|
+
|
|
26
|
+
Diffable::DiffService < ClassicDiffService
|
|
27
|
+
includes HashDiffNesting → change_diff, patch_update, patch_new, patch_delete, ...
|
|
28
|
+
includes HashHelpers
|
|
29
|
+
|
|
30
|
+
Diffable::ClassicDiffService
|
|
31
|
+
includes HashDiffNesting
|
|
32
|
+
provides #diff, #classic_diff, #curr_doc, #prev_doc
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Current Output Format of `DiffService#diff` — KNOWN ISSUE
|
|
38
|
+
|
|
39
|
+
**`#as_update` calls `DiffService#diff` which calls `classic_diff(flat: true)`, which calls
|
|
40
|
+
`change_diff(curr_doc, prev_doc)`.**
|
|
41
|
+
|
|
42
|
+
`change_diff` routes through `patch_update` for any Hash that has an `:id` key, producing:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
{
|
|
46
|
+
id: "abc123",
|
|
47
|
+
api_operation: :update,
|
|
48
|
+
change_data: { name: "New Name", status: "active" }
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**This is WRONG for GraphQL mutations.** The GraphQL mutation input should be flat:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
{ id: "abc123", name: "New Name", status: "active" }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Why it's wrong:** GraphQL mutation inputs (`UpdateActionInput`, `UpdatePageInput`, etc.)
|
|
59
|
+
do not have `api_operation` or `change_data` fields — they expect changed properties directly
|
|
60
|
+
at the top level alongside `id`.
|
|
61
|
+
|
|
62
|
+
**Root cause:** `HashDiffNesting` was ported from `ecoportal-api-v2`'s `HashDiffPatch`.
|
|
63
|
+
The v2 gem uses JSON Patch semantics (`api_operation`, `change_data`). GraphQL mutations
|
|
64
|
+
do not — they use direct property assignment.
|
|
65
|
+
|
|
66
|
+
**Decision (2026-06-04):** GraphQL `as_update` must produce changed-props-only output with
|
|
67
|
+
no patch-operation wrapper. See `DECISIONS.md` ("as_update diff: changed props only").
|
|
68
|
+
|
|
69
|
+
**Fix required (TODO 4.2/4.3):** Either:
|
|
70
|
+
- Override `classic_diff` in `DiffService` to call `change_data(curr, prev)` directly
|
|
71
|
+
(returns just the changed fields, no wrapper), and re-add `id` separately; or
|
|
72
|
+
- Have `AsInput#as_input` detect and unwrap the patch-op structure before building input.
|
|
73
|
+
The preferred approach per the DECISIONS is to fix `DiffService` itself.
|
|
74
|
+
|
|
75
|
+
### Nested objects
|
|
76
|
+
|
|
77
|
+
`diff_reduce` cascades through nested model objects (those that respond to `#as_update`)
|
|
78
|
+
and replaces the nested path in the result with each nested model's own `as_update` output.
|
|
79
|
+
These nested diffs also currently produce `api_operation`/`change_data` wrappers if the
|
|
80
|
+
nested object has an `id`.
|
|
81
|
+
|
|
82
|
+
For GraphQL, nested arrays of objects with IDs (e.g. `locations`, `associatedPeople`)
|
|
83
|
+
are expressed differently per mutation — sometimes as `[ID]` arrays, sometimes as
|
|
84
|
+
`IdDiffInput` (add/remove deltas), sometimes as full nested input objects. The `InputClass`
|
|
85
|
+
(per the `from_model` pattern, TODO 4.3) is responsible for reshaping nested diffs into
|
|
86
|
+
the correct format for each mutation.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## `AsInput` Current Behavior
|
|
91
|
+
|
|
92
|
+
### Instance method `#as_input(clientMutationId: '')`
|
|
93
|
+
|
|
94
|
+
1. Calls `as_update` → gets the diff hash (currently with patch-op wrapper, see above).
|
|
95
|
+
2. Calls `self.class.as_input(diff, clientMutationId: ...)`.
|
|
96
|
+
|
|
97
|
+
### Class method `.as_input(hash, clientMutationId: '')`
|
|
98
|
+
|
|
99
|
+
1. Deep-converts hash keys to symbols.
|
|
100
|
+
2. Merges `clientMutationId:`.
|
|
101
|
+
3. Calls `remove_nil_keys_deep(hash, target: :id)` — removes nil-valued `:id` keys
|
|
102
|
+
(keeps `id:` only when it has a value; removes nil ids from nested objects so they
|
|
103
|
+
don't confuse the server).
|
|
104
|
+
|
|
105
|
+
**Current output (wrong):**
|
|
106
|
+
```ruby
|
|
107
|
+
{
|
|
108
|
+
id: "abc123",
|
|
109
|
+
api_operation: :update,
|
|
110
|
+
change_data: { name: "New Name" },
|
|
111
|
+
clientMutationId: ""
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Desired output (target):**
|
|
116
|
+
```ruby
|
|
117
|
+
{ id: "abc123", name: "New Name", clientMutationId: "" }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## `patch_ver` — Concurrency Control
|
|
123
|
+
|
|
124
|
+
**Schema:** `patchVer: Int!` (non-null) on `BasePageInterface`. Present on all Page types.
|
|
125
|
+
`BasePage` in the gem maps it as `passthrough :patchVer`.
|
|
126
|
+
|
|
127
|
+
**How it works:**
|
|
128
|
+
- Every Page response includes `patchVer` (an incrementing Integer).
|
|
129
|
+
- On `UpdatePage` mutation, the caller may pass `patchVer:` (optional).
|
|
130
|
+
- The server checks that the submitted `patchVer` matches the current value; if not,
|
|
131
|
+
the update is rejected (optimistic locking / last-write-wins prevention).
|
|
132
|
+
|
|
133
|
+
**Current gap (TODO 4.2):** `AsInput#as_input` does not inject `patchVer` into the
|
|
134
|
+
output. The subject model (`BasePage` subclass) holds `patchVer` via `passthrough :patchVer`
|
|
135
|
+
but it is never extracted and included in the mutation input hash.
|
|
136
|
+
|
|
137
|
+
**Fix required:** In `AsInput#as_input` (instance method), read `self.patchVer` if the
|
|
138
|
+
model responds to it, and include it in the input hash at the top level:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
def as_input(clientMutationId: '')
|
|
142
|
+
diff = as_update # TODO: once fixed, returns flat changed props
|
|
143
|
+
diff[:patchVer] = patchVer if respond_to?(:patchVer) && !patchVer.nil?
|
|
144
|
+
self.class.as_input(diff, clientMutationId: clientMutationId)
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## `from_model` Pattern (TODO 4.3)
|
|
151
|
+
|
|
152
|
+
**Decision (2026-06-04):** Conversion from model diff to mutation-specific input shape is
|
|
153
|
+
owned by the `Input` class, not the model. Each mutation's input class implements
|
|
154
|
+
`InputClass.from_model(model)`.
|
|
155
|
+
|
|
156
|
+
**Why:** The model (`BasePage`, `Base::Action`, etc.) should not know which mutation it
|
|
157
|
+
feeds. The input class knows its own schema shape (field names, nested structure, ID vs
|
|
158
|
+
IdDiff arrays, etc.).
|
|
159
|
+
|
|
160
|
+
**Pattern:**
|
|
161
|
+
```ruby
|
|
162
|
+
class Input::Action::Update < Logic::Input
|
|
163
|
+
def self.from_model(action_model)
|
|
164
|
+
diff = action_model.as_update # after fixing: flat {name:, status:, ...}
|
|
165
|
+
new(
|
|
166
|
+
id: action_model.id,
|
|
167
|
+
patchVer: action_model.patchVer, # if applicable
|
|
168
|
+
**map_fields(diff)
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private_class_method def self.map_fields(diff)
|
|
173
|
+
# rename, reshape, filter fields as needed for this specific mutation input
|
|
174
|
+
diff.slice(:name, :description, :status, :dueDate, :assignedPersonMemberIds, ...)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**The `as_input` instance method becomes a convenience delegator:**
|
|
180
|
+
```ruby
|
|
181
|
+
def as_input(target_class: default_input_class, **kargs)
|
|
182
|
+
target_class.from_model(self, **kargs)
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## `root?` Objects in the Diff
|
|
189
|
+
|
|
190
|
+
Models marked `root!` (via `Ecoportal::API::Common::GraphQL::ClassHelpers`) are excluded
|
|
191
|
+
from cascaded diffs. These are typically "lookup" objects that are referenced but not
|
|
192
|
+
owned — e.g. `Base::Organization` (root!), `Base::Action` (root!). Their nested presence
|
|
193
|
+
in a model's JSON will not generate diff entries.
|
|
194
|
+
|
|
195
|
+
This is correct behaviour: you don't diff a referenced Organization when updating a Page;
|
|
196
|
+
you only diff properties the Page itself owns.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Key Files
|
|
201
|
+
|
|
202
|
+
| File | Role |
|
|
203
|
+
|------|------|
|
|
204
|
+
| `lib/.../model.rb` | `Model < DoubleModel`, includes `Diffable` and `AsInput` |
|
|
205
|
+
| `lib/.../model/diffable.rb` | `#as_update`, `#dirty?`, `DIFF_CLASS` |
|
|
206
|
+
| `lib/.../model/diffable/diff_service.rb` | Main diff logic, `#diff`, `#diff_reduce` |
|
|
207
|
+
| `lib/.../model/diffable/classic_diff_service.rb` | Base: `#curr_doc`, `#prev_doc`, `change_diff` dispatch |
|
|
208
|
+
| `lib/.../model/diffable/hash_diff_nesting.rb` | `change_diff`, `patch_update/new/delete`, array diffing |
|
|
209
|
+
| `lib/.../model/as_input.rb` | `#as_input`, `.as_input(hash)` class method |
|
|
210
|
+
| `lib/.../interface/base_page.rb` | `passthrough :patchVer` — Page models carry this |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Backend Reference (ecoPortal_master)
|
|
215
|
+
|
|
216
|
+
- `app/graphql/types/pages/interfaces/base_page_interface.rb` — `patchVer: Int!` (non-null)
|
|
217
|
+
- `app/graphql/mutations/pages/update.rb` — `argument :patch_ver, Integer, required: false`
|
|
218
|
+
- `app/graphql/types/fill_in_page/inputs/page_input.rb` — Page update input fields:
|
|
219
|
+
`locations`, `other_tags`, `time_zone`, `state`, `task_priority`, `name`, `external_id`
|
|
220
|
+
- `app/graphql/types/fill_in_page/inputs/data_field_input.rb` — DataField inputs (complex)
|
|
221
|
+
|
|
222
|
+
**UpdatePage mutation shape:**
|
|
223
|
+
```graphql
|
|
224
|
+
mutation UpdatePage($id: ID!, $patch_ver: Int, $page: PageInput, $data_fields: [DataFieldInput], ...) {
|
|
225
|
+
updatePage(id: $id, patchVer: $patch_ver, page: $page, dataFields: $data_fields, ...) {
|
|
226
|
+
item { ...BasePageInterface }
|
|
227
|
+
errors { ... }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Note: `id`, `patchVer`, and `page` (the input object) are separate top-level arguments —
|
|
233
|
+
not nested under a single `input:` object like Action mutations. This is a structural
|
|
234
|
+
difference that the `from_model` pattern must account for per mutation type.
|