monday_ruby 1.0.0 → 1.2.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -1
  3. data/.rspec +0 -1
  4. data/.rubocop.yml +19 -0
  5. data/.simplecov +1 -0
  6. data/CHANGELOG.md +49 -0
  7. data/CONTRIBUTING.md +165 -0
  8. data/README.md +167 -88
  9. data/docs/.vitepress/config.mjs +255 -0
  10. data/docs/.vitepress/theme/index.js +4 -0
  11. data/docs/.vitepress/theme/style.css +43 -0
  12. data/docs/README.md +80 -0
  13. data/docs/explanation/architecture.md +507 -0
  14. data/docs/explanation/best-practices/errors.md +478 -0
  15. data/docs/explanation/best-practices/performance.md +1084 -0
  16. data/docs/explanation/best-practices/rate-limiting.md +630 -0
  17. data/docs/explanation/best-practices/testing.md +820 -0
  18. data/docs/explanation/column-values.md +857 -0
  19. data/docs/explanation/design.md +795 -0
  20. data/docs/explanation/graphql.md +356 -0
  21. data/docs/explanation/migration/v1.md +808 -0
  22. data/docs/explanation/pagination.md +447 -0
  23. data/docs/guides/advanced/batch.md +1274 -0
  24. data/docs/guides/advanced/complex-queries.md +1114 -0
  25. data/docs/guides/advanced/errors.md +818 -0
  26. data/docs/guides/advanced/pagination.md +934 -0
  27. data/docs/guides/advanced/rate-limiting.md +981 -0
  28. data/docs/guides/authentication.md +286 -0
  29. data/docs/guides/boards/create.md +386 -0
  30. data/docs/guides/boards/delete.md +405 -0
  31. data/docs/guides/boards/duplicate.md +511 -0
  32. data/docs/guides/boards/query.md +530 -0
  33. data/docs/guides/boards/update.md +453 -0
  34. data/docs/guides/columns/create.md +452 -0
  35. data/docs/guides/columns/metadata.md +492 -0
  36. data/docs/guides/columns/query.md +455 -0
  37. data/docs/guides/columns/update-multiple.md +459 -0
  38. data/docs/guides/columns/update-values.md +509 -0
  39. data/docs/guides/files/add-to-column.md +40 -0
  40. data/docs/guides/files/add-to-update.md +37 -0
  41. data/docs/guides/files/clear-column.md +33 -0
  42. data/docs/guides/first-request.md +285 -0
  43. data/docs/guides/folders/manage.md +750 -0
  44. data/docs/guides/groups/items.md +626 -0
  45. data/docs/guides/groups/manage.md +501 -0
  46. data/docs/guides/installation.md +169 -0
  47. data/docs/guides/items/create.md +493 -0
  48. data/docs/guides/items/delete.md +514 -0
  49. data/docs/guides/items/query.md +605 -0
  50. data/docs/guides/items/subitems.md +483 -0
  51. data/docs/guides/items/update.md +699 -0
  52. data/docs/guides/updates/manage.md +619 -0
  53. data/docs/guides/use-cases/dashboard.md +1421 -0
  54. data/docs/guides/use-cases/import.md +1962 -0
  55. data/docs/guides/use-cases/task-management.md +1381 -0
  56. data/docs/guides/workspaces/manage.md +502 -0
  57. data/docs/index.md +69 -0
  58. data/docs/package-lock.json +2468 -0
  59. data/docs/package.json +13 -0
  60. data/docs/reference/client.md +540 -0
  61. data/docs/reference/configuration.md +586 -0
  62. data/docs/reference/errors.md +693 -0
  63. data/docs/reference/resources/account.md +208 -0
  64. data/docs/reference/resources/activity-log.md +369 -0
  65. data/docs/reference/resources/board-view.md +359 -0
  66. data/docs/reference/resources/board.md +393 -0
  67. data/docs/reference/resources/column.md +543 -0
  68. data/docs/reference/resources/file.md +236 -0
  69. data/docs/reference/resources/folder.md +386 -0
  70. data/docs/reference/resources/group.md +507 -0
  71. data/docs/reference/resources/item.md +348 -0
  72. data/docs/reference/resources/subitem.md +267 -0
  73. data/docs/reference/resources/update.md +259 -0
  74. data/docs/reference/resources/workspace.md +213 -0
  75. data/docs/reference/response.md +560 -0
  76. data/docs/tutorial/first-integration.md +713 -0
  77. data/lib/monday/client.rb +41 -2
  78. data/lib/monday/configuration.rb +13 -0
  79. data/lib/monday/deprecation.rb +23 -0
  80. data/lib/monday/error.rb +5 -2
  81. data/lib/monday/request.rb +19 -1
  82. data/lib/monday/resources/base.rb +4 -0
  83. data/lib/monday/resources/board.rb +52 -0
  84. data/lib/monday/resources/column.rb +6 -0
  85. data/lib/monday/resources/file.rb +56 -0
  86. data/lib/monday/resources/folder.rb +55 -0
  87. data/lib/monday/resources/group.rb +66 -0
  88. data/lib/monday/resources/item.rb +62 -0
  89. data/lib/monday/util.rb +33 -1
  90. data/lib/monday/version.rb +1 -1
  91. data/lib/monday_ruby.rb +1 -0
  92. metadata +92 -11
  93. data/monday_ruby.gemspec +0 -39
data/lib/monday/client.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "uri"
4
4
  require "net/http"
5
+ require "net/http/post/multipart"
5
6
  require "json"
6
7
 
7
8
  require_relative "configuration"
@@ -26,7 +27,25 @@ module Monday
26
27
  end
27
28
 
28
29
  def make_request(body)
29
- response = Request.post(uri, body, request_headers)
30
+ response = Request.post(
31
+ uri,
32
+ body,
33
+ request_headers,
34
+ open_timeout: @config.open_timeout,
35
+ read_timeout: @config.read_timeout
36
+ )
37
+
38
+ handle_response(Response.new(response))
39
+ end
40
+
41
+ def make_file_request(query, variables)
42
+ response = Request.post_multipart(
43
+ files_uri,
44
+ { query: query, variables: variables },
45
+ request_multipart_headers,
46
+ open_timeout: @config.open_timeout,
47
+ read_timeout: @config.read_timeout
48
+ )
30
49
 
31
50
  handle_response(Response.new(response))
32
51
  end
@@ -43,6 +62,10 @@ module Monday
43
62
  URI(@config.host)
44
63
  end
45
64
 
65
+ def files_uri
66
+ URI(@config.files_host)
67
+ end
68
+
46
69
  def request_headers
47
70
  {
48
71
  "Content-Type": "application/json",
@@ -50,6 +73,13 @@ module Monday
50
73
  }
51
74
  end
52
75
 
76
+ def request_multipart_headers
77
+ {
78
+ "Content-Type": "multipart/form-data",
79
+ Authorization: @config.token
80
+ }
81
+ end
82
+
53
83
  def handle_response(response)
54
84
  return response if response.success?
55
85
 
@@ -63,7 +93,7 @@ module Monday
63
93
  end
64
94
 
65
95
  def response_exception(response)
66
- error_code = response.body["error_code"]
96
+ error_code = response_error_code(response)
67
97
 
68
98
  return Error.new(response: response) if error_code.nil?
69
99
 
@@ -71,6 +101,15 @@ module Monday
71
101
  exception_klass.new(message: error_code, response: response, code: code)
72
102
  end
73
103
 
104
+ def response_error_code(response)
105
+ error_code = response.body["error_code"]
106
+ return error_code unless error_code.nil?
107
+
108
+ return unless response.body["errors"].is_a?(Array) && !response.body["errors"].empty?
109
+
110
+ response.body.dig("errors", 0, "extensions", "code") || response.body.dig("errors", 0, "extensions", "error_code")
111
+ end
112
+
74
113
  def default_exception(response)
75
114
  Util.status_code_exceptions_mapping(response.status).new(response: response)
76
115
  end
@@ -7,15 +7,22 @@ module Monday
7
7
  #
8
8
  # token: used to authenticate the requests
9
9
  # host: defaults to https://api.monday.com/v2
10
+ # files_host: defaults to https://api.monday.com/v2/files
10
11
  class Configuration
11
12
  DEFAULT_HOST = "https://api.monday.com/v2"
13
+ DEFAULT_FILES_HOST = "#{DEFAULT_HOST}/file"
12
14
  DEFAULT_TOKEN = nil
13
15
  DEFAULT_VERSION = "2023-07"
16
+ DEFAULT_OPEN_TIMEOUT = 10
17
+ DEFAULT_READ_TIMEOUT = 30
14
18
 
15
19
  CONFIGURATION_FIELDS = %i[
16
20
  token
17
21
  host
22
+ files_host
18
23
  version
24
+ open_timeout
25
+ read_timeout
19
26
  ].freeze
20
27
 
21
28
  attr_accessor(*CONFIGURATION_FIELDS)
@@ -25,8 +32,11 @@ module Monday
25
32
  raise ArgumentError, "Unknown arguments: #{invalid_keys}" unless invalid_keys.empty?
26
33
 
27
34
  @host = DEFAULT_HOST
35
+ @files_host = DEFAULT_FILES_HOST
28
36
  @token = DEFAULT_TOKEN
29
37
  @version = DEFAULT_VERSION
38
+ @open_timeout = DEFAULT_OPEN_TIMEOUT
39
+ @read_timeout = DEFAULT_READ_TIMEOUT
30
40
 
31
41
  config_args.each do |key, value|
32
42
  public_send("#{key}=", value)
@@ -36,7 +46,10 @@ module Monday
36
46
  def reset
37
47
  @token = DEFAULT_TOKEN
38
48
  @host = DEFAULT_HOST
49
+ @files_host = DEFAULT_FILES_HOST
39
50
  @version = DEFAULT_VERSION
51
+ @open_timeout = DEFAULT_OPEN_TIMEOUT
52
+ @read_timeout = DEFAULT_READ_TIMEOUT
40
53
  end
41
54
  end
42
55
  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 = {
@@ -15,5 +18,20 @@ module Monday
15
18
 
16
19
  http.request(request)
17
20
  end
21
+
22
+ def self.post_multipart(uri, body, headers, open_timeout: 10, read_timeout: 30)
23
+ http = Net::HTTP.new(uri.host, uri.port)
24
+ http.use_ssl = true
25
+ http.open_timeout = open_timeout
26
+ http.read_timeout = read_timeout
27
+
28
+ params = {
29
+ "query" => body[:query],
30
+ "variables[file]" => body[:variables][:file]
31
+ }
32
+
33
+ request = Net::HTTP::Post::Multipart.new(uri.request_uri, params, headers)
34
+ http.request(request)
35
+ end
18
36
  end
19
37
  end
@@ -12,6 +12,10 @@ module Monday
12
12
 
13
13
  protected
14
14
 
15
+ def make_file_request(query, variables)
16
+ client.make_file_request(query, variables)
17
+ end
18
+
15
19
  def make_request(query)
16
20
  client.make_request(query)
17
21
  end
@@ -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,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Monday
4
+ module Resources
5
+ # Represents Monday.com's file asset resource.
6
+ class File < Base
7
+ DEFAULT_SELECT = %w[id].freeze
8
+
9
+ # Adds a file to a file type column for an item.
10
+ #
11
+ # Allows customizing the column update using the args option.
12
+ # Allows customizing the values to retrieve using the select option.
13
+ # By default, The ID is retrieved.
14
+ def add_file_to_column(args: {}, select: DEFAULT_SELECT)
15
+ cloned_args = args.clone
16
+ variables = { file: cloned_args.delete(:file) }
17
+ cloned_args.merge!(file: "$file")
18
+ query = <<~QUERY
19
+ mutation add_file($file: File!) {
20
+ add_file_to_column#{Util.format_args(cloned_args)} {#{Util.format_select(select)}}
21
+ }
22
+ QUERY
23
+ make_file_request(query, variables)
24
+ end
25
+
26
+ # Adds a file to an update for an item.
27
+ #
28
+ # Allows customizing the update creation using the args option.
29
+ # Allows customizing the values to retrieve using the select option.
30
+ # By default, The ID is retrieved.
31
+ def add_file_to_update(args: {}, select: DEFAULT_SELECT)
32
+ cloned_args = args.clone
33
+ variables = { file: cloned_args.delete(:file) }
34
+ cloned_args.merge!(file: "$file")
35
+ query = <<~QUERY
36
+ mutation ($file: File!) {
37
+ add_file_to_update#{Util.format_args(cloned_args)} {#{Util.format_select(select)}}
38
+ }
39
+ QUERY
40
+ make_file_request(query, variables)
41
+ end
42
+
43
+ # Clear an item's files column. This is a helper method for files
44
+ # and you could also use the column.change_value to clear the column as well.
45
+ #
46
+ # Allows customizing the update creation using the args option.
47
+ # Allows customizing the values to retrieve using the select option.
48
+ # By default, ID is retrieved.
49
+ def clear_file_column(args: {}, select: DEFAULT_SELECT)
50
+ merged_args = args.merge(value: '{\"clear_all\": true}')
51
+ query = "mutation { change_column_value#{Util.format_args(merged_args)} {#{Util.format_select(select)}}}"
52
+ make_request(query)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -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,27 @@ 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)
111
+ return value if value.to_s.include?("$") # GraphQL query variable
81
112
  return value.to_json.to_json if value.is_a?(Hash)
82
113
  return value if integer?(value)
114
+ return "[#{value.map { |v| formatted_args_value(v) }.join(", ")}]" if value.is_a?(Array)
83
115
 
84
116
  "\"#{value}\""
85
117
  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.2.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