From 0c96eea27c87bedefe6e6de0c1bbb53ed83ea14a Mon Sep 17 00:00:00 2001 From: Matt Mazzola Date: Thu, 17 Oct 2019 12:39:57 -0700 Subject: [PATCH] feat: allow adding conditions to actions (#1332) * feat: allow adding conditions to actions * chore: display value conditions * chore: pdate internationalization language * chore: fix condition creator modal and display existing conditions * chore: start work on tests * test: more work on tests * test: fix tests * chore: update source hashText since it was moved * chore: allow multivalue entity selection * chore: more renaming to imply conditions * fix: multivalue entity conditions * test: add multivalue tests * fix: available condition matching and sorting * chore: move conditions utilities to dedicated file * chore: add comment for findNumberFromMemory * chore: simplify elementsFromConditions * chore: fix render condition logic more * chore: move logic about action conditions to utility module * test: add tests for dialog validity * chore: add protection around setting selected entity to undefined * fix: white font on add condition button * chore: make picker robust to no entities * fix: allow negative conditions * chore: add info to errors and move utilities * chore: spacing * chore: remove "Entities or" from labels * chore: more work on removing Entity Or from names fix setState bug * chore: update models --- cypress/fixtures/actionConditions.cl | 3051 +++++++++++++++++ .../regressionTs/actionConditions.spec.ts | 710 ++++ cypress/support/selectors.ts | 18 + package-lock.json | 65 +- package.json | 2 +- src/Utils/actionCondition.ts | 142 + src/components/ActionDetailsList.tsx | 53 +- src/components/modals/ActionCreatorEditor.css | 5 + src/components/modals/ActionCreatorEditor.tsx | 290 +- src/components/modals/ActionScorer.tsx | 70 +- .../modals/ConditionCreatorModal.css | 23 + .../modals/ConditionCreatorModal.tsx | 356 ++ src/react-intl-messages.ts | 14 +- src/routes/Apps/App/Settings.tsx | 1 + src/types/const.ts | 2 +- 15 files changed, 4610 insertions(+), 192 deletions(-) create mode 100644 cypress/fixtures/actionConditions.cl create mode 100644 cypress/integration/regressionTs/actionConditions.spec.ts create mode 100644 src/Utils/actionCondition.ts create mode 100644 src/components/modals/ConditionCreatorModal.css create mode 100644 src/components/modals/ConditionCreatorModal.tsx diff --git a/cypress/fixtures/actionConditions.cl b/cypress/fixtures/actionConditions.cl new file mode 100644 index 00000000..96bcb190 --- /dev/null +++ b/cypress/fixtures/actionConditions.cl @@ -0,0 +1,3051 @@ +{ + "trainDialogs": [ + { + "tags": [], + "description": "", + "trainDialogId": "5ef51e90-3fc1-49c1-b9ed-bdcfdb85da23", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "Set the enum value!", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "0260c4f9-5727-419a-9acc-b04e32ce9e9b", + "metrics": { + "predictMetrics": { + "blisTime": 0.006607770919799805, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "54b660a5-40e8-4d4e-940b-2ce3138c28cd", + "values": [ + { + "userText": "TWO", + "displayText": "TWO", + "builtinType": null, + "enumValueId": "9cd1a6e1-82ac-4df3-b211-703fa0e66dc6", + "resolution": null + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "76ac728c-a5d7-4a7d-8df4-2dec2f85a369", + "metrics": { + "predictMetrics": { + "blisTime": 0.006268978118896484, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.7510116-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "911281ea-9215-4867-a439-eaecef30946b", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "lets talk about fruits", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.015887975692749023, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "i like avocados, blueberries, and cucumbers", + "labelEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 7, + "endCharIndex": 14, + "entityText": "avocados" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 17, + "endCharIndex": 27, + "entityText": "blueberries" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 34, + "endCharIndex": 42, + "entityText": "cucumbers", + "resolution": {}, + "builtinType": "LUIS" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "avocados", + "displayText": "avocados", + "builtinType": null, + "resolution": null + }, + { + "userText": "blueberries", + "displayText": "blueberries", + "builtinType": null, + "resolution": null + }, + { + "userText": "cucumbers", + "displayText": "cucumbers", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "d87ed8d4-dfbd-41cd-9c41-1f4acee1ff02", + "metrics": { + "predictMetrics": { + "blisTime": 0.012417078018188477, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "avocados", + "displayText": "avocados", + "builtinType": null, + "resolution": null + }, + { + "userText": "blueberries", + "displayText": "blueberries", + "builtinType": null, + "resolution": null + }, + { + "userText": "cucumbers", + "displayText": "cucumbers", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.012477874755859375, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "i also each cranberries, peaches, and passion fruit", + "labelEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 12, + "endCharIndex": 22, + "entityText": "cranberries", + "resolution": {}, + "builtinType": "LUIS" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 25, + "endCharIndex": 31, + "entityText": "peaches" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 38, + "endCharIndex": 50, + "entityText": "passion fruit", + "resolution": {}, + "builtinType": "LUIS" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "avocados", + "displayText": "avocados", + "builtinType": null, + "resolution": null + }, + { + "userText": "blueberries", + "displayText": "blueberries", + "builtinType": null, + "resolution": null + }, + { + "userText": "cucumbers", + "displayText": "cucumbers", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "cranberries", + "displayText": "cranberries", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "peaches", + "displayText": "peaches", + "builtinType": null, + "resolution": null + }, + { + "userText": "passion fruit", + "displayText": "passion fruit", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "e436b289-e382-4a09-bb4a-977c4b0b7976", + "metrics": { + "predictMetrics": { + "blisTime": 0.012506246566772461, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "avocados", + "displayText": "avocados", + "builtinType": null, + "resolution": null + }, + { + "userText": "blueberries", + "displayText": "blueberries", + "builtinType": null, + "resolution": null + }, + { + "userText": "cucumbers", + "displayText": "cucumbers", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "cranberries", + "displayText": "cranberries", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "peaches", + "displayText": "peaches", + "builtinType": null, + "resolution": null + }, + { + "userText": "passion fruit", + "displayText": "passion fruit", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.006430864334106445, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.7508869-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "2bf1a467-54e2-4c04-9f24-ccd043ee6900", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "hi", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.012739181518554688, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "100", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 2, + "entityText": "100", + "resolution": { + "subtype": "integer", + "value": "100" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 2, + "entityText": "100", + "resolution": { + "subtype": "integer", + "value": "100" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "100", + "displayText": "100", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "100" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "07745c2f-cb58-478a-8b03-af584e1d24f6", + "metrics": { + "predictMetrics": { + "blisTime": 0.00571441650390625, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "100", + "displayText": "100", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "100" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011363983154296875, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "3", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "8745f0d3-4044-4c5d-974f-71c979494429", + "metrics": { + "predictMetrics": { + "blisTime": 0.007410526275634766, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.009434223175048828, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "8", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "8", + "resolution": { + "subtype": "integer", + "value": "8" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "8", + "resolution": { + "subtype": "integer", + "value": "8" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "8", + "displayText": "8", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "8" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "09825831-f220-4a42-9a5b-27f48773ae8a", + "metrics": { + "predictMetrics": { + "blisTime": 0.012062311172485352, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "8", + "displayText": "8", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "8" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.006615400314331055, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.7509452-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "638d3d36-eef2-4190-abfc-0170d53c5f78", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "Hi", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011181354522705078, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "28", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "28", + "resolution": { + "subtype": "integer", + "value": "28" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "28", + "resolution": { + "subtype": "integer", + "value": "28" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "28", + "displayText": "28", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "28" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "09825831-f220-4a42-9a5b-27f48773ae8a", + "metrics": { + "predictMetrics": { + "blisTime": 0.008887529373168945, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "28", + "displayText": "28", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "28" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.0059146881103515625, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "3", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "8745f0d3-4044-4c5d-974f-71c979494429", + "metrics": { + "predictMetrics": { + "blisTime": 0.005614042282104492, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": null + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.7507392-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "266f9bdf-2ac1-4a47-ab29-5f3970e5c18a", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "hi", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011990547180175781, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "6", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "6", + "resolution": { + "subtype": "integer", + "value": "6" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "6", + "resolution": { + "subtype": "integer", + "value": "6" + }, + "builtinType": "builtin.number" + } + ] + }, + { + "text": "7", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "7", + "resolution": { + "subtype": "integer", + "value": "7" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "7", + "resolution": { + "subtype": "integer", + "value": "7" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "6", + "displayText": "6", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "6" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "09825831-f220-4a42-9a5b-27f48773ae8a", + "metrics": { + "predictMetrics": { + "blisTime": 0.01236271858215332, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "6", + "displayText": "6", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "6" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011654853820800781, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "100", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 2, + "entityText": "100", + "resolution": { + "subtype": "integer", + "value": "100" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 2, + "entityText": "100", + "resolution": { + "subtype": "integer", + "value": "100" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "100", + "displayText": "100", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "100" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "07745c2f-cb58-478a-8b03-af584e1d24f6", + "metrics": { + "predictMetrics": { + "blisTime": 0.00574040412902832, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "100", + "displayText": "100", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "100" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.00557398796081543, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "2", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "2", + "resolution": { + "subtype": "integer", + "value": "2" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "2", + "resolution": { + "subtype": "integer", + "value": "2" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "2", + "displayText": "2", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "2" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "8745f0d3-4044-4c5d-974f-71c979494429", + "metrics": { + "predictMetrics": { + "blisTime": 0.009526729583740234, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "2", + "displayText": "2", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "2" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.008112430572509766, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.7507953-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "5697488f-7671-4ca0-be07-f41f2075249c", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "lets do different things based on number of labels", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.01158905029296875, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "i like apples", + "labelEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 7, + "endCharIndex": 12, + "entityText": "apples", + "resolution": {}, + "builtinType": "LUIS" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "e7ec68f7-75ff-4e14-8d4c-b0248bb88a69", + "metrics": { + "predictMetrics": { + "blisTime": 0.010613679885864258, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.008808135986328125, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "i like oranges and grapes", + "labelEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 7, + "endCharIndex": 13, + "entityText": "oranges", + "resolution": {}, + "builtinType": "LUIS" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 19, + "endCharIndex": 24, + "entityText": "grapes", + "resolution": {}, + "builtinType": "LUIS" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "oranges", + "displayText": "oranges", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "grapes", + "displayText": "grapes", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "d87ed8d4-dfbd-41cd-9c41-1f4acee1ff02", + "metrics": { + "predictMetrics": { + "blisTime": 0.0067501068115234375, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "oranges", + "displayText": "oranges", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "grapes", + "displayText": "grapes", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.0074765682220458984, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "i like bananas, pears, plums, and more!", + "labelEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 7, + "endCharIndex": 13, + "entityText": "bananas", + "resolution": {}, + "builtinType": "LUIS" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 16, + "endCharIndex": 20, + "entityText": "pears", + "resolution": {}, + "builtinType": "LUIS" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 23, + "endCharIndex": 27, + "entityText": "plums", + "resolution": {}, + "builtinType": "LUIS" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "oranges", + "displayText": "oranges", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "grapes", + "displayText": "grapes", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "bananas", + "displayText": "bananas", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "pears", + "displayText": "pears", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "plums", + "displayText": "plums", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "e436b289-e382-4a09-bb4a-977c4b0b7976", + "metrics": { + "predictMetrics": { + "blisTime": 0.006249189376831055, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "oranges", + "displayText": "oranges", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "grapes", + "displayText": "grapes", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "bananas", + "displayText": "bananas", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "pears", + "displayText": "pears", + "builtinType": "LUIS", + "resolution": {} + }, + { + "userText": "plums", + "displayText": "plums", + "builtinType": "LUIS", + "resolution": {} + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.011565446853637695, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.7509155-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "a58ae860-b702-460d-ada2-5cfeec363fba", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "Hi", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011302947998046875, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "3", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "8745f0d3-4044-4c5d-974f-71c979494429", + "metrics": { + "predictMetrics": { + "blisTime": 0.012307882308959961, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011409997940063477, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "15", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "15", + "resolution": { + "subtype": "integer", + "value": "15" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "15", + "resolution": { + "subtype": "integer", + "value": "15" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "15", + "displayText": "15", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "15" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "09825831-f220-4a42-9a5b-27f48773ae8a", + "metrics": { + "predictMetrics": { + "blisTime": 0.005852460861206055, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "15", + "displayText": "15", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "15" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011673450469970703, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "75", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "75", + "resolution": { + "subtype": "integer", + "value": "75" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "75", + "resolution": { + "subtype": "integer", + "value": "75" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "75", + "displayText": "75", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "75" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "07745c2f-cb58-478a-8b03-af584e1d24f6", + "metrics": { + "predictMetrics": { + "blisTime": 0.005656242370605469, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "75", + "displayText": "75", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "75" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.005657672882080078, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "25", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "25", + "resolution": { + "subtype": "integer", + "value": "25" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "25", + "resolution": { + "subtype": "integer", + "value": "25" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "25", + "displayText": "25", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "25" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011678218841552734, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.7509763-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "33324f38-de60-42cb-ab72-e651497b9813", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "aseras", + "labelEntities": [] + }, + { + "text": "hi", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.017904281616210938, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "3", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "8745f0d3-4044-4c5d-974f-71c979494429", + "metrics": { + "predictMetrics": { + "blisTime": 0.005651712417602539, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "3", + "displayText": "3", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "3" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011104822158813477, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "6", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "6", + "resolution": { + "subtype": "integer", + "value": "6" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 0, + "entityText": "6", + "resolution": { + "subtype": "integer", + "value": "6" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "6", + "displayText": "6", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "6" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "09825831-f220-4a42-9a5b-27f48773ae8a", + "metrics": { + "predictMetrics": { + "blisTime": 0.006937980651855469, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "6", + "displayText": "6", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "6" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011712312698364258, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "20", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "20", + "resolution": { + "subtype": "integer", + "value": "20" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "20", + "resolution": { + "subtype": "integer", + "value": "20" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "20", + "displayText": "20", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "20" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "09825831-f220-4a42-9a5b-27f48773ae8a", + "metrics": { + "predictMetrics": { + "blisTime": 0.006045103073120117, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "20", + "displayText": "20", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "20" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.012843847274780273, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "42", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "42", + "resolution": { + "subtype": "integer", + "value": "42" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 0, + "endCharIndex": 1, + "entityText": "42", + "resolution": { + "subtype": "integer", + "value": "42" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "42", + "displayText": "42", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "42" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "07745c2f-cb58-478a-8b03-af584e1d24f6", + "metrics": { + "predictMetrics": { + "blisTime": 0.010656356811523438, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "42", + "displayText": "42", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "42" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.010201215744018555, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:42:02.750848-07:00", + "lastModifiedDateTime": "2019-10-14T22:42:02+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "9e662ff8-cdec-43fc-8e2a-6995b237291b", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "hi", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.011936187744140625, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "The numbers are 4 and 21", + "labelEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "startCharIndex": 16, + "endCharIndex": 16, + "entityText": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 16, + "endCharIndex": 16, + "entityText": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "builtinType": "builtin.number" + }, + { + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "startCharIndex": 22, + "endCharIndex": 23, + "entityText": "21", + "resolution": { + "subtype": "integer", + "value": "21" + }, + "builtinType": "builtin.number" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "4", + "displayText": "4", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "4" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "8745f0d3-4044-4c5d-974f-71c979494429", + "metrics": { + "predictMetrics": { + "blisTime": 0.0058193206787109375, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "values": [ + { + "userText": "4", + "displayText": "4", + "builtinType": "builtin.number", + "resolution": { + "subtype": "integer", + "value": "4" + } + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "metrics": { + "predictMetrics": { + "blisTime": 0.005566120147705078, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:49:50.0346423-07:00", + "lastModifiedDateTime": "2019-10-14T22:52:43+00:00" + }, + { + "tags": [], + "description": "", + "trainDialogId": "18166f2e-cab3-48d2-abd6-e312a69c6edd", + "rounds": [ + { + "extractorStep": { + "textVariations": [ + { + "text": "lets label some fruits", + "labelEntities": [] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.015622854232788086, + "contextDialogBlisTime": 0 + } + } + } + ] + }, + { + "extractorStep": { + "textVariations": [ + { + "text": "apples, bananas, grapes, pears, and plums", + "labelEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 0, + "endCharIndex": 5, + "entityText": "apples" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 8, + "endCharIndex": 14, + "entityText": "bananas" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 17, + "endCharIndex": 22, + "entityText": "grapes" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 25, + "endCharIndex": 29, + "entityText": "pears" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "startCharIndex": 36, + "endCharIndex": 40, + "entityText": "plums" + } + ] + } + ] + }, + "scorerSteps": [ + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": null, + "resolution": null + }, + { + "userText": "bananas", + "displayText": "bananas", + "builtinType": null, + "resolution": null + }, + { + "userText": "grapes", + "displayText": "grapes", + "builtinType": null, + "resolution": null + }, + { + "userText": "pears", + "displayText": "pears", + "builtinType": null, + "resolution": null + }, + { + "userText": "plums", + "displayText": "plums", + "builtinType": null, + "resolution": null + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "e436b289-e382-4a09-bb4a-977c4b0b7976", + "metrics": { + "predictMetrics": { + "blisTime": 0.01227569580078125, + "contextDialogBlisTime": 0 + } + } + }, + { + "input": { + "filledEntities": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "values": [ + { + "userText": "apples", + "displayText": "apples", + "builtinType": null, + "resolution": null + }, + { + "userText": "bananas", + "displayText": "bananas", + "builtinType": null, + "resolution": null + }, + { + "userText": "grapes", + "displayText": "grapes", + "builtinType": null, + "resolution": null + }, + { + "userText": "pears", + "displayText": "pears", + "builtinType": null, + "resolution": null + }, + { + "userText": "plums", + "displayText": "plums", + "builtinType": null, + "resolution": null + } + ] + } + ], + "context": {}, + "maskedActions": [] + }, + "labelAction": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "metrics": { + "predictMetrics": { + "blisTime": 0.011931896209716797, + "contextDialogBlisTime": 0 + } + } + } + ] + } + ], + "clientData": { + "importHashes": [] + }, + "initialFilledEntities": [], + "createdDateTime": "2019-10-14T15:53:28.3112824-07:00", + "lastModifiedDateTime": "2019-10-14T22:54:29+00:00" + } + ], + "actions": [ + { + "actionId": "c8de0a6b-f0e2-4a74-b06e-184dfcfee7e5", + "createdDateTime": "2019-10-14T15:42:02.7505739-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"What is the number?\",\"marks\":[]}]}]}]}}}", + "isTerminal": true, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [], + "negativeConditions": [], + "suggestedEntity": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "8745f0d3-4044-4c5d-974f-71c979494429", + "createdDateTime": "2019-10-14T15:42:02.7506041-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"Your number is low!\",\"marks\":[]}]}]}]}}}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "value": 5, + "condition": "LESS_THAN" + } + ], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "09825831-f220-4a42-9a5b-27f48773ae8a", + "createdDateTime": "2019-10-14T15:42:02.7506238-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"Your number is normal.\",\"marks\":[]}]}]}]}}}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "value": 5, + "condition": "GREATER_THAN_OR_EQUAL" + }, + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "value": 30, + "condition": "LESS_THAN" + } + ], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "07745c2f-cb58-478a-8b03-af584e1d24f6", + "createdDateTime": "2019-10-14T15:42:02.7506372-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"Your number is high!\",\"marks\":[]}]}]}]}}}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [ + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "value": 30, + "condition": "GREATER_THAN_OR_EQUAL" + } + ], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "0260c4f9-5727-419a-9acc-b04e32ce9e9b", + "createdDateTime": "2019-10-14T15:42:02.7506473-07:00", + "actionType": "SET_ENTITY", + "payload": "{\"entityId\":\"54b660a5-40e8-4d4e-940b-2ce3138c28cd\",\"enumValueId\":\"9cd1a6e1-82ac-4df3-b211-703fa0e66dc6\"}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [], + "negativeConditions": [], + "entityId": "54b660a5-40e8-4d4e-940b-2ce3138c28cd", + "enumValueId": "9cd1a6e1-82ac-4df3-b211-703fa0e66dc6", + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "6d32a052-b12a-4a71-bcc8-00adfdc2f289", + "createdDateTime": "2019-10-14T15:42:02.750662-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"List your favorite fruits.\",\"marks\":[]}]}]}]}}}", + "isTerminal": true, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "e7ec68f7-75ff-4e14-8d4c-b0248bb88a69", + "createdDateTime": "2019-10-14T15:42:02.7506711-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"You have one fruit: \",\"marks\":[]}]},{\"kind\":\"inline\",\"type\":\"mention-inline-node\",\"isVoid\":false,\"data\":{\"completed\":true,\"option\":{\"id\":\"ba50584b-244c-4c73-83fe-b0bf04498714\",\"name\":\"myMultiValueEntity\"}},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"$myMultiValueEntity\",\"marks\":[]}]}]},{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"\",\"marks\":[]}]}]}]}}}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [ + "ba50584b-244c-4c73-83fe-b0bf04498714" + ], + "requiredEntities": [ + "ba50584b-244c-4c73-83fe-b0bf04498714" + ], + "negativeEntities": [], + "requiredConditions": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "value": 1, + "condition": "EQUAL" + } + ], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "d87ed8d4-dfbd-41cd-9c41-1f4acee1ff02", + "createdDateTime": "2019-10-14T15:42:02.7506858-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"You have some fruits: \",\"marks\":[]}]},{\"kind\":\"inline\",\"type\":\"mention-inline-node\",\"isVoid\":false,\"data\":{\"completed\":true,\"option\":{\"id\":\"ba50584b-244c-4c73-83fe-b0bf04498714\",\"name\":\"myMultiValueEntity\"}},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"$myMultiValueEntity\",\"marks\":[]}]}]},{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"\",\"marks\":[]}]}]}]}}}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [ + "ba50584b-244c-4c73-83fe-b0bf04498714" + ], + "requiredEntities": [ + "ba50584b-244c-4c73-83fe-b0bf04498714" + ], + "negativeEntities": [], + "requiredConditions": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "value": 1, + "condition": "GREATER_THAN" + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "value": 3, + "condition": "LESS_THEN_OR_EQUAL" + } + ], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "e436b289-e382-4a09-bb4a-977c4b0b7976", + "createdDateTime": "2019-10-14T15:42:02.7507009-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"You have many fruits: \",\"marks\":[]}]},{\"kind\":\"inline\",\"type\":\"mention-inline-node\",\"isVoid\":false,\"data\":{\"completed\":true,\"option\":{\"id\":\"ba50584b-244c-4c73-83fe-b0bf04498714\",\"name\":\"myMultiValueEntity\"}},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"$myMultiValueEntity\",\"marks\":[]}]}]},{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"\",\"marks\":[]}]}]}]}}}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [ + "ba50584b-244c-4c73-83fe-b0bf04498714" + ], + "requiredEntities": [ + "ba50584b-244c-4c73-83fe-b0bf04498714" + ], + "negativeEntities": [], + "requiredConditions": [ + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "value": 3, + "condition": "GREATER_THAN" + } + ], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "76ac728c-a5d7-4a7d-8df4-2dec2f85a369", + "createdDateTime": "2019-10-14T15:42:02.7507151-07:00", + "actionType": "TEXT", + "payload": "{\"json\":{\"kind\":\"value\",\"document\":{\"kind\":\"document\",\"data\":{},\"nodes\":[{\"kind\":\"block\",\"type\":\"line\",\"isVoid\":false,\"data\":{},\"nodes\":[{\"kind\":\"text\",\"leaves\":[{\"kind\":\"leaf\",\"text\":\"Enum Value is set to Two!\",\"marks\":[]}]}]}]}}}", + "isTerminal": true, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [ + { + "entityId": "54b660a5-40e8-4d4e-940b-2ce3138c28cd", + "valueId": "9cd1a6e1-82ac-4df3-b211-703fa0e66dc6", + "condition": "EQUAL" + } + ], + "negativeConditions": [], + "clientData": { + "importHashes": [] + } + }, + { + "actionId": "2c41993e-bbe5-4e5a-b3a8-822b7bc89600", + "createdDateTime": "2019-10-14T15:42:32.4296538-07:00", + "actionType": "SET_ENTITY", + "payload": "{\"entityId\":\"54b660a5-40e8-4d4e-940b-2ce3138c28cd\",\"enumValueId\":\"605e3f9f-ff60-4ed3-9fe9-af4d1826fe63\"}", + "isTerminal": false, + "isEntryNode": false, + "requiredEntitiesFromPayload": [], + "requiredEntities": [], + "negativeEntities": [], + "requiredConditions": [], + "negativeConditions": [], + "entityId": "54b660a5-40e8-4d4e-940b-2ce3138c28cd", + "enumValueId": "605e3f9f-ff60-4ed3-9fe9-af4d1826fe63", + "clientData": { + "importHashes": [] + } + } + ], + "entities": [ + { + "doNotMemorize": true, + "entityId": "0f07bf29-e6b3-4fcc-877c-04b7df609f20", + "createdDateTime": "2019-10-14T15:42:02.7502749-07:00", + "entityName": "builtin-number", + "entityType": "number", + "isMultivalue": false, + "isNegatible": false, + "isResolutionRequired": false + }, + { + "entityId": "4ad26b43-1c3b-40d9-8713-82dbee3b4b15", + "createdDateTime": "2019-10-14T15:42:02.7503486-07:00", + "entityName": "myNumber", + "entityType": "LUIS", + "isMultivalue": false, + "isNegatible": false, + "resolverType": "number", + "isResolutionRequired": true + }, + { + "entityId": "f17455aa-c010-4083-a4d3-57762d658ffa", + "createdDateTime": "2019-10-14T15:42:02.7503662-07:00", + "entityName": "myOtherEntity", + "entityType": "LUIS", + "isMultivalue": false, + "isNegatible": false, + "resolverType": "none", + "isResolutionRequired": false + }, + { + "entityId": "54b660a5-40e8-4d4e-940b-2ce3138c28cd", + "createdDateTime": "2019-10-14T15:42:02.750382-07:00", + "entityName": "myEnumEntity", + "entityType": "ENUM", + "isMultivalue": false, + "isNegatible": false, + "isResolutionRequired": false, + "enumValues": [ + { + "enumValueId": "7fbcf90a-6656-44a1-8467-a168919ee6c1", + "enumValue": "ONE" + }, + { + "enumValueId": "9cd1a6e1-82ac-4df3-b211-703fa0e66dc6", + "enumValue": "TWO" + }, + { + "enumValueId": "605e3f9f-ff60-4ed3-9fe9-af4d1826fe63", + "enumValue": "THREE" + } + ] + }, + { + "entityId": "ba50584b-244c-4c73-83fe-b0bf04498714", + "createdDateTime": "2019-10-14T15:42:02.750395-07:00", + "entityName": "myMultiValueEntity", + "entityType": "LUIS", + "isMultivalue": true, + "isNegatible": false, + "resolverType": "none", + "isResolutionRequired": false + } + ], + "packageId": "ce28be78-b001-4f76-ab00-ecd05ade919b" +} \ No newline at end of file diff --git a/cypress/integration/regressionTs/actionConditions.spec.ts b/cypress/integration/regressionTs/actionConditions.spec.ts new file mode 100644 index 00000000..741a17c3 --- /dev/null +++ b/cypress/integration/regressionTs/actionConditions.spec.ts @@ -0,0 +1,710 @@ +import * as util from '../../support/utilities' +import constants from '../../support/constants' +import s from '../../support/selectors' + +describe('Action Conditions', () => { + const testData = { + modelName: 'conditions', + modelFile: 'actionConditions.cl', + entities: { + enum: 'myEnumEntity', + number: 'myNumber', + multi: 'myMultiValueEntity', + other: 'myOtherEntity', + }, + operator: '>=', + constantValue: 5, + actions: { + whatIsNumber: 'What is the number?', + low: 'Your number is low!', + normal: 'Your number is normal.', + high: 'Your number is high!', + + listFruits: 'List your favorite fruits.', + one: 'You have one fruit', + some: 'You have some fruits', + many: 'You have many fruits', + + setEnumTwo: 'myEnumEntity: TWO', + enumTwo: 'Enum Value is set to Two!', + }, + conditions: { + gte5: `myNumber >= 5`, + lt5: `myNumber < 5`, + gte30: `myNumber >= 30`, + lt30: `myNumber < 30`, + eq1: `myMultiValueEntity == 1`, + gt1: `myMultiValueEntity > 1`, + lte3: `myMultiValueEntity <= 3`, + gt3: `myMultiValueEntity > 3`, + enumTwo: 'myEnumEntity == TWO' + }, + inputs: { + setEnumValue: 'Set the enum value!', + } + } + + const conditionDisplay = `${testData.entities.number} ${testData.operator} ${testData.constantValue}` + + before(() => { + cy.visit(`/`) + util.importModel(testData.modelName, testData.modelFile) + // Wait for training status to change. Sometimes training is fast and we can't rely on catching the transition to running status. Assume it happens and only ensure it's settled at completed. + cy.wait(3000) + cy.get(s.trainingStatus.completed, { timeout: constants.training.timeout }) + }) + + describe(`Condition Creation`, () => { + before(() => { + cy.get(s.model.buttonNavActions) + .click() + + cy.get(s.actions.buttonNewAction) + .click() + + cy.get(s.action.inputRequiredConditions) + .click() + + cy.get(s.action.buttonAddCondition) + .click() + }) + + after(() => { + cy.get(s.action.buttonCancel) + .click() + }) + + it(`should only show enum entities, multi value entities, and entities with required number resolver type`, () => { + cy.get(s.addConditionModal.dropdownEntity) + .click() + + // TODO: Why doesn't 2 argument version of contains(), maintain outer selector? + cy.get(s.common.dropDownOptions) + .should('have.length', 3) + .contains(testData.entities.enum) + + cy.get(s.common.dropDownOptions) + .contains(testData.entities.multi) + + cy.get(s.common.dropDownOptions) + .contains(testData.entities.number) + .click() + }) + + it(`should show existing conditions used by the selected entity`, () => { + cy.get(s.addConditionModal.existingCondition) + .should('have.length', 4) + + util.selectDropDownOption(s.addConditionModal.dropdownEntity, testData.entities.enum) + + cy.get(s.addConditionModal.existingCondition) + .should('have.length', 1) + }) + + it(`given enum entity is selected operator should be forced to equals and disabled and value should be list of enum values`, () => { + cy.get(s.addConditionModal.dropdownOperator) + .should('have.class', 'is-disabled') + .contains('==') + + cy.get(s.addConditionModal.dropdownEnumValue) + .click() + + cy.get(s.common.dropDownOptions) + .should('have.length', 3) + }) + + it(`given number entity it should allow all types of operators and show a number picker `, () => { + util.selectDropDownOption(s.addConditionModal.dropdownEntity, testData.entities.number) + + cy.get(s.addConditionModal.dropdownOperator) + .should('not.have.class', 'is-disabled') + .click() + + cy.get(s.common.dropDownOptions) + .should('have.length', 6) + + cy.get(s.addConditionModal.inputNumberValue) + }) + + it(`given condition being created is same as one of existing conditions, should show icon to indicate match`, () => { + cy.get(s.addConditionModal.inputNumberValue) + // Tried to type in number like 5 or 30 but it keeps getting reset on next element focus, however it doesn't do this on actual use. + // Use uparrow technique to achieve same result, value should be 5 + .type('{uparrow}'.repeat(testData.constantValue)) + + util.selectDropDownOption(s.addConditionModal.dropdownOperator, testData.operator) + + cy.get(s.addConditionModal.existingCondition) + .contains(conditionDisplay) + .parent() + .get(s.addConditionModal.existingConditionMatch) + }) + + it(`clicking "Create" should NOT add the condition if is a duplicate of another condition`, () => { + cy.get(s.addConditionModal.buttonCreate) + .click() + + cy.get(s.action.tagPickerRequired) + .contains(conditionDisplay) + .should('have.length', 1) + }) + + it(`clicking "Create" should add the ValueCondition in the action's "required conditions" list`, () => { + cy.get(s.action.inputRequiredConditions) + .click() + + cy.get(s.action.buttonAddCondition) + .click() + + cy.get(s.addConditionModal.buttonCreate) + .click() + + cy.get(s.action.tagPickerRequired) + .contains(`${testData.entities.number} == 0`) + }) + + it(`clicking "Use Condition" should also close and add the associated ValueCondition to the "required conditions" list`, () => { + cy.get(s.action.inputRequiredConditions) + .click() + + cy.get(s.action.buttonAddCondition) + .click() + + cy.get(s.addConditionModal.buttonUseCondition) + .first() + .click() + + cy.get(s.action.tagPickerRequired) + .contains(`${testData.entities.number} < 5`) + }) + + it(`clicking "Cancel" should close the model and NOT add the condition`, () => { + cy.get(s.action.inputRequiredConditions) + .click() + + cy.get(s.action.buttonAddCondition) + .click() + + cy.get(s.addConditionModal.buttonCancel) + .click() + + cy.get(s.addConditionModal.modal) + .should('not.be.visible') + }) + }) + + describe(`Action Constraints`, () => { + describe(`ValueConditions`, () => { + describe(`Single Value Entity - Number Value (number from label)`, () => { + it(`should properly constrain actions`, () => { + cy.get(s.model.buttonNavTrainDialogs).click() + cy.get(s.trainDialogs.buttonNew).click() + + // Start dialog + util.inputText('hi') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.whatIsNumber) + + // Enter 'low' number + util.inputText('3') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + // Verify conditions on other actions are disqualified + const lowInputActionConditionStates = [ + { + text: testData.actions.normal, + conditions: [ + { + text: testData.conditions.gte5, + match: false, + }, + { + text: testData.conditions.lt30, + match: true, + }, + ], + }, + { + text: testData.actions.high, + conditions: [ + { + text: testData.conditions.gte30, + match: false, + }, + ], + }, + { + text: testData.actions.low, + conditions: [ + { + text: testData.conditions.lt5, + match: true, + }, + ], + } + ] + + lowInputActionConditionStates.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.low) + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.whatIsNumber) + + // Enter 'Normal' number + util.inputText('9') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + const normalInputActionConditionStates = [ + { + text: testData.actions.normal, + conditions: [ + { + text: testData.conditions.gte5, + match: true, + }, + { + text: testData.conditions.lt30, + match: true, + }, + ], + }, + { + text: testData.actions.high, + conditions: [ + { + text: testData.conditions.gte30, + match: false, + }, + ], + }, + { + text: testData.actions.low, + conditions: [ + { + text: testData.conditions.lt5, + match: false, + }, + ], + } + ] + + normalInputActionConditionStates.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.normal) + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.whatIsNumber) + + // Enter 'High' number + util.inputText('89') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + const highInputActionConditionStates = [ + { + text: testData.actions.normal, + conditions: [ + { + text: testData.conditions.gte5, + match: true, + }, + { + text: testData.conditions.lt30, + match: false, + }, + ], + }, + { + text: testData.actions.high, + conditions: [ + { + text: testData.conditions.gte30, + match: true, + }, + ], + }, + { + text: testData.actions.low, + conditions: [ + { + text: testData.conditions.lt5, + match: false, + }, + ], + } + ] + + highInputActionConditionStates.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.high) + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.whatIsNumber) + + cy.get(s.trainDialog.buttonSave) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + cy.get(s.mergeModal.buttonSaveAsIs) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + }) + }) + + describe(`Multi Value Entity - Cardinality (number of labels)`, () => { + it(`should properly constrain actions`, () => { + cy.get(s.model.buttonNavTrainDialogs).click() + cy.get(s.trainDialogs.buttonNew).click() + + // Start dialog + util.inputText('lets do things based on number of fruits') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.listFruits) + + // Enter one fruit + util.inputText('i like apples') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + // Verify conditions on other actions are disqualified + const oneFruitActionConditionStates = [ + { + text: testData.actions.one, + conditions: [ + { + text: testData.conditions.eq1, + match: true, + }, + ], + }, + { + text: testData.actions.some, + conditions: [ + { + text: testData.conditions.gt1, + match: false, + }, + { + text: testData.conditions.lte3, + match: true, + }, + ], + }, + { + text: testData.actions.many, + conditions: [ + { + text: testData.conditions.gt3, + match: false, + }, + ], + } + ] + + oneFruitActionConditionStates.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.one) + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.listFruits) + + // Enter one fruit + util.inputText('i like oranges and grapes') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + // Verify conditions on other actions are disqualified + const someFruitActionConditionStates = [ + { + text: testData.actions.one, + conditions: [ + { + text: testData.conditions.eq1, + match: false, + }, + ], + }, + { + text: testData.actions.some, + conditions: [ + { + text: testData.conditions.gt1, + match: true, + }, + { + text: testData.conditions.lte3, + match: true, + }, + ], + }, + { + text: testData.actions.many, + conditions: [ + { + text: testData.conditions.gt3, + match: false, + }, + ], + } + ] + + someFruitActionConditionStates.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.some) + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.listFruits) + + // Enter one fruit + util.inputText('i like pears and plums') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + // Verify conditions on other actions are disqualified + const manyFruitActionConditionStates = [ + { + text: testData.actions.one, + conditions: [ + { + text: testData.conditions.eq1, + match: false, + }, + ], + }, + { + text: testData.actions.some, + conditions: [ + { + text: testData.conditions.gt1, + match: true, + }, + { + text: testData.conditions.lte3, + match: false, + }, + ], + }, + { + text: testData.actions.many, + conditions: [ + { + text: testData.conditions.gt3, + match: true, + }, + ], + } + ] + + manyFruitActionConditionStates.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.many) + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.listFruits) + + cy.get(s.trainDialog.buttonSave) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + cy.get(s.mergeModal.buttonSaveAsIs) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + }) + }) + }) + + describe(`EnumConditions`, () => { + it(`should properly constrain actions`, () => { + cy.get(s.model.buttonNavTrainDialogs).click() + cy.get(s.trainDialogs.buttonNew).click() + + // Start dialog + util.inputText('Set enum value!') + cy.get(s.trainDialog.buttonScoreActions).click() + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + const enumActionConditionStates = [ + { + text: testData.actions.enumTwo, + conditions: [ + { + text: testData.conditions.enumTwo, + match: false, + }, + ], + }, + ] + + enumActionConditionStates.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorer.enumActions, testData.actions.setEnumTwo) + + const enumAcs = [ + { + text: testData.actions.enumTwo, + conditions: [ + { + text: testData.conditions.enumTwo, + match: true, + }, + ], + }, + ] + + enumAcs.forEach(acs => verifyActionConditionsState(acs)) + + util.selectAction(s.trainDialog.actionScorerTextActions, testData.actions.enumTwo) + + cy.get(s.trainDialog.buttonSave) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + + cy.get(s.mergeModal.buttonSaveAsIs) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + }) + }) + }) + + describe(`Dialog Validity`, () => { + before(() => { + cy.get(s.model.buttonNavTrainDialogs).click() + }) + + describe(`ValueConditions`, () => { + describe('Single Value Entity - Number Value (number from label)', () => { + it(`when changing labels activities should be marked with errors if constraints not satisfied`, () => { + cy.get(s.trainDialogs.descriptions) + .contains('The numbers are 4 and 21') + .click() + + cy.get(s.webChat.messageFromBotException) + .should('not.exist') + + cy.get(s.webChat.activities) + .contains('The numbers are 4 and 21') + .click() + + util.removeLabel('4') + + cy.get('body') + .trigger(constants.events.selectWord, { detail: '21' }) + + cy.get(s.entityPicker.inputSearch) + .type(testData.entities.number) + .type(`{enter}`) + + cy.get(s.extractionEditor.buttonSubmitChanges) + .click() + + cy.get(s.webChat.messageFromBotException) + cy.get(s.trainDialog.buttonAbandon) + .click() + + cy.get(s.confirmCancelModal.buttonConfirm) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + }) + }) + + describe('Multi Value Entity - Cardinality (number of labels)', () => { + it(`when changing labels activities should be marked with errors if constraints not satisfied`, () => { + cy.get(s.trainDialogs.descriptions) + .contains(testData.inputs.setEnumValue) + .click() + + cy.get(s.webChat.messageFromBotException) + .should('not.exist') + + cy.get(s.webChat.activities) + .contains('memory.Set(myEnumEntity, TWO)') + .click() + + util.selectAction(s.trainDialog.actionScorer.enumActions, 'myEnumEntity: THREE') + cy.get(s.webChat.messageFromBotException) + cy.get(s.trainDialog.buttonAbandon) + .click() + + cy.get(s.confirmCancelModal.buttonConfirm) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + }) + }) + }) + + describe(`EnumConditions`, () => { + it(`when selecting different enum activities dependent on original enum should be marked with errors`, () => { + cy.get(s.trainDialogs.descriptions) + .contains(testData.inputs.setEnumValue) + .click() + + cy.get(s.webChat.messageFromBotException) + .should('not.exist') + + cy.get(s.webChat.activities) + .contains('memory.Set(myEnumEntity, TWO)') + .click() + + util.selectAction(s.trainDialog.actionScorer.enumActions, 'myEnumEntity: THREE') + cy.get(s.webChat.messageFromBotException) + cy.get(s.trainDialog.buttonAbandon) + .click() + + cy.get(s.confirmCancelModal.buttonConfirm) + .click() + + cy.get(s.common.spinner, { timeout: constants.spinner.timeout }) + .should('not.exist') + }) + }) + }) +}) + +type ActionQuality = { + text: string + conditions: { + text: string + match: boolean + }[] +} + +function verifyActionConditionsState(actionQualities: ActionQuality) { + cy.get(s.trainDialog.actionScorerTextActions) + .contains(actionQualities.text) + .parents(s.trainDialog.actionScorer.rowField) + .within(() => { + actionQualities.conditions.forEach(condition => { + const matchClass = condition.match + ? 'cl-entity--match' + : 'cl-entity--mismatch' + + cy.get(s.trainDialog.actionScorer.condition) + .contains(condition.text) + .should('have.class', matchClass) + }) + + const isActionDisabled = actionQualities.conditions.some(c => c.match === false) + if (isActionDisabled) { + cy.get(s.trainDialog.buttonSelectActionDisabled) + .should('have.class', 'is-disabled') + } + }) +} diff --git a/cypress/support/selectors.ts b/cypress/support/selectors.ts index 9c8ad98f..4a858713 100644 --- a/cypress/support/selectors.ts +++ b/cypress/support/selectors.ts @@ -68,6 +68,7 @@ const selectors = { buttonCreateEntity: '[data-testid="action-button-create-entity"]', buttonCancel: '[data-testid="action-creator-cancel-button"]', buttonDelete: '[data-testid="action-creator-delete-button"]', + buttonAddCondition: '[data-testid="action-creator-modal-button-add-condition"]', inputResponse: '[data-testid="action-text-response"] [contenteditable="true"]', inputRequiredConditions: '[data-testid="action-required-entities"] input', inputDisqualifiedConditions: '[data-testid="action-disqualifying-entities"] input', @@ -89,6 +90,18 @@ const selectors = { buttonConfirm: '[data-testid="action-delete-confirm"]', buttonCancel: '[data-testid="confirm-cancel-modal-cancel"]', }, + addConditionModal: { + modal: '[data-testid="condition-creator-modal-title"]', + buttonCreate: '[data-testid="condition-creator-button-create"]', + buttonCancel: '[data-testid="condition-creator-button-cancel"]', + buttonUseCondition: '[data-testid="condition-creator-modal-button-use-condition"]', + dropdownEntity: '[data-testid="condition-creator-modal-dropdown-entity"]', + dropdownOperator: '[data-testid="condition-creator-modal-dropdown-operator"]', + dropdownEnumValue: '[data-testid="condition-creator-modal-dropdown-enumvalue"]', + inputNumberValue: '[data-testid="condition-creator-modal-dropdown-numbervalue"] input', + existingCondition: '[data-testid="condition-creator-modal-existing-condition"]', + existingConditionMatch: '[data-testid="condition-creator-modal-existing-condition-match"]', + }, confirmCancelModal: { buttonCancel: '[data-testid="confirm-cancel-modal-cancel"]', buttonConfirm: '[data-testid="confirm-cancel-modal-accept"]', @@ -104,14 +117,18 @@ const selectors = { inputWebChat: 'input[placeholder="Type your message..."]', buttonScoreActions: '[data-testid="score-actions-button"]', buttonAbandon: '[data-testid="edit-dialog-modal-abandon-delete-button"]', + // TODO: Shouldn't have different test ids for enabled / disabled buttonSelectAction: '[data-testid="action-scorer-button-clickable"]', + buttonSelectActionDisabled: '[data-testid="action-scorer-button-no-click"]', buttonSave: '[data-testid="edit-teach-dialog-close-save-button"]', actionScorerSetEntityActions: '[data-testid="action-scorer-action-set-entity"]', actionScorerTextActions: '[data-testid="action-scorer-text-response"]', actionScorer: { + enumActions: '[data-testid="action-scorer-action-set-entity"]', rowField: '[data-automationid="DetailsRowFields"]', buttonCreate: '[data-testid="action-scorer-add-action-button"]', buttonSelected: '[data-testid="action-scorer-button-selected"]', + condition: '[data-testid="action-scorer-entities"]', }, }, mergeModal: { @@ -189,6 +206,7 @@ const selectors = { messageFromMeException: '.wc-border-error-from-me', messageColorException: '.wc-message-color-exception', messageDownArrow: '.wc-message-downarrow', + activities: '[data-testid="web-chat-utterances"]', buttonAddAction: '[data-testid="chat-edit-add-bot-response-button"]', buttonAddInput: '[data-testid="chat-edit-add-user-input-button"]', buttonBranch: '[data-testid="edit-dialog-modal-branch-button"]', diff --git a/package-lock.json b/package-lock.json index ceb07d1c..f93cb847 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,15 +102,13 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "optional": true + "dev": true }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, - "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -129,7 +127,6 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -313,7 +310,6 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, - "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -326,7 +322,6 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -392,8 +387,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "optional": true + "dev": true }, "is-glob": { "version": "4.0.0", @@ -410,7 +404,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, - "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -420,7 +413,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -431,8 +423,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true, - "optional": true + "dev": true }, "micromatch": { "version": "3.1.10", @@ -1782,9 +1773,9 @@ } }, "@conversationlearner/models": { - "version": "0.213.0", - "resolved": "https://registry.npmjs.org/@conversationlearner/models/-/models-0.213.0.tgz", - "integrity": "sha512-ijln5kokjdtCmvsNvoEC9ZFH15QT1xm/P2QrlIrcfMLoHDWvsu2uFKHc9+xdPrYMoLo09SQzdRLIk60riPIZ7w==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@conversationlearner/models/-/models-0.214.0.tgz", + "integrity": "sha512-WsMjHLCaXb1UtSkRLOWP4AL2X04RTIvR6G8crH9/qagxk0IExpwCReRtnGCTnmkz9AnlO62t6XN1nJaJpVaiCw==", "requires": { "jest-resolve": "^24.8.0" }, @@ -7751,8 +7742,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -7770,13 +7760,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7789,18 +7777,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -7903,8 +7888,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -7914,7 +7898,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7927,20 +7910,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7949,8 +7929,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -7965,7 +7944,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8038,8 +8016,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -8049,7 +8026,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -8125,8 +8101,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -8156,7 +8131,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8174,7 +8148,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8221,13 +8194,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, diff --git a/package.json b/package.json index f81c173d..83b8566d 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "private": true, "dependencies": { - "@conversationlearner/models": "0.213.0", + "@conversationlearner/models": "0.214.0", "@conversationlearner/webchat": "0.157.0", "@cypress/webpack-preprocessor": "4.0.3", "adaptivecards": "1.2.0", diff --git a/src/Utils/actionCondition.ts b/src/Utils/actionCondition.ts new file mode 100644 index 00000000..d04a7068 --- /dev/null +++ b/src/Utils/actionCondition.ts @@ -0,0 +1,142 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ +import * as CLM from '@conversationlearner/models' +import * as OF from 'office-ui-fabric-react' + +export interface IConditionalTag extends OF.ITag { + condition: CLM.Condition | null +} + +export const conditionDisplay: Record = { + [CLM.ConditionType.EQUAL]: '==', + [CLM.ConditionType.NOT_EQUAL]: '!=', + [CLM.ConditionType.GREATER_THAN]: '>', + [CLM.ConditionType.GREATER_THAN_OR_EQUAL]: '>=', + [CLM.ConditionType.LESS_THAN]: '<', + [CLM.ConditionType.LESS_THEN_OR_EQUAL]: '<=', +} + +export const getEnumConditionName = (entity: CLM.EntityBase, enumValue: CLM.EnumValue): string => { + return `${entity.entityName} == ${enumValue.enumValue}` +} + +export const getValueConditionName = (entity: CLM.EntityBase, condition: CLM.Condition): string => { + return `${entity.entityName} ${conditionDisplay[condition.condition]} ${condition.value}` +} + +export const convertConditionToConditionalTag = (condition: CLM.Condition, entities: CLM.EntityBase[]): IConditionalTag => { + const entity = entities.find(e => e.entityId === condition.entityId) + if (!entity) { + throw new Error(`Condition refers to non-existent Entity ${condition.entityId}`) + } + + let conditionalTag: IConditionalTag + if (entity.entityType === CLM.EntityType.ENUM) { + if (!entity.enumValues) { + throw new Error(`Condition refers to Entity without Enums ${entity.entityName}`) + } + + const enumValueId = condition.valueId + if (!enumValueId) { + throw new Error(`Condition refers to enum entity: ${entity.entityName}, but condition did not have enum value id.`) + } + const enumValue = entity.enumValues.find(e => e.enumValueId === enumValueId) + if (!enumValue) { + throw new Error(`Condition refers to non-existent EnumValue: ${enumValueId} on enum entity: ${entity.entityName}`) + } + + const name = getEnumConditionName(entity, enumValue) + conditionalTag = { + key: enumValue.enumValueId!, + name, + condition, + } + } + else { + const name = getValueConditionName(entity, condition) + const key = CLM.hashText(name) + conditionalTag = { + key, + name, + condition, + } + } + + return conditionalTag +} + +export const isConditionEqual = (conditionA: CLM.Condition, conditionB: CLM.Condition): boolean => { + return conditionA.entityId === conditionB.entityId + && conditionA.condition === conditionB.condition + && conditionA.valueId === conditionB.valueId + && conditionA.value === conditionB.value +} + + +/** + * Given memory value, + * If entity is multivalue returns the number of labels/values + * If entity has number resolution return number from label + * Otherwise, return undefined to indicate number can't be parsed from memory + */ +export const findNumberFromMemory = (memory: CLM.Memory, isMultivalue: boolean): number | undefined => { + if (isMultivalue) { + return memory.entityValues.length + } + + const valueString: string | undefined = memory + && memory.entityValues[0] + && (memory.entityValues[0].resolution ? true : undefined) + && (memory.entityValues[0].resolution as any).value as string + + const value = valueString + ? parseInt(valueString) + : undefined + + return value +} + +export const isValueConditionTrue = (condition: CLM.Condition, numberValue: number): boolean => { + let isTrue = false + + if (condition.value) { + isTrue = (condition.condition === CLM.ConditionType.EQUAL && numberValue == condition.value) + || (condition.condition === CLM.ConditionType.NOT_EQUAL && numberValue != condition.value) + || (condition.condition === CLM.ConditionType.GREATER_THAN && numberValue > condition.value) + || (condition.condition === CLM.ConditionType.GREATER_THAN_OR_EQUAL && numberValue >= condition.value) + || (condition.condition === CLM.ConditionType.LESS_THAN && numberValue < condition.value) + || (condition.condition === CLM.ConditionType.LESS_THEN_OR_EQUAL && numberValue <= condition.value) + } + + return isTrue +} + +export const isEnumConditionTrue = (condition: CLM.Condition, memory: CLM.Memory): boolean => { + const enumValueId = memory + && memory.entityValues[0] + && memory.entityValues[0].enumValueId + + return condition.valueId !== undefined + && condition.valueId === enumValueId +} + +export const getUniqueConditions = (actions: CLM.ActionBase[]): CLM.Condition[] => { + const allConditions = actions + .map(a => [...a.requiredConditions, ...a.negativeConditions]) + .reduce((a, b) => [...a, ...b], []) + + const uniqueConditions = allConditions + .reduce((conditions, condition) => { + const matchingCondition = conditions.find(c => isConditionEqual(c, condition)) + // If no identical condition was found, condition is unique, add it to list + if (!matchingCondition) { + conditions.push(condition) + } + + return conditions + }, []) + + return uniqueConditions +} diff --git a/src/components/ActionDetailsList.tsx b/src/components/ActionDetailsList.tsx index cb6cbf0f..1bce03e0 100644 --- a/src/components/ActionDetailsList.tsx +++ b/src/components/ActionDetailsList.tsx @@ -19,6 +19,7 @@ import { injectIntl, InjectedIntl, InjectedIntlProps } from 'react-intl' import { FM } from '../react-intl-messages' import './ActionDetailsList.css' import { autobind } from 'core-decorators' +import { getValueConditionName, getEnumConditionName } from '../Utils/actionCondition' interface ComponentState { columns: IRenderableColumn[] @@ -229,10 +230,10 @@ function getActionPayloadRenderer(action: CLM.ActionBase, component: ActionDetai if (action.actionType === CLM.ActionTypes.TEXT) { const textAction = new CLM.TextAction(action) return () + textAction={textAction} + entities={component.props.entities} + showMissingEntities={false} + />) } else if (action.actionType === CLM.ActionTypes.API_LOCAL) { const apiAction = new CLM.ApiAction(action) @@ -299,34 +300,37 @@ function renderConditions(entityIds: string[], conditions: CLM.Condition[], allE ]) } - const elements: JSX.Element[] = [] - entityIds.forEach(entityId => { + const elementsForEntityIds = entityIds.map(entityId => { const entity = allEntities.find(e => e.entityId === entityId) - if (!entity) { - elements.push(renderCondition(`Error - Missing Entity ID: ${entityId}`, isRequired)) - } - else { - elements.push(renderCondition(entity.entityName, isRequired)) - } + const name = !entity + ? `Error - Missing Entity ID: ${entityId}` + : entity.entityName + + return renderCondition(name, isRequired) }) - if (conditions) { - conditions.forEach(condition => { + + const elementsFromConditions = conditions + .map(condition => { const entity = allEntities.find(e => e.entityId === condition.entityId) + + let name: string if (!entity) { - elements.push(renderCondition(`Error - Missing Entity ID: ${condition.entityId}`, isRequired)) + name = `Error - Missing Entity ID: ${condition.entityId}` } - else { + else if (condition.valueId) { const enumValue = entity.enumValues ? entity.enumValues.find(eid => eid.enumValueId === condition.valueId) : undefined - if (!enumValue) { - elements.push(renderCondition(`Error - Missing Enum: ${condition.valueId}`, isRequired)) - } - else { - elements.push(renderCondition(`${entity.entityName} = ${enumValue.enumValue}`, isRequired)) - } + name = !enumValue + ? `Error - Missing Enum: ${condition.valueId}` + : getEnumConditionName(entity, enumValue) + } + else { + name = getValueConditionName(entity, condition) } + + return renderCondition(name, isRequired) }) - } - return elements + + return [...elementsForEntityIds, ...elementsFromConditions] } function getColumns(intl: InjectedIntl): IRenderableColumn[] { @@ -456,6 +460,7 @@ function getColumns(intl: InjectedIntl): IRenderableColumn[] { name: Util.formatMessageId(intl, FM.ACTIONDETAILSLIST_COLUMNS_ISTERMINAL), fieldName: 'isTerminal', minWidth: 50, + maxWidth: 50, isResizable: false, getSortValue: action => action.isTerminal ? 'a' : 'b', render: action => diff --git a/src/components/modals/ActionCreatorEditor.css b/src/components/modals/ActionCreatorEditor.css index fc69cfdc..a52e7429 100644 --- a/src/components/modals/ActionCreatorEditor.css +++ b/src/components/modals/ActionCreatorEditor.css @@ -13,4 +13,9 @@ .cl-action-creator-form { display: grid; grid-gap: 0.5em; +} + +.cl-tag-item-suggestion--add-condition { + background-color: white; + width: 100%; } \ No newline at end of file diff --git a/src/components/modals/ActionCreatorEditor.tsx b/src/components/modals/ActionCreatorEditor.tsx index 7c49ae12..fbd88506 100644 --- a/src/components/modals/ActionCreatorEditor.tsx +++ b/src/components/modals/ActionCreatorEditor.tsx @@ -13,6 +13,7 @@ import * as ActionPayloadEditor from './ActionPayloadEditor' import Plain from 'slate-plain-serializer' import actions from '../../actions' import ActionDeleteModal from './ActionDeleteModal' +import ConditionCreatorModal from './ConditionCreatorModal' import ConfirmCancelModal from './ConfirmCancelModal' import EntityCreatorEditor from './EntityCreatorEditor' import AdaptiveCardViewer from './AdaptiveCardViewer/AdaptiveCardViewer' @@ -32,9 +33,25 @@ import { injectIntl, InjectedIntlProps } from 'react-intl' import { FM } from '../../react-intl-messages' import './ActionCreatorEditor.css' import { autobind } from 'core-decorators'; +import { TagItemSuggestion } from 'office-ui-fabric-react' +import { IConditionalTag, getEnumConditionName, convertConditionToConditionalTag, isConditionEqual, getUniqueConditions } from 'src/Utils/actionCondition' const TEXT_SLOT = '#TEXT_SLOT#' +const addConditionPlaceholder: OF.ITag = { + key: 'addConditionPlaceholder', + name: 'Add Condition', +} +const addConditionPlaceholderButton = ( +
+ +
+) + const convertEntityToDropdownOption = (entity: CLM.EntityBase): OF.IDropdownOption => ({ key: entity.entityId, @@ -80,37 +97,6 @@ const convertEntityIdsToTags = (ids: string[], entities: CLM.EntityBase[], expan .reduce((a, b) => [...a, ...b], []) } -const toConditionName = (entity: CLM.EntityBase, enumValue: CLM.EnumValue): string => { - return `${entity.entityName} = ${enumValue.enumValue}` -} - -const convertConditionalsToTags = (conditions: CLM.Condition[], entities: CLM.EntityBase[]): IConditionalTag[] => { - const tags: IConditionalTag[] = [] - conditions.forEach(c => { - const entity = entities.find(e => e.entityId === c.entityId) - if (!entity) { - console.log(`ERROR: Condition refers to non-existent Entity ${c.entityId}`) - } - else if (!entity.enumValues) { - console.log(`ERROR: Condition refers to Entity without Enums ${entity.entityName}`) - } - else { - const enumValue = entity.enumValues.find(e => e.enumValueId === c.valueId) - if (!enumValue) { - console.log(`ERROR: Condition refers to non-existent EnumValue: ${c.valueId}`) - } - else { - tags.push({ - key: c.valueId, - name: toConditionName(entity, enumValue), - condition: c - }) - } - } - }) - return tags -} - /** * If entity is ENUM convert each value into EQUAL condition * Otherwise convert with no condition @@ -126,7 +112,7 @@ const convertEntityToConditionalTags = (entity: CLM.EntityBase, expand = true): const conditionalTags = entity.enumValues.map(enumValue => ({ key: enumValue.enumValueId!, - name: toConditionName(entity, enumValue), + name: getEnumConditionName(entity, enumValue), condition: { entityId: entity.entityId, valueId: enumValue.enumValueId!, @@ -145,13 +131,33 @@ const convertEntityToConditionalTags = (entity: CLM.EntityBase, expand = true): } // Entities that can be chosen for required / blocking -const conditionalEntityTags = (entities: CLM.EntityBase[]): IConditionalTag[] => { - return entities +const conditionalEntityTags = (entities: CLM.EntityBase[], actions: CLM.ActionBase[]): IConditionalTag[] => { + // Might have duplicates since different actions can have same conditions + const actionConditionTags = actions + .map(a => [...a.requiredConditions, ...a.negativeConditions]) + .reduce((a, b) => [...a, ...b], []) + .map(c => convertConditionToConditionalTag(c, entities)) + + const entityTags = entities // Ignore resolvers and negative entities .filter(e => !e.doNotMemorize && !e.positiveId) // Convert each entity to condition tag .map(e => convertEntityToConditionalTags(e)) .reduce((a, b) => [...a, ...b], []) + + const uniqueConditionTags = [ + ...entityTags, + ...actionConditionTags, + ].reduce((conditions, condition) => { + const isConditionDuplicate = conditions.some(c => c.key === condition.key) + if (!isConditionDuplicate) { + conditions.push(condition) + } + + return conditions + }, []) + + return uniqueConditionTags } // Entities that can be picked as expected entity @@ -241,9 +247,7 @@ const actionTypeOptions = (Object.values(CLM.ActionTypes) as string[]) type SlateValueMap = { [slot: string]: ActionPayloadEditor.SlateValue } -interface IConditionalTag extends OF.ITag { - condition: CLM.Condition | null -} + interface ComponentState { entityOptions: OF.IDropdownOption[] @@ -251,6 +255,7 @@ interface ComponentState { apiOptions: OF.IDropdownOption[] renderOptions: OF.IDropdownOption[] cardOptions: OF.IDropdownOption[] + conditionCreatorType: "required" | "disqualified" selectedEntityOptionKey: string | undefined selectedEnumValueOptionKey: string | undefined selectedApiOptionKey: string | number | undefined @@ -261,6 +266,7 @@ interface ComponentState { isEditing: boolean isEntityEditorModalOpen: boolean isCardViewerModalOpen: boolean + isConditionCreatorModalOpen: boolean isConfirmDeleteModalOpen: boolean isConfirmDeleteInUseModalOpen: boolean isConfirmEditModalOpen: boolean @@ -275,8 +281,8 @@ interface ComponentState { availableConditionalTags: OF.ITag[] expectedEntityTags: OF.ITag[] requiredEntityTagsFromPayload: OF.ITag[] - requiredEntityTags: IConditionalTag[] - negativeEntityTags: IConditionalTag[] + requiredConditionTags: IConditionalTag[] + negativeConditionTags: IConditionalTag[] slateValuesMap: SlateValueMap secondarySlateValuesMap: SlateValueMap isTerminal: boolean @@ -291,6 +297,7 @@ const initialState: Readonly = { apiOptions: [], renderOptions: [], cardOptions: [], + conditionCreatorType: "required", selectedEntityOptionKey: undefined, selectedEnumValueOptionKey: undefined, selectedApiOptionKey: undefined, @@ -301,6 +308,7 @@ const initialState: Readonly = { isEditing: false, isEntityEditorModalOpen: false, isCardViewerModalOpen: false, + isConditionCreatorModalOpen: false, isConfirmDeleteModalOpen: false, isConfirmDeleteInUseModalOpen: false, isConfirmEditModalOpen: false, @@ -315,8 +323,8 @@ const initialState: Readonly = { availableConditionalTags: [], expectedEntityTags: [], requiredEntityTagsFromPayload: [], - requiredEntityTags: [], - negativeEntityTags: [], + requiredConditionTags: [], + negativeConditionTags: [], slateValuesMap: { [TEXT_SLOT]: Plain.deserialize('') }, @@ -336,7 +344,7 @@ class ActionCreatorEditor extends React.Component { } initProps(): ComponentState { - const { entities, botInfo } = this.props + const { actions, entities, botInfo } = this.props const apiOptions = botInfo.callbacks.map(convertCallbackToOption) const cardOptions = botInfo.templates.map(convertTemplateToOption) const entityOptions = entities @@ -349,7 +357,7 @@ class ActionCreatorEditor extends React.Component { apiOptions, cardOptions, availableExpectedEntityTags: availableExpectedEntityTags(entities), - availableConditionalTags: conditionalEntityTags(entities), + availableConditionalTags: conditionalEntityTags(entities, actions).sort((a, b) => a.name.localeCompare(b.name)), isEditing: !!this.props.action } } @@ -373,7 +381,7 @@ class ActionCreatorEditor extends React.Component { newState = { ...newState, availableExpectedEntityTags: availableExpectedEntityTags(this.props.entities), - availableConditionalTags: conditionalEntityTags(this.props.entities), + availableConditionalTags: conditionalEntityTags(this.props.entities, this.props.actions).sort((a, b) => a.name.localeCompare(b.name)), } } @@ -401,7 +409,7 @@ class ActionCreatorEditor extends React.Component { const action = this.props.action const payloadOptions = prevProps.entities.map(convertEntityToOption) - const negativeEntityTags = convertEntityIdsToTags(action.negativeEntities, this.props.entities, false) + const negativeConditionTags = convertEntityIdsToTags(action.negativeEntities, this.props.entities, false) const expectedEntityTags = convertEntityIdsToTags((action.suggestedEntity ? [action.suggestedEntity] : []), this.props.entities) let selectedApiOptionKey: string | undefined let selectedCardOptionKey: string | undefined @@ -485,15 +493,17 @@ class ActionCreatorEditor extends React.Component { return [...entities, ...newEntities.filter(ne => !entities.some(e => e.key === ne.key))] }, []) - const requiredEntityTags = convertEntityIdsToTags(action.requiredEntities, this.props.entities, false) + const requiredConditionTags = convertEntityIdsToTags(action.requiredEntities, this.props.entities, false) .filter(t => !requiredEntityTagsFromPayload.some(tag => tag.key === t.key)) if (action.requiredConditions) { - requiredEntityTags.push(...convertConditionalsToTags(action.requiredConditions, prevProps.entities)) + const tags = action.requiredConditions.map(c => convertConditionToConditionalTag(c, prevProps.entities)) + requiredConditionTags.push(...tags) } if (action.negativeConditions) { - negativeEntityTags.push(...convertConditionalsToTags(action.negativeConditions, prevProps.entities)) + const tags = action.negativeConditions.map(c => convertConditionToConditionalTag(c, prevProps.entities)) + negativeConditionTags.push(...tags) } newState = { @@ -506,9 +516,9 @@ class ActionCreatorEditor extends React.Component { slateValuesMap, secondarySlateValuesMap, expectedEntityTags, - negativeEntityTags, + negativeConditionTags, requiredEntityTagsFromPayload, - requiredEntityTags, + requiredConditionTags, isTerminal: action.isTerminal, reprompt: action.repromptActionId !== undefined, isEntryNode: action.isEntryNode, @@ -538,8 +548,8 @@ class ActionCreatorEditor extends React.Component { || this.areSlateValuesChanged(this.state.secondarySlateValuesMap, initialEditState.secondarySlateValuesMap) const expectedEntitiesChanged = !initialEditState || !this.areTagsIdentical(this.state.expectedEntityTags, initialEditState.expectedEntityTags) - const requiredEntitiesChanged = !initialEditState || !this.areTagsIdentical(this.state.requiredEntityTags, initialEditState.requiredEntityTags) - const disqualifyingEntitiesChanged = !initialEditState || !this.areTagsIdentical(this.state.negativeEntityTags, initialEditState.negativeEntityTags) + const requiredEntitiesChanged = !initialEditState || !this.areTagsIdentical(this.state.requiredConditionTags, initialEditState.requiredConditionTags) + const disqualifyingEntitiesChanged = !initialEditState || !this.areTagsIdentical(this.state.negativeConditionTags, initialEditState.negativeConditionTags) const isTerminalChanged = initialEditState.isTerminal !== this.state.isTerminal const isRepromptChanged = initialEditState.reprompt !== this.state.reprompt @@ -577,8 +587,8 @@ class ActionCreatorEditor extends React.Component { // If a good card match exists switch to card view const bestCard = this.bestCardMatch(props.importedAction) if (bestCard) { - let index = actionTypeOptions.findIndex(ao => ao.key === CLM.ActionTypes.CARD) - await this.onChangeActionType(actionTypeOptions[index]) + const actionTypeOption = actionTypeOptions.find(ao => ao.key === CLM.ActionTypes.CARD) + await this.onChangeActionType(actionTypeOption) } } } @@ -927,10 +937,10 @@ class ActionCreatorEditor extends React.Component { throw new Error(`When attempting to submit action, the selected action type: ${this.state.selectedActionTypeOptionKey} did not have matching type`) } - const requiredTags = this.state.requiredEntityTags.filter(t => !t.condition) - const negativeTags = this.state.negativeEntityTags.filter(t => !t.condition) - const requiredConditions = this.state.requiredEntityTags.filter(t => t.condition).map(t => t.condition!) - const negativeConditions = this.state.negativeEntityTags.filter(t => t.condition).map(t => t.condition!) + const requiredTags = this.state.requiredConditionTags.filter(t => !t.condition) + const negativeTags = this.state.negativeConditionTags.filter(t => !t.condition) + const requiredConditions = this.state.requiredConditionTags.filter(t => t.condition).map(t => t.condition!) + const negativeConditions = this.state.negativeConditionTags.filter(t => t.condition).map(t => t.condition!) // TODO: This should be new type such as ActionInput for creation only. const action = new CLM.ActionBase({ @@ -1050,6 +1060,47 @@ class ActionCreatorEditor extends React.Component { } } + @autobind + onClickCreateConditionCreator(condition: CLM.Condition) { + const conditionalTag = convertConditionToConditionalTag(condition, this.props.entities) + + if (this.state.conditionCreatorType === "required") { + const isUnique = !this.state.requiredConditionTags + .filter(ct => ct.condition) + // condition guaranteed to exist based on filter above + .some(ct => isConditionEqual(ct.condition!, condition)) + + if (isUnique) { + this.setState(prevState => ({ + requiredConditionTags: [...prevState.requiredConditionTags, conditionalTag], + })) + } + } + else if (this.state.conditionCreatorType === "disqualified") { + const isUnique = !this.state.negativeConditionTags + .filter(ct => ct.condition) + // condition guaranteed to exist based on filter above + .some(ct => isConditionEqual(ct.condition!, condition)) + + if (isUnique) { + this.setState(prevState => ({ + negativeConditionTags: [...prevState.negativeConditionTags, conditionalTag], + })) + } + } + + this.setState({ + isConditionCreatorModalOpen: false, + }) + } + + @autobind + onClickCancelConditionCreator() { + this.setState({ + isConditionCreatorModalOpen: false, + }) + } + @autobind onCancelDeleteInUse() { this.setState({ @@ -1168,8 +1219,8 @@ class ActionCreatorEditor extends React.Component { secondarySlateValuesMap: {}, expectedEntityTags: [], requiredEntityTagsFromPayload: [], - requiredEntityTags: [], - negativeEntityTags: [], + requiredConditionTags: [], + negativeConditionTags: [], }) // If have preset text, pick the best matching card @@ -1201,7 +1252,7 @@ class ActionCreatorEditor extends React.Component { return getSuggestedTags( filterText, this.state.availableExpectedEntityTags, - [...selectedTags, ...this.state.requiredEntityTagsFromPayload, ...this.state.requiredEntityTags] + [...selectedTags, ...this.state.requiredEntityTagsFromPayload, ...this.state.requiredConditionTags] ) } @@ -1215,28 +1266,43 @@ class ActionCreatorEditor extends React.Component { const newExpectedEntityTag = tags[0] this.setState(prevState => ({ expectedEntityTags: tags, - negativeEntityTags: (!newExpectedEntityTag || prevState.negativeEntityTags.some(tag => tag.key === newExpectedEntityTag.key)) - ? prevState.negativeEntityTags - : [...prevState.negativeEntityTags, newExpectedEntityTag] + negativeConditionTags: (!newExpectedEntityTag || prevState.negativeConditionTags.some(tag => tag.key === newExpectedEntityTag.key)) + ? prevState.negativeConditionTags + : [...prevState.negativeConditionTags, newExpectedEntityTag] })) } - onResolveRequiredEntityTags = (filterText: string, selectedTags: OF.ITag[]): OF.ITag[] => { - return getSuggestedTags( + onResolveRequiredConditionTags = (filterText: string, selectedTags: OF.ITag[]): OF.ITag[] => { + const suggestedTags = getSuggestedTags( filterText, this.state.availableConditionalTags, - [...selectedTags, ...this.state.requiredEntityTagsFromPayload, ...this.state.negativeEntityTags, ...this.state.expectedEntityTags], - this.state.requiredEntityTags + [...selectedTags, ...this.state.requiredEntityTagsFromPayload, ...this.state.negativeConditionTags, ...this.state.expectedEntityTags], + this.state.requiredConditionTags ) + + return [ + addConditionPlaceholder, + ...suggestedTags, + ] } - onChangeRequiredEntityTags = (tags: IConditionalTag[]) => { + onChangeRequiredConditionTags = (tags: IConditionalTag[]) => { + const containsAddConditionPlaceholder = tags.some(t => t.key === addConditionPlaceholder.key) + // Assume if list has this item the user clicked the suggestion and intends to add condition + if (containsAddConditionPlaceholder) { + this.setState({ + isConditionCreatorModalOpen: true, + conditionCreatorType: "required", + }) + return + } + this.setState({ - requiredEntityTags: tags + requiredConditionTags: tags }) } - onRenderRequiredEntityTag = (props: ICLPickerItemProps): JSX.Element => { + onRenderRequiredConditionTag = (props: ICLPickerItemProps): JSX.Element => { const renderProps = { ...props } const locked = this.state.requiredEntityTagsFromPayload.some(t => t.key === props.key) @@ -1248,21 +1314,43 @@ class ActionCreatorEditor extends React.Component { return {props.item.name} } - onResolveNegativeEntityTags = (filterText: string, selectedTags: OF.ITag[]): OF.ITag[] => { - return getSuggestedTags( + @autobind + onRenderConditionSuggestion(tag: OF.ITag, itemProps: OF.ISuggestionItemProps): JSX.Element { + return tag.key === addConditionPlaceholder.key + ? addConditionPlaceholderButton + : {tag.name} + } + + onResolveNegativeConditionTags = (filterText: string, selectedTags: OF.ITag[]): OF.ITag[] => { + const suggestedTags = getSuggestedTags( filterText, this.state.availableConditionalTags, - [...selectedTags, ...this.state.requiredEntityTagsFromPayload, ...this.state.requiredEntityTags] + [...selectedTags, ...this.state.requiredEntityTagsFromPayload, ...this.state.requiredConditionTags] ) + + return [ + addConditionPlaceholder, + ...suggestedTags, + ] } - onChangeNegativeEntityTags = (tags: IConditionalTag[]) => { + onChangeNegativeConditionTags = (tags: IConditionalTag[]) => { + const containsAddConditionPlaceholder = tags.some(t => t.key === addConditionPlaceholder.key) + // Assume if list has this item the user clicked the suggestion and intends to add condition + if (containsAddConditionPlaceholder) { + this.setState({ + isConditionCreatorModalOpen: true, + conditionCreatorType: "disqualified", + }) + return + } + this.setState({ - negativeEntityTags: tags + negativeConditionTags: tags }) } - onRenderNegativeEntityTag = (props: ICLPickerItemProps): JSX.Element => { + onRenderNegativeConditionTag = (props: ICLPickerItemProps): JSX.Element => { const renderProps = { ...props } const suggestedEntityKey = this.state.expectedEntityTags.length > 0 ? this.state.expectedEntityTags[0].key : null @@ -1308,7 +1396,7 @@ class ActionCreatorEditor extends React.Component { .sort((a, b) => a.name.localeCompare(b.name)) // If we added entity to a payload which was already in the list of required entities remove it to avoid duplicates. - const requiredEntityTags = this.state.requiredEntityTags.filter(tag => !requiredEntityTagsFromPayload.some(t => t.key === tag.key)) + const requiredConditionTags = this.state.requiredConditionTags.filter(tag => !requiredEntityTagsFromPayload.some(t => t.key === tag.key)) const isPayloadMissing = (this.state.selectedActionTypeOptionKey === CLM.ActionTypes.TEXT && value.document.text.length === 0) @@ -1316,7 +1404,7 @@ class ActionCreatorEditor extends React.Component { entityWarning: false, isPayloadMissing, requiredEntityTagsFromPayload, - requiredEntityTags + requiredConditionTags, } if (isSecondary) { @@ -1344,6 +1432,7 @@ class ActionCreatorEditor extends React.Component { return false } } + saveDisabled(): boolean { // SET_ENTITY Actions are immutable if (this.props.action @@ -1400,7 +1489,7 @@ class ActionCreatorEditor extends React.Component { && (this.state.cardOptions.length === 0)); // Available Mentions: All entities - expected entity - required entities from payload - disqualified entities - const unavailableTags = [...this.state.expectedEntityTags, ...this.state.negativeEntityTags] + const unavailableTags = [...this.state.expectedEntityTags, ...this.state.negativeConditionTags] const optionsAvailableForPayload = this.props.entities .filter(e => !unavailableTags.some(t => t.key === e.entityId)) // Remove negative entities (Those which have a positiveId) @@ -1420,6 +1509,8 @@ class ActionCreatorEditor extends React.Component { && this.state.selectedApiOptionKey ? this.props.botInfo.callbacks.find(t => t.name === this.state.selectedApiOptionKey) : undefined + + const uniqueConditions = getUniqueConditions(this.props.actions) return ( { data-testid="action-required-entities" nonRemovableTags={this.state.requiredEntityTagsFromPayload} nonRemoveableStrikethrough={false} - label="Required Entities" - onResolveSuggestions={this.onResolveRequiredEntityTags} - onRenderItem={this.onRenderRequiredEntityTag} + label="Required Conditions" + onResolveSuggestions={this.onResolveRequiredConditionTags} + onRenderItem={this.onRenderRequiredConditionTag} + onRenderSuggestionsItem={this.onRenderConditionSuggestion} getTextFromItem={item => item.name} - onChange={this.onChangeRequiredEntityTags} + onChange={this.onChangeRequiredConditionTags} pickerSuggestionsProps={ { - suggestionsHeaderText: 'Entities', - noResultsFoundText: 'No Entities Found' + suggestionsHeaderText: 'Conditions', + noResultsFoundText: 'No Conditions Found' } } - selectedItems={this.state.requiredEntityTags} + selectedItems={this.state.requiredConditionTags} tipType={ToolTip.TipType.ACTION_REQUIRED} onDismiss={this.onDismissPicker} /> @@ -1717,18 +1809,19 @@ class ActionCreatorEditor extends React.Component {
item.name} - onChange={this.onChangeNegativeEntityTags} + onChange={this.onChangeNegativeConditionTags} pickerSuggestionsProps={ { - suggestionsHeaderText: 'Entities', - noResultsFoundText: 'No Entities Found' + suggestionsHeaderText: 'Conditions', + noResultsFoundText: 'No Conditions Found' } } - selectedItems={this.state.negativeEntityTags} + selectedItems={this.state.negativeConditionTags} tipType={ToolTip.TipType.ACTION_NEGATIVE} onDismiss={this.onDismissPicker} /> @@ -1748,7 +1841,7 @@ class ActionCreatorEditor extends React.Component { label={Util.formatMessageId(intl, FM.ACTIONCREATOREDITOR_CHECKBOX_REPROMPT_LABEL)} checked={this.state.reprompt} onChange={this.onChangeRepromptCheckbox} - style={{ marginTop: '1em', display: 'inline-block' }} + style={{ marginTop: '4em', display: 'inline-block' }} disabled={!this.state.isTerminal || [CLM.ActionTypes.END_SESSION, CLM.ActionTypes.SET_ENTITY, CLM.ActionTypes.DISPATCH].includes(this.state.selectedActionTypeOptionKey as CLM.ActionTypes)} tipType={ToolTip.TipType.ACTION_REPROMPT} /> @@ -1876,6 +1969,13 @@ class ActionCreatorEditor extends React.Component { : []} hideUndefined={false} /> + ); } diff --git a/src/components/modals/ActionScorer.tsx b/src/components/modals/ActionScorer.tsx index 978ef850..cce3dd20 100644 --- a/src/components/modals/ActionScorer.tsx +++ b/src/components/modals/ActionScorer.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import * as ActionPayloadRenderers from '../actionPayloadRenderers' import * as CLM from '@conversationlearner/models' -import * as OF from 'office-ui-fabric-react'; +import * as OF from 'office-ui-fabric-react' import * as Util from '../../Utils/util' import * as DialogEditing from '../../Utils/dialogEditing' import actions from '../../actions' @@ -25,6 +25,7 @@ import { injectIntl, InjectedIntl, InjectedIntlProps } from 'react-intl' import { FM } from '../../react-intl-messages' import './ActionScorer.css' import { autobind } from 'core-decorators'; +import { getValueConditionName, findNumberFromMemory, isValueConditionTrue, isEnumConditionTrue } from '../../Utils/actionCondition' const MISSING_ACTION = 'missing_action' @@ -40,6 +41,7 @@ interface IRenderableColumn extends OF.IColumn { render: (actionForRender: ActionForRender, component: ActionScorer, index: number) => React.ReactNode } + function getColumns(intl: InjectedIntl): IRenderableColumn[] { return [ { @@ -556,32 +558,66 @@ class ActionScorer extends React.Component { this.props.onActionSelected(trainScorerStep) } - isConditionMet(condition: CLM.Condition): { match: boolean, name: string } { - const entity = this.props.entities.filter(e => e.entityId === condition.entityId)[0]; + convertToScorerCondition(condition: CLM.Condition): { match: boolean, name: string } { + const entity = this.props.entities.find(e => e.entityId === condition.entityId) // If entity is null - there's a bug somewhere if (!entity) { return { match: false, name: 'ERROR' }; } - const enumValue = entity.enumValues && entity.enumValues.find(ev => ev.enumValueId === condition.valueId) - const memory = this.props.memories.filter(m => m.entityName === entity.entityName)[0]; - const match = memory !== undefined - && memory.entityValues[0] - && memory.entityValues[0].enumValueId === condition.valueId - return { match, name: `${entity.entityName} = ${enumValue ? enumValue.enumValue : "NOT FOUND"}` }; + const memory = this.props.memories.find(m => m.entityName === entity.entityName) + + // If EnumCondition + if (condition.valueId) { + const enumValue = entity.enumValues && entity.enumValues.find(ev => ev.enumValueId === condition.valueId) + const value = enumValue + ? enumValue.enumValue + : "NOT FOUND" + + const match = memory !== undefined + && isEnumConditionTrue(condition, memory) + + return { + match, + name: `${entity.entityName} == ${value}` + } + } + // If ValueCondition + else if (condition.value) { + const name = getValueConditionName(entity, condition) + let match = false + if (memory) { + const numberValue = findNumberFromMemory(memory, entity.isMultivalue) + if (numberValue) { + match = isValueConditionTrue(condition, numberValue) + } + } + + return { + match, + name + } + } + // Other conditions (StringCondition in future) + else { + return { + match: false, + name: `Unknown Condition Type` + } + } } // Check if entity is in memory and return it's name entityInMemory(entityId: string): { match: boolean, name: string } { - const entity = this.props.entities.filter(e => e.entityId === entityId)[0]; + const entity = this.props.entities.find(e => e.entityId === entityId); // If entity is null - there's a bug somewhere if (!entity) { return { match: false, name: 'ERROR' }; } - const memory = this.props.memories.filter(m => m.entityName === entity.entityName)[0]; + const memory = this.props.memories.find(m => m.entityName === entity.entityName); return { match: (memory !== undefined), name: entity.entityName }; } @@ -590,7 +626,7 @@ class ActionScorer extends React.Component { return null } - const action = this.props.actions.filter(a => a.actionId === actionId)[0]; + const action = this.props.actions.find(a => a.actionId === actionId); // If action is null - there's a bug somewhere if (!action) { @@ -620,7 +656,7 @@ class ActionScorer extends React.Component { } if (action.requiredConditions) { for (const condition of action.requiredConditions) { - const result = this.isConditionMet(condition) + const result = this.convertToScorerCondition(condition) items.push({ name: result.name, neg: false, @@ -632,7 +668,7 @@ class ActionScorer extends React.Component { } if (action.negativeConditions) { for (const condition of action.negativeConditions) { - const result = this.isConditionMet(condition) + const result = this.convertToScorerCondition(condition) items.push({ name: result.name, neg: true, @@ -710,7 +746,7 @@ class ActionScorer extends React.Component { } if (action.requiredConditions) { for (const condition of action.requiredConditions) { - const result = this.isConditionMet(condition) + const result = this.convertToScorerCondition(condition) if (!result.match) { return false } @@ -718,7 +754,7 @@ class ActionScorer extends React.Component { } if (action.negativeConditions) { for (const condition of action.negativeConditions) { - const result = this.isConditionMet(condition) + const result = this.convertToScorerCondition(condition) if (result.match) { return false } @@ -733,7 +769,7 @@ class ActionScorer extends React.Component { || !unscoredAction.reason || unscoredAction.reason === CLM.ScoreReason.NotCalculated) { - const action = this.props.actions.filter((a: CLM.ActionBase) => a.actionId === unscoredAction.actionId)[0]; + const action = this.props.actions.find(a => a.actionId === unscoredAction.actionId) // If action is null - there's a bug somewhere if (!action) { diff --git a/src/components/modals/ConditionCreatorModal.css b/src/components/modals/ConditionCreatorModal.css new file mode 100644 index 00000000..1ab26c3c --- /dev/null +++ b/src/components/modals/ConditionCreatorModal.css @@ -0,0 +1,23 @@ +.cl-condition-creator__expression { + display: grid; + grid-gap: 1em; + grid-template-columns: repeat(3, 1fr); +} + +.cl-condition-creator__existing-conditions { + display: grid; + grid-template: auto / max-content max-content min-content; + grid-gap: 1em; + align-content: center; + align-items: center; +} + +.cl-condition-creator__existing-condition { + background-color: var(--color-neutralQuaternaryAlt); + padding: 6px 10px; +} + +/** Hack to preserve white text. Do not repeat, dependency on non cl prefixed class names. */ +.ms-Suggestions-itemButton:hover .ms-Button-icon { + color: white; +} \ No newline at end of file diff --git a/src/components/modals/ConditionCreatorModal.tsx b/src/components/modals/ConditionCreatorModal.tsx new file mode 100644 index 00000000..66db018f --- /dev/null +++ b/src/components/modals/ConditionCreatorModal.tsx @@ -0,0 +1,356 @@ +import * as React from 'react' +import * as OF from 'office-ui-fabric-react' +import * as Util from '../../Utils/util' +import * as CLM from '@conversationlearner/models' +import { injectIntl, InjectedIntlProps } from 'react-intl' +import { FM } from '../../react-intl-messages' +import './ConditionCreatorModal.css' +import { Position } from 'office-ui-fabric-react/lib/utilities/positioning' +import { PreBuilts } from 'src/types' +import { conditionDisplay, convertConditionToConditionalTag, isConditionEqual } from '../../Utils/actionCondition' + +const entityIsAllowedInCondition = (entity: CLM.EntityBase): boolean => { + if (entity.entityType == CLM.EntityType.ENUM) { + return true + } + + if (entity.entityType == CLM.EntityType.LUIS + && entity.resolverType == PreBuilts.Number + && entity.isResolutionRequired == true) { + return true + } + + if (entity.isMultivalue === true) { + return true + } + + return false +} + +interface EntityOption extends OF.IDropdownOption { + data: CLM.EntityBase +} + +const convertEntityToDropdownOption = (entity: CLM.EntityBase): EntityOption => { + let secondaryInfo = entity.entityType === CLM.EntityType.ENUM + ? `enum` + : entity.isMultivalue + ? 'multi' + : 'single' + + return { + key: entity.entityId, + text: `${entity.entityName} - ${secondaryInfo}`, + data: entity, + } +} + +interface OperatorOption extends OF.IDropdownOption { + data: CLM.ConditionType +} + +const convertConditionTypesToDropdownOptions = (conditionTypes: object): OperatorOption[] => { + return Object.keys(conditionTypes) + .map((conditionType: string) => { + let conditionText = `unknown` + if (conditionDisplay && conditionDisplay[conditionType]) { + conditionText = conditionDisplay[conditionType] + } + + return { + key: conditionType, + text: conditionText, + data: conditionTypes[conditionType], + } + }) +} + +const operatorOptions = convertConditionTypesToDropdownOptions(CLM.ConditionType) +// We know EQUAL will be found since it was created from enum type in line above +const equalOperatorOption = operatorOptions.find(o => o.data == CLM.ConditionType.EQUAL)! + +interface EnumOption extends OF.IDropdownOption { + data: CLM.EnumValue +} + +// Enum here refers to Enum values on Entities +const convertEnumValueToDropdownOption = (enumValue: CLM.EnumValue): EnumOption => { + // TODO: Fix types to avoid this. EnumValues on Entities should be guaranteed to exist. + // Only don't exist during temporary creation + if (!enumValue.enumValueId) { + throw new Error(`Enum value must have id. When attempting to convert enum value to dropdown option, value did not have id. Perhaps it was not saved.`) + } + + return { + key: enumValue.enumValueId, + text: enumValue.enumValue, + data: enumValue, + } +} + +type Props = InjectedIntlProps + & { + entities: CLM.EntityBase[], + isOpen: boolean, + conditions: CLM.Condition[], + onClickCreate: (condition: CLM.Condition) => void, + onClickCancel: () => void, + } + +const Component: React.FC = (props) => { + // Entity Dropdown + const entityOptions = props.entities + .filter(entityIsAllowedInCondition) + .map(e => convertEntityToDropdownOption(e)) + .sort((a, b) => a.text.localeCompare(b.text)) + + const [selectedEntityOption, setSelectedEntityOption] = React.useState(entityOptions[0]) + React.useEffect(() => { + if (entityOptions.length > 0) { + setSelectedEntityOption(entityOptions[0]) + } + }, [props.entities]) + + const onChangeEntity = (event: React.FormEvent, option?: EntityOption | undefined, index?: number | undefined) => { + if (!option) { + return + } + + setSelectedEntityOption(option) + } + + // Operator Dropdown + const [selectedOperatorOption, setSelectedOperatorOption] = React.useState(equalOperatorOption) + const onChangeOperator = (event: React.FormEvent, option?: OperatorOption) => { + if (!option) { + return + } + + setSelectedOperatorOption(option) + } + + // Value + const [showNumberValue, setShowNumberValue] = React.useState(true) + const [numberValue, setNumberValue] = React.useState(0) + const [enumValueOptions, setEnumValueOptions] = React.useState([]) + React.useLayoutEffect(() => { + if (enumValueOptions.length > 0) { + setSelectedEnumValueOption(enumValueOptions[0]) + } + }, [enumValueOptions]) + + const [selectedEnumValueOption, setSelectedEnumValueOption] = React.useState() + const onChangeEnumValueOption = (event: React.FormEvent, option?: EnumOption) => { + if (!option) { + return + } + + setSelectedEnumValueOption(option) + } + + const [isCreateDisabled, setIsCreateDisabled] = React.useState(false) + + // If entity selected is ENUM show possible values in dropdown + // Otherwise, show number input + React.useEffect(() => { + if (!selectedEntityOption) { + return + } + + const entity = selectedEntityOption.data as CLM.EntityBase + if (entity.entityType === CLM.EntityType.ENUM && entity.enumValues) { + const enumValueOptions = entity.enumValues.map(ev => convertEnumValueToDropdownOption(ev)) + setEnumValueOptions(enumValueOptions) + // Only allow equal operator when selecting enum + setSelectedOperatorOption(equalOperatorOption) + setShowNumberValue(false) + } + else { + setShowNumberValue(true) + } + }, [selectedEntityOption]) + + // If any of inputs change, recompute validity + React.useEffect(() => { + const isValid = Boolean(selectedEntityOption) + && Boolean(selectedOperatorOption) + && (showNumberValue + || Boolean(selectedEnumValueOption)) + + setIsCreateDisabled(!isValid) + }, [selectedEntityOption, selectedOperatorOption, numberValue, selectedEnumValueOption]) + + // If modal has opened (from false to true) + React.useLayoutEffect(() => { + if (props.isOpen) { + // Reset operator and value + setSelectedOperatorOption(equalOperatorOption) + setNumberValue(0) + } + }, [props.isOpen]) + + const createConditionFromState = () => { + if (!selectedEntityOption + || !selectedOperatorOption) { + return + } + + const condition: CLM.Condition = { + entityId: selectedEntityOption.data.entityId, + condition: selectedOperatorOption.data + } + + if (showNumberValue) { + condition.value = numberValue + } + else if (selectedEnumValueOption) { + // TODO: Fix enum types + condition.valueId = selectedEnumValueOption.data.enumValueId! + } + + return condition + } + + const onClickCreate = () => { + const condition = createConditionFromState() + if (condition) { + props.onClickCreate(condition) + } + else { + console.warn(`User attempted to create condition but condition did not exist. Usually means there is bad state calculation in modal.`) + } + } + + const onClickCancel = () => { + props.onClickCancel() + } + + const onClickExistingCondition = (condition: CLM.Condition) => { + props.onClickCreate(condition) + } + + const isOperatorDisabled = selectedEntityOption && selectedEntityOption.data.entityType === CLM.EntityType.ENUM + const conditionsUsingEntity = props.conditions.filter(c => c.entityId === selectedEntityOption.key) + const currentCondition = createConditionFromState() + + return +
+ Create a Condition +
+ +
+
+ {entityOptions.length === 0 + ?

You may only create conditions on enum entities or those with resolver type number which is required. Your model does not have either type available. Please create either of these types of entities to create a condition.

+ : <> +

Current Condition:

+
+ + + {/* Little awkward to checkEnumValueOption here, but do it for type safety */} + {(showNumberValue || !selectedEnumValueOption) + ?
+ Number + ) => { + const value = parseInt(event.target.value) + if (!Number.isNaN(value)) { + setNumberValue(value) + } + }} + onDecrement={v => setNumberValue(prevValue => prevValue - 1)} + onIncrement={v => setNumberValue(prevValue => prevValue + 1)} + labelPosition={Position.bottom} + step={1} + /> +
+ : } +
+ +

Existing Conditions:

+
+ {conditionsUsingEntity.map(condition => { + const conditionalTag = convertConditionToConditionalTag(condition, props.entities) + const isActive = currentCondition + ? isConditionEqual(condition, currentCondition) + : false + + return +
+ {conditionalTag.name} +
+ + onClickExistingCondition(conditionalTag.condition!)} + > + Use Condition + + +
+ {isActive + && } +
+
+ })} +
+ + } +
+
+ +
+
+
+
+ + + +
+
+
+} + +export default injectIntl(Component) \ No newline at end of file diff --git a/src/react-intl-messages.ts b/src/react-intl-messages.ts index 97b43d80..6eade949 100644 --- a/src/react-intl-messages.ts +++ b/src/react-intl-messages.ts @@ -705,7 +705,7 @@ export default { [FM.ACTIONSCORER_COLUMNS_RESPONSE]: 'Response', [FM.ACTIONSCORER_COLUMNS_ARGUMENTS]: 'Arguments', [FM.ACTIONSCORER_COLUMNS_SCORE]: 'Score', - [FM.ACTIONSCORER_COLUMNS_ENTITIES]: 'Entities', + [FM.ACTIONSCORER_COLUMNS_ENTITIES]: 'Conditions', [FM.ACTIONSCORER_COLUMNS_ISTERMINAL]: 'Wait', [FM.ACTIONSCORER_COLUMNS_TYPE]: 'Type', @@ -717,8 +717,8 @@ export default { [FM.ACTIONDETAILSLIST_COLUMNS_RESPONSE]: 'Response', [FM.ACTIONDETAILSLIST_COLUMNS_ARGUMENTS]: 'Arguments', [FM.ACTIONDETAILSLIST_COLUMNS_TYPE]: 'Action Type', - [FM.ACTIONDETAILSLIST_COLUMNS_REQUIREDENTITIES]: 'Required Entities', - [FM.ACTIONDETAILSLIST_COLUMNS_DISQUALIFYINGENTITIES]: 'Disqualifying Entities', + [FM.ACTIONDETAILSLIST_COLUMNS_REQUIREDENTITIES]: 'Required Conditions', + [FM.ACTIONDETAILSLIST_COLUMNS_DISQUALIFYINGENTITIES]: 'Disqualifying Conditions', [FM.ACTIONDETAILSLIST_COLUMNS_SUGGESTEDENTITY]: 'Expected Entity', [FM.ACTIONDETAILSLIST_COLUMNS_ISTERMINAL]: 'Wait', [FM.ACTIONDETAILSLIST_COLUMNS_REPROMPT]: 'Reprompt', @@ -973,7 +973,7 @@ export default { [FM.TOOLTIP_ACTION_RESPONSE_ROW3]: '"Hi[, $name]"', [FM.TOOLTIP_ACTION_REQUIRED]: 'Action will not be selected unless Memory contains values for these Entities', - [FM.TOOLTIP_ACTION_REQUIRED_TITLE]: 'Required Entities', + [FM.TOOLTIP_ACTION_REQUIRED_TITLE]: 'Required Conditions', [FM.TOOLTIP_ACTION_REQUIRED_ROW1]: '"How would you like to pay?"', [FM.TOOLTIP_ACTION_REQUIRED_ROW2]: '$orderDetails $address', [FM.TOOLTIP_ACTION_REQUIRED_ROW3]: '"Hi, $name"', @@ -982,7 +982,7 @@ export default { [FM.TOOLTIP_ACTION_SCORE]: 'Score:', [FM.TOOLTIP_ACTION_SCORE_PERCENT]: 'Conversation Learner confidence in performing an Action', [FM.TOOLTIP_ACTION_SCORE_TRAINING]: `Action can't be scored yet as Conversation Learner is still training`, - [FM.TOOLTIP_ACTION_SCORE_DISQUALIFIED]: 'Action has been disqualified - Required Entities are missing or Blocked Entities are present', + [FM.TOOLTIP_ACTION_SCORE_DISQUALIFIED]: 'Action has been disqualified - Required Conditions are missing or Blocked Entities are present', [FM.TOOLTIP_ACTION_SUGGESTED]: `Hint to Conversation Learner that the user's reply to this Action will likely be a value for this Entity. Pre-Trained Entities cannot be used as Expected Entities.`, [FM.TOOLTIP_ACTION_SUGGESTED_TITLE]: 'Expected Response', @@ -1065,8 +1065,8 @@ export default { [FM.ACTIONDETAILSLIST_COLUMNS_RESPONSE]: 'Response', [FM.ACTIONDETAILSLIST_COLUMNS_ARGUMENTS]: 'Arguments', [FM.ACTIONDETAILSLIST_COLUMNS_TYPE]: 'Action Type', - [FM.ACTIONDETAILSLIST_COLUMNS_REQUIREDENTITIES]: 'Required Entities', - [FM.ACTIONDETAILSLIST_COLUMNS_DISQUALIFYINGENTITIES]: 'Disqualifying Entities', + [FM.ACTIONDETAILSLIST_COLUMNS_REQUIREDENTITIES]: 'Required Conditions', + [FM.ACTIONDETAILSLIST_COLUMNS_DISQUALIFYINGENTITIES]: 'Disqualifying Conditions', [FM.ACTIONDETAILSLIST_COLUMNS_SUGGESTEDENTITY]: 'Expected Entity', [FM.ACTIONDETAILSLIST_COLUMNS_ISTERMINAL]: 'Wait', [FM.ACTIONDETAILSLIST_COLUMNS_CREATED_DATE_TIME]: 'Created', diff --git a/src/routes/Apps/App/Settings.tsx b/src/routes/Apps/App/Settings.tsx index 18a00568..c0558739 100644 --- a/src/routes/Apps/App/Settings.tsx +++ b/src/routes/Apps/App/Settings.tsx @@ -402,6 +402,7 @@ class Settings extends React.Component { disabled={true} label='CONVERSATION_LEARNER_MODEL_ID' value={this.state.appIdVal} + readOnly={true} />
diff --git a/src/types/const.ts b/src/types/const.ts index 65686d56..1dc4ece2 100644 --- a/src/types/const.ts +++ b/src/types/const.ts @@ -61,4 +61,4 @@ export enum FeatureStrings { DISPATCHER = "DISPATCHER", } -export const fromLogTag = 'from-log' \ No newline at end of file +export const fromLogTag = 'from-log'