Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
sqs committed Dec 23, 2023
1 parent 4c96307 commit 5a6aa03
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 0 deletions.
50 changes: 50 additions & 0 deletions provider/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Docs context provider for OpenCodeGraph

This is a context provider for [OpenCodeGraph](https://opencodegraph.org) that indexes a documentation corpus and annotates your code with links to relevant documentation pages.

## Screenshot

![Screenshot of OpenCodeGraph docs annotations](<TODO(sqs)>)

_TODO(sqs)_

Visit the [OpenCodeGraph playground](https://opencodegraph.org/playground) for a live example.

## Usage

Add the following to your settings in any OpenCodeGraph client:

```json
"opencodegraph.providers": {
// ...other providers...
"https://opencodegraph.org/npm/@opencodegraph/provider-docs": {
// TODO(sqs)
}
},
```

TODO(sqs)

See "[Configuration](#configuration)" for more.

Tips:

- If you're using VS Code, you can put the snippet above in `.vscode/settings.json` in the repository or workspace root to configure per-repository links.
- Play around with the docs provider in realtime on the [OpenCodeGraph playground](https://opencodegraph.org/playground).

## Configuration

<!-- Keep in sync with index.ts -->

```typescript
/** Settings for the docs OpenCodeGraph provider. */
export interface Settings {
// TODO(sqs)
}
```

## Development

- [Source code](https://sourcegraph.com/github.com/sourcegraph/opencodegraph/-/tree/provider/docs)
- [Docs](https://opencodegraph.org/docs/providers/docs)
- License: Apache 2.0
57 changes: 57 additions & 0 deletions provider/docs/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { type AnnotationsResult, type CapabilitiesResult } from '@opencodegraph/provider'
import { describe, expect, test } from 'vitest'
import prometheus, { type Settings } from './index'

describe('prometheus', () => {
const SETTINGS: Settings = {
metricRegistrationPatterns: [
{
path: '**/*.go',
pattern: /prometheus\.HistogramOpts{\s*Name:\s*"([^"]+)"/.source,
urlTemplate: 'https://example.com/?q=$1',
},
],
}

test('capabilities', async () => {
expect(await prometheus.capabilities({}, SETTINGS)).toStrictEqual<CapabilitiesResult>({
selector: [{ path: '**/*.go' }],
})
})

test('annotations', () => {
expect(
prometheus.annotations(
{
file: 'file:///a/b.go',
content: `
// histogram is a Prometheus metric.
var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
Name: "random_numbers",
Help: "A histogram of normally distributed random numbers.",
Buckets: prometheus.LinearBuckets(-3, .1, 61),
})`.trim(),
},
SETTINGS
)
).toEqual<AnnotationsResult>({
items: [
{
id: 'random_numbers',
title: '📟 Prometheus metric: random_numbers',
url: 'https://example.com/?q=random_numbers',
preview: true,
},
],
annotations: [
{
item: { id: 'random_numbers' },
range: {
start: { line: 2, character: 14 },
end: { line: 2, character: 28 },
},
},
],
})
})
})
155 changes: 155 additions & 0 deletions provider/docs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import {
matchGlob,
type AnnotationsParams,
type AnnotationsResult,
type CapabilitiesParams,
type CapabilitiesResult,
type OpenCodeGraphItem,
type OpenCodeGraphPosition,
type OpenCodeGraphProvider,
type OpenCodeGraphRange,
} from '@opencodegraph/provider'

/** Settings for the Prometheus OpenCodeGraph provider. */
export interface Settings {
/**
* Patterns that match metric registrations.
*/
metricRegistrationPatterns?: MetricRegistrationPattern[]
}

interface MetricRegistrationPattern {
/**
* Glob pattern matching the file URIs in which to search for this pattern.
*/
path: string

/**
* Regexp pattern whose first capture group matches the metric name.
*
* @example `prometheus\\.HistogramOpts{\\s*Name:\\s*"([^"]+)`
* @example `new promClient\\.Histogram\\({\\s*name: '([^']+)`
*/
pattern: string

/**
* The URL to view matching metrics on Prometheus, Grafana, or another metrics viewer, with $1
* replaced by the metric name.
*
* @example https://prometheus.demo.do.prometheus.io/graph?g0.expr=$1&g0.tab=0
* @example
* https://grafana.example.com/explore?left=%5B%22now-6h%22,%22now%22,%22Prometheus%22,%7B%22expr%22:%22$1%22%7D%5D
*/
urlTemplate: string
}

/**
* An [OpenCodeGraph](https://opencodegraph.org) provider that lets you hover over a Prometheus
* metric in your code to see what it's doing in prod and to click through to the live metrics on
* [Prometheus](https://prometheus.io), [Grafana](https://grafana.com/), or another metrics viewer.
*
* These links will be visible in every dev's editor, in code search, on the code host, and in code
* review (assuming all of those tools have OpenCodeGraph support).
*
* - TODO(sqs): Make this find dashboards containing the metric (like
* https://github.com/panodata/grafana-wtf).
* - TODO(sqs): Hit the Prometheus/Grafana APIs to fetch data (eg `curl -XPOST
* 'https://prometheus.demo.do.prometheus.io/api/v1/query?query=go_gc_duration_seconds&timeout=200ms'`).
*/
const prometheus: OpenCodeGraphProvider<Settings> = {
capabilities(_params: CapabilitiesParams, settings: Settings): CapabilitiesResult {
return { selector: settings.metricRegistrationPatterns?.map(({ path }) => ({ path })) || [] }
},

annotations(params: AnnotationsParams, settings: Settings): AnnotationsResult {
const compiledPatterns:
| (Pick<MetricRegistrationPattern, 'urlTemplate'> & {
pattern: RegExp
matchPath: (path: string) => boolean
})[]
| undefined = settings.metricRegistrationPatterns?.map(({ pattern, path, ...other }) => ({
...other,
pattern: new RegExp(pattern, 'dgs'),
matchPath: matchGlob(path),
}))

const positionCalculator = createFilePositionCalculator(params.content)

const result: AnnotationsResult = {
items: [],
annotations: [],
}
const hasItem = (id: string): boolean => result.items.some(item => item.id === id)

for (const { matchPath, pattern, urlTemplate } of compiledPatterns || []) {
if (!matchPath(new URL(params.file).pathname)) {
continue
}

const ranges = matchResults(pattern, params.content, positionCalculator)
for (const { range, metricName } of ranges) {
const item: OpenCodeGraphItem = {
id: '',
title: `📟 Prometheus metric: ${metricName}`,
url: urlTemplate.replaceAll('$1', metricName),
preview: true,
}
item.id = metricName

result.annotations.push({
item: { id: item.id },
range,
})

if (!hasItem(item.id)) {
result.items.push(item)
}
}
}

return result
},
}

export default prometheus

interface MatchResult {
range: OpenCodeGraphRange
metricName: string
}

function matchResults(pattern: RegExp, content: string, pos: PositionCalculator): MatchResult[] {
const results: MatchResult[] = []
for (const match of content.matchAll(pattern)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const [start, end] = match.indices![1]
results.push({
range: {
start: pos(start),
end: pos(end),
},
metricName: match[1],
})
break // only add one match per line
}
return results
}

type PositionCalculator = (offset: number) => OpenCodeGraphPosition
function createFilePositionCalculator(content: string): PositionCalculator {
const lines = content.split('\n')
return (offset: number) => {
let line = 0
let character = 0
while (line < lines.length && offset > 0) {
const lineLength = lines[line].length + 1 // +1 for the newline
if (lineLength > offset) {
character = offset
break
}
offset -= lineLength
line += 1
}
return { line, character }
}
}
28 changes: 28 additions & 0 deletions provider/docs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@opencodegraph/provider-prometheus",
"version": "0.0.2",
"description": "Hover over a Prometheus metric to see what it's doing in prod (OpenCodeGraph provider)",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/sourcegraph/opencodegraph",
"directory": "provider/prometheus"
},
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"index.ts",
"!**/*.test.*",
"README.md"
],
"sideEffects": false,
"scripts": {
"build": "tsc --build",
"test": "vitest"
},
"dependencies": {
"@opencodegraph/provider": "workspace:*"
}
}
12 changes: 12 additions & 0 deletions provider/docs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../.config/tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"rootDir": ".",
"outDir": "dist",
"lib": ["ESNext"],
},
"include": ["*.ts"],
"exclude": ["dist", "vitest.config.ts"],
"references": [{ "path": "../../lib/provider" }],
}
3 changes: 3 additions & 0 deletions provider/docs/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({})
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
{ "path": "client/vscode/dev" },
{ "path": "client/vscode/test/integration" },
{ "path": "client/web-playground" },
{ "path": "provider/docs" },
{ "path": "provider/hello-world" },
{ "path": "provider/links" },
{ "path": "provider/prometheus" },
Expand Down

0 comments on commit 5a6aa03

Please sign in to comment.