Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): enhance /search endpoint with flexible filtering and dynamic aggregations #155

Open
wants to merge 7 commits into
base: staging
Choose a base branch
from
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ The `.env` looks like:
```bash
API_KEY="0000000000000000000164dbb81fbea0a98f09eae1ff2a51493cb3a633523891=="
CLOUD_ID="Deployment_name:0000000000000000000365ff7535528e43b5c6793e840c2b2a0a38e1648c930f"
INDEX="index-name"
```

### Indexes Configuration

The application supports multiple Elasticsearch indexes. The configuration for these indexes can be found in [`src/config/config.ts`](/src/config/config.ts). To add or modify indexes, update the `INDEXES` object.

## Installation

```bash
Expand Down
182 changes: 107 additions & 75 deletions src/components/customResults/Result.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef } from "react";
import React, { useRef, useState } from "react";
import { getResultTags } from "@/config/config-helper";
import FilterTags from "../filterTag/FilterTags";
import sanitizeHtml from "sanitize-html";
Expand All @@ -10,6 +10,8 @@ import DateIcon from "../svgs/DateIcon";
import ResultFavicon from "./ResultFavicon";
import { useTheme } from "@/context/Theme";
import { remapUrl } from "@/utils/documents";
import { FiCode } from "react-icons/fi";
import DocumentModal from "@/components/explore/DocumentModal";

const htmlToReactParser = new (Parser as any)();

Expand All @@ -21,6 +23,13 @@ type ResultProps = {
};

const Result = ({ result }: ResultProps) => {
const [isDevMode] = useState(
() =>
typeof window !== "undefined" &&
localStorage.getItem("devMode") === "true"
);
const [showModal, setShowModal] = useState(false);

let dateString = null;
const { url, title, body, domain } = result;

Expand Down Expand Up @@ -62,7 +71,6 @@ const Result = ({ result }: ResultProps) => {
};

const sanitizedBody = sanitizeHtml(getBodyData(result)).trim();

const strippedUrl = mappedUrl.replace(/^(https?:\/\/)/i, "");
const truncatedUrl =
strippedUrl.length > TruncateLinkInChar
Expand All @@ -76,94 +84,118 @@ const Result = ({ result }: ResultProps) => {
const siteName = getDomainName(domain);

const linkRef = useRef<HTMLAnchorElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const { theme } = useTheme();
const isDark = theme === "dark";

const handleCardClick = (e: React.MouseEvent<HTMLDivElement>) => {
// e.stopPropagation()
if (e.target === containerRef?.current) {
const link = linkRef.current;
link && link.click();
}
};
const { theme } = useTheme();
const isDark = theme === "dark";

const containerRef = useRef<HTMLDivElement>(null);

return (
<div
role="link"
ref={containerRef}
className="group/heading flex flex-col gap-[12px] 2xl:gap-4 px-1 py-2 lg:p-5 lg:hover:shadow-lg hover:rounded-xl max-w-full lg:max-w-2xl 2xl:max-w-4xl"
onClick={handleCardClick}
>
<div className="flex gap-2 2xl:gap-4 items-center lg:text-xs 2xl:text-base text-custom-otherLight font-medium">
<ResultFavicon
key={`${siteName}-dark:${isDark}`}
src={getDomainFavicon(domain, isDark)}
alt={`${siteName}-favicon`}
domain={domain}
isDark={isDark}
numbersOfRetry={0}
/>
<div className="font-geist leading-none font-medium flex flex-wrap flex-col gap-y-[2px] lg:flex-row lg:items-center lg:gap-x-2 2xl:gap-x-4">
<a
href={domain}
className="capitalize text-sm lg:text-base leading-none hover:underline"
>
{siteName}
</a>
<div className="hidden lg:block w-[2px] h-[2px] lg:w-[6px] lg:h-[6px] rounded-full text-custom-secondary-text bg-custom-black" />
<a
target="_blank"
className="text-[12px] lg:text-base leading-none"
href={mappedUrl}
data-umami-event="URL Clicked"
data-umami-event-src={mappedUrl}
ref={linkRef}
<>
<div
role="link"
ref={containerRef}
className="group/heading flex flex-col gap-[12px] 2xl:gap-4 px-1 py-2 lg:p-5 lg:hover:shadow-lg hover:rounded-xl max-w-full lg:max-w-2xl 2xl:max-w-4xl relative"
onClick={handleCardClick}
>
{isDevMode && (
<button
onClick={(e) => {
e.stopPropagation();
setShowModal(true);
}}
className="absolute top-2 right-2 p-2 text-custom-secondary-text hover:text-custom-accent transition-colors"
title="View document data"
>
{truncatedUrl}
</a>
<FiCode size={16} />
</button>
)}

<div className="flex gap-2 2xl:gap-4 items-center lg:text-xs 2xl:text-base text-custom-otherLight font-medium">
<ResultFavicon
key={`${siteName}-dark:${isDark}`}
src={getDomainFavicon(domain, isDark)}
alt={`${siteName}-favicon`}
domain={domain}
isDark={isDark}
numbersOfRetry={0}
/>
<div className="font-geist leading-none font-medium flex flex-wrap flex-col gap-y-[2px] lg:flex-row lg:items-center lg:gap-x-2 2xl:gap-x-4">
<a
href={domain}
className="capitalize text-sm lg:text-base leading-none hover:underline"
>
{siteName}
</a>
<div className="hidden lg:block w-[2px] h-[2px] lg:w-[6px] lg:h-[6px] rounded-full text-custom-secondary-text bg-custom-black" />
<a
target="_blank"
className="text-[12px] lg:text-base leading-none"
href={mappedUrl}
data-umami-event="URL Clicked"
data-umami-event-src={mappedUrl}
ref={linkRef}
>
{truncatedUrl}
</a>
</div>
</div>
</div>
<div className="font-mona pointer-events-none flex flex-col gap-2 lg:gap-5 ">
<h2 className="text-sm lg:text-base 2xl:text-[1.375rem] text-custom-primary-text font-semibold">
<a
href={mappedUrl}
target="_blank"
className="pointer-events-auto md:group-hover/heading:text-custom-accent cursor-pointer hover:underline"
>
{htmlToReactParser.parse(sanitizeHtml(title))}
</a>
</h2>
<p className="text-sm lg:text-base 2xl:text-lg text-custom-secondary-text">
{parsedBody}
</p>
</div>
<div className="pointer-events-none flex flex-col gap-4 md:flex-row md:justify-between xl:gap-12 lg:items-center">
{dateString && (
<div className="pointer-events-none flex shrink-0 gap-2 lg:gap-16 text-base font-semibold text-custom-primary-text">
<div className="flex w-full items-center gap-2 font-mona font-medium text-custom-secondary-text">
<DateIcon className="flex-shrink-0" />
<p className="text-[12px] mb-[-2px] whitespace-nowrap lg:text-sm 2xl:text-base leading-none">
{dateString}
</p>
<div className="font-mona pointer-events-none flex flex-col gap-2 lg:gap-5">
<h2 className="text-sm lg:text-base 2xl:text-[1.375rem] text-custom-primary-text font-semibold">
<a
href={mappedUrl}
target="_blank"
className="pointer-events-auto md:group-hover/heading:text-custom-accent cursor-pointer hover:underline"
>
{htmlToReactParser.parse(sanitizeHtml(title))}
</a>
</h2>
<p className="text-sm lg:text-base 2xl:text-lg text-custom-secondary-text">
{parsedBody}
</p>
</div>
<div className="pointer-events-none flex flex-col gap-4 md:flex-row md:justify-between xl:gap-12 lg:items-center">
{dateString && (
<div className="pointer-events-none flex shrink-0 gap-2 lg:gap-16 text-base font-semibold text-custom-primary-text">
<div className="flex w-full items-center gap-2 font-mona font-medium text-custom-secondary-text">
<DateIcon className="flex-shrink-0" />
<p className="text-[12px] mb-[-2px] whitespace-nowrap lg:text-sm 2xl:text-base leading-none">
{dateString}
</p>
</div>
</div>
)}
<div
className={`md:ml-auto pointer-events-auto flex overflow-hidden`}
>
{getResultTags().map((field, idx) => {
if (result[field])
return (
<FilterTags
key={`${field}_${idx}`}
field={field}
options={result[field]}
/>
);
})}
</div>
)}
<div className={`md:ml-auto pointer-events-auto flex overflow-hidden`}>
{getResultTags().map((field, idx) => {
if (result[field])
return (
<FilterTags
key={`${field}_${idx}`}
field={field}
options={result[field]}
/>
);
})}
</div>
</div>
</div>

{showModal && (
<DocumentModal
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For devMode in the results page, is there a reason fetching the document again?

we can pull the data already and pass it along to render. The only difference is data exempted from the main query, i.e summary_embeddings will be unavaialable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it makes it easier:

  • it allows us to reuse the same component as is
  • I don't need to care about moving data
  • This is a mode that will not be used a lot

isOpen={showModal}
onClose={() => setShowModal(false)}
id={result.id}
selectedIndex="main"
/>
)}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import {
Box,
VStack,
} from "@chakra-ui/react";
import { useDocumentContent } from "@/hooks/useDocumentContent";

interface DocumentModalProps {
isOpen: boolean;
onClose: () => void;
document: Record<string, any> | null;
isLoading: boolean;
isError: boolean;
error?: string;
id: string | null;
selectedIndex: string;
}

const formatValue = (value: any): string => {
Expand Down Expand Up @@ -86,35 +85,40 @@ const RenderObject = ({ object }: { object: Record<string, any> }) => {
const DocumentModal: React.FC<DocumentModalProps> = ({
isOpen,
onClose,
document,
isLoading,
isError,
error,
id,
selectedIndex,
}) => {
const { documentContent, isLoading, isError, error } = useDocumentContent(
id || "",
selectedIndex
);

return (
<Modal isOpen={isOpen} onClose={onClose} size="xl" scrollBehavior="inside">
<ModalOverlay />
<ModalContent>
<ModalHeader>{document?.title || "Document Details"}</ModalHeader>
<ModalHeader>
{documentContent?.title || "Document Details"}
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{isLoading && <Text>Loading document content...</Text>}
{isError && (
<Text color="red.500">Error loading document: {error}</Text>
<Text color="red.500">Error loading document: {error.message}</Text>
)}
{!isLoading && !isError && document && (
{!isLoading && !isError && documentContent && (
<VStack align="stretch" spacing={4}>
{document.url && (
{documentContent.url && (
<Link
href={document.url}
href={documentContent.url}
isExternal
color="blue.500"
_hover={{ textDecoration: "underline" }}
>
{document.url}
{documentContent.url}
</Link>
)}
<RenderObject object={document} />
<RenderObject object={documentContent} />
</VStack>
)}
</ModalBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import React from "react";
import { FaEye } from "react-icons/fa";
import { FaRegFileAlt } from "react-icons/fa";
import { formatTimeAgo } from "@/utils/dateUtils";
import { Document } from "@/types";
import type { FlatTableProps } from "./types";

interface DocumentTableProps {
documents: Document[];
domain: string;
onViewDocument: (url: string) => void;
}

const DocumentTable: React.FC<DocumentTableProps> = ({
export const FlatTable: React.FC<FlatTableProps> = ({
documents,
domain,
onViewDocument,
}) => {
const trimUrl = (url: string, domain: string): string => {
Expand All @@ -36,9 +29,9 @@ const DocumentTable: React.FC<DocumentTableProps> = ({
</tr>
</thead>
<tbody>
{documents?.map((doc, index) => (
{documents.map((doc, index) => (
<tr
key={doc.url}
key={doc.id}
className={
index % 2 === 0
? "bg-custom-hover-state dark:bg-custom-hover-primary"
Expand All @@ -59,7 +52,7 @@ const DocumentTable: React.FC<DocumentTableProps> = ({
className="text-custom-accent hover:underline"
title={doc.url}
>
{doc.url && trimUrl(doc.url, domain)}
{doc.url && trimUrl(doc.url, doc.domain)}
</a>
</div>
</td>
Expand All @@ -73,11 +66,11 @@ const DocumentTable: React.FC<DocumentTableProps> = ({
</td>
<td className="w-10 px-2 py-2 border-b border-custom-stroke text-center">
<button
onClick={() => onViewDocument(doc.url)}
onClick={() => onViewDocument(doc.id)}
className="text-custom-accent hover:text-custom-accent-dark"
title="View document"
>
<FaEye className="w-5 h-5 inline-block" />
<FaRegFileAlt className="w-5 h-5 inline-block" />
</button>
</td>
</tr>
Expand All @@ -86,5 +79,3 @@ const DocumentTable: React.FC<DocumentTableProps> = ({
</table>
);
};

export default DocumentTable;
Loading