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 +4 -4
- data/README.md +48 -14
- data/lib/notion_api/core.rb +60 -4
- data/lib/notion_api/notion_types/collection_view_blocks.rb +314 -217
- data/lib/notion_api/notion_types/template.rb +9 -1
- data/lib/notion_api/utils.rb +144 -56
- data/lib/notion_api/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c99389e680f113f6288c7809f95c3c4b86b894c7c0b741dd774d5738b398b241
|
4
|
+
data.tar.gz: 1f995b8c4d27874a248bf99023efe02a96198231e0a7cfb51d139bbae45f6cac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
- [
|
12
|
-
|
13
|
-
- [
|
14
|
-
- [
|
15
|
-
|
16
|
-
|
17
|
-
- [
|
18
|
-
|
19
|
-
|
20
|
-
- [Creating New
|
21
|
-
- [
|
22
|
-
|
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
|
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:
|
data/lib/notion_api/core.rb
CHANGED
@@ -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
|
50
|
-
|
51
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
request_body
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
-
|
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)
|
data/lib/notion_api/utils.rb
CHANGED
@@ -200,14 +200,14 @@ module Utils
|
|
200
200
|
|
201
201
|
args = if command == "listAfter"
|
202
202
|
{
|
203
|
-
|
204
|
-
|
205
|
-
|
203
|
+
after: target || block_id,
|
204
|
+
id: new_block_id || block_id,
|
205
|
+
}
|
206
206
|
else
|
207
207
|
{
|
208
|
-
|
209
|
-
|
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
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
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
|
-
|
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
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
565
|
+
"property" => "title",
|
566
|
+
"visible" => true,
|
567
|
+
"width" => 280,
|
568
|
+
},
|
512
569
|
{
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
570
|
+
"property" => "aliases",
|
571
|
+
"visible" => true,
|
572
|
+
"width" => 200,
|
573
|
+
},
|
517
574
|
{
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
575
|
+
"property" => "category",
|
576
|
+
"visible" => true,
|
577
|
+
"width" => 200,
|
578
|
+
},
|
522
579
|
{
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
580
|
+
"property" => "description",
|
581
|
+
"visible" => true,
|
582
|
+
"width" => 200,
|
583
|
+
},
|
527
584
|
{
|
528
|
-
|
529
|
-
|
530
|
-
|
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
|
-
|
544
|
-
|
545
|
-
|
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
|
data/lib/notion_api/version.rb
CHANGED
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
|
+
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
|
+
date: 2020-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|