eco-helpers 3.2.13 → 3.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ai-assistance/conventions/code-working-tree-protocol.md +176 -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/ep-ai-manager/SKILL.md +417 -0
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +215 -0
- data/.ai-assistance/standards-version.json +10 -0
- data/.ai-assistance/token-budget.json +39 -0
- data/.claude/settings.json +103 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +29 -1
- data/CLAUDE.md +83 -0
- data/eco-helpers.gemspec +1 -1
- data/lib/eco/api/usecases/CLAUDE.md +78 -0
- data/lib/eco/api/usecases/default/pages.rb +30 -0
- data/lib/eco/api/usecases/default/utils/add_page_id_case.rb +273 -0
- data/lib/eco/api/usecases/default/utils/cli/add_page_id_cli.rb +29 -0
- data/lib/eco/api/usecases/default/utils/cli/group_csv_cli.rb +5 -0
- data/lib/eco/api/usecases/default/utils/cli/track_files_cli.rb +16 -0
- data/lib/eco/api/usecases/default/utils/group_csv_case/file_handler.rb +62 -0
- data/lib/eco/api/usecases/default/utils/group_csv_case.rb +64 -22
- data/lib/eco/api/usecases/default/utils/track_files_case.rb +179 -0
- data/lib/eco/api/usecases/default/utils.rb +2 -0
- data/lib/eco/api/usecases/graphql/CLAUDE.md +120 -0
- data/lib/eco/api/usecases/graphql/compat/ooze_redirect/dirty_array.rb +22 -0
- data/lib/eco/api/usecases/graphql/compat/ooze_redirect/field_patches.rb +241 -0
- data/lib/eco/api/usecases/graphql/compat/ooze_redirect/force_compat.rb +73 -0
- data/lib/eco/api/usecases/graphql/compat/ooze_redirect.rb +234 -0
- data/lib/eco/api/usecases/graphql/compat.rb +6 -0
- data/lib/eco/api/usecases/graphql/helpers/CLAUDE.md +79 -0
- data/lib/eco/api/usecases/graphql/samples/CLAUDE.md +76 -0
- data/lib/eco/api/usecases/graphql/samples/pages/CLAUDE.md +59 -0
- data/lib/eco/api/usecases/graphql/samples/pages/org_page/base.rb +41 -0
- data/lib/eco/api/usecases/graphql/samples/pages/org_page/dsl.rb +8 -0
- data/lib/eco/api/usecases/graphql/samples/pages/org_page.rb +7 -0
- data/lib/eco/api/usecases/graphql/samples/pages/page/base.rb +148 -0
- data/lib/eco/api/usecases/graphql/samples/pages/page/dsl.rb +38 -0
- data/lib/eco/api/usecases/graphql/samples/pages/page.rb +7 -0
- data/lib/eco/api/usecases/graphql/samples/pages.rb +7 -0
- data/lib/eco/api/usecases/graphql/samples.rb +1 -0
- data/lib/eco/api/usecases/graphql.rb +1 -0
- data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +4 -0
- data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +7 -1
- data/lib/eco/version.rb +1 -1
- metadata +36 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class Eco::API::UseCases::Default::Utils::GroupCsv
|
|
2
|
+
class FileHandler
|
|
3
|
+
attr_reader :filename, :format
|
|
4
|
+
|
|
5
|
+
def initialize(filename, format: :csv)
|
|
6
|
+
@filename = filename
|
|
7
|
+
@format = format
|
|
8
|
+
|
|
9
|
+
open
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def <<(value)
|
|
13
|
+
msg = "File has been closed. Can't write to it: #{filename}"
|
|
14
|
+
raise msg unless file
|
|
15
|
+
|
|
16
|
+
case format
|
|
17
|
+
when :csv
|
|
18
|
+
file << value
|
|
19
|
+
when :jsonl
|
|
20
|
+
file.puts to_s(value)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def close
|
|
25
|
+
return if file.nil?
|
|
26
|
+
|
|
27
|
+
file.close.tap do
|
|
28
|
+
@file = nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :file
|
|
35
|
+
|
|
36
|
+
def to_s(value)
|
|
37
|
+
case value
|
|
38
|
+
when String
|
|
39
|
+
value.split("\n").first.tap do |line|
|
|
40
|
+
next if line == value
|
|
41
|
+
|
|
42
|
+
raise ArgumentError, "As string, value should be a single line. Given: #{value}"
|
|
43
|
+
end
|
|
44
|
+
when Hash
|
|
45
|
+
value.to_json
|
|
46
|
+
else
|
|
47
|
+
raise ArgumentError, "Unsupported type: #{value.class}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def open
|
|
52
|
+
case format
|
|
53
|
+
when :csv
|
|
54
|
+
@file = CSV.open(filename, 'wb')
|
|
55
|
+
when :jsonl
|
|
56
|
+
@file = File.open(filename, 'wb')
|
|
57
|
+
else
|
|
58
|
+
raise "Unknown output format: #{format}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -1,35 +1,59 @@
|
|
|
1
1
|
# This script assumes that for the `GROUP_BY_FIELD` rows are consecutive.
|
|
2
2
|
# @note you might run first the `sort-csv` case.
|
|
3
|
+
# @note when using `jsonl` as an output `format`, it doesn't merge fields,
|
|
4
|
+
# but it groups them based on some criteria.
|
|
5
|
+
# - In this case you need to define a `json_builder` method that returns a hash.
|
|
3
6
|
# @note you must inherit from this case and define the constants.
|
|
4
7
|
#
|
|
5
|
-
# GROUP_BY_FIELD = 'target_csv_field'.freeze
|
|
8
|
+
# GROUP_BY_FIELD = 'target_csv_field'.freeze # if `-by` command option isn't used
|
|
6
9
|
# GROUPED_FIELDS = [
|
|
7
10
|
# 'joined_field_1',
|
|
8
11
|
# 'joined_field_2',
|
|
9
12
|
# 'joined_field_3',
|
|
10
13
|
# ].freeze
|
|
11
|
-
#
|
|
14
|
+
# @note that `GROUPED_FIELDS` isn't necessary if `jsonl` is used as an output `format`
|
|
12
15
|
class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
13
16
|
name 'group-csv'
|
|
14
17
|
type :other
|
|
15
18
|
|
|
16
19
|
require_relative 'cli/group_csv_cli'
|
|
20
|
+
require_relative 'group_csv_case/file_handler'
|
|
21
|
+
|
|
22
|
+
OUTPUT_FORMAT = :csv # :csv or :jsonl
|
|
17
23
|
|
|
18
24
|
def main(*_args)
|
|
19
25
|
if simulate?
|
|
20
26
|
count = Eco::CSV.count(input_file)
|
|
21
27
|
log(:info) { "CSV '#{input_file}' has #{count} rows." }
|
|
22
28
|
else
|
|
29
|
+
msg = "You should define a json_builder method when using jsonl as output format"
|
|
30
|
+
raise msg unless respond_to?(:json_builder, true) || output_format != :jsonl
|
|
31
|
+
|
|
23
32
|
generate_file
|
|
24
33
|
end
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
private
|
|
28
37
|
|
|
38
|
+
attr_reader :in_index
|
|
39
|
+
|
|
40
|
+
def with_output_file
|
|
41
|
+
handler = FileHandler.new(output_filename, format: output_format)
|
|
42
|
+
|
|
43
|
+
yield handler
|
|
44
|
+
ensure
|
|
45
|
+
handler&.close
|
|
46
|
+
|
|
47
|
+
msg = "Generated file '#{output_filename}' "
|
|
48
|
+
msg << "with #{row_count} rows (out of #{in_index + 1})."
|
|
49
|
+
|
|
50
|
+
log(:info) { msg } unless simulate?
|
|
51
|
+
end
|
|
52
|
+
|
|
29
53
|
def generate_file # rubocop:disable Metrics/AbcSize
|
|
30
|
-
in_index = nil
|
|
54
|
+
@in_index = nil
|
|
31
55
|
|
|
32
|
-
|
|
56
|
+
with_output_file do |f_handler|
|
|
33
57
|
first = true
|
|
34
58
|
|
|
35
59
|
puts "\n"
|
|
@@ -38,11 +62,11 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
38
62
|
if first
|
|
39
63
|
first = false
|
|
40
64
|
headers!(row)
|
|
41
|
-
|
|
65
|
+
f_handler << headers if output_format == :csv
|
|
42
66
|
require_group_by_field!(row, file: input_file)
|
|
43
67
|
end
|
|
44
68
|
|
|
45
|
-
in_index = idx
|
|
69
|
+
@in_index = idx
|
|
46
70
|
next unless !block_given? || yield(row, idx)
|
|
47
71
|
|
|
48
72
|
next unless pivotable?(row, idx)
|
|
@@ -50,19 +74,25 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
50
74
|
|
|
51
75
|
row_count!
|
|
52
76
|
|
|
53
|
-
|
|
77
|
+
case output_format
|
|
78
|
+
when :csv
|
|
79
|
+
f_handler << last_group.values_at(*headers)
|
|
80
|
+
when :jsonl
|
|
81
|
+
f_handler << json_builder(last_group)
|
|
82
|
+
end
|
|
54
83
|
end
|
|
55
84
|
|
|
56
85
|
# finalize
|
|
57
86
|
if (l_row = pivot_row)
|
|
58
87
|
row_count!
|
|
59
|
-
out_csv << l_row.values_at(*headers)
|
|
60
|
-
end
|
|
61
|
-
ensure
|
|
62
|
-
msg = "Generated file '#{output_filename}' "
|
|
63
|
-
msg << "with #{row_count} rows (out of #{in_index + 1})."
|
|
64
88
|
|
|
65
|
-
|
|
89
|
+
case output_format
|
|
90
|
+
when :csv
|
|
91
|
+
f_handler << l_row.values_at(*headers)
|
|
92
|
+
when :jsonl
|
|
93
|
+
f_handler << json_builder(l_row)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
66
96
|
end
|
|
67
97
|
end
|
|
68
98
|
|
|
@@ -76,16 +106,23 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
76
106
|
pivot_value = row[group_by_field]
|
|
77
107
|
|
|
78
108
|
unless (last_pivot = @group[group_by_field])
|
|
109
|
+
# init
|
|
79
110
|
last_pivot = @group[group_by_field] = pivot_value
|
|
80
111
|
end
|
|
81
112
|
|
|
82
113
|
last = @group
|
|
83
114
|
@group = {group_by_field => pivot_value} unless pivot_value == last_pivot
|
|
84
115
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
case output_format
|
|
117
|
+
when :csv
|
|
118
|
+
headers_rest.each do |field|
|
|
119
|
+
curr_values = row[field].to_s.split('|').compact.uniq
|
|
120
|
+
group_values = @group[field].to_s.split('|').compact.uniq
|
|
121
|
+
@group[field] = (group_values | curr_values).join('|')
|
|
122
|
+
end
|
|
123
|
+
when :jsonl
|
|
124
|
+
@group['rows'] ||= []
|
|
125
|
+
@group['rows'] << row.to_h.slice(*headers_rest)
|
|
89
126
|
end
|
|
90
127
|
|
|
91
128
|
last unless last == @group
|
|
@@ -97,9 +134,10 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
97
134
|
def headers!(row)
|
|
98
135
|
return if headers?
|
|
99
136
|
|
|
100
|
-
@
|
|
101
|
-
@headers_rest
|
|
102
|
-
@
|
|
137
|
+
@grouped_fields = row.headers - [group_by_field] if output_format == :jsonl
|
|
138
|
+
@headers_rest = grouped_fields & row.headers
|
|
139
|
+
@headers_rest -= [group_by_field]
|
|
140
|
+
@headers = [group_by_field, *headers_rest]
|
|
103
141
|
end
|
|
104
142
|
|
|
105
143
|
def headers?
|
|
@@ -108,7 +146,7 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
108
146
|
|
|
109
147
|
def row_count!
|
|
110
148
|
@row_count ||= 0
|
|
111
|
-
(@row_count
|
|
149
|
+
(@row_count += 1).tap do |cnt|
|
|
112
150
|
if (cnt % 500).zero?
|
|
113
151
|
print "... Done #{cnt} rows \r"
|
|
114
152
|
$stdout.flush
|
|
@@ -141,10 +179,14 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
141
179
|
num
|
|
142
180
|
end
|
|
143
181
|
|
|
182
|
+
def output_format
|
|
183
|
+
options.dig(:output, :format)&.to_sym || self.class::OUTPUT_FORMAT
|
|
184
|
+
end
|
|
185
|
+
|
|
144
186
|
def output_filename
|
|
145
187
|
return unless input_name
|
|
146
188
|
|
|
147
|
-
File.join(input_dir, "#{input_name}_grouped
|
|
189
|
+
File.join(input_dir, "#{input_name}_grouped.#{output_format}")
|
|
148
190
|
end
|
|
149
191
|
|
|
150
192
|
def input_name
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Tracks the files of a source folder into a file
|
|
2
|
+
class Eco::API::UseCases::Default::Utils::TrackFiles < Eco::API::Custom::UseCase
|
|
3
|
+
name 'track-files'
|
|
4
|
+
type :other
|
|
5
|
+
|
|
6
|
+
require_relative 'cli/track_files_cli'
|
|
7
|
+
|
|
8
|
+
OUT_HEADERS = %w[
|
|
9
|
+
ref_id
|
|
10
|
+
filename
|
|
11
|
+
filesize
|
|
12
|
+
s3_path
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
REF_ID_PATH_POSITION = :last
|
|
16
|
+
BASE_S3_PATH = 'uploads'.freeze
|
|
17
|
+
# S3_SUBPATH = 'org-name'.freeze
|
|
18
|
+
|
|
19
|
+
def main(*_args)
|
|
20
|
+
if simulate?
|
|
21
|
+
count_files
|
|
22
|
+
else
|
|
23
|
+
generate_file
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :folder_count, :file_count
|
|
30
|
+
|
|
31
|
+
def folder_count!(cnt = 1)
|
|
32
|
+
@folder_count ||= 0
|
|
33
|
+
|
|
34
|
+
print '.'
|
|
35
|
+
@folder_count += cnt
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def file_count!(cnt = 1)
|
|
39
|
+
@file_count ||= 0
|
|
40
|
+
@file_count += cnt
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def count_files
|
|
44
|
+
with_each_file
|
|
45
|
+
|
|
46
|
+
log(:info) {
|
|
47
|
+
"Found #{file_count} files, in #{folder_count} folders (with files)."
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ref_id_path_position
|
|
52
|
+
self.class::REF_ID_PATH_POSITION
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def generate_file
|
|
56
|
+
CSV.open(output_filename, 'wb') do |csv|
|
|
57
|
+
csv << self.class::OUT_HEADERS
|
|
58
|
+
|
|
59
|
+
with_each_file do |file, src_path|
|
|
60
|
+
ref_id =
|
|
61
|
+
case ref_id_path_position
|
|
62
|
+
when :first then src_path.first
|
|
63
|
+
when :last then src_path.last
|
|
64
|
+
else
|
|
65
|
+
raise ArgumentError, "Unknown REF_ID_PATH_POSITION: #{ref_id_path_position} "
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
file_row = [ref_id]
|
|
69
|
+
file_row << file_name = File.basename(file)
|
|
70
|
+
file_row << File.size(file)
|
|
71
|
+
file_row << s3_path(file_name, src_path)
|
|
72
|
+
|
|
73
|
+
csv << file_row
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
ensure
|
|
77
|
+
msg = "Generated file '#{output_filename}' "
|
|
78
|
+
msg << "with #{file_count} files/rows "
|
|
79
|
+
msg << "organized in #{folder_count} folders."
|
|
80
|
+
|
|
81
|
+
log(:info) { msg } unless simulate?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def with_each_file(folders = top_subfolders, src_path: [], &block)
|
|
85
|
+
folders.each do |folder|
|
|
86
|
+
folder_name = File.basename(folder)
|
|
87
|
+
path = src_path[0..-1]
|
|
88
|
+
path << folder_name
|
|
89
|
+
|
|
90
|
+
files = folder_files(folder)
|
|
91
|
+
subfolders = top_subfolders(folder)
|
|
92
|
+
|
|
93
|
+
next if files.empty? && subfolders.empty? # skip
|
|
94
|
+
|
|
95
|
+
if files.any? && subfolders.any?
|
|
96
|
+
msg = "Folder '#{folder}' contains both files and subfolders."
|
|
97
|
+
msg << "\nFor correctly tracking and handling file attachments, "
|
|
98
|
+
msg << "this is not supported."
|
|
99
|
+
|
|
100
|
+
raise ArgumentError, msg
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
unless files.empty?
|
|
104
|
+
folder_count!
|
|
105
|
+
file_count!(files.count)
|
|
106
|
+
|
|
107
|
+
files.each do |file|
|
|
108
|
+
yield(file, path) if block_given?
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
next if subfolders.empty?
|
|
113
|
+
|
|
114
|
+
with_each_file(
|
|
115
|
+
subfolders,
|
|
116
|
+
src_path: path,
|
|
117
|
+
&block
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def s3_path(filename, path)
|
|
123
|
+
[
|
|
124
|
+
self.class::BASE_S3_PATH,
|
|
125
|
+
s3_subpath,
|
|
126
|
+
*path,
|
|
127
|
+
filename
|
|
128
|
+
].compact.join('/')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def s3_subpath
|
|
132
|
+
options.dig(:output, :s3_path) ||
|
|
133
|
+
s3_subpath_const ||
|
|
134
|
+
config.active_enviro
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def s3_subpath_const
|
|
138
|
+
self.class::S3_SUBPATH if self.class.const_defined?(:S3_SUBPATH)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def top_subfolders(base_folder = input_base_folder)
|
|
142
|
+
Dir[
|
|
143
|
+
File.join(base_folder, "*")
|
|
144
|
+
].select do |f|
|
|
145
|
+
File.directory?(f)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def folder_files(dir)
|
|
150
|
+
Dir[
|
|
151
|
+
File.join(dir, "*")
|
|
152
|
+
].select do |f|
|
|
153
|
+
File.file?(f)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def output_filename
|
|
158
|
+
return unless input_folder_name
|
|
159
|
+
|
|
160
|
+
File.join(
|
|
161
|
+
config.active_enviro,
|
|
162
|
+
'sftp',
|
|
163
|
+
"#{input_folder_name}_files.csv"
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def input_folder_name
|
|
168
|
+
@input_folder_name ||= File.basename(input_base_folder)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def input_base_folder
|
|
172
|
+
options.dig(:input, :folder).tap do |folder|
|
|
173
|
+
next if File.directory?(folder)
|
|
174
|
+
|
|
175
|
+
msg = "Expecting '#{folder}' to be a directory, but it isn't."
|
|
176
|
+
raise ArgumentError, msg
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -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.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Eco::API::UseCases::GraphQL::Compat::OozeRedirect
|
|
2
|
+
# Array subclass that calls the field's setter when elements are appended,
|
|
3
|
+
# ensuring GraphQL dirty-tracking fires on `fld.people_ids << value`.
|
|
4
|
+
class DirtyArray < Array
|
|
5
|
+
def initialize(field, data)
|
|
6
|
+
@field = field
|
|
7
|
+
super(Array(data))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def <<(value)
|
|
11
|
+
super
|
|
12
|
+
@field.people_ids = to_a
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def push(*values)
|
|
17
|
+
super
|
|
18
|
+
@field.people_ids = to_a
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|