@nyaruka/temba-components 0.139.0 → 0.141.0
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/.github/workflows/cla.yml +1 -1
- package/.github/workflows/copilot-setup-steps.yml +6 -1
- package/.lintstagedrc.js +10 -0
- package/CHANGELOG.md +32 -0
- package/demo/data/flows/sample-flow.json +24 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +702 -338
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +10 -7
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/Dropdown.js +3 -1
- package/out-tsc/src/display/Dropdown.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +4 -4
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +163 -5
- package/out-tsc/src/display/Thumbnail.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +65 -23
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +369 -49
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +118 -10
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +61 -14
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +13 -4
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +4 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +4 -1
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/audio-player.js +112 -0
- package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
- package/out-tsc/src/flow/actions/enter_flow.js +43 -0
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
- package/out-tsc/src/flow/actions/play_audio.js +57 -4
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +86 -3
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +6 -2
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +13 -0
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +7 -5
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +10 -3
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -3
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +18 -5
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +0 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +0 -1
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_run_result.js +10 -4
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/terminal.js +7 -0
- package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/operators.js +21 -5
- package/out-tsc/src/flow/operators.js.map +1 -1
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +79 -3
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +4 -2
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +56 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/Dialog.js +51 -7
- package/out-tsc/src/layout/Dialog.js.map +1 -1
- package/out-tsc/src/layout/Modax.js +20 -2
- package/out-tsc/src/layout/Modax.js.map +1 -1
- package/out-tsc/src/list/ContentMenu.js +14 -1
- package/out-tsc/src/list/ContentMenu.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +21 -4
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +102 -3
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/actions/add_contact_groups.test.js +35 -0
- package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -1
- package/out-tsc/test/actions/add_input_labels.test.js +53 -0
- package/out-tsc/test/actions/add_input_labels.test.js.map +1 -0
- package/out-tsc/test/actions/enter_flow.test.js +71 -0
- package/out-tsc/test/actions/enter_flow.test.js.map +1 -0
- package/out-tsc/test/actions/play_audio.test.js +118 -0
- package/out-tsc/test/actions/play_audio.test.js.map +1 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js +24 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -1
- package/out-tsc/test/actions/say_msg.test.js +158 -0
- package/out-tsc/test/actions/say_msg.test.js.map +1 -0
- package/out-tsc/test/actions/send_broadcast.test.js +41 -0
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
- package/out-tsc/test/actions/set_contact_channel.test.js +67 -0
- package/out-tsc/test/actions/set_contact_channel.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_field.test.js +52 -0
- package/out-tsc/test/actions/set_contact_field.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_language.test.js +39 -0
- package/out-tsc/test/actions/set_contact_language.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_name.test.js +28 -0
- package/out-tsc/test/actions/set_contact_name.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_status.test.js +44 -0
- package/out-tsc/test/actions/set_contact_status.test.js.map +1 -0
- package/out-tsc/test/actions/set_run_result.test.js +47 -0
- package/out-tsc/test/actions/set_run_result.test.js.map +1 -0
- package/out-tsc/test/actions/start_session.test.js +76 -0
- package/out-tsc/test/actions/start_session.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_contact_field.test.js +50 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_run_result.test.js +82 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_ticket.test.js +139 -0
- package/out-tsc/test/nodes/split_by_ticket.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_webhook.test.js +111 -0
- package/out-tsc/test/nodes/split_by_webhook.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
- package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-collision.test.js +261 -6
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +187 -0
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +19 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +6 -6
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +4 -1
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +4 -2
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +3 -9
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
- package/screenshots/truth/actions/add_input_labels/editor/multiple-labels.png +0 -0
- package/screenshots/truth/actions/add_input_labels/editor/single-label.png +0 -0
- package/screenshots/truth/actions/add_input_labels/render/multiple-labels.png +0 -0
- package/screenshots/truth/actions/add_input_labels/render/single-label.png +0 -0
- package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
- package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
- package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
- package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
- package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/render/sms-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/render/whatsapp-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/render/clear-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/render/set-value.png +0 -0
- package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
- package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
- package/screenshots/truth/actions/set_contact_language/render/english.png +0 -0
- package/screenshots/truth/actions/set_contact_language/render/french.png +0 -0
- package/screenshots/truth/actions/set_contact_name/editor/expression-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/editor/static-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/render/expression-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/render/static-name.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/active.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/archived.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/blocked.png +0 -0
- package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
- package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
- package/screenshots/truth/actions/set_run_result/render/expression-value.png +0 -0
- package/screenshots/truth/actions/set_run_result/render/with-category.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/display/Chat.ts +13 -7
- package/src/display/Dropdown.ts +3 -1
- package/src/display/FloatingTab.ts +4 -4
- package/src/display/Thumbnail.ts +162 -2
- package/src/flow/CanvasNode.ts +70 -24
- package/src/flow/Editor.ts +440 -99
- package/src/flow/NodeEditor.ts +137 -9
- package/src/flow/Plumber.ts +89 -14
- package/src/flow/StickyNote.ts +14 -4
- package/src/flow/actions/add_contact_groups.ts +4 -1
- package/src/flow/actions/add_input_labels.ts +4 -1
- package/src/flow/actions/audio-player.ts +127 -0
- package/src/flow/actions/enter_flow.ts +44 -0
- package/src/flow/actions/play_audio.ts +64 -5
- package/src/flow/actions/remove_contact_groups.ts +6 -1
- package/src/flow/actions/say_msg.ts +94 -4
- package/src/flow/actions/send_broadcast.ts +6 -2
- package/src/flow/actions/set_contact_channel.ts +13 -1
- package/src/flow/actions/set_contact_status.ts +7 -5
- package/src/flow/actions/start_session.ts +10 -3
- package/src/flow/config.ts +11 -3
- package/src/flow/nodes/shared-rules.ts +1 -1
- package/src/flow/nodes/split_by_contact_field.ts +16 -5
- package/src/flow/nodes/split_by_expression.ts +1 -1
- package/src/flow/nodes/split_by_llm_categorize.ts +0 -1
- package/src/flow/nodes/split_by_random.ts +0 -1
- package/src/flow/nodes/split_by_run_result.ts +10 -4
- package/src/flow/nodes/terminal.ts +9 -0
- package/src/flow/nodes/wait_for_audio.ts +88 -0
- package/src/flow/nodes/wait_for_dial.ts +176 -0
- package/src/flow/nodes/wait_for_digits.ts +87 -2
- package/src/flow/nodes/wait_for_menu.ts +209 -3
- package/src/flow/nodes/wait_for_response.ts +1 -1
- package/src/flow/operators.ts +23 -5
- package/src/flow/types.ts +23 -1
- package/src/flow/utils.ts +82 -3
- package/src/form/ArrayEditor.ts +4 -2
- package/src/form/FieldRenderer.ts +71 -1
- package/src/interfaces.ts +2 -1
- package/src/layout/Dialog.ts +52 -7
- package/src/layout/Modax.ts +19 -2
- package/src/list/ContentMenu.ts +15 -1
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/simulator/Simulator.ts +25 -4
- package/src/store/AppState.ts +120 -1
- package/src/store/flow-definition.d.ts +2 -0
- package/test/actions/add_contact_groups.test.ts +38 -0
- package/test/actions/add_input_labels.test.ts +67 -0
- package/test/actions/enter_flow.test.ts +88 -0
- package/test/actions/play_audio.test.ts +155 -0
- package/test/actions/remove_contact_groups.test.ts +29 -0
- package/test/actions/say_msg.test.ts +196 -0
- package/test/actions/send_broadcast.test.ts +44 -0
- package/test/actions/set_contact_channel.test.ts +88 -0
- package/test/actions/set_contact_field.test.ts +68 -0
- package/test/actions/set_contact_language.test.ts +55 -0
- package/test/actions/set_contact_name.test.ts +39 -0
- package/test/actions/set_contact_status.test.ts +64 -0
- package/test/actions/set_run_result.test.ts +61 -0
- package/test/actions/start_session.test.ts +82 -0
- package/test/nodes/split_by_contact_field.test.ts +59 -0
- package/test/nodes/split_by_run_result.test.ts +100 -0
- package/test/nodes/split_by_ticket.test.ts +157 -0
- package/test/nodes/split_by_webhook.test.ts +131 -0
- package/test/nodes/wait_for_audio.test.ts +182 -0
- package/test/nodes/wait_for_dial.test.ts +382 -0
- package/test/nodes/wait_for_digits.test.ts +233 -109
- package/test/nodes/wait_for_menu.test.ts +383 -0
- package/test/temba-flow-collision.test.ts +286 -6
- package/test/temba-flow-editor.test.ts +240 -0
- package/test/temba-flow-plumber.test.ts +62 -0
- package/test/temba-node-type-selector.test.ts +6 -6
- package/test/temba-select.test.ts +6 -1
- package/test/utils.test.ts +4 -2
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
|
@@ -2,6 +2,7 @@ import { html, fixture, expect } from '@open-wc/testing';
|
|
|
2
2
|
import { Editor } from '../src/flow/Editor';
|
|
3
3
|
import { Plumber } from '../src/flow/Plumber';
|
|
4
4
|
import { stub, restore } from 'sinon';
|
|
5
|
+
import { zustand } from '../src/store/AppState';
|
|
5
6
|
|
|
6
7
|
// Register the component
|
|
7
8
|
customElements.define('temba-flow-editor', Editor);
|
|
@@ -122,6 +123,46 @@ describe('Editor', () => {
|
|
|
122
123
|
});
|
|
123
124
|
});
|
|
124
125
|
|
|
126
|
+
describe('disconnectedCallback', () => {
|
|
127
|
+
it('clears flow data from the store when editor is removed', async () => {
|
|
128
|
+
// Set up some flow-specific state
|
|
129
|
+
zustand.setState({
|
|
130
|
+
flowDefinition: { nodes: [], _ui: { nodes: {} } } as any,
|
|
131
|
+
activity: { nodes: {}, segments: {} },
|
|
132
|
+
simulatorActivity: { nodes: {}, segments: {} },
|
|
133
|
+
simulatorActive: true,
|
|
134
|
+
flowInfo: {
|
|
135
|
+
results: [],
|
|
136
|
+
dependencies: [],
|
|
137
|
+
counts: { nodes: 0, languages: 0 },
|
|
138
|
+
locals: []
|
|
139
|
+
} as any,
|
|
140
|
+
dirtyDate: new Date()
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
editor = await fixture(html`
|
|
144
|
+
<temba-flow-editor>
|
|
145
|
+
<div id="canvas"></div>
|
|
146
|
+
</temba-flow-editor>
|
|
147
|
+
`);
|
|
148
|
+
|
|
149
|
+
// Verify state is populated
|
|
150
|
+
expect(zustand.getState().flowDefinition).to.not.be.null;
|
|
151
|
+
expect(zustand.getState().activity).to.not.be.null;
|
|
152
|
+
|
|
153
|
+
// Remove the editor from the DOM
|
|
154
|
+
editor.remove();
|
|
155
|
+
|
|
156
|
+
// Verify all flow-specific state has been cleared
|
|
157
|
+
expect(zustand.getState().flowDefinition).to.be.null;
|
|
158
|
+
expect(zustand.getState().activity).to.be.null;
|
|
159
|
+
expect(zustand.getState().simulatorActivity).to.be.null;
|
|
160
|
+
expect(zustand.getState().simulatorActive).to.be.false;
|
|
161
|
+
expect(zustand.getState().flowInfo).to.be.null;
|
|
162
|
+
expect(zustand.getState().dirtyDate).to.be.null;
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
125
166
|
describe('render method', () => {
|
|
126
167
|
it('renders loading when no definition', async () => {
|
|
127
168
|
editor = await fixture(html`
|
|
@@ -822,4 +863,203 @@ describe('Editor', () => {
|
|
|
822
863
|
expect(flowNodes[0].classList.contains('flow-start')).to.be.true;
|
|
823
864
|
});
|
|
824
865
|
});
|
|
866
|
+
|
|
867
|
+
describe('save feedback', () => {
|
|
868
|
+
let mockPostJSON: any;
|
|
869
|
+
let storeElement: HTMLElement;
|
|
870
|
+
|
|
871
|
+
before(() => {
|
|
872
|
+
// Create a mock temba-store element that getStore() will find
|
|
873
|
+
// Use the real zustand getState so all store interactions work
|
|
874
|
+
storeElement = document.createElement('temba-store');
|
|
875
|
+
(storeElement as any).getState = () => zustand.getState();
|
|
876
|
+
document.body.appendChild(storeElement);
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
after(() => {
|
|
880
|
+
storeElement.remove();
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
beforeEach(() => {
|
|
884
|
+
mockPostJSON = stub();
|
|
885
|
+
(storeElement as any).postJSON = mockPostJSON;
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
afterEach(() => {
|
|
889
|
+
// Clean up any dialogs left in the DOM
|
|
890
|
+
document.querySelectorAll('temba-dialog').forEach((d) => d.remove());
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it('sets isSaving when dirtyDate changes', async () => {
|
|
894
|
+
editor = await fixture(html`
|
|
895
|
+
<temba-flow-editor>
|
|
896
|
+
<div id="canvas"></div>
|
|
897
|
+
</temba-flow-editor>
|
|
898
|
+
`);
|
|
899
|
+
|
|
900
|
+
(editor as any).isSaving = false;
|
|
901
|
+
|
|
902
|
+
// Simulate a dirtyDate change via updated()
|
|
903
|
+
(editor as any).dirtyDate = new Date();
|
|
904
|
+
const changes = new Map();
|
|
905
|
+
changes.set('dirtyDate', null);
|
|
906
|
+
(editor as any).updated(changes);
|
|
907
|
+
|
|
908
|
+
expect((editor as any).isSaving).to.be.true;
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
it('renders save indicator with visible class when saving', async () => {
|
|
912
|
+
editor = await fixture(html`
|
|
913
|
+
<temba-flow-editor>
|
|
914
|
+
<div id="canvas"></div>
|
|
915
|
+
</temba-flow-editor>
|
|
916
|
+
`);
|
|
917
|
+
|
|
918
|
+
(editor as any).canvasSize = { width: 800, height: 600 };
|
|
919
|
+
(editor as any).isSaving = true;
|
|
920
|
+
await editor.updateComplete;
|
|
921
|
+
|
|
922
|
+
const indicator = editor.querySelector('.save-indicator');
|
|
923
|
+
expect(indicator).to.exist;
|
|
924
|
+
expect(indicator.classList.contains('visible')).to.be.true;
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
it('save indicator is not visible when not saving', async () => {
|
|
928
|
+
editor = await fixture(html`
|
|
929
|
+
<temba-flow-editor>
|
|
930
|
+
<div id="canvas"></div>
|
|
931
|
+
</temba-flow-editor>
|
|
932
|
+
`);
|
|
933
|
+
|
|
934
|
+
(editor as any).canvasSize = { width: 800, height: 600 };
|
|
935
|
+
(editor as any).isSaving = false;
|
|
936
|
+
await editor.updateComplete;
|
|
937
|
+
|
|
938
|
+
const indicator = editor.querySelector('.save-indicator');
|
|
939
|
+
expect(indicator).to.exist;
|
|
940
|
+
expect(indicator.classList.contains('visible')).to.be.false;
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
it('clears isSaving after successful save', async () => {
|
|
944
|
+
editor = await fixture(html`
|
|
945
|
+
<temba-flow-editor>
|
|
946
|
+
<div id="canvas"></div>
|
|
947
|
+
</temba-flow-editor>
|
|
948
|
+
`);
|
|
949
|
+
|
|
950
|
+
editor.flow = 'test-flow';
|
|
951
|
+
(editor as any).definition = { nodes: [], _ui: { nodes: {} } };
|
|
952
|
+
|
|
953
|
+
mockPostJSON.resolves({
|
|
954
|
+
status: 200,
|
|
955
|
+
json: {},
|
|
956
|
+
body: '{}',
|
|
957
|
+
headers: new Headers()
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
await (editor as any).saveChanges();
|
|
961
|
+
|
|
962
|
+
expect((editor as any).isSaving).to.be.false;
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
it('shows error dialog on non-200 response', async () => {
|
|
966
|
+
editor = await fixture(html`
|
|
967
|
+
<temba-flow-editor>
|
|
968
|
+
<div id="canvas"></div>
|
|
969
|
+
</temba-flow-editor>
|
|
970
|
+
`);
|
|
971
|
+
|
|
972
|
+
editor.flow = 'test-flow';
|
|
973
|
+
(editor as any).definition = { nodes: [], _ui: { nodes: {} } };
|
|
974
|
+
|
|
975
|
+
mockPostJSON.resolves({
|
|
976
|
+
status: 400,
|
|
977
|
+
json: { detail: 'Invalid flow definition' },
|
|
978
|
+
body: '{"detail":"Invalid flow definition"}',
|
|
979
|
+
headers: new Headers()
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
await (editor as any).saveChanges();
|
|
983
|
+
await editor.updateComplete;
|
|
984
|
+
|
|
985
|
+
expect((editor as any).isSaving).to.be.false;
|
|
986
|
+
const dialog = document.querySelector('temba-dialog');
|
|
987
|
+
expect(dialog).to.exist;
|
|
988
|
+
expect(dialog.textContent).to.contain('Invalid flow definition');
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it('shows error dialog on 500 server error', async () => {
|
|
992
|
+
editor = await fixture(html`
|
|
993
|
+
<temba-flow-editor>
|
|
994
|
+
<div id="canvas"></div>
|
|
995
|
+
</temba-flow-editor>
|
|
996
|
+
`);
|
|
997
|
+
|
|
998
|
+
editor.flow = 'test-flow';
|
|
999
|
+
(editor as any).definition = { nodes: [], _ui: { nodes: {} } };
|
|
1000
|
+
|
|
1001
|
+
mockPostJSON.rejects(new Response(null, { status: 500 }));
|
|
1002
|
+
|
|
1003
|
+
await (editor as any).saveChanges();
|
|
1004
|
+
await editor.updateComplete;
|
|
1005
|
+
|
|
1006
|
+
expect((editor as any).isSaving).to.be.false;
|
|
1007
|
+
const dialog = document.querySelector('temba-dialog');
|
|
1008
|
+
expect(dialog).to.exist;
|
|
1009
|
+
expect(dialog.textContent).to.contain('Server error');
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
it('shows error dialog on network failure', async () => {
|
|
1013
|
+
editor = await fixture(html`
|
|
1014
|
+
<temba-flow-editor>
|
|
1015
|
+
<div id="canvas"></div>
|
|
1016
|
+
</temba-flow-editor>
|
|
1017
|
+
`);
|
|
1018
|
+
|
|
1019
|
+
editor.flow = 'test-flow';
|
|
1020
|
+
(editor as any).definition = { nodes: [], _ui: { nodes: {} } };
|
|
1021
|
+
|
|
1022
|
+
mockPostJSON.rejects(new Error('Network error'));
|
|
1023
|
+
|
|
1024
|
+
await (editor as any).saveChanges();
|
|
1025
|
+
await editor.updateComplete;
|
|
1026
|
+
|
|
1027
|
+
expect((editor as any).isSaving).to.be.false;
|
|
1028
|
+
const dialog = document.querySelector('temba-dialog');
|
|
1029
|
+
expect(dialog).to.exist;
|
|
1030
|
+
expect(dialog.textContent).to.contain('Unable to reach the server');
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
it('extracts error message from response json fields', () => {
|
|
1034
|
+
editor = new Editor();
|
|
1035
|
+
|
|
1036
|
+
expect(
|
|
1037
|
+
(editor as any).extractErrorMessage({
|
|
1038
|
+
status: 400,
|
|
1039
|
+
json: { detail: 'Bad request' }
|
|
1040
|
+
})
|
|
1041
|
+
).to.equal('Bad request');
|
|
1042
|
+
|
|
1043
|
+
expect(
|
|
1044
|
+
(editor as any).extractErrorMessage({
|
|
1045
|
+
status: 400,
|
|
1046
|
+
json: { error: 'Something went wrong' }
|
|
1047
|
+
})
|
|
1048
|
+
).to.equal('Something went wrong');
|
|
1049
|
+
|
|
1050
|
+
expect(
|
|
1051
|
+
(editor as any).extractErrorMessage({
|
|
1052
|
+
status: 400,
|
|
1053
|
+
json: { description: 'Detailed error' }
|
|
1054
|
+
})
|
|
1055
|
+
).to.equal('Detailed error');
|
|
1056
|
+
|
|
1057
|
+
expect(
|
|
1058
|
+
(editor as any).extractErrorMessage({
|
|
1059
|
+
status: 403,
|
|
1060
|
+
json: {}
|
|
1061
|
+
})
|
|
1062
|
+
).to.equal('Save failed with status 403.');
|
|
1063
|
+
});
|
|
1064
|
+
});
|
|
825
1065
|
});
|
|
@@ -171,4 +171,66 @@ describe('calculateFlowchartPath', () => {
|
|
|
171
171
|
expect(path).to.include('M 150 0');
|
|
172
172
|
expect(path).to.include('L 50 100'); // ends at target
|
|
173
173
|
});
|
|
174
|
+
|
|
175
|
+
it('applies jogYOffset to shift the horizontal jog level', () => {
|
|
176
|
+
const pathNoOffset = calculateFlowchartPath(
|
|
177
|
+
50,
|
|
178
|
+
0,
|
|
179
|
+
150,
|
|
180
|
+
200,
|
|
181
|
+
20,
|
|
182
|
+
10,
|
|
183
|
+
5,
|
|
184
|
+
'top',
|
|
185
|
+
0
|
|
186
|
+
);
|
|
187
|
+
const pathPosOffset = calculateFlowchartPath(
|
|
188
|
+
50,
|
|
189
|
+
0,
|
|
190
|
+
150,
|
|
191
|
+
200,
|
|
192
|
+
20,
|
|
193
|
+
10,
|
|
194
|
+
5,
|
|
195
|
+
'top',
|
|
196
|
+
10
|
|
197
|
+
);
|
|
198
|
+
const pathNegOffset = calculateFlowchartPath(
|
|
199
|
+
50,
|
|
200
|
+
0,
|
|
201
|
+
150,
|
|
202
|
+
200,
|
|
203
|
+
20,
|
|
204
|
+
10,
|
|
205
|
+
5,
|
|
206
|
+
'top',
|
|
207
|
+
-10
|
|
208
|
+
);
|
|
209
|
+
expect(pathNoOffset).to.not.equal(pathPosOffset);
|
|
210
|
+
expect(pathNoOffset).to.not.equal(pathNegOffset);
|
|
211
|
+
expect(pathPosOffset).to.not.equal(pathNegOffset);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('produces same path with jogYOffset=0 as without offset', () => {
|
|
215
|
+
const pathDefault = calculateFlowchartPath(50, 0, 150, 200);
|
|
216
|
+
const pathZero = calculateFlowchartPath(
|
|
217
|
+
50,
|
|
218
|
+
0,
|
|
219
|
+
150,
|
|
220
|
+
200,
|
|
221
|
+
20,
|
|
222
|
+
10,
|
|
223
|
+
5,
|
|
224
|
+
'top',
|
|
225
|
+
0
|
|
226
|
+
);
|
|
227
|
+
expect(pathDefault).to.equal(pathZero);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('clamps jogYOffset to valid bounds', () => {
|
|
231
|
+
// Large positive offset should not push jogY past entryY
|
|
232
|
+
const path = calculateFlowchartPath(50, 0, 150, 50, 20, 10, 5, 'top', 1000);
|
|
233
|
+
expect(path).to.include('M 50 0');
|
|
234
|
+
expect(path).to.include('L 150 50'); // should still reach target
|
|
235
|
+
});
|
|
174
236
|
});
|
|
@@ -163,9 +163,9 @@ describe('temba-node-type-selector', () => {
|
|
|
163
163
|
item.textContent?.trim()
|
|
164
164
|
);
|
|
165
165
|
|
|
166
|
-
// voice flow should have Say Message and Play
|
|
166
|
+
// voice flow should have Say Message and Play Recording
|
|
167
167
|
expect(titles).to.include('Say Message');
|
|
168
|
-
expect(titles).to.include('Play
|
|
168
|
+
expect(titles).to.include('Play Recording');
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
it('filters actions by flow type - message flow should not show voice-only actions', async () => {
|
|
@@ -181,9 +181,9 @@ describe('temba-node-type-selector', () => {
|
|
|
181
181
|
item.textContent?.trim()
|
|
182
182
|
);
|
|
183
183
|
|
|
184
|
-
// message flow should not have Say Message or Play
|
|
184
|
+
// message flow should not have Say Message or Play Recording
|
|
185
185
|
expect(titles).to.not.include('Say Message');
|
|
186
|
-
expect(titles).to.not.include('Play
|
|
186
|
+
expect(titles).to.not.include('Play Recording');
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
it('filters splits by flow type - message flow should show wait for response', async () => {
|
|
@@ -219,9 +219,9 @@ describe('temba-node-type-selector', () => {
|
|
|
219
219
|
// voice flow should not have Wait for Response
|
|
220
220
|
expect(titles).to.not.include('Wait for Response');
|
|
221
221
|
|
|
222
|
-
// but should have Wait for Digits and Wait for Menu
|
|
222
|
+
// but should have Wait for Digits and Wait for Menu
|
|
223
223
|
expect(titles).to.include('Wait for Digits');
|
|
224
|
-
expect(titles).to.include('Wait for Menu
|
|
224
|
+
expect(titles).to.include('Wait for Menu');
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
it('filters by features - AI feature enables AI splits', async () => {
|
|
@@ -857,7 +857,12 @@ describe('temba-select', () => {
|
|
|
857
857
|
|
|
858
858
|
await openSelect(clock, select);
|
|
859
859
|
await typeInto('temba-select', 're', false);
|
|
860
|
-
|
|
860
|
+
|
|
861
|
+
// Ensure Lit has processed the input change and scheduled the debounced fetch
|
|
862
|
+
await select.updateComplete;
|
|
863
|
+
clock.runAll();
|
|
864
|
+
await select.updateComplete;
|
|
865
|
+
|
|
861
866
|
assert.equal(select.visibleOptions.length, 2);
|
|
862
867
|
|
|
863
868
|
await assertScreenshot('select/searching', getClipWithOptions(select));
|
package/test/utils.test.ts
CHANGED
|
@@ -220,7 +220,9 @@ export const delay = (millis: number) => {
|
|
|
220
220
|
});
|
|
221
221
|
};
|
|
222
222
|
|
|
223
|
-
// Enhanced wait utility for more robust testing
|
|
223
|
+
// Enhanced wait utility for more robust testing.
|
|
224
|
+
// Uses the Puppeteer-provided waitFor (real time) instead of delay (which uses
|
|
225
|
+
// window.setTimeout and breaks when sinon fake timers are active).
|
|
224
226
|
export const waitForCondition = async (
|
|
225
227
|
predicate: () => boolean,
|
|
226
228
|
maxAttempts: number = 20,
|
|
@@ -228,7 +230,7 @@ export const waitForCondition = async (
|
|
|
228
230
|
): Promise<void> => {
|
|
229
231
|
let attempts = 0;
|
|
230
232
|
while (!predicate() && attempts < maxAttempts) {
|
|
231
|
-
await
|
|
233
|
+
await waitFor(delayMs);
|
|
232
234
|
attempts++;
|
|
233
235
|
}
|
|
234
236
|
if (!predicate()) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|