mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
fix(web): Fix loading issues with references / definitions list (#617)
This commit is contained in:
parent
341836a2ed
commit
fbe1073d0e
30 changed files with 275 additions and 128 deletions
|
|
@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Fixed spurious infinite loads with explore panel, file tree, and file search command. [#617](https://github.com/sourcebot-dev/sourcebot/pull/617)
|
||||
|
||||
## [4.9.2] - 2025-11-13
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { getRepoInfoByName } from "@/actions";
|
||||
import { PathHeader } from "@/app/[domain]/components/pathHeader";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { getFileSource } from "@/features/search/fileSourceApi";
|
||||
import { cn, getCodeHostInfoForRepo, isServiceError } from "@/lib/utils";
|
||||
import Image from "next/image";
|
||||
import { PureCodePreviewPanel } from "./pureCodePreviewPanel";
|
||||
import { getFileSource } from "@/features/search/fileSourceApi";
|
||||
|
||||
interface CodePreviewPanelProps {
|
||||
path: string;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { useRef } from "react";
|
||||
import { FileTreeItem } from "@/features/fileTree/actions";
|
||||
import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent";
|
||||
import { getBrowsePath } from "../../hooks/utils";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useBrowseParams } from "../../hooks/useBrowseParams";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
import { FileTreeItem } from "@/features/fileTree/types";
|
||||
|
||||
interface PureTreePreviewPanelProps {
|
||||
items: FileTreeItem[];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { Separator } from "@/components/ui/separator";
|
||||
import { getRepoInfoByName } from "@/actions";
|
||||
import { PathHeader } from "@/app/[domain]/components/pathHeader";
|
||||
import { getFolderContents } from "@/features/fileTree/actions";
|
||||
import { getFolderContents } from "@/features/fileTree/api";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { PureTreePreviewPanel } from "./pureTreePreviewPanel";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { useState, useRef, useMemo, useEffect, useCallback } from "react";
|
|||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { FileTreeItem, getFiles } from "@/features/fileTree/actions";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog";
|
||||
import { useBrowseNavigation } from "../hooks/useBrowseNavigation";
|
||||
import { useBrowseState } from "../hooks/useBrowseState";
|
||||
|
|
@ -13,6 +12,8 @@ import { useBrowseParams } from "../hooks/useBrowseParams";
|
|||
import { FileTreeItemIcon } from "@/features/fileTree/components/fileTreeItemIcon";
|
||||
import { useLocalStorage } from "usehooks-ts";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { FileTreeItem } from "@/features/fileTree/types";
|
||||
import { getFiles } from "@/app/api/(client)/client";
|
||||
|
||||
const MAX_RESULTS = 100;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const useSuggestionsData = ({
|
|||
query: `file:${suggestionQuery}`,
|
||||
matches: 15,
|
||||
contextLines: 1,
|
||||
}, domain),
|
||||
}),
|
||||
select: (data): Suggestion[] => {
|
||||
if (isServiceError(data)) {
|
||||
return [];
|
||||
|
|
@ -75,7 +75,7 @@ export const useSuggestionsData = ({
|
|||
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
|
||||
matches: 15,
|
||||
contextLines: 1,
|
||||
}, domain),
|
||||
}),
|
||||
select: (data): Suggestion[] => {
|
||||
if (isServiceError(data)) {
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { CodePreview } from "./codePreview";
|
|||
import { SearchResultFile } from "@/features/search/types";
|
||||
import { SymbolIcon } from "@radix-ui/react-icons";
|
||||
import { SetStateAction, Dispatch, useMemo } from "react";
|
||||
import { getFileSource } from "@/features/search/fileSourceApi";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { getFileSource } from "@/app/api/(client)/client";
|
||||
|
||||
interface CodePreviewPanelProps {
|
||||
previewedFile: SearchResultFile;
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export const SearchResultsPage = ({
|
|||
matches: maxMatchCount,
|
||||
contextLines: 3,
|
||||
whole: false,
|
||||
}, domain)), "client.search"),
|
||||
})), "client.search"),
|
||||
select: ({ data, durationMs }) => ({
|
||||
...data,
|
||||
totalClientSearchDurationMs: durationMs,
|
||||
|
|
|
|||
|
|
@ -9,13 +9,22 @@ import {
|
|||
SearchRequest,
|
||||
SearchResponse,
|
||||
} from "@/features/search/types";
|
||||
import {
|
||||
FindRelatedSymbolsRequest,
|
||||
FindRelatedSymbolsResponse,
|
||||
} from "@/features/codeNav/types";
|
||||
import {
|
||||
GetFilesRequest,
|
||||
GetFilesResponse,
|
||||
GetTreeRequest,
|
||||
GetTreeResponse,
|
||||
} from "@/features/fileTree/types";
|
||||
|
||||
export const search = async (body: SearchRequest, domain: string): Promise<SearchResponse | ServiceError> => {
|
||||
export const search = async (body: SearchRequest): Promise<SearchResponse | ServiceError> => {
|
||||
const result = await fetch("/api/search", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Org-Domain": domain,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json());
|
||||
|
|
@ -27,12 +36,11 @@ export const search = async (body: SearchRequest, domain: string): Promise<Searc
|
|||
return result as SearchResponse | ServiceError;
|
||||
}
|
||||
|
||||
export const fetchFileSource = async (body: FileSourceRequest, domain: string): Promise<FileSourceResponse | ServiceError> => {
|
||||
export const getFileSource = async (body: FileSourceRequest): Promise<FileSourceResponse | ServiceError> => {
|
||||
const result = await fetch("/api/source", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Org-Domain": domain,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json());
|
||||
|
|
@ -60,3 +68,35 @@ export const getVersion = async (): Promise<GetVersionResponse> => {
|
|||
}).then(response => response.json());
|
||||
return result as GetVersionResponse;
|
||||
}
|
||||
|
||||
export const findSearchBasedSymbolReferences = async (body: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => {
|
||||
const result = await fetch("/api/find_references", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json());
|
||||
return result as FindRelatedSymbolsResponse | ServiceError;
|
||||
}
|
||||
|
||||
export const findSearchBasedSymbolDefinitions = async (body: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => {
|
||||
const result = await fetch("/api/find_definitions", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json());
|
||||
return result as FindRelatedSymbolsResponse | ServiceError;
|
||||
}
|
||||
|
||||
export const getTree = async (body: GetTreeRequest): Promise<GetTreeResponse | ServiceError> => {
|
||||
const result = await fetch("/api/tree", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json());
|
||||
return result as GetTreeResponse | ServiceError;
|
||||
}
|
||||
|
||||
export const getFiles = async (body: GetFilesRequest): Promise<GetFilesResponse | ServiceError> => {
|
||||
const result = await fetch("/api/files", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json());
|
||||
return result as GetFilesResponse | ServiceError;
|
||||
}
|
||||
23
packages/web/src/app/api/(server)/files/route.ts
Normal file
23
packages/web/src/app/api/(server)/files/route.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
'use server';
|
||||
|
||||
import { getFiles } from "@/features/fileTree/api";
|
||||
import { getFilesRequestSchema } from "@/features/fileTree/types";
|
||||
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
const body = await request.json();
|
||||
const parsed = await getFilesRequestSchema.safeParseAsync(body);
|
||||
if (!parsed.success) {
|
||||
return serviceErrorResponse(schemaValidationError(parsed.error));
|
||||
}
|
||||
|
||||
const response = await getFiles(parsed.data);
|
||||
if (isServiceError(response)) {
|
||||
return serviceErrorResponse(response);
|
||||
}
|
||||
|
||||
return Response.json(response);
|
||||
}
|
||||
|
||||
22
packages/web/src/app/api/(server)/find_definitions/route.ts
Normal file
22
packages/web/src/app/api/(server)/find_definitions/route.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
'use server';
|
||||
|
||||
import { findSearchBasedSymbolDefinitions } from "@/features/codeNav/api";
|
||||
import { findRelatedSymbolsRequestSchema } from "@/features/codeNav/types";
|
||||
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
const body = await request.json();
|
||||
const parsed = await findRelatedSymbolsRequestSchema.safeParseAsync(body);
|
||||
if (!parsed.success) {
|
||||
return serviceErrorResponse(schemaValidationError(parsed.error));
|
||||
}
|
||||
|
||||
const response = await findSearchBasedSymbolDefinitions(parsed.data);
|
||||
if (isServiceError(response)) {
|
||||
return serviceErrorResponse(response);
|
||||
}
|
||||
|
||||
return Response.json(response);
|
||||
}
|
||||
20
packages/web/src/app/api/(server)/find_references/route.ts
Normal file
20
packages/web/src/app/api/(server)/find_references/route.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { findSearchBasedSymbolReferences } from "@/features/codeNav/api";
|
||||
import { findRelatedSymbolsRequestSchema } from "@/features/codeNav/types";
|
||||
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
const body = await request.json();
|
||||
const parsed = await findRelatedSymbolsRequestSchema.safeParseAsync(body);
|
||||
if (!parsed.success) {
|
||||
return serviceErrorResponse(schemaValidationError(parsed.error));
|
||||
}
|
||||
|
||||
const response = await findSearchBasedSymbolReferences(parsed.data);
|
||||
if (isServiceError(response)) {
|
||||
return serviceErrorResponse(response);
|
||||
}
|
||||
|
||||
return Response.json(response);
|
||||
}
|
||||
23
packages/web/src/app/api/(server)/tree/route.ts
Normal file
23
packages/web/src/app/api/(server)/tree/route.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
'use server';
|
||||
|
||||
import { getTree } from "@/features/fileTree/api";
|
||||
import { getTreeRequestSchema } from "@/features/fileTree/types";
|
||||
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
const body = await request.json();
|
||||
const parsed = await getTreeRequestSchema.safeParseAsync(body);
|
||||
if (!parsed.success) {
|
||||
return serviceErrorResponse(schemaValidationError(parsed.error));
|
||||
}
|
||||
|
||||
const response = await getTree(parsed.data);
|
||||
if (isServiceError(response)) {
|
||||
return serviceErrorResponse(response);
|
||||
}
|
||||
|
||||
return Response.json(response);
|
||||
}
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
|
||||
import { findSearchBasedSymbolReferences, findSearchBasedSymbolDefinitions} from "@/app/api/(client)/client";
|
||||
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "@/features/codeNav/actions";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
|
@ -46,7 +46,7 @@ export const ExploreMenu = ({
|
|||
symbolName: selectedSymbolInfo.symbolName,
|
||||
language: selectedSymbolInfo.language,
|
||||
revisionName: selectedSymbolInfo.revisionName,
|
||||
}, domain)
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ export const ExploreMenu = ({
|
|||
symbolName: selectedSymbolInfo.symbolName,
|
||||
language: selectedSymbolInfo.language,
|
||||
revisionName: selectedSymbolInfo.revisionName,
|
||||
}, domain)
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { findSearchBasedSymbolDefinitions } from "@/features/codeNav/actions";
|
||||
import { findSearchBasedSymbolDefinitions } from "@/app/api/(client)/client";
|
||||
import { SourceRange } from "@/features/search/types";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
|
|
@ -56,7 +56,7 @@ export const useHoveredOverSymbolInfo = ({
|
|||
symbolName: symbolName!,
|
||||
language,
|
||||
revisionName,
|
||||
}, domain)
|
||||
})
|
||||
),
|
||||
select: ((data) => {
|
||||
return data.files.flatMap((file) => {
|
||||
|
|
|
|||
|
|
@ -251,7 +251,6 @@ const resolveFileSource = async ({ path, repo, revision }: FileSource) => {
|
|||
fileName: path,
|
||||
repository: repo,
|
||||
branch: revision,
|
||||
// @todo: handle multi-tenancy.
|
||||
});
|
||||
|
||||
if (isServiceError(fileSource)) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export const useSuggestionsData = ({
|
|||
query,
|
||||
matches: 10,
|
||||
contextLines: 1,
|
||||
}, domain))
|
||||
}))
|
||||
},
|
||||
select: (data): FileSuggestion[] => {
|
||||
return data.files.map((file) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { fetchFileSource } from "@/app/api/(client)/client";
|
||||
import { getFileSource } from "@/app/api/(client)/client";
|
||||
import { VscodeFileIcon } from "@/app/components/vscodeFileIcon";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
|
@ -99,11 +99,11 @@ export const ReferencedSourcesListView = ({
|
|||
const fileSourceQueries = useQueries({
|
||||
queries: referencedFileSources.map((file) => ({
|
||||
queryKey: ['fileSource', file.path, file.repo, file.revision, domain],
|
||||
queryFn: () => unwrapServiceError(fetchFileSource({
|
||||
queryFn: () => unwrapServiceError(getFileSource({
|
||||
fileName: file.path,
|
||||
repository: file.repo,
|
||||
branch: file.revision,
|
||||
}, domain)),
|
||||
})),
|
||||
staleTime: Infinity,
|
||||
})),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { z } from "zod"
|
||||
import { search } from "@/features/search/searchApi"
|
||||
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"
|
||||
import { InferToolInput, InferToolOutput, InferUITool, tool, ToolUIPart } from "ai";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { getFileSource } from "../search/fileSourceApi";
|
||||
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/actions";
|
||||
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/api";
|
||||
import { FileSourceResponse } from "../search/types";
|
||||
import { addLineNumbers, buildSearchQuery } from "./utils";
|
||||
import { toolNames } from "./constants";
|
||||
|
|
@ -36,8 +35,7 @@ export const findSymbolReferencesTool = tool({
|
|||
symbolName: symbol,
|
||||
language,
|
||||
revisionName: "HEAD",
|
||||
// @todo(mt): handle multi-tenancy.
|
||||
}, SINGLE_TENANT_ORG_DOMAIN);
|
||||
});
|
||||
|
||||
if (isServiceError(response)) {
|
||||
return response;
|
||||
|
|
@ -74,8 +72,7 @@ export const findSymbolDefinitionsTool = tool({
|
|||
symbolName: symbol,
|
||||
language,
|
||||
revisionName: revision,
|
||||
// @todo(mt): handle multi-tenancy.
|
||||
}, SINGLE_TENANT_ORG_DOMAIN);
|
||||
});
|
||||
|
||||
if (isServiceError(response)) {
|
||||
return response;
|
||||
|
|
|
|||
|
|
@ -1,60 +1,43 @@
|
|||
'use server';
|
||||
import 'server-only';
|
||||
|
||||
import { sew, withAuth, withOrgMembership } from "@/actions";
|
||||
import { sew } 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 { isServiceError } from "@/lib/utils";
|
||||
import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||
import { SearchResponse } from "../search/types";
|
||||
import { OrgRole } from "@sourcebot/db";
|
||||
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
|
||||
|
||||
// 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;
|
||||
export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
|
||||
withOptionalAuthV2(async () => {
|
||||
const {
|
||||
symbolName,
|
||||
language,
|
||||
revisionName = "HEAD",
|
||||
} = props;
|
||||
|
||||
const query = `\\b${symbolName}\\b rev:${revisionName} ${getExpandedLanguageFilter(language)} case:yes`;
|
||||
const query = `\\b${symbolName}\\b rev:${revisionName} ${getExpandedLanguageFilter(language)} case:yes`;
|
||||
|
||||
const searchResult = await search({
|
||||
query,
|
||||
matches: MAX_REFERENCE_COUNT,
|
||||
contextLines: 0,
|
||||
});
|
||||
const searchResult = await search({
|
||||
query,
|
||||
matches: MAX_REFERENCE_COUNT,
|
||||
contextLines: 0,
|
||||
});
|
||||
|
||||
if (isServiceError(searchResult)) {
|
||||
return searchResult;
|
||||
}
|
||||
if (isServiceError(searchResult)) {
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
return parseRelatedSymbolsSearchResponse(searchResult);
|
||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true)
|
||||
);
|
||||
return parseRelatedSymbolsSearchResponse(searchResult);
|
||||
}));
|
||||
|
||||
|
||||
export const findSearchBasedSymbolDefinitions = async (
|
||||
props: {
|
||||
symbolName: string,
|
||||
language: string,
|
||||
revisionName?: string,
|
||||
},
|
||||
domain: string,
|
||||
): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
|
||||
withAuth((session) =>
|
||||
withOrgMembership(session, domain, async () => {
|
||||
export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
|
||||
withOptionalAuthV2(async () => {
|
||||
const {
|
||||
symbolName,
|
||||
language,
|
||||
|
|
@ -74,8 +57,7 @@ export const findSearchBasedSymbolDefinitions = async (
|
|||
}
|
||||
|
||||
return parseRelatedSymbolsSearchResponse(searchResult);
|
||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true)
|
||||
);
|
||||
}));
|
||||
|
||||
const parseRelatedSymbolsSearchResponse = (searchResult: SearchResponse) => {
|
||||
const parser = searchResponseSchema.transform(async ({ files }) => ({
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { rangeSchema, repositoryInfoSchema } from "../search/schemas";
|
||||
import { z } from "zod";
|
||||
|
||||
export const findRelatedSymbolsResponseSchema = z.object({
|
||||
stats: z.object({
|
||||
matchCount: z.number(),
|
||||
}),
|
||||
files: z.array(z.object({
|
||||
fileName: z.string(),
|
||||
repository: z.string(),
|
||||
repositoryId: z.number(),
|
||||
webUrl: z.string().optional(),
|
||||
language: z.string(),
|
||||
matches: z.array(z.object({
|
||||
lineContent: z.string(),
|
||||
range: rangeSchema,
|
||||
}))
|
||||
})),
|
||||
repositoryInfo: z.array(repositoryInfoSchema),
|
||||
});
|
||||
|
|
@ -1,4 +1,29 @@
|
|||
import { z } from "zod";
|
||||
import { findRelatedSymbolsResponseSchema } from "./schemas";
|
||||
import { rangeSchema, repositoryInfoSchema } from "../search/schemas";
|
||||
|
||||
export const findRelatedSymbolsRequestSchema = z.object({
|
||||
symbolName: z.string(),
|
||||
language: z.string(),
|
||||
revisionName: z.string().optional(),
|
||||
});
|
||||
export type FindRelatedSymbolsRequest = z.infer<typeof findRelatedSymbolsRequestSchema>;
|
||||
|
||||
export const findRelatedSymbolsResponseSchema = z.object({
|
||||
stats: z.object({
|
||||
matchCount: z.number(),
|
||||
}),
|
||||
files: z.array(z.object({
|
||||
fileName: z.string(),
|
||||
repository: z.string(),
|
||||
repositoryId: z.number(),
|
||||
webUrl: z.string().optional(),
|
||||
language: z.string(),
|
||||
matches: z.array(z.object({
|
||||
lineContent: z.string(),
|
||||
range: rangeSchema,
|
||||
}))
|
||||
})),
|
||||
repositoryInfo: z.array(repositoryInfoSchema),
|
||||
});
|
||||
|
||||
export type FindRelatedSymbolsResponse = z.infer<typeof findRelatedSymbolsResponseSchema>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
'use server';
|
||||
import 'server-only';
|
||||
|
||||
import { sew } from '@/actions';
|
||||
import { env } from '@sourcebot/shared';
|
||||
|
|
@ -8,19 +8,10 @@ import { Repo } from '@sourcebot/db';
|
|||
import { createLogger } from '@sourcebot/shared';
|
||||
import path from 'path';
|
||||
import { simpleGit } from 'simple-git';
|
||||
import { FileTreeItem, FileTreeNode } from './types';
|
||||
|
||||
const logger = createLogger('file-tree');
|
||||
|
||||
export type FileTreeItem = {
|
||||
type: string;
|
||||
path: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type FileTreeNode = FileTreeItem & {
|
||||
children: FileTreeNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tree of files (blobs) and directories (trees) for a given repository,
|
||||
* at a given revision.
|
||||
|
|
@ -218,7 +209,7 @@ const buildFileTree = (flatList: { type: string, path: string }[]): FileTreeNode
|
|||
const part = parts[i];
|
||||
const isLeaf = i === parts.length - 1;
|
||||
const nodeType = isLeaf ? item.type : 'tree';
|
||||
let next = current.children.find(child => child.name === part && child.type === nodeType);
|
||||
let next = current.children.find((child: FileTreeNode) => child.name === part && child.type === nodeType);
|
||||
|
||||
if (!next) {
|
||||
next = {
|
||||
|
|
@ -240,7 +231,7 @@ const buildFileTree = (flatList: { type: string, path: string }[]): FileTreeNode
|
|||
|
||||
const sortedChildren = node.children
|
||||
.map(sortTree)
|
||||
.sort((a, b) => {
|
||||
.sort((a: FileTreeNode, b: FileTreeNode) => {
|
||||
if (a.type !== b.type) {
|
||||
return a.type === 'tree' ? -1 : 1;
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { FileTreeItem } from "../actions";
|
||||
import { useEffect, useRef } from "react";
|
||||
import clsx from "clsx";
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons";
|
||||
import { FileTreeItemIcon } from "./fileTreeItemIcon";
|
||||
import Link from "next/link";
|
||||
import { FileTreeItem } from "../types";
|
||||
|
||||
export const FileTreeItemComponent = ({
|
||||
node,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { FileTreeItem } from "../actions";
|
||||
import { useMemo } from "react";
|
||||
import { VscodeFolderIcon } from "@/app/components/vscodeFolderIcon";
|
||||
import { VscodeFileIcon } from "@/app/components/vscodeFileIcon";
|
||||
import { FileTreeItem } from "../types";
|
||||
|
||||
interface FileTreeItemIconProps {
|
||||
item: FileTreeItem;
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
'use client';
|
||||
|
||||
import { getTree } from "../actions";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { ResizablePanel } from "@/components/ui/resizable";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
|
||||
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
|
||||
import { PureFileTreePanel } from "./pureFileTreePanel";
|
||||
import { getTree } from "@/app/api/(client)/client";
|
||||
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ImperativePanelHandle } from "react-resizable-panels";
|
||||
import { ResizablePanel } from "@/components/ui/resizable";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { SearchIcon } from "lucide-react";
|
||||
import { useRef } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
GoSidebarCollapse as ExpandIcon,
|
||||
GoSidebarExpand as CollapseIcon
|
||||
GoSidebarExpand as CollapseIcon,
|
||||
GoSidebarCollapse as ExpandIcon
|
||||
} from "react-icons/go";
|
||||
import { Tooltip, TooltipContent } from "@/components/ui/tooltip";
|
||||
import { TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
|
||||
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
|
||||
import { SearchIcon } from "lucide-react";
|
||||
import { ImperativePanelHandle } from "react-resizable-panels";
|
||||
import { PureFileTreePanel } from "./pureFileTreePanel";
|
||||
|
||||
|
||||
interface FileTreePanelProps {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { FileTreeNode as RawFileTreeNode } from "../actions";
|
||||
import { FileTreeNode as RawFileTreeNode } from "../types";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import React, { useCallback, useMemo, useState, useEffect, useRef } from "react";
|
||||
import { FileTreeItemComponent } from "./fileTreeItemComponent";
|
||||
|
|
|
|||
44
packages/web/src/features/fileTree/types.ts
Normal file
44
packages/web/src/features/fileTree/types.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const getTreeRequestSchema = z.object({
|
||||
repoName: z.string(),
|
||||
revisionName: z.string(),
|
||||
});
|
||||
export type GetTreeRequest = z.infer<typeof getTreeRequestSchema>;
|
||||
|
||||
export const getFilesRequestSchema = z.object({
|
||||
repoName: z.string(),
|
||||
revisionName: z.string(),
|
||||
});
|
||||
export type GetFilesRequest = z.infer<typeof getFilesRequestSchema>;
|
||||
|
||||
export const fileTreeItemSchema = z.object({
|
||||
type: z.string(),
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
});
|
||||
export type FileTreeItem = z.infer<typeof fileTreeItemSchema>;
|
||||
|
||||
type FileTreeNodeType = {
|
||||
type: string;
|
||||
path: string;
|
||||
name: string;
|
||||
children: FileTreeNodeType[];
|
||||
};
|
||||
|
||||
export const fileTreeNodeSchema: z.ZodType<FileTreeNodeType> = z.lazy(() => z.object({
|
||||
type: z.string(),
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
children: z.array(fileTreeNodeSchema),
|
||||
}));
|
||||
export type FileTreeNode = z.infer<typeof fileTreeNodeSchema>;
|
||||
|
||||
export const getTreeResponseSchema = z.object({
|
||||
tree: fileTreeNodeSchema,
|
||||
});
|
||||
export type GetTreeResponse = z.infer<typeof getTreeResponseSchema>;
|
||||
|
||||
export const getFilesResponseSchema = z.array(fileTreeItemSchema);
|
||||
export type GetFilesResponse = z.infer<typeof getFilesResponseSchema>;
|
||||
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
'use server';
|
||||
|
||||
import 'server-only';
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { fileNotFound, ServiceError, unexpectedError } from "../../lib/serviceError";
|
||||
import { FileSourceRequest, FileSourceResponse } from "./types";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
'use server';
|
||||
|
||||
import 'server-only';
|
||||
import { sew } from "@/actions";
|
||||
import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||
import { PrismaClient, Repo } from "@sourcebot/db";
|
||||
|
|
|
|||
Loading…
Reference in a new issue