monday_ruby 1.0.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e8164389fc7acb67d40bee3bf79a9768e1256833268e5ef8002c6d9392a5b75
4
- data.tar.gz: 29165f3538b8658267944fd2ea91f2bbdf11c916c7967e17cfe55e27397959c3
3
+ metadata.gz: fca12fa6b3f49d85bea9353e7621904cbe773f69fc9b4b18afc641a7c25318a0
4
+ data.tar.gz: 738fa89ae3732c0bf6afe22c314d7dcc42bae2990bfc313e640ce8aee7183755
5
5
  SHA512:
6
- metadata.gz: f21b44ba54e5c14513cd7f39b6709126fe1a5e54a00468da76fc2275230d9a01e7cf4844ae5035bf8777dacd8153ee7571a8a53a3491b6bf4366b47ea324d6ce
7
- data.tar.gz: bbaee28c405107767469f7a9386c7d2b991b4e877ccc3b476a360837ca25bb776aee2d05579ef4aa641b74b747cafe2fb73dc72836dd6b8264f61d4982b36729
6
+ metadata.gz: 843ba4a79d3a53b75b8ddfc8eb545d442600fe3b5b90df4f01ced08dd1516c8e9aab4755180b094d7c8fa94a2c330497dbee0b86ecc04071101deb0e236d2d6c
7
+ data.tar.gz: e6a6b8a7c1b912aee79a55f3da44372cf4d35eed207c7e061f131e6385ed9c48475c87c5722162dbdbf259058eaf13f7acf0e990a765dbc3b33299662120d8ef
data/.env CHANGED
@@ -1 +1 @@
1
- token="eyJhbGciOiJIUzI1NiJ9.eyJ0aWQiOjM4MTE3NTY1MywiYWFpIjoxMSwidWlkIjo2MzEzMDMxMiwiaWFkIjoiMjAyNC0wNy0wN1QxMzo0OTo0Mi4wMzJaIiwicGVyIjoibWU6d3JpdGUiLCJhY3RpZCI6MjQzMDgwODMsInJnbiI6InVzZTEifQ.AKgwPueXJkzm9d0I9y4DzSKBwJ3Vi3t8IsOc2hpyDtk"
1
+ token="eyJhbGciOiJIUzI1NiJ9.eyJ0aWQiOjU3ODczMzkyMiwiYWFpIjoxMSwidWlkIjo5NDg5NDgxMywiaWFkIjoiMjAyNS0xMC0yN1QwMToyMToxMy44NDFaIiwicGVyIjoibWU6d3JpdGUiLCJhY3RpZCI6MzIxODc3OTQsInJnbiI6InVzZTEifQ.SgPS-m2FEMHjitKC0SYTYsiQ8vAHZ7ynjDmMwQInneE"
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --format documentation
2
1
  --color
3
2
  --require spec_helper
data/.rubocop.yml CHANGED
@@ -1,6 +1,14 @@
1
+ plugins:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+
1
5
  AllCops:
2
6
  TargetRubyVersion: 2.7
3
7
  NewCops: enable
8
+ SuggestExtensions: false
9
+ Exclude:
10
+ - "spec/support/**/*.rb"
11
+ - "vendor/**/*"
4
12
 
5
13
  Style/StringLiterals:
6
14
  Enabled: true
@@ -20,3 +28,13 @@ Metrics/BlockLength:
20
28
  Metrics/MethodLength:
21
29
  Exclude:
22
30
  - "lib/monday/util.rb"
31
+
32
+ Metrics/ParameterLists:
33
+ Exclude:
34
+ - "lib/monday/resources/group.rb"
35
+
36
+ RSpec/NestedGroups:
37
+ Max: 5
38
+
39
+ RSpec/MultipleMemoizedHelpers:
40
+ Max: 10
data/.simplecov CHANGED
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ SimpleCov.add_filter "/spec/support/"
3
4
  SimpleCov.minimum_coverage 97
data/CHANGELOG.md CHANGED
@@ -1,3 +1,38 @@
1
+ ## v1.1.0 (October 27, 2025)
2
+
3
+ ### Added
4
+
5
+ - **Cursor-based pagination for items**:
6
+ - Added `items_page` method to `Board` resource for paginated item retrieval
7
+ - Added `items_page` method to `Group` resource for paginated group items
8
+ - Added `items_page` method to `Item` resource for paginated item queries
9
+ - Support for cursor-based pagination with customizable limits (up to 500 items per page)
10
+ - Support for filtered queries using `query_params` with rules and operators
11
+
12
+ - **Deprecation warning system**:
13
+ - Added `Deprecation` module for issuing deprecation warnings
14
+ - Marked `delete_subscribers` method for deprecation in v2.0.0
15
+ - Provides clear migration paths for deprecated methods
16
+
17
+ - **Configurable request timeouts**:
18
+ - Added `open_timeout` configuration option (default: 10 seconds)
19
+ - Added `read_timeout` configuration option (default: 30 seconds)
20
+ - Configurable at both global and client instance levels
21
+
22
+ - **Documentation improvements**:
23
+ - Added CONTRIBUTING.md with development guidelines
24
+ - Added VCR testing guide in pull request template
25
+
26
+ ### Changed
27
+
28
+ - Updated Ruby version support matrix in CI (added Ruby 3.3 and 3.4)
29
+ - Updated base64 gem dependency to ~> 0.3.0
30
+ - Improved RuboCop configuration and fixed linting issues
31
+
32
+ ### Fixed
33
+
34
+ - CI workflow improvements and linting configurations
35
+
1
36
  ## v1.0.0 (July 30, 2024)
2
37
 
3
38
  ### Changed
data/CONTRIBUTING.md CHANGED
@@ -16,6 +16,67 @@ Please follow these steps to have your contribution considered:
16
16
  2. Follow the [commit guidelines](#commit-message-guidelines).
17
17
  3. After you submit your pull request, verify that all the status checks are passing.
18
18
 
19
+ ## Testing Guidelines
20
+
21
+ This project uses [VCR](https://github.com/vcr/vcr) to record HTTP interactions for tests. This means you **do not need a Monday.com API token** to run most tests or contribute to the project.
22
+
23
+ ### Running Tests
24
+
25
+ To run the test suite:
26
+
27
+ ```bash
28
+ bundle exec rake spec
29
+ ```
30
+
31
+ All tests will use pre-recorded VCR cassettes stored in `spec/fixtures/vcr_cassettes/`.
32
+
33
+ ### Working with VCR Cassettes
34
+
35
+ **For most contributions, you won't need to modify VCR cassettes.** The existing cassettes cover the current API functionality.
36
+
37
+ #### When You Need to Record New Cassettes
38
+
39
+ You only need to record new VCR cassettes when:
40
+ - Adding support for a **new API endpoint** that doesn't have existing test coverage
41
+ - Modifying an existing API call that changes the request/response structure
42
+
43
+ To record new cassettes:
44
+
45
+ 1. Set your Monday.com API token as an environment variable:
46
+ ```bash
47
+ export MONDAY_TOKEN="your_token_here"
48
+ ```
49
+
50
+ 2. Delete the old cassette file (if updating an existing test):
51
+ ```bash
52
+ rm spec/fixtures/vcr_cassettes/your_cassette_name.yml
53
+ ```
54
+
55
+ 3. Run the specific test to generate a new cassette:
56
+ ```bash
57
+ bundle exec rspec spec/path/to/your_spec.rb
58
+ ```
59
+
60
+ 4. **Important:** Before committing, verify the cassette doesn't contain sensitive data:
61
+ - VCR automatically filters the `Authorization` header
62
+ - Check for any other sensitive information in the cassette file
63
+ - Cassettes are committed to the repository
64
+
65
+ #### Testing New Features Without API Access
66
+
67
+ If you're adding a new feature but don't have API access to record cassettes:
68
+ 1. Write your implementation and tests
69
+ 2. Create a pull request noting that cassettes need to be recorded
70
+ 3. A maintainer with API access will record the cassettes for you
71
+
72
+ ### Code Quality
73
+
74
+ Run RuboCop to ensure code style compliance:
75
+
76
+ ```bash
77
+ bundle exec rake rubocop
78
+ ```
79
+
19
80
  ## Commit message guidelines
20
81
 
21
82
  * Use present tense ("Add feature" not "Added feature")
data/README.md CHANGED
@@ -58,6 +58,25 @@ Monday.configure do |config|
58
58
  end
59
59
  ```
60
60
 
61
+ You can also configure request timeouts (new in v1.1.0):
62
+
63
+ ```ruby
64
+ require "monday_ruby"
65
+
66
+ Monday.configure do |config|
67
+ config.token = "<AUTH_TOKEN>"
68
+ config.open_timeout = 10 # seconds (default: 10)
69
+ config.read_timeout = 30 # seconds (default: 30)
70
+ end
71
+
72
+ # Or configure per client
73
+ client = Monday::Client.new(
74
+ token: "<AUTH_TOKEN>",
75
+ open_timeout: 15,
76
+ read_timeout: 45
77
+ )
78
+ ```
79
+
61
80
  ### Accessing a response object
62
81
 
63
82
  Get access to response objects by initializing a client and using the appropriate action you want to perform:
@@ -87,7 +106,7 @@ response = client.boards # => <Monday::Response ...>
87
106
  response.success? # => true
88
107
 
89
108
  # To get the boards from the response
90
- response.dig("data", "boards") # => [...]
109
+ response.body.dig("data", "boards") # => [...]
91
110
  ```
92
111
 
93
112
  #### Creating a new board
@@ -110,7 +129,7 @@ response = client.create_board(args: args)
110
129
  response.success? # => true
111
130
 
112
131
  # To get the created board from the response
113
- response.dig("data", "create_board") # => { ... }
132
+ response.body.dig("data", "create_board") # => { ... }
114
133
  ```
115
134
 
116
135
  #### Creating a new item on board
@@ -140,7 +159,63 @@ response = client.create_item(args: args)
140
159
  response.success? # => true
141
160
 
142
161
  # To get the created item from the response
143
- response.dig("data", "create_item") # => { ... }
162
+ response.body.dig("data", "create_item") # => { ... }
163
+ ```
164
+
165
+ #### Fetching items with pagination (New in v1.1.0)
166
+
167
+ The library now supports efficient cursor-based pagination for retrieving large numbers of items. This is the recommended approach for working with boards, groups, or items that contain many records.
168
+
169
+ ```ruby
170
+ client = Monday::Client.new(token: <AUTH_TOKEN>)
171
+
172
+ # Fetch first page of items from a board (up to 500 items per page)
173
+ response = client.board.items_page(
174
+ board_ids: <BOARD_ID>,
175
+ limit: 100
176
+ )
177
+
178
+ # Extract items and cursor from response
179
+ items = response.body.dig("data", "boards", 0, "items_page", "items")
180
+ cursor = response.body.dig("data", "boards", 0, "items_page", "cursor")
181
+
182
+ # Fetch next page using cursor
183
+ if cursor
184
+ next_response = client.board.items_page(
185
+ board_ids: <BOARD_ID>,
186
+ limit: 100,
187
+ cursor: cursor
188
+ )
189
+ end
190
+
191
+ # You can also filter items using query_params
192
+ response = client.board.items_page(
193
+ board_ids: <BOARD_ID>,
194
+ limit: 50,
195
+ query_params: {
196
+ rules: [{ column_id: "status", compare_value: [1] }],
197
+ operator: :and
198
+ }
199
+ )
200
+ ```
201
+
202
+ Pagination is also available for groups and items:
203
+
204
+ ```ruby
205
+ # Fetch paginated items from a group
206
+ response = client.group.items_page(
207
+ board_ids: <BOARD_ID>,
208
+ group_ids: "group_id",
209
+ limit: 100
210
+ )
211
+
212
+ # Fetch paginated items with custom query
213
+ response = client.item.items_page(
214
+ limit: 100,
215
+ query_params: {
216
+ rules: [{ column_id: "status", compare_value: [5] }]
217
+ }
218
+ )
144
219
  ```
145
220
 
146
221
  ## Development
data/lib/monday/client.rb CHANGED
@@ -26,7 +26,13 @@ module Monday
26
26
  end
27
27
 
28
28
  def make_request(body)
29
- response = Request.post(uri, body, request_headers)
29
+ response = Request.post(
30
+ uri,
31
+ body,
32
+ request_headers,
33
+ open_timeout: @config.open_timeout,
34
+ read_timeout: @config.read_timeout
35
+ )
30
36
 
31
37
  handle_response(Response.new(response))
32
38
  end
@@ -63,7 +69,7 @@ module Monday
63
69
  end
64
70
 
65
71
  def response_exception(response)
66
- error_code = response.body["error_code"]
72
+ error_code = response_error_code(response)
67
73
 
68
74
  return Error.new(response: response) if error_code.nil?
69
75
 
@@ -71,6 +77,15 @@ module Monday
71
77
  exception_klass.new(message: error_code, response: response, code: code)
72
78
  end
73
79
 
80
+ def response_error_code(response)
81
+ error_code = response.body["error_code"]
82
+ return error_code unless error_code.nil?
83
+
84
+ return unless response.body["errors"].is_a?(Array) && !response.body["errors"].empty?
85
+
86
+ response.body.dig("errors", 0, "extensions", "code") || response.body.dig("errors", 0, "extensions", "error_code")
87
+ end
88
+
74
89
  def default_exception(response)
75
90
  Util.status_code_exceptions_mapping(response.status).new(response: response)
76
91
  end
@@ -11,11 +11,15 @@ module Monday
11
11
  DEFAULT_HOST = "https://api.monday.com/v2"
12
12
  DEFAULT_TOKEN = nil
13
13
  DEFAULT_VERSION = "2023-07"
14
+ DEFAULT_OPEN_TIMEOUT = 10
15
+ DEFAULT_READ_TIMEOUT = 30
14
16
 
15
17
  CONFIGURATION_FIELDS = %i[
16
18
  token
17
19
  host
18
20
  version
21
+ open_timeout
22
+ read_timeout
19
23
  ].freeze
20
24
 
21
25
  attr_accessor(*CONFIGURATION_FIELDS)
@@ -27,6 +31,8 @@ module Monday
27
31
  @host = DEFAULT_HOST
28
32
  @token = DEFAULT_TOKEN
29
33
  @version = DEFAULT_VERSION
34
+ @open_timeout = DEFAULT_OPEN_TIMEOUT
35
+ @read_timeout = DEFAULT_READ_TIMEOUT
30
36
 
31
37
  config_args.each do |key, value|
32
38
  public_send("#{key}=", value)
@@ -37,6 +43,8 @@ module Monday
37
43
  @token = DEFAULT_TOKEN
38
44
  @host = DEFAULT_HOST
39
45
  @version = DEFAULT_VERSION
46
+ @open_timeout = DEFAULT_OPEN_TIMEOUT
47
+ @read_timeout = DEFAULT_READ_TIMEOUT
40
48
  end
41
49
  end
42
50
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Monday
4
+ # Utility module for handling deprecation warnings
5
+ module Deprecation
6
+ # Issues a deprecation warning to stderr
7
+ #
8
+ # @param method_name [String] The name of the deprecated method
9
+ # @param removal_version [String] The version in which the method will be removed
10
+ # @param alternative [String, nil] The recommended alternative method
11
+ #
12
+ # @example
13
+ # Deprecation.warn(method_name: "items", removal_version: "2.0.0", alternative: "items_page")
14
+ # # => [DEPRECATION] `items` is deprecated and will be removed in v2.0.0. Use `items_page` instead.
15
+ def self.warn(method_name:, removal_version:, alternative: nil)
16
+ message = "[DEPRECATION] `#{method_name}` is deprecated and will be removed in v#{removal_version}."
17
+ message += " Use `#{alternative}` instead." if alternative
18
+
19
+ # Output to stderr so it doesn't interfere with normal output
20
+ Kernel.warn message
21
+ end
22
+ end
23
+ end
data/lib/monday/error.rb CHANGED
@@ -56,13 +56,16 @@ module Monday
56
56
  # AuthorizationError is raised when the request returns
57
57
  # a 401 or 403 status code.
58
58
  #
59
- # It is also raised when the body returns the following error_code:
60
- # UserUnauthorizedException
59
+ # It is also raised when the body returns the following error_codes:
60
+ # UserUnauthorizedException, USER_UNAUTHORIZED
61
61
  class AuthorizationError < Error
62
62
  end
63
63
 
64
64
  # RateLimitError is raised when the request returns
65
65
  # a 429 status code.
66
+ #
67
+ # It is also raised when the body returns the following error_codes:
68
+ # ComplexityException, COMPLEXITY_BUDGET_EXHAUSTED
66
69
  class RateLimitError < Error
67
70
  end
68
71
 
@@ -4,9 +4,12 @@ module Monday
4
4
  # Defines the HTTP request methods.
5
5
  class Request
6
6
  # Performs a POST request
7
- def self.post(uri, query, headers)
7
+ def self.post(uri, query, headers, open_timeout: 10, read_timeout: 30)
8
8
  http = Net::HTTP.new(uri.host, uri.port)
9
9
  http.use_ssl = true
10
+ http.open_timeout = open_timeout
11
+ http.read_timeout = read_timeout
12
+
10
13
  request = Net::HTTP::Post.new(uri.request_uri, headers)
11
14
 
12
15
  request.body = {
@@ -7,6 +7,7 @@ module Monday
7
7
  # Represents Monday.com's board resource.
8
8
  class Board < Base
9
9
  DEFAULT_SELECT = %w[id name description].freeze
10
+ DEFAULT_PAGINATED_SELECT = %w[id name].freeze
10
11
 
11
12
  # Retrieves all the boards.
12
13
  #
@@ -79,11 +80,62 @@ module Monday
79
80
  # Allows customizing the values to retrieve using the select option.
80
81
  # By default, returns the deleted subscriber IDs.
81
82
  def delete_subscribers(board_id, user_ids, select: ["id"])
83
+ Deprecation.warn(
84
+ method_name: "delete_subscribers",
85
+ removal_version: "2.0.0",
86
+ alternative: "user.delete_from_board"
87
+ )
88
+
82
89
  query = "mutation{delete_subscribers_from_board(" \
83
90
  "board_id: #{board_id}, user_ids: #{user_ids}){#{Util.format_select(select)}}}"
84
91
 
85
92
  make_request(query)
86
93
  end
94
+
95
+ # Retrieves paginated items from a board.
96
+ #
97
+ # Uses cursor-based pagination for efficient data retrieval.
98
+ # The items_page field is the modern replacement for the deprecated items field.
99
+ #
100
+ # @param board_id [Integer] The ID of the board
101
+ # @param limit [Integer] Number of items to retrieve per page (default: 25, max: 500)
102
+ # @param cursor [String, nil] Pagination cursor for fetching next page (expires after 60 minutes)
103
+ # @param query_params [Hash, nil] Query parameters for filtering items with rules and operators
104
+ # @param args [Hash] Additional board query arguments
105
+ # @param select [Array] Fields to retrieve for each item
106
+ # @return [Monday::Response] Response containing items and cursor
107
+ #
108
+ # @example Fetch first page of items
109
+ # response = client.board.items_page(board_id: 123, limit: 50)
110
+ # items = response.dig("data", "boards", 0, "items_page", "items")
111
+ # cursor = response.dig("data", "boards", 0, "items_page", "cursor")
112
+ #
113
+ # @example Fetch next page using cursor
114
+ # response = client.board.items_page(board_id: 123, cursor: cursor)
115
+ #
116
+ # @example Filter items by column value
117
+ # response = client.board.items_page(
118
+ # board_id: 123,
119
+ # limit: 100,
120
+ # query_params: {
121
+ # rules: [{ column_id: "status", compare_value: [1] }],
122
+ # operator: :and
123
+ # }
124
+ # )
125
+ def items_page(board_ids:, limit: 25, cursor: nil, query_params: nil, select: DEFAULT_PAGINATED_SELECT)
126
+ items_args_parts = ["limit: #{limit}"]
127
+ items_args_parts << "cursor: \"#{cursor}\"" if cursor
128
+ items_args_parts << "query_params: #{Util.format_graphql_object(query_params)}" if query_params
129
+
130
+ items_args_string = items_args_parts.empty? ? "" : "(#{items_args_parts.join(", ")})"
131
+
132
+ items_page_select = "items_page#{items_args_string}{cursor items{#{Util.format_select(select)}}}"
133
+
134
+ board_args = { ids: board_ids.is_a?(Array) ? board_ids : [board_ids] }
135
+ request_query = "query{boards#{Util.format_args(board_args)}{#{items_page_select}}}"
136
+
137
+ make_request(request_query)
138
+ end
87
139
  end
88
140
  end
89
141
  end
@@ -25,6 +25,12 @@ module Monday
25
25
  # Allows customizing the values to retrieve using the select option.
26
26
  # By default, ID, title and description fields are retrieved.
27
27
  def column_values(board_ids = [], item_ids = [], select: DEFAULT_SELECT)
28
+ Deprecation.warn(
29
+ method_name: "column_values",
30
+ removal_version: "2.0.0",
31
+ alternative: "item.column_values"
32
+ )
33
+
28
34
  board_args = board_ids.empty? ? "" : "ids: #{board_ids}"
29
35
  item_args = item_ids.empty? ? "" : "ids: #{item_ids}"
30
36
  query = "query{boards(#{board_args}){items(#{item_args})" \
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Monday
6
+ module Resources
7
+ # Represents Monday.com's board resource.
8
+ class Folder < Base
9
+ DEFAULT_SELECT = %w[id name].freeze
10
+
11
+ # Retrieves all the folders.
12
+ #
13
+ # Allows filtering folders using the args option.
14
+ # Allows customizing the values to retrieve using the select option.
15
+ # By default, ID and name fields are retrieved.
16
+ def query(args: {}, select: DEFAULT_SELECT)
17
+ request_query = "query{folders#{Util.format_args(args)}{#{Util.format_select(select)}}}"
18
+
19
+ make_request(request_query)
20
+ end
21
+
22
+ # Create a new folder.
23
+ #
24
+ # Allows customizing creating a folder using the args option.
25
+ # Allows customizing the values to retrieve using the select option.
26
+ # By default, ID and name fields are retrieved.
27
+ def create(args: {}, select: DEFAULT_SELECT)
28
+ query = "mutation{create_folder#{Util.format_args(args)}{#{Util.format_select(select)}}}"
29
+
30
+ make_request(query)
31
+ end
32
+
33
+ # Update a folder.
34
+ #
35
+ # Allows customizing updating the folder using the args option.
36
+ # By default, returns the ID of the updated folder.
37
+ def update(args: {}, select: ["id"])
38
+ query = "mutation{update_folder#{Util.format_args(args)}{#{Util.format_select(select)}}}"
39
+
40
+ make_request(query)
41
+ end
42
+
43
+ # Delete a folder.
44
+ #
45
+ # Requires folder_id in args option to delete the folder.
46
+ # Allows customizing the values to retrieve using the select option.
47
+ # By default, returns the ID of the folder deleted.
48
+ def delete(args: {}, select: ["id"])
49
+ query = "mutation{delete_folder#{Util.format_args(args)}{#{Util.format_select(select)}}}"
50
+
51
+ make_request(query)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -7,6 +7,7 @@ module Monday
7
7
  # Represents Monday.com's group resource.
8
8
  class Group < Base
9
9
  DEFAULT_SELECT = %w[id title].freeze
10
+ DEFAULT_PAGINATED_SELECT = %w[id name].freeze
10
11
 
11
12
  # Retrieves all the groups.
12
13
  #
@@ -83,6 +84,71 @@ module Monday
83
84
 
84
85
  make_request(query)
85
86
  end
87
+
88
+ # Retrieves paginated items from a group.
89
+ #
90
+ # Uses cursor-based pagination for efficient data retrieval.
91
+ # The items_page field is the modern replacement for the deprecated items field.
92
+ #
93
+ # @param board_ids [Integer, Array<Integer>] The ID(s) of the board(s) containing the group
94
+ # @param group_ids [String, Array<String>] The ID(s) of the group(s)
95
+ # @param limit [Integer] Number of items to retrieve per page (default: 25, max: 500)
96
+ # @param cursor [String, nil] Pagination cursor for fetching next page (expires after 60 minutes)
97
+ # @param query_params [Hash, nil] Query parameters for filtering items with rules and operators
98
+ # @param select [Array] Fields to retrieve for each item
99
+ # @return [Monday::Response] Response containing items and cursor
100
+ #
101
+ # @example Fetch first page of items from a group
102
+ # response = client.group.items_page(board_ids: 123, group_ids: "group_1", limit: 50)
103
+ # items = response.dig("data", "boards", 0, "groups", 0, "items_page", "items")
104
+ # cursor = response.dig("data", "boards", 0, "groups", 0, "items_page", "cursor")
105
+ #
106
+ # @example Fetch next page using cursor
107
+ # response = client.group.items_page(board_ids: 123, group_ids: "group_1", cursor: cursor)
108
+ #
109
+ # @example Filter items by column value
110
+ # response = client.group.items_page(
111
+ # board_ids: 123,
112
+ # group_ids: "group_1",
113
+ # limit: 100,
114
+ # query_params: {
115
+ # rules: [{ column_id: "status", compare_value: [1] }],
116
+ # operator: :and
117
+ # }
118
+ # )
119
+ def items_page(
120
+ board_ids:, group_ids:, limit: 25, cursor: nil, query_params: nil, select: DEFAULT_PAGINATED_SELECT
121
+ )
122
+ items_page_query = build_items_page_query(limit, cursor, query_params, select)
123
+ request_query = build_groups_items_page_request(board_ids, group_ids, items_page_query)
124
+
125
+ make_request(request_query)
126
+ end
127
+
128
+ private
129
+
130
+ def build_items_page_query(limit, cursor, query_params, select)
131
+ args_parts = ["limit: #{limit}"]
132
+ args_parts << "cursor: \"#{cursor}\"" if cursor
133
+ args_parts << "query_params: #{Util.format_graphql_object(query_params)}" if query_params
134
+
135
+ args_string = "(#{args_parts.join(", ")})"
136
+ "items_page#{args_string}{cursor items{#{Util.format_select(select)}}}"
137
+ end
138
+
139
+ def build_groups_items_page_request(board_ids, group_ids, items_page_query)
140
+ board_args = { ids: board_ids.is_a?(Array) ? board_ids : [board_ids] }
141
+ group_ids_formatted = format_group_ids(group_ids)
142
+
143
+ boards_part = "boards#{Util.format_args(board_args)}"
144
+ groups_part = "groups(ids: #{group_ids_formatted})"
145
+ "query{#{boards_part}{#{groups_part}{#{items_page_query}}}}"
146
+ end
147
+
148
+ def format_group_ids(group_ids)
149
+ ids_array = group_ids.is_a?(Array) ? group_ids : [group_ids]
150
+ "[#{ids_array.map { |id| "\"#{id}\"" }.join(", ")}]"
151
+ end
86
152
  end
87
153
  end
88
154
  end
@@ -7,6 +7,7 @@ module Monday
7
7
  # Represents Monday.com's item resource.
8
8
  class Item < Base
9
9
  DEFAULT_SELECT = %w[id name created_at].freeze
10
+ DEFAULT_PAGINATED_SELECT = %w[id name].freeze
10
11
 
11
12
  # Retrieves all the items for the boards.
12
13
  #
@@ -63,6 +64,67 @@ module Monday
63
64
 
64
65
  make_request(query)
65
66
  end
67
+
68
+ # Retrieves paginated items filtered by column values.
69
+ #
70
+ # Enables searching for items based on specific column values.
71
+ # Uses cursor-based pagination for efficient data retrieval.
72
+ #
73
+ # @param board_id [Integer] The ID of the board
74
+ # @param columns [Array<Hash>, nil] Column filtering criteria (mutually exclusive with cursor)
75
+ # Each hash should contain :column_id and :column_values
76
+ # @param limit [Integer] Number of items to retrieve per page (default: 25, max: 500)
77
+ # @param cursor [String, nil] Pagination cursor for fetching next page (mutually exclusive with columns)
78
+ # @param select [Array] Fields to retrieve for each item
79
+ # @return [Monday::Response] Response containing items and cursor
80
+ #
81
+ # @example Search items by column values
82
+ # response = client.item.page_by_column_values(
83
+ # board_id: 123,
84
+ # columns: [
85
+ # { column_id: "status", column_values: ["Done", "Working on it"] },
86
+ # { column_id: "text", column_values: ["High Priority"] }
87
+ # ],
88
+ # limit: 50
89
+ # )
90
+ # items = response.dig("data", "items_page_by_column_values", "items")
91
+ # cursor = response.dig("data", "items_page_by_column_values", "cursor")
92
+ #
93
+ # @example Fetch next page using cursor
94
+ # response = client.item.page_by_column_values(
95
+ # board_id: 123,
96
+ # cursor: cursor
97
+ # )
98
+ #
99
+ # @note Supported column types: Checkbox, Country, Date, Dropdown, Email, Hour, Link,
100
+ # Long Text, Numbers, People, Phone, Status, Text, Timeline, World Clock
101
+ # @note Columns use AND logic; values within a column use ANY_OF logic
102
+ def page_by_column_values(board_id:, columns: nil, limit: 25, cursor: nil, select: DEFAULT_PAGINATED_SELECT)
103
+ query_string = build_items_page_by_column_values_query(board_id, columns, limit, cursor, select)
104
+ make_request(query_string)
105
+ end
106
+
107
+ private
108
+
109
+ def build_items_page_by_column_values_query(board_id, columns, limit, cursor, select)
110
+ args_parts = ["board_id: #{board_id}", "limit: #{limit}"]
111
+ args_parts << "cursor: \"#{cursor}\"" if cursor
112
+ args_parts << "columns: #{format_columns(columns)}" if columns
113
+
114
+ args_string = "(#{args_parts.join(", ")})"
115
+ "query{items_page_by_column_values#{args_string}{cursor items{#{Util.format_select(select)}}}}"
116
+ end
117
+
118
+ def format_columns(columns)
119
+ return nil unless columns
120
+
121
+ column_strings = columns.map do |col|
122
+ values = col[:column_values].map { |v| "\"#{v}\"" }.join(", ")
123
+ "{column_id: \"#{col[:column_id]}\", column_values: [#{values}]}"
124
+ end
125
+
126
+ "[#{column_strings.join(", ")}]"
127
+ end
66
128
  end
67
129
  end
68
130
  end
data/lib/monday/util.rb CHANGED
@@ -29,6 +29,18 @@ module Monday
29
29
  values
30
30
  end
31
31
 
32
+ # Formats a hash as a GraphQL input object (not JSON string)
33
+ # Used for complex input types like ItemsQuery
34
+ #
35
+ # input: { rules: [{ column_id: "status", compare_value: [1] }], operator: :and }
36
+ # output: { rules: [{ column_id: "status", compare_value: [1] }], operator: and }
37
+ def format_graphql_object(hash)
38
+ formatted = hash.map do |key, value|
39
+ "#{key}: #{format_graphql_value(value)}"
40
+ end.join(", ")
41
+ "{#{formatted}}"
42
+ end
43
+
32
44
  def status_code_exceptions_mapping(status_code)
33
45
  {
34
46
  "500" => InternalServerError,
@@ -43,7 +55,9 @@ module Monday
43
55
  def response_error_exceptions_mapping(error_code)
44
56
  {
45
57
  "ComplexityException" => [ComplexityError, 429],
58
+ "COMPLEXITY_BUDGET_EXHAUSTED" => [RateLimitError, 429],
46
59
  "UserUnauthorizedException" => [AuthorizationError, 403],
60
+ "USER_UNAUTHORIZED" => [AuthorizationError, 403],
47
61
  "ResourceNotFoundException" => [ResourceNotFoundError, 404],
48
62
  "InvalidUserIdException" => [InvalidRequestError, 400],
49
63
  "InvalidVersionException" => [InvalidRequestError, 400],
@@ -58,7 +72,8 @@ module Monday
58
72
  "ItemNameTooLongException" => [InvalidRequestError, 400],
59
73
  "ColumnValueException" => [InvalidRequestError, 400],
60
74
  "CorrectedValueException" => [InvalidRequestError, 400],
61
- "InvalidWorkspaceIdException" => [InvalidRequestError, 400]
75
+ "InvalidWorkspaceIdException" => [InvalidRequestError, 400],
76
+ "INTERNAL_SERVER_ERROR" => [InternalServerError, 500]
62
77
  }[error_code] || [Error, 400]
63
78
  end
64
79
 
@@ -76,10 +91,26 @@ module Monday
76
91
  end.join(" ")
77
92
  end
78
93
 
94
+ def format_graphql_value(value)
95
+ case value
96
+ when Hash
97
+ format_graphql_object(value)
98
+ when Array
99
+ "[#{value.map { |v| format_graphql_value(v) }.join(", ")}]"
100
+ when Integer, TrueClass, FalseClass
101
+ value
102
+ when String
103
+ "\"#{value}\""
104
+ else
105
+ value.to_s
106
+ end
107
+ end
108
+
79
109
  def formatted_args_value(value)
80
110
  return value if value.is_a?(Symbol)
81
111
  return value.to_json.to_json if value.is_a?(Hash)
82
112
  return value if integer?(value)
113
+ return "[#{value.map { |v| formatted_args_value(v) }.join(", ")}]" if value.is_a?(Array)
83
114
 
84
115
  "\"#{value}\""
85
116
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Monday
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/monday_ruby.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "monday/client"
4
+ require_relative "monday/deprecation"
4
5
  require_relative "monday/version"
5
6
 
6
7
  # Module to configure the library globally
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monday_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sanif Himani
8
8
  - Wes Hays
9
- autorequire:
10
9
  bindir: exe
11
10
  cert_chain: []
12
- date: 2024-07-30 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: base64
@@ -17,14 +16,14 @@ dependencies:
17
16
  requirements:
18
17
  - - "~>"
19
18
  - !ruby/object:Gem::Version
20
- version: 0.2.0
19
+ version: 0.3.0
21
20
  type: :runtime
22
21
  prerelease: false
23
22
  version_requirements: !ruby/object:Gem::Requirement
24
23
  requirements:
25
24
  - - "~>"
26
25
  - !ruby/object:Gem::Version
27
- version: 0.2.0
26
+ version: 0.3.0
28
27
  description: A Gem to easily interact with monday.com API using native Ruby
29
28
  email:
30
29
  - sanifhimani92@gmail.com
@@ -46,6 +45,7 @@ files:
46
45
  - Rakefile
47
46
  - lib/monday/client.rb
48
47
  - lib/monday/configuration.rb
48
+ - lib/monday/deprecation.rb
49
49
  - lib/monday/error.rb
50
50
  - lib/monday/request.rb
51
51
  - lib/monday/resources.rb
@@ -55,6 +55,7 @@ files:
55
55
  - lib/monday/resources/board.rb
56
56
  - lib/monday/resources/board_view.rb
57
57
  - lib/monday/resources/column.rb
58
+ - lib/monday/resources/folder.rb
58
59
  - lib/monday/resources/group.rb
59
60
  - lib/monday/resources/item.rb
60
61
  - lib/monday/resources/subitem.rb
@@ -64,16 +65,14 @@ files:
64
65
  - lib/monday/util.rb
65
66
  - lib/monday/version.rb
66
67
  - lib/monday_ruby.rb
67
- - monday_ruby.gemspec
68
68
  homepage: https://github.com/sanifhimani/monday_ruby
69
69
  licenses:
70
70
  - MIT
71
71
  metadata:
72
72
  homepage_uri: https://github.com/sanifhimani/monday_ruby
73
73
  documentation_uri: https://monday-ruby.gitbook.io/docs/
74
- changelog_uri: https://github.com/sanifhimani/monday_ruby/blob/v1.0.0/CHANGELOG.md
74
+ changelog_uri: https://github.com/sanifhimani/monday_ruby/blob/v1.1.0/CHANGELOG.md
75
75
  rubygems_mfa_required: 'true'
76
- post_install_message:
77
76
  rdoc_options: []
78
77
  require_paths:
79
78
  - lib
@@ -88,8 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
87
  - !ruby/object:Gem::Version
89
88
  version: '0'
90
89
  requirements: []
91
- rubygems_version: 3.5.6
92
- signing_key:
90
+ rubygems_version: 3.6.9
93
91
  specification_version: 4
94
92
  summary: Ruby bindings to use the monday.com API
95
93
  test_files: []
data/monday_ruby.gemspec DELETED
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/monday/version"
4
-
5
- version = Monday::VERSION
6
- repository = "https://github.com/sanifhimani/monday_ruby"
7
-
8
- Gem::Specification.new do |spec|
9
- spec.name = "monday_ruby"
10
- spec.version = version
11
- spec.authors = ["Sanif Himani", "Wes Hays"]
12
- spec.email = ["sanifhimani92@gmail.com", "weshays@gmail.com"]
13
-
14
- spec.summary = "Ruby bindings to use the monday.com API"
15
- spec.description = "A Gem to easily interact with monday.com API using native Ruby"
16
- spec.homepage = repository
17
- spec.license = "MIT"
18
- spec.required_ruby_version = ">= 2.7.0"
19
-
20
- spec.metadata = {
21
- "homepage_uri" => spec.homepage,
22
- "documentation_uri" => "https://monday-ruby.gitbook.io/docs/",
23
- "changelog_uri" => "#{repository}/blob/v#{version}/CHANGELOG.md",
24
- "rubygems_mfa_required" => "true"
25
- }
26
-
27
- spec.files = Dir.chdir(__dir__) do
28
- `git ls-files -z`.split("\x0").reject do |f|
29
- (File.expand_path(f) == __FILE__) ||
30
- f.start_with?(*%w[bin/ test/ spec/ features/ .git Gemfile])
31
- end
32
- end
33
-
34
- spec.bindir = "exe"
35
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
36
- spec.require_paths = ["lib"]
37
-
38
- spec.add_dependency "base64", "~> 0.2.0"
39
- end