@tellescope/sdk 1.67.0 → 1.67.2

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.
@@ -10,6 +10,9 @@ import {
10
10
  UserDisplayInfo,
11
11
  } from "@tellescope/types-client"
12
12
  import {
13
+ CreateTicketActionInfo,
14
+ CreateTicketAssignmentStrategies,
15
+ CreateTicketAssignmentStrategy,
13
16
  FormResponseValue,
14
17
  ModelName,
15
18
  } from "@tellescope/types-models"
@@ -5026,8 +5029,414 @@ const nextReminderInMS_tests = async () => {
5026
5029
  ])
5027
5030
  }
5028
5031
 
5032
+ const pollForResults = async <T>(f: () => Promise<T>, evaluate: (r: T) => boolean, intervalInMS=500, iterations=20) => {
5033
+ for (let i = 0; i < iterations; i++) {
5034
+ await wait(undefined, intervalInMS)
5035
+ const result = await f()
5036
+ if (evaluate(result)) return result
5037
+ }
5038
+
5039
+ throw new Error("failed pollForResults")
5040
+ }
5041
+
5029
5042
  const test_ticket_automation_assignment_and_optimization = async () => {
5030
- console.log((await sdk.api.users.getSome({ })).length)
5043
+ log_header("Ticket Automation / Assignment Tests")
5044
+
5045
+ const users = await sdk.api.users.getSome()
5046
+ if (users.length < 3) throw new Error("Must have at least 3 users to detect invalid assignment")
5047
+
5048
+ await sdk.api.users.updateOne(sdk.userInfo.id, { tags: ['tag1', 'tag2'] })
5049
+ await sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { tags: ['tag1', 'tag3'] })
5050
+
5051
+ const journey = await sdk.api.journeys.createOne({ title: "Testing" })
5052
+
5053
+ let foregroundTestCounter = 0
5054
+ const testForegroundTicket = async ({
5055
+ assignedTo,
5056
+ info,
5057
+ validOwners,
5058
+ enduser,
5059
+ closedForReason,
5060
+ testDelayedChild,
5061
+ } : {
5062
+ assignedTo: string[]
5063
+ info: Pick<CreateTicketActionInfo, 'assignmentStrategy' | 'defaultAssignee'>,
5064
+ validOwners: string[],
5065
+ closedForReason?: string,
5066
+ enduser?: Enduser,
5067
+ testDelayedChild?: boolean,
5068
+ }) => {
5069
+ const e = enduser || await sdk.api.endusers.createOne({ assignedTo, journeys: { [journey.id]: '' } })
5070
+
5071
+ const step = await sdk.api.automation_steps.createOne({
5072
+ action: { type: 'createTicket', info: { ...info, title: 'background ticket' } },
5073
+ events: [{
5074
+ type: 'ticketCompleted',
5075
+ info: closedForReason ? { automationStepId: PLACEHOLDER_ID, closedForReason } : { automationStepId: PLACEHOLDER_ID }
5076
+ }],
5077
+ journeyId: journey.id,
5078
+ })
5079
+ const statusStep = await sdk.api.automation_steps.createOne({
5080
+ action: { type: 'setEnduserStatus', info: { status: 'Test Status' } },
5081
+ events: [{
5082
+ type: 'ticketCompleted',
5083
+ info: closedForReason ? { automationStepId: PLACEHOLDER_ID, closedForReason } : { automationStepId: PLACEHOLDER_ID }
5084
+ }],
5085
+ journeyId: journey.id,
5086
+ })
5087
+ const child = await sdk.api.automation_steps.createOne({
5088
+ action: { type: 'setEnduserStatus', info: { status: 'Test Status' } },
5089
+ events: [{
5090
+ type: 'afterAction',
5091
+ info: {
5092
+ automationStepId: step.id,
5093
+ delay: 0, delayInMS: 0, unit: 'Days',
5094
+ }
5095
+ }],
5096
+ journeyId: journey.id,
5097
+ })
5098
+
5099
+ const ticket = await sdk.api.tickets.createOne({
5100
+ title: 'foreground ticket',
5101
+ enduserId: e.id,
5102
+ automationStepId: PLACEHOLDER_ID,
5103
+ journeyId: journey.id,
5104
+ owner: validOwners[0],
5105
+ closedForReason,
5106
+ })
5107
+
5108
+ await async_test(
5109
+ `Foreground ticket assignment ${++foregroundTestCounter}`,
5110
+ () => sdk.api.tickets.close_ticket({ ticketId: ticket.id, closedForReason }),
5111
+ { onResult: ({ generated }) => !!generated?.owner && validOwners.includes(generated.owner) }
5112
+ )
5113
+ await async_test(
5114
+ `Foreground ticket nop, no duplicates`,
5115
+ () => sdk.api.automated_actions.getSome({ filter: { automationStepId: step.id } }),
5116
+ { onResult: steps => steps.length === 1 && !!steps[0].isNOP }
5117
+ )
5118
+ await async_test(
5119
+ `Background action queued, no duplicates`,
5120
+ () => sdk.api.automated_actions.getSome({ filter: { automationStepId: statusStep.id } }),
5121
+ {
5122
+ onResult: steps => steps.length === 1 && !steps[0].isNOP
5123
+ }
5124
+ )
5125
+
5126
+ // verify that ticket generated by close_ticket goes on to generate its own delayed actions
5127
+ if (testDelayedChild) {
5128
+ await async_test(
5129
+ `Delayed child ticket`,
5130
+ () => pollForResults(() => sdk.api.automated_actions.getSome({ filter: { automationStepId: child.id } }), t => !!t.length),
5131
+ { onResult: steps => steps.length === 1 && !steps[0].isNOP }
5132
+ )
5133
+ }
5134
+
5135
+ await Promise.all([
5136
+ sdk.api.endusers.deleteOne(e.id),
5137
+ sdk.api.automation_steps.deleteOne(step.id),
5138
+ sdk.api.automation_steps.deleteOne(statusStep.id),
5139
+ sdk.api.automation_steps.deleteOne(child.id),
5140
+ ])
5141
+ }
5142
+
5143
+ await testForegroundTicket({
5144
+ assignedTo: [],
5145
+ info: {
5146
+ assignmentStrategy: { type: 'default', info: {} } ,
5147
+ defaultAssignee: sdk.userInfo.id
5148
+ },
5149
+ validOwners: [sdk.userInfo.id],
5150
+ testDelayedChild: true,
5151
+ })
5152
+ await testForegroundTicket({
5153
+ assignedTo: [],
5154
+ info: {
5155
+ assignmentStrategy: { type: 'default', info: {} } ,
5156
+ defaultAssignee: sdk.userInfo.id
5157
+ },
5158
+ validOwners: [sdk.userInfo.id],
5159
+ closedForReason: "closedForReason test",
5160
+ testDelayedChild: true,
5161
+ })
5162
+ await testForegroundTicket({
5163
+ assignedTo: [],
5164
+ info: {
5165
+ assignmentStrategy: { type: 'default', info: {} } ,
5166
+ defaultAssignee: sdkNonAdmin.userInfo.id
5167
+ },
5168
+ validOwners: [sdkNonAdmin.userInfo.id],
5169
+ })
5170
+ await testForegroundTicket({
5171
+ assignedTo: [],
5172
+ info: {
5173
+ assignmentStrategy: { type: 'previous-owner', info: {} } ,
5174
+ defaultAssignee: sdk.userInfo.id
5175
+ },
5176
+ validOwners: [sdkNonAdmin.userInfo.id],
5177
+ })
5178
+ await testForegroundTicket({
5179
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5180
+ info: {
5181
+ assignmentStrategy: { type: 'care-team-primary', info: {} } ,
5182
+ defaultAssignee: sdk.userInfo.id
5183
+ },
5184
+ validOwners: [sdkNonAdmin.userInfo.id],
5185
+ })
5186
+ await testForegroundTicket({
5187
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5188
+ info: {
5189
+ assignmentStrategy: { type: 'care-team-random', info: {} } ,
5190
+ defaultAssignee: sdk.userInfo.id
5191
+ },
5192
+ validOwners: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5193
+ })
5194
+ await testForegroundTicket({
5195
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5196
+ info: {
5197
+ assignmentStrategy: { type: 'by-tags', info: { qualifier: 'One Of', values: ['tag1']} } ,
5198
+ defaultAssignee: sdkNonAdmin.userInfo.id,
5199
+ },
5200
+ validOwners: [sdk.userInfo.id, sdkNonAdmin.userInfo.id, ],
5201
+ })
5202
+ await testForegroundTicket({
5203
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5204
+ info: {
5205
+ assignmentStrategy: { type: 'by-tags', info: { qualifier: 'One Of', values: ['tag2']} } ,
5206
+ defaultAssignee: sdkNonAdmin.userInfo.id
5207
+ },
5208
+ validOwners: [sdk.userInfo.id],
5209
+ })
5210
+ await testForegroundTicket({
5211
+ assignedTo: [],
5212
+ info: {
5213
+ assignmentStrategy: { type: 'by-tags', info: { qualifier: 'One Of', values: ['tag3']} } ,
5214
+ defaultAssignee: sdk.userInfo.id
5215
+ },
5216
+ validOwners: [sdkNonAdmin.userInfo.id],
5217
+ })
5218
+
5219
+
5220
+ let backgroundTestCounter = 0
5221
+ const testBackgroundTicket = async ({
5222
+ assignedTo,
5223
+ info,
5224
+ validOwners,
5225
+ enduser,
5226
+ } : {
5227
+ assignedTo: string[]
5228
+ info: Pick<CreateTicketActionInfo, 'assignmentStrategy' | 'defaultAssignee'>,
5229
+ validOwners: string[],
5230
+ enduser?: Enduser,
5231
+ }) => {
5232
+ const e = enduser || await sdk.api.endusers.createOne({ assignedTo })
5233
+ await sdk.api.automated_actions.createOne({
5234
+ action: { type: 'createTicket', info: { ...info, title: 'background ticket' } },
5235
+ automationStepId: PLACEHOLDER_ID,
5236
+ enduserId: e.id,
5237
+ event: { type: 'afterAction', info: { automationStepId: PLACEHOLDER_ID, delay: 0, delayInMS: 0, unit: 'Days' } },
5238
+ journeyId: journey.id,
5239
+ status: 'active',
5240
+ processAfter: Date.now(),
5241
+ })
5242
+
5243
+ await async_test(
5244
+ `Background ticket assignment ${++backgroundTestCounter}`,
5245
+ () => pollForResults(() => sdk.api.tickets.getSome({ filter: { enduserId: e.id, title: 'background ticket' } }), t => !!t.length),
5246
+ { onResult: ts => ts.length === 1 && !!ts[0].owner && validOwners.includes(ts[0].owner) }
5247
+ )
5248
+
5249
+ await sdk.api.endusers.deleteOne(e.id)
5250
+ }
5251
+
5252
+ await testBackgroundTicket({
5253
+ assignedTo: [],
5254
+ info: {
5255
+ assignmentStrategy: { type: 'default', info: {} } ,
5256
+ defaultAssignee: sdk.userInfo.id
5257
+ },
5258
+ validOwners: [sdk.userInfo.id],
5259
+ })
5260
+ await testBackgroundTicket({
5261
+ assignedTo: [],
5262
+ info: {
5263
+ assignmentStrategy: { type: 'default', info: {} } ,
5264
+ defaultAssignee: sdkNonAdmin.userInfo.id
5265
+ },
5266
+ validOwners: [sdkNonAdmin.userInfo.id],
5267
+ })
5268
+
5269
+ // ticket needs existing enduser, previous owner for test to work
5270
+ const enduser = await sdk.api.endusers.createOne({ fname: 'previous-owner-test'})
5271
+ await sdk.api.tickets.createOne({
5272
+ // title should be different than 'background test' so it doesn't create false positive test
5273
+ title: 'previous-owner-test', enduserId: enduser.id, journeyId: journey.id, owner: sdkNonAdmin.userInfo.id
5274
+ })
5275
+ await testBackgroundTicket({
5276
+ assignedTo: [],
5277
+ enduser,
5278
+ info: {
5279
+ assignmentStrategy: { type: 'previous-owner', info: {} } ,
5280
+ defaultAssignee: sdk.userInfo.id
5281
+ },
5282
+ validOwners: [sdkNonAdmin.userInfo.id],
5283
+ })
5284
+
5285
+ await testBackgroundTicket({
5286
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5287
+ info: {
5288
+ assignmentStrategy: { type: 'care-team-primary', info: {} } ,
5289
+ defaultAssignee: sdk.userInfo.id
5290
+ },
5291
+ validOwners: [sdkNonAdmin.userInfo.id],
5292
+ })
5293
+ await testBackgroundTicket({
5294
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5295
+ info: {
5296
+ assignmentStrategy: { type: 'care-team-random', info: {} } ,
5297
+ defaultAssignee: sdk.userInfo.id
5298
+ },
5299
+ validOwners: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5300
+ })
5301
+ await testBackgroundTicket({
5302
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5303
+ info: {
5304
+ assignmentStrategy: { type: 'by-tags', info: { qualifier: 'One Of', values: ['tag1']} } ,
5305
+ defaultAssignee: sdkNonAdmin.userInfo.id,
5306
+ },
5307
+ validOwners: [sdk.userInfo.id, sdkNonAdmin.userInfo.id, ],
5308
+ })
5309
+ await testBackgroundTicket({
5310
+ assignedTo: [sdkNonAdmin.userInfo.id, sdk.userInfo.id],
5311
+ info: {
5312
+ assignmentStrategy: { type: 'by-tags', info: { qualifier: 'One Of', values: ['tag2']} } ,
5313
+ defaultAssignee: sdkNonAdmin.userInfo.id
5314
+ },
5315
+ validOwners: [sdk.userInfo.id],
5316
+ })
5317
+ await testBackgroundTicket({
5318
+ assignedTo: [],
5319
+ info: {
5320
+ assignmentStrategy: { type: 'by-tags', info: { qualifier: 'One Of', values: ['tag3']} } ,
5321
+ defaultAssignee: sdk.userInfo.id
5322
+ },
5323
+ validOwners: [sdkNonAdmin.userInfo.id],
5324
+ })
5325
+
5326
+ return Promise.all([
5327
+ await sdk.api.journeys.deleteOne(journey.id)
5328
+ ])
5329
+ }
5330
+
5331
+ const field_equals_trigger_tests = async () => {
5332
+ log_header("Field Equals / Trigger Tests")
5333
+
5334
+ const journey = await sdk.api.journeys.createOne({ title: 'test' })
5335
+ const step = await sdk.api.automation_steps.createOne({
5336
+ journeyId: journey.id,
5337
+ events: [{
5338
+ type: 'onJourneyStart',
5339
+ info: {}
5340
+ }],
5341
+ action: {
5342
+ type: 'addEnduserTags',
5343
+ info: { tags: ['Journey Tag']}
5344
+ },
5345
+ })
5346
+
5347
+ const existsTriggerTags = await sdk.api.automation_triggers.createOne({
5348
+ event: { type: 'Field Equals', info: { field: 'fname', value: "$exists" } },
5349
+ action: { type: 'Add Tags', info: { tags: ["Tag"] } },
5350
+ status: "Active",
5351
+ title: 'existsTriggerTags',
5352
+ })
5353
+ const existsTriggerAddToJourney = await sdk.api.automation_triggers.createOne({
5354
+ event: { type: 'Field Equals', info: { field: 'fname', value: "$exists" } },
5355
+ action: { type: 'Add To Journey', info: { journeyId: journey.id } },
5356
+ status: "Active",
5357
+ title: 'existsTriggerAddToJourney',
5358
+ })
5359
+ const equalsTriggerTags = await sdk.api.automation_triggers.createOne({
5360
+ event: { type: 'Field Equals', info: { field: 'lname', value: "Explicit" } },
5361
+ action: { type: 'Add Tags', info: { tags: ["Tag"] } },
5362
+ status: "Active",
5363
+ title: 'equalsTriggerTags',
5364
+ })
5365
+ const equalsTriggerAddToJourney = await sdk.api.automation_triggers.createOne({
5366
+ event: { type: 'Field Equals', info: { field: 'lname', value: "Explicit" } },
5367
+ action: { type: 'Add To Journey', info: { journeyId: journey.id } },
5368
+ enduserCondition: { $and: [ { condition: { lname: 'Explicit' } } ] },
5369
+ status: "Active",
5370
+ title: 'equalsTriggerAddToJourney',
5371
+ })
5372
+ const conditionalTriggerTags = await sdk.api.automation_triggers.createOne({
5373
+ event: { type: 'Field Equals', info: { field: 'mname', value: "$exists" } },
5374
+ action: { type: 'Add Tags', info: { tags: ["Tag"] } },
5375
+ status: "Active",
5376
+ enduserCondition: { $and: [ { condition: { lname: 'Conditional' } } ] },
5377
+ title: 'conditionalTriggerTags',
5378
+ })
5379
+ const conditionalTriggerAddToJourney = await sdk.api.automation_triggers.createOne({
5380
+ event: { type: 'Field Equals', info: { field: 'mname', value: "$exists" } },
5381
+ action: { type: 'Add To Journey', info: { journeyId: journey.id } },
5382
+ enduserCondition: { $and: [ { condition: { lname: 'Conditional' } } ] },
5383
+ status: "Active",
5384
+ title: 'conditionalTriggerAddToJourney',
5385
+ })
5386
+
5387
+ // names are capitalized automatically, so make sure that is reflected in conditions
5388
+ const endusers = (await sdk.api.endusers.createSome([
5389
+ { fname: 'Test' },
5390
+ { fname: 'Test' },
5391
+ { lname: 'Test' }, // should not be added to any journey
5392
+ { lname: 'Explicit' },
5393
+ { mname: 'Test' }, // should not be added to any journey for failing conditional logic
5394
+ { mname: 'Test' }, // should not be added to any journey for failing conditional logic
5395
+ { mname: 'Test', lname: 'Nonconditional' }, // should not be added to any journey for failing conditional logic
5396
+ { mname: 'Test', lname: 'Conditional' },
5397
+ ])).created
5398
+
5399
+ await async_test(
5400
+ `Journey and tags set`,
5401
+ () => pollForResults(
5402
+ sdk.api.endusers.getSome,
5403
+ es => (
5404
+ es.filter(e => e.tags?.includes('Tag') && e.journeys?.[journey.id] !== undefined).length === 4
5405
+ ),
5406
+ 200,
5407
+ 25,
5408
+ ),
5409
+ passOnAnyResult,
5410
+ )
5411
+ await async_test(
5412
+ `Background action queued for journey`,
5413
+ () => pollForResults(
5414
+ sdk.api.automated_actions.getSome,
5415
+ as => (
5416
+ as.filter(a => a.automationStepId === step.id && endusers.find(e => e.id === a.enduserId)).length === 4
5417
+ ),
5418
+ 200,
5419
+ 25,
5420
+ ),
5421
+ passOnAnyResult,
5422
+ )
5423
+ await async_test(
5424
+ `Endusers have trigger ids`,
5425
+ sdk.api.endusers.getSome,
5426
+ { onResult: es => es.filter(e => e.triggerIds?.length === 2).length === 4 },
5427
+ )
5428
+
5429
+
5430
+ return await Promise.all([
5431
+ sdk.api.journeys.deleteOne(journey.id),
5432
+ sdk.api.automation_triggers.deleteOne(existsTriggerTags.id),
5433
+ sdk.api.automation_triggers.deleteOne(existsTriggerAddToJourney.id),
5434
+ sdk.api.automation_triggers.deleteOne(equalsTriggerAddToJourney.id),
5435
+ sdk.api.automation_triggers.deleteOne(equalsTriggerTags.id),
5436
+ sdk.api.automation_triggers.deleteOne(conditionalTriggerTags.id),
5437
+ sdk.api.automation_triggers.deleteOne(conditionalTriggerAddToJourney.id),
5438
+ ...endusers.map(e => sdk.api.endusers.deleteOne(e.id)),
5439
+ ])
5031
5440
  }
5032
5441
 
5033
5442
  const NO_TEST = () => {}
@@ -5169,6 +5578,7 @@ const validate_schema = () => {
5169
5578
  await mfa_tests()
5170
5579
  await setup_tests()
5171
5580
  await multi_tenant_tests() // should come right after setup tests
5581
+ await field_equals_trigger_tests()
5172
5582
  await test_ticket_automation_assignment_and_optimization()
5173
5583
  await role_based_access_tests()
5174
5584
  await automation_trigger_tests()
@@ -223,7 +223,7 @@ const endusers_tests = async (isSubscribed: boolean) => {
223
223
 
224
224
  const update = { assignedTo: [sdk.userInfo.id] }
225
225
  await sdk.api.endusers.updateOne(enduser.id, update)
226
-
226
+ await sdk.api.endusers.updateOne(enduser.id, { fields: { 'dontIncludeInWebhook': true } }, { dontSendWebhook: true })
227
227
  await check_next_webhook(
228
228
  a => {
229
229
  delete a.updates?.[0]?.recordBeforeUpdate.humanReadableId
Binary file