Skip to content

Commit eb10d59

Browse files
chore: Sourcebot REST api surface (#290)
1 parent 0119510 commit eb10d59

36 files changed

+746
-550
lines changed

packages/web/.eslintrc.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"eslint:recommended",
88
"plugin:@typescript-eslint/recommended",
99
"plugin:react/recommended",
10-
"plugin:react-hooks/recommended",
1110
"next/core-web-vitals"
1211
],
1312
"rules": {

packages/web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dev": "next dev",
77
"build": "next build",
88
"start": "next start",
9-
"lint": "next lint",
9+
"lint": "cross-env SKIP_ENV_VALIDATION=1 next lint",
1010
"test": "vitest",
1111
"dev:emails": "email dev --dir ./src/emails",
1212
"stripe:listen": "stripe listen --forward-to http://localhost:3000/api/stripe"
@@ -146,6 +146,7 @@
146146
"@types/react-dom": "^18",
147147
"@typescript-eslint/eslint-plugin": "^8.3.0",
148148
"@typescript-eslint/parser": "^8.3.0",
149+
"cross-env": "^7.0.3",
149150
"eslint": "^8",
150151
"eslint-config-next": "14.2.6",
151152
"eslint-plugin-react": "^7.35.0",

packages/web/src/app/[domain]/browse/[...path]/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { FileHeader } from "@/app/[domain]/components/fileHeader";
22
import { TopBar } from "@/app/[domain]/components/topBar";
33
import { Separator } from '@/components/ui/separator';
4-
import { getFileSource, listRepositories } from '@/lib/server/searchService';
4+
import { getFileSource } from '@/features/search/fileSourceApi';
5+
import { listRepositories } from '@/features/search/listReposApi';
56
import { base64Decode, isServiceError } from "@/lib/utils";
67
import { CodePreview } from "./codePreview";
78
import { ErrorCode } from "@/lib/errorCodes";
@@ -57,7 +58,7 @@ export default async function BrowsePage({
5758
if (isServiceError(reposResponse)) {
5859
throw new ServiceErrorException(reposResponse);
5960
}
60-
const repo = reposResponse.List.Repos.find(r => r.Repository.Name === repoName);
61+
const repo = reposResponse.repos.find(r => r.name === repoName);
6162

6263
if (pathType === 'tree') {
6364
// @todo : proper tree handling
@@ -81,7 +82,7 @@ export default async function BrowsePage({
8182
<div className="bg-accent py-1 px-2 flex flex-row">
8283
<FileHeader
8384
fileName={path}
84-
repo={repo.Repository}
85+
repo={repo}
8586
branchDisplayName={revisionName}
8687
/>
8788
</div>

packages/web/src/app/[domain]/components/fileHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Repository } from "@/lib/types";
1+
import { Repository } from "@/features/search/types";
22
import { getRepoCodeHostInfo } from "@/lib/utils";
33
import { LaptopIcon } from "@radix-ui/react-icons";
44
import clsx from "clsx";

packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ const SearchSuggestionsBox = forwardRef(({
275275
searchHistorySuggestions,
276276
languageSuggestions,
277277
searchContextSuggestions,
278+
refineModeSuggestions,
278279
]);
279280

280281
// When the list of suggestions change, reset the highlight index

packages/web/src/app/[domain]/components/searchBar/useSuggestionModeAndQuery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const useSuggestionModeAndQuery = ({
6969
suggestionQuery: part,
7070
suggestionMode: "refine",
7171
}
72-
}, [cursorPosition, isSuggestionsEnabled, query, isHistorySearchEnabled]);
72+
}, [cursorPosition, isSuggestionsEnabled, query, isHistorySearchEnabled, suggestionModeMappings]);
7373

7474
// Debug logging for tracking mode transitions.
7575
const [prevSuggestionMode, setPrevSuggestionMode] = useState<SuggestionMode>("none");

packages/web/src/app/[domain]/components/searchBar/useSuggestionsData.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Suggestion, SuggestionMode } from "./searchSuggestionsBox";
55
import { getRepos, search } from "@/app/api/(client)/client";
66
import { getSearchContexts } from "@/actions";
77
import { useMemo } from "react";
8-
import { Symbol } from "@/lib/types";
8+
import { SearchSymbol } from "@/features/search/types";
99
import { languageMetadataMap } from "@/lib/languageMetadata";
1010
import {
1111
VscSymbolClass,
@@ -40,10 +40,9 @@ export const useSuggestionsData = ({
4040
queryKey: ["repoSuggestions"],
4141
queryFn: () => getRepos(domain),
4242
select: (data): Suggestion[] => {
43-
return data.List.Repos
44-
.map(r => r.Repository)
43+
return data.repos
4544
.map(r => ({
46-
value: r.Name
45+
value: r.name,
4746
}));
4847
},
4948
enabled: suggestionMode === "repo",
@@ -54,16 +53,17 @@ export const useSuggestionsData = ({
5453
queryKey: ["fileSuggestions", suggestionQuery],
5554
queryFn: () => search({
5655
query: `file:${suggestionQuery}`,
57-
maxMatchDisplayCount: 15,
56+
matches: 15,
57+
contextLines: 1,
5858
}, domain),
5959
select: (data): Suggestion[] => {
6060
if (isServiceError(data)) {
6161
return [];
6262
}
6363

64-
return data.Result.Files?.map((file) => ({
65-
value: file.FileName
66-
})) ?? [];
64+
return data.files.map((file) => ({
65+
value: file.fileName.text,
66+
}));
6767
},
6868
enabled: suggestionMode === "file"
6969
});
@@ -73,22 +73,23 @@ export const useSuggestionsData = ({
7373
queryKey: ["symbolSuggestions", suggestionQuery],
7474
queryFn: () => search({
7575
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
76-
maxMatchDisplayCount: 15,
76+
matches: 15,
77+
contextLines: 1,
7778
}, domain),
7879
select: (data): Suggestion[] => {
7980
if (isServiceError(data)) {
8081
return [];
8182
}
8283

83-
const symbols = data.Result.Files?.flatMap((file) => file.ChunkMatches).flatMap((chunk) => chunk.SymbolInfo ?? []);
84+
const symbols = data.files.flatMap((file) => file.chunks).flatMap((chunk) => chunk.symbols ?? []);
8485
if (!symbols) {
8586
return [];
8687
}
8788

8889
// De-duplicate on symbol name & kind.
89-
const symbolMap = new Map<string, Symbol>(symbols.map((symbol: Symbol) => [`${symbol.Kind}.${symbol.Sym}`, symbol]));
90+
const symbolMap = new Map<string, SearchSymbol>(symbols.map((symbol: SearchSymbol) => [`${symbol.kind}.${symbol.symbol}`, symbol]));
9091
const suggestions = Array.from(symbolMap.values()).map((symbol) => ({
91-
value: symbol.Sym,
92+
value: symbol.symbol,
9293
Icon: getSymbolIcon(symbol),
9394
} satisfies Suggestion));
9495

@@ -157,8 +158,8 @@ export const useSuggestionsData = ({
157158
}
158159
}
159160

160-
const getSymbolIcon = (symbol: Symbol) => {
161-
switch (symbol.Kind) {
161+
const getSymbolIcon = (symbol: SearchSymbol) => {
162+
switch (symbol.kind) {
162163
case "methodSpec":
163164
case "method":
164165
case "function":

packages/web/src/app/[domain]/connections/[id]/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { isServiceError } from "@/lib/utils"
2222
import { notFound } from "next/navigation"
2323
import { OrgRole } from "@sourcebot/db"
2424
import { CodeHostType } from "@/lib/utils"
25-
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type"
2625

2726
interface ConnectionManagementPageProps {
2827
params: {

packages/web/src/app/[domain]/connections/quickActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ export const bitbucketCloudQuickActions: QuickAction<BitbucketConnectionConfig>[
403403
selectionText: "username",
404404
description: (
405405
<div className="flex flex-col">
406-
<span>Username to use for authentication. This is only required if you're using an App Password (stored in <Code>token</Code>) for authentication.</span>
406+
<span>Username to use for authentication. This is only required if you&apos;re using an App Password (stored in <Code>token</Code>) for authentication.</span>
407407
</div>
408408
)
409409
},

packages/web/src/app/[domain]/search/components/codePreviewPanel/codePreview.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu";
44
import { Button } from "@/components/ui/button";
55
import { ScrollArea } from "@/components/ui/scroll-area";
6+
import { SearchResultChunk } from "@/features/search/types";
67
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
78
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
89
import { useSyntaxHighlightingExtension } from "@/hooks/useSyntaxHighlightingExtension";
910
import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension";
1011
import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension";
11-
import { SearchResultFileMatch } from "@/lib/types";
1212
import { search } from "@codemirror/search";
1313
import { EditorView } from "@codemirror/view";
1414
import { Cross1Icon, FileIcon } from "@radix-ui/react-icons";
@@ -22,7 +22,7 @@ export interface CodePreviewFile {
2222
content: string;
2323
filepath: string;
2424
link?: string;
25-
matches: SearchResultFileMatch[];
25+
matches: SearchResultChunk[];
2626
language: string;
2727
revision: string;
2828
}
@@ -84,7 +84,7 @@ export const CodePreview = ({
8484
}
8585

8686
return file.matches.flatMap((match) => {
87-
return match.Ranges;
87+
return match.matchRanges;
8888
})
8989
}, [file]);
9090

packages/web/src/app/[domain]/search/components/codePreviewPanel/index.tsx

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { fetchFileSource } from "@/app/api/(client)/client";
44
import { base64Decode } from "@/lib/utils";
55
import { useQuery } from "@tanstack/react-query";
66
import { CodePreview, CodePreviewFile } from "./codePreview";
7-
import { SearchResultFile } from "@/lib/types";
7+
import { SearchResultFile } from "@/features/search/types";
88
import { useDomain } from "@/hooks/useDomain";
99
import { SymbolIcon } from "@radix-ui/react-icons";
10+
1011
interface CodePreviewPanelProps {
1112
fileMatch?: SearchResultFile;
1213
onClose: () => void;
@@ -25,24 +26,24 @@ export const CodePreviewPanel = ({
2526
const domain = useDomain();
2627

2728
const { data: file, isLoading } = useQuery({
28-
queryKey: ["source", fileMatch?.FileName, fileMatch?.Repository, fileMatch?.Branches],
29+
queryKey: ["source", fileMatch?.fileName, fileMatch?.repository, fileMatch?.branches],
2930
queryFn: async (): Promise<CodePreviewFile | undefined> => {
3031
if (!fileMatch) {
3132
return undefined;
3233
}
3334

3435
// If there are multiple branches pointing to the same revision of this file, it doesn't
3536
// matter which branch we use here, so use the first one.
36-
const branch = fileMatch.Branches && fileMatch.Branches.length > 0 ? fileMatch.Branches[0] : undefined;
37+
const branch = fileMatch.branches && fileMatch.branches.length > 0 ? fileMatch.branches[0] : undefined;
3738

3839
return fetchFileSource({
39-
fileName: fileMatch.FileName,
40-
repository: fileMatch.Repository,
40+
fileName: fileMatch.fileName.text,
41+
repository: fileMatch.repository,
4142
branch,
4243
}, domain)
4344
.then(({ source }) => {
4445
const link = (() => {
45-
const template = repoUrlTemplates[fileMatch.Repository];
46+
const template = repoUrlTemplates[fileMatch.repository];
4647

4748
// This is a hacky parser for templates generated by
4849
// the go text/template package. Example template:
@@ -55,7 +56,7 @@ export const CodePreviewPanel = ({
5556
const url =
5657
template.substring("{{URLJoinPath ".length,template.indexOf("}}"))
5758
.replace(".Version", branch ?? "HEAD")
58-
.replace(".Path", fileMatch.FileName)
59+
.replace(".Path", fileMatch.fileName.text)
5960
.split(" ")
6061
.map((part) => {
6162
// remove wrapping quotes
@@ -68,24 +69,19 @@ export const CodePreviewPanel = ({
6869
const optionalQueryParams =
6970
template.substring(template.indexOf("}}") + 2)
7071
.replace("{{.Version}}", branch ?? "HEAD")
71-
.replace("{{.Path}}", fileMatch.FileName);
72+
.replace("{{.Path}}", fileMatch.fileName.text);
7273

7374
return url + optionalQueryParams;
7475
})();
7576

7677
const decodedSource = base64Decode(source);
7778

78-
// Filter out filename matches
79-
const filteredMatches = fileMatch.ChunkMatches.filter((match) => {
80-
return !match.FileName;
81-
});
82-
8379
return {
8480
content: decodedSource,
85-
filepath: fileMatch.FileName,
86-
matches: filteredMatches,
81+
filepath: fileMatch.fileName.text,
82+
matches: fileMatch.chunks,
8783
link: link,
88-
language: fileMatch.Language,
84+
language: fileMatch.language,
8985
revision: branch ?? "HEAD",
9086
};
9187
});
@@ -103,7 +99,7 @@ export const CodePreviewPanel = ({
10399
return (
104100
<CodePreview
105101
file={file}
106-
repoName={fileMatch?.Repository}
102+
repoName={fileMatch?.repository}
107103
onClose={onClose}
108104
selectedMatchIndex={selectedMatchIndex}
109105
onSelectedMatchIndexChange={onSelectedMatchIndexChange}

packages/web/src/app/[domain]/search/components/filterPanel/filter.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { useMemo, useState } from "react";
44
import { compareEntries, Entry } from "./entry";
55
import { Input } from "@/components/ui/input";
6-
import { ScrollArea } from "@/components/ui/scroll-area";
76
import Fuse from "fuse.js";
87
import { cn } from "@/lib/utils"
98

packages/web/src/app/[domain]/search/components/filterPanel/index.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use client';
22

33
import { FileIcon } from "@/components/ui/fileIcon";
4-
import { Repository, SearchResultFile } from "@/lib/types";
4+
import { Repository, SearchResultFile } from "@/features/search/types";
55
import { cn, getRepoCodeHostInfo } from "@/lib/utils";
66
import { LaptopIcon } from "@radix-ui/react-icons";
77
import Image from "next/image";
88
import { useRouter, useSearchParams } from "next/navigation";
9-
import { useEffect, useMemo } from "react";
9+
import { useCallback, useEffect, useMemo } from "react";
1010
import { Entry } from "./entry";
1111
import { Filter } from "./filter";
1212

@@ -28,15 +28,15 @@ export const FilterPanel = ({
2828
const searchParams = useSearchParams();
2929

3030
// Helper to parse query params into sets
31-
const getSelectedFromQuery = (param: string) => {
31+
const getSelectedFromQuery = useCallback((param: string) => {
3232
const value = searchParams.get(param);
3333
return value ? new Set(value.split(',')) : new Set();
34-
};
34+
}, [searchParams]);
3535

3636
const repos = useMemo(() => {
3737
const selectedRepos = getSelectedFromQuery(REPOS_QUERY_PARAM);
3838
return aggregateMatches(
39-
"Repository",
39+
"repository",
4040
matches,
4141
(key) => {
4242
const repo: Repository | undefined = repoMetadata[key];
@@ -60,12 +60,12 @@ export const FilterPanel = ({
6060
};
6161
}
6262
)
63-
}, [searchParams]);
63+
}, [getSelectedFromQuery, matches, repoMetadata]);
6464

6565
const languages = useMemo(() => {
6666
const selectedLanguages = getSelectedFromQuery(LANGUAGES_QUERY_PARAM);
6767
return aggregateMatches(
68-
"Language",
68+
"language",
6969
matches,
7070
(key) => {
7171
const Icon = (
@@ -81,7 +81,7 @@ export const FilterPanel = ({
8181
} satisfies Entry;
8282
}
8383
);
84-
}, [searchParams]);
84+
}, [getSelectedFromQuery, matches]);
8585

8686
// Calls `onFilterChanged` with the filtered list of matches
8787
// whenever the filter state changes.
@@ -91,8 +91,8 @@ export const FilterPanel = ({
9191

9292
const filteredMatches = matches.filter((match) =>
9393
(
94-
(selectedRepos.size === 0 ? true : selectedRepos.has(match.Repository)) &&
95-
(selectedLanguages.size === 0 ? true : selectedLanguages.has(match.Language))
94+
(selectedRepos.size === 0 ? true : selectedRepos.has(match.repository)) &&
95+
(selectedLanguages.size === 0 ? true : selectedLanguages.has(match.language))
9696
)
9797
);
9898
onFilterChanged(filteredMatches);
@@ -166,7 +166,7 @@ export const FilterPanel = ({
166166
* }
167167
*/
168168
const aggregateMatches = (
169-
propName: 'Repository' | 'Language',
169+
propName: 'repository' | 'language',
170170
matches: SearchResultFile[],
171171
createEntry: (key: string) => Entry
172172
) => {

0 commit comments

Comments
 (0)