Skip to content

Commit b075561

Browse files
heiskrsarahs
andauthored
Convert src/ai-editors and src/metrics JavaScript files to TypeScript (#56353)
Co-authored-by: Sarah Schneider <[email protected]>
1 parent 353c7ea commit b075561

File tree

17 files changed

+458
-194
lines changed

17 files changed

+458
-194
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
},
1717
"exports": "./src/frame/server.ts",
1818
"scripts": {
19+
"ai-edit": "tsx src/ai-editors/scripts/ai-edit.ts",
1920
"all-documents": "tsx src/content-render/scripts/all-documents/cli.ts",
2021
"analyze-text": "tsx src/search/scripts/analyze-text.ts",
2122
"analyze-comment": "tsx src/events/scripts/analyze-comment-cli.ts",
@@ -30,6 +31,8 @@
3031
"create-enterprise-issue": "tsx src/ghes-releases/scripts/create-enterprise-issue.ts",
3132
"debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon --inspect src/frame/server.ts",
3233
"delete-orphan-translation-files": "tsx src/workflows/delete-orphan-translation-files.ts",
34+
"docsaudit": "tsx src/metrics/scripts/docsaudit.ts",
35+
"docstat": "tsx src/metrics/scripts/docstat.ts",
3336
"deleted-assets-pr-comment": "tsx src/assets/scripts/deleted-assets-pr-comment.ts",
3437
"deleted-features-pr-comment": "tsx src/data-directory/scripts/deleted-features-pr-comment.ts",
3538
"deprecate-ghes": "tsx src/ghes-releases/scripts/deprecate/index.ts",

src/ai-editors/lib/call-models-api.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/ai-editors/lib/call-models-api.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const modelsCompletionsEndpoint = 'https://models.github.ai/inference/chat/completions'
2+
3+
interface ChatMessage {
4+
role: string
5+
content: string
6+
}
7+
8+
interface ChatCompletionRequest {
9+
messages: ChatMessage[]
10+
model?: string
11+
temperature?: number
12+
max_tokens?: number
13+
}
14+
15+
interface ChatCompletionChoice {
16+
message: {
17+
content: string
18+
role: string
19+
}
20+
finish_reason: string
21+
index: number
22+
}
23+
24+
interface ChatCompletionResponse {
25+
choices: ChatCompletionChoice[]
26+
id: string
27+
object: string
28+
created: number
29+
model: string
30+
usage?: {
31+
prompt_tokens: number
32+
completion_tokens: number
33+
total_tokens: number
34+
}
35+
}
36+
37+
export async function callModelsApi(promptWithContent: ChatCompletionRequest): Promise<string> {
38+
let aiResponse: ChatCompletionChoice
39+
40+
try {
41+
const response = await fetch(modelsCompletionsEndpoint, {
42+
method: 'post',
43+
body: JSON.stringify(promptWithContent),
44+
headers: {
45+
'Content-Type': 'application/json',
46+
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
47+
'X-GitHub-Api-Version': '2022-11-28',
48+
Accept: 'Accept: application/vnd.github+json',
49+
},
50+
})
51+
52+
const data: ChatCompletionResponse = await response.json()
53+
aiResponse = data.choices[0]
54+
} catch (error) {
55+
console.error('Error calling GitHub Models REST API')
56+
throw error
57+
}
58+
59+
return aiResponse.message.content
60+
}

src/ai-editors/scripts/ai-edit.js renamed to src/ai-editors/scripts/ai-edit.ts

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import fs from 'fs'
66
import yaml from 'js-yaml'
77
import path from 'path'
88
import ora from 'ora'
9-
import github from '#src/workflows/github.ts'
10-
import { callModelsApi } from '#src/ai-editors/lib/call-models-api.js'
9+
import { callModelsApi } from '@/ai-editors/lib/call-models-api'
10+
import dotenv from 'dotenv'
11+
dotenv.config()
1112

1213
const __dirname = path.dirname(fileURLToPath(import.meta.url))
1314
const promptDir = path.join(__dirname, '../prompts')
@@ -16,15 +17,36 @@ if (!process.env.GITHUB_TOKEN) {
1617
throw new Error('Error! You must have a GITHUB_TOKEN set in an .env file to run this script.')
1718
}
1819

19-
const responseTypes = {
20+
interface ResponseTypes {
21+
rewrite: string
22+
list: string
23+
json: string
24+
}
25+
26+
const responseTypes: ResponseTypes = {
2027
rewrite: 'Edit the versioning only. Return the edited content.',
2128
list: `Do NOT rewrite the content. Report your edits in numbered list format.`,
2229
json: `Do NOT rewrite the content. Report your edits as a JSON list, with the format { lineNumber, currentText, suggestion }.`,
2330
}
2431

25-
const validResponseTypes = Object.keys(responseTypes)
32+
const validResponseTypes = Object.keys(responseTypes) as Array<keyof ResponseTypes>
33+
34+
interface EditorType {
35+
promptFile: string
36+
description: string
37+
}
38+
39+
interface EditorTypes {
40+
versioning: EditorType
41+
// TODO
42+
// scannability: EditorType
43+
// readability: EditorType
44+
// technical: EditorType
45+
// styleguide: EditorType
46+
// contentModels: EditorType
47+
}
2648

27-
const editorTypes = {
49+
const editorTypes: EditorTypes = {
2850
versioning: {
2951
promptFile: 'versioning-editor.prompt.yml',
3052
description: 'Review against simplifying versioning guidelines.',
@@ -54,14 +76,21 @@ const editorTypes = {
5476
// Add more here...
5577
}
5678

57-
const editorDescriptions = () => {
79+
const editorDescriptions = (): string => {
5880
let str = '\n\n'
5981
Object.entries(editorTypes).forEach(([ed, edObj]) => {
6082
str += `\t${ed}\n\t\t\t${edObj.description}\n\n`
6183
})
6284
return str
6385
}
6486

87+
interface CliOptions {
88+
verbose?: boolean
89+
editor?: Array<keyof EditorTypes>
90+
response?: keyof ResponseTypes
91+
files: string[]
92+
}
93+
6594
const program = new Command()
6695

6796
program
@@ -80,15 +109,15 @@ program
80109
'-f, --files <files...>',
81110
'One or more content file paths in the content directory',
82111
)
83-
.action((options) => {
112+
.action((options: CliOptions) => {
84113
;(async () => {
85114
const spinner = ora('Starting AI review...').start()
86115

87116
const files = options.files
88117
const editors = options.editor || ['versioning']
89118
const response = options.response || 'rewrite'
90119

91-
let responseTypeInstruction
120+
let responseTypeInstruction: string
92121
if (validResponseTypes.includes(response)) {
93122
responseTypeInstruction = responseTypes[response]
94123
} else {
@@ -126,7 +155,8 @@ program
126155
}
127156
}
128157
} catch (err) {
129-
spinner.fail(`Error processing file ${file}: ${err.message}`)
158+
const error = err as Error
159+
spinner.fail(`Error processing file ${file}: ${error.message}`)
130160
process.exitCode = 1
131161
}
132162
}
@@ -135,13 +165,31 @@ program
135165

136166
program.parse(process.argv)
137167

138-
async function callEditor(editorType, responseTypeInstruction, content) {
168+
interface PromptMessage {
169+
content: string
170+
role: string
171+
}
172+
173+
interface PromptData {
174+
messages: PromptMessage[]
175+
model?: string
176+
temperature?: number
177+
max_tokens?: number
178+
}
179+
180+
async function callEditor(
181+
editorType: keyof EditorTypes,
182+
responseTypeInstruction: string,
183+
content: string,
184+
): Promise<string> {
139185
const promptName = editorTypes[editorType].promptFile
140186
const promptPath = path.join(promptDir, promptName)
141-
const prompt = yaml.load(fs.readFileSync(promptPath, 'utf8'))
187+
const prompt = yaml.load(fs.readFileSync(promptPath, 'utf8')) as PromptData
188+
142189
prompt.messages.forEach((msg) => {
143190
msg.content = msg.content.replace('{{responseTypeInstruction}}', responseTypeInstruction)
144191
msg.content = msg.content.replace('{{input}}', content)
145192
})
193+
146194
return callModelsApi(prompt)
147195
}

src/metrics/lib/dates.js renamed to src/metrics/lib/dates.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
const dateOpts = {
1+
const dateOpts: Intl.DateTimeFormatOptions = {
22
year: 'numeric',
33
month: 'long',
44
day: 'numeric',
55
}
66

7+
export interface DateRange {
8+
endDate: string
9+
startDate: string
10+
friendlyRange: string
11+
}
12+
713
// Default to 30 days ago if a range option is not provided
8-
export function getDates(range = '30') {
14+
export function getDates(range: string | number = '30'): DateRange {
915
// Get current datetime in ISO format
1016
const today = new Date()
1117
const todayISO = today.toISOString()
@@ -21,7 +27,7 @@ export function getDates(range = '30') {
2127
}
2228
}
2329

24-
function getDaysAgo(range) {
30+
function getDaysAgo(range: number): Date {
2531
const daysAgo = new Date()
2632
daysAgo.setDate(daysAgo.getDate() - range)
2733
return daysAgo

src/metrics/lib/kusto-client.js renamed to src/metrics/lib/kusto-client.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,46 @@
1-
import { Client as KustoClient, KustoConnectionStringBuilder } from 'azure-kusto-data'
1+
import {
2+
Client as KustoClient,
3+
KustoConnectionStringBuilder,
4+
KustoResultTable,
5+
} from 'azure-kusto-data'
26

37
import dotenv from 'dotenv'
8+
49
dotenv.config()
10+
511
if (!(process.env.KUSTO_CLUSTER || process.env.KUSTO_DATABASE)) {
612
console.error(`Add KUSTO_CLUSTER and KUSTO_DATABASE to your .env file`)
713
process.exit(0)
814
}
9-
const KUSTO_CLUSTER = process.env.KUSTO_CLUSTER
10-
const KUSTO_DATABASE = process.env.KUSTO_DATABASE
1115

12-
export function getKustoClient() {
13-
let client
16+
const KUSTO_CLUSTER = process.env.KUSTO_CLUSTER!
17+
const KUSTO_DATABASE = process.env.KUSTO_DATABASE!
18+
19+
export interface KustoQueryResult {
20+
primaryResults: KustoResultTable[]
21+
}
22+
23+
export function getKustoClient(): KustoClient | undefined {
24+
let client: KustoClient | undefined
25+
1426
try {
1527
const kcsb = KustoConnectionStringBuilder.withAzLoginIdentity(KUSTO_CLUSTER)
1628
client = new KustoClient(kcsb)
1729
} catch (error) {
1830
console.error('Error connecting to Kusto')
1931
console.error(error)
2032
}
33+
2134
return client
2235
}
2336

24-
export async function runQuery(pathToFetch, query, client, queryType, verbose = false) {
37+
export async function runQuery(
38+
pathToFetch: string | string[],
39+
query: string,
40+
client: KustoClient,
41+
queryType: string,
42+
verbose: boolean = false,
43+
): Promise<KustoQueryResult | null> {
2544
// Display query if verbose mode is on
2645
if (verbose) {
2746
console.log(`\n--- EXECUTING QUERY FOR "${queryType.toUpperCase()}" ---`)

src/metrics/queries/bounces.js renamed to src/metrics/queries/bounces.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
1-
import { runQuery } from '#src/metrics/lib/kusto-client.js'
2-
import { SHARED_DECLARATIONS, SHARED_FILTERS } from '#src/metrics/queries/constants.js'
1+
import { runQuery } from '@/metrics/lib/kusto-client'
2+
import { SHARED_DECLARATIONS, SHARED_FILTERS } from '@/metrics/queries/constants'
3+
import type { DateRange } from '@/metrics/lib/dates'
4+
import type { Client as KustoClient } from 'azure-kusto-data'
35

46
const QUERY_TYPE = 'bounces'
57

68
export async function getBounces(
7-
pathToFetch,
8-
client,
9-
dates,
10-
version = null,
11-
verbose = false,
12-
queryType = QUERY_TYPE,
13-
) {
9+
pathToFetch: string | string[],
10+
client: KustoClient,
11+
dates: DateRange,
12+
version: string | null = null,
13+
verbose: boolean = false,
14+
queryType: string = QUERY_TYPE,
15+
): Promise<string> {
1416
const query = getBouncesQuery(pathToFetch, dates, version)
1517
const results = await runQuery(pathToFetch, query, client, queryType, verbose)
18+
19+
if (!results) {
20+
return '0%'
21+
}
22+
1623
const data = JSON.parse(results.primaryResults[0].toString()).data[0]
1724
// Extract Bounces
1825
const bounces = data.Bounces
1926
return bounces
2027
}
2128

22-
export function getBouncesQuery(pathToFetch, dates, version) {
29+
export function getBouncesQuery(
30+
pathToFetch: string | string[],
31+
dates: DateRange,
32+
version: string | null,
33+
): string {
2334
return `
2435
${SHARED_DECLARATIONS(pathToFetch, dates, version)}
2536
let _exits = () {
@@ -28,8 +39,8 @@ export function getBouncesQuery(pathToFetch, dates, version) {
2839
};
2940
_exits
3041
| summarize Bounces=round(
31-
countif(exit_scroll_length < 0.1 and exit_visit_duration < 5) /
32-
toreal(count()),
42+
countif(exit_scroll_length < 0.1 and exit_visit_duration < 5) /
43+
toreal(count()),
3344
2
3445
)
3546
| project Bounces=strcat(toint(

src/metrics/queries/constants.js renamed to src/metrics/queries/constants.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import type { DateRange } from '@/metrics/lib/dates'
2+
13
// SHARED QUERY CONSTANTS
2-
export const SHARED_DECLARATIONS = (path, dates, version) =>
4+
export const SHARED_DECLARATIONS = (
5+
path: string | string[],
6+
dates: DateRange,
7+
version: string | null,
8+
): string =>
39
`
410
let _article = dynamic(['${Array.isArray(path) ? path.join("', '") : path}']);
511
let _articleType = dynamic(null);

0 commit comments

Comments
 (0)