mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 20:35:24 +00:00
wip on better error handling
This commit is contained in:
parent
1d1205f471
commit
5c624cda4f
6 changed files with 61 additions and 30 deletions
|
|
@ -48,6 +48,10 @@ export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> =>
|
||||||
Sentry.captureException(e);
|
Sentry.captureException(e);
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
|
if (e instanceof ServiceErrorException) {
|
||||||
|
return e.serviceError;
|
||||||
|
}
|
||||||
|
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
return unexpectedError(e.message);
|
return unexpectedError(e.message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@ export const SearchBar = ({
|
||||||
indentWithTab={false}
|
indentWithTab={false}
|
||||||
autoFocus={autoFocus ?? false}
|
autoFocus={autoFocus ?? false}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-row items-center gap-1">
|
<div className="flex flex-row items-center gap-1 ml-1">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import { CodePreviewPanel } from "./codePreviewPanel";
|
||||||
import { FilterPanel } from "./filterPanel";
|
import { FilterPanel } from "./filterPanel";
|
||||||
import { useFilteredMatches } from "./filterPanel/useFilterMatches";
|
import { useFilteredMatches } from "./filterPanel/useFilterMatches";
|
||||||
import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel";
|
import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel";
|
||||||
|
import { ServiceErrorException } from "@/lib/serviceError";
|
||||||
|
|
||||||
interface SearchResultsPageProps {
|
interface SearchResultsPageProps {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
|
|
@ -79,7 +80,7 @@ export const SearchResultsPage = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
toast({
|
toast({
|
||||||
description: `❌ Search failed. Reason: ${error.message}`,
|
description: `❌ Search failed. Reason: ${error instanceof ServiceErrorException ? error.serviceError.message : error.message}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [error, toast]);
|
}, [error, toast]);
|
||||||
|
|
@ -184,7 +185,7 @@ export const SearchResultsPage = ({
|
||||||
<div className="flex flex-col items-center justify-center h-full gap-2">
|
<div className="flex flex-col items-center justify-center h-full gap-2">
|
||||||
<AlertTriangleIcon className="h-6 w-6" />
|
<AlertTriangleIcon className="h-6 w-6" />
|
||||||
<p className="font-semibold text-center">Failed to search</p>
|
<p className="font-semibold text-center">Failed to search</p>
|
||||||
<p className="text-sm text-center">{error.message}</p>
|
<p className="text-sm text-center">{error instanceof ServiceErrorException ? error.serviceError.message : error.message}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<PanelGroup
|
<PanelGroup
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { RepositoryInfo, SearchRequest, SearchResultFile, SearchStats, StreamedSearchResponse } from '@/features/search';
|
import { RepositoryInfo, SearchRequest, SearchResultFile, SearchStats, StreamedSearchResponse } from '@/features/search';
|
||||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
import { ServiceErrorException } from '@/lib/serviceError';
|
||||||
|
import { isServiceError } from '@/lib/utils';
|
||||||
import * as Sentry from '@sentry/nextjs';
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface CacheEntry {
|
interface CacheEntry {
|
||||||
files: SearchResultFile[];
|
files: SearchResultFile[];
|
||||||
|
|
@ -132,6 +134,14 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
// Check if this is a service error response
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (contentType?.includes('application/json')) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
if (isServiceError(errorData)) {
|
||||||
|
throw new ServiceErrorException(errorData);
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,7 +259,7 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
|
||||||
...prev,
|
...prev,
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
timeToSearchCompletionMs,
|
timeToSearchCompletionMs,
|
||||||
error: error as Error,
|
error: error instanceof Error ? error : null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ import {
|
||||||
import { parser as _parser } from '@sourcebot/query-language';
|
import { parser as _parser } from '@sourcebot/query-language';
|
||||||
import { PrismaClient } from '@sourcebot/db';
|
import { PrismaClient } from '@sourcebot/db';
|
||||||
import { SINGLE_TENANT_ORG_ID } from '@/lib/constants';
|
import { SINGLE_TENANT_ORG_ID } from '@/lib/constants';
|
||||||
|
import { ServiceErrorException } from '@/lib/serviceError';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
import { ErrorCode } from '@/lib/errorCodes';
|
||||||
|
|
||||||
// Configure the parser to throw errors when encountering invalid syntax.
|
// Configure the parser to throw errors when encountering invalid syntax.
|
||||||
const parser = _parser.configure({
|
const parser = _parser.configure({
|
||||||
|
|
@ -61,35 +64,47 @@ export const parseQuerySyntaxIntoIR = async ({
|
||||||
},
|
},
|
||||||
prisma: PrismaClient,
|
prisma: PrismaClient,
|
||||||
}): Promise<QueryIR> => {
|
}): Promise<QueryIR> => {
|
||||||
// First parse the query into a Lezer tree.
|
|
||||||
const tree = parser.parse(query);
|
|
||||||
|
|
||||||
// Then transform the tree into the intermediate representation.
|
try {
|
||||||
return transformTreeToIR({
|
// First parse the query into a Lezer tree.
|
||||||
tree,
|
const tree = parser.parse(query);
|
||||||
input: query,
|
|
||||||
isCaseSensitivityEnabled: options.isCaseSensitivityEnabled ?? false,
|
// Then transform the tree into the intermediate representation.
|
||||||
isRegexEnabled: options.isRegexEnabled ?? false,
|
return transformTreeToIR({
|
||||||
onExpandSearchContext: async (contextName: string) => {
|
tree,
|
||||||
const context = await prisma.searchContext.findUnique({
|
input: query,
|
||||||
where: {
|
isCaseSensitivityEnabled: options.isCaseSensitivityEnabled ?? false,
|
||||||
name_orgId: {
|
isRegexEnabled: options.isRegexEnabled ?? false,
|
||||||
name: contextName,
|
onExpandSearchContext: async (contextName: string) => {
|
||||||
orgId: SINGLE_TENANT_ORG_ID,
|
const context = await prisma.searchContext.findUnique({
|
||||||
|
where: {
|
||||||
|
name_orgId: {
|
||||||
|
name: contextName,
|
||||||
|
orgId: SINGLE_TENANT_ORG_ID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
repos: true,
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
include: {
|
|
||||||
repos: true,
|
if (!context) {
|
||||||
|
throw new Error(`Search context "${contextName}" not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return context.repos.map((repo) => repo.name);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SyntaxError) {
|
||||||
|
throw new ServiceErrorException({
|
||||||
|
statusCode: StatusCodes.BAD_REQUEST,
|
||||||
|
errorCode: ErrorCode.FAILED_TO_PARSE_QUERY,
|
||||||
|
message: `Failed to parse query "${query}" with message: ${error.message}`,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (!context) {
|
throw error;
|
||||||
throw new Error(`Search context "${contextName}" not found`);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return context.repos.map((repo) => repo.name);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,5 @@ export enum ErrorCode {
|
||||||
API_KEY_NOT_FOUND = 'API_KEY_NOT_FOUND',
|
API_KEY_NOT_FOUND = 'API_KEY_NOT_FOUND',
|
||||||
INVALID_API_KEY = 'INVALID_API_KEY',
|
INVALID_API_KEY = 'INVALID_API_KEY',
|
||||||
CHAT_IS_READONLY = 'CHAT_IS_READONLY',
|
CHAT_IS_READONLY = 'CHAT_IS_READONLY',
|
||||||
|
FAILED_TO_PARSE_QUERY = 'FAILED_TO_PARSE_QUERY',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue