@webex/internal-plugin-conversation 2.59.2 → 2.59.3-next.1
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/.eslintrc.js +6 -6
- package/README.md +47 -47
- package/babel.config.js +3 -3
- package/dist/activities.js +4 -4
- package/dist/activities.js.map +1 -1
- package/dist/activity-thread-ordering.js +34 -34
- package/dist/activity-thread-ordering.js.map +1 -1
- package/dist/config.js +12 -12
- package/dist/config.js.map +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/conversation.js +474 -474
- package/dist/conversation.js.map +1 -1
- package/dist/convo-error.js +4 -4
- package/dist/convo-error.js.map +1 -1
- package/dist/decryption-transforms.js +155 -155
- package/dist/decryption-transforms.js.map +1 -1
- package/dist/encryption-transforms.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/share-activity.js +57 -57
- package/dist/share-activity.js.map +1 -1
- package/dist/to-array.js +7 -7
- package/dist/to-array.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +21 -20
- package/process +1 -1
- package/src/activities.js +157 -157
- package/src/activity-thread-ordering.js +283 -283
- package/src/activity-threading.md +282 -282
- package/src/config.js +37 -37
- package/src/constants.js +3 -3
- package/src/conversation.js +2535 -2535
- package/src/convo-error.js +15 -15
- package/src/decryption-transforms.js +541 -541
- package/src/encryption-transforms.js +345 -345
- package/src/index.js +327 -327
- package/src/share-activity.js +436 -436
- package/src/to-array.js +29 -29
- package/test/integration/spec/create.js +290 -290
- package/test/integration/spec/encryption.js +333 -333
- package/test/integration/spec/get.js +1255 -1255
- package/test/integration/spec/mercury.js +94 -94
- package/test/integration/spec/share.js +537 -537
- package/test/integration/spec/verbs.js +1041 -1041
- package/test/unit/spec/conversation.js +823 -823
- package/test/unit/spec/decrypt-transforms.js +460 -460
- package/test/unit/spec/encryption-transforms.js +93 -93
- package/test/unit/spec/share-activity.js +178 -178
|
@@ -1,282 +1,282 @@
|
|
|
1
|
-
# Activity Threading Ordering
|
|
2
|
-
|
|
3
|
-
Activity thread ordering (or "threading") is the act of flattening the hierarchical relationship of thread parents and thread replies.
|
|
4
|
-
|
|
5
|
-
## Why Thread Order?
|
|
6
|
-
|
|
7
|
-
When a client fetches activities from conversation service, convo returns chronologically ordered activities (by published date). If you are a client that attempts to display activities to a user, this is mostly useless. An example:
|
|
8
|
-
|
|
9
|
-
Client: hey convo, give me 10 activities
|
|
10
|
-
|
|
11
|
-
Convo: here.
|
|
12
|
-
|
|
13
|
-
1. reaction
|
|
14
|
-
2. root
|
|
15
|
-
3. reaction
|
|
16
|
-
4. reply
|
|
17
|
-
5. edit
|
|
18
|
-
6. edit
|
|
19
|
-
7. reply
|
|
20
|
-
8. reaction
|
|
21
|
-
9. meeting
|
|
22
|
-
10. reply
|
|
23
|
-
|
|
24
|
-
Client: ...
|
|
25
|
-
|
|
26
|
-
Convo: ...
|
|
27
|
-
|
|
28
|
-
By contrast, thread ordering returns activities in _thread order_, otherwise known as _useful order_. An example:
|
|
29
|
-
|
|
30
|
-
Client: hey convo, give me 10 activities in thread order
|
|
31
|
-
|
|
32
|
-
Convo: here.
|
|
33
|
-
|
|
34
|
-
1. root 4
|
|
35
|
-
2. reply to root 4
|
|
36
|
-
3. reply to root 4
|
|
37
|
-
4. root 3
|
|
38
|
-
5. reply to root 3
|
|
39
|
-
6. reply to root 3
|
|
40
|
-
7. root 2
|
|
41
|
-
8. reply to root 2
|
|
42
|
-
9. reply to root 2
|
|
43
|
-
10. root 1 (newest root activity)
|
|
44
|
-
|
|
45
|
-
Client: 👍
|
|
46
|
-
|
|
47
|
-
Convo: 👍
|
|
48
|
-
|
|
49
|
-
As you can see, thread ordering is useful, and as you may _not_ see, it is difficult to implement. The fewer clients that are forced to implement thread ordering themselves, the better for the wellbeing of the entire world.
|
|
50
|
-
|
|
51
|
-
<!-- ## Useful terminology
|
|
52
|
-
|
|
53
|
-
* thread parent, parent - an activity that has replies
|
|
54
|
-
* root activity, root - an activity that is or could be a thread parent
|
|
55
|
-
* child activity, child - an activity that points to a parent activity, but not necessarily a root
|
|
56
|
-
* thread reply, thread - an activity that is a reply to a root activity
|
|
57
|
-
* orphan - a child activity whose parent has not been fetched
|
|
58
|
-
-->
|
|
59
|
-
|
|
60
|
-
# The Code
|
|
61
|
-
|
|
62
|
-
Thread ordering methods live in the internal converation plugin
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
webex.internal.conversation[*]
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
There are two methods that compose thread ordering, a public and private method. The public method is simply a facade of the private method, abstracting the complex away from the caller.
|
|
69
|
-
|
|
70
|
-
### `_listActivitiesThreadOrdered(options)` (private)
|
|
71
|
-
|
|
72
|
-
The `_listActivitiesThreadOrdered` method is a stateful async generator function that fetches thread ordered activities.
|
|
73
|
-
|
|
74
|
-
#### Description
|
|
75
|
-
|
|
76
|
-
The `_listActivitiesThreadOrdered` method instantiates a generator function that yields a promise. It yields results in an infinite loop for the life of the generator (until the instance is no longer referenced and JS garbage collects). This means when the method yields a result it pauses execution until its next invocation, freezing its current state to be used by the next invocation. The generator instance internally tracks the oldest and newest activities it has fetched, using them as the timestamps to query the next set of results in either direction, thus the caller is not required to maintain any state to fetch older and newer activities. This method does not cache all fetched activities, therefore the caller is still required to store their fetched activities or be forced to refetch them.
|
|
77
|
-
|
|
78
|
-
The generator's returned `next` method accepts arguments as well, which allows the caller to change the direction of their querying from newer messages to older ones without creating a new generator instance. `next`
|
|
79
|
-
|
|
80
|
-
#### Parameters
|
|
81
|
-
|
|
82
|
-
_generator initialization options_
|
|
83
|
-
|
|
84
|
-
options
|
|
85
|
-
|
|
86
|
-
options.url: the conversation's convo URL
|
|
87
|
-
|
|
88
|
-
options.minActivities: the minimum number of activities you would like in your batch
|
|
89
|
-
|
|
90
|
-
options.queryType: the direction to fetch activities. One of 'newer', 'older', 'mid'
|
|
91
|
-
|
|
92
|
-
options.search: a server activity object to return as the middle point of the batch, along with its surrounding activities
|
|
93
|
-
|
|
94
|
-
_parameters accepted by the generator's `next` method_
|
|
95
|
-
|
|
96
|
-
options
|
|
97
|
-
|
|
98
|
-
options.queryType: see above
|
|
99
|
-
|
|
100
|
-
options.search: see above
|
|
101
|
-
|
|
102
|
-
#### Return value
|
|
103
|
-
|
|
104
|
-
Calling `_listActivitiesThreadOrdered` returns the generator instance, and does not execute any work. The generator returned has a `next` method that begins the first round of work and returns a `done` boolean and a `value`, the asked-for thread ordered activities.
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
interface IActivity {
|
|
108
|
-
id: string;
|
|
109
|
-
activity: <server Activity>;
|
|
110
|
-
reaction: <server ReactionSummary>
|
|
111
|
-
reactionSelf: <server ReactionSelfSummary>
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
done: boolean,
|
|
115
|
-
value: IActivity[]
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
#### Examples
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
const options = {
|
|
122
|
-
url: 'myconvourl.com',
|
|
123
|
-
minActivities: 20,
|
|
124
|
-
queryType: 'older',
|
|
125
|
-
search: null
|
|
126
|
-
}
|
|
127
|
-
const threadOrderedFetcher = webex.internal.conversation._listActivitiesThreadOrdered(options);
|
|
128
|
-
await threadOrderedFetcher.next() // => { done: boolean, value: IActivity[] }
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
#### The nitty gritty: design
|
|
132
|
-
|
|
133
|
-
There are 3 main phases of the method, with one major subphase:
|
|
134
|
-
|
|
135
|
-
1. initialization: up to the ` while (true)` loop
|
|
136
|
-
2. execution: the ` while (true)` loop
|
|
137
|
-
3. (subphase of execution) fetching: the ` while (!getNoMoreActs())` loop
|
|
138
|
-
4. re-calculation: after the ` yield`
|
|
139
|
-
|
|
140
|
-
##### initialization
|
|
141
|
-
|
|
142
|
-
Parsing the arguments and initializing state is done in this first phase, before the generator's first execution. State that must be retained independent of each execution loop (the code run by the generator's `next` method) is instantiated here. The following variables (or grouped of variables) are created and maintained for the life of the generator:
|
|
143
|
-
|
|
144
|
-
- oldestAct - the oldest activity (by published date) that has been fetched by the gen. instance
|
|
145
|
-
- newestAct - the newest activity that has been fetched by the gen. instance
|
|
146
|
-
- batchSize - the number of activities to fetch to achieve minActivities. This value is set lower in cases where we fetch children, because adding the children to their parents may help us reach minActivities count
|
|
147
|
-
- query - initialize fetching query. The first query will be forced to fetch newest activities in a space to initialize the activity states, unless a search is requested
|
|
148
|
-
- \*\*\*ActivityHash(es): cache objects for children activities. These caches store all child activity types returned from convo fetches in order to keep track of "orphans", or child activities that come in before their parent activity
|
|
149
|
-
|
|
150
|
-
##### execution
|
|
151
|
-
|
|
152
|
-
The execution phase will be run for every call to `next` method. Its job is to fetch activities until the minimum activity limit is reached, then it will build the ordered list of activities and yield them to the caller. Similar to init phase, execution phase has variables declared outside the fetching loop in order to keep track of what we have fetched so far, and whether continuing to fetch is necessary. The execuction phase runs the fetching subphase. The following state variables are tracked by this outer loop, before the fetching phase:
|
|
153
|
-
|
|
154
|
-
- rootActivityHash - the data structure used to track root activities we have fetched in a given batch
|
|
155
|
-
- [* helpers] - many helper functions are declared. They close over state variables and are used to group functionality used by the fetching loop, and for self-documenting purposes
|
|
156
|
-
- fetchLoopCount - track count of fetch loops run. Used to break out of loop in unlikely case that we fetch the same query over and over.
|
|
157
|
-
|
|
158
|
-
##### fetching
|
|
159
|
-
|
|
160
|
-
The fetching phase is contained within the execution phase, and is a loop that will run until we run out of activities to fetch, or explicitly break the loop when we reach the minActivities count with what we've fetched so far. Each fetch loop begins by setting up the query and passing options to `#listActivities` method. All query types depend on a first query to activities to get N root activities. midDate queries (aliased MID) and sinceDate queries (aliased NEWER) also must recursively fetch children to ensure we don't return to caller a root activity that is missing children.\*
|
|
161
|
-
|
|
162
|
-
\*NOTE: These sometimes-necessary children queries depend on the addition of a new method, `#listAllChildActivitiesByParentId`. This method is similar to `listChildActivitiesByParentId`, but this method recursively fetches all the chilren of a certain type, rather than forcing the calling method to fetch 10 children at a time (as is the current child limit set by underlying convo API).
|
|
163
|
-
|
|
164
|
-
Activities returned to us by convo fetches are sorted and placed into maps based off their activity type. Root activities are stored on a per-batch basis, whereas children are stored globally (prevents orphans). Root activities are keyed by their ID, where children are keyed by their parent's ID (`activity.parent.id`).
|
|
165
|
-
|
|
166
|
-
When reactions are fetched, however, we call a _different_ convo API that helps us reduce server roundtrips (this is why the fetch for reactions is different from the fetch for edits and replies).
|
|
167
|
-
|
|
168
|
-
Fetching loop can be explicitly broken out of when we have decided we have the necessary activities for the current batch. If we _don't_ have enough activities, we initialize a new query derived from the former query and return to the top of the fetching loop to continue building our list.
|
|
169
|
-
|
|
170
|
-
##### execution (revisited)
|
|
171
|
-
|
|
172
|
-
After a successful series of fetches have given us the activities necessary for the current batch, we initialize an empty array that acts as our ordered list. To begin, the current batch's root activities are sorted by their published date, and using the ordered list of IDs, we begin to loop over the rootActivityHash.
|
|
173
|
-
|
|
174
|
-
For every root activity, the ID of the root is used to check all the child hashes (keyed by _parent ID_, recall), and implement IActivity. When a root activity has replies, a similar process is undergone for the reply activity: sorting it by published date, checking its ID against the edits and reactions hash, and sanitizing to implement IActivity. Replies are then looped over and added to the ordered list before moving on to the next root.
|
|
175
|
-
|
|
176
|
-
We have successfully finished a batch, and will now yield the ordered list to the caller.
|
|
177
|
-
|
|
178
|
-
##### recalculation
|
|
179
|
-
|
|
180
|
-
The generator's `next` method may accepts arguments in order to alter the behavior of the existing generator. Assigning `yield` to a variable captures the arguments passed into `next` method. The `next` method accepts a new value for minActivities, as well as an override for `queryType`. This allows the same generator (and its state) to be used for multiple queries that build one upon another.
|
|
181
|
-
|
|
182
|
-
The first call to `next` (the first call) is initialized automatically as an INITIAL fetch, to populate the generator state, but subsequent calls to `next` method without a passed-in `queryType` will close the generator.
|
|
183
|
-
|
|
184
|
-
### `listActivitiesThreadOrdered(options)` (public)
|
|
185
|
-
|
|
186
|
-
The public facade exposes wrappers for ease of managing an instance of the generator. All methods implement the iterator protocol The following methods are exposed:
|
|
187
|
-
|
|
188
|
-
#### `getOlder()`
|
|
189
|
-
|
|
190
|
-
Implements iterator protocol. Returns oldest activities, then returns any older activities than the oldest fetched.
|
|
191
|
-
|
|
192
|
-
example:
|
|
193
|
-
|
|
194
|
-
```
|
|
195
|
-
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
196
|
-
const firstBatch = await myGen.getOlder();
|
|
197
|
-
console.log(firstBatch)
|
|
198
|
-
/*
|
|
199
|
-
{
|
|
200
|
-
done: false,
|
|
201
|
-
value: IActivity[] // the most recent activities from the given conversation
|
|
202
|
-
}
|
|
203
|
-
*/
|
|
204
|
-
const secondBatch = await myGen.getOlder();
|
|
205
|
-
console.log(secondBatch)
|
|
206
|
-
/*
|
|
207
|
-
{
|
|
208
|
-
done: boolean (true if we reach beginning of convo, else false),
|
|
209
|
-
value: IActivity[] // activities older than firstBatch[0]
|
|
210
|
-
}
|
|
211
|
-
*/
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
#### jumpToActivity(searchActivity)
|
|
215
|
-
|
|
216
|
-
Implements iterator protocol. Returns searched activity as the middle activity in a batch, with older and newer activities surrounding.
|
|
217
|
-
|
|
218
|
-
example:
|
|
219
|
-
|
|
220
|
-
```
|
|
221
|
-
const activitySearchResult = IServerActivity // actual activity object returned by server, most commonly returned by activity search
|
|
222
|
-
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
223
|
-
const results = await myGen.jumpToActivity(activitySearchResult);
|
|
224
|
-
console.log(results)
|
|
225
|
-
/*
|
|
226
|
-
{
|
|
227
|
-
done: true,
|
|
228
|
-
value: [...IActivity[], IActivity<activitySearchResult>, ...IActivity[]]
|
|
229
|
-
}
|
|
230
|
-
*/
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
#### `getNewer()`
|
|
234
|
-
|
|
235
|
-
Implements iterator protocol. Returns most recent activities, then returns any newer activities than the newest fetched.
|
|
236
|
-
|
|
237
|
-
example:
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
241
|
-
const firstBatch = await myGen.getNewer();
|
|
242
|
-
console.log(firstBatch)
|
|
243
|
-
/*
|
|
244
|
-
{
|
|
245
|
-
done: false,
|
|
246
|
-
value: IActivity[] // the most recent activities from the given conversation
|
|
247
|
-
}
|
|
248
|
-
*/
|
|
249
|
-
// NOTE: assume second batch is fetched before any new activities are created in the conversation
|
|
250
|
-
const secondBatch = await myGen.getNewer();
|
|
251
|
-
console.log(secondBatch)
|
|
252
|
-
/*
|
|
253
|
-
{
|
|
254
|
-
done: true,
|
|
255
|
-
value: []
|
|
256
|
-
}
|
|
257
|
-
*/
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
`getNewer()` is of limited use at first, but consider the following example, when used in conjunction with `jumpToActivity`:
|
|
261
|
-
|
|
262
|
-
```
|
|
263
|
-
const activitySearchResult = IServerActivity // actual activity object returned by server, most commonly returned by activity search
|
|
264
|
-
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
265
|
-
const firstBatch = await myGen.jumpToActivity(activitySearchResult);
|
|
266
|
-
console.log(firstBatch)
|
|
267
|
-
/*
|
|
268
|
-
{
|
|
269
|
-
done: true,
|
|
270
|
-
value: [...IActivity[], IActivity<activitySearchResult>, ...IActivity[]]
|
|
271
|
-
}
|
|
272
|
-
*/
|
|
273
|
-
|
|
274
|
-
const secondBatch = await myGen.getNewer();
|
|
275
|
-
console.log(secondBatch);
|
|
276
|
-
/*
|
|
277
|
-
{
|
|
278
|
-
done: false,
|
|
279
|
-
value: IActivity[] // where secondBatch[0] is the next activity after the last of firstBatch
|
|
280
|
-
}
|
|
281
|
-
*/
|
|
282
|
-
```
|
|
1
|
+
# Activity Threading Ordering
|
|
2
|
+
|
|
3
|
+
Activity thread ordering (or "threading") is the act of flattening the hierarchical relationship of thread parents and thread replies.
|
|
4
|
+
|
|
5
|
+
## Why Thread Order?
|
|
6
|
+
|
|
7
|
+
When a client fetches activities from conversation service, convo returns chronologically ordered activities (by published date). If you are a client that attempts to display activities to a user, this is mostly useless. An example:
|
|
8
|
+
|
|
9
|
+
Client: hey convo, give me 10 activities
|
|
10
|
+
|
|
11
|
+
Convo: here.
|
|
12
|
+
|
|
13
|
+
1. reaction
|
|
14
|
+
2. root
|
|
15
|
+
3. reaction
|
|
16
|
+
4. reply
|
|
17
|
+
5. edit
|
|
18
|
+
6. edit
|
|
19
|
+
7. reply
|
|
20
|
+
8. reaction
|
|
21
|
+
9. meeting
|
|
22
|
+
10. reply
|
|
23
|
+
|
|
24
|
+
Client: ...
|
|
25
|
+
|
|
26
|
+
Convo: ...
|
|
27
|
+
|
|
28
|
+
By contrast, thread ordering returns activities in _thread order_, otherwise known as _useful order_. An example:
|
|
29
|
+
|
|
30
|
+
Client: hey convo, give me 10 activities in thread order
|
|
31
|
+
|
|
32
|
+
Convo: here.
|
|
33
|
+
|
|
34
|
+
1. root 4
|
|
35
|
+
2. reply to root 4
|
|
36
|
+
3. reply to root 4
|
|
37
|
+
4. root 3
|
|
38
|
+
5. reply to root 3
|
|
39
|
+
6. reply to root 3
|
|
40
|
+
7. root 2
|
|
41
|
+
8. reply to root 2
|
|
42
|
+
9. reply to root 2
|
|
43
|
+
10. root 1 (newest root activity)
|
|
44
|
+
|
|
45
|
+
Client: 👍
|
|
46
|
+
|
|
47
|
+
Convo: 👍
|
|
48
|
+
|
|
49
|
+
As you can see, thread ordering is useful, and as you may _not_ see, it is difficult to implement. The fewer clients that are forced to implement thread ordering themselves, the better for the wellbeing of the entire world.
|
|
50
|
+
|
|
51
|
+
<!-- ## Useful terminology
|
|
52
|
+
|
|
53
|
+
* thread parent, parent - an activity that has replies
|
|
54
|
+
* root activity, root - an activity that is or could be a thread parent
|
|
55
|
+
* child activity, child - an activity that points to a parent activity, but not necessarily a root
|
|
56
|
+
* thread reply, thread - an activity that is a reply to a root activity
|
|
57
|
+
* orphan - a child activity whose parent has not been fetched
|
|
58
|
+
-->
|
|
59
|
+
|
|
60
|
+
# The Code
|
|
61
|
+
|
|
62
|
+
Thread ordering methods live in the internal converation plugin
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
webex.internal.conversation[*]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
There are two methods that compose thread ordering, a public and private method. The public method is simply a facade of the private method, abstracting the complex away from the caller.
|
|
69
|
+
|
|
70
|
+
### `_listActivitiesThreadOrdered(options)` (private)
|
|
71
|
+
|
|
72
|
+
The `_listActivitiesThreadOrdered` method is a stateful async generator function that fetches thread ordered activities.
|
|
73
|
+
|
|
74
|
+
#### Description
|
|
75
|
+
|
|
76
|
+
The `_listActivitiesThreadOrdered` method instantiates a generator function that yields a promise. It yields results in an infinite loop for the life of the generator (until the instance is no longer referenced and JS garbage collects). This means when the method yields a result it pauses execution until its next invocation, freezing its current state to be used by the next invocation. The generator instance internally tracks the oldest and newest activities it has fetched, using them as the timestamps to query the next set of results in either direction, thus the caller is not required to maintain any state to fetch older and newer activities. This method does not cache all fetched activities, therefore the caller is still required to store their fetched activities or be forced to refetch them.
|
|
77
|
+
|
|
78
|
+
The generator's returned `next` method accepts arguments as well, which allows the caller to change the direction of their querying from newer messages to older ones without creating a new generator instance. `next`
|
|
79
|
+
|
|
80
|
+
#### Parameters
|
|
81
|
+
|
|
82
|
+
_generator initialization options_
|
|
83
|
+
|
|
84
|
+
options
|
|
85
|
+
|
|
86
|
+
options.url: the conversation's convo URL
|
|
87
|
+
|
|
88
|
+
options.minActivities: the minimum number of activities you would like in your batch
|
|
89
|
+
|
|
90
|
+
options.queryType: the direction to fetch activities. One of 'newer', 'older', 'mid'
|
|
91
|
+
|
|
92
|
+
options.search: a server activity object to return as the middle point of the batch, along with its surrounding activities
|
|
93
|
+
|
|
94
|
+
_parameters accepted by the generator's `next` method_
|
|
95
|
+
|
|
96
|
+
options
|
|
97
|
+
|
|
98
|
+
options.queryType: see above
|
|
99
|
+
|
|
100
|
+
options.search: see above
|
|
101
|
+
|
|
102
|
+
#### Return value
|
|
103
|
+
|
|
104
|
+
Calling `_listActivitiesThreadOrdered` returns the generator instance, and does not execute any work. The generator returned has a `next` method that begins the first round of work and returns a `done` boolean and a `value`, the asked-for thread ordered activities.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
interface IActivity {
|
|
108
|
+
id: string;
|
|
109
|
+
activity: <server Activity>;
|
|
110
|
+
reaction: <server ReactionSummary>
|
|
111
|
+
reactionSelf: <server ReactionSelfSummary>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
done: boolean,
|
|
115
|
+
value: IActivity[]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Examples
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
const options = {
|
|
122
|
+
url: 'myconvourl.com',
|
|
123
|
+
minActivities: 20,
|
|
124
|
+
queryType: 'older',
|
|
125
|
+
search: null
|
|
126
|
+
}
|
|
127
|
+
const threadOrderedFetcher = webex.internal.conversation._listActivitiesThreadOrdered(options);
|
|
128
|
+
await threadOrderedFetcher.next() // => { done: boolean, value: IActivity[] }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### The nitty gritty: design
|
|
132
|
+
|
|
133
|
+
There are 3 main phases of the method, with one major subphase:
|
|
134
|
+
|
|
135
|
+
1. initialization: up to the ` while (true)` loop
|
|
136
|
+
2. execution: the ` while (true)` loop
|
|
137
|
+
3. (subphase of execution) fetching: the ` while (!getNoMoreActs())` loop
|
|
138
|
+
4. re-calculation: after the ` yield`
|
|
139
|
+
|
|
140
|
+
##### initialization
|
|
141
|
+
|
|
142
|
+
Parsing the arguments and initializing state is done in this first phase, before the generator's first execution. State that must be retained independent of each execution loop (the code run by the generator's `next` method) is instantiated here. The following variables (or grouped of variables) are created and maintained for the life of the generator:
|
|
143
|
+
|
|
144
|
+
- oldestAct - the oldest activity (by published date) that has been fetched by the gen. instance
|
|
145
|
+
- newestAct - the newest activity that has been fetched by the gen. instance
|
|
146
|
+
- batchSize - the number of activities to fetch to achieve minActivities. This value is set lower in cases where we fetch children, because adding the children to their parents may help us reach minActivities count
|
|
147
|
+
- query - initialize fetching query. The first query will be forced to fetch newest activities in a space to initialize the activity states, unless a search is requested
|
|
148
|
+
- \*\*\*ActivityHash(es): cache objects for children activities. These caches store all child activity types returned from convo fetches in order to keep track of "orphans", or child activities that come in before their parent activity
|
|
149
|
+
|
|
150
|
+
##### execution
|
|
151
|
+
|
|
152
|
+
The execution phase will be run for every call to `next` method. Its job is to fetch activities until the minimum activity limit is reached, then it will build the ordered list of activities and yield them to the caller. Similar to init phase, execution phase has variables declared outside the fetching loop in order to keep track of what we have fetched so far, and whether continuing to fetch is necessary. The execuction phase runs the fetching subphase. The following state variables are tracked by this outer loop, before the fetching phase:
|
|
153
|
+
|
|
154
|
+
- rootActivityHash - the data structure used to track root activities we have fetched in a given batch
|
|
155
|
+
- [* helpers] - many helper functions are declared. They close over state variables and are used to group functionality used by the fetching loop, and for self-documenting purposes
|
|
156
|
+
- fetchLoopCount - track count of fetch loops run. Used to break out of loop in unlikely case that we fetch the same query over and over.
|
|
157
|
+
|
|
158
|
+
##### fetching
|
|
159
|
+
|
|
160
|
+
The fetching phase is contained within the execution phase, and is a loop that will run until we run out of activities to fetch, or explicitly break the loop when we reach the minActivities count with what we've fetched so far. Each fetch loop begins by setting up the query and passing options to `#listActivities` method. All query types depend on a first query to activities to get N root activities. midDate queries (aliased MID) and sinceDate queries (aliased NEWER) also must recursively fetch children to ensure we don't return to caller a root activity that is missing children.\*
|
|
161
|
+
|
|
162
|
+
\*NOTE: These sometimes-necessary children queries depend on the addition of a new method, `#listAllChildActivitiesByParentId`. This method is similar to `listChildActivitiesByParentId`, but this method recursively fetches all the chilren of a certain type, rather than forcing the calling method to fetch 10 children at a time (as is the current child limit set by underlying convo API).
|
|
163
|
+
|
|
164
|
+
Activities returned to us by convo fetches are sorted and placed into maps based off their activity type. Root activities are stored on a per-batch basis, whereas children are stored globally (prevents orphans). Root activities are keyed by their ID, where children are keyed by their parent's ID (`activity.parent.id`).
|
|
165
|
+
|
|
166
|
+
When reactions are fetched, however, we call a _different_ convo API that helps us reduce server roundtrips (this is why the fetch for reactions is different from the fetch for edits and replies).
|
|
167
|
+
|
|
168
|
+
Fetching loop can be explicitly broken out of when we have decided we have the necessary activities for the current batch. If we _don't_ have enough activities, we initialize a new query derived from the former query and return to the top of the fetching loop to continue building our list.
|
|
169
|
+
|
|
170
|
+
##### execution (revisited)
|
|
171
|
+
|
|
172
|
+
After a successful series of fetches have given us the activities necessary for the current batch, we initialize an empty array that acts as our ordered list. To begin, the current batch's root activities are sorted by their published date, and using the ordered list of IDs, we begin to loop over the rootActivityHash.
|
|
173
|
+
|
|
174
|
+
For every root activity, the ID of the root is used to check all the child hashes (keyed by _parent ID_, recall), and implement IActivity. When a root activity has replies, a similar process is undergone for the reply activity: sorting it by published date, checking its ID against the edits and reactions hash, and sanitizing to implement IActivity. Replies are then looped over and added to the ordered list before moving on to the next root.
|
|
175
|
+
|
|
176
|
+
We have successfully finished a batch, and will now yield the ordered list to the caller.
|
|
177
|
+
|
|
178
|
+
##### recalculation
|
|
179
|
+
|
|
180
|
+
The generator's `next` method may accepts arguments in order to alter the behavior of the existing generator. Assigning `yield` to a variable captures the arguments passed into `next` method. The `next` method accepts a new value for minActivities, as well as an override for `queryType`. This allows the same generator (and its state) to be used for multiple queries that build one upon another.
|
|
181
|
+
|
|
182
|
+
The first call to `next` (the first call) is initialized automatically as an INITIAL fetch, to populate the generator state, but subsequent calls to `next` method without a passed-in `queryType` will close the generator.
|
|
183
|
+
|
|
184
|
+
### `listActivitiesThreadOrdered(options)` (public)
|
|
185
|
+
|
|
186
|
+
The public facade exposes wrappers for ease of managing an instance of the generator. All methods implement the iterator protocol The following methods are exposed:
|
|
187
|
+
|
|
188
|
+
#### `getOlder()`
|
|
189
|
+
|
|
190
|
+
Implements iterator protocol. Returns oldest activities, then returns any older activities than the oldest fetched.
|
|
191
|
+
|
|
192
|
+
example:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
196
|
+
const firstBatch = await myGen.getOlder();
|
|
197
|
+
console.log(firstBatch)
|
|
198
|
+
/*
|
|
199
|
+
{
|
|
200
|
+
done: false,
|
|
201
|
+
value: IActivity[] // the most recent activities from the given conversation
|
|
202
|
+
}
|
|
203
|
+
*/
|
|
204
|
+
const secondBatch = await myGen.getOlder();
|
|
205
|
+
console.log(secondBatch)
|
|
206
|
+
/*
|
|
207
|
+
{
|
|
208
|
+
done: boolean (true if we reach beginning of convo, else false),
|
|
209
|
+
value: IActivity[] // activities older than firstBatch[0]
|
|
210
|
+
}
|
|
211
|
+
*/
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### jumpToActivity(searchActivity)
|
|
215
|
+
|
|
216
|
+
Implements iterator protocol. Returns searched activity as the middle activity in a batch, with older and newer activities surrounding.
|
|
217
|
+
|
|
218
|
+
example:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
const activitySearchResult = IServerActivity // actual activity object returned by server, most commonly returned by activity search
|
|
222
|
+
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
223
|
+
const results = await myGen.jumpToActivity(activitySearchResult);
|
|
224
|
+
console.log(results)
|
|
225
|
+
/*
|
|
226
|
+
{
|
|
227
|
+
done: true,
|
|
228
|
+
value: [...IActivity[], IActivity<activitySearchResult>, ...IActivity[]]
|
|
229
|
+
}
|
|
230
|
+
*/
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `getNewer()`
|
|
234
|
+
|
|
235
|
+
Implements iterator protocol. Returns most recent activities, then returns any newer activities than the newest fetched.
|
|
236
|
+
|
|
237
|
+
example:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
241
|
+
const firstBatch = await myGen.getNewer();
|
|
242
|
+
console.log(firstBatch)
|
|
243
|
+
/*
|
|
244
|
+
{
|
|
245
|
+
done: false,
|
|
246
|
+
value: IActivity[] // the most recent activities from the given conversation
|
|
247
|
+
}
|
|
248
|
+
*/
|
|
249
|
+
// NOTE: assume second batch is fetched before any new activities are created in the conversation
|
|
250
|
+
const secondBatch = await myGen.getNewer();
|
|
251
|
+
console.log(secondBatch)
|
|
252
|
+
/*
|
|
253
|
+
{
|
|
254
|
+
done: true,
|
|
255
|
+
value: []
|
|
256
|
+
}
|
|
257
|
+
*/
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`getNewer()` is of limited use at first, but consider the following example, when used in conjunction with `jumpToActivity`:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
const activitySearchResult = IServerActivity // actual activity object returned by server, most commonly returned by activity search
|
|
264
|
+
const myGen = webex.internal.conversation.listActivitiesThreadOrdered(opts);
|
|
265
|
+
const firstBatch = await myGen.jumpToActivity(activitySearchResult);
|
|
266
|
+
console.log(firstBatch)
|
|
267
|
+
/*
|
|
268
|
+
{
|
|
269
|
+
done: true,
|
|
270
|
+
value: [...IActivity[], IActivity<activitySearchResult>, ...IActivity[]]
|
|
271
|
+
}
|
|
272
|
+
*/
|
|
273
|
+
|
|
274
|
+
const secondBatch = await myGen.getNewer();
|
|
275
|
+
console.log(secondBatch);
|
|
276
|
+
/*
|
|
277
|
+
{
|
|
278
|
+
done: false,
|
|
279
|
+
value: IActivity[] // where secondBatch[0] is the next activity after the last of firstBatch
|
|
280
|
+
}
|
|
281
|
+
*/
|
|
282
|
+
```
|
package/src/config.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
conversation: {
|
|
7
|
-
allowedInboundTags: {
|
|
8
|
-
'webex-mention': ['data-object-type', 'data-object-id', 'data-object-url'],
|
|
9
|
-
},
|
|
10
|
-
allowedOutboundTags: {
|
|
11
|
-
'webex-mention': ['data-object-type', 'data-object-id', 'data-object-url'],
|
|
12
|
-
},
|
|
13
|
-
// eslint-disable-next-line no-empty-function
|
|
14
|
-
inboundProcessFunc: () => {},
|
|
15
|
-
// eslint-disable-next-line no-empty-function
|
|
16
|
-
outboundProcessFunc: () => {},
|
|
17
|
-
allowedInboundStyles: [],
|
|
18
|
-
allowedOutboundStyles: [],
|
|
19
|
-
/**
|
|
20
|
-
* Max height for thumbnails generated when sharing an image
|
|
21
|
-
* @type {number}
|
|
22
|
-
*/
|
|
23
|
-
thumbnailMaxHeight: 960,
|
|
24
|
-
/**
|
|
25
|
-
* Max width for thumbnails generated when sharing an image
|
|
26
|
-
* @type {number}
|
|
27
|
-
*/
|
|
28
|
-
thumbnailMaxWidth: 640,
|
|
29
|
-
/**
|
|
30
|
-
* Primarily for testing. When true, decrypting an activity will create a
|
|
31
|
-
* sister property with the original encrypted string
|
|
32
|
-
* @type {Boolean}
|
|
33
|
-
*/
|
|
34
|
-
keepEncryptedProperties: false,
|
|
35
|
-
decryptionFailureMessage: 'This message cannot be decrypted',
|
|
36
|
-
},
|
|
37
|
-
};
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
conversation: {
|
|
7
|
+
allowedInboundTags: {
|
|
8
|
+
'webex-mention': ['data-object-type', 'data-object-id', 'data-object-url'],
|
|
9
|
+
},
|
|
10
|
+
allowedOutboundTags: {
|
|
11
|
+
'webex-mention': ['data-object-type', 'data-object-id', 'data-object-url'],
|
|
12
|
+
},
|
|
13
|
+
// eslint-disable-next-line no-empty-function
|
|
14
|
+
inboundProcessFunc: () => {},
|
|
15
|
+
// eslint-disable-next-line no-empty-function
|
|
16
|
+
outboundProcessFunc: () => {},
|
|
17
|
+
allowedInboundStyles: [],
|
|
18
|
+
allowedOutboundStyles: [],
|
|
19
|
+
/**
|
|
20
|
+
* Max height for thumbnails generated when sharing an image
|
|
21
|
+
* @type {number}
|
|
22
|
+
*/
|
|
23
|
+
thumbnailMaxHeight: 960,
|
|
24
|
+
/**
|
|
25
|
+
* Max width for thumbnails generated when sharing an image
|
|
26
|
+
* @type {number}
|
|
27
|
+
*/
|
|
28
|
+
thumbnailMaxWidth: 640,
|
|
29
|
+
/**
|
|
30
|
+
* Primarily for testing. When true, decrypting an activity will create a
|
|
31
|
+
* sister property with the original encrypted string
|
|
32
|
+
* @type {Boolean}
|
|
33
|
+
*/
|
|
34
|
+
keepEncryptedProperties: false,
|
|
35
|
+
decryptionFailureMessage: 'This message cannot be decrypted',
|
|
36
|
+
},
|
|
37
|
+
};
|
package/src/constants.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export const KEY_ROTATION_REQUIRED = 1400087;
|
|
2
|
-
export const KEY_ALREADY_ROTATED = 1409004;
|
|
3
|
-
export const ENCRYPTION_KEY_URL_MISMATCH = 1400049;
|
|
1
|
+
export const KEY_ROTATION_REQUIRED = 1400087;
|
|
2
|
+
export const KEY_ALREADY_ROTATED = 1409004;
|
|
3
|
+
export const ENCRYPTION_KEY_URL_MISMATCH = 1400049;
|