@webex/internal-plugin-search 3.0.0-beta.3 → 3.0.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/config.js +0 -3
- package/dist/config.js.map +1 -1
- package/dist/index.js +3 -20
- package/dist/index.js.map +1 -1
- package/dist/search.js +10 -39
- package/dist/search.js.map +1 -1
- package/package.json +10 -10
- package/src/config.js +1 -1
- package/src/index.js +15 -14
- package/src/search.js +31 -34
- package/test/integration/spec/search.js +298 -230
package/README.md
CHANGED
|
@@ -21,14 +21,12 @@ npm install --save @webex/internal-plugin-search
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
23
|
```js
|
|
24
|
-
|
|
25
24
|
import '@webex/internal-plugin-search';
|
|
26
25
|
|
|
27
26
|
import WebexCore from '@webex/webex-core';
|
|
28
27
|
|
|
29
28
|
const webex = new WebexCore();
|
|
30
|
-
webex.internal.search.WHATEVER
|
|
31
|
-
|
|
29
|
+
webex.internal.search.WHATEVER;
|
|
32
30
|
```
|
|
33
31
|
|
|
34
32
|
## Maintainers
|
package/dist/config.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
|
|
4
|
-
|
|
5
4
|
_Object$defineProperty(exports, "__esModule", {
|
|
6
5
|
value: true
|
|
7
6
|
});
|
|
8
|
-
|
|
9
7
|
exports.default = void 0;
|
|
10
|
-
|
|
11
8
|
/*!
|
|
12
9
|
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
13
10
|
*/
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["search"],"sources":["config.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nexport default {\n search: {}
|
|
1
|
+
{"version":3,"names":["search"],"sources":["config.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nexport default {\n search: {},\n};\n"],"mappings":";;;;;;;AAAA;AACA;AACA;AAFA,eAIe;EACbA,MAAM,EAAE,CAAC;AACX,CAAC;AAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,37 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
|
|
4
|
-
|
|
5
4
|
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
|
|
6
|
-
|
|
7
5
|
_Object$defineProperty(exports, "__esModule", {
|
|
8
6
|
value: true
|
|
9
7
|
});
|
|
10
|
-
|
|
11
8
|
_Object$defineProperty(exports, "default", {
|
|
12
9
|
enumerable: true,
|
|
13
10
|
get: function get() {
|
|
14
11
|
return _search.default;
|
|
15
12
|
}
|
|
16
13
|
});
|
|
17
|
-
|
|
18
14
|
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
|
|
19
|
-
|
|
20
15
|
var _has2 = _interopRequireDefault(require("lodash/has"));
|
|
21
|
-
|
|
16
|
+
require("@webex/internal-plugin-conversation");
|
|
17
|
+
require("@webex/internal-plugin-encryption");
|
|
22
18
|
var _webexCore = require("@webex/webex-core");
|
|
23
|
-
|
|
24
19
|
var _search = _interopRequireDefault(require("./search"));
|
|
25
|
-
|
|
26
20
|
var _config = _interopRequireDefault(require("./config"));
|
|
27
|
-
|
|
28
|
-
require("@webex/internal-plugin-conversation");
|
|
29
|
-
|
|
30
|
-
require("@webex/internal-plugin-encryption");
|
|
31
|
-
|
|
32
21
|
/*!
|
|
33
22
|
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
34
23
|
*/
|
|
24
|
+
|
|
35
25
|
(0, _webexCore.registerInternalPlugin)('search', _search.default, {
|
|
36
26
|
config: _config.default,
|
|
37
27
|
payloadTransformer: {
|
|
@@ -42,20 +32,16 @@ require("@webex/internal-plugin-encryption");
|
|
|
42
32
|
if (!(0, _has2.default)(options, 'body.query')) {
|
|
43
33
|
return _promise.default.resolve(false);
|
|
44
34
|
}
|
|
45
|
-
|
|
46
35
|
if (!(0, _has2.default)(options, 'body.searchEncryptionKeyUrl')) {
|
|
47
36
|
return _promise.default.resolve(false);
|
|
48
37
|
}
|
|
49
|
-
|
|
50
38
|
if (options.service === 'argonaut') {
|
|
51
39
|
return _promise.default.resolve(true);
|
|
52
40
|
}
|
|
53
|
-
|
|
54
41
|
if (options.url) {
|
|
55
42
|
var service = ctx.webex.internal.services.getServiceFromUrl(options.url);
|
|
56
43
|
return _promise.default.resolve(service && service.name === 'argonaut');
|
|
57
44
|
}
|
|
58
|
-
|
|
59
45
|
return _promise.default.resolve(false);
|
|
60
46
|
},
|
|
61
47
|
extract: function extract(options) {
|
|
@@ -69,16 +55,13 @@ require("@webex/internal-plugin-encryption");
|
|
|
69
55
|
if (!res) {
|
|
70
56
|
return _promise.default.resolve(false);
|
|
71
57
|
}
|
|
72
|
-
|
|
73
58
|
if (response.options.service === 'argonaut') {
|
|
74
59
|
return _promise.default.resolve(true);
|
|
75
60
|
}
|
|
76
|
-
|
|
77
61
|
if (response.options.url) {
|
|
78
62
|
var service = ctx.webex.internal.services.getServiceFromUrl(response.options.url);
|
|
79
63
|
return _promise.default.resolve(service && service.name === 'argonaut');
|
|
80
64
|
}
|
|
81
|
-
|
|
82
65
|
return _promise.default.resolve(false);
|
|
83
66
|
});
|
|
84
67
|
},
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["registerInternalPlugin","Search","config","payloadTransformer","predicates","name","direction","test","ctx","options","resolve","service","url","webex","internal","services","getServiceFromUrl","extract","body","response","then","res","activities","items","transforms","fn","object","encryption","encryptText","searchEncryptionKeyUrl","query","q"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerInternalPlugin} from '@webex/webex-core';\nimport {has} from 'lodash';\n\nimport Search from './search';\nimport config from './config';\n\
|
|
1
|
+
{"version":3,"names":["registerInternalPlugin","Search","config","payloadTransformer","predicates","name","direction","test","ctx","options","resolve","service","url","webex","internal","services","getServiceFromUrl","extract","body","response","then","res","activities","items","transforms","fn","object","encryption","encryptText","searchEncryptionKeyUrl","query","q"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\nimport '@webex/internal-plugin-conversation';\nimport '@webex/internal-plugin-encryption';\n\nimport {registerInternalPlugin} from '@webex/webex-core';\nimport {has} from 'lodash';\n\nimport Search from './search';\nimport config from './config';\n\nregisterInternalPlugin('search', Search, {\n config,\n payloadTransformer: {\n predicates: [\n {\n name: 'encryptSearchQuery',\n direction: 'outbound',\n test(ctx, options) {\n if (!has(options, 'body.query')) {\n return Promise.resolve(false);\n }\n\n if (!has(options, 'body.searchEncryptionKeyUrl')) {\n return Promise.resolve(false);\n }\n\n if (options.service === 'argonaut') {\n return Promise.resolve(true);\n }\n\n if (options.url) {\n const service = ctx.webex.internal.services.getServiceFromUrl(options.url);\n\n return Promise.resolve(service && service.name === 'argonaut');\n }\n\n return Promise.resolve(false);\n },\n extract(options) {\n return Promise.resolve(options.body);\n },\n },\n {\n name: 'transformObjectArray',\n direction: 'inbound',\n test(ctx, response) {\n return Promise.resolve(has(response, 'body.activities.items[0].objectType')).then(\n (res) => {\n if (!res) {\n return Promise.resolve(false);\n }\n\n if (response.options.service === 'argonaut') {\n return Promise.resolve(true);\n }\n\n if (response.options.url) {\n const service = ctx.webex.internal.services.getServiceFromUrl(response.options.url);\n\n return Promise.resolve(service && service.name === 'argonaut');\n }\n\n return Promise.resolve(false);\n }\n );\n },\n extract(response) {\n return Promise.resolve(response.body.activities.items);\n },\n },\n ],\n transforms: [\n {\n name: 'encryptSearchQuery',\n direction: 'outbound',\n fn(ctx, object) {\n return ctx.webex.internal.encryption\n .encryptText(object.searchEncryptionKeyUrl, object.query)\n .then((q) => {\n object.query = q;\n });\n },\n },\n ],\n },\n});\n\nexport {default} from './search';\n"],"mappings":";;;;;;;;;;;;;;;AAGA;AACA;AAEA;AAGA;AACA;AAVA;AACA;AACA;;AAUA,IAAAA,iCAAsB,EAAC,QAAQ,EAAEC,eAAM,EAAE;EACvCC,MAAM,EAANA,eAAM;EACNC,kBAAkB,EAAE;IAClBC,UAAU,EAAE,CACV;MACEC,IAAI,EAAE,oBAAoB;MAC1BC,SAAS,EAAE,UAAU;MACrBC,IAAI,gBAACC,GAAG,EAAEC,OAAO,EAAE;QACjB,IAAI,CAAC,mBAAIA,OAAO,EAAE,YAAY,CAAC,EAAE;UAC/B,OAAO,iBAAQC,OAAO,CAAC,KAAK,CAAC;QAC/B;QAEA,IAAI,CAAC,mBAAID,OAAO,EAAE,6BAA6B,CAAC,EAAE;UAChD,OAAO,iBAAQC,OAAO,CAAC,KAAK,CAAC;QAC/B;QAEA,IAAID,OAAO,CAACE,OAAO,KAAK,UAAU,EAAE;UAClC,OAAO,iBAAQD,OAAO,CAAC,IAAI,CAAC;QAC9B;QAEA,IAAID,OAAO,CAACG,GAAG,EAAE;UACf,IAAMD,OAAO,GAAGH,GAAG,CAACK,KAAK,CAACC,QAAQ,CAACC,QAAQ,CAACC,iBAAiB,CAACP,OAAO,CAACG,GAAG,CAAC;UAE1E,OAAO,iBAAQF,OAAO,CAACC,OAAO,IAAIA,OAAO,CAACN,IAAI,KAAK,UAAU,CAAC;QAChE;QAEA,OAAO,iBAAQK,OAAO,CAAC,KAAK,CAAC;MAC/B,CAAC;MACDO,OAAO,mBAACR,OAAO,EAAE;QACf,OAAO,iBAAQC,OAAO,CAACD,OAAO,CAACS,IAAI,CAAC;MACtC;IACF,CAAC,EACD;MACEb,IAAI,EAAE,sBAAsB;MAC5BC,SAAS,EAAE,SAAS;MACpBC,IAAI,gBAACC,GAAG,EAAEW,QAAQ,EAAE;QAClB,OAAO,iBAAQT,OAAO,CAAC,mBAAIS,QAAQ,EAAE,qCAAqC,CAAC,CAAC,CAACC,IAAI,CAC/E,UAACC,GAAG,EAAK;UACP,IAAI,CAACA,GAAG,EAAE;YACR,OAAO,iBAAQX,OAAO,CAAC,KAAK,CAAC;UAC/B;UAEA,IAAIS,QAAQ,CAACV,OAAO,CAACE,OAAO,KAAK,UAAU,EAAE;YAC3C,OAAO,iBAAQD,OAAO,CAAC,IAAI,CAAC;UAC9B;UAEA,IAAIS,QAAQ,CAACV,OAAO,CAACG,GAAG,EAAE;YACxB,IAAMD,OAAO,GAAGH,GAAG,CAACK,KAAK,CAACC,QAAQ,CAACC,QAAQ,CAACC,iBAAiB,CAACG,QAAQ,CAACV,OAAO,CAACG,GAAG,CAAC;YAEnF,OAAO,iBAAQF,OAAO,CAACC,OAAO,IAAIA,OAAO,CAACN,IAAI,KAAK,UAAU,CAAC;UAChE;UAEA,OAAO,iBAAQK,OAAO,CAAC,KAAK,CAAC;QAC/B,CAAC,CACF;MACH,CAAC;MACDO,OAAO,mBAACE,QAAQ,EAAE;QAChB,OAAO,iBAAQT,OAAO,CAACS,QAAQ,CAACD,IAAI,CAACI,UAAU,CAACC,KAAK,CAAC;MACxD;IACF,CAAC,CACF;IACDC,UAAU,EAAE,CACV;MACEnB,IAAI,EAAE,oBAAoB;MAC1BC,SAAS,EAAE,UAAU;MACrBmB,EAAE,cAACjB,GAAG,EAAEkB,MAAM,EAAE;QACd,OAAOlB,GAAG,CAACK,KAAK,CAACC,QAAQ,CAACa,UAAU,CACjCC,WAAW,CAACF,MAAM,CAACG,sBAAsB,EAAEH,MAAM,CAACI,KAAK,CAAC,CACxDV,IAAI,CAAC,UAACW,CAAC,EAAK;UACXL,MAAM,CAACI,KAAK,GAAGC,CAAC;QAClB,CAAC,CAAC;MACN;IACF,CAAC;EAEL;AACF,CAAC,CAAC"}
|
package/dist/search.js
CHANGED
|
@@ -1,51 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
|
|
4
|
-
|
|
5
4
|
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
|
|
6
|
-
|
|
7
5
|
_Object$defineProperty(exports, "__esModule", {
|
|
8
6
|
value: true
|
|
9
7
|
});
|
|
10
|
-
|
|
11
8
|
exports.default = void 0;
|
|
12
|
-
|
|
13
9
|
var _deleteProperty = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/reflect/delete-property"));
|
|
14
|
-
|
|
15
10
|
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
|
|
16
|
-
|
|
17
11
|
var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign"));
|
|
18
|
-
|
|
19
12
|
var _keys = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/keys"));
|
|
20
|
-
|
|
21
13
|
var _getOwnPropertyDescriptor = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor"));
|
|
22
|
-
|
|
23
14
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/slicedToArray"));
|
|
24
|
-
|
|
25
15
|
var _applyDecoratedDescriptor2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/applyDecoratedDescriptor"));
|
|
26
|
-
|
|
27
16
|
var _get2 = _interopRequireDefault(require("lodash/get"));
|
|
28
|
-
|
|
29
17
|
var _common = require("@webex/common");
|
|
30
|
-
|
|
31
18
|
var _webexCore = require("@webex/webex-core");
|
|
32
|
-
|
|
33
19
|
var _obj;
|
|
34
|
-
|
|
35
20
|
var Search = _webexCore.WebexPlugin.extend((_obj = {
|
|
36
21
|
namespace: 'Search',
|
|
37
22
|
people: function people(options) {
|
|
38
23
|
options = options || {};
|
|
39
|
-
|
|
40
24
|
if (!options.queryString && options.query) {
|
|
41
25
|
options.queryString = options.query;
|
|
42
26
|
(0, _deleteProperty.default)(options, 'query');
|
|
43
27
|
}
|
|
44
|
-
|
|
45
28
|
if (!options.queryString) {
|
|
46
29
|
return _promise.default.reject(new Error('`options.query` is required'));
|
|
47
30
|
}
|
|
48
|
-
|
|
49
31
|
return this.request({
|
|
50
32
|
api: 'argonaut',
|
|
51
33
|
resource: 'directory',
|
|
@@ -57,13 +39,11 @@ var Search = _webexCore.WebexPlugin.extend((_obj = {
|
|
|
57
39
|
},
|
|
58
40
|
bindSearchKey: function bindSearchKey() {
|
|
59
41
|
var _this = this;
|
|
60
|
-
|
|
61
42
|
return this.webex.internal.encryption.kms.createUnboundKeys({
|
|
62
43
|
count: 1
|
|
63
44
|
}).then(function (_ref) {
|
|
64
45
|
var _ref2 = (0, _slicedToArray2.default)(_ref, 1),
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
key = _ref2[0];
|
|
67
47
|
return _this.webex.internal.encryption.kms.createResource({
|
|
68
48
|
key: key,
|
|
69
49
|
userIds: [_this.webex.internal.device.userId]
|
|
@@ -72,26 +52,21 @@ var Search = _webexCore.WebexPlugin.extend((_obj = {
|
|
|
72
52
|
});
|
|
73
53
|
});
|
|
74
54
|
},
|
|
75
|
-
|
|
76
55
|
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
56
|
+
* Fetches search result activities
|
|
57
|
+
* @param {Object} options
|
|
58
|
+
* @param {boolean} options.includeRemoteClusterReferences when true,
|
|
59
|
+
* includes search results from remote clusters
|
|
60
|
+
* @returns {Promise<Array>} Resolves with the activities
|
|
61
|
+
*/
|
|
83
62
|
search: function search(options) {
|
|
84
63
|
var _this2 = this;
|
|
85
|
-
|
|
86
64
|
/* eslint max-nested-callbacks: [0] */
|
|
87
65
|
options = options || {};
|
|
88
|
-
|
|
89
66
|
var promise = _promise.default.resolve();
|
|
90
|
-
|
|
91
67
|
if (!this.webex.internal.device.searchEncryptionKeyUrl) {
|
|
92
68
|
promise = this.bindSearchKey();
|
|
93
69
|
}
|
|
94
|
-
|
|
95
70
|
return promise.then(function () {
|
|
96
71
|
return _this2.webex.request({
|
|
97
72
|
service: 'argonaut',
|
|
@@ -103,7 +78,6 @@ var Search = _webexCore.WebexPlugin.extend((_obj = {
|
|
|
103
78
|
});
|
|
104
79
|
}).then(function (res) {
|
|
105
80
|
var resActivities = (0, _get2.default)(res, 'body.activities.items', []);
|
|
106
|
-
|
|
107
81
|
if (options.includeRemoteClusterReferences && res.body.breadcrumbs) {
|
|
108
82
|
var breadcrumbs = res.body.breadcrumbs;
|
|
109
83
|
var promises = [];
|
|
@@ -112,16 +86,15 @@ var Search = _webexCore.WebexPlugin.extend((_obj = {
|
|
|
112
86
|
var editedCluster = "".concat(cluster, ":identityLookup");
|
|
113
87
|
var clusterActivityUrls = breadcrumbs[cluster].items.map(function (activity) {
|
|
114
88
|
return activity.activityUrl;
|
|
115
|
-
});
|
|
89
|
+
});
|
|
116
90
|
|
|
91
|
+
// Find activities per cluster
|
|
117
92
|
var bulkActivitiesPromise = _this2.webex.internal.conversation.bulkActivitiesFetch(clusterActivityUrls, {
|
|
118
93
|
cluster: editedCluster
|
|
119
94
|
}).catch(function (err) {
|
|
120
95
|
_this2.logger.warn('search: error fetching from remote clusters', err);
|
|
121
|
-
|
|
122
96
|
return _promise.default.resolve([]);
|
|
123
97
|
});
|
|
124
|
-
|
|
125
98
|
promises.push(bulkActivitiesPromise);
|
|
126
99
|
});
|
|
127
100
|
return _promise.default.all(promises).then(function (clusterResults) {
|
|
@@ -130,13 +103,11 @@ var Search = _webexCore.WebexPlugin.extend((_obj = {
|
|
|
130
103
|
}, resActivities);
|
|
131
104
|
});
|
|
132
105
|
}
|
|
133
|
-
|
|
134
106
|
return resActivities;
|
|
135
107
|
});
|
|
136
108
|
},
|
|
137
|
-
version: "3.0.0-beta.
|
|
109
|
+
version: "3.0.0-beta.31"
|
|
138
110
|
}, ((0, _applyDecoratedDescriptor2.default)(_obj, "bindSearchKey", [_common.oneFlight], (0, _getOwnPropertyDescriptor.default)(_obj, "bindSearchKey"), _obj)), _obj));
|
|
139
|
-
|
|
140
111
|
var _default = Search;
|
|
141
112
|
exports.default = _default;
|
|
142
113
|
//# sourceMappingURL=search.js.map
|
package/dist/search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Search","WebexPlugin","extend","namespace","people","options","queryString","query","reject","Error","request","api","resource","method","body","then","res","bindSearchKey","webex","internal","encryption","kms","createUnboundKeys","count","key","createResource","userIds","device","userId","set","uri","search","promise","resolve","searchEncryptionKeyUrl","service","resActivities","includeRemoteClusterReferences","breadcrumbs","promises","forEach","cluster","editedCluster","clusterActivityUrls","items","map","activity","activityUrl","bulkActivitiesPromise","conversation","bulkActivitiesFetch","catch","err","logger","warn","push","all","clusterResults","reduce","accumulator","clusterResult","concat","oneFlight"],"sources":["search.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {get} from 'lodash';\nimport {oneFlight} from '@webex/common';\nimport {WebexPlugin} from '@webex/webex-core';\n\nconst Search = WebexPlugin.extend({\n namespace: 'Search',\n\n people(options) {\n options = options || {};\n\n if (!options.queryString && options.query) {\n options.queryString = options.query;\n Reflect.deleteProperty(options, 'query');\n }\n\n if (!options.queryString) {\n return Promise.reject(new Error('`options.query` is required'));\n }\n\n return this.request({\n api: 'argonaut',\n resource: 'directory',\n method: 'POST',\n body: options
|
|
1
|
+
{"version":3,"names":["Search","WebexPlugin","extend","namespace","people","options","queryString","query","reject","Error","request","api","resource","method","body","then","res","bindSearchKey","webex","internal","encryption","kms","createUnboundKeys","count","key","createResource","userIds","device","userId","set","uri","search","promise","resolve","searchEncryptionKeyUrl","service","resActivities","includeRemoteClusterReferences","breadcrumbs","promises","forEach","cluster","editedCluster","clusterActivityUrls","items","map","activity","activityUrl","bulkActivitiesPromise","conversation","bulkActivitiesFetch","catch","err","logger","warn","push","all","clusterResults","reduce","accumulator","clusterResult","concat","oneFlight"],"sources":["search.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {get} from 'lodash';\nimport {oneFlight} from '@webex/common';\nimport {WebexPlugin} from '@webex/webex-core';\n\nconst Search = WebexPlugin.extend({\n namespace: 'Search',\n\n people(options) {\n options = options || {};\n\n if (!options.queryString && options.query) {\n options.queryString = options.query;\n Reflect.deleteProperty(options, 'query');\n }\n\n if (!options.queryString) {\n return Promise.reject(new Error('`options.query` is required'));\n }\n\n return this.request({\n api: 'argonaut',\n resource: 'directory',\n method: 'POST',\n body: options,\n }).then((res) => res.body);\n },\n\n @oneFlight\n bindSearchKey() {\n return this.webex.internal.encryption.kms.createUnboundKeys({count: 1}).then(([key]) =>\n this.webex.internal.encryption.kms\n .createResource({\n key,\n userIds: [this.webex.internal.device.userId],\n })\n .then(() => this.webex.internal.device.set('searchEncryptionKeyUrl', key.uri))\n );\n },\n\n /**\n * Fetches search result activities\n * @param {Object} options\n * @param {boolean} options.includeRemoteClusterReferences when true,\n * includes search results from remote clusters\n * @returns {Promise<Array>} Resolves with the activities\n */\n search(options) {\n /* eslint max-nested-callbacks: [0] */\n options = options || {};\n\n let promise = Promise.resolve();\n\n if (!this.webex.internal.device.searchEncryptionKeyUrl) {\n promise = this.bindSearchKey();\n }\n\n return promise\n .then(() =>\n this.webex.request({\n service: 'argonaut',\n resource: 'search',\n method: 'POST',\n body: Object.assign(options, {\n searchEncryptionKeyUrl: this.webex.internal.device.searchEncryptionKeyUrl,\n }),\n })\n )\n .then((res) => {\n const resActivities = get(res, 'body.activities.items', []);\n\n if (options.includeRemoteClusterReferences && res.body.breadcrumbs) {\n const {breadcrumbs} = res.body;\n const promises = [];\n\n Object.keys(breadcrumbs).forEach((cluster) => {\n // Map activity URLs to their cluster\n const editedCluster = `${cluster}:identityLookup`;\n const clusterActivityUrls = breadcrumbs[cluster].items.map(\n (activity) => activity.activityUrl\n );\n\n // Find activities per cluster\n const bulkActivitiesPromise = this.webex.internal.conversation\n .bulkActivitiesFetch(clusterActivityUrls, {cluster: editedCluster})\n .catch((err) => {\n this.logger.warn('search: error fetching from remote clusters', err);\n\n return Promise.resolve([]);\n });\n\n promises.push(bulkActivitiesPromise);\n });\n\n return Promise.all(promises).then((clusterResults) =>\n clusterResults.reduce(\n (accumulator, clusterResult) => accumulator.concat(clusterResult),\n resActivities\n )\n );\n }\n\n return resActivities;\n });\n },\n});\n\nexport default Search;\n"],"mappings":";;;;;;;;;;;;;;;;AAKA;AACA;AAA8C;AAE9C,IAAMA,MAAM,GAAGC,sBAAW,CAACC,MAAM,SAAC;EAChCC,SAAS,EAAE,QAAQ;EAEnBC,MAAM,kBAACC,OAAO,EAAE;IACdA,OAAO,GAAGA,OAAO,IAAI,CAAC,CAAC;IAEvB,IAAI,CAACA,OAAO,CAACC,WAAW,IAAID,OAAO,CAACE,KAAK,EAAE;MACzCF,OAAO,CAACC,WAAW,GAAGD,OAAO,CAACE,KAAK;MACnC,6BAAuBF,OAAO,EAAE,OAAO,CAAC;IAC1C;IAEA,IAAI,CAACA,OAAO,CAACC,WAAW,EAAE;MACxB,OAAO,iBAAQE,MAAM,CAAC,IAAIC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjE;IAEA,OAAO,IAAI,CAACC,OAAO,CAAC;MAClBC,GAAG,EAAE,UAAU;MACfC,QAAQ,EAAE,WAAW;MACrBC,MAAM,EAAE,MAAM;MACdC,IAAI,EAAET;IACR,CAAC,CAAC,CAACU,IAAI,CAAC,UAACC,GAAG;MAAA,OAAKA,GAAG,CAACF,IAAI;IAAA,EAAC;EAC5B,CAAC;EAGDG,aAAa,2BAAG;IAAA;IACd,OAAO,IAAI,CAACC,KAAK,CAACC,QAAQ,CAACC,UAAU,CAACC,GAAG,CAACC,iBAAiB,CAAC;MAACC,KAAK,EAAE;IAAC,CAAC,CAAC,CAACR,IAAI,CAAC;MAAA;QAAES,GAAG;MAAA,OAChF,KAAI,CAACN,KAAK,CAACC,QAAQ,CAACC,UAAU,CAACC,GAAG,CAC/BI,cAAc,CAAC;QACdD,GAAG,EAAHA,GAAG;QACHE,OAAO,EAAE,CAAC,KAAI,CAACR,KAAK,CAACC,QAAQ,CAACQ,MAAM,CAACC,MAAM;MAC7C,CAAC,CAAC,CACDb,IAAI,CAAC;QAAA,OAAM,KAAI,CAACG,KAAK,CAACC,QAAQ,CAACQ,MAAM,CAACE,GAAG,CAAC,wBAAwB,EAAEL,GAAG,CAACM,GAAG,CAAC;MAAA,EAAC;IAAA,EACjF;EACH,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;AACA;EACEC,MAAM,kBAAC1B,OAAO,EAAE;IAAA;IACd;IACAA,OAAO,GAAGA,OAAO,IAAI,CAAC,CAAC;IAEvB,IAAI2B,OAAO,GAAG,iBAAQC,OAAO,EAAE;IAE/B,IAAI,CAAC,IAAI,CAACf,KAAK,CAACC,QAAQ,CAACQ,MAAM,CAACO,sBAAsB,EAAE;MACtDF,OAAO,GAAG,IAAI,CAACf,aAAa,EAAE;IAChC;IAEA,OAAOe,OAAO,CACXjB,IAAI,CAAC;MAAA,OACJ,MAAI,CAACG,KAAK,CAACR,OAAO,CAAC;QACjByB,OAAO,EAAE,UAAU;QACnBvB,QAAQ,EAAE,QAAQ;QAClBC,MAAM,EAAE,MAAM;QACdC,IAAI,EAAE,qBAAcT,OAAO,EAAE;UAC3B6B,sBAAsB,EAAE,MAAI,CAAChB,KAAK,CAACC,QAAQ,CAACQ,MAAM,CAACO;QACrD,CAAC;MACH,CAAC,CAAC;IAAA,EACH,CACAnB,IAAI,CAAC,UAACC,GAAG,EAAK;MACb,IAAMoB,aAAa,GAAG,mBAAIpB,GAAG,EAAE,uBAAuB,EAAE,EAAE,CAAC;MAE3D,IAAIX,OAAO,CAACgC,8BAA8B,IAAIrB,GAAG,CAACF,IAAI,CAACwB,WAAW,EAAE;QAClE,IAAOA,WAAW,GAAItB,GAAG,CAACF,IAAI,CAAvBwB,WAAW;QAClB,IAAMC,QAAQ,GAAG,EAAE;QAEnB,mBAAYD,WAAW,CAAC,CAACE,OAAO,CAAC,UAACC,OAAO,EAAK;UAC5C;UACA,IAAMC,aAAa,aAAMD,OAAO,oBAAiB;UACjD,IAAME,mBAAmB,GAAGL,WAAW,CAACG,OAAO,CAAC,CAACG,KAAK,CAACC,GAAG,CACxD,UAACC,QAAQ;YAAA,OAAKA,QAAQ,CAACC,WAAW;UAAA,EACnC;;UAED;UACA,IAAMC,qBAAqB,GAAG,MAAI,CAAC9B,KAAK,CAACC,QAAQ,CAAC8B,YAAY,CAC3DC,mBAAmB,CAACP,mBAAmB,EAAE;YAACF,OAAO,EAAEC;UAAa,CAAC,CAAC,CAClES,KAAK,CAAC,UAACC,GAAG,EAAK;YACd,MAAI,CAACC,MAAM,CAACC,IAAI,CAAC,6CAA6C,EAAEF,GAAG,CAAC;YAEpE,OAAO,iBAAQnB,OAAO,CAAC,EAAE,CAAC;UAC5B,CAAC,CAAC;UAEJM,QAAQ,CAACgB,IAAI,CAACP,qBAAqB,CAAC;QACtC,CAAC,CAAC;QAEF,OAAO,iBAAQQ,GAAG,CAACjB,QAAQ,CAAC,CAACxB,IAAI,CAAC,UAAC0C,cAAc;UAAA,OAC/CA,cAAc,CAACC,MAAM,CACnB,UAACC,WAAW,EAAEC,aAAa;YAAA,OAAKD,WAAW,CAACE,MAAM,CAACD,aAAa,CAAC;UAAA,GACjExB,aAAa,CACd;QAAA,EACF;MACH;MAEA,OAAOA,aAAa;IACtB,CAAC,CAAC;EACN,CAAC;EAAA;AACH,CAAC,mEA7EE0B,iBAAS,gFA6EV;AAAC,eAEY9D,MAAM;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/internal-plugin-search",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.31",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Michael Wegman <mwegman@cisco.com>",
|
|
@@ -21,17 +21,17 @@
|
|
|
21
21
|
]
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@webex/test-helper-chai": "3.0.0-beta.
|
|
25
|
-
"@webex/test-helper-retry": "3.0.0-beta.
|
|
26
|
-
"@webex/test-helper-test-users": "3.0.0-beta.
|
|
24
|
+
"@webex/test-helper-chai": "3.0.0-beta.31",
|
|
25
|
+
"@webex/test-helper-retry": "3.0.0-beta.31",
|
|
26
|
+
"@webex/test-helper-test-users": "3.0.0-beta.31"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@webex/common": "3.0.0-beta.
|
|
30
|
-
"@webex/internal-plugin-conversation": "3.0.0-beta.
|
|
31
|
-
"@webex/internal-plugin-device": "3.0.0-beta.
|
|
32
|
-
"@webex/internal-plugin-encryption": "3.0.0-beta.
|
|
33
|
-
"@webex/internal-plugin-search": "3.0.0-beta.
|
|
34
|
-
"@webex/webex-core": "3.0.0-beta.
|
|
29
|
+
"@webex/common": "3.0.0-beta.31",
|
|
30
|
+
"@webex/internal-plugin-conversation": "3.0.0-beta.31",
|
|
31
|
+
"@webex/internal-plugin-device": "3.0.0-beta.31",
|
|
32
|
+
"@webex/internal-plugin-encryption": "3.0.0-beta.31",
|
|
33
|
+
"@webex/internal-plugin-search": "3.0.0-beta.31",
|
|
34
|
+
"@webex/webex-core": "3.0.0-beta.31",
|
|
35
35
|
"lodash": "^4.17.21",
|
|
36
36
|
"uuid": "^3.3.2"
|
|
37
37
|
}
|
package/src/config.js
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
3
|
*/
|
|
4
|
+
import '@webex/internal-plugin-conversation';
|
|
5
|
+
import '@webex/internal-plugin-encryption';
|
|
4
6
|
|
|
5
7
|
import {registerInternalPlugin} from '@webex/webex-core';
|
|
6
8
|
import {has} from 'lodash';
|
|
@@ -8,9 +10,6 @@ import {has} from 'lodash';
|
|
|
8
10
|
import Search from './search';
|
|
9
11
|
import config from './config';
|
|
10
12
|
|
|
11
|
-
import '@webex/internal-plugin-conversation';
|
|
12
|
-
import '@webex/internal-plugin-encryption';
|
|
13
|
-
|
|
14
13
|
registerInternalPlugin('search', Search, {
|
|
15
14
|
config,
|
|
16
15
|
payloadTransformer: {
|
|
@@ -41,14 +40,14 @@ registerInternalPlugin('search', Search, {
|
|
|
41
40
|
},
|
|
42
41
|
extract(options) {
|
|
43
42
|
return Promise.resolve(options.body);
|
|
44
|
-
}
|
|
43
|
+
},
|
|
45
44
|
},
|
|
46
45
|
{
|
|
47
46
|
name: 'transformObjectArray',
|
|
48
47
|
direction: 'inbound',
|
|
49
48
|
test(ctx, response) {
|
|
50
|
-
return Promise.resolve(has(response, 'body.activities.items[0].objectType'))
|
|
51
|
-
|
|
49
|
+
return Promise.resolve(has(response, 'body.activities.items[0].objectType')).then(
|
|
50
|
+
(res) => {
|
|
52
51
|
if (!res) {
|
|
53
52
|
return Promise.resolve(false);
|
|
54
53
|
}
|
|
@@ -64,26 +63,28 @@ registerInternalPlugin('search', Search, {
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
return Promise.resolve(false);
|
|
67
|
-
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
68
|
},
|
|
69
69
|
extract(response) {
|
|
70
70
|
return Promise.resolve(response.body.activities.items);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
73
|
],
|
|
74
74
|
transforms: [
|
|
75
75
|
{
|
|
76
76
|
name: 'encryptSearchQuery',
|
|
77
77
|
direction: 'outbound',
|
|
78
78
|
fn(ctx, object) {
|
|
79
|
-
return ctx.webex.internal.encryption
|
|
79
|
+
return ctx.webex.internal.encryption
|
|
80
|
+
.encryptText(object.searchEncryptionKeyUrl, object.query)
|
|
80
81
|
.then((q) => {
|
|
81
82
|
object.query = q;
|
|
82
83
|
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
]
|
|
86
|
-
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
export {default} from './search';
|
package/src/search.js
CHANGED
|
@@ -25,28 +25,29 @@ const Search = WebexPlugin.extend({
|
|
|
25
25
|
api: 'argonaut',
|
|
26
26
|
resource: 'directory',
|
|
27
27
|
method: 'POST',
|
|
28
|
-
body: options
|
|
29
|
-
})
|
|
30
|
-
.then((res) => res.body);
|
|
28
|
+
body: options,
|
|
29
|
+
}).then((res) => res.body);
|
|
31
30
|
},
|
|
32
31
|
|
|
33
32
|
@oneFlight
|
|
34
33
|
bindSearchKey() {
|
|
35
|
-
return this.webex.internal.encryption.kms.createUnboundKeys({count: 1})
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
return this.webex.internal.encryption.kms.createUnboundKeys({count: 1}).then(([key]) =>
|
|
35
|
+
this.webex.internal.encryption.kms
|
|
36
|
+
.createResource({
|
|
37
|
+
key,
|
|
38
|
+
userIds: [this.webex.internal.device.userId],
|
|
39
|
+
})
|
|
40
|
+
.then(() => this.webex.internal.device.set('searchEncryptionKeyUrl', key.uri))
|
|
41
|
+
);
|
|
41
42
|
},
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
* Fetches search result activities
|
|
46
|
+
* @param {Object} options
|
|
47
|
+
* @param {boolean} options.includeRemoteClusterReferences when true,
|
|
48
|
+
* includes search results from remote clusters
|
|
49
|
+
* @returns {Promise<Array>} Resolves with the activities
|
|
50
|
+
*/
|
|
50
51
|
search(options) {
|
|
51
52
|
/* eslint max-nested-callbacks: [0] */
|
|
52
53
|
options = options || {};
|
|
@@ -58,14 +59,16 @@ const Search = WebexPlugin.extend({
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
return promise
|
|
61
|
-
.then(() =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
.then(() =>
|
|
63
|
+
this.webex.request({
|
|
64
|
+
service: 'argonaut',
|
|
65
|
+
resource: 'search',
|
|
66
|
+
method: 'POST',
|
|
67
|
+
body: Object.assign(options, {
|
|
68
|
+
searchEncryptionKeyUrl: this.webex.internal.device.searchEncryptionKeyUrl,
|
|
69
|
+
}),
|
|
67
70
|
})
|
|
68
|
-
|
|
71
|
+
)
|
|
69
72
|
.then((res) => {
|
|
70
73
|
const resActivities = get(res, 'body.activities.items', []);
|
|
71
74
|
|
|
@@ -81,15 +84,10 @@ const Search = WebexPlugin.extend({
|
|
|
81
84
|
);
|
|
82
85
|
|
|
83
86
|
// Find activities per cluster
|
|
84
|
-
const bulkActivitiesPromise = this.webex.internal.conversation
|
|
85
|
-
clusterActivityUrls,
|
|
86
|
-
{cluster: editedCluster}
|
|
87
|
-
)
|
|
87
|
+
const bulkActivitiesPromise = this.webex.internal.conversation
|
|
88
|
+
.bulkActivitiesFetch(clusterActivityUrls, {cluster: editedCluster})
|
|
88
89
|
.catch((err) => {
|
|
89
|
-
this.logger.warn(
|
|
90
|
-
'search: error fetching from remote clusters',
|
|
91
|
-
err
|
|
92
|
-
);
|
|
90
|
+
this.logger.warn('search: error fetching from remote clusters', err);
|
|
93
91
|
|
|
94
92
|
return Promise.resolve([]);
|
|
95
93
|
});
|
|
@@ -97,8 +95,8 @@ const Search = WebexPlugin.extend({
|
|
|
97
95
|
promises.push(bulkActivitiesPromise);
|
|
98
96
|
});
|
|
99
97
|
|
|
100
|
-
return Promise.all(promises).then(
|
|
101
|
-
|
|
98
|
+
return Promise.all(promises).then((clusterResults) =>
|
|
99
|
+
clusterResults.reduce(
|
|
102
100
|
(accumulator, clusterResult) => accumulator.concat(clusterResult),
|
|
103
101
|
resActivities
|
|
104
102
|
)
|
|
@@ -107,8 +105,7 @@ const Search = WebexPlugin.extend({
|
|
|
107
105
|
|
|
108
106
|
return resActivities;
|
|
109
107
|
});
|
|
110
|
-
}
|
|
111
|
-
|
|
108
|
+
},
|
|
112
109
|
});
|
|
113
110
|
|
|
114
111
|
export default Search;
|
|
@@ -19,63 +19,70 @@ xdescribe('plugin-search', () => {
|
|
|
19
19
|
describe('#people', () => {
|
|
20
20
|
let bot, checkov, mccoy, webex;
|
|
21
21
|
|
|
22
|
-
before('create test users', () =>
|
|
23
|
-
.then((users) => {
|
|
22
|
+
before('create test users', () =>
|
|
23
|
+
testUsers.create({count: 2}).then((users) => {
|
|
24
24
|
[checkov, mccoy] = users;
|
|
25
25
|
|
|
26
26
|
webex = new WebexCore({
|
|
27
27
|
credentials: {
|
|
28
|
-
authorization: checkov.token
|
|
29
|
-
}
|
|
28
|
+
authorization: checkov.token,
|
|
29
|
+
},
|
|
30
30
|
});
|
|
31
|
-
})
|
|
31
|
+
})
|
|
32
|
+
);
|
|
32
33
|
|
|
33
|
-
before('connect to mercury', () =>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
before('connect to mercury', () =>
|
|
35
|
+
webex.internal.services
|
|
36
|
+
.updateServices({
|
|
37
|
+
from: 'limited',
|
|
38
|
+
query: {email: checkov.email},
|
|
39
|
+
})
|
|
40
|
+
.then(() => webex.internal.services.waitForCatalog('postauth'))
|
|
41
|
+
.then(() => webex.internal.mercury.connect())
|
|
42
|
+
);
|
|
39
43
|
|
|
40
44
|
before('create bot', function () {
|
|
41
45
|
this.timeout(retry.timeout(20000));
|
|
42
46
|
|
|
43
|
-
return retry(() =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
return retry(() =>
|
|
48
|
+
webex
|
|
49
|
+
.request({
|
|
50
|
+
api: 'hydra',
|
|
51
|
+
resource: 'applications',
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: {
|
|
54
|
+
name: 'Personal Assistant',
|
|
55
|
+
botEmail: `${uuid.v4()}@webex.bot`,
|
|
56
|
+
type: 'bot',
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
.then((results) => {
|
|
60
|
+
bot = results.body;
|
|
61
|
+
})
|
|
62
|
+
);
|
|
56
63
|
});
|
|
57
64
|
|
|
58
65
|
after(() => webex && webex.internal.mercury.disconnect());
|
|
59
66
|
|
|
60
|
-
it('searches for users with a specific string', () =>
|
|
61
|
-
.then((users) => {
|
|
67
|
+
it('searches for users with a specific string', () =>
|
|
68
|
+
webex.internal.search.people({query: 'abc'}).then((users) => {
|
|
62
69
|
assert.isArray(users);
|
|
63
70
|
}));
|
|
64
71
|
|
|
65
|
-
it('retrieves a specific user', () =>
|
|
66
|
-
.then((users) => {
|
|
72
|
+
it('retrieves a specific user', () =>
|
|
73
|
+
webex.internal.search.people({query: mccoy.email}).then((users) => {
|
|
67
74
|
assert.isAbove(users.length, 0, 'At least one user was found');
|
|
68
75
|
assert.equal(users[0].id, mccoy.id);
|
|
69
76
|
}));
|
|
70
77
|
|
|
71
|
-
it('retrieves a bot', () =>
|
|
72
|
-
.then((bots) => {
|
|
78
|
+
it('retrieves a bot', () =>
|
|
79
|
+
webex.internal.search.people({query: bot.botEmail, includeRobots: true}).then((bots) => {
|
|
73
80
|
assert.isAbove(bots.length, 0, 'At least one bot was found');
|
|
74
81
|
assert.equal(bots[0].email, bot.botEmail);
|
|
75
82
|
}));
|
|
76
83
|
|
|
77
|
-
it(
|
|
78
|
-
.then((bots) => {
|
|
84
|
+
it("doesn't retrieve a bot because of parameter", () =>
|
|
85
|
+
webex.internal.search.people({query: bot.botEmail, includeRobots: false}).then((bots) => {
|
|
79
86
|
assert.equal(bots.length, 0, 'No bots are returned');
|
|
80
87
|
}));
|
|
81
88
|
});
|
|
@@ -84,166 +91,227 @@ xdescribe('plugin-search', () => {
|
|
|
84
91
|
let checkov, mccoy, spock, kirkEU;
|
|
85
92
|
let party;
|
|
86
93
|
|
|
87
|
-
before('create 4 test users and connect them to mercury', () =>
|
|
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
|
-
|
|
94
|
+
before('create 4 test users and connect them to mercury', () =>
|
|
95
|
+
Promise.all([
|
|
96
|
+
testUsers.create({count: 3}),
|
|
97
|
+
testUsers.create({count: 1, config: {orgId: process.env.EU_PRIMARY_ORG_ID}}),
|
|
98
|
+
])
|
|
99
|
+
.then(([users, usersEU]) => {
|
|
100
|
+
[checkov, mccoy, spock] = users;
|
|
101
|
+
[kirkEU] = usersEU;
|
|
102
|
+
party = {
|
|
103
|
+
checkov,
|
|
104
|
+
mccoy,
|
|
105
|
+
spock,
|
|
106
|
+
kirkEU,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
checkov.webex = new WebexCore({
|
|
110
|
+
credentials: {
|
|
111
|
+
authorization: checkov.token,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
mccoy.webex = new WebexCore({
|
|
116
|
+
credentials: {
|
|
117
|
+
authorization: mccoy.token,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
spock.webex = new WebexCore({
|
|
122
|
+
credentials: {
|
|
123
|
+
authorization: spock.token,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
kirkEU.webex = new WebexCore({
|
|
128
|
+
credentials: {
|
|
129
|
+
authorization: kirkEU.token,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return Promise.all(
|
|
134
|
+
[checkov, mccoy, spock, kirkEU].map((user) =>
|
|
135
|
+
user.webex.internal.services
|
|
136
|
+
.updateServices({
|
|
137
|
+
from: 'limited',
|
|
138
|
+
query: {email: user.email},
|
|
139
|
+
})
|
|
140
|
+
.then(() => user.webex.internal.services.waitForCatalog('postauth'))
|
|
141
|
+
.then(() => user.webex.internal.mercury.connect())
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
})
|
|
145
|
+
.then(() => {
|
|
146
|
+
if (!process.env.PIPELINE) {
|
|
147
|
+
return spock.webex.internal.feature.setFeature(
|
|
148
|
+
'developer',
|
|
149
|
+
'use-v2-search-index',
|
|
150
|
+
true
|
|
151
|
+
);
|
|
119
152
|
}
|
|
120
|
-
});
|
|
121
153
|
|
|
122
|
-
|
|
123
|
-
checkov,
|
|
124
|
-
mccoy,
|
|
125
|
-
spock,
|
|
126
|
-
kirkEU
|
|
127
|
-
].map((user) => user.webex.internal.services.updateServices({
|
|
128
|
-
from: 'limited',
|
|
129
|
-
query: {email: user.email}
|
|
154
|
+
return Promise.resolve();
|
|
130
155
|
})
|
|
131
|
-
|
|
132
|
-
.then(() => user.webex.internal.mercury.connect())));
|
|
133
|
-
})
|
|
134
|
-
.then(() => {
|
|
135
|
-
if (!process.env.PIPELINE) {
|
|
136
|
-
return spock.webex.internal.feature.setFeature('developer', 'use-v2-search-index', true);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return Promise.resolve();
|
|
140
|
-
}));
|
|
156
|
+
);
|
|
141
157
|
|
|
142
158
|
let mccoyConversation, spockConversation, kirkEUConversation;
|
|
143
159
|
|
|
144
|
-
before(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
before(
|
|
161
|
+
'create a room for spock',
|
|
162
|
+
() =>
|
|
163
|
+
spockConversation ||
|
|
164
|
+
spock.webex.internal.conversation
|
|
165
|
+
.create({
|
|
166
|
+
displayName: 'Spock Room',
|
|
167
|
+
participants: map([checkov, mccoy, spock], 'id'),
|
|
168
|
+
})
|
|
169
|
+
.then((conversation) => {
|
|
170
|
+
spockConversation = conversation;
|
|
171
|
+
assert.lengthOf(spockConversation.participants.items, 3);
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
before(
|
|
176
|
+
'create a room for kirkEU',
|
|
177
|
+
() =>
|
|
178
|
+
kirkEUConversation ||
|
|
179
|
+
kirkEU.webex.internal.conversation
|
|
180
|
+
.create(
|
|
181
|
+
{
|
|
182
|
+
displayName: 'Kirk EU Room',
|
|
183
|
+
participants: map([mccoy, spock, kirkEU], 'id'),
|
|
184
|
+
},
|
|
185
|
+
{forceGrouped: true}
|
|
186
|
+
)
|
|
187
|
+
.then((conversation) => {
|
|
188
|
+
kirkEUConversation = conversation;
|
|
189
|
+
assert.lengthOf(kirkEUConversation.participants.items, 3);
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
before(
|
|
194
|
+
'create a room for mccoy',
|
|
195
|
+
() =>
|
|
196
|
+
mccoyConversation ||
|
|
197
|
+
mccoy.webex.internal.conversation
|
|
198
|
+
.create(
|
|
199
|
+
{
|
|
200
|
+
displayName: 'McCoy Room',
|
|
201
|
+
participants: map([mccoy, spock], 'id'),
|
|
202
|
+
},
|
|
203
|
+
{forceGrouped: true}
|
|
204
|
+
)
|
|
205
|
+
.then((conversation) => {
|
|
206
|
+
mccoyConversation = conversation;
|
|
207
|
+
assert.lengthOf(mccoyConversation.participants.items, 2);
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
after(() =>
|
|
212
|
+
Promise.all(
|
|
213
|
+
[checkov, mccoy, spock, kirkEU].map((user) =>
|
|
214
|
+
user.webex.internal.mercury.disconnect.bind(user)
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
);
|
|
172
218
|
|
|
173
219
|
function populateConversation(conversation) {
|
|
174
220
|
const spockConvo = spock.webex.internal.conversation;
|
|
175
221
|
const mccoyConvo = mccoy.webex.internal.conversation;
|
|
176
222
|
|
|
177
|
-
return mccoyConvo
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.then(() =>
|
|
187
|
-
|
|
188
|
-
|
|
223
|
+
return mccoyConvo
|
|
224
|
+
.post(conversation, {
|
|
225
|
+
displayName: 'Hello, Spock.',
|
|
226
|
+
})
|
|
227
|
+
.then(() =>
|
|
228
|
+
spockConvo.post(conversation, {
|
|
229
|
+
displayName: 'Hello, Doctor.',
|
|
230
|
+
})
|
|
231
|
+
)
|
|
232
|
+
.then(() =>
|
|
233
|
+
mccoyConvo.post(conversation, {
|
|
234
|
+
displayName: 'What did Jim say?',
|
|
235
|
+
})
|
|
236
|
+
)
|
|
237
|
+
.then(() =>
|
|
238
|
+
spockConvo.post(conversation, {
|
|
239
|
+
displayName: 'The captain wishes to board.',
|
|
240
|
+
})
|
|
241
|
+
);
|
|
189
242
|
}
|
|
190
243
|
|
|
191
|
-
before('prepare and verify conversation for spock', () =>
|
|
192
|
-
|
|
193
|
-
|
|
244
|
+
before('prepare and verify conversation for spock', () =>
|
|
245
|
+
populateConversation(spockConversation)
|
|
246
|
+
.then(() => {
|
|
247
|
+
assert.isDefined(spockConversation);
|
|
194
248
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
249
|
+
return spock.webex.internal.conversation.get(
|
|
250
|
+
{url: spockConversation.url},
|
|
251
|
+
{activitiesLimit: 10}
|
|
252
|
+
);
|
|
253
|
+
})
|
|
254
|
+
.then((conversation) => {
|
|
255
|
+
spockConversation = conversation;
|
|
256
|
+
// Removes the 'create' activity
|
|
257
|
+
spockConversation.activities.items.shift();
|
|
258
|
+
const comments = map(spockConversation.activities.items, 'object.displayName');
|
|
259
|
+
|
|
260
|
+
assert.lengthOf(comments, 4);
|
|
261
|
+
assert.equal(comments[0], 'Hello, Spock.');
|
|
262
|
+
assert.equal(comments[1], 'Hello, Doctor.');
|
|
263
|
+
assert.equal(comments[2], 'What did Jim say?');
|
|
264
|
+
assert.equal(comments[3], 'The captain wishes to board.');
|
|
265
|
+
})
|
|
266
|
+
);
|
|
209
267
|
|
|
210
|
-
before('prepare and verify conversation for mccoy', () =>
|
|
211
|
-
|
|
212
|
-
|
|
268
|
+
before('prepare and verify conversation for mccoy', () =>
|
|
269
|
+
populateConversation(mccoyConversation)
|
|
270
|
+
.then(() => {
|
|
271
|
+
assert.isDefined(mccoyConversation);
|
|
213
272
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
273
|
+
return mccoy.webex.internal.conversation.get(
|
|
274
|
+
{url: mccoyConversation.url},
|
|
275
|
+
{activitiesLimit: 10}
|
|
276
|
+
);
|
|
277
|
+
})
|
|
278
|
+
.then((conversation) => {
|
|
279
|
+
mccoyConversation = conversation;
|
|
218
280
|
|
|
219
|
-
|
|
220
|
-
|
|
281
|
+
mccoyConversation.activities.items.shift();
|
|
282
|
+
const comments = map(mccoyConversation.activities.items, 'object.displayName');
|
|
221
283
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
284
|
+
assert.lengthOf(comments, 4);
|
|
285
|
+
assert.equal(comments[0], 'Hello, Spock.');
|
|
286
|
+
assert.equal(comments[1], 'Hello, Doctor.');
|
|
287
|
+
assert.equal(comments[2], 'What did Jim say?');
|
|
288
|
+
assert.equal(comments[3], 'The captain wishes to board.');
|
|
289
|
+
})
|
|
290
|
+
);
|
|
228
291
|
|
|
229
|
-
before('prepare and verify conversation for kirkEU', () =>
|
|
230
|
-
|
|
231
|
-
|
|
292
|
+
before('prepare and verify conversation for kirkEU', () =>
|
|
293
|
+
populateConversation(kirkEUConversation)
|
|
294
|
+
.then(() => {
|
|
295
|
+
assert.isDefined(kirkEUConversation);
|
|
232
296
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
297
|
+
return kirkEU.webex.internal.conversation.get(
|
|
298
|
+
{url: kirkEUConversation.url},
|
|
299
|
+
{activitiesLimit: 10}
|
|
300
|
+
);
|
|
301
|
+
})
|
|
302
|
+
.then((conversation) => {
|
|
303
|
+
kirkEUConversation = conversation;
|
|
237
304
|
|
|
238
|
-
|
|
239
|
-
|
|
305
|
+
kirkEUConversation.activities.items.shift();
|
|
306
|
+
const comments = map(kirkEUConversation.activities.items, 'object.displayName');
|
|
240
307
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
308
|
+
assert.lengthOf(comments, 4);
|
|
309
|
+
assert.equal(comments[0], 'Hello, Spock.');
|
|
310
|
+
assert.equal(comments[1], 'Hello, Doctor.');
|
|
311
|
+
assert.equal(comments[2], 'What did Jim say?');
|
|
312
|
+
assert.equal(comments[3], 'The captain wishes to board.');
|
|
313
|
+
})
|
|
314
|
+
);
|
|
247
315
|
|
|
248
316
|
const testData = [
|
|
249
317
|
// Single word
|
|
@@ -251,16 +319,16 @@ xdescribe('plugin-search', () => {
|
|
|
251
319
|
{
|
|
252
320
|
given: {
|
|
253
321
|
user: 'spock',
|
|
254
|
-
query: 'hElLo'
|
|
322
|
+
query: 'hElLo',
|
|
255
323
|
},
|
|
256
324
|
expected: {
|
|
257
325
|
path: 'object.displayName',
|
|
258
326
|
results: [
|
|
259
327
|
{name: 'Hello, Spock.', count: 2},
|
|
260
|
-
{name: 'Hello, Doctor.', count: 2}
|
|
328
|
+
{name: 'Hello, Doctor.', count: 2},
|
|
261
329
|
],
|
|
262
|
-
resultsCount: 4
|
|
263
|
-
}
|
|
330
|
+
resultsCount: 4,
|
|
331
|
+
},
|
|
264
332
|
},
|
|
265
333
|
// Single word
|
|
266
334
|
// Asserts comments
|
|
@@ -269,16 +337,16 @@ xdescribe('plugin-search', () => {
|
|
|
269
337
|
given: {
|
|
270
338
|
user: 'spock',
|
|
271
339
|
query: 'hElLo',
|
|
272
|
-
includeRemoteClusterReferences: true
|
|
340
|
+
includeRemoteClusterReferences: true,
|
|
273
341
|
},
|
|
274
342
|
expected: {
|
|
275
343
|
path: 'object.displayName',
|
|
276
344
|
results: [
|
|
277
345
|
{name: 'Hello, Spock.', count: 3},
|
|
278
|
-
{name: 'Hello, Doctor.', count: 3}
|
|
346
|
+
{name: 'Hello, Doctor.', count: 3},
|
|
279
347
|
],
|
|
280
|
-
resultsCount: 6
|
|
281
|
-
}
|
|
348
|
+
resultsCount: 6,
|
|
349
|
+
},
|
|
282
350
|
},
|
|
283
351
|
// Single word
|
|
284
352
|
// Single result
|
|
@@ -286,15 +354,13 @@ xdescribe('plugin-search', () => {
|
|
|
286
354
|
{
|
|
287
355
|
given: {
|
|
288
356
|
user: 'checkov',
|
|
289
|
-
query: 'JiM'
|
|
357
|
+
query: 'JiM',
|
|
290
358
|
},
|
|
291
359
|
expected: {
|
|
292
360
|
path: 'object.displayName',
|
|
293
|
-
results: [
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
resultsCount: 1
|
|
297
|
-
}
|
|
361
|
+
results: [{name: 'What did Jim say?', count: 1}],
|
|
362
|
+
resultsCount: 1,
|
|
363
|
+
},
|
|
298
364
|
},
|
|
299
365
|
// Single word
|
|
300
366
|
// Single result
|
|
@@ -304,16 +370,14 @@ xdescribe('plugin-search', () => {
|
|
|
304
370
|
given: {
|
|
305
371
|
user: 'checkov',
|
|
306
372
|
query: 'JiM',
|
|
307
|
-
includeRemoteClusterReferences: true
|
|
373
|
+
includeRemoteClusterReferences: true,
|
|
308
374
|
},
|
|
309
375
|
expected: {
|
|
310
376
|
path: 'object.displayName',
|
|
311
|
-
results: [
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
}
|
|
377
|
+
results: [{name: 'What did Jim say?', count: 1}],
|
|
378
|
+
resultsCount: 1,
|
|
379
|
+
},
|
|
380
|
+
},
|
|
317
381
|
// Single word
|
|
318
382
|
// Asserts comments
|
|
319
383
|
// Different participant searching
|
|
@@ -475,47 +539,47 @@ xdescribe('plugin-search', () => {
|
|
|
475
539
|
given: {
|
|
476
540
|
user: 'spock',
|
|
477
541
|
query: 'hElLo',
|
|
478
|
-
size: 0
|
|
542
|
+
size: 0,
|
|
479
543
|
},
|
|
480
544
|
expected: {
|
|
481
545
|
path: 'object.displayName',
|
|
482
546
|
results: [
|
|
483
547
|
{name: 'Hello, Spock.', count: 2},
|
|
484
|
-
{name: 'Hello, Doctor.', count: 2}
|
|
548
|
+
{name: 'Hello, Doctor.', count: 2},
|
|
485
549
|
],
|
|
486
550
|
// Server treats 0 limit as default limit of 20
|
|
487
|
-
resultsCount: 4
|
|
488
|
-
}
|
|
551
|
+
resultsCount: 4,
|
|
552
|
+
},
|
|
489
553
|
},
|
|
490
554
|
{
|
|
491
555
|
given: {
|
|
492
556
|
user: 'spock',
|
|
493
557
|
query: 'hElLo',
|
|
494
558
|
size: 0,
|
|
495
|
-
includeRemoteClusterReferences: true
|
|
559
|
+
includeRemoteClusterReferences: true,
|
|
496
560
|
},
|
|
497
561
|
expected: {
|
|
498
562
|
path: 'object.displayName',
|
|
499
563
|
results: [
|
|
500
564
|
{name: 'Hello, Spock.', count: 3},
|
|
501
|
-
{name: 'Hello, Doctor.', count: 3}
|
|
565
|
+
{name: 'Hello, Doctor.', count: 3},
|
|
502
566
|
],
|
|
503
567
|
// Server treats 0 limit as default limit of 20
|
|
504
|
-
resultsCount: 6
|
|
505
|
-
}
|
|
568
|
+
resultsCount: 6,
|
|
569
|
+
},
|
|
506
570
|
},
|
|
507
571
|
{
|
|
508
572
|
given: {
|
|
509
573
|
user: 'spock',
|
|
510
574
|
query: 'hElLo',
|
|
511
|
-
size: 1
|
|
575
|
+
size: 1,
|
|
512
576
|
},
|
|
513
577
|
expected: {
|
|
514
578
|
path: 'object.displayName',
|
|
515
579
|
results: [],
|
|
516
|
-
resultsCount: 1
|
|
517
|
-
}
|
|
518
|
-
}
|
|
580
|
+
resultsCount: 1,
|
|
581
|
+
},
|
|
582
|
+
},
|
|
519
583
|
// {
|
|
520
584
|
// given: {
|
|
521
585
|
// user: `spock`,
|
|
@@ -562,14 +626,14 @@ xdescribe('plugin-search', () => {
|
|
|
562
626
|
{
|
|
563
627
|
given: {
|
|
564
628
|
user: 'spock',
|
|
565
|
-
type: ['comment']
|
|
629
|
+
type: ['comment'],
|
|
566
630
|
},
|
|
567
631
|
expected: {
|
|
568
632
|
path: 'object.displayName',
|
|
569
633
|
results: [],
|
|
570
|
-
resultsCount: 4
|
|
571
|
-
}
|
|
572
|
-
}
|
|
634
|
+
resultsCount: 4,
|
|
635
|
+
},
|
|
636
|
+
},
|
|
573
637
|
];
|
|
574
638
|
|
|
575
639
|
function runTests(testData) {
|
|
@@ -578,28 +642,31 @@ xdescribe('plugin-search', () => {
|
|
|
578
642
|
const conversationLimit = given.sharedIn ? given.sharedIn.length : 'all';
|
|
579
643
|
const resultLimit = given.size ? `a result limit of ${given.size}` : 'no result limit';
|
|
580
644
|
const queryString = given.query ? `for "${given.query}" ` : '';
|
|
581
|
-
const remoteClusterString = given.includeRemoteClusterReferences
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
645
|
+
const remoteClusterString = given.includeRemoteClusterReferences
|
|
646
|
+
? ' and remote cluster enabled'
|
|
647
|
+
: '';
|
|
648
|
+
const message = util.format(
|
|
649
|
+
`${given.user} searches ${conversationLimit} conversation(s) ${queryString}with ${resultLimit}${remoteClusterString}. Verifies ${expected.path}.`
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
it(message, () =>
|
|
653
|
+
retry(() => {
|
|
654
|
+
let params;
|
|
655
|
+
|
|
656
|
+
if (given.query) {
|
|
657
|
+
params = {
|
|
658
|
+
query: given.query,
|
|
659
|
+
limit: given.size,
|
|
660
|
+
includeRemoteClusterReferences: given.includeRemoteClusterReferences,
|
|
661
|
+
};
|
|
662
|
+
} else {
|
|
663
|
+
params = {
|
|
664
|
+
sharedIn: spockConversation.id,
|
|
665
|
+
type: given.type,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return party[given.user].webex.internal.search.search(params).then((results) => {
|
|
603
670
|
assert.isDefined(results);
|
|
604
671
|
assert.lengthOf(results, expected.resultsCount);
|
|
605
672
|
const plucked = map(results, expected.path);
|
|
@@ -609,7 +676,8 @@ xdescribe('plugin-search', () => {
|
|
|
609
676
|
assert.equal(counts[expectedResults.name], expectedResults.count);
|
|
610
677
|
});
|
|
611
678
|
});
|
|
612
|
-
|
|
679
|
+
})
|
|
680
|
+
);
|
|
613
681
|
});
|
|
614
682
|
}
|
|
615
683
|
|