notion 1.0.6 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04355fc0473c8f446e79ec4904105c0fb4fcb8a436032099e59d41ce6abbc9fc
4
- data.tar.gz: df7cf2d629fcd8c9dcf076a3186c2d49deef0f73454c051a2ad5530ffa98501d
3
+ metadata.gz: a6dfa3bb58bd4850c061ed6cf0efbb11ad3548f9dbcfb773b04fdb36d99cf356
4
+ data.tar.gz: 1be5e4c8dc4975fc2de62bc802b296b283cff406a943d3f5fa88980ecfa07b65
5
5
  SHA512:
6
- metadata.gz: 2359d17e2408b50b04a3ae1c9b4015de93b4e7d92b06a5b64d6cc8af48380e2affa7e4eb17d20e35def404116bdecef838bd9ec19c9c39867c05116de02eca40
7
- data.tar.gz: febb7a95cc0bd70c59e921a6479b4e4f7c80b6ad1443366aef0658358877876a04b37f9993827e15fe6d9eb0386efb7fa082063b0e6758e8ddeead1bb9dd5aa2
6
+ metadata.gz: 6291929f81e39755067265260f3ff94d4e691f420713af7cb7a1b7505a9ab097bdd78720bcc2d344df555cc20b106203aae0bd09eea52a041e8c4a8057b9b4a8
7
+ data.tar.gz: de33fa730ee063bb6e54877ce959e1ca7f8bde1345ae90db9c1a368e8c08ad37dec54f473d99c38c6bc26f3e97e17741bba92e3b09cd1d6cb64b5fdce629723b
data/README.md CHANGED
@@ -310,8 +310,12 @@ From here, you can instantiate the Notion Client with the following code:
310
310
  )
311
311
  ```
312
312
  ### Retrieve a full-page Collection View
313
- Currently, either a "normal" Page URL or the Page Block ID is accepted to the `get_page` method. Therefore, if you pass the full URL to the CV Table, it will raise an error:
314
- ```text
315
- the URL or ID passed to the get_page method must be that of a Page Block.
313
+ A full-page collection view must have a URL that follows the below pattern:
314
+ https://www.notion.so/danmurphy/[page-id]?v=[view-id]
315
+ Then, it can be retrieved with the following code:
316
+ ```ruby
317
+ >>> @client = NotionAPI::Client.new(
318
+ "<insert_v2_token_here>"
319
+ )
320
+ >>> @client.get_page("https://www.notion.so/danmurphy/[page-id]?v=[view-id]")
316
321
  ```
317
- To avoid this, you must pass only the ID of the full-page collection-view to the `get_page` method. This is next up on the features list, so passing the full URL will be supported soon:smile:
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'utils'
4
- require 'httparty'
3
+ require_relative "utils"
4
+ require "httparty"
5
5
 
6
6
  module NotionAPI
7
7
  # the initial methods available to an instantiated Cloent object are defined
8
8
  class Core
9
9
  include Utils
10
- @options = { 'cookies' => { :token_v2 => nil, 'x-active-user-header' => nil }, 'headers' => { 'Content-Type' => 'application/json' } }
11
- @type_whitelist = 'divider'
10
+ @options = { "cookies" => { :token_v2 => nil, "x-active-user-header" => nil }, "headers" => { "Content-Type" => "application/json" } }
11
+ @type_whitelist = "divider"
12
12
 
13
13
  class << self
14
14
  attr_reader :options, :type_whitelist, :token_v2, :active_user_header
@@ -30,39 +30,16 @@ module NotionAPI
30
30
  pageId: clean_id,
31
31
  chunkNumber: 0,
32
32
  limit: 100,
33
- verticalColumns: false
33
+ verticalColumns: false,
34
34
  }
35
- jsonified_record_response = get_all_block_info(clean_id, request_body)
36
- i = 0
37
- while jsonified_record_response.empty? || jsonified_record_response['block'].empty?
38
- return {} if i >= 10
35
+ jsonified_record_response = get_all_block_info(request_body)
39
36
 
40
- jsonified_record_response = get_all_block_info(clean_id, request_body)
41
- i += 1
42
- end
43
-
44
- block_id = clean_id
45
37
  block_type = extract_type(clean_id, jsonified_record_response)
46
38
  block_parent_id = extract_parent_id(clean_id, jsonified_record_response)
47
39
 
48
- raise 'the URL or ID passed to the get_page method must be that of a Page Block.' if !['collection_view_page', 'page'].include?(block_type)
49
-
50
- if block_type == "page"
51
- block_title = extract_title(clean_id, jsonified_record_response)
52
- PageBlock.new(block_id, block_title, block_parent_id)
53
- elsif block_type == "collection_view_page"
54
- collection_id = extract_collection_id(block_id, jsonified_record_response)
55
- block_title = extract_collection_title(clean_id, collection_id, jsonified_record_response)
56
- view_id = extract_view_ids(block_id, jsonified_record_response)[0]
57
- schema = extract_collection_schema(collection_id, view_id, jsonified_record_response)
58
- column_mappings = schema.keys
59
- column_names = column_mappings.map { |mapping| schema[mapping]['name']}
60
-
61
- collection_view_page = CollectionViewPage.new(block_id, block_title, block_parent_id, collection_id, view_id)
62
- collection_view_page.instance_variable_set(:@column_names, column_names)
63
- CollectionView.class_eval{attr_reader :column_names}
64
- collection_view_page
65
- end
40
+ raise ArgumentError, "the URL or ID passed to the get_page method must be that of a Page Block." if !["collection_view_page", "page"].include?(block_type)
41
+
42
+ get_instantiated_instance_for(block_type, clean_id, block_parent_id, jsonified_record_response)
66
43
  end
67
44
 
68
45
  def children(url_or_id = @id)
@@ -87,18 +64,12 @@ module NotionAPI
87
64
  pageId: clean_id,
88
65
  chunkNumber: 0,
89
66
  limit: 100,
90
- verticalColumns: false
67
+ verticalColumns: false,
91
68
  }
92
- jsonified_record_response = get_all_block_info(clean_id, request_body)
93
- i = 0
94
- while jsonified_record_response.empty?
95
- return {} if i >= 10
69
+ jsonified_record_response = get_all_block_info(request_body)
96
70
 
97
- jsonified_record_response = get_all_block_info(clean_id, request_body)
98
- i += 1
99
- end
100
-
101
- jsonified_record_response['block'][clean_id]['value']['content'] || []
71
+ # if no content, returns empty list
72
+ jsonified_record_response["block"][clean_id]["value"]["content"] || []
102
73
  end
103
74
 
104
75
  private
@@ -106,19 +77,19 @@ module NotionAPI
106
77
  def get_notion_id(body)
107
78
  # ! retrieves a users ID from the headers of a Notion response object.
108
79
  # ! body -> the body to send in the request : ``Hash``
109
- Core.options['cookies'][:token_v2] = @@token_v2
110
- Core.options['headers']['x-notion-active-user-header'] = @@active_user_header
111
- cookies = Core.options['cookies']
112
- headers = Core.options['headers']
80
+ Core.options["cookies"][:token_v2] = @@token_v2
81
+ Core.options["headers"]["x-notion-active-user-header"] = @@active_user_header
82
+ cookies = Core.options["cookies"]
83
+ headers = Core.options["headers"]
113
84
  request_url = URLS[:GET_BLOCK]
114
85
 
115
86
  response = HTTParty.post(
116
87
  request_url,
117
88
  body: body.to_json,
118
89
  cookies: cookies,
119
- headers: headers
90
+ headers: headers,
120
91
  )
121
- response.headers['x-notion-user-id']
92
+ response.headers["x-notion-user-id"]
122
93
  end
123
94
 
124
95
  def get_last_page_block_id(url_or_id)
@@ -132,48 +103,53 @@ module NotionAPI
132
103
  pageId: clean_id,
133
104
  chunkNumber: 0,
134
105
  limit: 100,
135
- verticalColumns: false
106
+ verticalColumns: false,
136
107
  }
137
- jsonified_record_response = get_all_block_info(clean_id, request_body)
138
- i = 0
139
- while jsonified_record_response.empty?
140
- return {:properties => {title: [[block_title]]}, :format => {}} if i >= 10
108
+ jsonified_record_response = get_all_block_info(request_body)
141
109
 
142
- jsonified_record_response = get_all_block_info(clean_id, request_body)
143
- i += 1
144
- end
145
- properties = jsonified_record_response['block'][clean_id]['value']['properties']
146
- formats = jsonified_record_response['block'][clean_id]['value']['format']
110
+ properties = jsonified_record_response["block"][clean_id]["value"]["properties"]
111
+ formats = jsonified_record_response["block"][clean_id]["value"]["format"]
147
112
  return {
148
- :properties => properties,
149
- :format => formats
150
- }
113
+ :properties => properties,
114
+ :format => formats,
115
+ }
151
116
  end
152
117
 
153
- def get_all_block_info(_clean_id, body)
118
+ def get_all_block_info(body, i = 0)
154
119
  # ! retrieves all info pertaining to a block Id.
155
120
  # ! clean_id -> the block ID or URL cleaned : ``str``
156
- Core.options['cookies'][:token_v2] = @@token_v2
157
- Core.options['headers']['x-notion-active-user-header'] = @@active_user_header
158
- cookies = Core.options['cookies']
159
- headers = Core.options['headers']
160
-
121
+ Core.options["cookies"][:token_v2] = @@token_v2
122
+ Core.options["headers"]["x-notion-active-user-header"] = @@active_user_header
123
+ cookies = Core.options["cookies"]
124
+ headers = Core.options["headers"]
161
125
  request_url = URLS[:GET_BLOCK]
162
126
 
163
127
  response = HTTParty.post(
164
128
  request_url,
165
129
  body: body.to_json,
166
130
  cookies: cookies,
167
- headers: headers
131
+ headers: headers,
168
132
  )
169
133
 
170
- JSON.parse(response.body)['recordMap']
134
+ jsonified_record_response = JSON.parse(response.body)["recordMap"]
135
+ response_invalid = (!jsonified_record_response || jsonified_record_response.empty? || jsonified_record_response["block"].empty?)
136
+
137
+ if i < 10 && response_invalid
138
+ i = i + 1
139
+ return get_all_block_info(body, i)
140
+ else
141
+ if i == 10 && response_invalid
142
+ raise InvalidClientInstantiationError, "Attempted to retrieve block 10 times and received an empty response each time. Please make sure you have a valid token_v2 value set. If you do, then try setting the 'active_user_header' variable as well."
143
+ else
144
+ return jsonified_record_response
145
+ end
146
+ end
171
147
  end
172
148
 
173
149
  def filter_nil_blocks(jsonified_record_response)
174
150
  # ! removes any blocks that are empty [i.e. have no title / content]
175
151
  # ! jsonified_record_responses -> parsed JSON representation of a notion response object : ``Json``
176
- jsonified_record_response.empty? || jsonified_record_response['block'].empty? ? nil : jsonified_record_response['block']
152
+ jsonified_record_response.empty? || jsonified_record_response["block"].empty? ? nil : jsonified_record_response["block"]
177
153
  end
178
154
 
179
155
  def extract_title(clean_id, jsonified_record_response)
@@ -181,14 +157,13 @@ module NotionAPI
181
157
  # ! clean_id -> the cleaned block ID: ``str``
182
158
  # ! jsonified_record_response -> parsed JSON representation of a notion response object : ``Json``
183
159
  filter_nil_blocks = filter_nil_blocks(jsonified_record_response)
184
- if filter_nil_blocks.nil? || filter_nil_blocks[clean_id].nil? || filter_nil_blocks[clean_id]['value']['properties'].nil?
160
+ if filter_nil_blocks.nil? || filter_nil_blocks[clean_id].nil? || filter_nil_blocks[clean_id]["value"]["properties"].nil?
185
161
  nil
186
162
  else
187
163
  # titles for images are called source, while titles for text-based blocks are called title, so lets dynamically grab it
188
164
  # https://stackoverflow.com/questions/23765996/get-all-keys-from-ruby-hash/23766007
189
- title_value = filter_nil_blocks[clean_id]['value']['properties'].keys[0]
190
- Core.type_whitelist.include?(filter_nil_blocks[clean_id]['value']['type']) ? nil : jsonified_record_response['block'][clean_id]['value']['properties'][title_value].flatten[0]
191
-
165
+ title_value = filter_nil_blocks[clean_id]["value"]["properties"].keys[0]
166
+ Core.type_whitelist.include?(filter_nil_blocks[clean_id]["value"]["type"]) ? nil : jsonified_record_response["block"][clean_id]["value"]["properties"][title_value].flatten[0]
192
167
  end
193
168
  end
194
169
 
@@ -197,7 +172,7 @@ module NotionAPI
197
172
  # ! clean_id -> the cleaned block ID: ``str``
198
173
  # ! collection_id -> the collection ID: ``str``
199
174
  # ! jsonified_record_response -> parsed JSON representation of a notion response object : ``Json``
200
- jsonified_record_response['collection'][collection_id]['value']['name'].flatten.join if jsonified_record_response['collection'] and jsonified_record_response['collection'][collection_id]['value']['name']
175
+ jsonified_record_response["collection"][collection_id]["value"]["name"].flatten.join if jsonified_record_response["collection"] and jsonified_record_response["collection"][collection_id]["value"]["name"]
201
176
  end
202
177
 
203
178
  def extract_type(clean_id, jsonified_record_response)
@@ -208,8 +183,7 @@ module NotionAPI
208
183
  if filter_nil_blocks.nil?
209
184
  nil
210
185
  else
211
- filter_nil_blocks[clean_id]['value']['type']
212
-
186
+ filter_nil_blocks[clean_id]["value"]["type"]
213
187
  end
214
188
  end
215
189
 
@@ -217,37 +191,46 @@ module NotionAPI
217
191
  # ! extract parent ID from core JSON response object.
218
192
  # ! clean_id -> the block ID or URL cleaned : ``str``
219
193
  # ! jsonified_record_response -> parsed JSON representation of a notion response object : ``Json``
220
- jsonified_record_response.empty? || jsonified_record_response['block'].empty? ? {} : jsonified_record_response['block'][clean_id]['value']['parent_id']
194
+ jsonified_record_response.empty? || jsonified_record_response["block"].empty? ? {} : jsonified_record_response["block"][clean_id]["value"]["parent_id"]
221
195
  end
222
196
 
223
197
  def extract_collection_id(clean_id, jsonified_record_response)
224
198
  # ! extract the collection ID
225
199
  # ! clean_id -> the block ID or URL cleaned : ``str``
226
200
  # ! jsonified_record_response -> parsed JSON representation of a notion response object : ``Json``
227
- jsonified_record_response['block'][clean_id]['value']['collection_id']
201
+ jsonified_record_response["block"][clean_id]["value"]["collection_id"]
228
202
  end
229
203
 
230
204
  def extract_view_ids(clean_id, jsonified_record_response)
231
- jsonified_record_response['block'][clean_id]['value']['view_ids'] || []
205
+ jsonified_record_response["block"][clean_id]["value"]["view_ids"] || []
232
206
  end
233
-
207
+
234
208
  def extract_id(url_or_id)
235
209
  # ! parse and clean the URL or ID object provided.
236
210
  # ! url_or_id -> the block ID or URL : ``str``
237
211
  http_or_https = url_or_id.match(/^(http|https)/) # true if http or https in url_or_id...
238
- if (url_or_id.length == 36) && ((url_or_id.split('-').length == 5) && !http_or_https)
212
+ collection_view_match = url_or_id.match(/(\?v=)/)
213
+
214
+ if (url_or_id.length == 36) && ((url_or_id.split("-").length == 5) && !http_or_https)
239
215
  # passes if url_or_id is perfectly formatted already...
240
216
  url_or_id
241
- elsif (http_or_https && (url_or_id.split('-').last.length == 32)) || (!http_or_https && (url_or_id.length == 32))
217
+ elsif (http_or_https && (url_or_id.split("-").last.length == 32)) || (!http_or_https && (url_or_id.length == 32)) || (collection_view_match)
242
218
  # passes if either:
243
219
  # 1. a URL is passed as url_or_id and the ID at the end is 32 characters long or
244
220
  # 2. a URL is not passed and the ID length is 32 [aka unformatted]
245
221
  pattern = [8, 13, 18, 23]
246
- id = url_or_id.split('-').last
247
- pattern.each { |index| id.insert(index, '-') }
248
- id
222
+ if collection_view_match
223
+ id_without_view = url_or_id.split("?")[0]
224
+ clean_id = id_without_view.split("/").last
225
+ pattern.each { |index| clean_id.insert(index, "-") }
226
+ clean_id
227
+ else
228
+ id = url_or_id.split("-").last
229
+ pattern.each { |index| id.insert(index, "-") }
230
+ id
231
+ end
249
232
  else
250
- raise ArgumentError, 'Expected a Notion page URL or a page ID. Please consult the documentation for further information.'
233
+ raise ArgumentError, "Expected a Notion page URL or a page ID. Please consult the documentation for further information."
251
234
  end
252
235
  end
253
236
 
@@ -255,42 +238,88 @@ module NotionAPI
255
238
  # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
256
239
  # ! collection_id -> the collection ID : ``str``
257
240
  # ! view_id -> the view ID : ``str``
258
- cookies = Core.options['cookies']
259
- headers = Core.options['headers']
241
+ cookies = Core.options["cookies"]
242
+ headers = Core.options["headers"]
260
243
 
261
244
  if response.empty?
262
- query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
245
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, "")
263
246
 
264
247
  request_url = URLS[:GET_COLLECTION]
265
248
  response = HTTParty.post(
266
249
  request_url,
267
250
  body: query_collection_hash.to_json,
268
251
  cookies: cookies,
269
- headers: headers
252
+ headers: headers,
270
253
  )
271
- response['recordMap']['collection'][collection_id]['value']['schema']
254
+ response["recordMap"]["collection"][collection_id]["value"]["schema"]
272
255
  else
273
- response['collection'][collection_id]['value']['schema']
256
+ response["collection"][collection_id]["value"]["schema"]
274
257
  end
275
258
  end
276
-
259
+
277
260
  def extract_collection_data(collection_id, view_id)
278
261
  # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
279
262
  # ! collection_id -> the collection ID : ``str``
280
263
  # ! view_id -> the view ID : ``str``
281
- cookies = Core.options['cookies']
282
- headers = Core.options['headers']
264
+ cookies = Core.options["cookies"]
265
+ headers = Core.options["headers"]
283
266
 
284
- query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
267
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, "")
285
268
 
286
269
  request_url = URLS[:GET_COLLECTION]
287
270
  response = HTTParty.post(
288
271
  request_url,
289
272
  body: query_collection_hash.to_json,
290
273
  cookies: cookies,
291
- headers: headers
274
+ headers: headers,
292
275
  )
293
- response['recordMap']
276
+ response["recordMap"]
277
+ end
278
+
279
+ def extract_page_information(page_meta = {})
280
+ # ! helper method for extracting information about a page block
281
+ # ! page_meta -> hash containing data points useful for the extraction of a page blocks information.
282
+ # ! This should include clean_id, jsonified_record_response, and parent_id
283
+ clean_id = page_meta.fetch(:clean_id)
284
+ jsonified_record_response = page_meta.fetch(:jsonified_record_response)
285
+ block_parent_id = page_meta.fetch(:parent_id)
286
+
287
+ block_title = extract_title(clean_id, jsonified_record_response)
288
+ PageBlock.new(clean_id, block_title, block_parent_id)
289
+ end
290
+
291
+ def extract_collection_view_page_information(page_meta = {})
292
+ # ! helper method for extracting information about a Collection View page block
293
+ # ! page_meta -> hash containing data points useful for the extraction of a page blocks information.
294
+ # ! This should include clean_id, jsonified_record_response, and parent_id
295
+ clean_id = page_meta.fetch(:clean_id)
296
+ jsonified_record_response = page_meta.fetch(:jsonified_record_response)
297
+ block_parent_id = page_meta.fetch(:parent_id)
298
+
299
+ collection_id = extract_collection_id(clean_id, jsonified_record_response)
300
+ block_title = extract_collection_title(clean_id, collection_id, jsonified_record_response)
301
+ view_id = extract_view_ids(clean_id, jsonified_record_response)[0]
302
+ schema = extract_collection_schema(collection_id, view_id, jsonified_record_response)
303
+ column_names = NotionAPI::CollectionView.extract_collection_view_column_names(schema)
304
+
305
+ collection_view_page = CollectionViewPage.new(clean_id, block_title, block_parent_id, collection_id, view_id)
306
+ collection_view_page.instance_variable_set(:@column_names, column_names)
307
+ CollectionView.class_eval { attr_reader :column_names }
308
+ collection_view_page
309
+ end
310
+
311
+ def get_instantiated_instance_for(block_type, clean_id, parent_id, jsonified_record_response)
312
+ case block_type
313
+ when "page" then extract_page_information(clean_id: clean_id, parent_id: parent_id, jsonified_record_response: jsonified_record_response)
314
+ when "collection_view_page" then extract_collection_view_page_information(clean_id: clean_id, parent_id: parent_id, jsonified_record_response: jsonified_record_response)
315
+ end
316
+ end
317
+
318
+ class InvalidClientInstantiationError < StandardError
319
+ def initialize(msg = "Custom exception that is raised when an invalid property type is passed as a mapping.", exception_type = "instantiation_type")
320
+ @exception_type = exception_type
321
+ super(msg)
322
+ end
294
323
  end
295
324
  end
296
325
  end
@@ -1,16 +1,16 @@
1
1
  module NotionAPI
2
2
 
3
- # Code block: used to store code, should be assigned a coding language.
4
- class CodeBlock < BlockTemplate
5
- @notion_type = 'code'
6
- @type = 'code'
7
-
8
- def type
9
- NotionAPI::CodeBlock.notion_type
10
- end
11
-
12
- class << self
13
- attr_reader :notion_type, :type
14
- end
3
+ # Code block: used to store code, should be assigned a coding language.
4
+ class CodeBlock < BlockTemplate
5
+ @notion_type = "code"
6
+ @type = "code"
7
+
8
+ def type
9
+ NotionAPI::CodeBlock.notion_type
10
+ end
11
+
12
+ class << self
13
+ attr_reader :notion_type, :type
15
14
  end
16
- end
15
+ end
16
+ end
@@ -33,7 +33,9 @@ module NotionAPI
33
33
  transaction_id = extract_id(SecureRandom.hex(16))
34
34
  space_id = extract_id(SecureRandom.hex(16))
35
35
  new_block_id = extract_id(SecureRandom.hex(16))
36
- schema = extract_collection_schema(@collection_id, @view_id)
36
+ collection_data = extract_collection_data(collection_id, view_id)
37
+ last_row_id = collection_data["collection_view"][@view_id]["value"]["page_sort"][-1]
38
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
37
39
  keys = schema.keys
38
40
  col_map = {}
39
41
  keys.map { |key| col_map[schema[key]["name"]] = key }
@@ -47,17 +49,27 @@ module NotionAPI
47
49
  instantiate_row = Utils::CollectionViewComponents.add_new_row(new_block_id)
48
50
  set_block_alive = Utils::CollectionViewComponents.set_collection_blocks_alive(new_block_id, @collection_id)
49
51
  new_block_edited_time = Utils::BlockComponents.last_edited_time(new_block_id)
50
- parent_edited_time = Utils::BlockComponents.last_edited_time(@parent_id)
52
+ page_sort = Utils::BlockComponents.row_location_add(last_row_id, new_block_id, @view_id)
51
53
 
52
54
  operations = [
53
55
  instantiate_row,
54
56
  set_block_alive,
55
57
  new_block_edited_time,
56
- parent_edited_time,
58
+ page_sort,
57
59
  ]
58
60
 
59
61
  data.keys.each_with_index do |col_name, j|
60
62
  unless col_map.keys.include?(col_name.to_s); raise ArgumentError, "Column '#{col_name.to_s}' does not exist." end
63
+ if %q[select multi_select].include?(schema[col_map[col_name.to_s]]["type"])
64
+ options = schema[col_map[col_name.to_s]]["options"].nil? ? [] : schema[col_map[col_name.to_s]]["options"].map { |option| option["value"] }
65
+ multi_select_multi_options = data[col_name].split(",")
66
+ multi_select_multi_options.each do |option|
67
+ if !options.include?(option.strip)
68
+ create_new_option = Utils::CollectionViewComponents.add_new_option(col_map[col_name.to_s], option.strip, @collection_id)
69
+ operations.push(create_new_option)
70
+ end
71
+ end
72
+ end
61
73
  child_component = Utils::CollectionViewComponents.insert_data(new_block_id, col_map[col_name.to_s], data[col_name], schema[col_map[col_name.to_s]]["type"])
62
74
  operations.push(child_component)
63
75
  end
@@ -74,7 +86,18 @@ module NotionAPI
74
86
  unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
75
87
  Please try again, and if issues persist open an issue in GitHub."; end
76
88
 
77
- NotionAPI::CollectionViewRow.new(new_block_id, @parent_id, @collection_id, @view_id)
89
+ collection_row = NotionAPI::CollectionViewRow.new(new_block_id, @parent_id, @collection_id, @view_id)
90
+
91
+ properties = {}
92
+ data.keys.each do |col|
93
+ properties[col_map[col.to_s]] = [[data[col]]]
94
+ end
95
+
96
+ collection_data["block"][collection_row.id] = { "role" => "editor", "value" => { "id" => collection_row.id, "version" => 12, "type" => "page", "properties" => properties, "created_time" => 1607253360000, "last_edited_time" => 1607253360000, "parent_id" => "dde513c6-2428-4a5d-a830-7a67fdbf6b48", "parent_table" => "collection", "alive" => true, "created_by_table" => "notion_user", "created_by_id" => "0c5f02f3-495d-4b73-b1c5-9f6fe03a8c26", "last_edited_by_table" => "notion_user", "last_edited_by_id" => "0c5f02f3-495d-4b73-b1c5-9f6fe03a8c26", "shard_id" => 955090, "space_id" => "f687f7de-7f4c-4a86-b109-941a8dae92d2" } }
97
+ row_data = collection_data["block"][collection_row.id]
98
+ create_singleton_methods_and_instance_variables(collection_row, row_data)
99
+
100
+ collection_row
78
101
  end
79
102
 
80
103
  def add_property(name, type)
@@ -135,20 +158,11 @@ module NotionAPI
135
158
  limit: 100,
136
159
  verticalColumns: false,
137
160
  }
138
- jsonified_record_response = get_all_block_info(clean_id, request_body)
139
-
140
- i = 0
141
- while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
142
- return {} if i >= 10
143
-
144
- jsonified_record_response = get_all_block_info(clean_id, request_body)
145
- i += 1
146
- end
161
+ jsonified_record_response = get_all_block_info(request_body)
147
162
 
148
163
  collection_data = extract_collection_data(@collection_id, @view_id)
149
164
  schema = collection_data["collection"][collection_id]["value"]["schema"]
150
- column_mappings = schema.keys
151
- column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
165
+ column_names = NotionAPI::CollectionView.extract_collection_view_column_names(schema)
152
166
 
153
167
  collection_row = CollectionViewRow.new(row_id, @parent_id, @collection_id, @view_id)
154
168
  collection_row.instance_variable_set(:@column_names, column_names)
@@ -171,29 +185,20 @@ module NotionAPI
171
185
  verticalColumns: false,
172
186
  }
173
187
 
174
- jsonified_record_response = get_all_block_info(clean_id, request_body)
175
- i = 0
176
- while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
177
- return {} if i >= 10
178
-
179
- jsonified_record_response = get_all_block_info(clean_id, request_body)
180
- i += 1
181
- end
188
+ jsonified_record_response = get_all_block_info(request_body)
182
189
 
183
190
  jsonified_record_response["collection_view"][@view_id]["value"]["page_sort"]
184
191
  end
185
192
 
186
193
  def rows
187
194
  # ! returns all rows as instantiated class instances.
188
- row_id_array = row_ids
195
+ row_id_array = row_ids()
189
196
  parent_id = @parent_id
190
197
  collection_id = @collection_id
191
198
  view_id = @view_id
192
199
  collection_data = extract_collection_data(@collection_id, @view_id)
193
200
  schema = collection_data["collection"][collection_id]["value"]["schema"]
194
- column_mappings = schema.keys
195
- column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
196
-
201
+ column_names = NotionAPI::CollectionView.extract_collection_view_column_names(schema)
197
202
  row_instances = row_id_array.map { |row_id| NotionAPI::CollectionViewRow.new(row_id, parent_id, collection_id, view_id) }
198
203
  clean_row_instances = row_instances.filter { |row| collection_data["block"][row.id] }
199
204
  clean_row_instances.each { |row| row.instance_variable_set(:@column_names, column_names) }
@@ -205,6 +210,7 @@ module NotionAPI
205
210
  end
206
211
  clean_row_instances
207
212
  end
213
+
208
214
  def create_singleton_methods_and_instance_variables(row, row_data)
209
215
  # ! creates singleton methods for each property in a CollectionView.
210
216
  # ! row -> the block ID of the 'row' to retrieve: ``str``
@@ -214,22 +220,24 @@ module NotionAPI
214
220
  column_mappings = schema.keys
215
221
  column_hash = {}
216
222
  column_names = column_mappings.map { |mapping| column_hash[mapping] = schema[mapping]["name"].downcase }
217
-
223
+
218
224
  column_hash.keys.each_with_index do |column, i|
219
225
  # loop over the column names...
220
226
  # set instance variables for each column, allowing the dev to 'read' the column value
221
- cleaned_column = column_hash[column].split(" ").join("_").downcase.to_sym
227
+ cleaned_column = clean_property_names(column_hash, column)
222
228
 
223
- # p row_data["value"]["properties"][column_mappings[i]], !(row_data["value"]["properties"][column] or row_data["value"]["properties"][column_mappings[i]])
224
229
  if row_data["value"]["properties"].nil? or row_data["value"]["properties"][column].nil?
225
230
  value = ""
226
231
  else
227
- value = row_data["value"]["properties"][column][0][0]
232
+ value = row_data["value"]["properties"][column][0][0]
233
+ if ["‣"].include?(value.to_s)
234
+ value = row_data["value"]["properties"][column][0][1].flatten[-1]
235
+ end
228
236
  end
229
237
 
230
238
  row.instance_variable_set("@#{cleaned_column}", value)
231
239
  CollectionViewRow.class_eval { attr_reader cleaned_column }
232
- # then, define singleton methods for each column that are used to update the table cell
240
+ # then, define singleton methods for each column that are used to update the table cell
233
241
  row.define_singleton_method("#{cleaned_column}=") do |new_value|
234
242
  # neat way to get the name of the currently invoked method...
235
243
  parsed_method = __method__.to_s[0...-1].split("_").join(" ")
@@ -246,12 +254,23 @@ module NotionAPI
246
254
  space_id: space_id,
247
255
  }
248
256
 
249
- update_property_value = Utils::CollectionViewComponents.update_property_value(@id, column_hash.key(parsed_method), new_value)
257
+ update_property_value_hash = Utils::CollectionViewComponents.update_property_value(@id, column_hash.key(parsed_method), new_value, schema[column_hash.key(parsed_method)]["type"])
250
258
 
251
259
  operations = [
252
- update_property_value,
260
+ update_property_value_hash,
253
261
  ]
254
262
 
263
+ if %q[select multi_select].include?(schema[column_hash.key(parsed_method)]["type"])
264
+ options = schema[column_hash.key(parsed_method)]["options"].nil? ? [] : schema[column_hash.key(parsed_method)]["options"].map { |option| option["value"] }
265
+ multi_select_multi_options = new_value.split(",")
266
+ multi_select_multi_options.each do |option|
267
+ if !options.include?(option.strip)
268
+ create_new_option = Utils::CollectionViewComponents.add_new_option(column_hash.key(parsed_method), option.strip, @collection_id)
269
+ operations.push(create_new_option)
270
+ end
271
+ end
272
+ end
273
+
255
274
  request_url = URLS[:UPDATE_BLOCK]
256
275
  request_body = build_payload(operations, request_ids)
257
276
  response = HTTParty.post(
@@ -261,14 +280,31 @@ module NotionAPI
261
280
  headers: headers,
262
281
  )
263
282
  unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
264
- Please try again, and if issues persist open an issue in GitHub.";end
265
-
283
+ Please try again, and if issues persist open an issue in GitHub."; end
284
+
266
285
  # set the instance variable to the updated value!
267
286
  _ = row.instance_variable_set("@#{__method__.to_s[0...-1]}", new_value)
268
287
  row
269
288
  end
270
289
  end
271
290
  end
291
+
292
+ def clean_property_names(prop_hash, prop_notion_name)
293
+ # ! standardize property names by splitting the words in the property name into an array, removing non-alphanumeric
294
+ # ! characters, downcasing, and then re-joining the array with underscores.
295
+ # ! prop_hash -> hash of property notion names and property textual names: ``str``
296
+ # ! prop_notion_name -> the four-character long name of the notion property: ``str``
297
+
298
+ prop_hash[prop_notion_name].split(" ").map { |word| word.gsub(/[^a-z0-9]/i, "").downcase }.join("_").to_sym
299
+ end
300
+
301
+ def self.extract_collection_view_column_names(schema)
302
+ # ! extract the column names of a Collection View
303
+ # ! schema: the schema of the collection view
304
+ column_mappings = column_mappings = schema.keys
305
+ column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
306
+ column_names
307
+ end
272
308
  end
273
309
 
274
310
  # class that represents each row in a CollectionView