notion 1.0.3 → 1.1.0

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: 58ac8026dc81c27d34f42b51e388bd036b03d2338ef9113b43bcaee0bd57f6a2
4
- data.tar.gz: 914d54deb74e73057b6d52f0c7a8227903b501250243f4d61d81c2196e42bc0f
3
+ metadata.gz: c99389e680f113f6288c7809f95c3c4b86b894c7c0b741dd774d5738b398b241
4
+ data.tar.gz: 1f995b8c4d27874a248bf99023efe02a96198231e0a7cfb51d139bbae45f6cac
5
5
  SHA512:
6
- metadata.gz: bdb71d3859daaf1ded2f418cc348a9d3c3fb65cf446efd94df49dc28330355571b078415f8a48f53a31328c7f672f6e0b26f02176e8ec9702810e144c5341535
7
- data.tar.gz: 035343a8e4fe1326b7e735d4a019db969d1c3d356faaf80f56fc861d60e9cdb3ee419fc9c776abaf7ffeaab0572a69dfec19fe42ab6362e600dd014f105c593e
6
+ metadata.gz: 65d7e7713162c1ca4e694972715d904b927dada01eb3c679f9470119bb7c35afa801ffc2894cba5485348a6ae1beb4b6df931af3fada0f74317d0ae1bd27d94d
7
+ data.tar.gz: 375a2c1357cd5a56064a86fe650e523ddaee2834d7d0e3039b6a73d17b5157e418a4f2fa298562748e1bee350fcf5221efe1880db8b60970f2462556c4bc8468
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
 
@@ -114,7 +121,7 @@ For example:
114
121
  ```
115
122
  For example:
116
123
  ![move a block](https://github.com/danmurphy1217/notion-ruby/blob/master/gifs/move_before_and_after.gif)
117
- ### Get a Collection View - Table
124
+ ### Get a Collection View
118
125
  To retrieve a collection, you use the `get_collection` method. This method is designed to work with Table collections, but the codebase is actively being updated to support others:
119
126
  ```ruby
120
127
  >>> @page = @client.get_page("https://www.notion.so/danmurphy/TEST-PAGE-d2ce338f19e847f586bd17679f490e66")
@@ -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,9 @@ 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
+ 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.
316
+ ```
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:
@@ -42,13 +42,27 @@ module NotionAPI
42
42
  end
43
43
 
44
44
  block_id = clean_id
45
- block_title = extract_title(clean_id, jsonified_record_response)
46
45
  block_type = extract_type(clean_id, jsonified_record_response)
47
46
  block_parent_id = extract_parent_id(clean_id, jsonified_record_response)
48
47
 
49
- raise 'the URL or ID passed to the get_page method must be that of a Page Block.' if block_type != 'page'
50
-
51
- PageBlock.new(block_id, block_title, block_parent_id)
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
52
66
  end
53
67
 
54
68
  def children(url_or_id = @id)
@@ -236,5 +250,47 @@ module NotionAPI
236
250
  raise ArgumentError, 'Expected a Notion page URL or a page ID. Please consult the documentation for further information.'
237
251
  end
238
252
  end
253
+
254
+ def extract_collection_schema(collection_id, view_id, response = {})
255
+ # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
256
+ # ! collection_id -> the collection ID : ``str``
257
+ # ! view_id -> the view ID : ``str``
258
+ cookies = Core.options['cookies']
259
+ headers = Core.options['headers']
260
+
261
+ if response.empty?
262
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
263
+
264
+ request_url = URLS[:GET_COLLECTION]
265
+ response = HTTParty.post(
266
+ request_url,
267
+ body: query_collection_hash.to_json,
268
+ cookies: cookies,
269
+ headers: headers
270
+ )
271
+ response['recordMap']['collection'][collection_id]['value']['schema']
272
+ else
273
+ response['collection'][collection_id]['value']['schema']
274
+ end
275
+ end
276
+
277
+ def extract_collection_data(collection_id, view_id)
278
+ # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
279
+ # ! collection_id -> the collection ID : ``str``
280
+ # ! view_id -> the view ID : ``str``
281
+ cookies = Core.options['cookies']
282
+ headers = Core.options['headers']
283
+
284
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
285
+
286
+ request_url = URLS[:GET_COLLECTION]
287
+ response = HTTParty.post(
288
+ request_url,
289
+ body: query_collection_hash.to_json,
290
+ cookies: cookies,
291
+ headers: headers
292
+ )
293
+ response['recordMap']
294
+ end
239
295
  end
240
296
  end
@@ -1,228 +1,325 @@
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
+ if !options.include?(data[col_name])
66
+ create_new_option = Utils::CollectionViewComponents.add_new_option(col_map[col_name.to_s], data[col_name], @collection_id)
67
+ operations.push(create_new_option)
68
+ end
62
69
  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)
70
+ 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"])
71
+ operations.push(child_component)
77
72
  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
124
- 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
- }
73
+
74
+ request_url = URLS[:UPDATE_BLOCK]
75
+ request_body = build_payload(operations, request_ids)
76
+ response = HTTParty.post(
77
+ request_url,
78
+ body: request_body.to_json,
79
+ cookies: cookies,
80
+ headers: headers,
81
+ )
82
+
83
+ 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}.
84
+ Please try again, and if issues persist open an issue in GitHub."; end
85
+
86
+ NotionAPI::CollectionViewRow.new(new_block_id, @parent_id, @collection_id, @view_id)
87
+ end
88
+
89
+ def add_property(name, type)
90
+ # ! add a property (column) to the table.
91
+ # ! name -> name of the property : ``str``
92
+ # ! type -> type of the property : ``str``
93
+ cookies = Core.options["cookies"]
94
+ headers = Core.options["headers"]
95
+
96
+ request_id = extract_id(SecureRandom.hex(16))
97
+ transaction_id = extract_id(SecureRandom.hex(16))
98
+ space_id = extract_id(SecureRandom.hex(16))
99
+
100
+ request_ids = {
101
+ request_id: request_id,
102
+ transaction_id: transaction_id,
103
+ space_id: space_id,
104
+ }
105
+
106
+ # create updated schema
107
+ schema = extract_collection_schema(@collection_id, @view_id)
108
+ schema[name] = {
109
+ name: name,
110
+ type: type,
111
+ }
112
+ new_schema = {
113
+ schema: schema,
114
+ }
115
+
116
+ add_collection_property = Utils::CollectionViewComponents.add_collection_property(@collection_id, new_schema)
117
+
118
+ operations = [
119
+ add_collection_property,
120
+ ]
121
+
122
+ request_url = URLS[:UPDATE_BLOCK]
123
+ request_body = build_payload(operations, request_ids)
124
+ response = HTTParty.post(
125
+ request_url,
126
+ body: request_body.to_json,
127
+ cookies: cookies,
128
+ headers: headers,
129
+ )
130
+ 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}.
131
+ Please try again, and if issues persist open an issue in GitHub."; end
132
+
133
+ true
134
+ end
135
+
136
+ def row(row_id)
137
+ # ! retrieve a row from a CollectionView Table.
138
+ # ! row_id -> the ID for the row to retrieve: ``str``
139
+ clean_id = extract_id(row_id)
140
+
141
+ request_body = {
142
+ pageId: clean_id,
143
+ chunkNumber: 0,
144
+ limit: 100,
145
+ verticalColumns: false,
146
+ }
147
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
148
+
149
+ i = 0
150
+ while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
151
+ return {} if i >= 10
152
+
137
153
  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
154
+ i += 1
152
155
  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
-
156
+
157
+ collection_data = extract_collection_data(@collection_id, @view_id)
158
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
159
+ column_mappings = schema.keys
160
+ column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
161
+
162
+ collection_row = CollectionViewRow.new(row_id, @parent_id, @collection_id, @view_id)
163
+ collection_row.instance_variable_set(:@column_names, column_names)
164
+ CollectionViewRow.class_eval { attr_reader :column_names }
165
+
166
+ row_data = collection_data["block"][collection_row.id]
167
+ create_singleton_methods_and_instance_variables(collection_row, row_data)
168
+
169
+ collection_row
170
+ end
171
+
172
+ def row_ids
173
+ # ! retrieve all Collection View table rows.
174
+ clean_id = extract_id(@id)
175
+
176
+ request_body = {
177
+ pageId: clean_id,
178
+ chunkNumber: 0,
179
+ limit: 100,
180
+ verticalColumns: false,
181
+ }
182
+
183
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
184
+ i = 0
185
+ while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
186
+ return {} if i >= 10
187
+
165
188
  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']
189
+ i += 1
206
190
  end
191
+
192
+ jsonified_record_response["collection_view"][@view_id]["value"]["page_sort"]
207
193
  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
194
+
195
+ def rows
196
+ # ! returns all rows as instantiated class instances.
197
+ row_id_array = row_ids
198
+ parent_id = @parent_id
199
+ collection_id = @collection_id
200
+ view_id = @view_id
201
+ collection_data = extract_collection_data(@collection_id, @view_id)
202
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
203
+ column_mappings = schema.keys
204
+ column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
205
+
206
+ row_instances = row_id_array.map { |row_id| NotionAPI::CollectionViewRow.new(row_id, parent_id, collection_id, view_id) }
207
+ clean_row_instances = row_instances.filter { |row| collection_data["block"][row.id] }
208
+ clean_row_instances.each { |row| row.instance_variable_set(:@column_names, column_names) }
209
+ CollectionViewRow.class_eval { attr_reader :column_names }
210
+
211
+ clean_row_instances.each do |collection_row|
212
+ row_data = collection_data["block"][collection_row.id]
213
+ create_singleton_methods_and_instance_variables(collection_row, row_data)
218
214
  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
215
+ clean_row_instances
216
+ end
217
+ def create_singleton_methods_and_instance_variables(row, row_data)
218
+ # ! creates singleton methods for each property in a CollectionView.
219
+ # ! row -> the block ID of the 'row' to retrieve: ``str``
220
+ # ! row_data -> the data corresponding to that row, should be key-value pairs where the keys are the columns: ``hash``
221
+ collection_data = extract_collection_data(@collection_id, @view_id)
222
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
223
+ column_mappings = schema.keys
224
+ column_hash = {}
225
+ column_names = column_mappings.map { |mapping| column_hash[mapping] = schema[mapping]["name"].downcase }
226
+
227
+ column_hash.keys.each_with_index do |column, i|
228
+ # loop over the column names...
229
+ # set instance variables for each column, allowing the dev to 'read' the column value
230
+ cleaned_column = column_hash[column].split(" ").join("_").downcase.to_sym
231
+
232
+ # p row_data["value"]["properties"][column_mappings[i]], !(row_data["value"]["properties"][column] or row_data["value"]["properties"][column_mappings[i]])
233
+ if row_data["value"]["properties"].nil? or row_data["value"]["properties"][column].nil?
234
+ value = ""
235
+ else
236
+ value = row_data["value"]["properties"][column][0][0]
237
+ end
238
+
239
+ row.instance_variable_set("@#{cleaned_column}", value)
240
+ CollectionViewRow.class_eval { attr_reader cleaned_column }
241
+ # then, define singleton methods for each column that are used to update the table cell
242
+ row.define_singleton_method("#{cleaned_column}=") do |new_value|
243
+ # neat way to get the name of the currently invoked method...
244
+ parsed_method = __method__.to_s[0...-1].split("_").join(" ")
245
+ cookies = Core.options["cookies"]
246
+ headers = Core.options["headers"]
247
+
248
+ request_id = extract_id(SecureRandom.hex(16))
249
+ transaction_id = extract_id(SecureRandom.hex(16))
250
+ space_id = extract_id(SecureRandom.hex(16))
251
+
252
+ request_ids = {
253
+ request_id: request_id,
254
+ transaction_id: transaction_id,
255
+ space_id: space_id,
256
+ }
257
+
258
+ update_property_value = Utils::CollectionViewComponents.update_property_value(@id, column_hash.key(parsed_method), new_value)
259
+
260
+ operations = [
261
+ update_property_value,
262
+ ]
263
+
264
+ request_url = URLS[:UPDATE_BLOCK]
265
+ request_body = build_payload(operations, request_ids)
266
+ response = HTTParty.post(
267
+ request_url,
268
+ body: request_body.to_json,
269
+ cookies: cookies,
270
+ headers: headers,
271
+ )
272
+ 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}.
273
+ Please try again, and if issues persist open an issue in GitHub.";end
274
+
275
+ # set the instance variable to the updated value!
276
+ _ = row.instance_variable_set("@#{__method__.to_s[0...-1]}", new_value)
277
+ row
278
+ end
226
279
  end
227
280
  end
228
- end
281
+ end
282
+
283
+ # class that represents each row in a CollectionView
284
+ class CollectionViewRow < Core
285
+ @notion_type = "table_row"
286
+ @type = "table_row"
287
+
288
+ def type
289
+ NotionAPI::CollectionViewRow.notion_type
290
+ end
291
+
292
+ def inspect
293
+ "CollectionViewRow - id: #{self.id} - parent id: #{self.parent_id}"
294
+ end
295
+
296
+ class << self
297
+ attr_reader :notion_type, :type, :parent_id
298
+ end
299
+
300
+ attr_reader :parent_id, :id
301
+
302
+ def initialize(id, parent_id, collection_id, view_id)
303
+ @id = id
304
+ @parent_id = parent_id
305
+ @collection_id = collection_id
306
+ @view_id = view_id
307
+ end
308
+ end
309
+
310
+ # class that represents a CollectionViewPage, inheriting all properties from CollectionView
311
+ class CollectionViewPage < CollectionView
312
+ @notion_type = "collection_view_page"
313
+ @type = "collection_view_page"
314
+
315
+ def type
316
+ NotionAPI::CollectionViewRow.notion_type
317
+ end
318
+
319
+ class << self
320
+ attr_reader :notion_type, :type, :parent_id
321
+ end
322
+
323
+ attr_reader :parent_id, :id
324
+ end
325
+ 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,49 @@ 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
+ colors = ["default", "gray", "brown", "orange", "yellow", "green", "blue", "purple", "pink", "red"]
495
+ random_color = colors[rand(0...colors.length)]
496
+ table = "collection"
497
+ path = ["schema", column, "options"]
498
+ command = "keyedObjectListAfter"
499
+ args = {
500
+ "value": {
501
+ "id": SecureRandom.hex(16),
502
+ "value": value,
503
+ "color": random_color
504
+ }
505
+ }
506
+
507
+ {
508
+ table: table,
509
+ id: collection_id,
454
510
  command: command,
455
- args: mapping == "date" ? [["‣",[["d",{"type": "date","start_date": value}]]]] : [[value]],
511
+ path: path,
512
+ args: args,
456
513
  }
457
514
  end
458
515
 
@@ -505,45 +562,45 @@ module Utils
505
562
  args["format"] = {
506
563
  "table_properties" => [
507
564
  {
508
- "property" => "title",
509
- "visible" => true,
510
- "width" => 280,
511
- },
565
+ "property" => "title",
566
+ "visible" => true,
567
+ "width" => 280,
568
+ },
512
569
  {
513
- "property" => "aliases",
514
- "visible" => true,
515
- "width" => 200,
516
- },
570
+ "property" => "aliases",
571
+ "visible" => true,
572
+ "width" => 200,
573
+ },
517
574
  {
518
- "property" => "category",
519
- "visible" => true,
520
- "width" => 200,
521
- },
575
+ "property" => "category",
576
+ "visible" => true,
577
+ "width" => 200,
578
+ },
522
579
  {
523
- "property" => "description",
524
- "visible" => true,
525
- "width" => 200,
526
- },
580
+ "property" => "description",
581
+ "visible" => true,
582
+ "width" => 200,
583
+ },
527
584
  {
528
- "property" => "ios_version",
529
- "visible" => true,
530
- "width" => 200,
531
- },
585
+ "property" => "ios_version",
586
+ "visible" => true,
587
+ "width" => 200,
588
+ },
589
+ {
590
+ "property" => "tags",
591
+ "visible" => true,
592
+ "width" => 200,
593
+ },
532
594
  {
533
- "property" => "tags",
534
- "visible" => true,
535
- "width" => 200,
536
- },
537
- {
538
595
  "property" => "phone",
539
596
  "visible" => true,
540
597
  "width" => 200,
541
598
  },
542
599
  {
543
- "property" => "unicode_version",
544
- "visible" => true,
545
- "width" => 200,
546
- }
600
+ "property" => "unicode_version",
601
+ "visible" => true,
602
+ "width" => 200,
603
+ },
547
604
  ],
548
605
  }
549
606
  {
@@ -554,6 +611,37 @@ module Utils
554
611
  args: args,
555
612
  }
556
613
  end
614
+
615
+ def self.update_property_value(page_id, column_name, new_value)
616
+ # ! update the specified column_name to new_value
617
+ # ! page_id -> the ID of the page: ``str``
618
+ # ! column_name -> the name of the column ["property"] to update: ``str``
619
+ # ! new_value -> the new value to assign to that column ["property"]: ``str``
620
+ table = "block"
621
+ path = [
622
+ "properties",
623
+ column_name,
624
+ ]
625
+ command = "set"
626
+ args = [[
627
+ new_value,
628
+ ]]
629
+
630
+ {
631
+ id: page_id,
632
+ table: table,
633
+ path: path,
634
+ command: command,
635
+ args: args,
636
+ }
637
+ end
638
+ end
639
+
640
+ class SchemaTypeError < StandardError
641
+ def initialize(msg="Custom exception that is raised when an invalid property type is passed as a mapping.", exception_type="schema_type")
642
+ @exception_type = exception_type
643
+ super(msg)
644
+ end
557
645
  end
558
646
 
559
647
  def build_payload(operations, request_ids)
@@ -576,4 +664,4 @@ module Utils
576
664
  }
577
665
  payload
578
666
  end
579
- end
667
+ end
@@ -1,3 +1,3 @@
1
1
  module NotionAPI
2
- VERSION = '1.0.3'
2
+ VERSION = '1.1.0'
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.3
4
+ version: 1.1.0
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-29 00:00:00.000000000 Z
11
+ date: 2020-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty