powerplatform-review-tool
Version:
Evaluate Power Platform solution zip files based on best practice patterns
223 lines (222 loc) • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.reviewCopilotComponents = reviewCopilotComponents;
const bothelper_1 = require("../utilities/bothelper");
const copilotPatterns_1 = require("./copilotPatterns");
const ManifestConstant_1 = require("../ManifestConstant");
/**
* This function reviews copilot components (bot topics/entities) from a solution zip,
* and returns an array of CopilotResult objects.
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
async function reviewCopilotComponents(zip, patternDetails,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
customPromptResults = []) {
/* single source of truth – agentsList */
const { agents } = await (0, bothelper_1.extractAgentsDetails)(zip);
const promptRows = Array.isArray(customPromptResults) ? customPromptResults : [];
const byAgent = groupBy(promptRows, (r) => r.AgentLogicalName);
const copilotResults = [];
let globalPasses = 0;
let globalConsidered = 0;
for (const agent of agents) {
const topics = agent.botComponents.filter((c) => c.componentType === "topic");
const entities = agent.botComponents.filter((c) => c.componentType === "entity");
const knowledge = agent.botComponents.filter((c) => c.componentType === "knowledge");
/* parse-failure tracking (→ Skipped) */
const parseFailures = new Set();
if (!agent.botXml)
parseFailures.add("botXml");
if (topics.some((t) => t.data == null))
parseFailures.add("topic");
if (entities.some((e) => e.data == null))
parseFailures.add("entity");
if (knowledge.some((k) => k.data == null))
parseFailures.add("knowledge");
const isGenMode = agent.isGenModeEnabled;
const patterns = [
/* ─── classic-only patterns ─── */
{
fn: () => (0, copilotPatterns_1.patternCheckTriggerPhraseCount)(topics),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.TriggerPhraseCount].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.TriggerPhraseCount].patternName,
mode: "classic" /* StringConstants.classic */,
componentsUsed: ["topic"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckSingleWordPhrases)(topics),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.SingleWordPhrases].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.SingleWordPhrases].patternName,
mode: "classic" /* StringConstants.classic */,
componentsUsed: ["topic"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckLongPhrases)(topics),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.LongPhrases].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.LongPhrases].patternName,
mode: "classic" /* StringConstants.classic */,
componentsUsed: ["topic"],
},
/* ─── classic+genmode patterns ─── */
{
fn: () => (0, copilotPatterns_1.patternCheckConditionCount)(topics),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.ConditionCount].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.ConditionCount].patternName,
mode: "classic/genmode" /* StringConstants.classicgenmode */,
componentsUsed: ["topic"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckFlowCount)(topics),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.FlowCount].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.FlowCount].patternName,
mode: "classic/genmode" /* StringConstants.classicgenmode */,
componentsUsed: ["topic"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckSharePointAuthMode)({
botXml: agent.botXml,
botComponents: { knowledge },
}),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.SharePointAuthMode].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.SharePointAuthMode].patternName,
mode: "classic/genmode" /* StringConstants.classicgenmode */,
componentsUsed: ["botXml", "knowledge"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckUnusedTopics)(topics, agent.botLogicalName),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.UnusedTopics].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.UnusedTopics].patternName,
mode: "classic" /* StringConstants.classic */,
componentsUsed: ["topic"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckSynonymsQuality)(entities),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.SynonymsQuality].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.SynonymsQuality].patternName,
mode: "classic/genmode" /* StringConstants.classicgenmode */,
componentsUsed: ["entity"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckDuplicateRegexEntities)(entities),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.DuplicateRegexEntities].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.DuplicateRegexEntities].patternName,
mode: "classic/genmode" /* StringConstants.classicgenmode */,
componentsUsed: ["entity"],
},
{
fn: () => (0, copilotPatterns_1.patternCheckEndOfConversationUsage)(topics, agent.botLogicalName),
patternID: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.EndOfConversationReference].patternID,
patternName: ManifestConstant_1.BotPatternDetails[ManifestConstant_1.BotPatternInfo.EndOfConversationReference].patternName,
mode: "classic/genmode" /* StringConstants.classicgenmode */,
componentsUsed: ["topic"],
},
];
/* run/skip patterns */
const patternResults = [];
let botPasses = 0;
let botConsidered = 0;
for (const p of patterns) {
const base = (0, bothelper_1.getBasePatternInfo)(p.patternID, patternDetails);
/* (A) Skip – component parse failure */
if (p.componentsUsed.some((c) => parseFailures.has(c))) {
patternResults.push({
...base,
patternID: p.patternID,
status: "Skipped" /* ManifestPropertyStatusNames.Skipped */,
instanceValue: {},
failureReason: `Skipped – component parse failure (${p.componentsUsed.join(", ")})`,
});
botConsidered++;
continue;
}
/* (B) NotExecuted – wrong mode */
const classicNeeded = p.mode === "classic" /* StringConstants.classic */ || p.mode === "classic/genmode" /* StringConstants.classicgenmode */;
const genNeeded = p.mode === "genmode" /* StringConstants.genmode */ || p.mode === "classic/genmode" /* StringConstants.classicgenmode */;
if ((isGenMode && !genNeeded) || (!isGenMode && !classicNeeded)) {
patternResults.push({
...base,
patternID: p.patternID,
status: "NotExecuted" /* ManifestPropertyStatusNames.NotExecuted */,
instanceValue: {},
failureReason: `Pattern not executed for mode ${isGenMode ? "gen" : "classic"}.`,
});
continue;
}
/* (C) Execute pattern */
const r = await p.fn();
patternResults.push(r);
botConsidered++;
if (r.status === "Pass" /* ManifestPropertyStatusNames.Pass */)
botPasses++;
else if (r.status === "Warning" /* ManifestPropertyStatusNames.Warning */) {
botPasses += 0.5;
} // partial pass for warning
}
/* ────────────────────────────────────────────────────────────────
CUSTOM PROMPT PATTERNS for this agent — new universal logic
▸ Pass → every topic in the pattern has Score ≥ 0.7
▸ Fail → at least one topic has Score < 0.7
▸ Warning state no longer used
▸ instanceValue = only the failing topics with
{ TopicName, ModelDescription, Score }
──────────────────────────────────────────────────────────────── */
const promptRowsForAgent = byAgent[agent.botLogicalName] ?? [];
if (promptRowsForAgent.length > 0) {
/* group by patternId */
const byPattern = groupBy(promptRowsForAgent, (r) => r.PatternId);
for (const patternId of Object.keys(byPattern)) {
const rows = byPattern[patternId];
/* find topics whose Score < 0.7 */
const failing = rows.filter((r) => (Number(r.Score) || 0) < 0.7);
const status = failing.length === 0 ? "Pass" /* ManifestPropertyStatusNames.Pass */ : "Fail" /* ManifestPropertyStatusNames.Fail */;
const base = (0, bothelper_1.getBasePatternInfo)(patternId, patternDetails);
/* instanceValue: only TopicName, ModelDescription, Score */
const instanceValue = [
{
Topic: failing.map((f) => ({
TopicName: f.TopicName,
ModelDescription: f.ModelDescription ?? "",
Score: Number(f.Score),
})),
},
];
patternResults.push({
...base,
patternID: patternId,
status,
instanceValue,
failureReason: status === "Pass" /* ManifestPropertyStatusNames.Pass */ ? "" : `${failing.length} topic(s) scored below 0.7`,
});
/* scoring ------------------------------------------------------- */
botConsidered++;
if (status === "Pass" /* ManifestPropertyStatusNames.Pass */)
botPasses++;
/* Fail adds 0 */
}
}
/* score % for this bot */
globalPasses += botPasses;
globalConsidered += botConsidered;
const copilotScoreInPercentage = botConsidered === 0 ? "0%" : Math.round((botPasses / botConsidered) * 100).toString() + "%";
copilotResults.push({
name: agent.botLogicalName,
displayName: agent.botDisplayName,
result: patternResults,
score: copilotScoreInPercentage,
});
}
const finalScore = globalConsidered === 0 ? "0%" : Math.round((globalPasses / globalConsidered) * 100).toString() + "%";
return {
items: copilotResults,
totalBots: copilotResults.length,
totalScore: finalScore,
};
}
/* place near other helpers / imports */
function groupBy(arr, keyFn) {
return arr.reduce((acc, item) => {
const k = keyFn(item);
(acc[k] || (acc[k] = [])).push(item);
return acc;
}, {});
}