Skip to content

Commit

Permalink
upgraded to searchkit v4 (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hironori Yamamoto authored Mar 9, 2023
1 parent aea8aeb commit e74a6c3
Show file tree
Hide file tree
Showing 16 changed files with 879 additions and 1,277 deletions.
3 changes: 2 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
LOG_LEVEL=INFO
REDASH__URL=
REDASH__API_KEY=
NEXT_PUBLIC_APP__URL=http://localhost:3000
NEXT_PUBLIC_REDASH__URL=${REDASH__URL}
OPEN_SEARCH__URL=http://opensearch-node-main:9200
OPEN_SEARCH__URL=http://localhost:9200
OPEN_SEARCH__USERNAME=admin
OPEN_SEARCH__PASSWORD=admin
87 changes: 87 additions & 0 deletions redash-searcher-web/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { EuiPageTemplate, EuiTitle } from "@elastic/eui";
import React from "react";
import {
RefinementList,
SearchBox,
InstantSearch,
CurrentRefinements,
Stats,
InfiniteHits,
InstantSearchProps,
Hits,
} from "react-instantsearch-dom";
import Hit from "../components/Hit";

interface CurrentRefinementItem {
id: string;
index: string;
attribute: string;
label: string;
currentRefinement: string[];
}

const reduceDuplicateRefinement = (items: CurrentRefinementItem[]) => {
const seen = new Set();
return items.filter((item: any) => {
if (seen.has(item.label)) {
return false;
} else {
seen.add(item.label);
return true;
}
});
};

export default function App(props: InstantSearchProps) {
return (
<div className="ais-InstantSearch">
<InstantSearch {...props}>
<EuiPageTemplate panelled={true}>
<EuiPageTemplate.Sidebar>
<EuiTitle size="l">
<h1>Redash Searcher</h1>
</EuiTitle>
<EuiTitle size="xs">
<h4>Data Source</h4>
</EuiTitle>
<RefinementList
attribute="data_source_type"
searchable={true}
limit={10}
/>
<EuiTitle size="xs">
<h4>User Name</h4>
</EuiTitle>
<RefinementList
attribute="user_name"
searchable={true}
limit={10}
/>
<EuiTitle size="xs">
<h4>User Email</h4>
</EuiTitle>
<RefinementList
attribute="user_email"
searchable={true}
limit={10}
/>
<EuiTitle size="xs">
<h4>Tags</h4>
</EuiTitle>
<RefinementList attribute="tags" searchable={true} limit={10} />
</EuiPageTemplate.Sidebar>
<EuiPageTemplate.Section>
<SearchBox />
<Stats />
<div style={{ margin: "1rem 0" }}>
<CurrentRefinements transformItems={reduceDuplicateRefinement} />
</div>
</EuiPageTemplate.Section>
<EuiPageTemplate.Section>
<InfiniteHits hitComponent={Hit} />
</EuiPageTemplate.Section>
</EuiPageTemplate>
</InstantSearch>
</div>
);
}
48 changes: 31 additions & 17 deletions redash-searcher-web/components/HighlightedQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
import { EuiCallOut, EuiText } from "@elastic/eui";
import dompurify from "dompurify";
import { EuiCallOut } from "@elastic/eui";
import { connectHighlight } from "react-instantsearch-dom";

export interface HighlightedQueryProps {
query: string;
}

export const HighlightedQuery: React.FC<HighlightedQueryProps> = ({
query,
}: HighlightedQueryProps) => {
const sanitizer = dompurify.sanitize;
const HighlightQueryInner = ({ highlight, attribute, hit }: any) => {
const parsedHit = highlight({
highlightProperty: "_highlightResult",
attribute,
hit,
});
return (
<EuiCallOut>
<p
style={{ whiteSpace: "pre-wrap" }}
dangerouslySetInnerHTML={{
__html: sanitizer(query),
}}
/>
<EuiCallOut
style={{ whiteSpace: "pre-wrap", maxWidth: "1000px" }}
color={"success"}
>
<span className="ais-Highlight">
<span>
{parsedHit.map((part: any, index: number) =>
part.isHighlighted ? (
<mark className="ais-Highlight-highlighted" key={index}>
{part.value}
</mark>
) : (
<span className="ais-Highlight-nonHighlighted" key={index}>
{part.value}
</span>
)
)}
</span>
</span>
</EuiCallOut>
);
};

const HighlightedQuery = connectHighlight(HighlightQueryInner);

export default HighlightedQuery;
92 changes: 92 additions & 0 deletions redash-searcher-web/components/Hit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Hit as HitCore } from "react-instantsearch-core";
import { IResultHitField } from "../pages/api/models";
import {
EuiFlexGrid,
EuiFlexItem,
EuiCard,
EuiSpacer,
EuiDescriptionList,
EuiFlexGroup,
EuiAvatar,
} from "@elastic/eui";
import HighlightedQuery from "./HighlightedQuery";

export interface HitProps {
hit: HitCore<IResultHitField>;
}

const REDASH_URL = (process.env.NEXT_PUBLIC_REDASH__URL || "").replace(
/\/$/,
""
);

const Hit: React.FC<HitProps> = ({ hit }: HitProps) => {
return (
<EuiCard
layout="horizontal"
icon={
<EuiAvatar
name={`logo-${hit.data_source_type}`}
size="l"
imageUrl={`${REDASH_URL}/static/images/db-logos/${hit.data_source_type}.png`}
color="#e0e5ee"
/>
}
title={hit.name}
href={`${REDASH_URL}/queries/${hit.id}/source`}
description={hit.description}
>
<EuiFlexGrid gutterSize="xl">
<EuiFlexGroup>
<EuiFlexItem grow={5}>
<EuiDescriptionList
textStyle="reverse"
listItems={[
{
title: "Data Source Type",
description: hit.data_source_type,
},
{
title: "Author",
description: hit.user_name,
},
{
title: "Tags",
description: hit.tags.join(", ") || "No Tags",
},
]}
/>
</EuiFlexItem>
<EuiFlexItem grow={5}>
<EuiDescriptionList
textStyle="reverse"
listItems={[
{
title: "Data Source Name",
description: hit.data_source_name,
},
{
title: "Created At",
description: hit.created_at,
},
{
title: "Updated At",
description: hit.updated_at,
},
]}
/>
</EuiFlexItem>
</EuiFlexGroup>
{hit._highlightResult.query?.matchedWords.length !== 0 && (
<EuiFlexGroup>
<EuiFlexItem grow={5}>
<HighlightedQuery hit={hit} attribute="query"></HighlightedQuery>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiFlexGrid>
</EuiCard>
);
};

export default Hit;
101 changes: 0 additions & 101 deletions redash-searcher-web/components/HitList.tsx

This file was deleted.

Loading

0 comments on commit e74a6c3

Please sign in to comment.