Skip to content
This repository was archived by the owner on Mar 17, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"extends": [
"@sourcegraph/eslint-config"
],
"extends": ["@sourcegraph/eslint-config"],
"parserOptions": {
"project": "tsconfig.json"
}
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@
"serve": "yarn run symlink-package && parcel serve --no-hmr --out-file dist/search-insights.js src/search-insights.ts",
"watch:typecheck": "tsc -p tsconfig.json -w",
"watch:build": "tsc -p tsconfig.dist.json -w",
"sourcegraph:prepublish": "yarn run typecheck && yarn run build"
"sourcegraph:prepublish": "yarn run typecheck && yarn run build",
"prettier": "prettier '**/{*.{js?(on),ts?(x),graphql,md,scss},.*.js?(on)}' --write --list-different --config prettier.config.js"
},
"browserslist": [
"last 1 Chrome versions",
Expand All @@ -154,6 +155,7 @@
"lnfs-cli": "^2.1.0",
"mkdirp": "^1.0.4",
"parcel-bundler": "^1.12.4",
"prettier": "^2.3.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added prettier and prettier script to align code style in this repo

"sourcegraph": "^24.7.0",
"ts-graphql-plugin": "^1.12.0",
"typescript": "^3.8.3"
Expand Down
2 changes: 1 addition & 1 deletion prettier.config.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('@sourcegraph/prettierrc')
module.exports = require('@sourcegraph/prettierrc')
177 changes: 112 additions & 65 deletions src/search-insights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ const queryGraphQL = async <T = IQuery>(query: string, variables: object = {}):
if (errors && errors.length > 0) {
throw new Error(errors.map(e => e.message).join('\n'))
}
return (data as any) as T
return data as any as T
}

/**
* Special empty data point value for line chart.
*/
const EMPTY_DATA_POINT_VALUE = null

interface Insight {
title: string
subtitle?: string
Expand Down Expand Up @@ -56,58 +61,72 @@ export function activate(context: sourcegraph.ExtensionContext): void {
let previousSubscriptions = new Subscription()

context.subscriptions.add(
insightChanges.pipe(
tap(() => {
previousSubscriptions.unsubscribe()
previousSubscriptions = new Subscription()
context.subscriptions.add(previousSubscriptions)
}),
mergeAll()
).subscribe(([id, insight]) => {
if (!insight) {
return
}
const { repositories = 'current' } = insight
insightChanges
.pipe(
tap(() => {
previousSubscriptions.unsubscribe()
previousSubscriptions = new Subscription()
context.subscriptions.add(previousSubscriptions)
}),
mergeAll()
)
.subscribe(([id, insight]) => {
if (!insight) {
return
}
const { repositories = 'current' } = insight

// TODO diff and unregister removed insights
if (repositories === 'current') {
context.subscriptions.add(
sourcegraph.app.registerViewProvider(`${id}.directory`, {
where: 'directory',
provideView: async ({ viewer }) => {
const { repo, path } = resolveDocumentURI(viewer.directory.uri)
return getInsightContent([repo], path, insight)
},
})
)
} else if (Array.isArray(repositories)) {
const provideView = (): Promise<sourcegraph.View> =>
getInsightContent(repositories, undefined, insight)

// TODO diff and unregister removed insights
if (repositories === 'current') {
context.subscriptions.add(
sourcegraph.app.registerViewProvider(`${id}.directory`, {
const directoryPageProvider = sourcegraph.app.registerViewProvider(`${id}.directory`, {
where: 'directory',
provideView: async ({ viewer }) => {
const { repo, path } = resolveDocumentURI(viewer.directory.uri)
return getInsightContent([repo], path, insight)
},
provideView,
})
)
} else if (Array.isArray(repositories)) {
const provideView = (): Promise<sourcegraph.View> => getInsightContent(repositories, undefined, insight)

const directoryPageProvider = sourcegraph.app.registerViewProvider(`${id}.directory`, {
where: 'directory',
provideView,
})

const homePageProvider = sourcegraph.app.registerViewProvider(`${id}.homepage`, {
where: 'homepage',
provideView,
})

const insightPageProvider = sourcegraph.app.registerViewProvider(`${id}.insightsPage`, {
where: 'insightsPage',
provideView,
})

// Pass next providers to enclosure subscription bag in case if we got update
// from the user/org settings in order for us to be able to close provider observables.
previousSubscriptions.add(insightPageProvider)
previousSubscriptions.add(directoryPageProvider)
previousSubscriptions.add(homePageProvider)
}
})

const homePageProvider = sourcegraph.app.registerViewProvider(`${id}.homepage`, {
where: 'homepage',
provideView,
})

const insightPageProvider = sourcegraph.app.registerViewProvider(`${id}.insightsPage`, {
where: 'insightsPage',
provideView,
})

// Pass next providers to enclosure subscription bag in case if we got update
// from the user/org settings in order for us to be able to close provider observables.
previousSubscriptions.add(insightPageProvider)
previousSubscriptions.add(directoryPageProvider)
previousSubscriptions.add(homePageProvider)
}
})
)
}

interface InsightSeriesData {
date: number
[seriesName: string]: number
}

interface RepoCommit {
date: Date
commit: string
repo: string
}

async function getInsightContent(
repos: string[],
path: string | undefined,
Expand All @@ -118,12 +137,31 @@ async function getInsightContent(

const pathRegexp = path ? `^${escapeRegExp(path)}/` : undefined

// -------- Initialize data ---------
const data: InsightSeriesData[] = []

for (const date of dates) {
const dataIndex = dates.indexOf(date)

// Initialize data series object by all dates.
data[dataIndex] = {
date: date.getTime(),
// Initialize all series to 0
...Object.fromEntries(insight.series.map(series => [series.name, EMPTY_DATA_POINT_VALUE])),
}
}

// Get commits to search for each day
const repoCommits = (
await Promise.all(
repos.map(async repo => (await determineCommitsToSearch(dates, repo)).map(commit => ({ repo, ...commit })))
)
).flat()
)
.flat()
// For commit which we can't find we should not run search API request
// instead of it we will use just EMPTY_DATA_POINT_VALUE
.filter(commitData => commitData.commit !== null) as RepoCommit[]

const searchQueries = insight.series.flatMap(({ query, name }) =>
repoCommits.map(({ date, repo, commit }) => ({
name,
Expand Down Expand Up @@ -158,29 +196,29 @@ async function getInsightContent(
// The bulk search may timeout, but a retry is then likely faster because caches are warm
.pipe(retry(3))
.toPromise()

const searchResults = Object.entries(rawSearchResults).map(([field, result]) => {
const index = +field.slice('search'.length)
const query = searchQueries[index]
return { ...query, result }
})

const data: {
date: number
[seriesName: string]: number
}[] = []
// Merge initial data and search API data
for (const { name, date, result } of searchResults) {
const dataKey = name
const dataIndex = dates.indexOf(date)
const obj =
data[dataIndex] ??
(data[dataIndex] = {
date: date.getTime(),
// Initialize all series to 0
...Object.fromEntries(insight.series.map(series => [series.name, 0])),
})
// Sum across repos
const countForRepo = result.results.matchCount
obj[dataKey] += countForRepo
const object = data[dataIndex]

const countForRepo = result?.results.matchCount

// If we got some data that means for this data points we got
// a valid commit in a git history therefore we need write some data
// to this series.
if (object[dataKey] === EMPTY_DATA_POINT_VALUE) {
object[dataKey] = countForRepo ?? 0
} else {
object[dataKey] += countForRepo ?? 0
}
}

return {
Expand Down Expand Up @@ -228,7 +266,12 @@ function getDaysToQuery(step: globalThis.Duration): Date[] {
return dates
}

async function determineCommitsToSearch(dates: Date[], repo: string): Promise<{ date: Date; commit: string }[]> {
interface SearchCommit {
date: Date
commit: string | null
}

async function determineCommitsToSearch(dates: Date[], repo: string): Promise<SearchCommit[]> {
const commitQueries = dates.map(date => {
const before = formatISO(date)
return `repo:^${escapeRegExp(repo)}$ type:commit before:${before} count:1`
Expand Down Expand Up @@ -263,18 +306,22 @@ async function determineCommitsToSearch(dates: Date[], repo: string): Promise<{
)
const commitOids = Object.entries(commitResults).map(([name, search], index) => {
const i = +name.slice('search'.length)
const date = dates[i]

if (i !== index) {
throw new Error(`Expected field ${name} to be at index ${i} of object keys`)
}

if (search.results.results.length === 0) {
throw new Error(`No result for ${commitQueries[i]}`)
console.warn(`No result for ${commitQueries[i]}`)

return { commit: null, date }
}
const commit = (search.results.results[0] as ICommitSearchResult).commit

// Sanity check
const commitDate = commit.committer && new Date(commit.committer.date)
const date = dates[i]

if (!commitDate) {
throw new Error(`Expected commit to have committer: \`${commit.oid}\``)
}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5088,6 +5088,11 @@ prelude-ls@~1.1.2:
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=

prettier@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==

private@^0.1.8:
version "0.1.8"
resolved "https://registry.npmjs.org/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
Expand Down