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,868 @@
|
|
|
1
|
+
# Search Filters — Code Spec
|
|
2
|
+
|
|
3
|
+
*Extracted from Insomnia_2026-06-07.yaml — real production queries.*
|
|
4
|
+
*Cross-referenced with Confluence APIDOCS partial filter reference.*
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## GOTCHA — register search matches on the field's GraphQL DEFINITION key (VERIFIED, 2026-07-02)
|
|
9
|
+
|
|
10
|
+
A register/field filter matches on the field's **definition key** (e.g. `plain_text.zc366f454`),
|
|
11
|
+
NOT on the field `name:`. Under APIv2 a filter could carry `{ name:, key: }` and v2 resolved the
|
|
12
|
+
field **loosely by name**, so a wrong/stale `key:` still worked. **On GraphQL the literal `key:` is
|
|
13
|
+
used** — a wrong key silently matches nothing (0 results), which for an upsert means "not found →
|
|
14
|
+
create" → **duplicate pages every run**. This bit the jamestrong cans-upsert live (2026-07-01): the
|
|
15
|
+
case hardcoded `plain_text.z5573fdc2` while the real `Identifier:` key is `plain_text.zc366f454`,
|
|
16
|
+
so the existence search never matched and dups accumulated. Always take the field key from a known-
|
|
17
|
+
good source (a working register filter URL / introspection), not an APIv2-era hardcode.
|
|
18
|
+
|
|
19
|
+
**Exact vs partial (RESOLVED 2026-07-02):** the right op for an exact-id lookup on a plain_text
|
|
20
|
+
field is **`is_filter`** (whole-value equality on the `.exact` keyword subfield) — NOT `match_filter`
|
|
21
|
+
(analyzed/partial, which missed the exact SKU → 0 results) and NOT `exact_filter` (which does not
|
|
22
|
+
exist for plain_text register fields). `contains_filter` is substring (what the UI exposes). The gem
|
|
23
|
+
now translates a v2-style `type: 'exact_filter'` (or `'is_filter'`) → GraphQL `is_filter` with the
|
|
24
|
+
`membranes.<type>` path derived from the key (`Compat::FilterTranslator#translate_exact_filter`).
|
|
25
|
+
CAVEAT: `is_filter` on plain_text is **case-insensitive** and matches only the **first 64 chars**
|
|
26
|
+
(values lowercased + truncated at index). No case-sensitive / >64-char exact op exists. Prefer keying
|
|
27
|
+
integration upserts on `externalId` (server-enforced unique) over data-field search where possible —
|
|
28
|
+
see `ecoPortal_architecture/02_data_model.md`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Two Query Modalities
|
|
33
|
+
|
|
34
|
+
### 1. Org search — `currentOrganization { pages(...) }`
|
|
35
|
+
|
|
36
|
+
```graphql
|
|
37
|
+
query OrgSearchPages(
|
|
38
|
+
$searchConf: Search,
|
|
39
|
+
$templatesOnly: Boolean,
|
|
40
|
+
$showHiddenData: Boolean,
|
|
41
|
+
$first: Int,
|
|
42
|
+
$last: Int,
|
|
43
|
+
$after: String,
|
|
44
|
+
$before: String
|
|
45
|
+
) {
|
|
46
|
+
currentOrganization {
|
|
47
|
+
pages(
|
|
48
|
+
searchConf: $searchConf,
|
|
49
|
+
templatesOnly: $templatesOnly,
|
|
50
|
+
showHiddenData: $showHiddenData,
|
|
51
|
+
first: $first,
|
|
52
|
+
last: $last,
|
|
53
|
+
after: $after,
|
|
54
|
+
before: $before
|
|
55
|
+
) {
|
|
56
|
+
totalCount
|
|
57
|
+
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
|
|
58
|
+
nodes { ...PageUnion }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- **Source:** PostgreSQL DB, rendered by API layer
|
|
65
|
+
- **Returns:** `PageUnion` (`BasicPage | PhasedPage`) — full data including `patchVer`,
|
|
66
|
+
field IDs, all typed data field models
|
|
67
|
+
- **Performance:** ~3s/page on render (slow for large result sets)
|
|
68
|
+
- **Archived:** controlled via `searchConf` filters (e.g. `state_filter` or no filter)
|
|
69
|
+
- **Extra args:** `templatesOnly: Boolean`, `showHiddenData: Boolean`
|
|
70
|
+
- **Best for:** Pre-update fetch, pre-create fetch, finding pages when field IDs are needed
|
|
71
|
+
|
|
72
|
+
### 2. Register search — `currentOrganization { register(id:) { previewPages(...) } }`
|
|
73
|
+
|
|
74
|
+
```graphql
|
|
75
|
+
query SearchRegisterPages(
|
|
76
|
+
$registerId: ID!,
|
|
77
|
+
$presetId: ID,
|
|
78
|
+
$after: String,
|
|
79
|
+
$search: Search,
|
|
80
|
+
$includeArchived: Boolean = false
|
|
81
|
+
) {
|
|
82
|
+
currentOrganization {
|
|
83
|
+
register(id: $registerId) {
|
|
84
|
+
previewPages(
|
|
85
|
+
presetViewId: $presetId,
|
|
86
|
+
after: $after,
|
|
87
|
+
searchConf: $search,
|
|
88
|
+
includeArchived: $includeArchived
|
|
89
|
+
) {
|
|
90
|
+
totalCount
|
|
91
|
+
pageInfo { endCursor hasNextPage }
|
|
92
|
+
nodes { ...PreviewPage }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- **Source:** Elasticsearch index (fast — sub-second)
|
|
100
|
+
- **Returns:** `PreviewPage` — field preview values only. **No field IDs, no `patchVer`,
|
|
101
|
+
no typed field models.** Cannot be used to build mutation inputs.
|
|
102
|
+
- **Archived:** `includeArchived: Boolean` parameter — defaults to `false`, can be `true`
|
|
103
|
+
- **Extra args:** `presetViewId: ID` — applies a register PageView preset configuration
|
|
104
|
+
- **Field name:** `previewPages`, not `pages`
|
|
105
|
+
- **Best for:** Fast lookup of page IDs by metadata before switching to org search
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## SearchConf Structure
|
|
110
|
+
|
|
111
|
+
The `searchConf` / `$search` argument type is `Search` — a Hash, not a typed GraphQL
|
|
112
|
+
input model. The top-level structure is:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"filters": [ <filter_object>, ... ],
|
|
117
|
+
"sorters": { "key": "field_name", "direction": "asc" | "desc" },
|
|
118
|
+
"query": "optional full-text search string"
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Note on `filters`:** Can be an array of filter objects (standard) or a single filter
|
|
123
|
+
object (observed in some older contractor queries). Prefer array form.
|
|
124
|
+
|
|
125
|
+
**Note on `sorters`:** Can be a single sorter object or an array. Key field is `key`
|
|
126
|
+
(not `fieldName`).
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Filter Operations
|
|
131
|
+
|
|
132
|
+
All operations are **snake_case strings** in the `operation` field.
|
|
133
|
+
|
|
134
|
+
### `exact_filter` — equality match
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"operation": "exact_filter",
|
|
139
|
+
"params": {
|
|
140
|
+
"key": "external_id",
|
|
141
|
+
"value": "1223"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Common keys: `external_id`, `state`, `creator_id`, `template_id`
|
|
147
|
+
|
|
148
|
+
### `date_filter` — date range
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"operation": "date_filter",
|
|
153
|
+
"params": {
|
|
154
|
+
"key": "updated_at",
|
|
155
|
+
"gte": "2025-01-01T00:00:00+00:00",
|
|
156
|
+
"time_zone": "UTC"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Params: `key` (field), `gte` (from), `lte` (to), `time_zone`. Date format: ISO8601.
|
|
162
|
+
Common keys: `updated_at`, `created_at`
|
|
163
|
+
|
|
164
|
+
**Human date mode** — instead of ISO8601 bounds, use a named time window via `mode`:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"operation": "date_filter",
|
|
169
|
+
"params": {
|
|
170
|
+
"key": "updated_at",
|
|
171
|
+
"mode": "last_month",
|
|
172
|
+
"time_zone": "Pacific/Auckland"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Supported modes (from `HumanDateFilterable` concern in backend):
|
|
178
|
+
|
|
179
|
+
| Category | Modes |
|
|
180
|
+
|---|---|
|
|
181
|
+
| Past relative | `last_year`, `last_quarter`, `last_month`, `last_week` |
|
|
182
|
+
| Past counted | `last_days` (+`days_count`), `last_hours` (+`hours_count`), `last_months` (+`months_count`) |
|
|
183
|
+
| Current period | `current_year`, `current_quarter`, `current_month`, `current_week`, `current_day` |
|
|
184
|
+
| Future relative | `next_year`, `next_quarter`, `next_month`, `next_week` |
|
|
185
|
+
| Future counted | `next_days` (+`days_count`), `next_hours` (+`hours_count`), `next_months` (+`months_count`) |
|
|
186
|
+
| Point-in-time | `exact` (+`days_count`), `less_than` (+`days_count`), `more_than` (+`days_count`), `between` (+`days_count`) |
|
|
187
|
+
| Financial year | `last_financial_year`, `current_financial_year`, `next_financial_year`, `custom_financial_year` (+`year`) |
|
|
188
|
+
| Open-ended | `past`, `future` |
|
|
189
|
+
|
|
190
|
+
Extra params used with some modes: `days_count`, `hours_count`, `months_count`, `year`, `time_zone` (default: `"Pacific/Auckland"`).
|
|
191
|
+
|
|
192
|
+
### `register_filter` — scope to one or more registers (org search only)
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"operation": "register_filter",
|
|
197
|
+
"params": {
|
|
198
|
+
"ids": ["registerId1", "registerId2"]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Use this in the org search to narrow results to a specific register without switching
|
|
204
|
+
to the register-scoped query. Equivalent to `currentOrganization.pages` filtered by register.
|
|
205
|
+
|
|
206
|
+
### `and_filter` — explicit AND group
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"operation": "and_filter",
|
|
211
|
+
"params": {
|
|
212
|
+
"filters": [
|
|
213
|
+
{ "operation": "exact_filter", "params": { "key": "external_id", "value": "X" } },
|
|
214
|
+
{ "operation": "date_filter", "params": { "key": "updated_at", "gte": "2025-01-01" } }
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Top-level `filters` array is implicitly AND. Use `and_filter` explicitly when nesting
|
|
221
|
+
inside an `or_filter`.
|
|
222
|
+
|
|
223
|
+
### `or_filter` — OR group
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"operation": "or_filter",
|
|
228
|
+
"params": {
|
|
229
|
+
"filters": [
|
|
230
|
+
{ "operation": "exact_filter", "params": { "key": "external_id", "value": "3333" } },
|
|
231
|
+
{ "operation": "exact_filter", "params": { "key": "external_id", "value": "TMS00323" } }
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `one_of_filter` — match any value in a set
|
|
238
|
+
|
|
239
|
+
More concise than an `or_filter` of `exact_filter` calls when matching the same field
|
|
240
|
+
against multiple values.
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"operation": "one_of_filter",
|
|
245
|
+
"params": {
|
|
246
|
+
"key": "external_id",
|
|
247
|
+
"values": ["3333", "TMS00323", "ABC-001"]
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Ruby: `SearchConf.new.filter({ operation: 'one_of_filter', params: { key: 'external_id', values: [...] } })`
|
|
253
|
+
|
|
254
|
+
### `none_of_filter` — exclude all values in a set
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"operation": "none_of_filter",
|
|
259
|
+
"params": {
|
|
260
|
+
"key": "state",
|
|
261
|
+
"values": ["inactive", "complete"]
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `has_any_filter` — field has any non-null/non-empty value
|
|
267
|
+
|
|
268
|
+
Most common frontend filter. Checks that a field is populated.
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"operation": "has_any_filter",
|
|
273
|
+
"params": {
|
|
274
|
+
"key": "external_id"
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### `match_filter` — full-text / partial text match
|
|
280
|
+
|
|
281
|
+
For text fields where you want contains/partial matching rather than exact equality.
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"operation": "match_filter",
|
|
286
|
+
"params": {
|
|
287
|
+
"key": "name",
|
|
288
|
+
"value": "safety inspection"
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### `exists_filter` / `doesnt_exist_filter` — field presence check
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{ "operation": "exists_filter", "params": { "field": "external_id" } }
|
|
297
|
+
{ "operation": "doesnt_exist_filter", "params": { "field": "external_id" } }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Note: uses `field` (not `key`) in params.
|
|
301
|
+
|
|
302
|
+
### `boolean_filter` — boolean field match
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"operation": "boolean_filter",
|
|
307
|
+
"params": {
|
|
308
|
+
"key": "archived",
|
|
309
|
+
"value": true
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### `numeric_range_filter` — numeric range
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"operation": "numeric_range_filter",
|
|
319
|
+
"params": {
|
|
320
|
+
"key": "mould_counter",
|
|
321
|
+
"gte": 5,
|
|
322
|
+
"lte": 100
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### `isnt_exact_filter` — negated exact match
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"operation": "isnt_exact_filter",
|
|
332
|
+
"params": {
|
|
333
|
+
"key": "state",
|
|
334
|
+
"value": "complete"
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### `not_register_filter` — exclude registers
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"operation": "not_register_filter",
|
|
344
|
+
"params": {
|
|
345
|
+
"ids": ["registerId1"]
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Sorters
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{ "key": "created_at", "direction": "asc" }
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Or as an array:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
[{ "key": "created_at", "direction": "desc" }]
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Common keys: `created_at`, `updated_at`
|
|
365
|
+
Directions: `"asc"`, `"desc"`
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## fieldName Reference — Pages
|
|
370
|
+
|
|
371
|
+
*Extracted from backend filter class analysis (`app/services/new_ep/es/filters/pages/`)
|
|
372
|
+
and frontend `createFilter.ts`. All keys are **snake_case**.*
|
|
373
|
+
|
|
374
|
+
### Core metadata fields
|
|
375
|
+
|
|
376
|
+
| fieldName | Supported operations | Values / notes |
|
|
377
|
+
|---|---|---|
|
|
378
|
+
| `external_id` | `exact_filter`, `one_of_filter`, `has_any_filter`, `doesnt_exist_filter` | Unique per org |
|
|
379
|
+
| `state` | `exact_filter`, `one_of_filter`, `isnt_exact_filter` | `"active"`, `"inactive"`, `"complete"` |
|
|
380
|
+
| `name` | `match_filter`, `exact_filter` | Use `match_filter` for text/partial search |
|
|
381
|
+
| `creator_id` | `exact_filter`, `one_of_filter` | PersonMember ID string |
|
|
382
|
+
| `created_at` | `date_filter` | ISO8601 or human mode |
|
|
383
|
+
| `updated_at` | `date_filter` | ISO8601 or human mode |
|
|
384
|
+
| `template_id` | `exact_filter`, `one_of_filter` | Source template MongoDB ID |
|
|
385
|
+
| `id` | `exact_filter`, `one_of_filter` | Page MongoDB ID |
|
|
386
|
+
|
|
387
|
+
### Location fields
|
|
388
|
+
|
|
389
|
+
| fieldName | Supported operations | Notes |
|
|
390
|
+
|---|---|---|
|
|
391
|
+
| `location_id` | `exact_filter`, `one_of_filter` | Specific location node — **no** ancestor traversal |
|
|
392
|
+
| `all_location_ids` | `exact_filter`, `one_of_filter`, `has_any_filter` | Includes ancestor location IDs — broader match |
|
|
393
|
+
|
|
394
|
+
**Prefer `all_location_ids`** when you want "pages assigned to location X or any of its children".
|
|
395
|
+
Use `location_id` only when you need an exact location match with no ancestor roll-up.
|
|
396
|
+
|
|
397
|
+
### Tag / categorisation fields
|
|
398
|
+
|
|
399
|
+
| fieldName | Supported operations | Notes |
|
|
400
|
+
|---|---|---|
|
|
401
|
+
| `other_tags` | `exact_filter`, `one_of_filter`, `has_any_filter` | Free-form tag strings |
|
|
402
|
+
| `source` | `exact_filter` | Page source classification |
|
|
403
|
+
| `mould_counter` | `exact_filter`, `numeric_range_filter` | Sequence counter |
|
|
404
|
+
|
|
405
|
+
### Boolean / presence flags
|
|
406
|
+
|
|
407
|
+
| fieldName | Supported operations | Notes |
|
|
408
|
+
|---|---|---|
|
|
409
|
+
| `archived` | `boolean_filter`, `exact_filter` | `true` / `false` |
|
|
410
|
+
| `draft` | `boolean_filter`, `exact_filter` | Draft state |
|
|
411
|
+
|
|
412
|
+
### Sorter fieldNames (common)
|
|
413
|
+
|
|
414
|
+
| fieldName | Notes |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `created_at` | |
|
|
417
|
+
| `updated_at` | |
|
|
418
|
+
| `name` | Alphabetical |
|
|
419
|
+
| `mould_counter` | Sequence order |
|
|
420
|
+
|
|
421
|
+
### Operations not currently in `Input::SearchConf`
|
|
422
|
+
|
|
423
|
+
The backend supports these operations confirmed from production frontend code,
|
|
424
|
+
not yet wrapped as `SearchConf::*` value objects. Pass raw hashes via `SearchConf#filter`:
|
|
425
|
+
|
|
426
|
+
| Operation | Key param | Use case |
|
|
427
|
+
|---|---|---|
|
|
428
|
+
| `one_of_filter` | `key`, `values` | Match any of N values for one field |
|
|
429
|
+
| `none_of_filter` | `key`, `values` | Exclude N values |
|
|
430
|
+
| `has_any_filter` | `key` | Field has any non-empty value |
|
|
431
|
+
| `match_filter` | `key`, `value` | Partial/full-text match |
|
|
432
|
+
| `exists_filter` | `field` | Field is present in ES document |
|
|
433
|
+
| `doesnt_exist_filter` | `field` | Field is absent |
|
|
434
|
+
| `boolean_filter` | `key`, `value` | True/false field |
|
|
435
|
+
| `numeric_range_filter` | `key`, `gte`, `lte` | Numeric bounds |
|
|
436
|
+
| `isnt_exact_filter` | `key`, `value` | Negated exact match |
|
|
437
|
+
| `not_register_filter` | `ids` | Exclude pages from registers |
|
|
438
|
+
|
|
439
|
+
Raw hash usage:
|
|
440
|
+
```ruby
|
|
441
|
+
conf = SearchConf.new.filter(
|
|
442
|
+
{ 'operation' => 'one_of_filter', 'params' => { 'key' => 'state', 'values' => ['active', 'complete'] } }
|
|
443
|
+
)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Parametrization Patterns
|
|
449
|
+
|
|
450
|
+
`SearchConf#with(**substitutions)` returns a cloned `SearchConf` with placeholder
|
|
451
|
+
symbols replaced throughout all nested filters. Use this to build reusable filter
|
|
452
|
+
templates where only target values differ between calls.
|
|
453
|
+
|
|
454
|
+
### Template for find-by-externalId in any register
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
FIND_BY_EXT_ID = SearchConf.new
|
|
458
|
+
.filter(SearchConf::Register.new(:REGISTER_ID))
|
|
459
|
+
.filter(SearchConf::Exact.new(:external_id, :EXT_ID))
|
|
460
|
+
|
|
461
|
+
# Instantiate for a specific register + ID
|
|
462
|
+
query_conf = FIND_BY_EXT_ID.with(REGISTER_ID: 'reg_abc', EXT_ID: '1223')
|
|
463
|
+
result = org.pages(searchConf: query_conf.to_h, first: 1)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Template for date-range sweep with any field
|
|
467
|
+
|
|
468
|
+
```ruby
|
|
469
|
+
DATE_SWEEP = SearchConf.new
|
|
470
|
+
.filter(SearchConf::Register.new(:REGISTER_ID))
|
|
471
|
+
.filter(SearchConf::DateRange.new(:updated_at, gte: :FROM_DATE, lte: :TO_DATE))
|
|
472
|
+
.sort(:updated_at, :asc)
|
|
473
|
+
|
|
474
|
+
last_week = DATE_SWEEP.with(
|
|
475
|
+
REGISTER_ID: 'reg_abc',
|
|
476
|
+
FROM_DATE: '2025-06-01T00:00:00+12:00',
|
|
477
|
+
TO_DATE: '2025-06-07T23:59:59+12:00'
|
|
478
|
+
)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Template for multi-state search
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
ACTIVE_IN_REGISTER = SearchConf.new
|
|
485
|
+
.filter(SearchConf::Register.new(:REGISTER_ID))
|
|
486
|
+
.filter(SearchConf::Exact.new(:state, 'active'))
|
|
487
|
+
|
|
488
|
+
# Reuse across registers — only REGISTER_ID substituted
|
|
489
|
+
reg1_results = ACTIVE_IN_REGISTER.with(REGISTER_ID: 'reg_1')
|
|
490
|
+
reg2_results = ACTIVE_IN_REGISTER.with(REGISTER_ID: 'reg_2')
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Substitution rules
|
|
494
|
+
|
|
495
|
+
- Placeholder: any Ruby symbol passed as a parameter value (e.g. `:REGISTER_ID`)
|
|
496
|
+
- `with(**kv)` traverses all nested filters recursively and replaces matching symbol keys
|
|
497
|
+
- The original `SearchConf` is never mutated — `with` always returns a fresh clone
|
|
498
|
+
- Substitutions that don't match any placeholder are silently ignored
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## SearchConf Ruby API — Quick Reference
|
|
503
|
+
|
|
504
|
+
`Input::SearchConf` is the Ruby value object that builds `searchConf` hashes.
|
|
505
|
+
All classes live under `Ecoportal::API::GraphQL::Input::SearchConf`.
|
|
506
|
+
|
|
507
|
+
### Wrapped filter classes (produce correct `to_h` output)
|
|
508
|
+
|
|
509
|
+
| Ruby class | operation string | Constructor |
|
|
510
|
+
|---|---|---|
|
|
511
|
+
| `SearchConf::Exact` | `exact_filter` | `Exact.new(:key, value)` |
|
|
512
|
+
| `SearchConf::DateRange` | `date_filter` | `DateRange.new(:key, gte: iso8601, lte: iso8601, time_zone: 'UTC')` |
|
|
513
|
+
| `SearchConf::Register` | `register_filter` | `Register.new(register_id)` |
|
|
514
|
+
| `SearchConf::And` | `and_filter` | `And.new(filter1, filter2, ...)` |
|
|
515
|
+
| `SearchConf::Or` | `or_filter` | `Or.new(filter1, filter2, ...)` |
|
|
516
|
+
|
|
517
|
+
### Builder methods on `SearchConf` instance
|
|
518
|
+
|
|
519
|
+
```ruby
|
|
520
|
+
conf = SearchConf.new
|
|
521
|
+
.filter(SearchConf::Exact.new(:external_id, 'ABC')) # add one filter
|
|
522
|
+
.filter(SearchConf::Register.new('reg_id')) # chain multiple
|
|
523
|
+
.sort(:created_at, :desc) # add sorter
|
|
524
|
+
.sort(:name, :asc) # multiple sorters → array
|
|
525
|
+
.query('search text') # full-text query string
|
|
526
|
+
|
|
527
|
+
conf.to_h # → Hash for the searchConf argument
|
|
528
|
+
conf.as_json # → same (JSON serialisation)
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Common patterns
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
# Find by externalId in a register
|
|
535
|
+
SearchConf.new
|
|
536
|
+
.filter(SearchConf::Register.new('REG_ID'))
|
|
537
|
+
.filter(SearchConf::Exact.new(:external_id, 'ABC-123'))
|
|
538
|
+
|
|
539
|
+
# Find active pages updated after a date
|
|
540
|
+
SearchConf.new
|
|
541
|
+
.filter(SearchConf::Register.new('REG_ID'))
|
|
542
|
+
.filter(SearchConf::Exact.new(:state, 'active'))
|
|
543
|
+
.filter(SearchConf::DateRange.new(:updated_at, gte: '2025-01-01T00:00:00Z'))
|
|
544
|
+
.sort(:updated_at, :asc)
|
|
545
|
+
|
|
546
|
+
# Find pages with one of several externalIds (OR logic)
|
|
547
|
+
SearchConf.new
|
|
548
|
+
.filter(SearchConf::Or.new(
|
|
549
|
+
SearchConf::Exact.new(:external_id, 'ABC'),
|
|
550
|
+
SearchConf::Exact.new(:external_id, 'DEF')
|
|
551
|
+
))
|
|
552
|
+
|
|
553
|
+
# Explicit AND (nesting inside OR — top-level array is already AND)
|
|
554
|
+
SearchConf.new
|
|
555
|
+
.filter(SearchConf::Or.new(
|
|
556
|
+
SearchConf::And.new(
|
|
557
|
+
SearchConf::Exact.new(:state, 'active'),
|
|
558
|
+
SearchConf::Register.new('REG_1')
|
|
559
|
+
),
|
|
560
|
+
SearchConf::And.new(
|
|
561
|
+
SearchConf::Exact.new(:state, 'complete'),
|
|
562
|
+
SearchConf::Register.new('REG_2')
|
|
563
|
+
)
|
|
564
|
+
))
|
|
565
|
+
|
|
566
|
+
# Reusable template with #with (symbol placeholders)
|
|
567
|
+
TEMPLATE = SearchConf.new
|
|
568
|
+
.filter(SearchConf::Register.new(:REG_ID))
|
|
569
|
+
.filter(SearchConf::Exact.new(:external_id, :EXT_ID))
|
|
570
|
+
|
|
571
|
+
conf = TEMPLATE.with(REG_ID: 'reg_abc', EXT_ID: 'ABC-123')
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### For operations NOT yet wrapped (pass raw hash)
|
|
575
|
+
|
|
576
|
+
```ruby
|
|
577
|
+
# one_of_filter — match any of N values
|
|
578
|
+
SearchConf.new.filter(
|
|
579
|
+
{ 'operation' => 'one_of_filter',
|
|
580
|
+
'params' => { 'key' => 'state', 'values' => ['active', 'complete'] } }
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# has_any_filter — field is populated
|
|
584
|
+
SearchConf.new.filter(
|
|
585
|
+
{ 'operation' => 'has_any_filter', 'params' => { 'key' => 'external_id' } }
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
# date_filter with human mode
|
|
589
|
+
SearchConf.new.filter(
|
|
590
|
+
{ 'operation' => 'date_filter',
|
|
591
|
+
'params' => { 'key' => 'updated_at', 'mode' => 'last_month', 'time_zone' => 'Pacific/Auckland' } }
|
|
592
|
+
)
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
All three patterns work: `SearchConf` filters accept filter objects (wrapped classes),
|
|
596
|
+
raw hashes (unwrapped operations), or a mix. `normalize_variables` in `Logic::BaseQuery`
|
|
597
|
+
calls `.to_h` automatically so you never need to call `.to_h` explicitly at the call site.
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Production Examples
|
|
602
|
+
|
|
603
|
+
### Find page by externalId (org search)
|
|
604
|
+
|
|
605
|
+
```json
|
|
606
|
+
{
|
|
607
|
+
"showHiddenData": true,
|
|
608
|
+
"templatesOnly": false,
|
|
609
|
+
"searchConf": {
|
|
610
|
+
"filters": [
|
|
611
|
+
{
|
|
612
|
+
"operation": "exact_filter",
|
|
613
|
+
"params": { "key": "external_id", "value": "1223" }
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
},
|
|
617
|
+
"first": 10
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Find pages in a register updated after a date (org search)
|
|
622
|
+
|
|
623
|
+
```json
|
|
624
|
+
{
|
|
625
|
+
"showHiddenData": true,
|
|
626
|
+
"templatesOnly": false,
|
|
627
|
+
"searchConf": {
|
|
628
|
+
"filters": [
|
|
629
|
+
{
|
|
630
|
+
"operation": "register_filter",
|
|
631
|
+
"params": { "ids": ["REGISTER_ID"] }
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
"operation": "date_filter",
|
|
635
|
+
"params": { "key": "updated_at", "gte": "2025-01-01", "time_zone": "UTC" }
|
|
636
|
+
}
|
|
637
|
+
],
|
|
638
|
+
"sorters": { "key": "created_at", "direction": "asc" }
|
|
639
|
+
},
|
|
640
|
+
"first": 5
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Find pages by one of several externalIds (register search — OR logic)
|
|
645
|
+
|
|
646
|
+
```json
|
|
647
|
+
{
|
|
648
|
+
"registerId": "REGISTER_ID",
|
|
649
|
+
"includeArchived": true,
|
|
650
|
+
"search": {
|
|
651
|
+
"sorters": { "key": "created_at", "direction": "desc" },
|
|
652
|
+
"filters": [
|
|
653
|
+
{
|
|
654
|
+
"operation": "or_filter",
|
|
655
|
+
"params": {
|
|
656
|
+
"filters": [
|
|
657
|
+
{ "operation": "exact_filter", "params": { "key": "external_id", "value": "3333" } },
|
|
658
|
+
{ "operation": "exact_filter", "params": { "key": "external_id", "value": "TMS00323" } }
|
|
659
|
+
]
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
]
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Contractor search by externalId (same filter pattern, different query)
|
|
668
|
+
|
|
669
|
+
```json
|
|
670
|
+
{
|
|
671
|
+
"searchConf": {
|
|
672
|
+
"filters": [
|
|
673
|
+
{
|
|
674
|
+
"operation": "exact_filter",
|
|
675
|
+
"params": { "key": "external_id", "value": "9429041280072" }
|
|
676
|
+
}
|
|
677
|
+
]
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Find pages by state (org search)
|
|
683
|
+
|
|
684
|
+
Filter by page lifecycle state. The `state` key is snake_case; values are lowercase strings.
|
|
685
|
+
|
|
686
|
+
```json
|
|
687
|
+
{
|
|
688
|
+
"searchConf": {
|
|
689
|
+
"filters": [
|
|
690
|
+
{
|
|
691
|
+
"operation": "exact_filter",
|
|
692
|
+
"params": { "key": "state", "value": "active" }
|
|
693
|
+
}
|
|
694
|
+
]
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**Common state values:** `"active"`, `"inactive"`, `"complete"`
|
|
700
|
+
|
|
701
|
+
**Find archived pages** — archived pages are a separate flag (`archived: true`), not a state.
|
|
702
|
+
To include archived pages in register search, use `includeArchived: true` on the
|
|
703
|
+
`previewPages` query. For org search, archived pages appear in results by default
|
|
704
|
+
(no separate flag needed — use a state filter if you want to restrict to one state).
|
|
705
|
+
|
|
706
|
+
**Ruby (SearchConf):**
|
|
707
|
+
```ruby
|
|
708
|
+
SearchConf.new.filter(SearchConf::Exact.new(:state, 'active'))
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Find pages by location ID (org search)
|
|
712
|
+
|
|
713
|
+
Filter pages associated with a specific location node. The filter key is `location_id`
|
|
714
|
+
(confirmed from Confluence APIDOCS filter reference).
|
|
715
|
+
|
|
716
|
+
```json
|
|
717
|
+
{
|
|
718
|
+
"searchConf": {
|
|
719
|
+
"filters": [
|
|
720
|
+
{
|
|
721
|
+
"operation": "exact_filter",
|
|
722
|
+
"params": { "key": "location_id", "value": "LOCATION_NODE_ID" }
|
|
723
|
+
}
|
|
724
|
+
]
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
**Note:** `location_id` targets a single specific node in the location hierarchy.
|
|
730
|
+
To filter by multiple locations, use an `or_filter` wrapping multiple `exact_filter` entries,
|
|
731
|
+
or check whether `in_filter` (listed in Confluence APIDOCS but not yet confirmed via Insomnia)
|
|
732
|
+
is supported.
|
|
733
|
+
|
|
734
|
+
**Ruby (SearchConf):**
|
|
735
|
+
```ruby
|
|
736
|
+
SearchConf.new.filter(SearchConf::Exact.new(:location_id, 'NODE_ID'))
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
## The Required Pattern: Find Before Mutate
|
|
742
|
+
|
|
743
|
+
There is no `fetch page by externalId` endpoint. externalId is unique per organisation
|
|
744
|
+
but there is no direct lookup — you must search. The standard workflow:
|
|
745
|
+
|
|
746
|
+
```ruby
|
|
747
|
+
# Step 1: find the page ID via org search (register-scoped for speed)
|
|
748
|
+
conf = SearchConf.new
|
|
749
|
+
.filter(SearchConf::Register.new('REGISTER_ID')) # scope to register
|
|
750
|
+
.filter(SearchConf::Exact.new(:external_id, 'ABC-123'))
|
|
751
|
+
|
|
752
|
+
result = org.pages(searchConf: conf.to_h, first: 1)
|
|
753
|
+
page = result.nodes.first
|
|
754
|
+
# page is a PageUnion (BasicPage or PhasedPage) with full data including patchVer + field IDs
|
|
755
|
+
|
|
756
|
+
# Step 2: build mutation input from the fetched page
|
|
757
|
+
input = Input::Page::Update.from_model(page)
|
|
758
|
+
return if input.nil? # no changes
|
|
759
|
+
|
|
760
|
+
# Step 3: update
|
|
761
|
+
payload = api.page.update(input: input)
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
Why `register_filter` in org search (not register search)?
|
|
765
|
+
- Register search (`previewPages`) is fast/ES but returns `PreviewPage` — no field IDs, no `patchVer`
|
|
766
|
+
- Org search with `register_filter` gives the full `PageUnion` from DB — slow but complete
|
|
767
|
+
- For "does this page exist?" checks, register search is fine. For "update this page", always use org search.
|
|
768
|
+
|
|
769
|
+
The `register_filter` narrows the org search to one register, which dramatically reduces
|
|
770
|
+
result set size and improves response time despite the ~3s/page DB render cost.
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
## Filter Rules — Silent Failure Traps
|
|
775
|
+
|
|
776
|
+
**snake_case is mandatory.** Operation names must be snake_case strings (e.g. `exact_filter`,
|
|
777
|
+
`and_filter`). If CamelCase is used (e.g. `ExactFilter`), the server returns **no error and
|
|
778
|
+
no results** — it silently ignores the filter. This is one of the hardest bugs to diagnose.
|
|
779
|
+
|
|
780
|
+
**`filters` should always be an Array.** The server normalises a single object to an array
|
|
781
|
+
internally, so a bare object happens to work in some endpoints, but always use the array
|
|
782
|
+
form for consistency and forward-compatibility.
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## Important Observations
|
|
787
|
+
|
|
788
|
+
### `ArchivePage` — compound mutation pattern
|
|
789
|
+
|
|
790
|
+
Archiving a page in production runs **two mutations in a single GraphQL request**:
|
|
791
|
+
|
|
792
|
+
```graphql
|
|
793
|
+
mutation ArchivePage($id: ID!, $blank_external_id: Boolean = true) {
|
|
794
|
+
updatePage(input: { id: $id, page: { externalId: null } })
|
|
795
|
+
@include(if: $blank_external_id) {
|
|
796
|
+
item { ...corePageData }
|
|
797
|
+
}
|
|
798
|
+
archivePage(input: { id: $id }) {
|
|
799
|
+
item { ...corePageData }
|
|
800
|
+
errors { details fullMessages }
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
The `externalId` is blanked first (to prevent duplicate externalId conflicts if the page
|
|
806
|
+
is later unarchived and recreated with the same ID), then the archive mutation runs.
|
|
807
|
+
The `@include(if: $blank_external_id)` directive makes the blank optional.
|
|
808
|
+
|
|
809
|
+
**Implication for `Mutation::Page::Archive`:** May need to support the combined pattern,
|
|
810
|
+
or at minimum document that callers should blank `externalId` before archiving if they
|
|
811
|
+
manage externalIds.
|
|
812
|
+
|
|
813
|
+
### Two build patterns
|
|
814
|
+
|
|
815
|
+
**`Build - Model`** — older pattern, own inline fragment definitions, all 20 data field
|
|
816
|
+
types, no `$content`/`$fields` variables. Gives raw field structure.
|
|
817
|
+
|
|
818
|
+
**`Build - Standard`** — uses the `CommonPageUnion` fragment with
|
|
819
|
+
`$fields/$content/$only_content` variables. This is the production standard and the
|
|
820
|
+
pattern the gem's `Fragment::Pages::CommonPageUnion` is based on.
|
|
821
|
+
|
|
822
|
+
### `typo: extact_filter`
|
|
823
|
+
|
|
824
|
+
One contractor query uses `"operation": "extact_filter"` (typo). The correct name is
|
|
825
|
+
`exact_filter`. The server appears to tolerate this — no results were returned rather
|
|
826
|
+
than an error. Worth noting for the filter builder's validation.
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
## Filter Builder — Operation Constants
|
|
831
|
+
|
|
832
|
+
*Confirmed from backend source (`app/services/new_ep/es/filters/global/`) and frontend
|
|
833
|
+
`createFilter.ts`. All are valid snake_case operation strings.*
|
|
834
|
+
|
|
835
|
+
```ruby
|
|
836
|
+
# Core — wrapped by Input::SearchConf::* value objects
|
|
837
|
+
EXACT_FILTER = 'exact_filter'
|
|
838
|
+
DATE_FILTER = 'date_filter'
|
|
839
|
+
REGISTER_FILTER = 'register_filter'
|
|
840
|
+
AND_FILTER = 'and_filter'
|
|
841
|
+
OR_FILTER = 'or_filter'
|
|
842
|
+
|
|
843
|
+
# Confirmed from backend + frontend — pass as raw hash via SearchConf#filter
|
|
844
|
+
ONE_OF_FILTER = 'one_of_filter' # match any of N values
|
|
845
|
+
NONE_OF_FILTER = 'none_of_filter' # exclude all of N values
|
|
846
|
+
ALL_OF_FILTER = 'all_of_filter' # match all values (rarely needed)
|
|
847
|
+
HAS_ANY_FILTER = 'has_any_filter' # field has any non-empty value
|
|
848
|
+
MATCH_FILTER = 'match_filter' # partial text match
|
|
849
|
+
EXISTS_FILTER = 'exists_filter' # field present in ES document
|
|
850
|
+
DOESNT_EXIST_FILTER = 'doesnt_exist_filter' # field absent
|
|
851
|
+
BOOLEAN_FILTER = 'boolean_filter' # true/false field
|
|
852
|
+
NUMERIC_RANGE_FILTER = 'numeric_range_filter' # numeric gte/lte
|
|
853
|
+
ISNT_EXACT_FILTER = 'isnt_exact_filter' # negated exact match
|
|
854
|
+
NOT_REGISTER_FILTER = 'not_register_filter' # exclude registers
|
|
855
|
+
|
|
856
|
+
# Backend-confirmed, frontend-usage only (not typically used in scripts)
|
|
857
|
+
ARCHIVED_FILTER = 'archived_filter'
|
|
858
|
+
UNARCHIVED_FILTER = 'unarchived_filter'
|
|
859
|
+
NESTED_FILTER = 'nested_filter' # ES nested object query
|
|
860
|
+
ID_FILTER = 'id_filter'
|
|
861
|
+
PREFIX_FILTER = 'prefix_filter'
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
**Previously noted as "not confirmed"** — status now resolved:
|
|
865
|
+
- `exists_filter` — **confirmed** (see above)
|
|
866
|
+
- `in_filter` — **not a real operation**; use `one_of_filter` instead
|
|
867
|
+
- `range_filter` — **not a real operation**; use `date_filter` or `numeric_range_filter`
|
|
868
|
+
- `ne_filter` — **not a real operation**; use `isnt_exact_filter` instead
|