eco-helpers 3.2.23 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -39
  3. data/lib/eco/api/common/session/base_session.rb +4 -0
  4. data/lib/eco/api/common/session/environment.rb +5 -0
  5. data/lib/eco/api/custom/cli.rb +3 -0
  6. data/lib/eco/api/session/config/api.rb +33 -14
  7. data/lib/eco/api/usecases/CLAUDE.md +78 -0
  8. data/lib/eco/api/usecases/graphql/CLAUDE.md +120 -0
  9. data/lib/eco/api/usecases/graphql/compat/ooze_redirect.rb +13 -2
  10. data/lib/eco/api/usecases/graphql/helpers/CLAUDE.md +79 -0
  11. data/lib/eco/api/usecases/graphql/helpers/access_logs/base/reader.rb +59 -0
  12. data/lib/eco/api/usecases/graphql/helpers/access_logs/base.rb +17 -0
  13. data/lib/eco/api/usecases/graphql/helpers/access_logs.rb +7 -0
  14. data/lib/eco/api/usecases/graphql/helpers/base/connection_reader.rb +70 -0
  15. data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +6 -2
  16. data/lib/eco/api/usecases/graphql/helpers/base.rb +1 -0
  17. data/lib/eco/api/usecases/graphql/helpers/contractors/base/manager_settings.rb +64 -0
  18. data/lib/eco/api/usecases/graphql/helpers/contractors/base.rb +2 -0
  19. data/lib/eco/api/usecases/graphql/helpers/dashboards/base/reader.rb +30 -0
  20. data/lib/eco/api/usecases/graphql/helpers/dashboards/base.rb +20 -0
  21. data/lib/eco/api/usecases/graphql/helpers/dashboards.rb +7 -0
  22. data/lib/eco/api/usecases/graphql/helpers/pages/activities.rb +51 -0
  23. data/lib/eco/api/usecases/graphql/helpers/pages.rb +2 -1
  24. data/lib/eco/api/usecases/graphql/helpers.rb +2 -0
  25. data/lib/eco/api/usecases/graphql/samples/CLAUDE.md +76 -0
  26. data/lib/eco/api/usecases/graphql/samples/contractors/dsl.rb +16 -0
  27. data/lib/eco/api/usecases/graphql/samples/pages/CLAUDE.md +59 -0
  28. data/lib/eco/api/usecases/graphql/samples/pages/template/base.rb +46 -37
  29. data/lib/eco/api/usecases/graphql/samples/pages/template.rb +1 -1
  30. data/lib/eco/version.rb +1 -1
  31. metadata +22 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2803cb1dc211ef8682ebcd33e4007f4e748b5eb42f56ecbaa931b5258064ead8
4
- data.tar.gz: 7c0f5f66654761ce6ee5cb37ca4eb354054161c0fc3914c37da27f7f6fce4273
3
+ metadata.gz: 2c1f4723827d6dabfa094fd943b573a00bfe19c6d16b4ea0cb528c01d23d74d0
4
+ data.tar.gz: 6cb1e6109c1d4150f2a52c0359941b02be893065cc6b732932af117ad6bbd08c
5
5
  SHA512:
6
- metadata.gz: 10235574cf1a38826f4de995d715747b65fd657e0ef4e183b04644f8e26bebb2471c4d118fbbc00eac72e174d74c72d4685c9fb49a9a3667d8320c005ef7983a
7
- data.tar.gz: e9f15c9f0e9c5243c835402fe64efd1384021773d3e0c0d65fbbfe17bd10128e944932e9a8ac21501cf31025040c9fcc0b2b26b8eb70844fd631f899008c822c
6
+ metadata.gz: 515a129e3e75c42d0c16de5154a0e06e7d5854b65e766cf7e144b7097b6e55f2d2db5130ee05828f3c672da57f443517198816df18523ff952aefd8d93f9a645
7
+ data.tar.gz: aa5c685e34346dfe3bc41c006c89790eba83470327d950eeb531b0416b14228616d8cee57ba6d9f82d351db5ef3d4d6aa6a45aa147c6702821a1454a730fe879
data/CHANGELOG.md CHANGED
@@ -2,45 +2,98 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [3.2.23] - 2026-09-30
5
+ ## [3.3.0] - 2026-08-13
6
+
7
+ Adopts **`ecoportal-api-graphql` 2.0.0** (published 2026-08-13): floor raised from
8
+ `~> 1.3, >= 1.3.14` to `~> 2.0, >= 2.0.0`. Minor rather than major here — 2.0.0's breaking set
9
+ removed surfaces that either targeted mutations the live schema never had or were dead accessors,
10
+ and a sweep of this repo finds **no consumer of any of them** (`editPage`,
11
+ `editPageCreatorPermissions`, the AI-summary mutations, the SmartFill pair — zero hits outside
12
+ this repo's own readiness notes). Nothing in `lib/` changes behaviour as a result of the bump.
13
+
14
+ ★ **What starts WORKING, which matters more here than what broke.** Several paths were sending
15
+ documents the schema rejected, so they always errored; on 2.0.0 they really write. For this gem
16
+ the one to watch is the **`OozeRedirect` force path** — malformed `executeWorkflowCommands` until
17
+ graphql 1.3.15 fixed it, so its behaviour flips from "always errored" to "really writes" with no
18
+ signature change to warn anyone. **Re-verify it on a sandbox org before trusting it in a customer
19
+ run.** Also newly live via the gem: the page-level `executeWorkflowCommands` bus, File-field
20
+ attach writes, `updatePresetView` column edits, viewable-field management and Date-field renewals.
21
+
22
+ Consumers pinned to `~> 3.2` do not pick this up automatically, so adopting graphql 2.0.0 stays a
23
+ deliberate per-repo decision.
24
+
25
+ ★ **The published gem is now `lib/` only.** `spec.files` excluded just `test|spec|features`, so
26
+ every top-level directory added since shipped by default — `.ai-assistance/` internal notes,
27
+ `docs/` (including the rolling worklog) and `.claude/`. `spec.files` is now an allowlist
28
+ (`lib/`, `exe/`, README, LICENSE, CHANGELOG). All `lib/` files are unchanged; nothing outside
29
+ `lib/` was ever loaded at runtime. Same fix as `ecoportal-api-graphql` 2.0.0.
30
+
31
+ ## [3.2.20] - 2026-08-11
32
+
33
+ Reunites the released `3.2.19` hotfix line with `master`. `3.2.19` was cut from the `v3.2.18` tag
34
+ and **never merged back**, so two halves were split across branches: `master` carried
35
+ `Eco::API::Custom::Cli` (unreleased since 2026-07-25, and the reason `mns/config/cli.rb` could not
36
+ load on any published gem) while `3.2.19` carried the ooze KPI counter fix and the graphql floor.
37
+ This release carries both. Dependency floors are the max of each side.
38
+
39
+ ### Native GraphQL activity/dashboard readers + contractor manager-settings write
40
+
41
+ Additive, all **NATIVE** (no `Compat::` layer). Depends on the `ecoportal-api-graphql`
42
+ `feat/graphql-activity-logs` branch (polymorphic `activityLogs` / `page.activities` models);
43
+ during development it is wired via a temporary Gemfile `path:` override that a gem version bump
44
+ replaces on release.
45
+
46
+ ### Changed
47
+
48
+ - `ecoportal-api` dependency floor raised to `0.10.17`: inherits the `WithRetry` fix making
49
+ `HTTP::TimeoutError` retryable (transient timeouts no longer kill batch loops).
50
+
51
+ ### Added
52
+
53
+ - **Shared connection-reader substrate** — `Helpers::Base::ConnectionReader`: an `Enumerable`
54
+ mixin that wraps the gem's lazy `QueryConnection#each` (cursor pagination over
55
+ `pageInfo.endCursor`/`hasNextPage`) behind a uniform `each` / `to_a(limit:)` / `page_size`
56
+ surface. All new readers build on it.
57
+ - **System Access Logs reader** — `Helpers::AccessLogs::Base#access_logs` over
58
+ `currentOrganization.activityLogs`, exposing the full filter set (user_ids, resource_ids,
59
+ filter_superusers, activity_type, resource_type, source, date_filter) → yields `Model::ActivityLog`.
60
+ - **Page history reader** — `Helpers::Pages::Activities#page_activities` over `page(id:).activities`
61
+ (page id + `sorters:`/`search_conf:` + lazy pagination) → yields `Model::Activity`.
62
+ - **Dashboards reader** — `Helpers::Dashboards::Base#dashboards` over `dashboardSearch` → yields
63
+ `Model::MinimalDashboard`.
64
+ - **Contractor manager-settings WRITE** — `Helpers::Contractors::Base::ManagerSettings#update_contractor_manager_settings`
65
+ (also exposed via `Samples::Contractors::DSL#update_manager_settings`), driving the gem's
66
+ `Builder::ContractorEntity#update_manager_settings`. Guarded: **dry-run/simulate** short-circuits
67
+ before any mutation (returns the intended input); **-no-email** discipline honoured
68
+ (`options.workflow.no_email` or explicit `no_email:` suppresses the helper's own notification).
69
+ - **Thin live drivers** under `tests/` (read-only for the three readers; dry-run-only for the
70
+ contractor write) plus a shared `tests/env_graphql.rb` harness.
71
+ - Offline specs for all of the above (23 examples).
72
+ - `Eco::API::Common::Loaders::CliConfig` exposed via `Eco::API::Custom::Cli`.
73
+
74
+ ### Fixed
75
+
76
+ - **`Template::Base` could never run — `CommandEmitter` was unresolvable.** The class was declared
77
+ with the compact `class Template::Base` form inside `module ...::Pages`, which leaves `Template`
78
+ out of the file's lexical scope, so the bare `CommandEmitter` reference in `#desired_commands`
79
+ raised `NameError` on every call — meaning every build-from-scratch template use case failed
80
+ before emitting a single command. Now nested as `module Template` + `class Base`, matching the
81
+ sibling files (`command_emitter.rb`, `csv_build/builder.rb`) whose bare cross-references already
82
+ worked for exactly that reason. Public constant path is unchanged. Found by the first spec ever
83
+ written against this class.
6
84
 
7
- Security republish of 3.2.22. **Backwards-compatible; no code change**: same dependencies, same
8
- behaviour. Supersedes the published versions of this line, which are to be yanked.
9
-
10
- ### Security
11
-
12
- - Removed customer and internal identifiers from shipped code comments and this changelog (a
13
- comment listing customer organisations and their case names; internal repository names and
14
- internal documentation paths). Comments and changelog only. It also carries the full-path packaging allowlist from 3.3.2, so the five internal
15
- `CLAUDE.md` files that 3.2.22 packaged under `lib/` no longer ship.
16
- - Packaging: the three runtime JSON files are declared in `.release-smoke-allow`, and the release
17
- tasks accept `release/*` maintenance branches.
18
-
19
- ## [3.2.22] - 2026-08-14
20
-
21
- ### Fixed
22
-
23
- - Republish of `3.2.21`: its `lib/eco/version.rb` shipped with doubled carriage returns
24
- (`
25
- ` line endings), causing a `warning: encountered in middle of line` on every
26
- load. Functionally identical otherwise; `3.2.21` will be yanked.
27
-
28
- ## [3.2.21] - 2026-08-14
29
-
30
- ### Fixed
31
-
32
- - Packaging-only republish of `3.2.19` with an allowlisted gemspec (backported from `3.3.0`).
33
- Versions `3.2.16`-`3.2.19` shipped internal repository content (the repo's internal docs tooling,
34
- `.claude/settings.json`) to rubygems.org via the old denylist `spec.files`; `3.2.19` is
35
- yanked and `3.2.16`/`3.2.18` are queued for deletion by RubyGems support. `3.2.21` is the
36
- identical `lib/` code packaged clean, so constraints like `'~> 3.2.0', '>= 3.2.19'` keep
37
- resolving on the 3.2 line (its graphql dependency stays `~> 1.3`, satisfied by the clean
38
- `1.3.16`). The version number `3.2.20` is intentionally skipped: it exists as a
39
- tagged-but-deliberately-unpublished version (see the 3.3.0-era changelog corrections).
85
+ - **Ooze update KPI counters now count GraphQL updates.** `RegisterUpdateCase` tallied
86
+ `updated`/`failed` only when the result `is_a?(Ecoportal::API::Common::Response)`, but the
87
+ GraphQL compat layer returns an `Ecoportal::API::GraphQL::Compat::Response` (duck-types
88
+ `success?`/`status`, not in that class hierarchy) — so GraphQL updates were silently
89
+ uncounted (`Updated 0 (attempted: N)`, `Failed 0`) even when the write applied. Guard is now
90
+ `respond_to?(:success?)`; `false`/`nil` (dry-run / no-op) still skip. Inherited by
91
+ `TargetOozesUpdateCase` → TOOCS / cans-upsert. Also shipped as the maintenance release
92
+ `3.2.19` off `v3.2.18`; regression spec added.
40
93
 
41
94
  ## [3.2.19] - 2026-07-16
42
95
 
43
- A customer's `cans-upsert` reliability adoption + an ooze KPI counter fix. **Backwards-compatible.**
96
+ Farmers / `cans-upsert` reliability adoption + an ooze KPI counter fix. **Backwards-compatible.**
44
97
  Cut from the `v3.2.18` tag (not `master`, which carries the native GraphQL activity/dashboard
45
98
  readers depending on the unreleased gem `1.4.0`), so this ships needing only the published
46
99
  `ecoportal-api-graphql 1.3.14`.
@@ -73,7 +126,7 @@ guard so the dead-fragment class of bug can't reach production again.
73
126
  on 2026-07-04 (`243822b9`), then the live-crash fix `352a9657` (LocationDraft dead-fragment
74
127
  convention) landed on 2026-07-05 — keeping the same `3.2.17` label. Because `3.2.17` is installed
75
128
  from git/path (unpublished), a consumer bundled in that ~26h window reports `3.2.17` yet lacks the
76
- fix. This crashed the **live customer** org-structure sync again on 2026-07-09 with the exact
129
+ fix. This crashed the **live act-gov** org-structure sync again on 2026-07-09 with the exact
77
130
  `uninitialized constant …Fragment::LocationDraft (NameError)`. Bumping to `3.2.18` makes the fixed
78
131
  build unambiguously identifiable: any consumer on `>= 3.2.18` provably has the fix.
79
132
 
@@ -82,7 +135,7 @@ build unambiguously identifiable: any consumer on `>= 3.2.18` provably has the f
82
135
  - **Regression-guard spec for the dead-fragment convention.**
83
136
  `spec/…/helpers/location/command/end_points/optimizations_spec.rb` scans every file under
84
137
  `usecases/graphql/` and fails if any references a fragment via the removed `___Const__Fragment` /
85
- `::Fragment::<Name>` constant convention (the exact NameError that crashed a customer sync), and asserts
138
+ `::Fragment::<Name>` constant convention (the exact NameError that crashed act-gov), and asserts
86
139
  the three Location command payload procs still route fragments through the `spread :Name` registry
87
140
  DSL. It is a SOURCE lint, not a full offline render: rendering needs the graphlient fork's
88
141
  `to_query_string`/`spread` DSL, but eco-helpers' own bundle resolves stock graphlient `0.8.0` (the
@@ -107,7 +160,7 @@ to the gem's `Diff` module (gem v1.3.11), which is tagged but unpublished.
107
160
  `___Const__Fragment` constant convention (`___Ecoportal__API__GraphQL__Fragment__LocationDraft` /
108
161
  `…__LocationsError`), which the gem's registry-based fragments no longer expose as Ruby constants →
109
162
  `uninitialized constant … Fragment::LocationDraft (NameError)` when building a locations-draft
110
- `addCommands`/`create`/`publish` request. Crashed a customer's live tagtree / org-structure sync.
163
+ `addCommands`/`create`/`publish` request. Crashed the live act-gov tagtree / org-structure sync.
111
164
  Now `spread :LocationDraft` / `spread :LocationsError`, matching the gem.
112
165
 
113
166
  ### Dependencies
@@ -115,7 +168,7 @@ to the gem's `Diff` module (gem v1.3.11), which is tagged but unpublished.
115
168
  - **Raised floors to the fixed stack: `ecoportal-api-graphql >= 1.3.11`, `ecoportal-api-v2 >= 3.3.3`.**
116
169
  Stops the stale gem 1.3.9 (missing the LocationStructure `updatedAt`/`createdAt` selection fixes from
117
170
  1.3.10) and the buggy v2 3.3.2 (Ruby-3.x `DoubleModel` cascade `TypeError`) from resolving on
118
- consumers — both crashed a customer's live integration.
171
+ consumers — both crashed the live act-gov integration.
119
172
 
120
173
  ### Added
121
174
 
@@ -36,6 +36,10 @@ module Eco::API::Common::Session
36
36
  enviro.api?(version: version)
37
37
  end
38
38
 
39
+ def missing_api_params(version: nil)
40
+ enviro.missing_api_params(version: version)
41
+ end
42
+
39
43
  def file_manager
40
44
  enviro.file_manager
41
45
  end
@@ -63,6 +63,11 @@ module Eco::API::Common::Session
63
63
  config.apis.active_api.version_available?(version)
64
64
  end
65
65
 
66
+ # @return [Array<Symbol>] required parameters for `version` that are missing.
67
+ def missing_api_params(version:)
68
+ config.apis.active_api.missing_api_params(version)
69
+ end
70
+
66
71
  # Shortcut to logger.
67
72
  def log(level, &block)
68
73
  return unless logger.respond_to?(:level)
@@ -0,0 +1,3 @@
1
+ # Helper class to create a custom `Cli` config
2
+ class Eco::API::Custom::Cli < Eco::API::Common::Loaders::CliConfig
3
+ end
@@ -104,8 +104,10 @@ module Eco
104
104
  return current if current && !switch_logger
105
105
 
106
106
  unless api_params?(version)
107
+ missing = missing_api_params(version)
107
108
  msg = "The api configuration for #{description} "
108
- msg << "is missing data for the api version '#{self.version(version)}'"
109
+ msg << "is missing data for the api version '#{self.version(version)}': "
110
+ msg << "missing params #{missing.map {|param| ":#{param}"}.join(', ')}"
109
111
  raise ArgumentError, msg
110
112
  end
111
113
 
@@ -196,6 +198,12 @@ module Eco
196
198
  api_params?(version)
197
199
  end
198
200
 
201
+ # @return [Array<Symbol>] the required parameters for `version` that are
202
+ # currently missing (empty when all of them are present).
203
+ def missing_api_params(version)
204
+ required_api_params(version).reject {|_param, value| value}.keys
205
+ end
206
+
199
207
  def version(value = nil)
200
208
  self.class.to_version(value || self['version'])
201
209
  end
@@ -224,15 +232,18 @@ module Eco
224
232
  when :v2
225
233
  klass.new(user_key: user_key, org_key: external_key, host: host, logger: logger)
226
234
  when :graphql
227
- kargs = {
228
- host: host,
229
- org_id: org_id,
230
- email: email,
231
- pass: pass
232
- }
233
-
234
- kargs.merge!({no_schema: true}) if ENV['GRAPHQL_FETCH_SCHEMA'] == "false"
235
- klass.new(**kargs)
235
+ # NOTE: `GRAPHQL_FETCH_SCHEMA` is a documented no-op. `Ecoportal::API::GraphQL#initialize`
236
+ # (email:, pass:, org_id:, host:, api_key:) has never accepted a `no_schema:` keyword —
237
+ # it always builds its internal client with `no_schema: true` and has no public way to
238
+ # turn schema introspection on. Passing the unknown keyword raised ArgumentError, so the
239
+ # flag was worse than a no-op: setting `GRAPHQL_FETCH_SCHEMA=false` crashed api() instead
240
+ # of skipping a fetch that the gem's public API never performs regardless.
241
+ if ENV['GRAPHQL_FETCH_SCHEMA'] == "false"
242
+ warn "[eco-helpers] GRAPHQL_FETCH_SCHEMA is a no-op: " \
243
+ "Ecoportal::API::GraphQL never fetches the schema (no_schema: true, hardcoded)."
244
+ end
245
+
246
+ klass.new(host: host, org_id: org_id, email: email, pass: pass)
236
247
  end.tap do |api|
237
248
  next unless api
238
249
  next if log_connection? # prevent over-logging
@@ -248,15 +259,23 @@ module Eco
248
259
 
249
260
  # Checks if the necessary parameters for a specific `API` version are available.
250
261
  def api_params?(version)
262
+ missing_api_params(version).empty?
263
+ end
264
+
265
+ # Required parameters (as `name => value` pairs) for a specific `API` version.
266
+ # @note `to_version` always normalises to one of the known versions below.
267
+ def required_api_params(version)
251
268
  case self.class.to_version(version)
252
269
  when :v0
253
- internal_key && host
270
+ {internal_key: internal_key, host: host}
254
271
  when :v1
255
- external_key && host
272
+ {external_key: external_key, host: host}
256
273
  when :v2
257
- external_key && user_key && host
274
+ {external_key: external_key, user_key: user_key, host: host}
258
275
  when :graphql
259
- email && pass && org_id && host
276
+ {email: email, pass: pass, org_id: org_id, host: host}
277
+ else
278
+ {}
260
279
  end
261
280
  end
262
281
 
@@ -0,0 +1,78 @@
1
+ # usecases
2
+
3
+ The use-case registry and all built-in case base classes for scripting against EcoPortal.
4
+
5
+ ---
6
+
7
+ ## What a use case is
8
+
9
+ A use case is a self-contained, named, runnable unit of work. It registers itself with
10
+ the CLI framework, receives `session`, `options`, and `usecase` from the runner, and
11
+ executes its `process` (or `process_ooze` / `process_page`) method.
12
+
13
+ ```
14
+ CLI invokes rake → rake finds registered case → UseCase#launch → main() → process()
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Directory structure
20
+
21
+ | Path | What lives there |
22
+ |------|-----------------|
23
+ | `graphql/` | GraphQL-native base cases + samples (see `graphql/CLAUDE.md`) |
24
+ | `ooze_samples/` | APIv2/REST base cases: `OozeBaseCase`, `RegisterUpdateCase` |
25
+ | `ooze_cases/` | Concrete built-in ooze cases (export register, etc.) |
26
+ | `default/` | Built-in people, location, and utility cases |
27
+ | `default_cases/` | Loader and samples for default cases |
28
+ | `samples/` | Misc driver samples |
29
+ | `graphql.rb` | GraphQL use case loader |
30
+ | `ooze_samples.rb` | Ooze/REST use case loader |
31
+ | `default.rb` | Default use case loader |
32
+
33
+ ---
34
+
35
+ ## Adding a new use case
36
+
37
+ 1. Subclass the appropriate base:
38
+
39
+ | Your use case | Inherit from |
40
+ |---|---|
41
+ | Process pages in a register (update workflow) | `Eco::API::UseCases::GraphQL::PageCase` |
42
+ | Process pages org-wide (cross-register, audit) | `Eco::API::UseCases::GraphQL::OrgPageCase` |
43
+ | Custom GraphQL script (export, report, one-off) | `Eco::API::UseCases::GraphQL::Base` |
44
+ | Legacy APIv2 register update | `Eco::API::UseCases::OozeSamples::RegisterUpdateCase` |
45
+
46
+ 2. Set `name` and `type`:
47
+ ```ruby
48
+ name 'my-case-name' # CLI identifier: called with -my-case-name
49
+ type :other # :people | :contractors | :other
50
+ ```
51
+
52
+ 3. Override the entry point (`process_page`, `process`, or `process_ooze`).
53
+
54
+ 4. Register in the org's `config/cli.rb`:
55
+ ```ruby
56
+ cases.add('-my-case-name', :other, 'Description')
57
+ ```
58
+
59
+ ---
60
+
61
+ ## How cases are launched
62
+
63
+ `Eco::API::UseCases::UseCase#launch` calls `callback.call(*uio.params)` where the
64
+ callback is bound to `method(:main)`. Before launch, `@session` and `@options` are
65
+ injected into the instance — subclasses access them via the `attr_reader` in `CaseEnv`.
66
+
67
+ The `:other` type passes `(session, options, usecase)` positionally to `main`.
68
+ For `GraphQL::Base` subclasses the signature is `main(*_args)` — `session` and
69
+ `options` are already available via the helpers module before `main` is called.
70
+
71
+ ---
72
+
73
+ ## Related
74
+
75
+ - `graphql/CLAUDE.md` — GraphQL case hierarchy, PageCase/OrgPageCase
76
+ - `ooze_samples/` — legacy v2 cases (RegisterUpdateCase, OozeBaseCase)
77
+ - `eco-helpers/CLAUDE.md` — top-level gem context
78
+ - `ecoportal-api-graphql` — upstream gem providing `SearchConf`, `Compat::Pages`, etc.
@@ -0,0 +1,120 @@
1
+ # usecases/graphql
2
+
3
+ GraphQL-native use case base classes and helpers. All cases here work directly with
4
+ `ecoportal-api-graphql` — no v2 REST layer, no ooze objects.
5
+
6
+ ---
7
+
8
+ ## Class hierarchy
9
+
10
+ ```
11
+ Eco::API::Common::Loaders::UseCase (registration + launch)
12
+ ↓
13
+ Eco::API::UseCases::GraphQL::Base ← universal GraphQL env
14
+ ├── GraphQL::Samples::Pages::Page::Base ← register-scoped pages
15
+ │ ├── GraphQL::Samples::Pages::OrgPage::Base ← org-wide pages
16
+ │ └── your subclass (process_page, search_conf)
17
+ └── your subclass directly (custom scripts: exports, reports, one-offs)
18
+ ```
19
+
20
+ Samples live under `samples/pages/` — NOT in the `graphql/` root. The root only
21
+ has `base.rb`, `helpers.rb`, `utils.rb`, and `samples.rb`.
22
+
23
+ ---
24
+
25
+ ## Base — `graphql/base.rb`
26
+
27
+ Universal GraphQL environment. Provides `graphql`, `session`, `options`, `config`,
28
+ `simulate?`, `log`, `backup` via `Helpers::Base` (see `helpers/CLAUDE.md`).
29
+
30
+ Override `process` to write your script:
31
+ ```ruby
32
+ class MyCase < Eco::API::UseCases::GraphQL::Base
33
+ name 'my-case'
34
+ def process
35
+ graphql.currentOrganization.contractorEntities.each { |c| puts c.name }
36
+ end
37
+ end
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Pages — `samples/pages/`
43
+
44
+ Page processing base cases. Follow the hierarchy: `page/base` → `org_page/base`.
45
+
46
+ ### `samples/pages/page/base.rb` — `Samples::Pages::Page::Base`
47
+
48
+ For **register-scoped** page update workflows.
49
+
50
+ **Class methods:** `register_id 'REG_ID'`, `batch_size 50` (default)
51
+
52
+ **Override points:**
53
+ - `process_page(page)` — **required** — transformation for one page
54
+ - `search_conf` — optional — call `super` to keep register scope, then add filters
55
+
56
+ **Protected helpers:** `update_page`, `skip(reason)`, `each_page`
57
+
58
+ **KPI readers:** `total_pages`, `processed_pages`, `updated_pages`, `skipped_pages`, `failed_pages`
59
+
60
+ **DSL (via `samples/pages/page/dsl.rb`):** `sc`, `in_register`, `state_is`, `external_id_eq`, `updated_since`
61
+
62
+ ```ruby
63
+ class Custom::UseCase::UpdateStatus < Eco::API::UseCases::GraphQL::Samples::Pages::Page::Base
64
+ name 'update-status'
65
+ register_id 'REG_ABC'
66
+
67
+ def search_conf
68
+ super.filter(state_is(:active))
69
+ end
70
+
71
+ def process_page(page)
72
+ page.name = page.name.upcase
73
+ update_page(page)
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### `samples/pages/org_page/base.rb` — `Samples::Pages::OrgPage::Base`
79
+
80
+ Inherits `Page::Base`. `search_conf` starts empty (org-wide, no register scope).
81
+ Use for: archive sweeps, cross-register audits, bulk org operations.
82
+
83
+ ---
84
+
85
+ ## Samples — `graphql/samples/`
86
+
87
+ Built-in ready-to-use case implementations:
88
+ - `samples/location.rb` — location structure management cases
89
+ - `samples/contractors.rb` — contractor entity cases
90
+
91
+ See `samples/CLAUDE.md` for details.
92
+
93
+ ---
94
+
95
+ ## Helpers — `graphql/helpers/`
96
+
97
+ Mixins providing domain-specific access patterns. See `helpers/CLAUDE.md`.
98
+
99
+ ---
100
+
101
+ ## Loader order in `graphql.rb`
102
+
103
+ ```ruby
104
+ require 'graphql/helpers' # environment mixins (graphql, session, simulate? etc.)
105
+ require 'graphql/utils' # utility modules (SFTP etc.)
106
+ require 'graphql/base' # GraphQL::Base — universal foundation
107
+ require 'graphql/samples' # sample cases: location, contractors, pages, ...
108
+ # └─ graphql/samples/pages.rb
109
+ # └─ pages/page.rb → page/dsl.rb, page/base.rb
110
+ # └─ pages/org_page.rb → org_page/dsl.rb, org_page/base.rb
111
+ ```
112
+
113
+ Page base cases are in `samples/pages/` — NOT in the `graphql/` root.
114
+ Custom org cases are NOT loaded here — they live in the implementation repo.
115
+
116
+ ## default/pages/
117
+
118
+ CLI-integrated page use cases go in `default/pages/` (mirroring `default/locations/`
119
+ and `default/people/`). Currently empty — add cases there when a pattern is common
120
+ enough to expose to all org environments. See `default/pages.rb` for the convention.
@@ -62,8 +62,19 @@ module Eco::API::UseCases::GraphQL::Compat
62
62
  # force.custom_script = new_script → write the LISP script
63
63
  # force.script → raw script content (alias)
64
64
  #
65
- # Affected cases currently blocked: roughly half of all ooze cases in the internal script repos'
66
- # survey (per-customer breakdown kept in the internal migration notes, not in this gem).
65
+ # Affected cases currently blocked (from multi_org_api survey, ~50% of all
66
+ # ooze cases):
67
+ # act-gov: 5 x 20240130_act_*_case, rearrage_page_sites_case
68
+ # briscoes: remove_induction_sections, 310524_Briscoes_Remove_Tasks
69
+ # chorus: 4 x audit_update cases
70
+ # hcc: update_enterprise_risk_case
71
+ # lic: update_life_cycle_force_case
72
+ # mitre10: rich_text_update, update_location_force, updating_template
73
+ # npdc: contractor_title_force, risk_titile_force, fix_title_syncing,
74
+ # reminder_date_fields, 10092024_NPDC_CP_Add_Force
75
+ # profile-group: int_training_review, 20231026_profile_wellness
76
+ # turners-growers: event_changes, inj_cost_calc, remove_line_force
77
+ # twg: hide_attached_risks, add_new_force
67
78
  #
68
79
  # Implementation sketch (to be built when the endpoint lands):
69
80
  #
@@ -0,0 +1,79 @@
1
+ # usecases/graphql/helpers
2
+
3
+ Mixin modules providing domain-specific helper methods for GraphQL use cases.
4
+ All modules are ultimately included via `Helpers::Base` into `GraphQL::Base`.
5
+
6
+ ---
7
+
8
+ ## Include chain
9
+
10
+ ```
11
+ GraphQL::Base
12
+ includes Helpers::Base
13
+ includes CaseEnv → session, options, config, simulate?, log, ErrorHandling
14
+ includes GraphQLEnv → graphql (lazy, memoized)
15
+ includes Helpers (loader)
16
+ includes Helpers::Location → location tree helpers
17
+ includes Helpers::Contractors → contractor entity helpers
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Helpers::Base (`helpers/base.rb`)
23
+
24
+ Core environment — included in every GraphQL use case.
25
+
26
+ | Method | Source | Description |
27
+ |--------|--------|-------------|
28
+ | `session` | `CaseEnv` | Current `Eco::API::Session` |
29
+ | `options` | `CaseEnv` | Options hash from CLI/runner |
30
+ | `config` | `CaseEnv` | `session.config` shortcut |
31
+ | `simulate?` | `CaseEnv` | `options[:simulate] \|\| options[:dry_run]` |
32
+ | `log(level)` | `CaseEnv` | Logger proxy |
33
+ | `graphql` | `GraphQLEnv` | Lazy-loaded `Ecoportal::API::GraphQL` instance |
34
+ | `backup(data, type:)` | `Helpers::Base` | Save JSON to requests folder |
35
+ | `exit_error(msg)` | `Helpers::Base` | Log error and `exit(1)` |
36
+
37
+ ---
38
+
39
+ ## Helpers::Location (`helpers/location/`)
40
+
41
+ Location tree access, tag remapping, classification parsing.
42
+
43
+ - `helpers/location/base.rb` — `Location::Base` mixin
44
+ - `helpers/location/base/tree_tracking.rb` — track tree mutations
45
+ - `helpers/location/base/classifications_parser.rb` — parse location classifications
46
+ - `helpers/location/tags_remap/` — remapping tags across location changes
47
+ - `helpers/location/command/` — apply/diff location structure commands
48
+
49
+ ---
50
+
51
+ ## Helpers::Contractors (`helpers/contractors/`)
52
+
53
+ Contractor entity loading helpers.
54
+
55
+ - `helpers/contractors/base.rb` — base contractor helpers
56
+ - `helpers/contractors/base/load.rb` — batch load contractor entities
57
+
58
+ ---
59
+
60
+ ## Adding a new helper
61
+
62
+ 1. Create `helpers/my_domain/base.rb`:
63
+ ```ruby
64
+ module Eco::API::UseCases::GraphQL::Helpers
65
+ module MyDomain
66
+ module Base
67
+ private
68
+ def my_helper_method
69
+ graphql.myDomainQuery(...)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ ```
75
+ 2. Create `helpers/my_domain.rb` as a loader that includes `Base`
76
+ 3. Add `require_relative 'my_domain'` to `helpers.rb`
77
+
78
+ The helper is then available in all cases that include `Helpers::Base` (i.e., all
79
+ subclasses of `GraphQL::Base` including `PageCase` and `OrgPageCase`).
@@ -0,0 +1,59 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::AccessLogs
2
+ module Base
3
+ # NATIVE reader over `currentOrganization.activityLogs` (the ActivityLogUnion behind the web
4
+ # "System Access Logs" page). Lazily paginates and yields `Model::ActivityLog` records.
5
+ #
6
+ # Exposes the full server filter set. Construct via `Helpers::AccessLogs::Base#access_logs`:
7
+ # access_logs(source: 'web', resource_type: 'view_dashboard').each { |log| ... }
8
+ # access_logs(date_filter: { from: from_iso, to: to_iso }).to_a(limit: 500)
9
+ class Reader
10
+ include Eco::API::UseCases::GraphQL::Helpers::Base::ConnectionReader
11
+
12
+ # @param graphql [Ecoportal::API::GraphQL] the active gem client.
13
+ # @param user_ids [Array<String>, nil]
14
+ # @param resource_ids [Array<String>, nil]
15
+ # @param filter_superusers [Boolean, nil]
16
+ # @param activity_type [String, nil] free-text activity type filter.
17
+ # @param resource_type [String, nil] ActivityLogTypesEnum value (e.g. 'view_dashboard').
18
+ # @param source [String, nil] SourceEnum value (e.g. 'web', 'api').
19
+ # @param date_filter [Hash, nil] `{ from:, to: }` (ISO8601), maps to DateRange.
20
+ def initialize(
21
+ graphql,
22
+ user_ids: nil,
23
+ resource_ids: nil,
24
+ filter_superusers: nil,
25
+ activity_type: nil,
26
+ resource_type: nil,
27
+ source: nil,
28
+ date_filter: nil
29
+ )
30
+ @graphql = graphql
31
+ @user_ids = user_ids
32
+ @resource_ids = resource_ids
33
+ @filter_superusers = filter_superusers
34
+ @activity_type = activity_type
35
+ @resource_type = resource_type
36
+ @source = source
37
+ @date_filter = date_filter
38
+ end
39
+
40
+ private
41
+
42
+ def connection_query
43
+ @graphql.currentOrganization.activityLogs
44
+ end
45
+
46
+ def reader_params
47
+ {
48
+ userIds: @user_ids,
49
+ resourceIds: @resource_ids,
50
+ filterSuperusers: @filter_superusers,
51
+ activityType: @activity_type,
52
+ resourceType: @resource_type,
53
+ source: @source,
54
+ dateFilter: @date_filter
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end