Skip to content

Commit d654408

Browse files
Icon & link support for self-hosted repositories (#93)
1 parent 01f4329 commit d654408

File tree

12 files changed

+155
-100
lines changed

12 files changed

+155
-100
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Added file suggestions as a suggestion type. ([#88](https://github.com/sourcebot-dev/sourcebot/pull/88))
13+
- Added icon and link support for self-hosted repositories. ([#93](https://github.com/sourcebot-dev/sourcebot/pull/93))
1314

1415
## [2.5.0] - 2024-11-22
1516

packages/web/src/app/components/repositoryCarousel.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ interface RepositoryBadgeProps {
5656
const RepositoryBadge = ({
5757
repo
5858
}: RepositoryBadgeProps) => {
59-
const { repoIcon, repoName, repoLink } = (() => {
60-
const info = getRepoCodeHostInfo(repo.Name);
59+
const { repoIcon, displayName, repoLink } = (() => {
60+
const info = getRepoCodeHostInfo(repo);
6161

6262
if (info) {
6363
return {
@@ -66,14 +66,14 @@ const RepositoryBadge = ({
6666
alt={info.costHostName}
6767
className={`w-4 h-4 ${info.iconClassName}`}
6868
/>,
69-
repoName: info.repoName,
69+
displayName: info.displayName,
7070
repoLink: info.repoLink,
7171
}
7272
}
7373

7474
return {
7575
repoIcon: <FileIcon className="w-4 h-4" />,
76-
repoName: repo.Name,
76+
displayName: repo.Name,
7777
repoLink: undefined,
7878
}
7979
})();
@@ -91,7 +91,7 @@ const RepositoryBadge = ({
9191
>
9292
{repoIcon}
9393
<span className="text-sm font-mono">
94-
{repoName}
94+
{displayName}
9595
</span>
9696
</div>
9797
)

packages/web/src/app/repos/columns.tsx

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

33
import { Button } from "@/components/ui/button";
4-
import { getRepoCodeHostInfo } from "@/lib/utils";
54
import { Column, ColumnDef } from "@tanstack/react-table"
65
import { ArrowUpDown } from "lucide-react"
76
import prettyBytes from "pretty-bytes";
@@ -19,6 +18,7 @@ export type RepositoryColumnInfo = {
1918
lastIndexed: string;
2019
latestCommit: string;
2120
commitUrlTemplate: string;
21+
url: string;
2222
}
2323

2424
export const columns: ColumnDef<RepositoryColumnInfo>[] = [
@@ -27,14 +27,16 @@ export const columns: ColumnDef<RepositoryColumnInfo>[] = [
2727
header: "Name",
2828
cell: ({ row }) => {
2929
const repo = row.original;
30-
const info = getRepoCodeHostInfo(repo.name);
30+
const url = repo.url;
31+
// local repositories will have a url of 0 length
32+
const isRemoteRepo = url.length === 0;
3133
return (
3234
<div className="flex flex-row items-center gap-2">
3335
<span
34-
className={info?.repoLink ? "cursor-pointer text-blue-500 hover:underline": ""}
36+
className={!isRemoteRepo ? "cursor-pointer text-blue-500 hover:underline": ""}
3537
onClick={() => {
36-
if (info?.repoLink) {
37-
window.open(info.repoLink, "_blank");
38+
if (!isRemoteRepo) {
39+
window.open(url, "_blank");
3840
}
3941
}}
4042
>

packages/web/src/app/repos/repositoryTable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const RepositoryTable = async () => {
2626
latestCommit: repo.Repository.LatestCommitDate,
2727
indexedFiles: repo.Stats.Documents,
2828
commitUrlTemplate: repo.Repository.CommitURLTemplate,
29+
url: repo.Repository.URL,
2930
}
3031
}).sort((a, b) => {
3132
return new Date(b.lastIndexed).getTime() - new Date(a.lastIndexed).getTime();

packages/web/src/app/search/components/codePreviewPanel/index.tsx

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

33
import { fetchFileSource } from "@/app/api/(client)/client";
4-
import { getCodeHostFilePreviewLink, base64Decode } from "@/lib/utils";
4+
import { base64Decode } from "@/lib/utils";
55
import { useQuery } from "@tanstack/react-query";
66
import { CodePreview, CodePreviewFile } from "./codePreview";
77
import { SearchResultFile } from "@/lib/types";
@@ -11,13 +11,15 @@ interface CodePreviewPanelProps {
1111
onClose: () => void;
1212
selectedMatchIndex: number;
1313
onSelectedMatchIndexChange: (index: number) => void;
14+
repoUrlTemplates: Record<string, string>;
1415
}
1516

1617
export const CodePreviewPanel = ({
1718
fileMatch,
1819
onClose,
1920
selectedMatchIndex,
2021
onSelectedMatchIndexChange,
22+
repoUrlTemplates,
2123
}: CodePreviewPanelProps) => {
2224

2325
const { data: file } = useQuery({
@@ -37,8 +39,15 @@ export const CodePreviewPanel = ({
3739
branch,
3840
})
3941
.then(({ source }) => {
40-
// @todo : refector this to use the templates provided by zoekt.
41-
const link = getCodeHostFilePreviewLink(fileMatch.Repository, fileMatch.FileName, branch);
42+
const link = (() => {
43+
const template = repoUrlTemplates[fileMatch.Repository];
44+
if (!template) {
45+
return undefined;
46+
}
47+
return template
48+
.replace("{{.Version}}", branch ?? "HEAD")
49+
.replace("{{.Path}}", fileMatch.FileName);
50+
})();
4251

4352
const decodedSource = base64Decode(source);
4453

packages/web/src/app/search/components/filterPanel/entry.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@
22

33
import { QuestionMarkCircledIcon } from "@radix-ui/react-icons";
44
import clsx from "clsx";
5-
import Image from "next/image";
65

76
export type Entry = {
87
key: string;
98
displayName: string;
109
count: number;
1110
isSelected: boolean;
12-
icon?: string;
13-
iconAltText?: string;
14-
iconClassName?: string;
11+
Icon?: React.ReactNode;
1512
}
1613

1714
interface EntryProps {
@@ -22,11 +19,9 @@ interface EntryProps {
2219
export const Entry = ({
2320
entry: {
2421
isSelected,
25-
icon,
26-
iconAltText,
27-
iconClassName,
2822
displayName,
2923
count,
24+
Icon,
3025
},
3126
onClicked,
3227
}: EntryProps) => {
@@ -42,13 +37,7 @@ export const Entry = ({
4237
onClick={() => onClicked()}
4338
>
4439
<div className="flex flex-row items-center gap-1">
45-
{icon ? (
46-
<Image
47-
src={icon}
48-
alt={iconAltText ?? ''}
49-
className={`w-4 h-4 flex-shrink-0 ${iconClassName}`}
50-
/>
51-
) : (
40+
{Icon ? Icon : (
5241
<QuestionMarkCircledIcon className="w-4 h-4 flex-shrink-0" />
5342
)}
5443
<p className="text-wrap">{displayName}</p>

packages/web/src/app/search/components/filterPanel/index.tsx

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

3-
import { SearchResultFile } from "@/lib/types";
4-
import { getRepoCodeHostInfo } from "@/lib/utils";
3+
import { Repository, SearchResultFile } from "@/lib/types";
4+
import { cn, getRepoCodeHostInfo } from "@/lib/utils";
55
import { SetStateAction, useCallback, useEffect, useState } from "react";
66
import { Entry } from "./entry";
77
import { Filter } from "./filter";
88
import { getLanguageIcon } from "./languageIcons";
9+
import Image from "next/image";
10+
import { LaptopIcon, QuestionMarkCircledIcon } from "@radix-ui/react-icons";
911

1012
interface FilePanelProps {
1113
matches: SearchResultFile[];
1214
onFilterChanged: (filteredMatches: SearchResultFile[]) => void,
15+
repoMetadata: Record<string, Repository>;
1316
}
1417

1518
export const FilterPanel = ({
1619
matches,
1720
onFilterChanged,
21+
repoMetadata,
1822
}: FilePanelProps) => {
1923
const [repos, setRepos] = useState<Record<string, Entry>>({});
2024
const [languages, setLanguages] = useState<Record<string, Entry>>({});
@@ -24,19 +28,28 @@ export const FilterPanel = ({
2428
"Repository",
2529
matches,
2630
(key) => {
27-
const info = getRepoCodeHostInfo(key);
31+
const repo: Repository | undefined = repoMetadata[key];
32+
const info = getRepoCodeHostInfo(repo);
33+
const Icon = info ? (
34+
<Image
35+
src={info.icon}
36+
alt={info.costHostName}
37+
className={cn('w-4 h-4 flex-shrink-0', info.iconClassName)}
38+
/>
39+
) : (
40+
<LaptopIcon className="w-4 h-4 flex-shrink-0" />
41+
);
42+
2843
return {
2944
key,
30-
displayName: info?.repoName ?? key,
45+
displayName: info?.displayName ?? key,
3146
count: 0,
3247
isSelected: false,
33-
icon: info?.icon,
34-
iconAltText: info?.costHostName,
35-
iconClassName: info?.iconClassName,
48+
Icon,
3649
};
3750
}
3851
);
39-
52+
4053
setRepos(_repos);
4154
}, [matches, setRepos]);
4255

@@ -45,12 +58,23 @@ export const FilterPanel = ({
4558
"Language",
4659
matches,
4760
(key) => {
61+
const iconSrc = getLanguageIcon(key);
62+
const Icon = iconSrc ? (
63+
<Image
64+
src={iconSrc}
65+
alt={key}
66+
className="w-4 h-4 flex-shrink-0"
67+
/>
68+
) : (
69+
<QuestionMarkCircledIcon className="w-4 h-4 flex-shrink-0" />
70+
);
71+
4872
return {
4973
key,
5074
displayName: key,
5175
count: 0,
5276
isSelected: false,
53-
icon: getLanguageIcon(key),
77+
Icon: Icon,
5478
} satisfies Entry;
5579
}
5680
)
@@ -85,10 +109,10 @@ export const FilterPanel = ({
85109
);
86110

87111
const filteredMatches = matches.filter((match) =>
88-
(
89-
(selectedRepos.size === 0 ? true : selectedRepos.has(match.Repository)) &&
90-
(selectedLanguages.size === 0 ? true : selectedLanguages.has(match.Language))
91-
)
112+
(
113+
(selectedRepos.size === 0 ? true : selectedRepos.has(match.Repository)) &&
114+
(selectedLanguages.size === 0 ? true : selectedLanguages.has(match.Language))
115+
)
92116
);
93117

94118
onFilterChanged(filteredMatches);

packages/web/src/app/search/components/searchResultsPanel/fileMatchContainer.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import { getRepoCodeHostInfo } from "@/lib/utils";
44
import { useCallback, useMemo } from "react";
55
import Image from "next/image";
6-
import { DoubleArrowDownIcon, DoubleArrowUpIcon, FileIcon } from "@radix-ui/react-icons";
6+
import { DoubleArrowDownIcon, DoubleArrowUpIcon, LaptopIcon } from "@radix-ui/react-icons";
77
import clsx from "clsx";
88
import { Separator } from "@/components/ui/separator";
9-
import { SearchResultFile } from "@/lib/types";
9+
import { Repository, SearchResultFile } from "@/lib/types";
1010
import { FileMatch } from "./fileMatch";
1111

1212
export const MAX_MATCHES_TO_PREVIEW = 3;
@@ -18,6 +18,7 @@ interface FileMatchContainerProps {
1818
showAllMatches: boolean;
1919
onShowAllMatchesButtonClicked: () => void;
2020
isBranchFilteringEnabled: boolean;
21+
repoMetadata: Record<string, Repository>;
2122
}
2223

2324
export const FileMatchContainer = ({
@@ -27,6 +28,7 @@ export const FileMatchContainer = ({
2728
showAllMatches,
2829
onShowAllMatchesButtonClicked,
2930
isBranchFilteringEnabled,
31+
repoMetadata,
3032
}: FileMatchContainerProps) => {
3133

3234
const matchCount = useMemo(() => {
@@ -59,11 +61,13 @@ export const FileMatchContainer = ({
5961
return null;
6062
}, [matches]);
6163

62-
const { repoIcon, repoName, repoLink } = useMemo(() => {
63-
const info = getRepoCodeHostInfo(file.Repository);
64+
const { repoIcon, displayName, repoLink } = useMemo(() => {
65+
const repo: Repository | undefined = repoMetadata[file.Repository];
66+
const info = getRepoCodeHostInfo(repo);
67+
6468
if (info) {
6569
return {
66-
repoName: info.repoName,
70+
displayName: info.displayName,
6771
repoLink: info.repoLink,
6872
repoIcon: <Image
6973
src={info.icon}
@@ -74,11 +78,11 @@ export const FileMatchContainer = ({
7478
}
7579

7680
return {
77-
repoName: file.Repository,
81+
displayName: file.Repository,
7882
repoLink: undefined,
79-
repoIcon: <FileIcon className="w-4 h-4" />
83+
repoIcon: <LaptopIcon className="w-4 h-4" />
8084
}
81-
}, [file]);
85+
}, [file.Repository, repoMetadata]);
8286

8387
const isMoreContentButtonVisible = useMemo(() => {
8488
return matchCount > MAX_MATCHES_TO_PREVIEW;
@@ -122,7 +126,7 @@ export const FileMatchContainer = ({
122126
}
123127
}}
124128
>
125-
{repoName}
129+
{displayName}
126130
</span>
127131
{isBranchFilteringEnabled && branches.length > 0 && (
128132
<span

packages/web/src/app/search/components/searchResultsPanel/index.tsx

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

3-
import { SearchResultFile } from "@/lib/types";
3+
import { Repository, SearchResultFile } from "@/lib/types";
44
import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer";
55
import { useVirtualizer } from "@tanstack/react-virtual";
66
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
@@ -12,6 +12,7 @@ interface SearchResultsPanelProps {
1212
isLoadMoreButtonVisible: boolean;
1313
onLoadMoreButtonClicked: () => void;
1414
isBranchFilteringEnabled: boolean;
15+
repoMetadata: Record<string, Repository>;
1516
}
1617

1718
const ESTIMATED_LINE_HEIGHT_PX = 20;
@@ -25,6 +26,7 @@ export const SearchResultsPanel = ({
2526
isLoadMoreButtonVisible,
2627
onLoadMoreButtonClicked,
2728
isBranchFilteringEnabled,
29+
repoMetadata,
2830
}: SearchResultsPanelProps) => {
2931
const parentRef = useRef<HTMLDivElement>(null);
3032
const [showAllMatchesStates, setShowAllMatchesStates] = useState(Array(fileMatches.length).fill(false));
@@ -148,6 +150,7 @@ export const SearchResultsPanel = ({
148150
onShowAllMatchesButtonClicked(virtualRow.index);
149151
}}
150152
isBranchFilteringEnabled={isBranchFilteringEnabled}
153+
repoMetadata={repoMetadata}
151154
/>
152155
</div>
153156
))}

0 commit comments

Comments
 (0)