Skip to content

Commit 6f08931

Browse files
Add metadata query (#487)
1 parent 00f3f12 commit 6f08931

File tree

10 files changed

+630
-16
lines changed

10 files changed

+630
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
## Next Release
44

5-
- Fixed an issue where an error is thrown during a retry when a response is not returned by the previous call (#476).
5+
- Fixed an issue where an error is thrown during a retry when a response is not returned by the previous call ([#476](https://github.com/box/box-node-sdk/pull/76)).
6+
- Added the ability to [query](./docs/metadata.md#query) Box items based on their metadata ([#487](https://github.com/box/box-node-sdk/pull/487)).
67

78
## 1.31.0 [2020-02-13]
89

docs/metadata.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ in a flexible way, without pre-defined template structure.
4343
- [Get All Cascade Policies For a Folder](#get-all-cascade-policies-for-a-folder)
4444
- [Force Apply Cascade Policy](#force-apply-cascade-policy)
4545
- [Delete Cascade Policy](#delete-cascade-policy)
46+
- [Query](#query)
4647

4748
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
4849

@@ -902,3 +903,138 @@ client.metadata.deleteCascadePolicy(policyID)
902903
```
903904

904905
[delete-cascade-policy]: http://opensource.box.com/box-node-sdk/jsdoc/Metadata.html#deleteCascadePolicy
906+
907+
Query
908+
---------------------
909+
910+
To query box items based on their metadata, call [`metadata.query(from, ancestorFolderId, options, callback)`][query] with the metadata template and the folder ID to restrain the query. Additional options like the a specific query, a marker, etc. can be passed in through the options object.
911+
912+
```js
913+
var from = 'enterprise_12345.someTemplate',
914+
ancestorFolderId = '5555',
915+
options = {
916+
query: 'amount >= :arg',
917+
queryParams: {
918+
arg: 100
919+
},
920+
useIndex: 'amountAsc',
921+
orderBy: [
922+
{
923+
field_key: 'amount',
924+
direction: 'asc'
925+
}
926+
],
927+
limit: 100,
928+
marker: 'AAAAAmVYB1FWec8GH6yWu2nwmanfMh07IyYInaa7DZDYjgO1H4KoLW29vPlLY173OKsci6h6xGh61gG73gnaxoS+o0BbI1/h6le6cikjlupVhASwJ2Cj0tOD9wlnrUMHHw3/ISf+uuACzrOMhN6d5fYrbidPzS6MdhJOejuYlvsg4tcBYzjauP3+VU51p77HFAIuObnJT0ff'
929+
};
930+
client.metadata.query(from, ancestorFolderId, options)
931+
.then(items => {
932+
/* items -> {
933+
"entries": [
934+
{
935+
"item": {
936+
"type": "file",
937+
"id": "1617554169109",
938+
"file_version": {
939+
"type": "file_version",
940+
"id": "1451884469385",
941+
"sha1": "69888bb1bff455d1b2f8afea75ed1ff0b4879bf6"
942+
},
943+
"sequence_id": "0",
944+
"etag": "0",
945+
"sha1": "69888bb1bff455d1b2f8afea75ed1ff0b4879bf6",
946+
"name": "My Contract.docx",
947+
"description": "",
948+
"size": 25600,
949+
"path_collection": {
950+
"total_count": 4,
951+
"entries": [
952+
{
953+
"type": "folder",
954+
"id": "0",
955+
"sequence_id": null,
956+
"etag": null,
957+
"name": "All Files"
958+
},
959+
{
960+
"type": "folder",
961+
"id": "15017998644",
962+
"sequence_id": "0",
963+
"etag": "0",
964+
"name": "Contracts"
965+
},
966+
{
967+
"type": "folder",
968+
"id": "15286891196",
969+
"sequence_id": "1",
970+
"etag": "1",
971+
"name": "North America"
972+
},
973+
{
974+
"type": "folder",
975+
"id": "16125613433",
976+
"sequence_id": "0",
977+
"etag": "0",
978+
"name": "2017"
979+
}
980+
]
981+
},
982+
"created_at": "2017-04-20T12:55:27-07:00",
983+
"modified_at": "2017-04-20T12:55:27-07:00",
984+
"trashed_at": null,
985+
"purged_at": null,
986+
"content_created_at": "2017-01-06T17:59:01-08:00",
987+
"content_modified_at": "2017-01-06T17:59:01-08:00",
988+
"created_by": {
989+
"type": "user",
990+
"id": "193973366",
991+
"name": "Box Admin",
992+
"login": "admin@company.com"
993+
},
994+
"modified_by": {
995+
"type": "user",
996+
"id": "193973366",
997+
"name": "Box Admin",
998+
"login": "admin@company.com"
999+
},
1000+
"owned_by": {
1001+
"type": "user",
1002+
"id": "193973366",
1003+
"name": "Box Admin",
1004+
"login": "admin@company.com"
1005+
},
1006+
"shared_link": null,
1007+
"parent": {
1008+
"type": "folder",
1009+
"id": "16125613433",
1010+
"sequence_id": "0",
1011+
"etag": "0",
1012+
"name": "2017"
1013+
},
1014+
"item_status": "active"
1015+
},
1016+
"metadata": {
1017+
"enterprise_123456": {
1018+
"someTemplate": {
1019+
"$parent": "file_161753469109",
1020+
"$version": 0,
1021+
"customerName": "Phoenix Corp",
1022+
"$type": "someTemplate-3d5fcaca-f496-4bb6-9046-d25c37bc5594",
1023+
"$typeVersion": 0,
1024+
"$id": "ba52e2cc-371d-4659-8d53-50f1ac642e35",
1025+
"amount": 100,
1026+
"claimDate": "2016-04-10T00:00:00Z",
1027+
"region": "West",
1028+
"$typeScope": "enterprise_123456"
1029+
}
1030+
}
1031+
}
1032+
}
1033+
],
1034+
"next_marker": ""
1035+
}
1036+
*/
1037+
});
1038+
```
1039+
1040+
[query]: http://opensource.box.com/box-node-sdk/jsdoc/Metadata.html#query

lib/api-request.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ APIRequest.prototype.getResponseStream = function() {
224224
* @private
225225
*/
226226
APIRequest.prototype._handleResponse = function(err, response) {
227-
228227
// Clean sensitive headers here to prevent the user from accidentily using/logging them in prod
229228
cleanSensitiveHeaders(this.request);
230229

lib/managers/folders.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Folders.prototype.get = function(folderID, options, callback) {
6161
* @param {string} folderID - Box ID of the folder being requested
6262
* @param {Object} [options] - Additional options for the request. Can be left null in most cases.
6363
* @param {Function} [callback] - Passed the folder information if it was acquired successfully
64-
* @returns {Promise<Object>} A prmoise resolving to the collection of the items in the folder
64+
* @returns {Promise<Object>} A promise resolving to the collection of the items in the folder
6565
*/
6666
Folders.prototype.getItems = function(folderID, options, callback) {
6767
var params = {

lib/managers/metadata.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
// -----------------------------------------------------------------------------
2929
// Requirements
3030
// -----------------------------------------------------------------------------
31-
var urlPath = require('../util/url-path');
31+
var urlPath = require('../util/url-path'),
32+
merge = require('merge-options');
3233

3334
// -----------------------------------------------------------------------------
3435
// Private
@@ -38,7 +39,8 @@ var PROPERTIES_TEMPLATE = 'properties',
3839
SCHEMA_SUBRESOURCE = 'schema',
3940
ENTERPRISE_SCOPE = 'enterprise',
4041
GLOBAL_SCOPE = 'global',
41-
CASCADE_POLICIES_PATH = '/metadata_cascade_policies';
42+
CASCADE_POLICIES_PATH = '/metadata_cascade_policies',
43+
QUERY_PATH = '/metadata_queries/execute_read';
4244

4345
// -----------------------------------------------------------------------------
4446
// Public
@@ -313,6 +315,31 @@ Metadata.prototype = {
313315
};
314316

315317
return this.client.wrapWithDefaultHandler(this.client.post)(apiPath, params, callback);
318+
},
319+
320+
/**
321+
* Query Box items by their metadata
322+
*
323+
* API Endpoint: '/metadata_queries/execute_read'
324+
* Method: POST
325+
*
326+
* @param {string} from - The template used in the query. Must be in the form scope.templateKey
327+
* @param {string} ancestorFolderId - The folder_id to which to restrain the query
328+
* @param {Object} options - Query options
329+
* @param {Function} [callback] - Passed a collection of items and their associated metadata
330+
* @returns {Promise<void>} Promise resolving to a collection of items and their associated metadata
331+
*/
332+
query(from, ancestorFolderId, options, callback) {
333+
var body = {
334+
from,
335+
ancestor_folder_id: ancestorFolderId
336+
};
337+
338+
var params = {
339+
body: merge(body, options)
340+
};
341+
342+
return this.client.wrapWithDefaultHandler(this.client.post)(QUERY_PATH, params, callback);
316343
}
317344
};
318345

lib/util/paging-iterator.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ class PagingIterator {
5858
* @returns {boolean} Whether the response is iterable
5959
*/
6060
static isIterable(response) {
61-
var isGetRequest = (response.request && response.request.method === 'GET'),
61+
var isGetOrPostRequest = (response.request && (response.request.method === 'GET' || response.request.method === 'POST')),
6262
hasEntries = (response.body && Array.isArray(response.body.entries)),
6363
notEventStream = (response.body && !response.body.next_stream_position);
6464

65-
return Boolean(isGetRequest && hasEntries && notEventStream);
65+
return Boolean(isGetOrPostRequest && hasEntries && notEventStream);
6666
}
6767

6868
/**
@@ -80,7 +80,6 @@ class PagingIterator {
8080

8181

8282
var data = response.body;
83-
8483
if (Number.isSafeInteger(data.offset)) {
8584
this.nextField = PAGING_MODES.OFFSET;
8685
this.nextValue = data.offset;
@@ -102,6 +101,13 @@ class PagingIterator {
102101
headers: response.request.headers,
103102
qs: querystring.parse(response.request.uri.query)
104103
};
104+
if (response.request.body) {
105+
if (Object.prototype.toString.call(response.request.body) === '[object Object]') {
106+
this.options.body = response.request.body;
107+
} else {
108+
this.options.body = JSON.parse(response.request.body);
109+
}
110+
}
105111

106112
// querystring.parse() makes everything a string, ensure numeric params are the correct type
107113
if (this.options.qs.limit) {
@@ -112,7 +118,12 @@ class PagingIterator {
112118
}
113119

114120
delete this.options.headers.Authorization;
115-
this.fetch = client.get.bind(client, href);
121+
if (response.request.method === 'GET') {
122+
this.fetch = client.get.bind(client, href);
123+
}
124+
if (response.request.method === 'POST') {
125+
this.fetch = client.post.bind(client, href);
126+
}
116127
this.buffer = response.body.entries;
117128
this.queue = new PromiseQueue(1, Infinity);
118129
this._updatePaging(response);
@@ -146,8 +157,13 @@ class PagingIterator {
146157
this.done = true;
147158
}
148159
}
149-
150-
this.options.qs[this.nextField] = this.nextValue;
160+
if (response.request.method === 'GET') {
161+
this.options.qs[this.nextField] = this.nextValue;
162+
} else if (response.request.method === 'POST') {
163+
this.options.body[this.nextField] = this.nextValue;
164+
let bodyString = JSON.stringify(this.options.body);
165+
this.options.headers['content-length'] = bodyString.length;
166+
}
151167
}
152168

153169
/**

0 commit comments

Comments
 (0)