@nyaruka/temba-components 0.133.0 → 0.134.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/demo/components/webchat/example.html +1 -1
  3. package/dist/locales/es.js +5 -5
  4. package/dist/locales/es.js.map +1 -1
  5. package/dist/locales/fr.js +5 -5
  6. package/dist/locales/fr.js.map +1 -1
  7. package/dist/locales/locale-codes.js +2 -11
  8. package/dist/locales/locale-codes.js.map +1 -1
  9. package/dist/locales/pt.js +5 -5
  10. package/dist/locales/pt.js.map +1 -1
  11. package/dist/temba-components.js +307 -259
  12. package/dist/temba-components.js.map +1 -1
  13. package/out-tsc/src/display/Chat.js +223 -90
  14. package/out-tsc/src/display/Chat.js.map +1 -1
  15. package/out-tsc/src/display/TembaUser.js +3 -3
  16. package/out-tsc/src/display/TembaUser.js.map +1 -1
  17. package/out-tsc/src/events.js.map +1 -1
  18. package/out-tsc/src/flow/CanvasNode.js +8 -0
  19. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  20. package/out-tsc/src/flow/Editor.js +117 -28
  21. package/out-tsc/src/flow/Editor.js.map +1 -1
  22. package/out-tsc/src/flow/utils.js +141 -0
  23. package/out-tsc/src/flow/utils.js.map +1 -1
  24. package/out-tsc/src/interfaces.js.map +1 -1
  25. package/out-tsc/src/live/ContactChat.js +122 -170
  26. package/out-tsc/src/live/ContactChat.js.map +1 -1
  27. package/out-tsc/src/locales/es.js +5 -5
  28. package/out-tsc/src/locales/es.js.map +1 -1
  29. package/out-tsc/src/locales/fr.js +5 -5
  30. package/out-tsc/src/locales/fr.js.map +1 -1
  31. package/out-tsc/src/locales/locale-codes.js +2 -11
  32. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  33. package/out-tsc/src/locales/pt.js +5 -5
  34. package/out-tsc/src/locales/pt.js.map +1 -1
  35. package/out-tsc/src/store/AppState.js +3 -0
  36. package/out-tsc/src/store/AppState.js.map +1 -1
  37. package/out-tsc/src/store/Store.js +5 -5
  38. package/out-tsc/src/store/Store.js.map +1 -1
  39. package/out-tsc/src/webchat/WebChat.js +22 -9
  40. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  41. package/out-tsc/test/actions/send_broadcast.test.js +9 -4
  42. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  43. package/out-tsc/test/temba-flow-collision.test.js +673 -0
  44. package/out-tsc/test/temba-flow-collision.test.js.map +1 -0
  45. package/out-tsc/test/temba-flow-editor-node.test.js +128 -42
  46. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  47. package/package.json +1 -1
  48. package/screenshots/truth/contacts/chat-failure.png +0 -0
  49. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  50. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  51. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  52. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  53. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  54. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  55. package/src/display/Chat.ts +303 -129
  56. package/src/display/TembaUser.ts +3 -2
  57. package/src/events.ts +11 -8
  58. package/src/flow/CanvasNode.ts +10 -0
  59. package/src/flow/Editor.ts +156 -28
  60. package/src/flow/utils.ts +207 -1
  61. package/src/interfaces.ts +7 -0
  62. package/src/live/ContactChat.ts +129 -180
  63. package/src/locales/es.ts +13 -18
  64. package/src/locales/fr.ts +13 -18
  65. package/src/locales/locale-codes.ts +2 -11
  66. package/src/locales/pt.ts +13 -18
  67. package/src/store/AppState.ts +2 -0
  68. package/src/store/Store.ts +5 -5
  69. package/src/webchat/WebChat.ts +24 -10
  70. package/test/actions/send_broadcast.test.ts +2 -1
  71. package/test/temba-flow-collision.test.ts +833 -0
  72. package/test/temba-flow-editor-node.test.ts +142 -47
@@ -123,17 +123,38 @@ const renderContactURNsChanged = (event) => {
123
123
  export const renderTicketAction = (event, action) => {
124
124
  var _a;
125
125
  const ticketUUID = ((_a = event.ticket) === null || _a === void 0 ? void 0 : _a.uuid) || event.ticket_uuid;
126
- if (event._user) {
127
- return html `<div>
128
- <strong>${event._user.name}</strong> ${action} a
129
- <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
130
- </div>`;
126
+ const actionNote = event.note
127
+ ? html `<div
128
+ style="width:85%; background: #fffac3; padding: 1em;margin-bottom: 1em 0; border: 1px solid #ffe97f;border-radius: var(--curvature);"
129
+ >
130
+ <div style="color: #8e830fff; font-size: 1em;margin-bottom:0.25em">
131
+ <strong>${event._user ? event._user.name : 'Someone'}</strong> added a
132
+ note
133
+ <temba-date
134
+ value=${event.created_on.toISOString()}
135
+ display="relative"
136
+ ></temba-date>
137
+ </div>
138
+ ${event.note}
139
+ </div>`
140
+ : null;
141
+ if (action === 'noted') {
142
+ return html `${actionNote}`;
131
143
  }
132
- return html `<div>
133
- A
134
- <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong> was
135
- <strong>${action}</strong>
136
- </div>`;
144
+ const description = event._user
145
+ ? html `<div>
146
+ <strong>${event._user.name}</strong> ${action} a
147
+ <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
148
+ </div>`
149
+ : html `<div>
150
+ A
151
+ <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
152
+ was <strong>${action}</strong>
153
+ </div>`;
154
+ return html `<div style="${actionNote ? 'margin-bottom: 1em;' : ''}">
155
+ ${description}
156
+ </div>
157
+ ${actionNote}`;
137
158
  };
138
159
  export const renderTicketAssigneeChanged = (event) => {
139
160
  if (event._user) {
@@ -448,6 +469,7 @@ export class ContactChat extends ContactStoreElement {
448
469
  }
449
470
  }
450
471
  updated(changedProperties) {
472
+ var _a;
451
473
  super.updated(changedProperties);
452
474
  // if we don't have an endpoint infer one
453
475
  if (changedProperties.has('data') ||
@@ -459,9 +481,15 @@ export class ContactChat extends ContactStoreElement {
459
481
  }
460
482
  this.currentContact = this.data;
461
483
  }
462
- if (changedProperties.has('currentContact')) {
484
+ if (changedProperties.has('currentContact') && this.currentContact) {
463
485
  this.chat = this.shadowRoot.querySelector('temba-chat');
464
- this.reset();
486
+ if (this.currentContact.uuid !==
487
+ ((_a = changedProperties.get('currentContact')) === null || _a === void 0 ? void 0 : _a.uuid)) {
488
+ this.reset();
489
+ }
490
+ else {
491
+ setTimeout(() => this.checkForNewMessages(), 500);
492
+ }
465
493
  this.fetchPreviousMessages();
466
494
  }
467
495
  }
@@ -515,8 +543,9 @@ export class ContactChat extends ContactStoreElement {
515
543
  postJSON(`/contact/chat/${this.currentContact.uuid}/`, payload)
516
544
  .then((response) => {
517
545
  if (response.status < 400) {
518
- const msg = this.createChatForMessageEvent(response.json.event);
519
- this.chat.addMessages([msg], null, true);
546
+ const event = response.json.event;
547
+ event.created_on = new Date(event.created_on);
548
+ this.chat.addMessages([event], null, true);
520
549
  // reset polling interval to 2 seconds after sending a message
521
550
  this.pollingInterval = 2000;
522
551
  this.checkForNewMessages();
@@ -557,221 +586,153 @@ export class ContactChat extends ContactStoreElement {
557
586
  this.checkForNewMessages();
558
587
  }, this.pollingInterval);
559
588
  }
560
- getEventMessage(event) {
561
- let message = null;
589
+ prerender(event) {
562
590
  switch (event.type) {
563
591
  case Events.AIRTIME_TRANSFERRED:
564
- message = {
565
- type: MessageType.Inline,
566
- text: renderAirtimeTransferredEvent(event)
592
+ event._rendered = {
593
+ html: renderAirtimeTransferredEvent(event),
594
+ type: MessageType.Inline
567
595
  };
568
596
  break;
569
597
  case Events.CALL_CREATED:
570
598
  case Events.CALL_MISSED:
571
599
  case Events.CALL_RECEIVED:
572
- message = {
573
- type: MessageType.Inline,
574
- text: renderCallEvent(event)
600
+ event._rendered = {
601
+ html: renderCallEvent(event),
602
+ type: MessageType.Inline
575
603
  };
576
604
  break;
577
605
  case Events.CHAT_STARTED:
578
- message = {
579
- type: MessageType.Inline,
580
- text: renderChatStartedEvent(event)
606
+ event._rendered = {
607
+ html: renderChatStartedEvent(event),
608
+ type: MessageType.Inline
581
609
  };
582
610
  break;
583
611
  case Events.CONTACT_FIELD_CHANGED:
584
- message = {
585
- type: MessageType.Inline,
586
- text: renderUpdateEvent(event)
612
+ event._rendered = {
613
+ html: renderUpdateEvent(event),
614
+ type: MessageType.Inline
587
615
  };
588
616
  break;
589
617
  case Events.CONTACT_GROUPS_CHANGED:
590
- message = {
591
- type: MessageType.Inline,
592
- text: renderContactGroupsEvent(event)
618
+ event._rendered = {
619
+ html: renderContactGroupsEvent(event),
620
+ type: MessageType.Inline
593
621
  };
594
622
  break;
595
623
  case Events.CONTACT_LANGUAGE_CHANGED:
596
- message = {
597
- type: MessageType.Inline,
598
- text: renderContactLanguageChangedEvent(event)
624
+ event._rendered = {
625
+ html: renderContactLanguageChangedEvent(event),
626
+ type: MessageType.Inline
599
627
  };
600
628
  break;
601
629
  case Events.CONTACT_NAME_CHANGED:
602
- message = {
603
- type: MessageType.Inline,
604
- text: renderNameChanged(event)
630
+ event._rendered = {
631
+ html: renderNameChanged(event),
632
+ type: MessageType.Inline
605
633
  };
606
634
  break;
607
635
  case Events.CONTACT_STATUS_CHANGED:
608
- message = {
609
- type: MessageType.Inline,
610
- text: renderContactStatusChangedEvent(event)
636
+ event._rendered = {
637
+ html: renderContactStatusChangedEvent(event),
638
+ type: MessageType.Inline
611
639
  };
612
640
  break;
613
641
  case Events.CONTACT_URNS_CHANGED:
614
- message = {
615
- type: MessageType.Inline,
616
- text: renderContactURNsChanged(event)
642
+ event._rendered = {
643
+ html: renderContactURNsChanged(event),
644
+ type: MessageType.Inline
617
645
  };
618
646
  break;
619
647
  case Events.OPTIN_REQUESTED:
620
648
  case Events.OPTIN_STARTED:
621
649
  case Events.OPTIN_STOPPED:
622
- message = {
623
- type: MessageType.Inline,
624
- text: renderOptInEvent(event)
650
+ event._rendered = {
651
+ html: renderOptInEvent(event),
652
+ type: MessageType.Inline
625
653
  };
626
654
  break;
627
655
  case Events.RUN_STARTED:
628
656
  case Events.RUN_ENDED:
629
- message = {
630
- type: MessageType.Inline,
631
- text: renderRunEvent(event)
657
+ event._rendered = {
658
+ html: renderRunEvent(event),
659
+ type: MessageType.Inline
632
660
  };
633
661
  break;
634
662
  case Events.TICKET_ASSIGNEE_CHANGED:
635
- message = {
636
- type: MessageType.Inline,
637
- text: renderTicketAssigneeChanged(event)
663
+ event._rendered = {
664
+ html: renderTicketAssigneeChanged(event),
665
+ type: MessageType.Inline
638
666
  };
639
667
  break;
640
668
  case Events.TICKET_CLOSED:
641
- message = {
642
- type: MessageType.Inline,
643
- text: renderTicketAction(event, 'closed')
669
+ event._rendered = {
670
+ html: renderTicketAction(event, 'closed'),
671
+ type: MessageType.Inline
644
672
  };
645
673
  break;
646
674
  case Events.TICKET_OPENED:
647
- message = {
648
- type: MessageType.Inline,
649
- text: renderTicketAction(event, 'opened')
675
+ event._rendered = {
676
+ html: renderTicketAction(event, 'opened'),
677
+ type: MessageType.Inline
678
+ };
679
+ break;
680
+ case Events.TICKET_NOTE_ADDED:
681
+ event._rendered = {
682
+ html: renderTicketAction(event, 'noted'),
683
+ type: MessageType.Inline
650
684
  };
651
685
  break;
652
686
  case Events.TICKET_REOPENED:
653
- message = {
654
- type: MessageType.Inline,
655
- text: renderTicketAction(event, 'reopened')
687
+ event._rendered = {
688
+ html: renderTicketAction(event, 'reopened'),
689
+ type: MessageType.Inline
656
690
  };
657
691
  break;
658
692
  case Events.TICKET_TOPIC_CHANGED:
659
- message = {
660
- type: MessageType.Inline,
661
- text: html `<div>
693
+ event._rendered = {
694
+ html: html `<div>
662
695
  Topic changed to
663
696
  <strong>${event.topic.name}</strong>
664
- </div>`
697
+ </div>`,
698
+ type: MessageType.Inline
665
699
  };
666
700
  break;
667
701
  case Events.CHANNEL_EVENT: // deprecated
668
- message = {
669
- type: MessageType.Inline,
670
- text: renderChannelEvent(event)
702
+ event._rendered = {
703
+ html: renderChannelEvent(event),
704
+ type: MessageType.Inline
671
705
  };
672
706
  break;
673
707
  default:
674
708
  console.error('Unknown event type', event);
675
709
  }
676
- if (message) {
677
- message.id = event.uuid;
678
- message.date = new Date(event.created_on);
679
- }
680
- return message;
681
- }
682
- getUserForEvent(event) {
683
- if (event.type === 'msg_received') {
684
- return {
685
- name: this.currentContact.name
686
- };
687
- }
688
- else if (event._user) {
689
- return event._user;
690
- }
691
- return null;
692
- }
693
- createChatForMessageEvent(msgEvent) {
694
- return {
695
- id: msgEvent.uuid,
696
- type: msgEvent.type === 'msg_received' ? 'msg_in' : 'msg_out',
697
- user: this.getUserForEvent(msgEvent),
698
- date: new Date(msgEvent.created_on),
699
- attachments: msgEvent.msg.attachments,
700
- text: msgEvent.msg.text,
701
- sendError: msgEvent._status &&
702
- (msgEvent._status.status === 'errored' ||
703
- msgEvent._status.status === 'failed'),
704
- popup: html `<div
705
- style="display: flex; flex-direction: row; align-items:center; justify-content: space-between;font-size:0.9em;line-height:1em;min-width:10em"
706
- >
707
- <div style="justify-content:left;text-align:left">
708
- <temba-date
709
- value=${msgEvent.created_on}
710
- display="duration"
711
- ></temba-date>
712
-
713
- ${msgEvent.optin
714
- ? html `<div style="font-size:0.9em;color:#aaa">
715
- ${msgEvent.optin.name}
716
- </div>`
717
- : null}
718
- </div>
719
- ${msgEvent._logs_url
720
- ? html `<a style="margin-left:0.5em" href="${msgEvent._logs_url}"
721
- ><temba-icon name="log"></temba-icon
722
- ></a>`
723
- : null}
724
- </div> `
725
- };
726
710
  }
727
711
  createMessages(page) {
728
712
  if (page.events) {
729
- let messages = [];
713
+ const messages = [];
730
714
  page.events.forEach((event) => {
731
715
  // track the UUID of the newest event for polling
732
716
  if (!this.afterUUID ||
733
717
  event.uuid.toLowerCase() > this.afterUUID.toLowerCase()) {
734
718
  this.afterUUID = event.uuid;
735
719
  }
736
- if (event.type === 'ticket_note_added') {
737
- const ticketEvent = event;
738
- messages.push({
739
- type: MessageType.Note,
740
- id: event.created_on + event.type,
741
- user: this.getUserForEvent(ticketEvent),
742
- date: new Date(ticketEvent.created_on),
743
- text: ticketEvent.note
744
- });
745
- }
746
- else if (event.type === 'ticket_opened') {
747
- // ticket open events can have a note attached
748
- const ticketEvent = event;
749
- messages.push({
750
- type: MessageType.Note,
751
- id: event.created_on + event.type + '_note',
752
- user: this.getUserForEvent(ticketEvent),
753
- date: new Date(ticketEvent.created_on),
754
- text: ticketEvent.note
755
- });
756
- // but the opening of the ticket is a normal event
757
- messages.push(this.getEventMessage(event));
758
- }
759
- else if (event.type === 'msg_created' ||
720
+ // convert to dates
721
+ event.created_on = new Date(event.created_on);
722
+ if (event.type === 'msg_created' ||
760
723
  event.type === 'msg_received' ||
761
724
  event.type === 'ivr_created') {
762
- const msgEvent = event;
763
- messages.push(this.createChatForMessageEvent(msgEvent));
725
+ messages.push(event);
764
726
  }
765
727
  else {
766
- const msg = this.getEventMessage(event);
767
- if (msg) {
768
- messages.push(msg);
728
+ this.prerender(event);
729
+ if (event._rendered) {
730
+ messages.push(event);
769
731
  }
770
732
  }
771
733
  });
772
734
  // remove any messages we don't recognize
773
- messages = messages.filter((msg) => !!msg);
774
- return messages;
735
+ return messages.filter((msg) => !!msg);
775
736
  }
776
737
  return [];
777
738
  }
@@ -782,7 +743,6 @@ export class ContactChat extends ContactStoreElement {
782
743
  return;
783
744
  }
784
745
  const chat = this.chat;
785
- const contactChat = this;
786
746
  if (this.currentContact && this.afterUUID) {
787
747
  this.polling = true;
788
748
  this.lastFetchTime = Date.now();
@@ -792,13 +752,10 @@ export class ContactChat extends ContactStoreElement {
792
752
  }
793
753
  const fetchContact = this.currentContact.uuid;
794
754
  fetchContactHistory(endpoint, (_a = this.currentTicket) === null || _a === void 0 ? void 0 : _a.uuid, null, this.afterUUID).then((page) => {
755
+ const messages = this.createMessages(page);
756
+ messages.reverse();
795
757
  if (fetchContact === this.currentContact.uuid) {
796
- const messages = this.createMessages(page);
797
758
  const hasNewEvents = messages.length > 0;
798
- if (messages.length === 0) {
799
- contactChat.blockFetching = true;
800
- }
801
- messages.reverse();
802
759
  chat.addMessages(messages, null, true);
803
760
  this.polling = false;
804
761
  this.scheduleRefresh(hasNewEvents);
@@ -951,15 +908,13 @@ export class ContactChat extends ContactStoreElement {
951
908
  refreshTicket() {
952
909
  if (this.currentTicket) {
953
910
  fetchResults(`/api/v2/tickets.json?uuid=${this.currentTicket.uuid}`).then((values) => {
954
- this.store.resolveUsers(values, ['assignee']).then(() => {
955
- if (values.length > 0) {
956
- this.fireCustomEvent(CustomEventType.TicketUpdated, {
957
- ticket: values[0],
958
- previous: this.currentTicket
959
- });
960
- this.currentTicket = values[0];
961
- }
962
- });
911
+ if (values.length > 0) {
912
+ this.fireCustomEvent(CustomEventType.TicketUpdated, {
913
+ ticket: values[0],
914
+ previous: this.currentTicket
915
+ });
916
+ this.currentTicket = values[0];
917
+ }
963
918
  });
964
919
  }
965
920
  }
@@ -1000,6 +955,7 @@ export class ContactChat extends ContactStoreElement {
1000
955
  @temba-fetch-complete=${this.fetchComplete}
1001
956
  avatar=${this.avatar}
1002
957
  agent
958
+ ?hasFooter=${inFlow}
1003
959
  >
1004
960
  ${inFlow
1005
961
  ? html `
@@ -1147,17 +1103,13 @@ export const fetchContactHistory = (endpoint, ticket = undefined, before = undef
1147
1103
  if (params.length > 0) {
1148
1104
  url += (url.includes('?') ? '&' : '?') + params.join('&');
1149
1105
  }
1150
- const store = document.querySelector('temba-store');
1151
1106
  getUrl(url, controller)
1152
1107
  .then((response) => {
1153
1108
  // on success, remove our abort controller
1154
1109
  pendingRequests = pendingRequests.filter((controller) => {
1155
1110
  return response.controller === controller;
1156
1111
  });
1157
- const page = response.json;
1158
- store.resolveUsers(page.events, ['created_by']).then(() => {
1159
- resolve(page);
1160
- });
1112
+ resolve(response.json);
1161
1113
  })
1162
1114
  .catch(() => {
1163
1115
  // canceled