mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 12:25:22 +00:00
Sourcebot V4 introduces authentication, performance improvements and code navigation. Checkout the [migration guide](https://docs.sourcebot.dev/self-hosting/upgrade/v3-to-v4-guide) for information on upgrading your instance to v4. ### Changed - [**Breaking Change**] Authentication is now required by default. Notes: - When setting up your instance, email / password login will be the default authentication provider. - The first user that logs into the instance is given the `owner` role. ([docs](https://docs.sourcebot.dev/docs/more/roles-and-permissions)). - Subsequent users can request to join the instance. The `owner` can approve / deny requests to join the instance via `Settings` > `Members` > `Pending Requests`. - If a user is approved to join the instance, they are given the `member` role. - Additional login providers, including email links and SSO, can be configured with additional environment variables. ([docs](https://docs.sourcebot.dev/self-hosting/configuration/authentication)). - Clicking on a search result now takes you to the `/browse` view. Files can still be previewed by clicking the "Preview" button or holding `Cmd` / `Ctrl` when clicking on a search result. [#315](https://github.com/sourcebot-dev/sourcebot/pull/315) ### Added - [Sourcebot EE] Added search-based code navigation, allowing you to jump between symbol definition and references when viewing source files. [Read the documentation](https://docs.sourcebot.dev/docs/search/code-navigation). [#315](https://github.com/sourcebot-dev/sourcebot/pull/315) - Added collapsible filter panel. [#315](https://github.com/sourcebot-dev/sourcebot/pull/315) ### Fixed - Improved scroll performance for large numbers of search results. [#315](https://github.com/sourcebot-dev/sourcebot/pull/315)
119 lines
No EOL
3.9 KiB
TypeScript
119 lines
No EOL
3.9 KiB
TypeScript
'use server';
|
|
|
|
import { sew, withAuth, withOrgMembership } from "@/actions";
|
|
import { searchResponseSchema } from "@/features/search/schemas";
|
|
import { search } from "@/features/search/searchApi";
|
|
import { isServiceError } from "@/lib/utils";
|
|
import { FindRelatedSymbolsResponse } from "./types";
|
|
import { ServiceError } from "@/lib/serviceError";
|
|
import { SearchResponse } from "../search/types";
|
|
import { OrgRole } from "@sourcebot/db";
|
|
|
|
// The maximum number of matches to return from the search API.
|
|
const MAX_REFERENCE_COUNT = 1000;
|
|
|
|
export const findSearchBasedSymbolReferences = async (
|
|
props: {
|
|
symbolName: string,
|
|
language: string,
|
|
revisionName?: string,
|
|
},
|
|
domain: string,
|
|
): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
|
|
withAuth((session) =>
|
|
withOrgMembership(session, domain, async () => {
|
|
const {
|
|
symbolName,
|
|
language,
|
|
revisionName = "HEAD",
|
|
} = props;
|
|
|
|
const query = `\\b${symbolName}\\b rev:${revisionName} ${getExpandedLanguageFilter(language)} case:yes`;
|
|
|
|
const searchResult = await search({
|
|
query,
|
|
matches: MAX_REFERENCE_COUNT,
|
|
contextLines: 0,
|
|
}, domain);
|
|
|
|
if (isServiceError(searchResult)) {
|
|
return searchResult;
|
|
}
|
|
|
|
return parseRelatedSymbolsSearchResponse(searchResult);
|
|
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true)
|
|
);
|
|
|
|
|
|
export const findSearchBasedSymbolDefinitions = async (
|
|
props: {
|
|
symbolName: string,
|
|
language: string,
|
|
revisionName?: string,
|
|
},
|
|
domain: string,
|
|
): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
|
|
withAuth((session) =>
|
|
withOrgMembership(session, domain, async () => {
|
|
const {
|
|
symbolName,
|
|
language,
|
|
revisionName = "HEAD",
|
|
} = props;
|
|
|
|
const query = `sym:\\b${symbolName}\\b rev:${revisionName} ${getExpandedLanguageFilter(language)}`;
|
|
|
|
const searchResult = await search({
|
|
query,
|
|
matches: MAX_REFERENCE_COUNT,
|
|
contextLines: 0,
|
|
}, domain);
|
|
|
|
if (isServiceError(searchResult)) {
|
|
return searchResult;
|
|
}
|
|
|
|
return parseRelatedSymbolsSearchResponse(searchResult);
|
|
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true)
|
|
);
|
|
|
|
const parseRelatedSymbolsSearchResponse = (searchResult: SearchResponse) => {
|
|
const parser = searchResponseSchema.transform(async ({ files }) => ({
|
|
stats: {
|
|
matchCount: searchResult.stats.matchCount,
|
|
},
|
|
files: files.flatMap((file) => {
|
|
const chunks = file.chunks;
|
|
|
|
return {
|
|
fileName: file.fileName.text,
|
|
repository: file.repository,
|
|
repositoryId: file.repositoryId,
|
|
webUrl: file.webUrl,
|
|
language: file.language,
|
|
matches: chunks.flatMap((chunk) => {
|
|
return chunk.matchRanges.map((range) => ({
|
|
lineContent: chunk.content,
|
|
range: range,
|
|
}))
|
|
})
|
|
}
|
|
}).filter((file) => file.matches.length > 0),
|
|
repositoryInfo: searchResult.repositoryInfo
|
|
}));
|
|
|
|
return parser.parseAsync(searchResult);
|
|
}
|
|
|
|
// Expands the language filter to include all variants of the language.
|
|
const getExpandedLanguageFilter = (language: string) => {
|
|
switch (language) {
|
|
case "TypeScript":
|
|
case "JavaScript":
|
|
case "JSX":
|
|
case "TSX":
|
|
return `(lang:TypeScript or lang:JavaScript or lang:JSX or lang:TSX)`
|
|
default:
|
|
return `lang:${language}`
|
|
}
|
|
} |