botium-core 1.13.3 → 1.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.13.3",
3
+ "version": "1.13.5",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "homepage": "https://www.botium.ai",
34
34
  "dependencies": {
35
- "@babel/runtime": "^7.18.6",
35
+ "@babel/runtime": "^7.19.0",
36
36
  "async": "^3.2.4",
37
37
  "body-parser": "^1.20.0",
38
38
  "boolean": "^3.2.0",
@@ -42,7 +42,7 @@
42
42
  "esprima": "^4.0.1",
43
43
  "express": "^4.18.1",
44
44
  "globby": "11.0.4",
45
- "ioredis": "^5.1.0",
45
+ "ioredis": "^5.2.3",
46
46
  "is-class": "^0.0.9",
47
47
  "is-json": "^2.0.1",
48
48
  "jsonpath": "^1.1.1",
@@ -59,13 +59,13 @@
59
59
  "rimraf": "^3.0.2",
60
60
  "sanitize-filename": "^1.6.3",
61
61
  "slugify": "^1.6.5",
62
- "socket.io": "^4.5.1",
63
- "socket.io-client": "^4.5.1",
62
+ "socket.io": "^4.5.2",
63
+ "socket.io-client": "^4.5.2",
64
64
  "socketio-auth": "^0.1.1",
65
- "swagger-jsdoc": "^6.2.1",
66
- "swagger-ui-express": "^4.4.0",
67
- "uuid": "^8.3.2",
68
- "vm2": "^3.9.10",
65
+ "swagger-jsdoc": "^6.2.5",
66
+ "swagger-ui-express": "^4.5.0",
67
+ "uuid": "^9.0.0",
68
+ "vm2": "^3.9.11",
69
69
  "word-error-rate": "0.0.7",
70
70
  "write-yaml": "^1.0.0",
71
71
  "xlsx": "^0.18.5",
@@ -73,27 +73,27 @@
73
73
  "yaml": "^2.1.1"
74
74
  },
75
75
  "devDependencies": {
76
- "@babel/core": "^7.18.6",
77
- "@babel/node": "^7.18.6",
78
- "@babel/plugin-transform-runtime": "^7.18.6",
79
- "@babel/preset-env": "^7.18.6",
76
+ "@babel/core": "^7.19.3",
77
+ "@babel/node": "^7.19.1",
78
+ "@babel/plugin-transform-runtime": "^7.19.1",
79
+ "@babel/preset-env": "^7.19.3",
80
80
  "chai": "^4.3.6",
81
81
  "chai-as-promised": "^7.1.1",
82
82
  "cross-env": "^7.0.3",
83
- "eslint": "^8.19.0",
83
+ "eslint": "^8.24.0",
84
84
  "eslint-config-standard": "^17.0.0",
85
85
  "eslint-plugin-import": "^2.26.0",
86
86
  "eslint-plugin-mocha": "^10.1.0",
87
- "eslint-plugin-n": "^15.2.4",
88
- "eslint-plugin-promise": "^6.0.0",
87
+ "eslint-plugin-n": "^15.3.0",
88
+ "eslint-plugin-promise": "^6.0.1",
89
89
  "eslint-plugin-standard": "^4.1.0",
90
90
  "license-checker": "^25.0.1",
91
91
  "license-compatibility-checker": "^0.3.5",
92
92
  "mocha": "^10.0.0",
93
- "nock": "^13.2.8",
94
- "npm-check-updates": "^15.2.6",
93
+ "nock": "^13.2.9",
94
+ "npm-check-updates": "^16.3.4",
95
95
  "nyc": "^15.1.0",
96
- "rollup": "^2.76.0",
96
+ "rollup": "^2.79.1",
97
97
  "rollup-plugin-babel": "^4.4.0",
98
98
  "rollup-plugin-commonjs": "^10.1.0",
99
99
  "rollup-plugin-json": "^4.0.0",
@@ -133,6 +133,8 @@ module.exports = {
133
133
  SCRIPTING_UTTEXPANSION_MODE: 'SCRIPTING_UTTEXPANSION_MODE',
134
134
  SCRIPTING_UTTEXPANSION_RANDOM_COUNT: 'SCRIPTING_UTTEXPANSION_RANDOM_COUNT',
135
135
  SCRIPTING_UTTEXPANSION_INCOMPREHENSION: 'SCRIPTING_UTTEXPANSION_INCOMPREHENSION',
136
+ SCRIPTING_UTTEXPANSION_INCOMPREHENSIONINTENTS: 'SCRIPTING_UTTEXPANSION_INCOMPREHENSIONINTENTS',
137
+ SCRIPTING_UTTEXPANSION_INCOMPREHENSIONUTTS: 'SCRIPTING_UTTEXPANSION_INCOMPREHENSIONUTTS',
136
138
  SCRIPTING_UTTEXPANSION_USENAMEASINTENT: 'SCRIPTING_UTTEXPANSION_USENAMEASINTENT',
137
139
  // justLineTag, utterance
138
140
  SCRIPTING_UTTEXPANSION_NAMING_MODE: 'SCRIPTING_UTTEXPANSION_NAMING_MODE',
package/src/Defaults.js CHANGED
@@ -49,6 +49,10 @@ module.exports = {
49
49
  [Capabilities.SCRIPTING_UTTEXPANSION_RANDOM_COUNT]: 1,
50
50
  [Capabilities.SCRIPTING_UTTEXPANSION_NAMING_MODE]: 'justLineTag',
51
51
  [Capabilities.SCRIPTING_UTTEXPANSION_NAMING_UTTERANCE_MAX]: '16',
52
+ [Capabilities.SCRIPTING_UTTEXPANSION_INCOMPREHENSION]: null,
53
+ [Capabilities.SCRIPTING_UTTEXPANSION_INCOMPREHENSIONINTENTS]: [],
54
+ [Capabilities.SCRIPTING_UTTEXPANSION_INCOMPREHENSIONUTTS]: [],
55
+ [Capabilities.SCRIPTING_UTTEXPANSION_USENAMEASINTENT]: false,
52
56
  [Capabilities.SCRIPTING_MEMORYEXPANSION_KEEP_ORIG]: false,
53
57
  [Capabilities.SCRIPTING_ENABLE_SKIP_ASSERT_ERRORS]: false,
54
58
  [Capabilities.SCRIPTING_FORCE_BOT_CONSUMED]: false,
@@ -4,6 +4,7 @@ class BotiumMockAsserter {
4
4
  constructor (fromJson = {}) {
5
5
  this.name = fromJson.name
6
6
  this.args = _.cloneDeep(fromJson.args)
7
+ this.not = fromJson.not
7
8
  }
8
9
  }
9
10
  class BotiumMockUserInput {
@@ -256,6 +256,7 @@ module.exports = class CompilerXlsx extends CompilerBase {
256
256
  if (!convo.header.name) {
257
257
  convo.header.name = `${convo.header.sheetname}-${this.colnames[convo.header.colindex]}${formatRowIndex(convo.header.rowindex)}`
258
258
  }
259
+ // it is not used anymore?
259
260
  convo.header.sort = convo.header.name
260
261
  scriptResults.push(convo)
261
262
  })
@@ -73,6 +73,32 @@ const SCRIPTING_FUNCTIONS_RAW = {
73
73
  return Date.now()
74
74
  },
75
75
 
76
+ $tomorrow: (pattern) => {
77
+ if (pattern) {
78
+ return moment().add(1, 'day').format(pattern)
79
+ }
80
+ return moment().add(1, 'day').toDate().toLocaleDateString()
81
+ },
82
+ $yesterday: (pattern) => {
83
+ if (pattern) {
84
+ return moment().subtract(1, 'day').format(pattern)
85
+ }
86
+ return moment().subtract(1, 'day').toDate().toLocaleDateString()
87
+ },
88
+
89
+ $date_add: (amount, unit, pattern) => {
90
+ if (pattern) {
91
+ return moment().add(amount, unit).format(pattern)
92
+ }
93
+ return moment().add(amount, unit).toDate().toLocaleDateString()
94
+ },
95
+ $date_subtract: (amount, unit, pattern) => {
96
+ if (pattern) {
97
+ return moment().subtract(amount, unit).format(pattern)
98
+ }
99
+ return moment().subtract(amount, unit).toDate().toLocaleDateString()
100
+ },
101
+
76
102
  $year: () => {
77
103
  return new Date().getFullYear()
78
104
  },
@@ -168,7 +194,8 @@ const SCRIPTING_FUNCTIONS_RAW = {
168
194
  require: false,
169
195
  env: caps[Capabilities.SECURITY_ALLOW_UNSAFE] ? process.env : {},
170
196
  sandbox: {
171
- caps
197
+ caps,
198
+ moment
172
199
  }
173
200
  })
174
201
  return vm.run(`module.exports = (${code})`)
@@ -246,7 +273,19 @@ const _apply = (scriptingMemory, str, caps, mockMsg) => {
246
273
  for (const match of matches) {
247
274
  if (match.indexOf('(') > 0) {
248
275
  const arg = match.substring(match.indexOf('(') + 1, match.lastIndexOf(')')).replace(/\\\)/g, ')')
249
- str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps, arg, mockMsg))
276
+ let args = [arg]
277
+ if (SCRIPTING_FUNCTIONS[key].numberOfArguments > 1) {
278
+ args = arg.split(',')
279
+ }
280
+ args = args.map(arg => {
281
+ arg = arg.trim()
282
+ if (arg.startsWith('"') && arg.endsWith('"')) {
283
+ return arg.substring(1, arg.length - 1)
284
+ } else {
285
+ return arg
286
+ }
287
+ })
288
+ str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps, ...args, mockMsg))
250
289
  } else {
251
290
  str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps))
252
291
  }
@@ -770,31 +770,79 @@ module.exports = class ScriptingProvider {
770
770
  this._sortConvos()
771
771
  }
772
772
 
773
- ExpandUtterancesToConvos ({ useNameAsIntent, incomprehensionUtt } = {}) {
773
+ ExpandUtterancesToConvos ({ useNameAsIntent, incomprehensionIntents, incomprehensionUtts, incomprehensionUtt } = {}) {
774
774
  const expandedConvos = []
775
775
 
776
776
  if (_.isUndefined(useNameAsIntent)) {
777
777
  useNameAsIntent = !!this.caps[Capabilities.SCRIPTING_UTTEXPANSION_USENAMEASINTENT]
778
778
  }
779
+ if (_.isUndefined(incomprehensionIntents)) {
780
+ incomprehensionIntents = this.caps[Capabilities.SCRIPTING_UTTEXPANSION_INCOMPREHENSIONINTENTS]
781
+ }
782
+ if (_.isUndefined(incomprehensionUtts)) {
783
+ incomprehensionUtts = this.caps[Capabilities.SCRIPTING_UTTEXPANSION_INCOMPREHENSIONUTTS]
784
+ }
779
785
  if (_.isUndefined(incomprehensionUtt)) {
780
786
  incomprehensionUtt = this.caps[Capabilities.SCRIPTING_UTTEXPANSION_INCOMPREHENSION]
781
787
  }
782
788
 
783
- if (useNameAsIntent && incomprehensionUtt) {
784
- throw new Error('ExpandUtterancesToConvos - SCRIPTING_UTTEXPANSION_USENAMEASINTENT and SCRIPTING_UTTEXPANSION_INCOMPREHENSION are incompatible')
789
+ if (incomprehensionUtt && (!incomprehensionUtts || incomprehensionUtts.length === 0) && !this.utterances[incomprehensionUtt]) {
790
+ throw new Error(`ExpandUtterancesToConvos - incomprehension utterance '${incomprehensionUtt}' undefined (and no user examples given)`)
785
791
  }
786
- if (incomprehensionUtt && !this.utterances[incomprehensionUtt]) {
787
- throw new Error(`ExpandUtterancesToConvos - incomprehension utterance '${incomprehensionUtt}' undefined`)
792
+ if (incomprehensionUtts && incomprehensionUtts.length > 0) {
793
+ if (!incomprehensionUtt) {
794
+ incomprehensionUtt = 'UTT_INCOMPREHENSION'
795
+ }
796
+ if (this.utterances[incomprehensionUtt]) {
797
+ this.utterances[incomprehensionUtt].utterances.push(...incomprehensionUtts)
798
+ } else {
799
+ this.utterances[incomprehensionUtt] = {
800
+ name: incomprehensionUtt,
801
+ utterances: [...incomprehensionUtts]
802
+ }
803
+ }
788
804
  }
789
805
 
790
806
  if (useNameAsIntent) {
791
807
  debug('ExpandUtterancesToConvos - Using utterance name as NLU intent')
792
- } else if (incomprehensionUtt) {
793
- debug(`ExpandUtterancesToConvos - Using incomprehension utterance expansion mode: ${incomprehensionUtt}`)
808
+ }
809
+ if (incomprehensionIntents && incomprehensionIntents.length > 0) {
810
+ debug(`ExpandUtterancesToConvos - Using ${incomprehensionIntents.length} incomprehension NLU intent(s)`)
811
+ }
812
+ if (incomprehensionUtt) {
813
+ debug(`ExpandUtterancesToConvos - Using incomprehension utterance expansion mode: ${incomprehensionUtt}, ${this.utterances[incomprehensionUtt].utterances.length} user example(s)`)
794
814
  }
795
815
 
796
816
  _.keys(this.utterances).filter(u => u !== incomprehensionUtt).forEach(uttName => {
797
817
  const utt = this.utterances[uttName]
818
+
819
+ const responseStep = {
820
+ sender: 'bot',
821
+ messageText: '',
822
+ asserters: [],
823
+ stepTag: 'Step 2 - check bot response',
824
+ not: false
825
+ }
826
+ if (useNameAsIntent) {
827
+ responseStep.asserters.push({
828
+ name: 'INTENT',
829
+ args: [utt.name]
830
+ })
831
+ }
832
+ if (incomprehensionIntents && incomprehensionIntents.length > 0) {
833
+ incomprehensionIntents.forEach(ii => {
834
+ responseStep.asserters.push({
835
+ name: 'INTENT',
836
+ args: [ii],
837
+ not: true
838
+ })
839
+ })
840
+ }
841
+ if (incomprehensionUtt) {
842
+ responseStep.messageText = incomprehensionUtt
843
+ responseStep.not = true
844
+ }
845
+
798
846
  expandedConvos.push(new Convo(this._buildScriptContext(), {
799
847
  header: {
800
848
  name: utt.name,
@@ -811,31 +859,7 @@ module.exports = class ScriptingProvider {
811
859
  messageText: utt.name,
812
860
  stepTag: 'Step 1 - tell utterance'
813
861
  },
814
- useNameAsIntent
815
- ? {
816
- sender: 'bot',
817
- asserters: [
818
- {
819
- name: 'INTENT',
820
- args: [utt.name]
821
- }
822
- ],
823
- stepTag: 'Step 2 - check intent',
824
- not: false
825
- }
826
- : incomprehensionUtt
827
- ? {
828
- sender: 'bot',
829
- messageText: incomprehensionUtt,
830
- stepTag: 'Step 2 - check incomprehension',
831
- not: true
832
- }
833
- : {
834
- sender: 'bot',
835
- messageText: '',
836
- stepTag: 'Step 2 - check bot response',
837
- not: false
838
- }
862
+ responseStep
839
863
  ],
840
864
  sourceTag: Object.assign({}, utt.sourceTag, { origUttName: utt.name })
841
865
  }))
@@ -844,27 +868,68 @@ module.exports = class ScriptingProvider {
844
868
  this._sortConvos()
845
869
  }
846
870
 
847
- ExpandConvos () {
871
+ ExpandConvos (options = {}) {
872
+ options = Object.assign({
873
+ // use skip and keep, or justHeader
874
+ justHeader: false,
875
+ // drop unwanted convos
876
+ convoFilter: null
877
+ }, options)
878
+ const context = { count: 0 }
848
879
  const expandedConvos = []
849
880
  debug(`ExpandConvos - Using utterances expansion mode: ${this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE]}`)
850
881
  this.convos.forEach((convo) => {
851
882
  convo.expandPartialConvos()
852
- this._expandConvo(expandedConvos, convo)
883
+ for (const expanded of this._expandConvo(convo, options, context)) {
884
+ expanded.header.assertionCount = this.GetAssertionCount(expanded)
885
+ if (options.justHeader) {
886
+ const ConvoWithOnlyHeader = {
887
+ header: {
888
+ name: expanded.header.name,
889
+ assertionCount: expanded.header.assertionCount
890
+ }
891
+ }
892
+ expandedConvos.push(ConvoWithOnlyHeader)
893
+ } else {
894
+ expandedConvos.push(expanded)
895
+ }
896
+ }
853
897
  })
854
898
  this.convos = expandedConvos
855
- this._sortConvos()
899
+ if (!options.justHeader) {
900
+ this._sortConvos()
901
+ } else {
902
+ this._updateConvos()
903
+ }
904
+ }
905
+
906
+ ExpandConvosIterable (options = {}) {
907
+ options = Object.assign({
908
+ // drop unwanted convos
909
+ convoFilter: null
910
+ }, options)
911
+ debug(`ExpandConvos - Using utterances expansion mode: ${this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE]}`)
912
+ // creating a nested generator, calling the other.
913
+ // We hope this.convos does not changes while this iterator is used
914
+ const _convosIterable = function * (options) {
915
+ const context = { count: 0 }
916
+ for (const convo of this.convos) {
917
+ convo.expandPartialConvos()
918
+ yield * this._expandConvo(convo, options, context)
919
+ }
920
+ }.bind(this)
921
+
922
+ this.convosIterable = _convosIterable(options)
856
923
  }
857
924
 
858
925
  /**
859
- *
860
- * @param expandedConvos
926
+ * This is a generator function with yield
861
927
  * @param currentConvo
862
928
  * @param convoStepIndex
863
929
  * @param convoStepsStack list of ConvoSteps
864
- * @param context {width: }
865
930
  * @private
866
931
  */
867
- _expandConvo (expandedConvos, currentConvo, convoStepIndex = 0, convoStepsStack = [], context = {}) {
932
+ * _expandConvo (currentConvo, options, context, convoStepIndex = 0, convoStepsStack = []) {
868
933
  const utterancePostfix = (lineTag, uttOrUserInput) => {
869
934
  const naming = this.caps[Capabilities.SCRIPTING_UTTEXPANSION_NAMING_MODE] || Defaults.capabilities[Capabilities.SCRIPTING_UTTEXPANSION_NAMING_MODE]
870
935
  if (naming === 'justLineTag') {
@@ -884,7 +949,7 @@ module.exports = class ScriptingProvider {
884
949
  if (currentStep.sender === 'bot' || currentStep.sender === 'begin' || currentStep.sender === 'end') {
885
950
  const currentStepsStack = convoStepsStack.slice()
886
951
  currentStepsStack.push(_.cloneDeep(currentStep))
887
- this._expandConvo(expandedConvos, currentConvo, convoStepIndex + 1, currentStepsStack, context)
952
+ yield * this._expandConvo(currentConvo, options, context, convoStepIndex + 1, currentStepsStack)
888
953
  } else if (currentStep.sender === 'me') {
889
954
  let useUnexpanded = true
890
955
  if (currentStep.messageText) {
@@ -901,28 +966,32 @@ module.exports = class ScriptingProvider {
901
966
  }
902
967
  if (this.utterances[uttName]) {
903
968
  const allutterances = this.utterances[uttName].utterances
904
- const processSampleUtterances = (sampleutterances, myContext) => {
905
- sampleutterances.forEach((utt, index) => {
906
- processSampleUtterance(utt, sampleutterances.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context))
907
- })
969
+ const processSampleUtterances = function * (sampleutterances, myContext) {
970
+ for (let index = 0; index < sampleutterances.length; index++) {
971
+ yield * processSampleUtterance(sampleutterances[index], sampleutterances.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context))
972
+ }
908
973
  }
909
- const processSampleUtterance = (sampleutterance, length, index, myContext) => {
910
- const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
974
+ const processSampleUtterance = function * (sampleutterance, length, index, myContext) {
911
975
  const currentStepsStack = convoStepsStack.slice()
912
976
  if (uttArgs) {
913
977
  sampleutterance = util.format(sampleutterance, ...uttArgs)
914
978
  }
915
979
  currentStepsStack.push(Object.assign(_.cloneDeep(currentStep), { messageText: sampleutterance }))
916
980
  const currentConvoLabeled = _.cloneDeep(currentConvo)
917
- Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${uttName}-${utterancePostfix(lineTag, sampleutterance)}` })
981
+ if (length > 1) {
982
+ const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
983
+ Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${uttName}-${utterancePostfix(lineTag, sampleutterance)}` })
984
+ }
918
985
  if (!currentConvoLabeled.sourceTag) currentConvoLabeled.sourceTag = {}
919
986
  if (!currentConvoLabeled.sourceTag.origConvoName) currentConvoLabeled.sourceTag.origConvoName = currentConvo.header.name
920
- this._expandConvo(expandedConvos, currentConvoLabeled, convoStepIndex + 1, currentStepsStack, myContext || context)
921
- }
922
- if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
987
+ yield * this._expandConvo(currentConvoLabeled, options, myContext || context, convoStepIndex + 1, currentStepsStack)
988
+ }.bind(this)
989
+ if (allutterances.length === 1) {
990
+ yield * processSampleUtterances([allutterances[0]], context)
991
+ } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
923
992
  if (_.isNil(context.indexExpansionModeWidth)) {
924
993
  // executed for the first found utterance
925
- processSampleUtterances(allutterances, Object.assign({}, context, { indexExpansionModeWidth: allutterances.length }))
994
+ yield * processSampleUtterances(allutterances, Object.assign({}, context, { indexExpansionModeWidth: allutterances.length }))
926
995
  } else {
927
996
  if (_.isNil(context.indexExpansionModeIndex)) {
928
997
  throw new Error('indexExpansionModeIndex must be set!')
@@ -933,58 +1002,67 @@ module.exports = class ScriptingProvider {
933
1002
  debug(`While expanding convos by index found in utterance "${uttName}" less examples (${allutterances.length}) as expected (${context.indexExpansionModeWidth})`)
934
1003
  }
935
1004
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: Math.max(allutterances.length, context.indexExpansionModeWidth) })
936
- processSampleUtterance(allutterances[localIndex], allutterances.length, localIndex, myContext)
1005
+ yield * processSampleUtterance(allutterances[localIndex], allutterances.length, localIndex, myContext)
937
1006
  if (allutterances.length > context.indexExpansionModeWidth && context.indexExpansionModeIndex + 1 === context.indexExpansionModeWidth) {
938
1007
  debug(`While expanding convos by index found in utterance "${uttName}" more examples (${allutterances.length}) as expected (${context.indexExpansionModeWidth})`)
939
1008
  for (let i = context.indexExpansionModeWidth; i < allutterances.length; i++) {
940
1009
  // if we found a utterance with more examples as any utterances before, we have to start new 'thread'
941
1010
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: allutterances.length, indexExpansionModeIndex: i })
942
- processSampleUtterance(allutterances[i], allutterances.length, i, myContext)
1011
+ yield * processSampleUtterance(allutterances[i], allutterances.length, i, myContext)
943
1012
  }
944
1013
  }
945
1014
  }
946
1015
  } else {
947
1016
  if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'first') {
948
- processSampleUtterances([allutterances[0]])
1017
+ yield * processSampleUtterances([allutterances[0]], context)
949
1018
  } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'random') {
950
- processSampleUtterances(allutterances
1019
+ yield * processSampleUtterances(allutterances
951
1020
  .map(x => ({ x, r: Math.random() }))
952
1021
  .sort((a, b) => a.r - b.r)
953
1022
  .map(a => a.x)
954
- .slice(0, this.caps[Capabilities.SCRIPTING_UTTEXPANSION_RANDOM_COUNT]))
1023
+ .slice(0, this.caps[Capabilities.SCRIPTING_UTTEXPANSION_RANDOM_COUNT]), context)
955
1024
  } else {
956
- processSampleUtterances(allutterances)
1025
+ yield * processSampleUtterances(allutterances, context)
957
1026
  }
958
1027
  }
959
1028
  useUnexpanded = false
960
1029
  }
961
1030
  }
962
1031
  if (currentStep.userInputs && currentStep.userInputs.length > 0) {
963
- currentStep.userInputs.forEach((ui, uiIndex) => {
1032
+ for (let uiIndex = 0; uiIndex < currentStep.userInputs.length; uiIndex++) {
1033
+ const ui = currentStep.userInputs[uiIndex]
964
1034
  const userInput = this.userInputs[ui.name]
965
1035
  if (userInput && userInput.expandConvo) {
966
1036
  const expandedUserInputs = userInput.expandConvo({ convo: currentConvo, convoStep: currentStep, args: ui.args })
967
1037
  if (expandedUserInputs && expandedUserInputs.length > 0) {
968
1038
  // let sampleinputs = expandedUserInputs
969
- const processSampleInputs = (sampleinputs, myContext, uiIndex) => {
970
- sampleinputs.forEach((input, index) => {
971
- processSampleInput(input, sampleinputs.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context), uiIndex)
972
- })
1039
+ const processSampleInputs = function * (sampleinputs, myContext, uiIndex) {
1040
+ for (let index = 0; index < sampleinputs.length; index++) {
1041
+ yield * processSampleInput(sampleinputs[index], sampleinputs.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context), uiIndex)
1042
+ }
973
1043
  }
974
- const processSampleInput = (sampleinput, length, index, myContext, uiIndex) => {
975
- const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
1044
+ const processSampleInput = function * (sampleinput, length, index, myContext, uiIndex) {
976
1045
  const currentStepsStack = convoStepsStack.slice()
977
1046
  const currentStepMod = _.cloneDeep(currentStep)
978
1047
  currentStepMod.userInputs[uiIndex] = sampleinput
979
1048
 
980
1049
  currentStepsStack.push(currentStepMod)
981
1050
  const currentConvoLabeled = _.cloneDeep(currentConvo)
982
- Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${ui.name}-${utterancePostfix(lineTag, (sampleinput.args && sampleinput.args.length) ? sampleinput.args.join(', ') : 'no-args')}` })
983
- this._expandConvo(expandedConvos, currentConvoLabeled, convoStepIndex + 1, currentStepsStack, myContext || context)
984
- }
985
- if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
1051
+ if (length > 1) {
1052
+ if (sampleinput.convoPostfix) {
1053
+ Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${ui.name}-${sampleinput.convoPostfix}` })
1054
+ } else {
1055
+ const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
1056
+ Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${ui.name}-${utterancePostfix(lineTag, (sampleinput.args && sampleinput.args.length) ? sampleinput.args.join(', ') : 'no-args')}` })
1057
+ }
1058
+ }
1059
+ yield * this._expandConvo(currentConvoLabeled, options, myContext || context, convoStepIndex + 1, currentStepsStack)
1060
+ }.bind(this)
1061
+ if (expandedUserInputs.length === 1) {
1062
+ yield * processSampleInputs([expandedUserInputs[0]], context, uiIndex)
1063
+ } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
986
1064
  if (_.isNil(context.indexExpansionModeWidth)) {
987
- processSampleInputs(expandedUserInputs, Object.assign({}, context, { indexExpansionModeWidth: expandedUserInputs.length }), uiIndex)
1065
+ yield * processSampleInputs(expandedUserInputs, Object.assign({}, context, { indexExpansionModeWidth: expandedUserInputs.length }), uiIndex)
988
1066
  } else {
989
1067
  if (_.isNil(context.indexExpansionModeIndex)) {
990
1068
  throw new Error('indexExpansionModeIndex must be set!')
@@ -995,20 +1073,20 @@ module.exports = class ScriptingProvider {
995
1073
  debug(`While expanding convos by index found user input "${ui.name}, ${ui.args}" less examples (${expandedUserInputs.length}) as expected (${context.indexExpansionModeWidth})`)
996
1074
  }
997
1075
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: Math.max(expandedUserInputs.length, context.indexExpansionModeWidth) })
998
- processSampleInput(expandedUserInputs[localIndex], expandedUserInputs.length, localIndex, myContext, uiIndex)
1076
+ yield * processSampleInput(expandedUserInputs[localIndex], expandedUserInputs.length, localIndex, myContext, uiIndex)
999
1077
  if (expandedUserInputs.length > context.indexExpansionModeWidth && context.indexExpansionModeIndex + 1 === context.indexExpansionModeWidth) {
1000
1078
  debug(`While expanding convos by index found user input "${ui.name}, ${ui.args}" more examples (${expandedUserInputs.length}) as expected (${context.indexExpansionModeWidth})`)
1001
1079
  for (let i = context.indexExpansionModeWidth; i < expandedUserInputs.length; i++) {
1002
1080
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: expandedUserInputs.length, indexExpansionModeIndex: i })
1003
- processSampleInput(expandedUserInputs[i], expandedUserInputs.length, i, myContext, uiIndex)
1081
+ yield * processSampleInput(expandedUserInputs[i], expandedUserInputs.length, i, myContext, uiIndex)
1004
1082
  }
1005
1083
  }
1006
1084
  }
1007
1085
  } else {
1008
1086
  if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'first') {
1009
- processSampleInputs([expandedUserInputs[0]], context, uiIndex)
1087
+ yield * processSampleInputs([expandedUserInputs[0]], context, uiIndex)
1010
1088
  } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'random') {
1011
- processSampleInputs(expandedUserInputs
1089
+ yield * processSampleInputs(expandedUserInputs
1012
1090
  .map(x => ({
1013
1091
  x,
1014
1092
  r: Math.random()
@@ -1017,35 +1095,49 @@ module.exports = class ScriptingProvider {
1017
1095
  .map(a => a.x)
1018
1096
  .slice(0, this.caps[Capabilities.SCRIPTING_UTTEXPANSION_RANDOM_COUNT]), context, uiIndex)
1019
1097
  } else {
1020
- processSampleInputs(expandedUserInputs, context, uiIndex)
1098
+ yield * processSampleInputs(expandedUserInputs, context, uiIndex)
1021
1099
  }
1022
1100
  }
1023
1101
  useUnexpanded = false
1024
1102
  }
1025
1103
  }
1026
- })
1104
+ }
1027
1105
  }
1028
1106
  if (useUnexpanded) {
1029
1107
  const currentStepsStack = convoStepsStack.slice()
1030
1108
  currentStepsStack.push(_.cloneDeep(currentStep))
1031
- this._expandConvo(expandedConvos, currentConvo, convoStepIndex + 1, currentStepsStack, context)
1109
+ yield * this._expandConvo(currentConvo, options, context, convoStepIndex + 1, currentStepsStack)
1032
1110
  }
1033
1111
  }
1034
1112
  } else {
1035
- expandedConvos.push(Object.assign(_.cloneDeep(currentConvo), { conversation: _.cloneDeep(convoStepsStack) }))
1113
+ const expanded = Object.assign(_.cloneDeep(currentConvo), { conversation: _.cloneDeep(convoStepsStack) })
1114
+ if (!options.convoFilter || options.convoFilter(expanded)) {
1115
+ context.count++
1116
+ const logPerEntry = context.count < 10 ? 1 : context.count < 100 ? 10 : context.count < 1000 ? 100 : context.count < 10000 ? 1000 : 10000
1117
+ if (context.count % logPerEntry === 0) {
1118
+ debug(`Convo #${context.count} expanded (${expanded.header.name})`)
1119
+ }
1120
+ yield expanded
1121
+ }
1036
1122
  }
1037
1123
  }
1038
1124
 
1039
1125
  _sortConvos () {
1040
1126
  this.convos = _.sortBy(this.convos, [(convo) => convo.header.sort || convo.header.name])
1127
+ this._updateConvos()
1128
+ }
1129
+
1130
+ _updateConvos () {
1041
1131
  let i = 0
1042
1132
  this.convos.forEach((convo) => {
1043
- convo.header.order = ++i
1044
- if (!convo.header.projectname) {
1045
- convo.header.projectname = this.caps[Capabilities.PROJECTNAME]
1046
- }
1047
- if (!convo.header.testsessionname) {
1048
- convo.header.testsessionname = this.caps[Capabilities.TESTSESSIONNAME]
1133
+ if (convo) {
1134
+ convo.header.order = ++i
1135
+ if (!convo.header.projectname) {
1136
+ convo.header.projectname = this.caps[Capabilities.PROJECTNAME]
1137
+ }
1138
+ if (!convo.header.testsessionname) {
1139
+ convo.header.testsessionname = this.caps[Capabilities.TESTSESSIONNAME]
1140
+ }
1049
1141
  }
1050
1142
  })
1051
1143
  }
@@ -1056,7 +1148,11 @@ module.exports = class ScriptingProvider {
1056
1148
  } else if (convos) {
1057
1149
  this.convos.push(convos)
1058
1150
  }
1059
- this._sortConvos()
1151
+ if (this.convos.filter(c => _.isNil(c))) {
1152
+ this._updateConvos()
1153
+ } else {
1154
+ this._sortConvos()
1155
+ }
1060
1156
  }
1061
1157
 
1062
1158
  AddUtterances (utterances) {
@@ -1310,4 +1406,30 @@ module.exports = class ScriptingProvider {
1310
1406
  ...lines,
1311
1407
  '}'].join('\r\n')
1312
1408
  }
1409
+
1410
+ GetAssertionCount (convo) {
1411
+ if (!convo) {
1412
+ return 0
1413
+ }
1414
+ let counter = 0
1415
+ for (const step of convo.conversation) {
1416
+ if (step.sender === 'bot') {
1417
+ let stepCounter = step.asserters ? step.asserters.length : 0
1418
+ if (step.messageText) {
1419
+ stepCounter++
1420
+ }
1421
+ stepCounter = stepCounter === 0 ? 1 : stepCounter
1422
+ counter += stepCounter
1423
+ }
1424
+ }
1425
+
1426
+ if (convo.convoBegin && convo.convoBegin.asserters) {
1427
+ counter += convo.convoBegin.asserters.length
1428
+ }
1429
+
1430
+ if (convo.convoEnd && convo.convoEnd.asserters) {
1431
+ counter += convo.convoEnd.asserters.length
1432
+ }
1433
+ return counter === 0 ? 1 : counter
1434
+ }
1313
1435
  }
@@ -437,6 +437,8 @@ const convoStepToLines = (step) => {
437
437
  lines.push('MEDIA ' + step.media[0].mediaUri)
438
438
  } else if (step.messageText) {
439
439
  lines.push(step.messageText)
440
+ } else if (step.sourceData) {
441
+ lines.push(JSON.stringify(step.sourceData, null, 2))
440
442
  }
441
443
  step.userInputs && step.userInputs.forEach((userInput) => {
442
444
  lines.push(userInput.name + _formatAppendArgs(userInput.args))