@nyaruka/temba-components 0.130.3 → 0.130.5

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/demo/sortable-rules-demo.html +155 -0
  3. package/dist/temba-components.js +133 -143
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/events.js.map +1 -1
  6. package/out-tsc/src/flow/CanvasNode.js +13 -7
  7. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  8. package/out-tsc/src/flow/actions/send_msg.js +1 -0
  9. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  10. package/out-tsc/src/flow/nodes/split_by_groups.js +149 -1
  11. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  12. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +1 -0
  13. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  14. package/out-tsc/src/flow/nodes/wait_for_response.js +81 -75
  15. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  16. package/out-tsc/src/form/ArrayEditor.js +106 -28
  17. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  18. package/out-tsc/src/form/select/Select.js +21 -25
  19. package/out-tsc/src/form/select/Select.js.map +1 -1
  20. package/out-tsc/src/list/SortableList.js +214 -140
  21. package/out-tsc/src/list/SortableList.js.map +1 -1
  22. package/out-tsc/src/live/ContactChat.js +18 -13
  23. package/out-tsc/src/live/ContactChat.js.map +1 -1
  24. package/out-tsc/test/nodes/split_by_groups.test.js +130 -0
  25. package/out-tsc/test/nodes/split_by_groups.test.js.map +1 -0
  26. package/out-tsc/test/nodes/wait_for_response.test.js +149 -0
  27. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  28. package/out-tsc/test/temba-field-config.test.js +56 -0
  29. package/out-tsc/test/temba-field-config.test.js.map +1 -1
  30. package/package.json +1 -1
  31. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  32. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  33. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  34. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  35. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  36. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  37. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  38. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  39. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  40. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  41. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  42. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  43. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  44. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  45. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  46. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  47. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  48. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  49. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  50. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  51. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  52. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  53. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  54. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  55. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  56. package/screenshots/truth/contacts/chat-failure.png +0 -0
  57. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  58. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  59. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  60. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  61. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  62. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  63. package/screenshots/truth/editor/wait.png +0 -0
  64. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  65. package/screenshots/truth/list/fields-dragging.png +0 -0
  66. package/screenshots/truth/list/sortable-dragging.png +0 -0
  67. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  68. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  69. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  70. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  71. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  72. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  73. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  74. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  75. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  76. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  77. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  78. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  79. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  80. package/screenshots/truth/select/search-enabled.png +0 -0
  81. package/screenshots/truth/select/search-selected-focus.png +0 -0
  82. package/screenshots/truth/select/search-selected.png +0 -0
  83. package/screenshots/truth/templates/default.png +0 -0
  84. package/screenshots/truth/templates/unapproved.png +0 -0
  85. package/src/events.ts +6 -6
  86. package/src/flow/CanvasNode.ts +15 -13
  87. package/src/flow/actions/send_msg.ts +1 -0
  88. package/src/flow/nodes/split_by_groups.ts +190 -1
  89. package/src/flow/nodes/split_by_llm_categorize.ts +1 -0
  90. package/src/flow/nodes/wait_for_response.ts +98 -74
  91. package/src/form/ArrayEditor.ts +112 -28
  92. package/src/form/select/Select.ts +24 -25
  93. package/src/list/SortableList.ts +250 -149
  94. package/src/live/ContactChat.ts +20 -13
  95. package/test/nodes/split_by_groups.test.ts +165 -0
  96. package/test/nodes/wait_for_response.test.ts +182 -0
  97. package/test/temba-field-config.test.ts +69 -0
  98. package/test-assets/contacts/history.json +37 -35
  99. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
@@ -0,0 +1,165 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { split_by_groups } from '../../src/flow/nodes/split_by_groups';
3
+ import { Node } from '../../src/store/flow-definition.d';
4
+
5
+ describe('temba-split-by-groups', () => {
6
+ it('should transform from flow definition to form data correctly', () => {
7
+ const node: Node = {
8
+ uuid: 'test-node-uuid',
9
+ actions: [],
10
+ router: {
11
+ type: 'switch',
12
+ cases: [
13
+ {
14
+ uuid: 'case-1',
15
+ type: 'has_group',
16
+ arguments: ['group-uuid-1', 'Group 1'],
17
+ category_uuid: 'cat-1'
18
+ },
19
+ {
20
+ uuid: 'case-2',
21
+ type: 'has_group',
22
+ arguments: ['group-uuid-2', 'Group 2'],
23
+ category_uuid: 'cat-2'
24
+ }
25
+ ],
26
+ categories: [
27
+ { uuid: 'cat-1', name: 'Group 1', exit_uuid: 'exit-1' },
28
+ { uuid: 'cat-2', name: 'Group 2', exit_uuid: 'exit-2' },
29
+ { uuid: 'cat-other', name: 'Other', exit_uuid: 'exit-other' }
30
+ ],
31
+ default_category_uuid: 'cat-other',
32
+ operand: '@contact.groups',
33
+ result_name: ''
34
+ },
35
+ exits: [
36
+ { uuid: 'exit-1', destination_uuid: null },
37
+ { uuid: 'exit-2', destination_uuid: null },
38
+ { uuid: 'exit-other', destination_uuid: null }
39
+ ]
40
+ };
41
+
42
+ const formData = split_by_groups.toFormData!(node);
43
+
44
+ expect(formData.uuid).to.equal('test-node-uuid');
45
+ expect(formData.groups).to.have.lengthOf(2);
46
+ expect(formData.groups[0]).to.deep.equal({
47
+ uuid: 'group-uuid-1',
48
+ name: 'Group 1'
49
+ });
50
+ expect(formData.groups[1]).to.deep.equal({
51
+ uuid: 'group-uuid-2',
52
+ name: 'Group 2'
53
+ });
54
+ });
55
+
56
+ it('should transform from form data to flow definition correctly', () => {
57
+ const originalNode: Node = {
58
+ uuid: 'test-node-uuid',
59
+ actions: [],
60
+ exits: []
61
+ };
62
+
63
+ const formData = {
64
+ uuid: 'test-node-uuid',
65
+ groups: [
66
+ { uuid: 'group-uuid-1', name: 'Group 1' },
67
+ { uuid: 'group-uuid-2', name: 'Group 2' }
68
+ ]
69
+ };
70
+
71
+ const resultNode = split_by_groups.fromFormData!(formData, originalNode);
72
+
73
+ expect(resultNode.uuid).to.equal('test-node-uuid');
74
+ expect(resultNode.router!.type).to.equal('switch');
75
+ expect(resultNode.router!.operand).to.equal('@contact.groups');
76
+ expect(resultNode.router!.cases).to.have.lengthOf(2);
77
+ expect(resultNode.router!.categories).to.have.lengthOf(3); // 2 groups + Other
78
+ expect(resultNode.exits).to.have.lengthOf(3); // 2 groups + Other
79
+
80
+ // Check first group case
81
+ const case1 = resultNode.router!.cases![0];
82
+ expect(case1.type).to.equal('has_group');
83
+ expect(case1.arguments).to.deep.equal(['group-uuid-1', 'Group 1']);
84
+
85
+ // Check that "Other" category exists
86
+ const otherCategory = resultNode.router!.categories!.find(
87
+ (cat) => cat.name === 'Other'
88
+ );
89
+ expect(otherCategory).to.exist;
90
+ expect(resultNode.router!.default_category_uuid).to.equal(
91
+ otherCategory!.uuid
92
+ );
93
+ });
94
+
95
+ it('should validate form data correctly', () => {
96
+ // Valid form data
97
+ const validData = {
98
+ groups: [{ uuid: 'group-uuid-1', name: 'Group 1' }]
99
+ };
100
+
101
+ const validResult = split_by_groups.validate!(validData);
102
+ expect(validResult.valid).to.be.true;
103
+ expect(Object.keys(validResult.errors)).to.have.lengthOf(0);
104
+
105
+ // Invalid form data - no groups
106
+ const invalidData = {
107
+ groups: []
108
+ };
109
+
110
+ const invalidResult = split_by_groups.validate!(invalidData);
111
+ expect(invalidResult.valid).to.be.false;
112
+ expect(invalidResult.errors.groups).to.equal(
113
+ 'At least one group is required'
114
+ );
115
+
116
+ // Invalid form data - missing groups
117
+ const missingGroupsData = {};
118
+
119
+ const missingResult = split_by_groups.validate!(missingGroupsData);
120
+ expect(missingResult.valid).to.be.false;
121
+ expect(missingResult.errors.groups).to.equal(
122
+ 'At least one group is required'
123
+ );
124
+ });
125
+
126
+ it('should handle arbitrary groups correctly', () => {
127
+ const originalNode: Node = {
128
+ uuid: 'test-node-uuid',
129
+ actions: [],
130
+ exits: []
131
+ };
132
+
133
+ const formData = {
134
+ uuid: 'test-node-uuid',
135
+ groups: [
136
+ { uuid: 'group-uuid-1', name: 'Existing Group' },
137
+ { name: 'New Group', arbitrary: true }
138
+ ]
139
+ };
140
+
141
+ const resultNode = split_by_groups.fromFormData!(formData, originalNode);
142
+
143
+ expect(resultNode.router!.cases).to.have.lengthOf(2);
144
+
145
+ // Check existing group
146
+ const existingGroupCase = resultNode.router!.cases!.find(
147
+ (c) => c.arguments![0] === 'group-uuid-1'
148
+ );
149
+ expect(existingGroupCase).to.exist;
150
+ expect(existingGroupCase!.arguments).to.deep.equal([
151
+ 'group-uuid-1',
152
+ 'Existing Group'
153
+ ]);
154
+
155
+ // Check arbitrary group (should have generated UUID)
156
+ const arbitraryGroupCase = resultNode.router!.cases!.find(
157
+ (c) => c.arguments![1] === 'New Group'
158
+ );
159
+ expect(arbitraryGroupCase).to.exist;
160
+ expect(arbitraryGroupCase!.arguments![0]).to.match(
161
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
162
+ );
163
+ expect(arbitraryGroupCase!.arguments![1]).to.equal('New Group');
164
+ });
165
+ });
@@ -755,5 +755,187 @@ describe('wait_for_response node config', () => {
755
755
  );
756
756
  expect(secondCases).to.have.length(1);
757
757
  });
758
+
759
+ it('preserves category UUIDs when rules are reordered', () => {
760
+ const originalNode = {
761
+ uuid: 'test-node',
762
+ actions: [],
763
+ router: {
764
+ type: 'switch' as const,
765
+ operand: '@input.text',
766
+ categories: [
767
+ {
768
+ uuid: 'yes-category-uuid',
769
+ name: 'Yes',
770
+ exit_uuid: 'yes-exit-uuid'
771
+ },
772
+ {
773
+ uuid: 'no-category-uuid',
774
+ name: 'No',
775
+ exit_uuid: 'no-exit-uuid'
776
+ },
777
+ {
778
+ uuid: 'other-category-uuid',
779
+ name: 'Other',
780
+ exit_uuid: 'other-exit-uuid'
781
+ }
782
+ ],
783
+ cases: [
784
+ {
785
+ uuid: 'yes-case-uuid',
786
+ type: 'has_phrase',
787
+ arguments: ['yes'],
788
+ category_uuid: 'yes-category-uuid'
789
+ },
790
+ {
791
+ uuid: 'no-case-uuid',
792
+ type: 'has_phrase',
793
+ arguments: ['no'],
794
+ category_uuid: 'no-category-uuid'
795
+ }
796
+ ]
797
+ },
798
+ exits: [
799
+ { uuid: 'yes-exit-uuid', destination_uuid: null },
800
+ { uuid: 'no-exit-uuid', destination_uuid: null },
801
+ { uuid: 'other-exit-uuid', destination_uuid: null }
802
+ ]
803
+ };
804
+
805
+ // Reorder the rules - put "No" first, "Yes" second
806
+ const formData = {
807
+ uuid: 'test-node',
808
+ result_name: 'response',
809
+ rules: [
810
+ {
811
+ operator: { value: 'has_phrase', name: 'contains phrase' },
812
+ value1: 'no',
813
+ category: 'No' // This rule was originally second
814
+ },
815
+ {
816
+ operator: { value: 'has_phrase', name: 'contains phrase' },
817
+ value1: 'yes',
818
+ category: 'Yes' // This rule was originally first
819
+ }
820
+ ]
821
+ };
822
+
823
+ const result = wait_for_response.fromFormData!(formData, originalNode);
824
+
825
+ // Verify that categories keep their original UUIDs despite reordering
826
+ expect(result.router?.categories).to.have.length(3); // Two rules + Other
827
+
828
+ const yesCategory = result.router!.categories.find(
829
+ (cat) => cat.name === 'Yes'
830
+ );
831
+ const noCategory = result.router!.categories.find(
832
+ (cat) => cat.name === 'No'
833
+ );
834
+ const otherCategory = result.router!.categories.find(
835
+ (cat) => cat.name === 'Other'
836
+ );
837
+
838
+ // Categories should preserve their original UUIDs
839
+ expect(yesCategory?.uuid).to.equal('yes-category-uuid');
840
+ expect(yesCategory?.exit_uuid).to.equal('yes-exit-uuid');
841
+
842
+ expect(noCategory?.uuid).to.equal('no-category-uuid');
843
+ expect(noCategory?.exit_uuid).to.equal('no-exit-uuid');
844
+
845
+ expect(otherCategory?.uuid).to.equal('other-category-uuid');
846
+ expect(otherCategory?.exit_uuid).to.equal('other-exit-uuid');
847
+
848
+ // Verify the cases are created in the new order but reference correct categories
849
+ expect(result.router?.cases).to.have.length(2);
850
+
851
+ const firstCase = result.router!.cases[0];
852
+ const secondCase = result.router!.cases[1];
853
+
854
+ // First case should be "no" and reference the No category
855
+ expect(firstCase.arguments).to.deep.equal(['no']);
856
+ expect(firstCase.category_uuid).to.equal('no-category-uuid');
857
+
858
+ // Second case should be "yes" and reference the Yes category
859
+ expect(secondCase.arguments).to.deep.equal(['yes']);
860
+ expect(secondCase.category_uuid).to.equal('yes-category-uuid');
861
+ });
862
+
863
+ it('preserves rule order when rules have duplicate categories', () => {
864
+ const formData = {
865
+ uuid: 'test-node',
866
+ result_name: 'response',
867
+ rules: [
868
+ {
869
+ operator: { value: 'has_phrase', name: 'contains phrase' },
870
+ value1: 'first',
871
+ category: 'Shared'
872
+ },
873
+ {
874
+ operator: { value: 'has_phrase', name: 'contains phrase' },
875
+ value1: 'middle',
876
+ category: 'Different'
877
+ },
878
+ {
879
+ operator: { value: 'has_phrase', name: 'contains phrase' },
880
+ value1: 'last',
881
+ category: 'Shared' // Same as first rule
882
+ }
883
+ ]
884
+ };
885
+
886
+ const originalNode: Node = {
887
+ uuid: 'test-node',
888
+ actions: [],
889
+ router: {
890
+ type: 'switch',
891
+ result_name: 'response',
892
+ categories: [],
893
+ cases: []
894
+ },
895
+ exits: []
896
+ };
897
+
898
+ const result = wait_for_response.fromFormData!(formData, originalNode);
899
+
900
+ // Should have 3 cases in the same order as the input rules
901
+ expect(result.router?.cases).to.have.length(3);
902
+
903
+ const cases = result.router!.cases;
904
+
905
+ // First case should be "first"
906
+ expect(cases[0].arguments).to.deep.equal(['first']);
907
+ expect(cases[0].type).to.equal('has_phrase');
908
+
909
+ // Second case should be "middle"
910
+ expect(cases[1].arguments).to.deep.equal(['middle']);
911
+ expect(cases[1].type).to.equal('has_phrase');
912
+
913
+ // Third case should be "last"
914
+ expect(cases[2].arguments).to.deep.equal(['last']);
915
+ expect(cases[2].type).to.equal('has_phrase');
916
+
917
+ // Find the shared category
918
+ const sharedCategory = result.router!.categories.find(
919
+ (cat) => cat.name === 'Shared'
920
+ );
921
+ const differentCategory = result.router!.categories.find(
922
+ (cat) => cat.name === 'Different'
923
+ );
924
+
925
+ expect(sharedCategory).to.exist;
926
+ expect(differentCategory).to.exist;
927
+
928
+ // First and third cases should reference the same "Shared" category
929
+ expect(cases[0].category_uuid).to.equal(sharedCategory!.uuid);
930
+ expect(cases[2].category_uuid).to.equal(sharedCategory!.uuid);
931
+
932
+ // Second case should reference the "Different" category
933
+ expect(cases[1].category_uuid).to.equal(differentCategory!.uuid);
934
+
935
+ // Should have 3 categories: Shared, Different, Other
936
+ expect(result.router?.categories).to.have.length(3);
937
+ const categoryNames = result.router!.categories.map((cat) => cat.name);
938
+ expect(categoryNames).to.deep.equal(['Shared', 'Different', 'Other']);
939
+ });
758
940
  });
759
941
  });
@@ -150,5 +150,74 @@ describe('Field Configuration System', () => {
150
150
  // Expects 3 items: 2 initial items + 1 auto-generated empty item
151
151
  expect(items?.length).to.equal(3);
152
152
  });
153
+
154
+ it('should render with sortable list when sortable=true', async () => {
155
+ const itemConfig = {
156
+ operator: { type: 'text', label: 'Operator' },
157
+ value: { type: 'text', label: 'Value' }
158
+ };
159
+
160
+ const initialValue = [
161
+ { operator: 'equals', value: 'test' },
162
+ { operator: 'contains', value: 'example' }
163
+ ];
164
+
165
+ const el = await fixture(html`
166
+ <temba-array-editor
167
+ .value=${initialValue}
168
+ .itemConfig=${itemConfig}
169
+ .sortable=${true}
170
+ itemLabel="Rule"
171
+ ></temba-array-editor>
172
+ `);
173
+
174
+ await (el as any).updateComplete;
175
+
176
+ expect(el).to.exist;
177
+
178
+ // Should have a sortable list component
179
+ const sortableList = el.shadowRoot?.querySelector('temba-sortable-list');
180
+ expect(sortableList).to.exist;
181
+
182
+ // Should have sortable items with proper classes and IDs
183
+ const sortableItems = el.shadowRoot?.querySelectorAll('.sortable');
184
+ expect(sortableItems?.length).to.equal(2); // Only non-empty items should be sortable
185
+
186
+ // Each sortable item should have a unique ID
187
+ const firstItem = sortableItems?.[0] as HTMLElement;
188
+ const secondItem = sortableItems?.[1] as HTMLElement;
189
+ expect(firstItem?.id).to.equal('array-item-0');
190
+ expect(secondItem?.id).to.equal('array-item-1');
191
+ });
192
+
193
+ it('should not render sortable list when sortable=false', async () => {
194
+ const itemConfig = {
195
+ operator: { type: 'text', label: 'Operator' },
196
+ value: { type: 'text', label: 'Value' }
197
+ };
198
+
199
+ const initialValue = [{ operator: 'equals', value: 'test' }];
200
+
201
+ const el = await fixture(html`
202
+ <temba-array-editor
203
+ .value=${initialValue}
204
+ .itemConfig=${itemConfig}
205
+ .sortable=${false}
206
+ itemLabel="Rule"
207
+ ></temba-array-editor>
208
+ `);
209
+
210
+ await (el as any).updateComplete;
211
+
212
+ expect(el).to.exist;
213
+
214
+ // Should not have a sortable list component
215
+ const sortableList = el.shadowRoot?.querySelector('temba-sortable-list');
216
+ expect(sortableList).to.not.exist;
217
+
218
+ // Should have regular list container instead
219
+ const listContainer = el.shadowRoot?.querySelector('.list-items');
220
+ expect(listContainer).to.exist;
221
+ });
153
222
  });
154
223
  });
@@ -6,7 +6,9 @@
6
6
  "start_date": "2019-09-22",
7
7
  "events": [
8
8
  {
9
+ "uuid": "01997d74-bf67-749a-8440-688a41c3b275",
9
10
  "type": "ticket_reopened",
11
+ "created_on": "2025-09-24T20:40:27.239439+00:00",
10
12
  "note": null,
11
13
  "topic": null,
12
14
  "assignee": null,
@@ -18,11 +20,12 @@
18
20
  "status": "O",
19
21
  "subject": "Open ticket",
20
22
  "body": ""
21
- },
22
- "created_on": "2021-03-31T00:30:00.585471+00:00"
23
+ }
23
24
  },
24
25
  {
26
+ "uuid": "01997d74-bf67-76d5-a447-db5e048c62b5",
25
27
  "type": "ticket_closed",
28
+ "created_on": "2025-09-24T20:40:27.239438+00:00",
26
29
  "note": null,
27
30
  "topic": null,
28
31
  "assignee": null,
@@ -35,16 +38,15 @@
35
38
  "subject": "Open ticket",
36
39
  "body": ""
37
40
  },
38
- "created_on": "2021-03-31T00:30:00.585471+00:00",
39
41
  "_user": {
40
42
  "uuid": "21ec6c16-b822-4134-9b2d-268f8a340634",
41
43
  "name": "Enrique Newcomers"
42
44
  }
43
45
  },
44
46
  {
45
- "uuid": "01988a70-075b-74e4-b738-3be521174d83",
47
+ "uuid": "01997d74-bf67-7165-b9e4-e5529d9f6712",
46
48
  "type": "msg_created",
47
- "created_on": "2021-03-31T00:30:00.585471+00:00",
49
+ "created_on": "2025-09-24T20:40:27.239437+00:00",
48
50
  "msg": {
49
51
  "urn": "tel:+250788123123",
50
52
  "text": "Hey John, just checking in on you",
@@ -59,8 +61,9 @@
59
61
  "logs_url": null
60
62
  },
61
63
  {
64
+ "uuid": "01997d74-bf67-7305-b5f7-017c6eb04cf7",
62
65
  "type": "run_ended",
63
- "created_on": "2021-03-30T22:20:26.704467+00:00",
66
+ "created_on": "2025-09-23T20:40:27.239436+00:00",
64
67
  "run_uuid": "0198c846-da2b-799f-8af9-aaf8857f947f",
65
68
  "flow": {
66
69
  "uuid": "d076d716-8071-417e-b188-d9746db223a1",
@@ -69,18 +72,18 @@
69
72
  "status": "completed"
70
73
  },
71
74
  {
72
- "uuid": "01988a70-3e8f-7890-b1d2-7418db7a575e",
75
+ "uuid": "01997d74-bf67-7332-9623-f387dc0aeb09",
73
76
  "type": "airtime_transferred",
74
- "created_on": "2021-03-30T22:20:35.573511+00:00",
77
+ "created_on": "2025-09-23T20:40:27.239435+00:00",
75
78
  "sender": "tel:123456",
76
79
  "recipient": "tel:+250788123123?channel=8a81e9e0-10a0-4319-9b00-ce723cfa8303&id=9951&priority=1000",
77
80
  "currency": null,
78
81
  "amount": "0.00"
79
82
  },
80
83
  {
81
- "uuid": "01988a70-6cd1-738d-b5d6-ef9f9a0c529f",
84
+ "uuid": "01997d74-bf67-77d4-9c9b-458abb7bf993",
82
85
  "type": "msg_created",
83
- "created_on": "2021-03-30T22:20:33.924847+00:00",
86
+ "created_on": "2025-09-23T20:40:27.239434+00:00",
84
87
  "msg": {
85
88
  "urn": "tel:+250788123123",
86
89
  "text": "No problem, we are all done then!",
@@ -93,9 +96,9 @@
93
96
  "logs_url": "/channels/channellog/read/1478/"
94
97
  },
95
98
  {
96
- "uuid": "01988a71-045a-7902-8734-84311a7569de",
99
+ "uuid": "01997d74-bf67-74a1-97db-faaf45bbff53",
97
100
  "type": "msg_received",
98
- "created_on": "2021-03-30T22:20:33.801814+00:00",
101
+ "created_on": "2025-09-23T20:40:27.239433+00:00",
99
102
  "msg": {
100
103
  "urn": "tel:+250788123123",
101
104
  "text": "no",
@@ -108,9 +111,9 @@
108
111
  "logs_url": "/channels/channellog/read/1477/"
109
112
  },
110
113
  {
111
- "uuid": "01988a71-6736-7948-9517-32971fcba1cb",
114
+ "uuid": "01997d74-bf67-70c2-bfae-14c661955736",
112
115
  "type": "msg_created",
113
- "created_on": "2021-03-30T22:20:31.933653+00:00",
116
+ "created_on": "2025-09-23T20:40:27.239432+00:00",
114
117
  "msg": {
115
118
  "urn": "tel:+250788123123",
116
119
  "text": "Mmmmm... delicious Primus. If only they made blue Primus! Do you want to set your name?",
@@ -123,9 +126,9 @@
123
126
  "logs_url": "/channels/channellog/read/1476/"
124
127
  },
125
128
  {
126
- "uuid": "01988a75-60cc-7681-bf4b-2b0b42347fc4",
129
+ "uuid": "01997d74-bf67-7199-8e8a-200e41a90d71",
127
130
  "type": "msg_received",
128
- "created_on": "2021-03-30T22:20:31.656303+00:00",
131
+ "created_on": "2025-09-23T20:40:27.239431+00:00",
129
132
  "msg": {
130
133
  "urn": "tel:+250788123123",
131
134
  "text": "primus",
@@ -138,9 +141,9 @@
138
141
  "logs_url": "/channels/channellog/read/1475/"
139
142
  },
140
143
  {
141
- "uuid": "01988a76-58fb-7252-9fac-3c6a385e8af1",
144
+ "uuid": "01997d74-bf67-7768-9b3e-1a2dd9b90aa4",
142
145
  "type": "msg_created",
143
- "created_on": "2021-03-30T22:20:29.397529+00:00",
146
+ "created_on": "2025-09-23T20:40:27.239430+00:00",
144
147
  "msg": {
145
148
  "urn": "tel:+250788123123",
146
149
  "text": "Good choice, I like Blue too! What is your favorite beer?",
@@ -153,9 +156,9 @@
153
156
  "logs_url": "/channels/channellog/read/1474/"
154
157
  },
155
158
  {
156
- "uuid": "01988a76-afe4-7958-bea8-51683d99f665",
159
+ "uuid": "01997d74-bf67-74c6-86d6-a9398ee1f5f4",
157
160
  "type": "msg_received",
158
- "created_on": "2021-03-30T22:20:29.277186+00:00",
161
+ "created_on": "2025-09-23T20:40:27.239429+00:00",
159
162
  "msg": {
160
163
  "urn": "tel:+250788123123",
161
164
  "text": "blue",
@@ -168,33 +171,31 @@
168
171
  "logs_url": "/channels/channellog/read/1473/"
169
172
  },
170
173
  {
174
+ "uuid": "01997d74-bf67-75c9-bd59-36a465fc0c88",
171
175
  "type": "contact_groups_changed",
172
- "created_on": "2021-03-30T15:20:26.704497-07:00",
173
- "step_uuid": "83cbe193-29d8-40ce-a820-0b06d719932a",
176
+ "created_on": "2025-09-23T20:40:27.239428+00:00",
174
177
  "groups_removed": [
175
178
  {
176
179
  "uuid": "b13e01ec-6c83-4349-b153-a2e48d0fd394",
177
180
  "name": "aaaa"
178
181
  }
179
- ],
180
- "session_uuid": "885a9736-88fd-4676-b124-2577e1eaabd9"
182
+ ]
181
183
  },
182
184
  {
185
+ "uuid": "01997d74-bf67-703c-a223-85da4b048994",
183
186
  "type": "contact_groups_changed",
184
- "created_on": "2021-03-30T15:20:26.704495-07:00",
185
- "step_uuid": "83cbe193-29d8-40ce-a820-0b06d719932a",
187
+ "created_on": "2025-09-23T20:40:27.239427+00:00",
186
188
  "groups_added": [
187
189
  {
188
190
  "uuid": "b13e01ec-6c83-4349-b153-a2e48d0fd394",
189
191
  "name": "aaaa"
190
192
  }
191
- ],
192
- "session_uuid": "885a9736-88fd-4676-b124-2577e1eaabd9"
193
+ ]
193
194
  },
194
195
  {
195
- "uuid": "01988a76-febd-7157-b2cd-4bc0c1663173",
196
+ "uuid": "01997d74-bf67-74f0-951f-726f573ac18d",
196
197
  "type": "msg_created",
197
- "created_on": "2021-03-30T22:20:26.704491+00:00",
198
+ "created_on": "2025-09-23T20:40:27.239426+00:00",
198
199
  "msg": {
199
200
  "urn": "tel:+250788123123",
200
201
  "text": "What is your favorite color?",
@@ -207,13 +208,14 @@
207
208
  "logs_url": "/channels/channellog/read/1472/"
208
209
  },
209
210
  {
210
- "uuid": "0198e7b8-fe00-76e6-91e4-1253dd9a6230",
211
+ "uuid": "01997d74-bf67-70a9-a0fc-6543b4d0b3d1",
211
212
  "type": "call_missed",
212
- "created_on": "2021-03-30T22:20:26.704467+00:00"
213
+ "created_on": "2025-09-23T20:40:27.239425+00:00"
213
214
  },
214
215
  {
216
+ "uuid": "01997d74-bf67-7632-908a-555c328137cd",
215
217
  "type": "run_started",
216
- "created_on": "2021-03-30T22:20:26.704467+00:00",
218
+ "created_on": "2025-09-23T20:40:27.239424+00:00",
217
219
  "run_uuid": "0198c846-2cf4-7992-9bd0-9b6581e42358",
218
220
  "flow": {
219
221
  "uuid": "d076d716-8071-417e-b188-d9746db223a1",
@@ -221,9 +223,9 @@
221
223
  }
222
224
  },
223
225
  {
224
- "uuid": "01988a77-979e-7768-a940-8d9c348e24fe",
226
+ "uuid": "01997d74-bf67-7684-bc3b-99ebdaf16e7e",
225
227
  "type": "msg_received",
226
- "created_on": "2021-03-30T22:20:26.557388+00:00",
228
+ "created_on": "2025-09-23T20:40:27.239423+00:00",
227
229
  "msg": {
228
230
  "urn": "tel:+250788123123",
229
231
  "text": "fav",