UNPKG

powerplatform-review-tool

Version:

Evaluate Power Platform solution zip files based on best practice patterns

223 lines (222 loc) 12.6 kB
"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; }, {}); }