From 201aa430d4f1945de72df918ba0b23b8497f0cd3 Mon Sep 17 00:00:00 2001 From: Nick Larew Date: Fri, 8 Nov 2024 12:16:18 -0500 Subject: [PATCH] (EAI-383) Fetch & Refine Top Jira Tickets (#539) --- package-lock.json | 25 +- .../mongodb-artifact-generator/package.json | 4 +- .../mongodb-artifact-generator/src/Config.ts | 12 + .../src/chat/index.ts | 25 +- .../src/chat/makeGenerateChatCompletion.ts | 5 +- .../src/chat/makeGeneratePrompts.ts | 101 ++++ .../src/chat/makeGenerateResponse.ts | 106 ++++ .../src/chat/makeSummarizer.test.ts | 88 ++++ .../src/chat/makeSummarizer.ts | 112 +++++ .../src/chat/utils.test.ts | 66 +++ .../src/chat/utils.ts | 113 +++++ .../commands/generateJiraPromptResponse.ts | 455 +++++++++++++++++ .../examples/ADMIN-10208.json | 400 +++++++++++++++ .../examples/WORKPLACE-119.json | 470 ++++++++++++++++++ .../src/jiraPromptResponse/topIssues.json | 236 +++++++++ .../src/operations.ts | 218 ++++---- .../src/release-notes/createChangelog.ts | 48 +- .../summarizeReleaseArtifacts.ts | 24 +- .../src/runId.test.ts | 65 +++ .../mongodb-artifact-generator/src/runId.ts | 14 + .../src/runlogger.ts | 3 +- .../mongodb-artifact-generator/tsconfig.json | 5 +- 22 files changed, 2446 insertions(+), 149 deletions(-) create mode 100644 packages/mongodb-artifact-generator/src/chat/makeGeneratePrompts.ts create mode 100644 packages/mongodb-artifact-generator/src/chat/makeGenerateResponse.ts create mode 100644 packages/mongodb-artifact-generator/src/chat/makeSummarizer.test.ts create mode 100644 packages/mongodb-artifact-generator/src/chat/makeSummarizer.ts create mode 100644 packages/mongodb-artifact-generator/src/chat/utils.test.ts create mode 100644 packages/mongodb-artifact-generator/src/chat/utils.ts create mode 100644 packages/mongodb-artifact-generator/src/commands/generateJiraPromptResponse.ts create mode 100644 packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/ADMIN-10208.json create mode 100644 packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/WORKPLACE-119.json create mode 100644 packages/mongodb-artifact-generator/src/jiraPromptResponse/topIssues.json create mode 100644 packages/mongodb-artifact-generator/src/runId.test.ts create mode 100644 packages/mongodb-artifact-generator/src/runId.ts diff --git a/package-lock.json b/package-lock.json index d9e2887ba..625a7381e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40571,6 +40571,18 @@ "zod": "^3.23.3" } }, + "node_modules/zod-validation-error": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz", + "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } + }, "node_modules/zwitch": { "version": "2.0.4", "license": "MIT", @@ -41411,7 +41423,9 @@ "papaparse": "^5.4.1", "yaml": "^2.3.1", "yargs": "^17", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-to-json-schema": "^3.23.2", + "zod-validation-error": "^3.4.0" }, "bin": { "mongodb-ai": "build/main.js" @@ -41674,6 +41688,15 @@ "node": ">=12" } }, + "packages/mongodb-artifact-generator/node_modules/zod-to-json-schema": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", + "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.23.3" + } + }, "packages/mongodb-atlas": { "version": "1.0.0", "license": "Apache-2.0", diff --git a/packages/mongodb-artifact-generator/package.json b/packages/mongodb-artifact-generator/package.json index 609e041e8..5bca9f3f6 100644 --- a/packages/mongodb-artifact-generator/package.json +++ b/packages/mongodb-artifact-generator/package.json @@ -60,6 +60,8 @@ "papaparse": "^5.4.1", "yaml": "^2.3.1", "yargs": "^17", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-to-json-schema": "^3.23.2", + "zod-validation-error": "^3.4.0" } } diff --git a/packages/mongodb-artifact-generator/src/Config.ts b/packages/mongodb-artifact-generator/src/Config.ts index 5b62e0acb..ea63b5971 100644 --- a/packages/mongodb-artifact-generator/src/Config.ts +++ b/packages/mongodb-artifact-generator/src/Config.ts @@ -39,10 +39,22 @@ export type Config = { */ jiraApi?: Constructor; + /** + The maximum number of concurrent requests to make to the Jira API. + @default 12 + */ + jiraApiMaxConcurrency?: number; + /** The GitHub API client. */ githubApi?: Constructor; + + /** + The maximum number of concurrent requests to make to an LLM generator. + @default 8 + */ + llmMaxConcurrency?: number; }; export type Constructor = (() => T) | (() => Promise); diff --git a/packages/mongodb-artifact-generator/src/chat/index.ts b/packages/mongodb-artifact-generator/src/chat/index.ts index 5868e30e7..4cb358397 100644 --- a/packages/mongodb-artifact-generator/src/chat/index.ts +++ b/packages/mongodb-artifact-generator/src/chat/index.ts @@ -1,19 +1,22 @@ -export type ChatMessage = { - role: "user" | "assistant" | "system"; - content: string; -}; +import { OpenAI } from "mongodb-rag-core/openai"; -export function chatMessage(t: T) { +export function chatMessage(t: T) { return t; } -export const systemMessage = (content: string) => - chatMessage({ role: "system", content }); +export const systemMessage = ( + args: Omit +): OpenAI.ChatCompletionSystemMessageParam => + chatMessage({ role: "system", ...args }); -export const userMessage = (content: string) => - chatMessage({ role: "user", content }); +export const userMessage = ( + args: Omit +): OpenAI.ChatCompletionUserMessageParam => + chatMessage({ role: "user", ...args }); -export const assistantMessage = (content: string) => - chatMessage({ role: "assistant", content }); +export const assistantMessage = ( + args: Omit +): OpenAI.ChatCompletionAssistantMessageParam => + chatMessage({ role: "assistant", ...args }); export * from "./makeGenerateChatCompletion"; diff --git a/packages/mongodb-artifact-generator/src/chat/makeGenerateChatCompletion.ts b/packages/mongodb-artifact-generator/src/chat/makeGenerateChatCompletion.ts index 680a5fb2b..5e2660dd6 100644 --- a/packages/mongodb-artifact-generator/src/chat/makeGenerateChatCompletion.ts +++ b/packages/mongodb-artifact-generator/src/chat/makeGenerateChatCompletion.ts @@ -3,11 +3,10 @@ import { assertEnvVars, CORE_OPENAI_CHAT_COMPLETION_ENV_VARS, } from "mongodb-rag-core"; -import { AzureOpenAI } from "mongodb-rag-core/openai"; -import { ChatMessage } from "."; +import { OpenAI, AzureOpenAI } from "mongodb-rag-core/openai"; export type GenerateChatCompletion = ( - messages: ChatMessage[] + messages: OpenAI.ChatCompletionMessageParam[] ) => Promise; export function makeGenerateChatCompletion(): GenerateChatCompletion { diff --git a/packages/mongodb-artifact-generator/src/chat/makeGeneratePrompts.ts b/packages/mongodb-artifact-generator/src/chat/makeGeneratePrompts.ts new file mode 100644 index 000000000..028f38a6f --- /dev/null +++ b/packages/mongodb-artifact-generator/src/chat/makeGeneratePrompts.ts @@ -0,0 +1,101 @@ +import { OpenAI } from "mongodb-rag-core/openai"; +import { FormattedJiraIssueWithSummary } from "../commands/generateJiraPromptResponse"; +import { RunLogger } from "../runlogger"; +import { + asJsonSchema, + formatFewShotExamples, + formatMessagesForArtifact, + PromptExamplePair, +} from "./utils"; +import { z } from "zod"; +import { stripIndents } from "common-tags"; + +export type GeneratedPrompts = z.infer; +export const GeneratedPrompts = z.object({ + prompts: z.array(z.string()).min(1).max(4), +}); + +const generatePromptsTool: OpenAI.FunctionDefinition = { + name: "generatePrompts", + description: + "A list of generated example prompts that would elicit a given response.", + parameters: asJsonSchema(GeneratedPrompts), +}; + +export type MakeGeneratePromptsArgs = { + openAi: { + client: OpenAI; + model: string; + }; + logger?: RunLogger; + directions?: string; + examples?: PromptExamplePair[]; +}; + +export type GeneratePromptsArgs = FormattedJiraIssueWithSummary; + +export function makeGeneratePrompts({ + openAi, + logger, + directions, + examples = [], +}: MakeGeneratePromptsArgs) { + return async function generatePrompts({ + issue, + summary, + }: GeneratePromptsArgs) { + const messages = [ + { + role: "system", + content: [ + `Your task is to convert a provided input into a prompt-response format. The format mimics a conversation where one participant sends a prompt and the other replies with a response.`, + directions ?? "", + ].join("\n"), + }, + ...formatFewShotExamples({ + examples, + functionName: generatePromptsTool.name, + responseSchema: GeneratedPrompts, + }), + { + role: "user", + content: JSON.stringify({ issue, summary }), + }, + ] satisfies OpenAI.ChatCompletionMessageParam[]; + const result = await openAi.client.chat.completions.create({ + model: openAi.model, + messages, + temperature: 0, + max_tokens: 1500, + functions: [generatePromptsTool], + function_call: { + name: generatePromptsTool.name, + }, + }); + const response = result.choices[0].message; + if (response === undefined) { + throw new Error("No response from OpenAI"); + } + if ( + response.function_call === undefined || + response.function_call === null + ) { + throw new Error("No function call in response from OpenAI"); + } + const generatedPrompts = GeneratedPrompts.parse( + JSON.parse(response.function_call.arguments) + ); + + logger?.appendArtifact( + `chatTemplates/generatePrompts-${Date.now()}.json`, + stripIndents` + ${formatMessagesForArtifact(messages).join("\n")} + + ${JSON.stringify(summary)} + + ` + ); + + return generatedPrompts; + }; +} diff --git a/packages/mongodb-artifact-generator/src/chat/makeGenerateResponse.ts b/packages/mongodb-artifact-generator/src/chat/makeGenerateResponse.ts new file mode 100644 index 000000000..3da249d0b --- /dev/null +++ b/packages/mongodb-artifact-generator/src/chat/makeGenerateResponse.ts @@ -0,0 +1,106 @@ +import { OpenAI } from "mongodb-rag-core/openai"; +import { FormattedJiraIssue } from "../commands/generateJiraPromptResponse"; +import { RunLogger } from "../runlogger"; +import { + asJsonSchema, + formatFewShotExamples, + formatMessagesForArtifact, + PromptExamplePair, +} from "./utils"; +import { z } from "zod"; +import { stripIndents } from "common-tags"; +import { Summary } from "./makeSummarizer"; + +export type GeneratedResponse = z.infer; +export const GeneratedResponse = z.object({ + response: z.string().describe("The generated response text."), +}); + +const generateResponseTool: OpenAI.FunctionDefinition = { + name: "generateResponse", + description: "A response generated based on a given context.", + parameters: asJsonSchema(GeneratedResponse), +}; + +export type MakeGenerateResponseArgs = { + openAi: { + client: OpenAI; + model: string; + }; + logger?: RunLogger; + directions?: string; + examples?: PromptExamplePair[]; +}; + +export type GenerateResponseArgs = { + issue: FormattedJiraIssue; + summary: Summary; + prompt: string; +}; + +export function makeGenerateResponse({ + openAi, + logger, + directions, + examples = [], +}: MakeGenerateResponseArgs) { + return async function generateResponse({ + issue, + summary, + prompt, + }: GenerateResponseArgs) { + const messages = [ + { + role: "system", + content: [ + `Your task is to generate a response to a provided input. The response should be relevant to the input and based only on the provided context.`, + directions ?? "", + ].join("\n"), + }, + ...formatFewShotExamples({ + examples, + functionName: generateResponseTool.name, + responseSchema: GeneratedResponse, + }), + { + role: "user", + content: JSON.stringify({ issue, summary, prompt }), + }, + ] satisfies OpenAI.ChatCompletionMessageParam[]; + const result = await openAi.client.chat.completions.create({ + model: openAi.model, + messages, + temperature: 0, + max_tokens: 1500, + functions: [generateResponseTool], + function_call: { + name: generateResponseTool.name, + }, + }); + const response = result.choices[0].message; + if (response === undefined) { + throw new Error("No response from OpenAI"); + } + if ( + response.function_call === undefined || + response.function_call === null + ) { + throw new Error("No function call in response from OpenAI"); + } + const generatedResponse = GeneratedResponse.parse( + JSON.parse(response.function_call.arguments) + ); + + logger?.appendArtifact( + `chatTemplates/generateResponse-${Date.now()}.json`, + stripIndents` + ${formatMessagesForArtifact(messages).join("\n")} + + ${JSON.stringify(summary)} + + ` + ); + + return generatedResponse; + }; +} diff --git a/packages/mongodb-artifact-generator/src/chat/makeSummarizer.test.ts b/packages/mongodb-artifact-generator/src/chat/makeSummarizer.test.ts new file mode 100644 index 000000000..927a5a0a6 --- /dev/null +++ b/packages/mongodb-artifact-generator/src/chat/makeSummarizer.test.ts @@ -0,0 +1,88 @@ +import { CORE_ENV_VARS, assertEnvVars } from "mongodb-rag-core"; +import { AzureOpenAI } from "mongodb-rag-core/openai"; +import { makeSummarizer } from "./makeSummarizer"; +import { PromptExamplePair } from "./utils"; + +const { + OPENAI_CHAT_COMPLETION_DEPLOYMENT, + OPENAI_ENDPOINT, + OPENAI_API_KEY, + OPENAI_API_VERSION, +} = assertEnvVars(CORE_ENV_VARS); + +const openAiClient = new AzureOpenAI({ + apiKey: OPENAI_API_KEY, + endpoint: OPENAI_ENDPOINT, + apiVersion: OPENAI_API_VERSION, +}); + +// TODO: Convert this into a .eval file +describe.skip("makeSummarizer", () => { + it("creates a summarizer for content", async () => { + const summarizePoem = makeSummarizer({ + openAi: { + client: openAiClient, + model: OPENAI_CHAT_COMPLETION_DEPLOYMENT, + }, + directions: "The provided content will be a poem.", + examples: [ + [ + "Two roads diverged in a yellow wood,\nAnd sorry I could not travel both\nAnd be one traveler, long I stood\nAnd looked down one as far as I could\nTo where it bent in the undergrowth;", + { + description: + "A traveler encounters a fork in a road in a forest and reflects on the decision of choosing a path.", + topics: ["travel", "decisions", "forests", "fork in the road"], + }, + ], + [ + "Hope is the thing with feathers\nThat perches in the soul,\nAnd sings the tune without the words,\nAnd never stops at all,", + { + description: + "Hope is described as a bird that lives in the soul, continuously singing a wordless tune.", + // topics: ["hope", "soul", "birds", "music"], + }, + ], + [ + "I wandered lonely as a cloud\nThat floats on high o'er vales and hills,\nWhen all at once I saw a crowd,\nA host, of golden daffodils;", + { + descriptionz: + "A person feels lonely but then finds joy upon seeing a field of daffodils.", + topics: ["loneliness", "flowers"], + }, + ], + ], + }); + + const testCases = [ + [ + "The moon has a face like the clock in the hall;\nShe shines on thieves on the garden wall,\nOn streets and fields and harbor quays,\nAnd birdies asleep in the forks of the trees.", + { + description: + "The moon is compared to a clock, casting its light over different scenes, from thieves to sleeping birds.", + topics: ["moon", "night", "nature", "light"], + }, + ], + [ + "The rose is a rose,\nAnd was always a rose.\nBut the theory now goes\nThat the apple’s a rose,\nAnd the pear is, and so’s\nThe plum, I suppose.", + { + description: + "The poem reflects on the constancy and transformation of things, suggesting all things can be seen as a rose.", + topics: ["roses", "nature", "change", "metaphor"], + }, + ], + [ + "Fog drifts in,\nSoftly, softly,\nBlanketing the world\nIn a whisper.", + { + description: + "A quiet fog moves in, gently covering the surroundings in silence.", + topics: ["fog", "silence", "nature", "stillness"], + }, + ], + ] satisfies PromptExamplePair[]; + + for (const [poem, expected] of testCases) { + const summary = await summarizePoem({ input: poem }); + // expect(summary).toEqual(expected); + } + }); +}); diff --git a/packages/mongodb-artifact-generator/src/chat/makeSummarizer.ts b/packages/mongodb-artifact-generator/src/chat/makeSummarizer.ts new file mode 100644 index 000000000..a57cad86a --- /dev/null +++ b/packages/mongodb-artifact-generator/src/chat/makeSummarizer.ts @@ -0,0 +1,112 @@ +import "dotenv/config"; +import { OpenAI } from "mongodb-rag-core/openai"; +import { stripIndents } from "common-tags"; +import { z } from "zod"; +import { RunLogger } from "../runlogger"; +import { + asJsonSchema, + formatFewShotExamples, + formatMessagesForArtifact, + PromptExamplePair, +} from "./utils"; + +export type Summarizer = (args: { + input: string; +}) => Summary | Promise; + +export type Summary = z.infer; +export const Summary = z.object({ + topics: z + .array( + z + .string() + .describe( + "The name of a topic in the input. This could be a product name, a feature, or any other relevant noun mentioned in the input." + + "\nExamples:\n" + + ["MongoDB Atlas", "Atlas Search", "Architecture", "Atlas SQL"].join( + "\n- " + ) + ) + ) + .describe("A list of topics mentioned in the input."), + description: z + .string() + .describe("A summarized text description of the input."), +}); + +const summarizeTool: OpenAI.FunctionDefinition = { + name: "summarize", + description: "A structured summary of the provided input", + parameters: asJsonSchema(Summary), +}; + +export type MakeSummarizerArgs = { + openAi: { + client: OpenAI; + model: string; + }; + logger?: RunLogger; + directions?: string; + examples?: PromptExamplePair[]; +}; + +export function makeSummarizer({ + openAi, + logger, + directions = "", + examples = [], +}: MakeSummarizerArgs): Summarizer { + return async function summarize({ input }: { input: string }) { + const messages = [ + { + role: "system", + content: [ + `Your task is to summarize a provided input. This information will be used to drive a generative process, so precision and correctness are incredibly important.`, + directions ?? "", + ].join("\n"), + }, + ...formatFewShotExamples({ + examples, + functionName: summarizeTool.name, + responseSchema: Summary, + }), + { + role: "user", + content: input, + }, + ] satisfies OpenAI.ChatCompletionMessageParam[]; + const result = await openAi.client.chat.completions.create({ + model: openAi.model, + messages, + temperature: 0, + max_tokens: 1500, + functions: [summarizeTool], + function_call: { + name: summarizeTool.name, + }, + }); + const response = result.choices[0].message; + if (response === undefined) { + throw new Error("No response from OpenAI"); + } + if ( + response.function_call === undefined || + response.function_call === null + ) { + throw new Error("No function call in response from OpenAI"); + } + const summary = Summary.parse(JSON.parse(response.function_call.arguments)); + + logger?.appendArtifact( + `chatTemplates/summarizer-${Date.now()}.json`, + stripIndents` + ${formatMessagesForArtifact(messages).join("\n")} + + ${JSON.stringify(summary)} + + ` + ); + + return summary; + }; +} diff --git a/packages/mongodb-artifact-generator/src/chat/utils.test.ts b/packages/mongodb-artifact-generator/src/chat/utils.test.ts new file mode 100644 index 000000000..1b463e1a8 --- /dev/null +++ b/packages/mongodb-artifact-generator/src/chat/utils.test.ts @@ -0,0 +1,66 @@ +import { formatFewShotExamples } from "./utils"; +import { z } from "zod"; + +describe("formatFewShotExamples", () => { + test("formats few shot examples into an LLM conversation", () => { + const result = formatFewShotExamples({ + examples: [ + ["input1", "output1"], + ["input2", "output2"], + ], + responseSchema: z.string(), + functionName: "test-few-shot-function", + }); + expect(result).toEqual([ + { role: "user", content: "input1" }, + { + role: "assistant", + content: null, + function_call: { + name: "test-few-shot-function", + arguments: JSON.stringify("output1"), + }, + }, + { role: "user", content: "input2" }, + { + role: "assistant", + content: null, + function_call: { + name: "test-few-shot-function", + arguments: JSON.stringify("output2"), + }, + }, + ]); + }); + + test("allows you to pass objects as output", () => { + const result = formatFewShotExamples({ + examples: [ + ["input1", { key: "value1" }], + ["input2", { key: "value2" }], + ], + responseSchema: z.object({ key: z.string() }), + functionName: "test-few-shot-function", + }); + expect(result).toEqual([ + { role: "user", content: "input1" }, + { + role: "assistant", + content: null, + function_call: { + name: "test-few-shot-function", + arguments: JSON.stringify({ key: "value1" }), + }, + }, + { role: "user", content: "input2" }, + { + role: "assistant", + content: null, + function_call: { + name: "test-few-shot-function", + arguments: JSON.stringify({ key: "value2" }), + }, + }, + ]); + }); +}); diff --git a/packages/mongodb-artifact-generator/src/chat/utils.ts b/packages/mongodb-artifact-generator/src/chat/utils.ts new file mode 100644 index 000000000..f30e217bf --- /dev/null +++ b/packages/mongodb-artifact-generator/src/chat/utils.ts @@ -0,0 +1,113 @@ +import { promises as fs } from "fs"; +import { OpenAI } from "mongodb-rag-core/openai"; +import { z, ZodTypeAny } from "zod"; +import zodToJsonSchema from "zod-to-json-schema"; +import { fromError } from "zod-validation-error"; +import { assistantMessage, userMessage } from "."; + +export function formatMessagesForArtifact( + messages: OpenAI.ChatCompletionMessageParam[] +) { + const tagsByRole = { + system: "SystemMessage", + user: "UserMessage", + assistant: "AssistantMessage", + tool: "ToolMessage", + function: "FunctionMessage", + }; + return messages + .filter((message) => message.role in tagsByRole) + .map((message) => { + const tag = tagsByRole[message.role as keyof typeof tagsByRole]; + const isFunctionCall = + message.role === "assistant" && + (message.function_call ?? undefined) !== undefined; + const content = !isFunctionCall + ? message.content + : `Function Call:\n${JSON.stringify(message.function_call)}`; + + return `<${tag}>\n${content}\n`; + }); +} + +export async function loadPromptExamplePairFromFile( + path: string +): Promise { + const fileDataRaw = await fs.readFile(path, "utf-8"); + const [input, output] = JSON.parse(fileDataRaw); + return PromptExamplePair.parse([JSON.stringify(input), output]); +} + +export function toBulletPoint(text: string) { + return `* ${text}`; +} + +export function asBulletPoints(...lines: string[]) { + return lines.map(toBulletPoint).join("\n"); +} + +export type PromptExamplePair = z.infer; +export const PromptExamplePair = z.tuple([z.string(), z.unknown()]); + +export function formatFewShotExamples(args: { + examples: PromptExamplePair[]; + responseSchema?: ZodTypeAny; + functionName: string; +}): OpenAI.ChatCompletionMessageParam[] { + return args.examples.flatMap(([input, output], exampleIndex) => { + try { + const parsedOutput = args.responseSchema + ? args.responseSchema.parse(output) + : output; + + return [ + userMessage({ + content: input, + }), + assistantMessage({ + content: null, + function_call: { + name: args.functionName, + arguments: JSON.stringify(parsedOutput), + }, + }), + ]; + } catch (error) { + if (error instanceof z.ZodError) { + throw new Error( + [ + `message: Error parsing few shot example at position ${exampleIndex}`, + `error: ${fromError(error)}`, + "given: |", + ...input.split("\n").map((line) => `\t${line}`), + "expected: |", + ...JSON.stringify(output, null, 2) + .split("\n") + .map((line) => `\t${line}`), + ].join("\n") + ); + } + throw error; + } + }); +} + +export type AsJsonSchemaOptions = { + // examples?: PromptExamplePair[]; + zodToJsonSchema?: Parameters[1]; +}; + +export function asJsonSchema( + schema: ZodTypeAny, + options: AsJsonSchemaOptions = {} +) { + if (typeof options.zodToJsonSchema === "string") { + const name = options.zodToJsonSchema; + options.zodToJsonSchema = { name }; + } + const convertedJsonSchema = zodToJsonSchema(schema, { + $refStrategy: "none", + ...(options.zodToJsonSchema ?? {}), + }); + return convertedJsonSchema; +} diff --git a/packages/mongodb-artifact-generator/src/commands/generateJiraPromptResponse.ts b/packages/mongodb-artifact-generator/src/commands/generateJiraPromptResponse.ts new file mode 100644 index 000000000..984983dc3 --- /dev/null +++ b/packages/mongodb-artifact-generator/src/commands/generateJiraPromptResponse.ts @@ -0,0 +1,455 @@ +import { + createConfiguredAction, + withConfig, + withConfigOptions, +} from "../withConfig"; +import { createCommand } from "../createCommand"; +import { makeRunLogger, type RunLogger } from "../runlogger"; +import path from "path"; +import { PromisePool } from "@supercharge/promise-pool"; +import { makeSummarizer, Summary } from "../chat/makeSummarizer"; +import { assertEnvVars } from "mongodb-rag-core"; +import { asBulletPoints, loadPromptExamplePairFromFile } from "../chat/utils"; +import { createRunId } from "../runId"; +import { makeGeneratePrompts } from "../chat/makeGeneratePrompts"; +import { makeGenerateResponse } from "../chat/makeGenerateResponse"; +import { promises as fs } from "fs"; +import { z } from "zod"; + +const DEFAULT_JIRA_API_MAX_CONCURRENCY = 12; + +const DEFAULT_LLM_MAX_CONCURRENCY = 8; + +const { OPENAI_CHAT_COMPLETION_DEPLOYMENT } = assertEnvVars({ + OPENAI_CHAT_COMPLETION_DEPLOYMENT: "", +}); + +let logger: RunLogger; + +export const JiraCommentSchema = z.object({ + id: z.string(), + body: z.string(), + author: z.object({ + emailAddress: z.string(), + displayName: z.string(), + }), + created: z.string(), + updated: z.string(), +}); +export type JiraComment = z.infer; + +const RawJiraIssueSchema = z.object({ + key: z.string(), + fields: z.object({ + project: z.object({ + name: z.string(), + }), + summary: z.string(), + status: z.object({ + name: z.string(), + }), + created: z.string(), + updated: z.string(), + description: z.string(), + comment: z.object({ + comments: z.array(JiraCommentSchema), + }), + }), +}); + +export type RawJiraIssue = z.infer; + +export const FormattedJiraIssueSchema = z.object({ + key: z.string(), + projectName: z.string(), + summary: z.string(), + status: z.string(), + created: z.string(), + updated: z.string(), + description: z.string(), + comments: z.array(JiraCommentSchema), +}); +export type FormattedJiraIssue = z.infer; + +export const FormattedJiraIssueWithSummarySchema = z.object({ + issue: FormattedJiraIssueSchema, + summary: Summary, +}); + +export type FormattedJiraIssueWithSummary = z.infer< + typeof FormattedJiraIssueWithSummarySchema +>; + +type GenerateJiraPromptResponseCommandArgs = { + runId?: string; + jiraApiMaxConcurrency?: number; + llmMaxConcurrency?: number; + maxInputLength?: number; + issuesFilePath?: string; + issue?: string | string[]; +}; + +export default createCommand({ + command: "generateJiraPromptResponse", + builder(args) { + return withConfigOptions(args) + .option("runId", { + type: "string", + demandOption: false, + description: + "A unique name for the run. This controls where outputs artifacts and logs are stored.", + }) + .option("jiraApiMaxConcurrency", { + type: "number", + demandOption: false, + description: + "The maximum number of concurrent requests to the Jira API. Can be specified in the config file as `jiraApiMaxConcurrency`.", + }) + .option("llmMaxConcurrency", { + type: "number", + demandOption: false, + description: + "The maximum number of concurrent requests to the LLM API. Can be specified in the config file as `llmMaxConcurrency`.", + }) + .option("maxInputLength", { + type: "number", + demandOption: false, + description: + "The maximum number of issues to process in this run. Any additional issues are skipped.", + }) + .option("issuesFilePath", { + type: "string", + demandOption: false, + description: + "Path to a JSON file containing an array of issue key strings to process.", + }) + .option("issue", { + type: "string", + demandOption: false, + description: "A single issue key to process.", + }); + }, + async handler(args) { + const runId = args.runId ?? createRunId(); + logger = makeRunLogger({ + topic: "GenerateJiraPromptResponse", + runId, + }); + logger.logInfo(`Run ID: ${runId}`); + logger.logInfo(`Running command with args: ${JSON.stringify(args)}`); + try { + const result = await withConfig(action, args); + logger.logInfo(`Success`); + return result; + } finally { + await logger.flushArtifacts(); + await logger.flushLogs(); + } + }, + describe: + "Generate prompt-response pairs that crytallize knowledge from raw jira issues.", +}); + +export const action = + createConfiguredAction( + async (config, args) => { + logger.logInfo(`Setting up...`); + + const { jiraApi, openAiClient } = config; + if (!jiraApi) { + throw new Error( + "jiraApi is required. Make sure to define it in the config." + ); + } + if (!openAiClient) { + throw new Error( + "openAiClient is required. Make sure to define it in the config." + ); + } + + const jiraApiMaxConcurrency = + args.jiraApiMaxConcurrency ?? + config.jiraApiMaxConcurrency ?? + DEFAULT_JIRA_API_MAX_CONCURRENCY; + + const llmMaxConcurrency = + args.jiraApiMaxConcurrency ?? + config.jiraApiMaxConcurrency ?? + DEFAULT_LLM_MAX_CONCURRENCY; + + // Determine which Jira issues to process + const inputIssueKeys = new Set(); + const addInputIssueKeys = (issueKeys: unknown) => { + z.array(z.string()) + .parse(issueKeys) + .forEach((issueKey) => { + inputIssueKeys.add(issueKey); + }); + }; + + if (args.issue) { + addInputIssueKeys( + Array.isArray(args.issue) ? args.issue : [args.issue] + ); + } + if (args.issuesFilePath) { + addInputIssueKeys( + JSON.parse(await fs.readFile(args.issuesFilePath, "utf-8")) + ); + } + if (inputIssueKeys.size === 0) { + throw new Error("No issues provided."); + } + const issueKeys = [...inputIssueKeys]; + + // Fetch Jira issues + console.log("Fetching Jira issues..."); + const { results: jiraIssues } = await PromisePool.for( + issueKeys.slice(0, args.maxInputLength) + ) + .withConcurrency(jiraApiMaxConcurrency) + .handleError((error, issueKey) => { + const parsedErrorMessage = JSON.parse(error.message); + const logErrorMessage = + parsedErrorMessage?.errorMessages?.[0] ?? "Something went wrong."; + logger.logError( + `Error fetching issue: ${issueKey} - ${logErrorMessage}` + ); + }) + .process(async (issueKey) => { + return await jiraApi.getIssue(issueKey); + }); + logger.appendArtifact("jiraIssues.raw.json", JSON.stringify(jiraIssues)); + + // Format Jira issues + console.log("Formatting Jira issues..."); + const formattedJiraIssues = z + .array(RawJiraIssueSchema) + .parse(jiraIssues) + .map((issue) => { + return FormattedJiraIssueSchema.parse({ + key: issue.key, + projectName: issue.fields.project.name, + summary: issue.fields.summary, + status: issue.fields.status.name, + created: issue.fields.created, + updated: issue.fields.updated, + description: issue.fields.description, + comments: issue.fields.comment.comments.map((comment) => { + return { + id: comment.id, + body: comment.body, + author: { + emailAddress: comment.author.emailAddress, + displayName: comment.author.displayName, + }, + created: comment.created, + updated: comment.updated, + }; + }), + }); + }); + + logger.appendArtifact( + "jiraIssues.formatted.json", + JSON.stringify(formattedJiraIssues) + ); + + // Summarize each issue using a promise pool + const summarizeJiraIssue = makeSummarizer({ + openAi: { + client: openAiClient, + model: OPENAI_CHAT_COMPLETION_DEPLOYMENT, + }, + logger, + directions: asBulletPoints( + "The task is to summarize the provided Jira issue.", + "If there is a notable bug or issue being described, include that in the summary.", + "If there are any notable comments, include those in the summary.", + "If there are any notable actions that need to be taken to solve, include specific details about those in the summary." + ), + examples: await Promise.all([ + loadPromptExamplePairFromFile( + path.join( + __dirname, + "../jiraPromptResponse/examples/WORKPLACE-119.json" + ) + ), + loadPromptExamplePairFromFile( + path.join( + __dirname, + "../jiraPromptResponse/examples/ADMIN-10208.json" + ) + ), + ]), + }); + + console.log(`Summarizing ${jiraIssues.length} Jira issues...`); + const summariesByIssueKey = new Map(); + await PromisePool.for(jiraIssues) + .withConcurrency(llmMaxConcurrency) + .handleError((error, issue) => { + logger.logError( + `Error summarizing issue ${issue.key}: ${JSON.stringify(error)}` + ); + }) + .process(async (issue) => { + const summary = await summarizeJiraIssue({ + input: JSON.stringify(issue), + }); + logger.logInfo(`summarized issue ${issue.key}: ${summary}`); + summariesByIssueKey.set(issue.key, summary); + }); + + logger.appendArtifact( + "summaries.json", + JSON.stringify(Object.fromEntries(summariesByIssueKey)) + ); + + // Append summaries to formatted issues + const formattedIssuesWithSummaries = formattedJiraIssues.map((issue) => { + const summary = summariesByIssueKey.get(issue.key); + if (!summary) { + throw new Error(`No summary found for issue ${issue.key}`); + } + return { + issue, + summary, + }; + }) satisfies FormattedJiraIssueWithSummary[]; + logger.appendArtifact( + "jiraIssues.formattedWithSummaries.json", + JSON.stringify(formattedIssuesWithSummaries) + ); + + // // Generate a list of N questions/prompts for each issue + const promptsByIssueKey = new Map(); + const generatePrompts = makeGeneratePrompts({ + openAi: { + client: openAiClient, + model: OPENAI_CHAT_COMPLETION_DEPLOYMENT, + }, + logger, + directions: asBulletPoints( + "Assume the prompter is not familiar with or looking for the provided Jira issue specifically.", + "Assume the prompter is looking for a specific piece of information about the topic or bug discussed in the Jira issue.", + "The prompt should be a question that can be answered with the information in the Jira issue but not about the issue itself.", + "If the Jira issue is for a specific language, framework, platform, driver, etc., indicate that in the prompt.", + "Do not reference hypothetical or speculative information." + ), + }); + console.log("Generating prompts..."); + await PromisePool.for(formattedIssuesWithSummaries) + .withConcurrency(llmMaxConcurrency) + .handleError((error, { issue }) => { + logger.logError( + `Error generating prompts for ${issue.key}: ${JSON.stringify( + error + )}` + ); + }) + .process(async ({ issue, summary }) => { + const { prompts } = await generatePrompts({ issue, summary }); + console.log( + ` generated ${prompts.length} prompts for issue ${issue.key}` + ); + logger.logInfo( + `generated ${prompts.length} prompts for issue ${issue.key}` + ); + promptsByIssueKey.set(issue.key, prompts); + }); + const generatedPrompts: [issueKey: string, prompt: string][] = [ + ...promptsByIssueKey.entries(), + ].flatMap(([issueKey, prompts]) => { + return prompts.map((prompt) => [issueKey, prompt] as [string, string]); + }); + logger.appendArtifact( + "generatedPrompts.json", + JSON.stringify(generatedPrompts) + ); + + // Have the LLM generate a response to each prompt with the formatted/summarized issue as context + const responsesByIssueKey = new Map< + string, + [prompt: string, response: string][] + >(); + + const generateResponse = makeGenerateResponse({ + openAi: { + client: openAiClient, + model: OPENAI_CHAT_COMPLETION_DEPLOYMENT, + }, + logger, + directions: asBulletPoints( + "The response should be as accurate as possible based on the information in the Jira issue.", + "You may use your knowledge of the topic to provide a more detailed response. However, avoid answering the main request with information that is not in the Jira issue.", + "Do not make strong claims about MongoDB's product plans or future releases.", + "Do not directly refer to the product backlog or project management tools.", + "Do not reference the names of specific people or teams. If you must refer to a person, use a generic term like 'MongoDB employee', 'community member', 'developer', or 'engineer'." + ), + }); + + const prompts = generatedPrompts.map(([issueKey, prompt]) => { + const summary = summariesByIssueKey.get(issueKey); + if (!summary) { + throw new Error(`No summary found for key ${issueKey}`); + } + const issue = formattedJiraIssues.find( + (issue) => issue.key === issueKey + ); + if (!issue) { + throw new Error(`No issue data found for key ${issueKey}`); + } + return { + summary, + issue, + prompt, + }; + }); + + console.log("Generating responses..."); + let numGeneratedResponses = 0; + await PromisePool.for(prompts) + .withConcurrency(llmMaxConcurrency) + .handleError((error, { issue }) => { + logger.logError( + `Error generating response for ${issue.key}: ${JSON.stringify( + error + )}` + ); + }) + .process(async ({ summary, issue, prompt }, i) => { + const { response } = await generateResponse({ + summary, + issue, + prompt, + }); + console.log( + ` generated response ${++numGeneratedResponses}/${ + prompts.length + } for issue ${issue.key}` + ); + logger.logInfo( + `generated response ${numGeneratedResponses}/${prompts.length} for issue ${issue.key}` + ); + if (!responsesByIssueKey.has(issue.key)) { + responsesByIssueKey.set(issue.key, []); + } + responsesByIssueKey.get(issue.key)?.push([prompt, response]); + }); + const generatedResponses = [...responsesByIssueKey.entries()].flatMap( + ([issueKey, responses]) => { + return responses.map(([prompt, response]) => ({ + issueKey, + prompt, + response, + })); + } + ); + + logger.appendArtifact( + "generatedResponses.json", + JSON.stringify(generatedResponses) + ); + } + ); diff --git a/packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/ADMIN-10208.json b/packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/ADMIN-10208.json new file mode 100644 index 000000000..2c61cfb87 --- /dev/null +++ b/packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/ADMIN-10208.json @@ -0,0 +1,400 @@ +[ + { + "expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations", + "id": "108475", + "self": "https://jira.mongodb.org/rest/agile/1.0/issue/108475", + "key": "ADMIN-10208", + "fields": { + "customfield_14150": { + "self": "https://jira.mongodb.org/rest/api/2/customFieldOption/19783", + "value": "New York City", + "id": "19783", + "disabled": false + }, + "fixVersions": [], + "resolution": { + "self": "https://jira.mongodb.org/rest/api/2/resolution/9", + "id": "9", + "description": "GreenHopper Managed Resolution. Grab-bag resolution for other things (e.g.: re-directing users to google groups)", + "name": "Done" + }, + "lastViewed": null, + "priority": { + "self": "https://jira.mongodb.org/rest/api/2/priority/4", + "iconUrl": "https://jira.mongodb.org/images/icons/priorities/minor.svg", + "name": "Minor - P4", + "id": "4" + }, + "labels": [], + "aggregatetimeoriginalestimate": null, + "timeestimate": null, + "versions": [], + "issuelinks": [], + "assignee": null, + "status": { + "self": "https://jira.mongodb.org/rest/api/2/status/6", + "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", + "iconUrl": "https://jira.mongodb.org/images/icons/statuses/closed.png", + "name": "Closed", + "id": "6", + "statusCategory": { + "self": "https://jira.mongodb.org/rest/api/2/statuscategory/3", + "id": 3, + "key": "done", + "colorName": "success", + "name": "Done" + } + }, + "components": [ + { + "self": "https://jira.mongodb.org/rest/api/2/component/11842", + "id": "11842", + "name": "CLI" + } + ], + "customfield_10051": [ + "jimbob@gmail.com(jimbob)", + "somebody.person@10gen.com(somebody.person@10gen.com)", + "excellent.sme@mongodb.com(excellent.sme@mongodb.com)" + ], + "aggregatetimeestimate": null, + "creator": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jimbob%40gmail.com", + "name": "jimbob@gmail.com", + "key": "jimbob@gmail.com", + "emailAddress": "jimbob@gmail.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jimbob%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jimbob%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jimbob%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jimbob%40gmail.com&avatarId=11105" + }, + "displayName": "jimbob", + "active": true, + "timeZone": "America/New_York" + }, + "subtasks": [], + "reporter": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jimbob%40gmail.com", + "name": "jimbob@gmail.com", + "key": "jimbob@gmail.com", + "emailAddress": "jimbob@gmail.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jimbob%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jimbob%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jimbob%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jimbob%40gmail.com&avatarId=11105" + }, + "displayName": "jimbob", + "active": true, + "timeZone": "America/New_York" + }, + "customfield_15850": "{summaryBean=com.atlassian.jira.plugin.devstatus.rest.SummaryBean@27f7a1d5[summary={pullrequest=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@5b14819e[byInstanceType={},overall=PullRequestOverallBean{stateCount=0, state='OPEN', details=PullRequestOverallDetails{openCount=0, mergedCount=0, declinedCount=0}}], build=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@2513a91f[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BuildOverallBean@5af4c452[failedBuildCount=0,successfulBuildCount=0,unknownBuildCount=0,count=0,lastUpdated=,lastUpdatedTimestamp=]], review=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@3075b425[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.ReviewsOverallBean@dc905a0[dueDate=,overDue=false,state=,stateCount=0,count=0,lastUpdated=,lastUpdatedTimestamp=]], deployment-environment=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@352a1e57[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.DeploymentOverallBean@49809af4[showProjects=false,successfulCount=0,topEnvironments=[],count=0,lastUpdated=,lastUpdatedTimestamp=]], repository=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@54c9db7d[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.CommitOverallBean@11e82041[count=0,lastUpdated=,lastUpdatedTimestamp=]], branch=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@74555c8d[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BranchOverallBean@394f3fc9[count=0,lastUpdated=,lastUpdatedTimestamp=]]},configErrors=[],errors=[]], devSummaryJson={\"cachedValue\":{\"errors\":[],\"configErrors\":[],\"summary\":{\"pullrequest\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":\"OPEN\",\"details\":{\"openCount\":0,\"mergedCount\":0,\"declinedCount\":0,\"total\":0},\"open\":true},\"byInstanceType\":{}},\"build\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"failedBuildCount\":0,\"successfulBuildCount\":0,\"unknownBuildCount\":0},\"byInstanceType\":{}},\"review\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":null,\"dueDate\":null,\"overDue\":false,\"completed\":false},\"byInstanceType\":{}},\"deployment-environment\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"topEnvironments\":[],\"showProjects\":false,\"successfulCount\":0},\"byInstanceType\":{}},\"repository\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}},\"branch\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}}}},\"isStale\":false}}", + "aggregateprogress": { "progress": 0, "total": 0 }, + "progress": { "progress": 0, "total": 0 }, + "votes": { + "self": "https://jira.mongodb.org/rest/api/2/issue/ADMIN-10208/votes", + "votes": 10, + "hasVoted": false + }, + "worklog": { "startAt": 0, "maxResults": 20, "total": 0, "worklogs": [] }, + "archivedby": null, + "issuetype": { + "self": "https://jira.mongodb.org/rest/api/2/issuetype/4", + "id": "4", + "description": "An improvement or enhancement to an existing feature or task.", + "iconUrl": "https://jira.mongodb.org/secure/viewavatar?size=xsmall&avatarId=14710&avatarType=issuetype", + "name": "Improvement", + "subtask": false, + "avatarId": 14710 + }, + "timespent": null, + "project": { + "self": "https://jira.mongodb.org/rest/api/2/project/11281", + "id": "11281", + "key": "ADMIN", + "name": "Admin Server", + "projectTypeKey": "service_desk", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/projectavatar?pid=11281&avatarId=17807", + "24x24": "https://jira.mongodb.org/secure/projectavatar?size=small&pid=11281&avatarId=17807", + "16x16": "https://jira.mongodb.org/secure/projectavatar?size=xsmall&pid=11281&avatarId=17807", + "32x32": "https://jira.mongodb.org/secure/projectavatar?size=medium&pid=11281&avatarId=17807" + }, + "projectCategory": { + "self": "https://jira.mongodb.org/rest/api/2/projectCategory/12111", + "id": "12111", + "description": "", + "name": "FinOps" + } + }, + "resolutiondate": "2014-03-20T17:21:15.000+0000", + "workratio": -1, + "watches": { + "self": "https://jira.mongodb.org/rest/api/2/issue/ADMIN-10208/watchers", + "watchCount": 10, + "isWatching": false + }, + "created": "2018-01-30T22:22:56.000+0000", + "updated": "2024-06-01T02:23:47.000+0000", + "timeoriginalestimate": null, + "summary": "Server is returning error when creating a new user", + "description": "The server is returning an error when creating a new user. The error message is: \"Error: User already exists.\". This is a problem because we need to be able to create new users. I don't think this user actually exists!", + "timetracking": {}, + "attachment": [], + "flagged": false, + "customfield_11452": { + "id": "34", + "name": "Time to first response", + "_links": { + "self": "https://jira.mongodb.org/rest/servicedeskapi/request/108475/sla/34" + }, + "completedCycles": [] + }, + "customfield_11451": { + "id": "33", + "name": "Time to resolution", + "_links": { + "self": "https://jira.mongodb.org/rest/servicedeskapi/request/108475/sla/33" + }, + "completedCycles": [] + }, + "environment": "Men's room", + "duedate": null, + "comment": { + "comments": [ + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/491172", + "id": "491172", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=somebody.person%4010gen.com", + "name": "somebody.person@10gen.com", + "key": "somebody.person@10gen.com", + "emailAddress": "somebody.person@10gen.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?avatarId=10123", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&avatarId=10123", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&avatarId=10123", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&avatarId=10123" + }, + "displayName": "somebody.person@10gen.com", + "active": false, + "timeZone": "UTC" + }, + "body": "This happens in the UI sometimes too", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=somebody.person%4010gen.com", + "name": "somebody.person@10gen.com", + "key": "somebody.person@10gen.com", + "emailAddress": "?", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?avatarId=10123", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&avatarId=10123", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&avatarId=10123", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&avatarId=10123" + }, + "displayName": "somebody.person@10gen.com", + "active": false, + "timeZone": "UTC" + }, + "created": "2014-01-30T22:24:55.000+0000", + "updated": "2014-01-30T22:24:55.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/491173", + "id": "491173", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=triager%40mongodb.com", + "name": "triager@mongodb.com", + "key": "triager@gmail.com", + "emailAddress": "jimbob@gmail.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=triager%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=triager%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=triager%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=triager%40gmail.com&avatarId=11105" + }, + "displayName": "jimbob", + "active": true, + "timeZone": "America/New_York" + }, + "body": "[~somebody.person@10gen.com]: That's weird. I wonder if it's related to the issue we had with the server last week. Do you have a reproduction case? cc [~excellent.sme@mongodb.com] any ideas?", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=triager%40mongodb.com", + "name": "triager@mongodb.com", + "key": "triager@gmail.com", + "emailAddress": "triager@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=triager%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=triager%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=triager%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=triager%40gmail.com&avatarId=11105" + }, + "displayName": "Triager", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-01-30T22:26:53.000+0000", + "updated": "2014-01-30T22:26:53.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/491176", + "id": "491176", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=somebody.person%40mongodb.com", + "name": "somebody.person@mongodb.com", + "key": "somebody.person", + "emailAddress": "somebody.person@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=somebody.person&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=somebody.person&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=somebody.person&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=somebody.person&avatarId=11409" + }, + "displayName": "somebody.person", + "active": true, + "timeZone": "America/New_York" + }, + "body": "Maybe a caching thing? A repro case would help.", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=somebody.person%40mongodb.com", + "name": "somebody.person@mongodb.com", + "key": "somebody.person", + "emailAddress": "somebody.person@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=somebody.person&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=somebody.person&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=somebody.person&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=somebody.person&avatarId=11409" + }, + "displayName": "somebody.person", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-01-30T22:31:38.000+0000", + "updated": "2014-01-30T22:31:38.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/518934", + "id": "518934", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=excellent.sme%40mongodb.com", + "name": "excellent.sme@mongodb.com", + "key": "excellent.sme", + "emailAddress": "excellent.sme@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=excellent.sme&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=excellent.sme&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=excellent.sme&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=excellent.sme&avatarId=11409" + }, + "displayName": "excellent.sme", + "active": true, + "timeZone": "America/New_York" + }, + "body": "This could definitely be a cache thing - I ran into this on my test rig just now. Try running admin local cache delete and see if that helps.", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=excellent.sme%40mongodb.com", + "name": "excellent.sme@mongodb.com", + "key": "excellent.sme", + "emailAddress": "excellent.sme@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=excellent.sme&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=excellent.sme&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=excellent.sme&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=excellent.sme&avatarId=11409" + }, + "displayName": "excellent.sme", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-03-19T00:43:05.000+0000", + "updated": "2014-03-19T00:43:05.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/518985", + "id": "518985", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=somebody.person%40mongodb.com", + "name": "somebody.person@mongodb.com", + "key": "somebody.person@gmail.com", + "emailAddress": "somebody.person@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=somebody.person%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=somebody.person%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=somebody.person%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=somebody.person%40gmail.com&avatarId=11105" + }, + "displayName": "somebody.person%40mongodb.com", + "active": true, + "timeZone": "America/New_York" + }, + "body": "Nice! Please let us know if that works [~somebody.person@40gmail.com]", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=somebody.person%40mongodb.com", + "name": "somebody.person@mongodb.com", + "key": "somebody.person@gmail.com", + "emailAddress": "somebody.person@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=somebody.person%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=somebody.person%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=somebody.person%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=somebody.person%40gmail.com&avatarId=11105" + }, + "displayName": "somebody.person%40mongodb.com", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-03-19T04:54:25.000+0000", + "updated": "2014-03-19T04:54:25.000+0000" + }, + + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/518985", + "id": "518985", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jimbob%40gmail.com", + "name": "jimbob@gmail.com", + "key": "jimbob@gmail.com", + "emailAddress": "jimbob@gmail.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jimbob%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jimbob%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jimbob%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jimbob%40gmail.com&avatarId=11105" + }, + "displayName": "jimbob", + "active": true, + "timeZone": "America/New_York" + }, + "body": "Yes that solved it thanks!", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jimbob%40gmail.com", + "name": "jimbob@gmail.com", + "key": "jimbob@gmail.com", + "emailAddress": "jimbob@gmail.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jimbob%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jimbob%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jimbob%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jimbob%40gmail.com&avatarId=11105" + }, + "displayName": "jimbob", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-03-19T04:54:25.000+0000", + "updated": "2014-03-19T04:54:25.000+0000" + } + ], + "maxResults": 7, + "total": 7, + "startAt": 0 + } + } + }, + { + "topics": ["server error", "user creation", "cache issue", "Admin Server"], + "description": "The issue reports a server error when creating a new user, with the message 'Error: User already exists.' The reporter believes the user does not actually exist. Comments suggest it might be a caching issue. The solution, confirmed to work by the reporter, involves clearing the cache by running `admin local cache delete`." + } +] diff --git a/packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/WORKPLACE-119.json b/packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/WORKPLACE-119.json new file mode 100644 index 000000000..f1b09f029 --- /dev/null +++ b/packages/mongodb-artifact-generator/src/jiraPromptResponse/examples/WORKPLACE-119.json @@ -0,0 +1,470 @@ +[ + { + "expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations", + "id": "108475", + "self": "https://jira.mongodb.org/rest/agile/1.0/issue/108475", + "key": "WORKPLACE-119", + "fields": { + "customfield_14150": { + "self": "https://jira.mongodb.org/rest/api/2/customFieldOption/19783", + "value": "New York City", + "id": "19783", + "disabled": false + }, + "fixVersions": [], + "resolution": { + "self": "https://jira.mongodb.org/rest/api/2/resolution/9", + "id": "9", + "description": "GreenHopper Managed Resolution. Grab-bag resolution for other things (e.g.: re-directing users to google groups)", + "name": "Done" + }, + "lastViewed": null, + "priority": { + "self": "https://jira.mongodb.org/rest/api/2/priority/4", + "iconUrl": "https://jira.mongodb.org/images/icons/priorities/minor.svg", + "name": "Minor - P4", + "id": "4" + }, + "labels": [], + "aggregatetimeoriginalestimate": null, + "timeestimate": null, + "versions": [], + "issuelinks": [], + "assignee": null, + "status": { + "self": "https://jira.mongodb.org/rest/api/2/status/6", + "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", + "iconUrl": "https://jira.mongodb.org/images/icons/statuses/closed.png", + "name": "Closed", + "id": "6", + "statusCategory": { + "self": "https://jira.mongodb.org/rest/api/2/statuscategory/3", + "id": 3, + "key": "done", + "colorName": "success", + "name": "Done" + } + }, + "components": [ + { + "self": "https://jira.mongodb.org/rest/api/2/component/11842", + "id": "11842", + "name": "New York Office" + } + ], + "customfield_10051": [ + "akshay@mongodb.com(akshay)", + "carol.huang@10gen.com(carol.huang@10gen.com)", + "isabelle.davis(isabelle.davis)", + "jmikola@mongodb.com(jmikola@gmail.com)", + "kym.ganade(kym@10gen.com)" + ], + "aggregatetimeestimate": null, + "creator": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jmikola%40mongodb.com", + "name": "jmikola@mongodb.com", + "key": "jmikola@gmail.com", + "emailAddress": "jmikola@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jmikola%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jmikola%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jmikola%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jmikola%40gmail.com&avatarId=11105" + }, + "displayName": "Jeremy Mikola", + "active": true, + "timeZone": "America/New_York" + }, + "subtasks": [], + "reporter": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jmikola%40mongodb.com", + "name": "jmikola@mongodb.com", + "key": "jmikola@gmail.com", + "emailAddress": "jmikola@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jmikola%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jmikola%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jmikola%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jmikola%40gmail.com&avatarId=11105" + }, + "displayName": "Jeremy Mikola", + "active": true, + "timeZone": "America/New_York" + }, + "customfield_15850": "{summaryBean=com.atlassian.jira.plugin.devstatus.rest.SummaryBean@27f7a1d5[summary={pullrequest=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@5b14819e[byInstanceType={},overall=PullRequestOverallBean{stateCount=0, state='OPEN', details=PullRequestOverallDetails{openCount=0, mergedCount=0, declinedCount=0}}], build=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@2513a91f[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BuildOverallBean@5af4c452[failedBuildCount=0,successfulBuildCount=0,unknownBuildCount=0,count=0,lastUpdated=,lastUpdatedTimestamp=]], review=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@3075b425[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.ReviewsOverallBean@dc905a0[dueDate=,overDue=false,state=,stateCount=0,count=0,lastUpdated=,lastUpdatedTimestamp=]], deployment-environment=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@352a1e57[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.DeploymentOverallBean@49809af4[showProjects=false,successfulCount=0,topEnvironments=[],count=0,lastUpdated=,lastUpdatedTimestamp=]], repository=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@54c9db7d[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.CommitOverallBean@11e82041[count=0,lastUpdated=,lastUpdatedTimestamp=]], branch=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@74555c8d[byInstanceType={},overall=com.atlassian.jira.plugin.devstatus.summary.beans.BranchOverallBean@394f3fc9[count=0,lastUpdated=,lastUpdatedTimestamp=]]},configErrors=[],errors=[]], devSummaryJson={\"cachedValue\":{\"errors\":[],\"configErrors\":[],\"summary\":{\"pullrequest\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":\"OPEN\",\"details\":{\"openCount\":0,\"mergedCount\":0,\"declinedCount\":0,\"total\":0},\"open\":true},\"byInstanceType\":{}},\"build\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"failedBuildCount\":0,\"successfulBuildCount\":0,\"unknownBuildCount\":0},\"byInstanceType\":{}},\"review\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":null,\"dueDate\":null,\"overDue\":false,\"completed\":false},\"byInstanceType\":{}},\"deployment-environment\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"topEnvironments\":[],\"showProjects\":false,\"successfulCount\":0},\"byInstanceType\":{}},\"repository\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}},\"branch\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}}}},\"isStale\":false}}", + "aggregateprogress": { "progress": 0, "total": 0 }, + "progress": { "progress": 0, "total": 0 }, + "votes": { + "self": "https://jira.mongodb.org/rest/api/2/issue/WORKPLACE-119/votes", + "votes": 10, + "hasVoted": false + }, + "worklog": { "startAt": 0, "maxResults": 20, "total": 0, "worklogs": [] }, + "archivedby": null, + "issuetype": { + "self": "https://jira.mongodb.org/rest/api/2/issuetype/4", + "id": "4", + "description": "An improvement or enhancement to an existing feature or task.", + "iconUrl": "https://jira.mongodb.org/secure/viewavatar?size=xsmall&avatarId=14710&avatarType=issuetype", + "name": "Improvement", + "subtask": false, + "avatarId": 14710 + }, + "timespent": null, + "project": { + "self": "https://jira.mongodb.org/rest/api/2/project/11281", + "id": "11281", + "key": "WORKPLACE", + "name": "Workplace & Real Estate", + "projectTypeKey": "service_desk", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/projectavatar?pid=11281&avatarId=17807", + "24x24": "https://jira.mongodb.org/secure/projectavatar?size=small&pid=11281&avatarId=17807", + "16x16": "https://jira.mongodb.org/secure/projectavatar?size=xsmall&pid=11281&avatarId=17807", + "32x32": "https://jira.mongodb.org/secure/projectavatar?size=medium&pid=11281&avatarId=17807" + }, + "projectCategory": { + "self": "https://jira.mongodb.org/rest/api/2/projectCategory/12111", + "id": "12111", + "description": "", + "name": "FinOps" + } + }, + "resolutiondate": "2014-03-20T17:21:15.000+0000", + "workratio": -1, + "watches": { + "self": "https://jira.mongodb.org/rest/api/2/issue/WORKPLACE-119/watchers", + "watchCount": 10, + "isWatching": false + }, + "created": "2014-01-30T22:22:56.000+0000", + "updated": "2020-06-01T02:23:47.000+0000", + "timeoriginalestimate": null, + "description": "!stall-gap.jpg!\n\nThere is a noticeable gap between the stall panels and the tile wall in the men's room (as illustrated in the left-most portion of the diagram). In the past, some resourceful individuals have hung tissue paper to cover the gap. Unfortunately, that is not a sustainable solution and the janitors tend to remove it (as illustrated in the right-most portion of the diagram).\n\nA somewhat related issue is the stability of the stall door locks. Specifically on the pair of stalls nearest the exit. Oftentimes, when someone enters or leaves an adjacent stall, a door lock will come loose. If the stall panels end up being realigned, it might be reasonable to also re-calibrate the door locks so they are not prone to becoming unlocked position so easily. ([~akshay] may be able to attest to this issue). Do let me know if I should open a separate issue for that, or if we should make it a sub-task of this issue.\n\nThank you,", + "timetracking": {}, + "attachment": [ + { + "self": "https://jira.mongodb.org/rest/api/2/attachment/37000", + "id": "37000", + "filename": "stall-gap.jpg", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jmikola%40mongodb.com", + "name": "jmikola@mongodb.com", + "key": "jmikola@gmail.com", + "emailAddress": "jmikola@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jmikola%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jmikola%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jmikola%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jmikola%40gmail.com&avatarId=11105" + }, + "displayName": "Jeremy Mikola", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-01-30T22:22:56.000+0000", + "size": 144169, + "mimeType": "image/jpeg", + "content": "https://jira.mongodb.org/secure/attachment/37000/stall-gap.jpg", + "thumbnail": "https://jira.mongodb.org/secure/thumbnail/37000/_thumb_37000.png" + } + ], + "flagged": false, + "summary": "Significant stall panel gap in the men's room", + "customfield_11452": { + "id": "34", + "name": "Time to first response", + "_links": { + "self": "https://jira.mongodb.org/rest/servicedeskapi/request/108475/sla/34" + }, + "completedCycles": [] + }, + "customfield_11451": { + "id": "33", + "name": "Time to resolution", + "_links": { + "self": "https://jira.mongodb.org/rest/servicedeskapi/request/108475/sla/33" + }, + "completedCycles": [] + }, + "environment": "Men's room", + "duedate": null, + "comment": { + "comments": [ + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/491172", + "id": "491172", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=carol.huang%4010gen.com", + "name": "carol.huang@10gen.com", + "key": "carol.huang@10gen.com", + "emailAddress": "?", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?avatarId=10123", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&avatarId=10123", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&avatarId=10123", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&avatarId=10123" + }, + "displayName": "carol.huang@10gen.com", + "active": false, + "timeZone": "UTC" + }, + "body": "The gap is also an issue in the women's restroom, for the record. ", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=carol.huang%4010gen.com", + "name": "carol.huang@10gen.com", + "key": "carol.huang@10gen.com", + "emailAddress": "?", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?avatarId=10123", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&avatarId=10123", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&avatarId=10123", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&avatarId=10123" + }, + "displayName": "carol.huang@10gen.com", + "active": false, + "timeZone": "UTC" + }, + "created": "2014-01-30T22:24:55.000+0000", + "updated": "2014-01-30T22:24:55.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/491173", + "id": "491173", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jmikola%40mongodb.com", + "name": "jmikola@mongodb.com", + "key": "jmikola@gmail.com", + "emailAddress": "jmikola@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jmikola%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jmikola%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jmikola%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jmikola%40gmail.com&avatarId=11105" + }, + "displayName": "Jeremy Mikola", + "active": true, + "timeZone": "America/New_York" + }, + "body": "[~carol.huang@10gen.com]: Thank you for coming forward. I've heard rumors but this is the first confirmation I've had.", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jmikola%40mongodb.com", + "name": "jmikola@mongodb.com", + "key": "jmikola@gmail.com", + "emailAddress": "jmikola@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jmikola%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jmikola%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jmikola%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jmikola%40gmail.com&avatarId=11105" + }, + "displayName": "Jeremy Mikola", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-01-30T22:26:53.000+0000", + "updated": "2014-01-30T22:26:53.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/491176", + "id": "491176", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=akshay%40mongodb.com", + "name": "akshay@mongodb.com", + "key": "akshay", + "emailAddress": "akshay@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=akshay&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=akshay&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=akshay&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=akshay&avatarId=11409" + }, + "displayName": "Akshay Kumar", + "active": true, + "timeZone": "America/New_York" + }, + "body": "ProTip: Use the Westin across the street.", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=akshay%40mongodb.com", + "name": "akshay@mongodb.com", + "key": "akshay", + "emailAddress": "akshay@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=akshay&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=akshay&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=akshay&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=akshay&avatarId=11409" + }, + "displayName": "Akshay Kumar", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-01-30T22:31:38.000+0000", + "updated": "2014-01-30T22:31:38.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/491185", + "id": "491185", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=isabelle.davis", + "name": "isabelle.davis", + "key": "isabelle.davis", + "emailAddress": "isabelle@10gen.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=isabelle.davis&avatarId=12232", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=isabelle.davis&avatarId=12232", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=isabelle.davis&avatarId=12232", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=isabelle.davis&avatarId=12232" + }, + "displayName": "Isabelle Davis", + "active": true, + "timeZone": "America/New_York" + }, + "body": "The building is aware. ", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=isabelle.davis", + "name": "isabelle.davis", + "key": "isabelle.davis", + "emailAddress": "isabelle@10gen.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=isabelle.davis&avatarId=12232", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=isabelle.davis&avatarId=12232", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=isabelle.davis&avatarId=12232", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=isabelle.davis&avatarId=12232" + }, + "displayName": "Isabelle Davis", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-01-30T22:40:53.000+0000", + "updated": "2014-01-30T22:40:53.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/518934", + "id": "518934", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=akshay%40mongodb.com", + "name": "akshay@mongodb.com", + "key": "akshay", + "emailAddress": "akshay@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=akshay&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=akshay&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=akshay&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=akshay&avatarId=11409" + }, + "displayName": "Akshay Kumar", + "active": true, + "timeZone": "America/New_York" + }, + "body": "This looks like it has been resolved!", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=akshay%40mongodb.com", + "name": "akshay@mongodb.com", + "key": "akshay", + "emailAddress": "akshay@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=akshay&avatarId=11409", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=akshay&avatarId=11409", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=akshay&avatarId=11409", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=akshay&avatarId=11409" + }, + "displayName": "Akshay Kumar", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-03-19T00:43:05.000+0000", + "updated": "2014-03-19T00:43:05.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/518985", + "id": "518985", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jmikola%40mongodb.com", + "name": "jmikola@mongodb.com", + "key": "jmikola@gmail.com", + "emailAddress": "jmikola@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jmikola%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jmikola%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jmikola%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jmikola%40gmail.com&avatarId=11105" + }, + "displayName": "Jeremy Mikola", + "active": true, + "timeZone": "America/New_York" + }, + "body": "Quite masterfully, I might add. Good work team!", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=jmikola%40mongodb.com", + "name": "jmikola@mongodb.com", + "key": "jmikola@gmail.com", + "emailAddress": "jmikola@mongodb.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?ownerId=jmikola%40gmail.com&avatarId=11105", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&ownerId=jmikola%40gmail.com&avatarId=11105", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&ownerId=jmikola%40gmail.com&avatarId=11105", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&ownerId=jmikola%40gmail.com&avatarId=11105" + }, + "displayName": "Jeremy Mikola", + "active": true, + "timeZone": "America/New_York" + }, + "created": "2014-03-19T04:54:25.000+0000", + "updated": "2014-03-19T04:54:25.000+0000" + }, + { + "self": "https://jira.mongodb.org/rest/api/2/issue/108475/comment/520128", + "id": "520128", + "author": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=kym.ganade", + "name": "kym.ganade", + "key": "kym@10gen.com", + "emailAddress": "kym@10gen.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?avatarId=10122", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&avatarId=10122", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&avatarId=10122", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&avatarId=10122" + }, + "displayName": "Kym Ganade [X]", + "active": true, + "timeZone": "America/Havana" + }, + "body": "Panels Have been added", + "updateAuthor": { + "self": "https://jira.mongodb.org/rest/api/2/user?username=kym.ganade", + "name": "kym.ganade", + "key": "kym@10gen.com", + "emailAddress": "kym@10gen.com", + "avatarUrls": { + "48x48": "https://jira.mongodb.org/secure/useravatar?avatarId=10122", + "24x24": "https://jira.mongodb.org/secure/useravatar?size=small&avatarId=10122", + "16x16": "https://jira.mongodb.org/secure/useravatar?size=xsmall&avatarId=10122", + "32x32": "https://jira.mongodb.org/secure/useravatar?size=medium&avatarId=10122" + }, + "displayName": "Kym Ganade [X]", + "active": true, + "timeZone": "America/Havana" + }, + "created": "2014-03-20T17:21:15.000+0000", + "updated": "2014-03-20T17:21:15.000+0000" + } + ], + "maxResults": 7, + "total": 7, + "startAt": 0 + } + } + }, + { + "topics": [ + "stall panel gap", + "men's restroom", + "women's restroom", + "New York office", + "stall door locks" + ], + "description": "This was filed to alert the workplace team of a significant gap between the stall panels and the tile wall in the men's room at the New York office. The issue also mentions the instability of the stall door locks, particularly those nearest the exit. A commenter confirmed that the gap is also present in the women's restroom. The issue was resolved by adding panels." + } +] diff --git a/packages/mongodb-artifact-generator/src/jiraPromptResponse/topIssues.json b/packages/mongodb-artifact-generator/src/jiraPromptResponse/topIssues.json new file mode 100644 index 000000000..14ffc64d1 --- /dev/null +++ b/packages/mongodb-artifact-generator/src/jiraPromptResponse/topIssues.json @@ -0,0 +1,236 @@ +[ + "PYTHON-3257", + "JAVA-4154", + "GODRIVER-1763", + "CSHARP-3653", + "SERVER-40578", + "SERVER-72774", + "SERVER-46977", + "JAVA-2407", + "SPARK-142", + "GODRIVER-2613", + "COMPASS-4657", + "CSHARP-3095", + "SERVER-46951", + "CSHARP-4058", + "MONGOID-4067", + "GODRIVER-897", + "SERVER-63143", + "CSHARP-1165", + "SERVER-32790", + "CSHARP-1895", + "NODE-5711", + "COMPASS-7283", + "KAFKA-395", + "SPARK-250", + "SERVER-19638", + "RUST-940", + "DOCS-15498", + "NODE-2313", + "SERVER-62699", + "COMPASS-4884", + "TOOLS-2151", + "SERVER-59766", + "SERVER-18335", + "JAVA-2609", + "SERVER-37846", + "SERVER-52537", + "MONGOID-210", + "SERVER-31459", + "JAVA-3452", + "PYTHON-1434", + "JAVA-2569", + "SERVER-32827", + "GODRIVER-1194", + "SERVER-74739", + "csharp-1309", + "CSHARP-4566", + "TOOLS-1059", + "SERVER-53337", + "server-6992", + "WT-3276", + "WT-10563", + "SERVER-35370", + "CSHARP-1592", + "SERVER-34879", + "JAVA-3668", + "SERVER-78369", + "WT-7126", + "SERVER-38549", + "TOOLS-1012", + "JAVA-466", + "NODE-3631", + "NODE-4209", + "CSHARP-1483", + "NODE-3523", + "JAVA-4044", + "SERVER-37554", + "DOCS-7669", + "COMPASS-4547", + "SERVER-59482", + "WT-11173", + "CSHARP-3578", + "SERVER-63417", + "TOOLS-896", + "CSHARP-3282", + "SERVER-47660", + "SERVER-49317", + "SERVER-37814", + "GODRIVER-1763", + "SERVER-46929", + "TOOLS-162", + "SERVER-51343", + "SERVER-46871", + "MOTOR-457", + "SERVER-64005", + "WT-9379", + "SERVER-41216", + "CSHARP-673", + "SERVER-48161", + "SERVER-54571", + "CSHARP-1726", + "SERVER-42366", + "SERVER-57782", + "CSHARP-863", + "PHPC-1180", + "NODE-5662", + "SERVER-31340", + "SERVER-58195", + "CDRIVER-1072", + "DOCS-5270", + "SERVER-31478", + "PYTHON-1880", + "SERVER-55459", + "RUBY-2167", + "SERVER-34604", + "SERVER-25883", + "SERVER-10777", + "RUBY-1917", + "SERVER-50067", + "SERVER-47553", + "GODRIVER-879", + "RUBY-2615", + "MONGOID-5627", + "SERVER-61680", + "NODE-3780", + "SERVER-4683", + "TOOLS-435", + "MONGOSH-1031", + "COMPASS-7575", + "SERVER-31602", + "CSHARP-3270", + "NODE-1649", + "SERVER-67014", + "DOCS-13727", + "SERVER-22056", + "SERVER-53177", + "JAVA-4076", + "CDRIVER-2045", + "MONGOID-518", + "SERVER-20243", + "SERVER-48398", + "SERVER-17357", + "SPARK-352", + "MONGOID-1023", + "COMPASS-2389", + "SERVER-31598", + "SERVER-25760", + "CSHARP-994", + "MONGOSH-818", + "SERVER-6798", + "SERVER-69199", + "SERVER-41791", + "COMPASS-4837", + "MONGOID-247", + "WT-3186", + "TOOLS-2352", + "WT-8003", + "NODE-1051", + "CSHARP-4474", + "SERVER-59926", + "DOCS-12798", + "SERVER-39562", + "SERVER-57086", + "TOOLS-2603", + "SERVER-19470", + "TOOLS-1549", + "SERVER-1603", + "SERVER-8985", + "DOCS-14793", + "DOCS-13654", + "MONGOSH-587", + "SERVER-5044", + "RUST-1779", + "CSHARP-1734", + "SERVER-41449", + "WT-7691", + "SERVER-58935", + "SERVER-9209", + "SERVER-38600", + "SERVER-13520", + "GODRIVER-1182", + "NODE-3654", + "NODE-5305", + "SERVER-45647", + "SERVER-31931", + "SERVER-64822", + "SERVER-10860", + "DOCS-12941", + "GODRIVER-3062", + "COMPASS-2437", + "SPARK-197", + "SERVER-27369", + "SERVER-34633", + "SERVER-18869", + "SERVER-38367", + "NODE-1917", + "MONGOID-3525", + "SERVER-27281", + "KAFKA-272", + "CDRIVER-4112", + "DOCS-8907", + "PHPC-682", + "SERVER-33122", + "MONGOSH-296", + "RUST-392", + "SERVER-30518", + "RUST-300", + "JAVA-1086", + "SERVER-59339", + "SERVER-59608", + "DOCS-6979", + "CSHARP-2455", + "NODE-962", + "SERVER-13025", + "WT-9805", + "SERVER-86419", + "CSHARP-1554", + "SERVER-62277", + "SERVER-58264", + "JAVA-3023", + "SPARK-60", + "WT-7475", + "SERVER-19445", + "SERVER-37319", + "SERVER-68365", + "MONGOID-4976", + "PYTHON-1167", + "SERVER-6720", + "SERVER-59178", + "SERVER-16975", + "SERVER-49429", + "SERVER-62759", + "MONGOID-3439", + "CSHARP-1984", + "JAVA-4471", + "SERVER-45906", + "TOOLS-2208", + "SERVER-64293", + "SERVER-27832", + "SERVER-69000", + "SERVER-48934", + "DOCS-11156", + "KAFKA-348", + "DOCS-10947", + "CSHARP-3903" +] diff --git a/packages/mongodb-artifact-generator/src/operations.ts b/packages/mongodb-artifact-generator/src/operations.ts index bdf85ad26..f2da157ad 100644 --- a/packages/mongodb-artifact-generator/src/operations.ts +++ b/packages/mongodb-artifact-generator/src/operations.ts @@ -12,16 +12,20 @@ export async function summarize({ }) { generate = generate ?? makeGenerateChatCompletion(); const analyzeCodeChat = [ - systemMessage(stripIndents` + systemMessage({ + content: stripIndents` Your task is to analyze a provided code snippet and write a succinct description of the code's purpose as well as its style and other notable choices. Limit your response to 100 words. - `), - userMessage(html` + `, + }), + userMessage({ + content: html` Analyze the following code snippet and describe its purpose: ${sourceCode} - `), + `, + }), ]; const analyzeCodeOutput = await generate(analyzeCodeChat); @@ -40,16 +44,20 @@ export async function summarizePage({ }) { generate = generate ?? makeGenerateChatCompletion(); const analyzeCodeChat = [ - systemMessage(stripIndents` - Your task is to analyze a provided documentation page and write a succinct - description of the content as well as its style and other notable choices. - Limit your response to 100 words. - `), - userMessage(html` - Analyze the following page and describe its contents: - - ${sourcePage} - `), + systemMessage({ + content: stripIndents` + Your task is to analyze a provided documentation page and write a succinct + description of the content as well as its style and other notable choices. + Limit your response to 100 words. + `, + }), + userMessage({ + content: html` + Analyze the following page and describe its contents: + + ${sourcePage} + `, + }), ]; const analyzeCodeOutput = await generate(analyzeCodeChat); @@ -74,39 +82,43 @@ export async function translate({ }) { generate = generate ?? makeGenerateChatCompletion(); const translateCodeChat = [ - systemMessage(stripIndents` - You transform source code files from one programming language into another programming language. + systemMessage({ + content: stripIndents` + You transform source code files from one programming language into another programming language. - Assume the provided code is correct. + Assume the provided code is correct. - Use idiomatic code and style conventions in the tranformed output. + Use idiomatic code and style conventions in the tranformed output. - Output only the transformed code with no additional text. - `), - userMessage(html` - ${ - !searchResults - ? "" - : html` - Here is some helpful context for the page you will be translating: + Output only the transformed code with no additional text. + `, + }), + userMessage({ + content: html` + ${ + !searchResults + ? "" + : html` + Here is some helpful context for the page you will be translating: - ${searchResults} + ${searchResults} - ` - } - This is a description of the original source code file: + ` + } + This is a description of the original source code file: - ${sourceDescription} + ${sourceDescription} - The desired output has the following description: + The desired output has the following description: - ${targetDescription} + ${targetDescription} - Here is the source code file to translate: + Here is the source code file to translate: - ${sourceCode} + ${sourceCode} - Now translate to the desired output. Return only the transformed code with no additional text.`), + Now translate to the desired output. Return only the transformed code with no additional text.`, + }), ]; const output = await generate(translateCodeChat); @@ -131,38 +143,42 @@ export async function translatePage({ }) { generate = generate ?? makeGenerateChatCompletion(); const translateCodeChat = [ - systemMessage(stripIndents` - You transform technical documentation pages based on a user's requests. The user will provide the content to transform and a description of the desired output. + systemMessage({ + content: stripIndents` + You transform technical documentation pages based on a user's requests. The user will provide the content to transform and a description of the desired output. - For example, you might translate a documentation page from one programming language to another, or from one platform to another. + For example, you might translate a documentation page from one programming language to another, or from one platform to another. - Assume the provided information is correct and do your best to completely and accurately represent all of relevant provided information in your output. + Assume the provided information is correct and do your best to completely and accurately represent all of relevant provided information in your output. - Use idiomatic suggestions and style conventions in the tranformed output. - `), - userMessage(html` - ${ - !searchResults - ? "" - : html` - Here is some helpful context for the page you will be translating: + Use idiomatic suggestions and style conventions in the tranformed output. + `, + }), + userMessage({ + content: html` + ${ + !searchResults + ? "" + : html` + Here is some helpful context for the page you will be translating: - ${searchResults} + ${searchResults} - ` - } - This is a description of the original page: + ` + } + This is a description of the original page: - ${sourceDescription} + ${sourceDescription} - The desired output has the following description: + The desired output has the following description: - ${targetDescription} + ${targetDescription} - Now translate the following original page text to match desired output. Return only the translated page with no additional text. + Now translate the following original page text to match desired output. Return only the translated page with no additional text. - ${sourcePage} - `), + ${sourcePage} + `, + }), ]; const output = await generate(translateCodeChat); @@ -185,30 +201,34 @@ export async function bluehawkify({ }) { generate = generate ?? makeGenerateChatCompletion(); const bluehawkifyChat = [ - systemMessage(stripIndents` - You transform source code files into unit test files that ensure the behavior of the provided source code. - The unit test files should include the provided source code rather than importing from another file or otherwise obfuscating the source code. - Assume the provided code is correct. - Use idiomatic code and style conventions in the test code. - Output only the test file code with no additional text. - `), - userMessage(html` - Adapt the following code snippet into a file that tests the provided source code. Make sure to import the necessary dependencies and declare any necessary types, classes, structs, etc. + systemMessage({ + content: stripIndents` + You transform source code files into unit test files that ensure the behavior of the provided source code. + The unit test files should include the provided source code rather than importing from another file or otherwise obfuscating the source code. + Assume the provided code is correct. + Use idiomatic code and style conventions in the test code. + Output only the test file code with no additional text. + `, + }), + userMessage({ + content: html` + Adapt the following code snippet into a file that tests the provided source code. Make sure to import the necessary dependencies and declare any necessary types, classes, structs, etc. - The source code snippet has the following description: + The source code snippet has the following description: - ${sourceDescription} + ${sourceDescription} - The desired output has the following description: + The desired output has the following description: - ${targetDescription} + ${targetDescription} - Here is the source code snippet: + Here is the source code snippet: - ${sourceCode} + ${sourceCode} - Now generate the desired output. Return only the code with no additional text., - `), + Now generate the desired output. Return only the code with no additional text., + `, + }), ]; const output = await generate(bluehawkifyChat); @@ -229,28 +249,32 @@ export async function generatePage({ }) { generate = generate ?? makeGenerateChatCompletion(); const translateCodeChat = [ - systemMessage(stripIndents` - Generate a new MongoDB documentation page based on the provided description. - `), - userMessage(html` - ${ - !searchResults - ? "" - : html` - Here is some helpful context for the page you will be creating: - - ${searchResults} - ` - } - - The desired output has the following description: - - ${targetDescription} - - Here is a template - - Now create the documentation page matching the desired output. Return only the translated page with no additional text. - `), + systemMessage({ + content: stripIndents` + Generate a new MongoDB documentation page based on the provided description. + `, + }), + userMessage({ + content: html` + ${ + !searchResults + ? "" + : html` + Here is some helpful context for the page you will be creating: + + ${searchResults} + ` + } + + The desired output has the following description: + + ${targetDescription} + + Here is a template + + Now create the documentation page matching the desired output. Return only the translated page with no additional text. + `, + }), ]; const output = await generate(translateCodeChat); diff --git a/packages/mongodb-artifact-generator/src/release-notes/createChangelog.ts b/packages/mongodb-artifact-generator/src/release-notes/createChangelog.ts index 89618ce42..859c5a961 100644 --- a/packages/mongodb-artifact-generator/src/release-notes/createChangelog.ts +++ b/packages/mongodb-artifact-generator/src/release-notes/createChangelog.ts @@ -30,37 +30,41 @@ export async function createChangelog({ }: CreateChangelogArgs) { generate = generate ?? makeGenerateChatCompletion(); const chatTemplate = [ - systemMessage(stripIndents` - Your task is to create a thorough changelog for a software release. A changelog is a file which contains a curated, chronologically ordered list of notable changes for each version of a project. + systemMessage({ + content: stripIndents` + Your task is to create a thorough changelog for a software release. A changelog is a file which contains a curated, chronologically ordered list of notable changes for each version of a project. - You will be provided a data set that describes artifacts associated with a release of the software. Based on that data set, you will generate a set of change log entries. + You will be provided a data set that describes artifacts associated with a release of the software. Based on that data set, you will generate a set of change log entries. - A changelog entry is a brief description of the change written in the present tense. + A changelog entry is a brief description of the change written in the present tense. - Limit each changelog entry length to 1 or 2 sentences and a maximum of 50 words. + Limit each changelog entry length to 1 or 2 sentences and a maximum of 50 words. - If the provided data does not contain any changes, return only and exactly the following text: ${NO_CHANGELOG_ENTRY} + If the provided data does not contain any changes, return only and exactly the following text: ${NO_CHANGELOG_ENTRY} - Format each entry as a markdown unordered list item. For multiple entries, separate each list item with a newline. + Format each entry as a markdown unordered list item. For multiple entries, separate each list item with a newline. - For example, a set of changelog entries might resemble the following: + For example, a set of changelog entries might resemble the following: - - Adds the atlas projects update command. - - Fixes an issue with the atlas kubernetes config generate command. - - Adds support for Podman 5.0.0 for local deployments. - - Fixes an issue where the Atlas CLI didn't sign Windows binaries. - - Adds a CommonJS (CJS) build output for mongodb-chatbot-ui package, allowing it to be used in environments that require CJS modules. - - Upgrades CLI Go version to 1.17. + - Adds the atlas projects update command. + - Fixes an issue with the atlas kubernetes config generate command. + - Adds support for Podman 5.0.0 for local deployments. + - Fixes an issue where the Atlas CLI didn't sign Windows binaries. + - Adds a CommonJS (CJS) build output for mongodb-chatbot-ui package, allowing it to be used in environments that require CJS modules. + - Upgrades CLI Go version to 1.17. -
- ${projectDescription} -
- `), - userMessage(stripIndents` - ${JSON.stringify(artifactSummary.artifact)} +
+ ${projectDescription} +
+ `, + }), + userMessage({ + content: stripIndents` + ${JSON.stringify(artifactSummary.artifact)} - ${artifactSummary.summary} - `), + ${artifactSummary.summary} + `, + }), ]; logger?.appendArtifact( `chatTemplates/changelog-${safeFileName( diff --git a/packages/mongodb-artifact-generator/src/release-notes/summarizeReleaseArtifacts.ts b/packages/mongodb-artifact-generator/src/release-notes/summarizeReleaseArtifacts.ts index 9ea3a5388..49da031de 100644 --- a/packages/mongodb-artifact-generator/src/release-notes/summarizeReleaseArtifacts.ts +++ b/packages/mongodb-artifact-generator/src/release-notes/summarizeReleaseArtifacts.ts @@ -30,22 +30,24 @@ export async function summarizeReleaseArtifact({ }: SummarizeReleaseArtifactArgs) { generate = generate ?? makeGenerateChatCompletion(); const chatTemplate = [ - systemMessage(stripIndents` - Your task is to analyze a provided artifact associated with a software release and write a succinct summarized description. Artifacts may include information from task tracking software like Jira, source control information like Git diffs and commit messages, or other sources. + systemMessage({ + content: stripIndents` + Your task is to analyze a provided artifact associated with a software release and write a succinct summarized description. Artifacts may include information from task tracking software like Jira, source control information like Git diffs and commit messages, or other sources. - Your summary should be a brief, high-level description of the artifact's contents and purpose. The goal is to provide a clear, concise overview that can be used to generate one or more change log entries for the release. Focus on the facts of the changes. Avoid value judgments or subjective language such as how substantial an update was. Do not infer the broader intent behind the changes or speculate on future implications unless specifically mentioned in the artifact. + Your summary should be a brief, high-level description of the artifact's contents and purpose. The goal is to provide a clear, concise overview that can be used to generate one or more change log entries for the release. Focus on the facts of the changes. Avoid value judgments or subjective language such as how substantial an update was. Do not infer the broader intent behind the changes or speculate on future implications unless specifically mentioned in the artifact. - The user may prepend the artifact with additional style guide information or other metadata. This section is denoted by frontmatter enclosed in triple dashes (---). Do not mention this frontmatter in the summary but do follow its guidance. + The user may prepend the artifact with additional style guide information or other metadata. This section is denoted by frontmatter enclosed in triple dashes (---). Do not mention this frontmatter in the summary but do follow its guidance. - Assume the reader of your summary is familiar with the product's features and use cases. + Assume the reader of your summary is familiar with the product's features and use cases. - Limit the summary length to a maximum of 200 words. + Limit the summary length to a maximum of 200 words. -
- ${projectDescription} -
- `), - userMessage(createUserPromptForReleaseArtifact(artifact)), +
+ ${projectDescription} +
+ `, + }), + userMessage({ content: createUserPromptForReleaseArtifact(artifact) }), ]; logger?.appendArtifact( `chatTemplates/${safeFileName(releaseArtifactIdentifier(artifact))}.txt`, diff --git a/packages/mongodb-artifact-generator/src/runId.test.ts b/packages/mongodb-artifact-generator/src/runId.test.ts new file mode 100644 index 000000000..28d18eddc --- /dev/null +++ b/packages/mongodb-artifact-generator/src/runId.test.ts @@ -0,0 +1,65 @@ +import { createRunId } from "./runId"; +import { promises as fs, writeFileSync } from "fs"; + +function mockStaticDate(input: ConstructorParameters[0]) { + const date = new Date(input); + jest.spyOn(global, "Date").mockImplementation(() => date); +} + +describe("createRunId", () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + it("creates a runId based on the current date", () => { + mockStaticDate("2024-08-21T20:20:01.188Z"); + expect(new Date().getTime()).toBe(1724271601188); + expect(createRunId()).toBe("20240821-2020-4asie"); + }); + + it("does nothing to ensure that namespace is a valid filename", () => { + mockStaticDate("2024-08-21T20:20:01.188Z"); + const ns = `--_._((my-nam/es \\ pace-`; + const runId = createRunId(ns); + expect(runId).toBe("--_._((my-nam/es \\ pace--20240821-2020-4asie"); + expect(() => + writeFileSync( + `./${runId}.test.json`, + "this should error because of an invalid file name" + ) + ).toThrow(`ENOENT: no such file or directory, open './${runId}.test.json'`); + }); + + it("allows you to prepend a namespace prefix", () => { + mockStaticDate("2024-08-21T20:20:01.188Z"); + const ns = "my-namespace"; + const runIdWithoutNs = createRunId(); + const runIdWithNs = createRunId(ns); + expect(runIdWithoutNs).toBe("20240821-2020-4asie"); + expect(runIdWithNs).toBe("my-namespace-20240821-2020-4asie"); + }); + + it("creates runIds that sort by the time they were created", () => { + mockStaticDate("2024-08-21T20:20:01.188Z"); + const runId1 = createRunId(); + jest.restoreAllMocks(); + mockStaticDate("2024-08-21T20:20:17.223Z"); + const runId2 = createRunId(); + jest.restoreAllMocks(); + mockStaticDate("2024-08-21T20:21:01.011Z"); + const runId3 = createRunId(); + jest.restoreAllMocks(); + mockStaticDate("2024-09-01T01:00:00.003Z"); + const runId4 = createRunId(); + + expect(runId1).toBe("20240821-2020-4asie"); + expect(runId2).toBe("20240821-2020-4asur"); + expect(runId3).toBe("20240821-2021-4atsk"); + expect(runId4).toBe("20240901-0100-iv734"); + expect(runId1 < runId2).toBe(true); + expect(runId2 < runId3).toBe(true); + expect(runId3 < runId4).toBe(true); + expect(runId1.localeCompare(runId2)).toBe(-1); + expect(runId2.localeCompare(runId3)).toBe(-1); + expect(runId3.localeCompare(runId4)).toBe(-1); + }); +}); diff --git a/packages/mongodb-artifact-generator/src/runId.ts b/packages/mongodb-artifact-generator/src/runId.ts new file mode 100644 index 000000000..57be28fd0 --- /dev/null +++ b/packages/mongodb-artifact-generator/src/runId.ts @@ -0,0 +1,14 @@ +export function createRunId(namespace?: string) { + const now = new Date(); + const [date, time] = now.toISOString().split("T"); + // Format a string for the current day in YYYYMMDD format + const datestamp = date.replace(/-/g, ""); + // Format a string for the current time in 24H HHMM format + const minutestamp = time.replace(/:/g, "").slice(0, 4); + const millistamp = now + .getTime() + .toString(36) + .slice(2, 2 + 5); + const timestamp = `${datestamp}-${minutestamp}-${millistamp}`; + return namespace ? `${namespace}-${timestamp}` : timestamp; +} diff --git a/packages/mongodb-artifact-generator/src/runlogger.ts b/packages/mongodb-artifact-generator/src/runlogger.ts index dbeaa703c..27e44d43c 100644 --- a/packages/mongodb-artifact-generator/src/runlogger.ts +++ b/packages/mongodb-artifact-generator/src/runlogger.ts @@ -1,6 +1,7 @@ import { promises as fs } from "fs"; import path from "path"; import { z } from "zod"; +import { createRunId } from "./runId"; import { ObjectId } from "mongodb-rag-core/mongodb"; export type Artifact = z.infer; @@ -63,7 +64,7 @@ export class RunLogger { topic: string; constructor(args: RunLoggerArgs) { - this.#runId = args.runId ?? new ObjectId().toHexString(); + this.#runId = args.runId ?? createRunId(); this.topic = args.topic; } diff --git a/packages/mongodb-artifact-generator/tsconfig.json b/packages/mongodb-artifact-generator/tsconfig.json index 58c159dd3..a8e7e3be8 100644 --- a/packages/mongodb-artifact-generator/tsconfig.json +++ b/packages/mongodb-artifact-generator/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "lib": ["ES2022"], "target": "ES2022", - "outDir": "./build" + "outDir": "./build", + "resolveJsonModule": true, }, - "include": ["./src/**/*.ts"] + "include": ["./src/**/*.ts", "./src/**/*.json"], }