notion 1.0.4 → 1.1.1

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: 3b63efe4325f07f105d8c2efb29e5037737651d330723a745d78bdc66bc02e32
4
- data.tar.gz: 7e32f2d257d3d58ee3b29f6cbfdc3f901c88985a20e6980927265932afdb6e02
3
+ metadata.gz: 761cf6b775c714eb4b8eb65710baf3407eaa6061d717b18e59510a048c0edf7f
4
+ data.tar.gz: 7f3ba657e798d7e9a24b0aa77ee098462fdb164f958309fdc12ca39d54b78231
5
5
  SHA512:
6
- metadata.gz: 970d6921cbe05fc19a5c610c24cd995832adc45c95bb6a1710ad2a22be86a7c6c8ec3078e7d95b6a042cd0e732fc293e70a0cacd97410c1d111ee7c4a3a99880
7
- data.tar.gz: 72bc8e752d9f2aad62a9eeb5b91322bb623ee42b21370392285ffaf4ac16c5b8c795584c5dc0682f905a11cdb13b3f9803148ec3f9f784299cb7f004bd95bf30
6
+ metadata.gz: 281bc33e957a074c02bf2866e526aa866d2f75945f7c9dbf9ec0a41a5ac7b5795fe2cedf429e8b2e6220b16550d2bf8c9c7107832672bf4915124fb8c9dae167
7
+ data.tar.gz: f02fde242ad8ebfeef9edc3c1850270a8d1459a93b1fafd83ec88eae323ca4dba0ae07bacfc9ef38e053012e81c71b2690c7fe6b68d88e31ca7898e3a4592df7
data/README.md CHANGED
@@ -8,18 +8,23 @@
8
8
  - Check out the [Gem](https://rubygems.org/gems/notion)!
9
9
 
10
10
  ## Table of Contents
11
- - [Getting Started](#getting-started)
12
- * [Installation](#installation)
13
- - [Retrieving a Page](#retrieving-a-page)
14
- - [Retrieving a Block within the Page](#retrieving-a-block-within-the-page)
15
- * [Get a Block](#get-a-block)
16
- * [Get a Collection View - Table](#get-a-collection-view---table)
17
- - [Creating New Blocks](#creating-new-blocks)
18
- * [Create a block whose parent is the page](#create-a-block-whose-parent-is-the-page)
19
- * [Create a block whose parent is another block](#create-a-block-whose-parent-is-another-block)
20
- - [Creating New Collections](#creating-new-collections)
21
- - [Troubleshooting](#troubleshooting)
22
- * [No results returned when attempting to get a page](#no-results-returned-when-attempting-to-get-a-page)
11
+ - [Unofficial Notion Client for Ruby.](#unofficial-notion-client-for-ruby)
12
+ - [Table of Contents](#table-of-contents)
13
+ - [Getting Started](#getting-started)
14
+ - [Installation](#installation)
15
+ - [Retrieving a Page](#retrieving-a-page)
16
+ - [Retrieving a CollectionView Page](#retrieving-a-collectionview-page)
17
+ - [Retrieving a Block within the Page](#retrieving-a-block-within-the-page)
18
+ - [Get a Block](#get-a-block)
19
+ - [Get a Collection View](#get-a-collection-view)
20
+ - [Creating New Blocks](#creating-new-blocks)
21
+ - [Create a block whose parent is the page](#create-a-block-whose-parent-is-the-page)
22
+ - [Create a block whose parent is another block](#create-a-block-whose-parent-is-another-block)
23
+ - [Creating New Collections](#creating-new-collections)
24
+ - [Updating Collection View Cells](#updating-collection-view-cells)
25
+ - [Troubleshooting](#troubleshooting)
26
+ - [No results returned when attempting to get a page](#no-results-returned-when-attempting-to-get-a-page)
27
+ - [Retrieve a full-page Collection View](#retrieve-a-full-page-collection-view)
23
28
 
24
29
  ## Getting Started
25
30
  ### Installation
@@ -60,6 +65,8 @@ The following attributes can be read from any block class instance:
60
65
  To update the title of the page:
61
66
  ![Update the title of a page](https://github.com/danmurphy1217/notion-ruby/blob/master/gifs/change_title.gif)
62
67
 
68
+ ## Retrieving a CollectionView Page
69
+ This is achieved by passing the ID of the Collection View to the `get_page` method. Currently, the full URL of a Collection View Page is not supported (next up on the features list!). Once you retrieve the Collection View Page, all of the methods exposed to a normal Collection View instance are available (such as `.rows`, `.row(<row_id>)`, and all else outlined in [Updating a Collection](#updating-collection-view-cells)).
63
70
  ## Retrieving a Block within the Page
64
71
  Now that you have retrieved a Notion Page, you have full access to the blocks on that page. You can retrieve a specific block or collection view, retrieve all children IDs (array of children IDs), or retrieve all children (array of children class instances).
65
72
 
@@ -268,6 +275,28 @@ The first argument passed to `create_collection` determines which type of collec
268
275
  4. timeline
269
276
  5. gallery
270
277
 
278
+ ## Updating Collection View Cells
279
+ When you retrieve a `CollectionViewRow` instance with `.row(<row_id>)` or a list of `CollectionViewRow` instances with `.rows`, a handful of methods are created. Each row instance has access attributes that represent the properties in the Notion Collection View. So, let's say we are working with the following Notion Collection View:
280
+ | emoji | description | category | aliases | tags | unicode_version | ios_version |
281
+ |-------|--------------|---------------------|---------|---------|-----------------|-------------|
282
+ | 😉 | "winking face" | "Smileys & Emotion" | "wink" | "flirt" | "6.0" | "6.0" |
283
+
284
+ If you wanted to update the unicode and ios versions, you could use the following code:
285
+ ```ruby
286
+ >>> collection_view = @page.get_collection("1234567") # the ID of the collection block is 1234567
287
+ >>> rows = collection_view.rows
288
+ >>> row[0].unicode_version = "updated version here!"
289
+ >>> row[0].ios_version = "I was updated too!"
290
+ ```
291
+ Now, your Collection View will look like this:
292
+ | emoji | description | category | aliases | tags | unicode_version | ios_version |
293
+ |-------|--------------|---------------------|---------|---------|-----------------|-------------|
294
+ | 😉 | "winking face" | "Smileys & Emotion" | "wink" | "flirt" | "updated version here!" | "I was updated too!" |
295
+
296
+ You can also add new rows with the `.add_row({<data!>})` method and add new properties with the `.add_property("name_of_property", "type_of_property")` method.
297
+
298
+ **One important thing to be aware of:**
299
+ When adding a row with `.add_row`, the hash of data passed must be in the same order as it appears in your Notion Collection View.
271
300
  ## Troubleshooting
272
301
  ### No results returned when attempting to get a page
273
302
  If an empty hash is returned when you attempt to retrieve a Notion page, you'll need to include the `x-notion-active-user-header` when instantiating the Notion Client.
@@ -280,4 +309,13 @@ From here, you can instantiate the Notion Client with the following code:
280
309
  "<insert_x_notion_active_user_header_here>"
281
310
  )
282
311
  ```
283
-
312
+ ### Retrieve a full-page Collection View
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])
321
+ ```
@@ -54,8 +54,14 @@ module NotionAPI
54
54
  collection_id = extract_collection_id(block_id, jsonified_record_response)
55
55
  block_title = extract_collection_title(clean_id, collection_id, jsonified_record_response)
56
56
  view_id = extract_view_ids(block_id, jsonified_record_response)[0]
57
-
58
- CollectionViewPage.new(block_id, block_title, block_parent_id, collection_id, view_id)
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
59
65
  end
60
66
  end
61
67
 
@@ -229,20 +235,71 @@ module NotionAPI
229
235
  # ! parse and clean the URL or ID object provided.
230
236
  # ! url_or_id -> the block ID or URL : ``str``
231
237
  http_or_https = url_or_id.match(/^(http|https)/) # true if http or https in url_or_id...
238
+ collection_view_match = url_or_id.match(/(\?v=)/)
239
+
232
240
  if (url_or_id.length == 36) && ((url_or_id.split('-').length == 5) && !http_or_https)
233
241
  # passes if url_or_id is perfectly formatted already...
234
242
  url_or_id
235
- elsif (http_or_https && (url_or_id.split('-').last.length == 32)) || (!http_or_https && (url_or_id.length == 32))
243
+ elsif (http_or_https && (url_or_id.split('-').last.length == 32)) || (!http_or_https && (url_or_id.length == 32)) || (collection_view_match)
236
244
  # passes if either:
237
245
  # 1. a URL is passed as url_or_id and the ID at the end is 32 characters long or
238
246
  # 2. a URL is not passed and the ID length is 32 [aka unformatted]
239
247
  pattern = [8, 13, 18, 23]
240
- id = url_or_id.split('-').last
241
- pattern.each { |index| id.insert(index, '-') }
242
- id
248
+ if collection_view_match
249
+ id_without_view = url_or_id.split('?')[0]
250
+ clean_id = id_without_view.split('/').last
251
+ pattern.each { |index| clean_id.insert(index, '-') }
252
+ clean_id
253
+ else
254
+ id = url_or_id.split('-').last
255
+ pattern.each { |index| id.insert(index, '-') }
256
+ id
257
+ end
243
258
  else
244
259
  raise ArgumentError, 'Expected a Notion page URL or a page ID. Please consult the documentation for further information.'
245
260
  end
246
261
  end
262
+
263
+ def extract_collection_schema(collection_id, view_id, response = {})
264
+ # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
265
+ # ! collection_id -> the collection ID : ``str``
266
+ # ! view_id -> the view ID : ``str``
267
+ cookies = Core.options['cookies']
268
+ headers = Core.options['headers']
269
+
270
+ if response.empty?
271
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
272
+
273
+ request_url = URLS[:GET_COLLECTION]
274
+ response = HTTParty.post(
275
+ request_url,
276
+ body: query_collection_hash.to_json,
277
+ cookies: cookies,
278
+ headers: headers
279
+ )
280
+ response['recordMap']['collection'][collection_id]['value']['schema']
281
+ else
282
+ response['collection'][collection_id]['value']['schema']
283
+ end
284
+ end
285
+
286
+ def extract_collection_data(collection_id, view_id)
287
+ # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
288
+ # ! collection_id -> the collection ID : ``str``
289
+ # ! view_id -> the view ID : ``str``
290
+ cookies = Core.options['cookies']
291
+ headers = Core.options['headers']
292
+
293
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
294
+
295
+ request_url = URLS[:GET_COLLECTION]
296
+ response = HTTParty.post(
297
+ request_url,
298
+ body: query_collection_hash.to_json,
299
+ cookies: cookies,
300
+ headers: headers
301
+ )
302
+ response['recordMap']
303
+ end
247
304
  end
248
305
  end
@@ -1,243 +1,357 @@
1
1
  module NotionAPI
2
- # collection views such as tables and timelines.
3
- class CollectionView < Core
4
- attr_reader :id, :title, :parent_id, :collection_id, :view_id
5
-
6
- @notion_type = 'collection_view'
7
- @type = 'collection_view'
8
-
9
- def type
10
- NotionAPI::CollectionView.notion_type
11
- end
12
-
13
- class << self
14
- attr_reader :notion_type, :type
15
- end
16
-
17
- def initialize(id, title, parent_id, collection_id, view_id)
18
- @id = id
19
- @title = title
20
- @parent_id = parent_id
21
- @collection_id = collection_id
22
- @view_id = view_id
23
- end
24
-
25
- def add_row(data)
26
- # ! add new row to Collection View table.
27
- # ! data -> data to add to table : ``hash``
28
-
29
- cookies = Core.options['cookies']
30
- headers = Core.options['headers']
31
-
32
- request_id = extract_id(SecureRandom.hex(16))
33
- transaction_id = extract_id(SecureRandom.hex(16))
34
- space_id = extract_id(SecureRandom.hex(16))
35
- new_block_id = extract_id(SecureRandom.hex(16))
36
- schema = extract_collection_schema(@collection_id, @view_id)
37
- keys = schema.keys
38
- col_map = {}
39
- keys.map { |key| col_map[schema[key]['name']] = key }
40
-
41
- request_ids = {
42
- request_id: request_id,
43
- transaction_id: transaction_id,
44
- space_id: space_id
45
- }
46
-
47
- instantiate_row = Utils::CollectionViewComponents.add_new_row(new_block_id)
48
- set_block_alive = Utils::CollectionViewComponents.set_collection_blocks_alive(new_block_id, @collection_id)
49
- new_block_edited_time = Utils::BlockComponents.last_edited_time(new_block_id)
50
- parent_edited_time = Utils::BlockComponents.last_edited_time(@parent_id)
51
-
52
- operations = [
53
- instantiate_row,
54
- set_block_alive,
55
- new_block_edited_time,
56
- parent_edited_time
57
- ]
58
-
59
- data.keys.each_with_index do |col_name, j|
60
- child_component = Utils::CollectionViewComponents.insert_data(new_block_id, j.zero? ? 'title' : col_map[col_name], data[col_name], j.zero? ? schema['title']["type"] : schema[col_map[col_name]]['type'])
61
- operations.push(child_component)
2
+ # collection views such as tables and timelines.
3
+ class CollectionView < Core
4
+ attr_reader :id, :title, :parent_id, :collection_id, :view_id
5
+
6
+ @notion_type = "collection_view"
7
+ @type = "collection_view"
8
+
9
+ def type
10
+ NotionAPI::CollectionView.notion_type
11
+ end
12
+
13
+ class << self
14
+ attr_reader :notion_type, :type
15
+ end
16
+
17
+ def initialize(id, title, parent_id, collection_id, view_id)
18
+ @id = id
19
+ @title = title
20
+ @parent_id = parent_id
21
+ @collection_id = collection_id
22
+ @view_id = view_id
23
+ end
24
+
25
+ def add_row(data)
26
+ # ! add new row to Collection View table.
27
+ # ! data -> data to add to table : ``hash``
28
+
29
+ cookies = Core.options["cookies"]
30
+ headers = Core.options["headers"]
31
+
32
+ request_id = extract_id(SecureRandom.hex(16))
33
+ transaction_id = extract_id(SecureRandom.hex(16))
34
+ space_id = extract_id(SecureRandom.hex(16))
35
+ new_block_id = extract_id(SecureRandom.hex(16))
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"]
39
+ keys = schema.keys
40
+ col_map = {}
41
+ keys.map { |key| col_map[schema[key]["name"]] = key }
42
+
43
+ request_ids = {
44
+ request_id: request_id,
45
+ transaction_id: transaction_id,
46
+ space_id: space_id,
47
+ }
48
+
49
+ instantiate_row = Utils::CollectionViewComponents.add_new_row(new_block_id)
50
+ set_block_alive = Utils::CollectionViewComponents.set_collection_blocks_alive(new_block_id, @collection_id)
51
+ new_block_edited_time = Utils::BlockComponents.last_edited_time(new_block_id)
52
+ page_sort = Utils::BlockComponents.row_location_add(last_row_id, new_block_id, @view_id)
53
+
54
+ operations = [
55
+ instantiate_row,
56
+ set_block_alive,
57
+ new_block_edited_time,
58
+ page_sort,
59
+ ]
60
+
61
+ data.keys.each_with_index do |col_name, j|
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
62
72
  end
63
-
64
- request_url = URLS[:UPDATE_BLOCK]
65
- request_body = build_payload(operations, request_ids)
66
- response = HTTParty.post(
67
- request_url,
68
- body: request_body.to_json,
69
- cookies: cookies,
70
- headers: headers
71
- )
72
-
73
- 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}.
74
- Please try again, and if issues persist open an issue in GitHub."; end
75
-
76
- NotionAPI::CollectionViewRow.new(new_block_id, @parent_id, @collection_id, @view_id)
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"])
74
+ operations.push(child_component)
77
75
  end
78
-
79
- def add_property(name, type)
80
- # ! add a property (column) to the table.
81
- # ! name -> name of the property : ``str``
82
- # ! type -> type of the property : ``str``
83
- cookies = Core.options['cookies']
84
- headers = Core.options['headers']
85
-
86
- request_id = extract_id(SecureRandom.hex(16))
87
- transaction_id = extract_id(SecureRandom.hex(16))
88
- space_id = extract_id(SecureRandom.hex(16))
89
-
90
- request_ids = {
91
- request_id: request_id,
92
- transaction_id: transaction_id,
93
- space_id: space_id
94
- }
95
-
96
- # create updated schema
97
- schema = extract_collection_schema(@collection_id, @view_id)
98
- schema[name] = {
99
- name: name,
100
- type: type
101
- }
102
- new_schema = {
103
- schema: schema
104
- }
105
-
106
- add_collection_property = Utils::CollectionViewComponents.add_collection_property(@collection_id, new_schema)
107
-
108
- operations = [
109
- add_collection_property
110
- ]
111
-
112
- request_url = URLS[:UPDATE_BLOCK]
113
- request_body = build_payload(operations, request_ids)
114
- response = HTTParty.post(
115
- request_url,
116
- body: request_body.to_json,
117
- cookies: cookies,
118
- headers: headers
119
- )
120
- 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}.
121
- Please try again, and if issues persist open an issue in GitHub."; end
122
-
123
- true
76
+
77
+ request_url = URLS[:UPDATE_BLOCK]
78
+ request_body = build_payload(operations, request_ids)
79
+ response = HTTParty.post(
80
+ request_url,
81
+ body: request_body.to_json,
82
+ cookies: cookies,
83
+ headers: headers,
84
+ )
85
+
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}.
87
+ Please try again, and if issues persist open an issue in GitHub."; end
88
+
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]]]
124
94
  end
125
-
126
- def row(row_id)
127
- # ! retrieve a row from a CollectionView Table.
128
- # ! row_id -> the ID for the row to retrieve: ``str``
129
- clean_id = extract_id(row_id)
130
-
131
- request_body = {
132
- pageId: clean_id,
133
- chunkNumber: 0,
134
- limit: 100,
135
- verticalColumns: false
136
- }
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
101
+ end
102
+
103
+ def add_property(name, type)
104
+ # ! add a property (column) to the table.
105
+ # ! name -> name of the property : ``str``
106
+ # ! type -> type of the property : ``str``
107
+ cookies = Core.options["cookies"]
108
+ headers = Core.options["headers"]
109
+
110
+ request_id = extract_id(SecureRandom.hex(16))
111
+ transaction_id = extract_id(SecureRandom.hex(16))
112
+ space_id = extract_id(SecureRandom.hex(16))
113
+
114
+ request_ids = {
115
+ request_id: request_id,
116
+ transaction_id: transaction_id,
117
+ space_id: space_id,
118
+ }
119
+
120
+ # create updated schema
121
+ schema = extract_collection_schema(@collection_id, @view_id)
122
+ schema[name] = {
123
+ name: name,
124
+ type: type,
125
+ }
126
+ new_schema = {
127
+ schema: schema,
128
+ }
129
+
130
+ add_collection_property = Utils::CollectionViewComponents.add_collection_property(@collection_id, new_schema)
131
+
132
+ operations = [
133
+ add_collection_property,
134
+ ]
135
+
136
+ request_url = URLS[:UPDATE_BLOCK]
137
+ request_body = build_payload(operations, request_ids)
138
+ response = HTTParty.post(
139
+ request_url,
140
+ body: request_body.to_json,
141
+ cookies: cookies,
142
+ headers: headers,
143
+ )
144
+ 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}.
145
+ Please try again, and if issues persist open an issue in GitHub."; end
146
+
147
+ true
148
+ end
149
+
150
+ def row(row_id)
151
+ # ! retrieve a row from a CollectionView Table.
152
+ # ! row_id -> the ID for the row to retrieve: ``str``
153
+ clean_id = extract_id(row_id)
154
+
155
+ request_body = {
156
+ pageId: clean_id,
157
+ chunkNumber: 0,
158
+ limit: 100,
159
+ verticalColumns: false,
160
+ }
161
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
162
+
163
+ i = 0
164
+ while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
165
+ return {} if i >= 10
166
+
137
167
  jsonified_record_response = get_all_block_info(clean_id, request_body)
138
- schema = extract_collection_schema(@collection_id, @view_id)
139
- keys = schema.keys
140
- column_names = keys.map { |key| schema[key]['name'] }
141
- i = 0
142
- while jsonified_record_response.empty? || jsonified_record_response['block'].empty?
143
- return {} if i >= 10
144
-
145
- jsonified_record_response = get_all_block_info(clean_id, request_body)
146
- i += 1
147
- end
148
- row_jsonified_response = jsonified_record_response['block'][clean_id]['value']['properties']
149
- row_data = {}
150
- keys.each_with_index { |key, idx| row_data[column_names[idx]] = row_jsonified_response[key] ? row_jsonified_response[key].flatten : [] }
151
- row_data
168
+ i += 1
152
169
  end
153
-
154
- def row_ids
155
- # ! retrieve all Collection View table rows.
156
- clean_id = extract_id(@id)
157
-
158
- request_body = {
159
- pageId: clean_id,
160
- chunkNumber: 0,
161
- limit: 100,
162
- verticalColumns: false
163
- }
164
-
170
+
171
+ collection_data = extract_collection_data(@collection_id, @view_id)
172
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
173
+ column_mappings = schema.keys
174
+ column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
175
+
176
+ collection_row = CollectionViewRow.new(row_id, @parent_id, @collection_id, @view_id)
177
+ collection_row.instance_variable_set(:@column_names, column_names)
178
+ CollectionViewRow.class_eval { attr_reader :column_names }
179
+
180
+ row_data = collection_data["block"][collection_row.id]
181
+ create_singleton_methods_and_instance_variables(collection_row, row_data)
182
+
183
+ collection_row
184
+ end
185
+
186
+ def row_ids
187
+ # ! retrieve all Collection View table rows.
188
+ clean_id = extract_id(@id)
189
+
190
+ request_body = {
191
+ pageId: clean_id,
192
+ chunkNumber: 0,
193
+ limit: 100,
194
+ verticalColumns: false,
195
+ }
196
+
197
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
198
+ i = 0
199
+ while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
200
+ return {} if i >= 10
201
+
165
202
  jsonified_record_response = get_all_block_info(clean_id, request_body)
166
- i = 0
167
- while jsonified_record_response.empty? || jsonified_record_response['block'].empty?
168
- return {} if i >= 10
169
-
170
- jsonified_record_response = get_all_block_info(clean_id, request_body)
171
- i += 1
172
- end
173
-
174
- jsonified_record_response['collection_view'][@view_id]['value']['page_sort']
175
- end
176
-
177
- def rows
178
- # ! returns all rows as instantiated class instances.
179
- row_id_array = row_ids
180
- parent_id = @parent_id
181
- collection_id = @collection_id
182
- view_id = @view_id
183
-
184
- row_id_array.map { |row_id| NotionAPI::CollectionViewRow.new(row_id, parent_id, collection_id, view_id) }
185
- end
186
-
187
- private
188
-
189
- def extract_collection_schema(collection_id, view_id)
190
- # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
191
- # ! collection_id -> the collection ID : ``str``
192
- # ! view_id -> the view ID : ``str``
193
- cookies = Core.options['cookies']
194
- headers = Core.options['headers']
195
-
196
- query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
197
-
198
- request_url = URLS[:GET_COLLECTION]
199
- response = HTTParty.post(
200
- request_url,
201
- body: query_collection_hash.to_json,
202
- cookies: cookies,
203
- headers: headers
204
- )
205
- response['recordMap']['collection'][collection_id]['value']['schema']
203
+ i += 1
206
204
  end
205
+
206
+ jsonified_record_response["collection_view"][@view_id]["value"]["page_sort"]
207
207
  end
208
- class CollectionViewRow < Core
209
- @notion_type = 'table_row'
210
- @type = 'table_row'
211
-
212
- def type
213
- NotionAPI::CollectionViewRow.notion_type
214
- end
215
-
216
- class << self
217
- attr_reader :notion_type, :type, :parent_id
218
- end
219
-
220
- attr_reader :parent_id, :id
221
- def initialize(id, parent_id, collection_id, view_id)
222
- @id = id
223
- @parent_id = parent_id
224
- @collection_id = collection_id
225
- @view_id = view_id
208
+
209
+ def rows
210
+ # ! returns all rows as instantiated class instances.
211
+ row_id_array = row_ids
212
+ parent_id = @parent_id
213
+ collection_id = @collection_id
214
+ view_id = @view_id
215
+ collection_data = extract_collection_data(@collection_id, @view_id)
216
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
217
+ column_mappings = schema.keys
218
+ column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
219
+
220
+ row_instances = row_id_array.map { |row_id| NotionAPI::CollectionViewRow.new(row_id, parent_id, collection_id, view_id) }
221
+ clean_row_instances = row_instances.filter { |row| collection_data["block"][row.id] }
222
+ clean_row_instances.each { |row| row.instance_variable_set(:@column_names, column_names) }
223
+ CollectionViewRow.class_eval { attr_reader :column_names }
224
+
225
+ clean_row_instances.each do |collection_row|
226
+ row_data = collection_data["block"][collection_row.id]
227
+ create_singleton_methods_and_instance_variables(collection_row, row_data)
226
228
  end
229
+ clean_row_instances
227
230
  end
228
231
 
229
- class CollectionViewPage < CollectionView
230
- @notion_type = 'collection_view_page'
231
- @type = 'collection_view_page'
232
-
233
- def type
234
- NotionAPI::CollectionViewRow.notion_type
235
- end
236
-
237
- class << self
238
- attr_reader :notion_type, :type, :parent_id
232
+ def create_singleton_methods_and_instance_variables(row, row_data)
233
+ # ! creates singleton methods for each property in a CollectionView.
234
+ # ! row -> the block ID of the 'row' to retrieve: ``str``
235
+ # ! row_data -> the data corresponding to that row, should be key-value pairs where the keys are the columns: ``hash``
236
+ collection_data = extract_collection_data(@collection_id, @view_id)
237
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
238
+ column_mappings = schema.keys
239
+ column_hash = {}
240
+ column_names = column_mappings.map { |mapping| column_hash[mapping] = schema[mapping]["name"].downcase }
241
+
242
+ column_hash.keys.each_with_index do |column, i|
243
+ # loop over the column names...
244
+ # set instance variables for each column, allowing the dev to 'read' the column value
245
+ cleaned_column = column_hash[column].split(" ").join("_").downcase.to_sym
246
+
247
+ # p row_data["value"]["properties"][column_mappings[i]], !(row_data["value"]["properties"][column] or row_data["value"]["properties"][column_mappings[i]])
248
+ if row_data["value"]["properties"].nil? or row_data["value"]["properties"][column].nil?
249
+ value = ""
250
+ else
251
+ value = row_data["value"]["properties"][column][0][0]
252
+ if ["‣"].include?(value.to_s)
253
+ value = row_data["value"]["properties"][column][0][1].flatten[-1]
254
+ end
255
+ end
256
+
257
+ row.instance_variable_set("@#{cleaned_column}", value)
258
+ CollectionViewRow.class_eval { attr_reader cleaned_column }
259
+ # then, define singleton methods for each column that are used to update the table cell
260
+ row.define_singleton_method("#{cleaned_column}=") do |new_value|
261
+ # neat way to get the name of the currently invoked method...
262
+ parsed_method = __method__.to_s[0...-1].split("_").join(" ")
263
+ cookies = Core.options["cookies"]
264
+ headers = Core.options["headers"]
265
+
266
+ p new_value, column_hash.key(parsed_method), column_names, schema
267
+
268
+ request_id = extract_id(SecureRandom.hex(16))
269
+ transaction_id = extract_id(SecureRandom.hex(16))
270
+ space_id = extract_id(SecureRandom.hex(16))
271
+
272
+ request_ids = {
273
+ request_id: request_id,
274
+ transaction_id: transaction_id,
275
+ space_id: space_id,
276
+ }
277
+
278
+ update_property_value_hash = Utils::CollectionViewComponents.update_property_value(@id, column_hash.key(parsed_method), new_value)
279
+
280
+ operations = [
281
+ update_property_value_hash,
282
+ ]
283
+
284
+ if %q[select multi_select].include?(schema[column_hash.key(parsed_method)]["type"])
285
+ p "ENTERED THE ABYSS"
286
+ options = schema[column_hash.key(parsed_method)]["options"].nil? ? [] : schema[column_hash.key(parsed_method)]["options"].map { |option| option["value"] }
287
+ multi_select_multi_options = new_value.split(",")
288
+ multi_select_multi_options.each do |option|
289
+ if !options.include?(option.strip)
290
+ create_new_option = Utils::CollectionViewComponents.add_new_option(column_hash.key(parsed_method), option.strip, @collection_id)
291
+ operations.push(create_new_option)
292
+ end
293
+ end
294
+ end
295
+
296
+ request_url = URLS[:UPDATE_BLOCK]
297
+ request_body = build_payload(operations, request_ids)
298
+ response = HTTParty.post(
299
+ request_url,
300
+ body: request_body.to_json,
301
+ cookies: cookies,
302
+ headers: headers,
303
+ )
304
+ 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}.
305
+ Please try again, and if issues persist open an issue in GitHub."; end
306
+
307
+ # set the instance variable to the updated value!
308
+ _ = row.instance_variable_set("@#{__method__.to_s[0...-1]}", new_value)
309
+ row
310
+ end
239
311
  end
240
-
241
- attr_reader :parent_id, :id
242
312
  end
243
- end
313
+ end
314
+
315
+ # class that represents each row in a CollectionView
316
+ class CollectionViewRow < Core
317
+ @notion_type = "table_row"
318
+ @type = "table_row"
319
+
320
+ def type
321
+ NotionAPI::CollectionViewRow.notion_type
322
+ end
323
+
324
+ def inspect
325
+ "CollectionViewRow - id: #{self.id} - parent id: #{self.parent_id}"
326
+ end
327
+
328
+ class << self
329
+ attr_reader :notion_type, :type, :parent_id
330
+ end
331
+
332
+ attr_reader :parent_id, :id
333
+
334
+ def initialize(id, parent_id, collection_id, view_id)
335
+ @id = id
336
+ @parent_id = parent_id
337
+ @collection_id = collection_id
338
+ @view_id = view_id
339
+ end
340
+ end
341
+
342
+ # class that represents a CollectionViewPage, inheriting all properties from CollectionView
343
+ class CollectionViewPage < CollectionView
344
+ @notion_type = "collection_view_page"
345
+ @type = "collection_view_page"
346
+
347
+ def type
348
+ NotionAPI::CollectionViewRow.notion_type
349
+ end
350
+
351
+ class << self
352
+ attr_reader :notion_type, :type, :parent_id
353
+ end
354
+
355
+ attr_reader :parent_id, :id
356
+ end
357
+ end
@@ -304,7 +304,15 @@ module NotionAPI
304
304
  block_collection_id = extract_collection_id(clean_id, jsonified_record_response)
305
305
  block_view_id = extract_view_ids(clean_id, jsonified_record_response)
306
306
  collection_title = extract_collection_title(clean_id, block_collection_id, jsonified_record_response)
307
- block_class.new(clean_id, collection_title, block_parent_id, block_collection_id, block_view_id.join)
307
+
308
+ block = block_class.new(clean_id, collection_title, block_parent_id, block_collection_id, block_view_id.join)
309
+ schema = extract_collection_schema(block_collection_id, block_view_id[0])
310
+ column_mappings = schema.keys
311
+ column_names = column_mappings.map { |mapping| schema[mapping]['name']}
312
+ block.instance_variable_set(:@column_names, column_names)
313
+ CollectionView.class_eval{attr_reader :column_names}
314
+
315
+ block
308
316
  else
309
317
  block_title = extract_title(clean_id, jsonified_record_response)
310
318
  block_class.new(clean_id, block_title, block_parent_id)
@@ -200,14 +200,14 @@ module Utils
200
200
 
201
201
  args = if command == "listAfter"
202
202
  {
203
- after: target || block_id,
204
- id: new_block_id || block_id,
205
- }
203
+ after: target || block_id,
204
+ id: new_block_id || block_id,
205
+ }
206
206
  else
207
207
  {
208
- before: target || block_id,
209
- id: new_block_id || block_id,
210
- }
208
+ before: target || block_id,
209
+ id: new_block_id || block_id,
210
+ }
211
211
  end
212
212
 
213
213
  {
@@ -219,6 +219,21 @@ module Utils
219
219
  }
220
220
  end
221
221
 
222
+ def self.row_location_add(last_row_id, block_id, view_id)
223
+ {
224
+ "table": "collection_view",
225
+ "id": view_id,
226
+ "path": [
227
+ "page_sort"
228
+ ],
229
+ "command": "listAfter",
230
+ "args": {
231
+ "after": last_row_id,
232
+ "id": block_id
233
+ }
234
+ }
235
+ end
236
+
222
237
  def self.block_location_remove(block_parent_id, block_id)
223
238
  # ! removes a notion block
224
239
  # ! block_parent_id -> the parent ID of the block to remove : ``str``
@@ -276,9 +291,9 @@ module Utils
276
291
  def self.add_emoji_icon(block_id, icon)
277
292
  {
278
293
  id: block_id,
279
- table:"block",
280
- path:["format","page_icon"],
281
- command:"set","args": icon
294
+ table: "block",
295
+ path: ["format", "page_icon"],
296
+ command: "set", "args": icon,
282
297
  }
283
298
  end
284
299
  end
@@ -383,14 +398,14 @@ module Utils
383
398
  # ! new_block_id -> id of the new block
384
399
  # ! data -> json data to insert into table.
385
400
  col_names = data[0].keys
386
- data_mappings = {Integer => "number", String => "text", Array => "text", Float => "number", Date => "date"}
401
+ data_mappings = { Integer => "number", String => "text", Array => "text", Float => "number", Date => "date" }
387
402
  exceptions = [ArgumentError, TypeError]
388
403
  data_types = col_names.map do |name|
389
404
  # TODO: this is a little hacky... should probably think about a better way or add a requirement for user input to match a certain criteria.
390
- begin
405
+ begin
391
406
  DateTime.parse(data[0][name]) ? data_mappings[Date] : nil
392
407
  rescue *exceptions
393
- data_mappings[data[0][name].class]
408
+ data_mappings[data[0][name].class]
394
409
  end
395
410
  end
396
411
 
@@ -403,18 +418,18 @@ module Utils
403
418
  end
404
419
  end
405
420
  return {
406
- id: collection_id,
407
- table: "collection",
408
- path: [],
409
- command: "update",
410
- args: {
411
- id: collection_id,
412
- schema: schema_conf,
413
- parent_id: new_block_id,
414
- parent_table: "block",
415
- alive: true,
416
- },
417
- }, data_types
421
+ id: collection_id,
422
+ table: "collection",
423
+ path: [],
424
+ command: "update",
425
+ args: {
426
+ id: collection_id,
427
+ schema: schema_conf,
428
+ parent_id: new_block_id,
429
+ parent_table: "block",
430
+ alive: true,
431
+ },
432
+ }, data_types
418
433
  end
419
434
 
420
435
  def self.set_collection_title(collection_title, collection_id)
@@ -440,6 +455,11 @@ module Utils
440
455
  # ! column -> the name of the column to insert data into.
441
456
  # ! value -> the value to insert into the column.
442
457
  # ! mapping -> the column data type.
458
+ simple_mappings = ["title", "text", "phone_number", "email", "url", "number", "checkbox", "select", "multi_select"]
459
+ datetime_mappings = ["date"]
460
+ media_mappings = ["file"]
461
+ person_mappings = ["person"]
462
+
443
463
  table = "block"
444
464
  path = [
445
465
  "properties",
@@ -447,12 +467,50 @@ module Utils
447
467
  ]
448
468
  command = "set"
449
469
 
470
+ if simple_mappings.include?(mapping)
471
+ args = [[value]]
472
+ elsif media_mappings.include?(mapping)
473
+ args = [[value, [["a", value]]]]
474
+ elsif datetime_mappings.include?(mapping)
475
+ args = [["‣", [["d", { "type": "date", "start_date": value }]]]]
476
+ elsif person_mappings.include?(mapping)
477
+ args = [["‣",
478
+ [["u", value]]
479
+ ]]
480
+ else
481
+ raise SchemaTypeError, "Invalid property type: #{mapping}"
482
+ end
483
+
450
484
  {
451
- id: block_id,
452
485
  table: table,
486
+ id: block_id,
487
+ command: command,
453
488
  path: path,
489
+ args: args,
490
+ }
491
+ end
492
+
493
+ def self.add_new_option(column, value, collection_id)
494
+ table = "collection"
495
+ path = ["schema", column, "options"]
496
+ command = "keyedObjectListAfter"
497
+ colors = ["default", "gray", "brown", "orange", "yellow", "green", "blue", "purple", "pink", "red"]
498
+ random_color = colors[rand(0...colors.length)]
499
+
500
+ args = {
501
+ "value": {
502
+ "id": SecureRandom.hex(16),
503
+ "value": value,
504
+ "color": random_color
505
+ }
506
+ }
507
+
508
+ {
509
+ table: table,
510
+ id: collection_id,
454
511
  command: command,
455
- args: mapping == "date" ? [["‣",[["d",{"type": "date","start_date": value}]]]] : [[value]],
512
+ path: path,
513
+ args: args,
456
514
  }
457
515
  end
458
516
 
@@ -505,45 +563,45 @@ module Utils
505
563
  args["format"] = {
506
564
  "table_properties" => [
507
565
  {
508
- "property" => "title",
509
- "visible" => true,
510
- "width" => 280,
511
- },
566
+ "property" => "title",
567
+ "visible" => true,
568
+ "width" => 280,
569
+ },
512
570
  {
513
- "property" => "aliases",
514
- "visible" => true,
515
- "width" => 200,
516
- },
571
+ "property" => "aliases",
572
+ "visible" => true,
573
+ "width" => 200,
574
+ },
517
575
  {
518
- "property" => "category",
519
- "visible" => true,
520
- "width" => 200,
521
- },
576
+ "property" => "category",
577
+ "visible" => true,
578
+ "width" => 200,
579
+ },
522
580
  {
523
- "property" => "description",
524
- "visible" => true,
525
- "width" => 200,
526
- },
581
+ "property" => "description",
582
+ "visible" => true,
583
+ "width" => 200,
584
+ },
527
585
  {
528
- "property" => "ios_version",
529
- "visible" => true,
530
- "width" => 200,
531
- },
586
+ "property" => "ios_version",
587
+ "visible" => true,
588
+ "width" => 200,
589
+ },
590
+ {
591
+ "property" => "tags",
592
+ "visible" => true,
593
+ "width" => 200,
594
+ },
532
595
  {
533
- "property" => "tags",
534
- "visible" => true,
535
- "width" => 200,
536
- },
537
- {
538
596
  "property" => "phone",
539
597
  "visible" => true,
540
598
  "width" => 200,
541
599
  },
542
600
  {
543
- "property" => "unicode_version",
544
- "visible" => true,
545
- "width" => 200,
546
- }
601
+ "property" => "unicode_version",
602
+ "visible" => true,
603
+ "width" => 200,
604
+ },
547
605
  ],
548
606
  }
549
607
  {
@@ -554,6 +612,37 @@ module Utils
554
612
  args: args,
555
613
  }
556
614
  end
615
+
616
+ def self.update_property_value(page_id, column_name, new_value)
617
+ # ! update the specified column_name to new_value
618
+ # ! page_id -> the ID of the page: ``str``
619
+ # ! column_name -> the name of the column ["property"] to update: ``str``
620
+ # ! new_value -> the new value to assign to that column ["property"]: ``str``
621
+ table = "block"
622
+ path = [
623
+ "properties",
624
+ column_name,
625
+ ]
626
+ command = "set"
627
+ args = [[
628
+ new_value,
629
+ ]]
630
+
631
+ {
632
+ id: page_id,
633
+ table: table,
634
+ path: path,
635
+ command: command,
636
+ args: args,
637
+ }
638
+ end
639
+ end
640
+
641
+ class SchemaTypeError < StandardError
642
+ def initialize(msg="Custom exception that is raised when an invalid property type is passed as a mapping.", exception_type="schema_type")
643
+ @exception_type = exception_type
644
+ super(msg)
645
+ end
557
646
  end
558
647
 
559
648
  def build_payload(operations, request_ids)
@@ -576,4 +665,4 @@ module Utils
576
665
  }
577
666
  payload
578
667
  end
579
- end
668
+ end
@@ -1,3 +1,3 @@
1
1
  module NotionAPI
2
- VERSION = '1.0.4'
2
+ VERSION = '1.1.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: notion
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Murphy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-30 00:00:00.000000000 Z
11
+ date: 2020-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty