diff --git a/packages/lu/src/parser/cross-train/crossTrainer.js b/packages/lu/src/parser/cross-train/crossTrainer.js index 53eded8bc..0ca769685 100644 --- a/packages/lu/src/parser/cross-train/crossTrainer.js +++ b/packages/lu/src/parser/cross-train/crossTrainer.js @@ -439,8 +439,8 @@ const qnaCrossTrainCore = function (luResource, qnaResource, fileName, interrupt qnaSectionContent += `> !# @qna.pair.source = ${qnaSection.source}${NEWLINE}${NEWLINE}` } - if (qnaSection.Id) { - qnaSectionContent += `${NEWLINE}${NEWLINE}` + if (qnaSection.QAPairId) { + qnaSectionContent += `${NEWLINE}${NEWLINE}` } qnaSectionContent += `# ? ${Array.from(new Set(qnaSection.Questions)).join(NEWLINE + '- ')}${NEWLINE}${NEWLINE}**Filters:**${NEWLINE}- ${qnaSection.FilterPairs.map(f => f.key + '=' + f.value).join(NEWLINE + '- ')}${NEWLINE}${NEWLINE}\`\`\`${NEWLINE}${qnaSection.Answer}${NEWLINE}\`\`\`` diff --git a/packages/lu/src/parser/lufile/luParser.js b/packages/lu/src/parser/lufile/luParser.js index e2ad2d063..92cbd8ca0 100644 --- a/packages/lu/src/parser/lufile/luParser.js +++ b/packages/lu/src/parser/lufile/luParser.js @@ -124,7 +124,7 @@ class LUParser { })) } - this.extractIntentBody(sections, content) + this.extractSectionBody(sections, content) return new LUResource(sections, content, errors); } @@ -300,11 +300,14 @@ class LUParser { * @param {any[]} sections * @param {string} content */ - static extractIntentBody(sections, content) { + static extractSectionBody(sections, content) { sections.sort((a, b) => a.ParseTree.start.line - b.ParseTree.start.line) const originList = content.split(/\r?\n/) + let qnaSectionIndex = 0 sections.forEach(function (section, index) { - if (section.SectionType === SectionType.SIMPLEINTENTSECTION || section.SectionType === SectionType.NESTEDINTENTSECTION) { + if (section.SectionType === SectionType.SIMPLEINTENTSECTION + || section.SectionType === SectionType.NESTEDINTENTSECTION + || section.SectionType === SectionType.QNASECTION) { const startLine = section.ParseTree.start.line - 1 let stopLine if (index + 1 < sections.length) { @@ -316,13 +319,21 @@ class LUParser { stopLine = originList.length } - const destList = originList.slice(startLine + 1, stopLine) + let destList + if (section.SectionType === SectionType.QNASECTION) { + destList = originList.slice(startLine, stopLine) + section.Id = qnaSectionIndex + qnaSectionIndex++ + } else { + destList = originList.slice(startLine + 1, stopLine) + } + section.Body = destList.join(NEWLINE) section.StartLine = startLine section.StopLine = stopLine - 1 if (section.SectionType === SectionType.NESTEDINTENTSECTION) { - LUParser.extractIntentBody(section.SimpleIntentSections, originList.slice(0, stopLine).join(NEWLINE)) + LUParser.extractSectionBody(section.SimpleIntentSections, originList.slice(0, stopLine).join(NEWLINE)) } } else { section.StartLine = section.ParseTree.start.line diff --git a/packages/lu/src/parser/lufile/parseFileContents.js b/packages/lu/src/parser/lufile/parseFileContents.js index 5a4f93f90..a2a4d51fa 100644 --- a/packages/lu/src/parser/lufile/parseFileContents.js +++ b/packages/lu/src/parser/lufile/parseFileContents.js @@ -1817,8 +1817,8 @@ const parseAndHandleQnaSection = function (parsedContent, luResource) { let qnas = luResource.Sections.filter(s => s.SectionType === SectionType.QNASECTION); if (qnas && qnas.length > 0) { for (const qna of qnas) { - if (qna.Id) { - qna.Id = parseInt(qna.Id); + if (qna.QAPairId) { + qna.QAPairId = parseInt(qna.QAPairId); } let questions = qna.Questions; // detect if any question is a reference @@ -1844,7 +1844,7 @@ const parseAndHandleQnaSection = function (parsedContent, luResource) { context.prompts.push(new qnaPrompt(prompt.displayText, prompt.linkedQuestion, undefined, contextOnly, idx)); }) } - parsedContent.qnaJsonStructure.qnaList.push(new qnaListObj(qna.Id || 0, answer.trim(), qna.source, questions, metadata, context)); + parsedContent.qnaJsonStructure.qnaList.push(new qnaListObj(qna.QAPairId || 0, answer.trim(), qna.source, questions, metadata, context)); } } } diff --git a/packages/lu/src/parser/lufile/qnaSection.js b/packages/lu/src/parser/lufile/qnaSection.js index 27775f2f6..ff35dc012 100644 --- a/packages/lu/src/parser/lufile/qnaSection.js +++ b/packages/lu/src/parser/lufile/qnaSection.js @@ -1,7 +1,6 @@ const QnaSectionContext = require('./generated/LUFileParser').LUFileParser.QnaSectionContext; const LUSectionTypes = require('./../utils/enums/lusectiontypes'); const BuildDiagnostic = require('./diagnostic').BuildDiagnostic; -const helpers = require('../utils/helpers'); const QNA_GENERIC_SOURCE = "custom editorial"; class QnaSection { @@ -24,14 +23,14 @@ class QnaSection { this.prompts = result.promptDefinitions; this.promptsText = result.promptTextList; this.Errors = this.Errors.concat(result.errors); - this.Id = this.ExtractAssignedId(parseTree); + this.QAPairId = this.ExtractAssignedId(parseTree); this.source = this.ExtractSourceInfo(parseTree); } ExtractSourceInfo(parseTree) { let srcAssignment = parseTree.qnaDefinition().qnaSourceInfo() if (srcAssignment) { - let srcRegExp = new RegExp(/^[ ]*\>[ ]*!#[ ]*@qna.pair.source[ ]*=[ ]*(?.*?)$/gmi); + let srcRegExp = /^[ ]*\>[ ]*!#[ ]*@qna.pair.source[ ]*=[ ]*(?.*?)$/gmi; let srcParsed = srcRegExp.exec(srcAssignment.getText().trim()); return srcParsed.groups.sourceInfo || QNA_GENERIC_SOURCE; } @@ -41,7 +40,7 @@ class QnaSection { ExtractAssignedId(parseTree) { let idAssignment = parseTree.qnaDefinition().qnaIdMark() if (idAssignment) { - let idTextRegExp = new RegExp(/^\.*?)[\"\'][ ]*>[ ]*\<\/a\>$/gmi); + let idTextRegExp = /^\.*?)[\"\'][ ]*>[ ]*\<\/a\>$/gmi; let idTextParsed = idTextRegExp.exec(idAssignment.getText().trim()); return idTextParsed.groups.idCaptured || undefined; } @@ -70,7 +69,7 @@ class QnaSection { let filterLineText = promptLine.getText().trim(); filterLineText = filterLineText.substr(1).trim(); promptTextList.push(filterLineText); - let promptConfigurationRegExp = new RegExp(/^\[(?.*?)]\([ ]*\#[ ]*[ ?]*(?.*?)\)[ ]*(?\`context-only\`)?.*?$/gmi); + let promptConfigurationRegExp = /^\[(?.*?)]\([ ]*\#[ ]*[ ?]*(?.*?)\)[ ]*(?\`context-only\`)?.*?$/gmi; let splitLine = promptConfigurationRegExp.exec(filterLineText); if (!splitLine) { errors.push(BuildDiagnostic({ diff --git a/packages/lu/src/parser/lufile/sectionOperator.js b/packages/lu/src/parser/lufile/sectionOperator.js index 6793ff962..559e5ee9c 100644 --- a/packages/lu/src/parser/lufile/sectionOperator.js +++ b/packages/lu/src/parser/lufile/sectionOperator.js @@ -49,15 +49,25 @@ class SectionOperator { return luParser.parse(newContent); } - replaceRangeContent(originString, startLine, stopLine, replaceString) { - - if (!originString) { - throw new Error('replace content with error parameters.'); + insertSection(id, sectionContent) { + sectionContent = helpers.sanitizeNewLines(sectionContent); + const section = this.Luresource.Sections.find(u => u.Id === id); + if (!section && this.Luresource.Sections.length > 0 ) { + return this.Luresource; } + const startLine = section ? section.StartLine : 0; + const newContent = this.replaceRangeContent(this.Luresource.Content, startLine, startLine - 1, sectionContent); + + const result = luParser.parse(newContent); + + return result; + } + + replaceRangeContent(originString, startLine, stopLine, replaceString) { const originList = originString.split(/\r?\n/); let destList = []; - if (isNaN(startLine) || isNaN(stopLine) || startLine < 0 || startLine > stopLine || originList.length <= stopLine) { + if (isNaN(startLine) || isNaN(stopLine) || startLine < 0 || startLine > stopLine + 1 || originList.length <= stopLine) { throw new Error("index out of range."); } diff --git a/packages/lu/test/parser/lufile/sectionapi.test.js b/packages/lu/test/parser/lufile/sectionapi.test.js index 3a596414b..239fa89f7 100644 --- a/packages/lu/test/parser/lufile/sectionapi.test.js +++ b/packages/lu/test/parser/lufile/sectionapi.test.js @@ -293,4 +293,113 @@ describe('Section CRUD tests for error import in utterances', () => { assert.equal(luresource.Sections[1].Name, 'Cancel') assert.equal(luresource.Sections[1].Body, '- cancel that') }); +}); + +describe('Section CRUD tests for qna', () => { + let luresource = undefined; + + let fileContent = +`# ? who is CEO of Microsoft +- Microsoft CEO + +\`\`\` +Satya Nadella +\`\`\``; + + let addedFileContent = +`# ? what about the weather of Seattle +- how about the weather of Seattle + +\`\`\` +warm and rainy +\`\`\`` + + let updatedFileConent = +`# ? who is CEO of Facebook +- Facebook CEO + +\`\`\` +Mark Zuckerberg +\`\`\``; + + let insertFileContent = +`# ? how to greet + +\`\`\` +hello +\`\`\`` + + it('add qna section test', () => { + luresource = luparser.parse(fileContent); + + assert.equal(luresource.Errors.length, 0); + assert.equal(luresource.Sections.length, 1); + assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), fileContent); + + luresource = new SectionOperator(luresource).addSection(addedFileContent); + + assert.equal(luresource.Errors.length, 0); + assert.equal(luresource.Sections.length, 2); + assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), addedFileContent); + }); + + it('update qna section test', () => { + luresource = new SectionOperator(luresource).updateSection(luresource.Sections[0].Id, updatedFileConent); + + assert.equal(luresource.Errors.length, 0); + assert.equal(luresource.Sections.length, 2); + assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), updatedFileConent); + }); + + it('insert qna section at begining test', () => { + luresource = new SectionOperator(luresource).insertSection(luresource.Sections[0].Id, insertFileContent); + + assert.equal(luresource.Errors.length, 0); + assert.equal(luresource.Sections.length, 3); + assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[2].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), insertFileContent); + assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), updatedFileConent); + assert.equal(luresource.Sections[2].Body.replace(/\r\n/g,"\n"), addedFileContent); + }); + + it('delete qna section test', () => { + luresource = new SectionOperator(luresource).deleteSection(luresource.Sections[0].Id); + + assert.equal(luresource.Errors.length, 0); + assert.equal(luresource.Sections.length, 2); + assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), updatedFileConent); + assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), addedFileContent); + }); + + it('insert qna section at middle test', () => { + luresource = new SectionOperator(luresource).insertSection(luresource.Sections[1].Id, insertFileContent); + + assert.equal(luresource.Errors.length, 0); + assert.equal(luresource.Sections.length, 3); + assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[2].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), updatedFileConent); + assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), insertFileContent); + assert.equal(luresource.Sections[2].Body.replace(/\r\n/g,"\n"), addedFileContent); + }); + + it('insert qna section at empty file', () => { + luresource = luparser.parse(''); + luresource = new SectionOperator(luresource).insertSection(0, insertFileContent); + + assert.equal(luresource.Errors.length, 0); + assert.equal(luresource.Sections.length, 1); + assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION); + assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), insertFileContent + '\n'); + }); }); \ No newline at end of file