Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 3323a48

Browse files
CRUD optimizations for qna sections (#854)
* support qna section CRUD * reduce dup code * change qna section Id to QAPairId * typo * fix RegExp not work in client env * add insertSection API * support insert at an empty file Co-authored-by: Vishwac Sena Kannan <[email protected]>
1 parent 86f0302 commit 3323a48

File tree

6 files changed

+149
-20
lines changed

6 files changed

+149
-20
lines changed

packages/lu/src/parser/cross-train/crossTrainer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,8 @@ const qnaCrossTrainCore = function (luResource, qnaResource, fileName, interrupt
439439
qnaSectionContent += `> !# @qna.pair.source = ${qnaSection.source}${NEWLINE}${NEWLINE}`
440440
}
441441

442-
if (qnaSection.Id) {
443-
qnaSectionContent += `<a id = "${qnaSection.Id}"></a>${NEWLINE}${NEWLINE}`
442+
if (qnaSection.QAPairId) {
443+
qnaSectionContent += `<a id = "${qnaSection.QAPairId}"></a>${NEWLINE}${NEWLINE}`
444444
}
445445

446446
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}\`\`\``

packages/lu/src/parser/lufile/luParser.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class LUParser {
124124
}))
125125
}
126126

127-
this.extractIntentBody(sections, content)
127+
this.extractSectionBody(sections, content)
128128

129129
return new LUResource(sections, content, errors);
130130
}
@@ -300,11 +300,14 @@ class LUParser {
300300
* @param {any[]} sections
301301
* @param {string} content
302302
*/
303-
static extractIntentBody(sections, content) {
303+
static extractSectionBody(sections, content) {
304304
sections.sort((a, b) => a.ParseTree.start.line - b.ParseTree.start.line)
305305
const originList = content.split(/\r?\n/)
306+
let qnaSectionIndex = 0
306307
sections.forEach(function (section, index) {
307-
if (section.SectionType === SectionType.SIMPLEINTENTSECTION || section.SectionType === SectionType.NESTEDINTENTSECTION) {
308+
if (section.SectionType === SectionType.SIMPLEINTENTSECTION
309+
|| section.SectionType === SectionType.NESTEDINTENTSECTION
310+
|| section.SectionType === SectionType.QNASECTION) {
308311
const startLine = section.ParseTree.start.line - 1
309312
let stopLine
310313
if (index + 1 < sections.length) {
@@ -316,13 +319,21 @@ class LUParser {
316319
stopLine = originList.length
317320
}
318321

319-
const destList = originList.slice(startLine + 1, stopLine)
322+
let destList
323+
if (section.SectionType === SectionType.QNASECTION) {
324+
destList = originList.slice(startLine, stopLine)
325+
section.Id = qnaSectionIndex
326+
qnaSectionIndex++
327+
} else {
328+
destList = originList.slice(startLine + 1, stopLine)
329+
}
330+
320331
section.Body = destList.join(NEWLINE)
321332
section.StartLine = startLine
322333
section.StopLine = stopLine - 1
323334

324335
if (section.SectionType === SectionType.NESTEDINTENTSECTION) {
325-
LUParser.extractIntentBody(section.SimpleIntentSections, originList.slice(0, stopLine).join(NEWLINE))
336+
LUParser.extractSectionBody(section.SimpleIntentSections, originList.slice(0, stopLine).join(NEWLINE))
326337
}
327338
} else {
328339
section.StartLine = section.ParseTree.start.line

packages/lu/src/parser/lufile/parseFileContents.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,8 +1817,8 @@ const parseAndHandleQnaSection = function (parsedContent, luResource) {
18171817
let qnas = luResource.Sections.filter(s => s.SectionType === SectionType.QNASECTION);
18181818
if (qnas && qnas.length > 0) {
18191819
for (const qna of qnas) {
1820-
if (qna.Id) {
1821-
qna.Id = parseInt(qna.Id);
1820+
if (qna.QAPairId) {
1821+
qna.QAPairId = parseInt(qna.QAPairId);
18221822
}
18231823
let questions = qna.Questions;
18241824
// detect if any question is a reference
@@ -1844,7 +1844,7 @@ const parseAndHandleQnaSection = function (parsedContent, luResource) {
18441844
context.prompts.push(new qnaPrompt(prompt.displayText, prompt.linkedQuestion, undefined, contextOnly, idx));
18451845
})
18461846
}
1847-
parsedContent.qnaJsonStructure.qnaList.push(new qnaListObj(qna.Id || 0, answer.trim(), qna.source, questions, metadata, context));
1847+
parsedContent.qnaJsonStructure.qnaList.push(new qnaListObj(qna.QAPairId || 0, answer.trim(), qna.source, questions, metadata, context));
18481848
}
18491849
}
18501850
}

packages/lu/src/parser/lufile/qnaSection.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const QnaSectionContext = require('./generated/LUFileParser').LUFileParser.QnaSectionContext;
22
const LUSectionTypes = require('./../utils/enums/lusectiontypes');
33
const BuildDiagnostic = require('./diagnostic').BuildDiagnostic;
4-
const helpers = require('../utils/helpers');
54
const QNA_GENERIC_SOURCE = "custom editorial";
65

76
class QnaSection {
@@ -24,14 +23,14 @@ class QnaSection {
2423
this.prompts = result.promptDefinitions;
2524
this.promptsText = result.promptTextList;
2625
this.Errors = this.Errors.concat(result.errors);
27-
this.Id = this.ExtractAssignedId(parseTree);
26+
this.QAPairId = this.ExtractAssignedId(parseTree);
2827
this.source = this.ExtractSourceInfo(parseTree);
2928
}
3029

3130
ExtractSourceInfo(parseTree) {
3231
let srcAssignment = parseTree.qnaDefinition().qnaSourceInfo()
3332
if (srcAssignment) {
34-
let srcRegExp = new RegExp(/^[ ]*\>[ ]*!#[ ]*@qna.pair.source[ ]*=[ ]*(?<sourceInfo>.*?)$/gmi);
33+
let srcRegExp = /^[ ]*\>[ ]*!#[ ]*@qna.pair.source[ ]*=[ ]*(?<sourceInfo>.*?)$/gmi;
3534
let srcParsed = srcRegExp.exec(srcAssignment.getText().trim());
3635
return srcParsed.groups.sourceInfo || QNA_GENERIC_SOURCE;
3736
}
@@ -41,7 +40,7 @@ class QnaSection {
4140
ExtractAssignedId(parseTree) {
4241
let idAssignment = parseTree.qnaDefinition().qnaIdMark()
4342
if (idAssignment) {
44-
let idTextRegExp = new RegExp(/^\<a[ ]*id[ ]*=[ ]*[\"\'](?<idCaptured>.*?)[\"\'][ ]*>[ ]*\<\/a\>$/gmi);
43+
let idTextRegExp = /^\<a[ ]*id[ ]*=[ ]*[\"\'](?<idCaptured>.*?)[\"\'][ ]*>[ ]*\<\/a\>$/gmi;
4544
let idTextParsed = idTextRegExp.exec(idAssignment.getText().trim());
4645
return idTextParsed.groups.idCaptured || undefined;
4746
}
@@ -70,7 +69,7 @@ class QnaSection {
7069
let filterLineText = promptLine.getText().trim();
7170
filterLineText = filterLineText.substr(1).trim();
7271
promptTextList.push(filterLineText);
73-
let promptConfigurationRegExp = new RegExp(/^\[(?<displayText>.*?)]\([ ]*\#[ ]*[ ?]*(?<linkedQuestion>.*?)\)[ ]*(?<contextOnly>\`context-only\`)?.*?$/gmi);
72+
let promptConfigurationRegExp = /^\[(?<displayText>.*?)]\([ ]*\#[ ]*[ ?]*(?<linkedQuestion>.*?)\)[ ]*(?<contextOnly>\`context-only\`)?.*?$/gmi;
7473
let splitLine = promptConfigurationRegExp.exec(filterLineText);
7574
if (!splitLine) {
7675
errors.push(BuildDiagnostic({

packages/lu/src/parser/lufile/sectionOperator.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,25 @@ class SectionOperator {
4949
return luParser.parse(newContent);
5050
}
5151

52-
replaceRangeContent(originString, startLine, stopLine, replaceString) {
53-
54-
if (!originString) {
55-
throw new Error('replace content with error parameters.');
52+
insertSection(id, sectionContent) {
53+
sectionContent = helpers.sanitizeNewLines(sectionContent);
54+
const section = this.Luresource.Sections.find(u => u.Id === id);
55+
if (!section && this.Luresource.Sections.length > 0 ) {
56+
return this.Luresource;
5657
}
5758

59+
const startLine = section ? section.StartLine : 0;
60+
const newContent = this.replaceRangeContent(this.Luresource.Content, startLine, startLine - 1, sectionContent);
61+
62+
const result = luParser.parse(newContent);
63+
64+
return result;
65+
}
66+
67+
replaceRangeContent(originString, startLine, stopLine, replaceString) {
5868
const originList = originString.split(/\r?\n/);
5969
let destList = [];
60-
if (isNaN(startLine) || isNaN(stopLine) || startLine < 0 || startLine > stopLine || originList.length <= stopLine) {
70+
if (isNaN(startLine) || isNaN(stopLine) || startLine < 0 || startLine > stopLine + 1 || originList.length <= stopLine) {
6171
throw new Error("index out of range.");
6272
}
6373

packages/lu/test/parser/lufile/sectionapi.test.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,113 @@ describe('Section CRUD tests for error import in utterances', () => {
293293
assert.equal(luresource.Sections[1].Name, 'Cancel')
294294
assert.equal(luresource.Sections[1].Body, '- cancel that')
295295
});
296+
});
297+
298+
describe('Section CRUD tests for qna', () => {
299+
let luresource = undefined;
300+
301+
let fileContent =
302+
`# ? who is CEO of Microsoft
303+
- Microsoft CEO
304+
305+
\`\`\`
306+
Satya Nadella
307+
\`\`\``;
308+
309+
let addedFileContent =
310+
`# ? what about the weather of Seattle
311+
- how about the weather of Seattle
312+
313+
\`\`\`
314+
warm and rainy
315+
\`\`\``
316+
317+
let updatedFileConent =
318+
`# ? who is CEO of Facebook
319+
- Facebook CEO
320+
321+
\`\`\`
322+
Mark Zuckerberg
323+
\`\`\``;
324+
325+
let insertFileContent =
326+
`# ? how to greet
327+
328+
\`\`\`
329+
hello
330+
\`\`\``
331+
332+
it('add qna section test', () => {
333+
luresource = luparser.parse(fileContent);
334+
335+
assert.equal(luresource.Errors.length, 0);
336+
assert.equal(luresource.Sections.length, 1);
337+
assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION);
338+
assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), fileContent);
339+
340+
luresource = new SectionOperator(luresource).addSection(addedFileContent);
341+
342+
assert.equal(luresource.Errors.length, 0);
343+
assert.equal(luresource.Sections.length, 2);
344+
assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION);
345+
assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION);
346+
assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), addedFileContent);
347+
});
348+
349+
it('update qna section test', () => {
350+
luresource = new SectionOperator(luresource).updateSection(luresource.Sections[0].Id, updatedFileConent);
351+
352+
assert.equal(luresource.Errors.length, 0);
353+
assert.equal(luresource.Sections.length, 2);
354+
assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION);
355+
assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION);
356+
assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), updatedFileConent);
357+
});
358+
359+
it('insert qna section at begining test', () => {
360+
luresource = new SectionOperator(luresource).insertSection(luresource.Sections[0].Id, insertFileContent);
361+
362+
assert.equal(luresource.Errors.length, 0);
363+
assert.equal(luresource.Sections.length, 3);
364+
assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION);
365+
assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION);
366+
assert.equal(luresource.Sections[2].SectionType, LUSectionTypes.QNASECTION);
367+
assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), insertFileContent);
368+
assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), updatedFileConent);
369+
assert.equal(luresource.Sections[2].Body.replace(/\r\n/g,"\n"), addedFileContent);
370+
});
371+
372+
it('delete qna section test', () => {
373+
luresource = new SectionOperator(luresource).deleteSection(luresource.Sections[0].Id);
374+
375+
assert.equal(luresource.Errors.length, 0);
376+
assert.equal(luresource.Sections.length, 2);
377+
assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION);
378+
assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION);
379+
assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), updatedFileConent);
380+
assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), addedFileContent);
381+
});
382+
383+
it('insert qna section at middle test', () => {
384+
luresource = new SectionOperator(luresource).insertSection(luresource.Sections[1].Id, insertFileContent);
385+
386+
assert.equal(luresource.Errors.length, 0);
387+
assert.equal(luresource.Sections.length, 3);
388+
assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION);
389+
assert.equal(luresource.Sections[1].SectionType, LUSectionTypes.QNASECTION);
390+
assert.equal(luresource.Sections[2].SectionType, LUSectionTypes.QNASECTION);
391+
assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), updatedFileConent);
392+
assert.equal(luresource.Sections[1].Body.replace(/\r\n/g,"\n"), insertFileContent);
393+
assert.equal(luresource.Sections[2].Body.replace(/\r\n/g,"\n"), addedFileContent);
394+
});
395+
396+
it('insert qna section at empty file', () => {
397+
luresource = luparser.parse('');
398+
luresource = new SectionOperator(luresource).insertSection(0, insertFileContent);
399+
400+
assert.equal(luresource.Errors.length, 0);
401+
assert.equal(luresource.Sections.length, 1);
402+
assert.equal(luresource.Sections[0].SectionType, LUSectionTypes.QNASECTION);
403+
assert.equal(luresource.Sections[0].Body.replace(/\r\n/g,"\n"), insertFileContent + '\n');
404+
});
296405
});

0 commit comments

Comments
 (0)