Skip to content

Commit

Permalink
send click events to algolia
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity committed Dec 18, 2024
1 parent 44a79e2 commit d467f60
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TooltipPortal } from "@radix-ui/react-tooltip";
import type { SendEventForHits } from "instantsearch.js/es/lib/utils";
import { PropsWithChildren, ReactNode, memo } from "react";
import { Snippet } from "react-instantsearch";

import { useSearchHits } from "../../hooks/use-search-hits";
import { useSearchHits, useSendEvent } from "../../hooks/use-search-hits";
import { AlgoliaRecordHit } from "../../types";
import * as Command from "../cmdk";
import { PageIcon } from "../icons/page";
Expand All @@ -23,25 +24,36 @@ export const CommandSearchHits = ({
}): ReactNode => {
const isQueryEmpty = Command.useCommandState((state) => state.search.trimStart().length === 0) as boolean;
const items = useSearchHits();
const sendEvent = useSendEvent();

const { filters } = useFacetFilters();

if ((filters.length === 0 && isQueryEmpty) || items.length === 0) {
return false;
}

return <MemoizedCommandSearchHits items={items} onSelect={onSelect} prefetch={prefetch} domain={domain} />;
return (
<MemoizedCommandSearchHits
items={items}
sendEvent={sendEvent}
onSelect={onSelect}
prefetch={prefetch}
domain={domain}
/>
);
};

const MemoizedCommandSearchHits = memo(
({
domain,
items,
sendEvent,
onSelect,
prefetch,
}: {
domain: string;
items: AlgoliaRecordHit[];
sendEvent: SendEventForHits;
onSelect: (path: string) => void;
prefetch?: (path: string) => Promise<void>;
}) => {
Expand All @@ -51,11 +63,19 @@ const MemoizedCommandSearchHits = memo(
<TooltipProvider>
{groups.map((group, index) => (
<Command.Group key={group.title ?? index} heading={group.title ?? "Results"} forceMount>
{group.hits.map((hit) => (
{group.hits.map((hit, hitIndex) => (
<CommandHit
key={hit.path}
hit={hit}
onSelect={onSelect}
sendClickEvent={(eventName, additionalData) => {
if (hit.record) {
sendEvent("click", hit.record, eventName, {
search_position: hitIndex + 1,
...additionalData,
});
}
}}
prefetch={prefetch}
domain={domain}
/>
Expand All @@ -71,6 +91,7 @@ function CommandHit({
hit,
domain,
onSelect,
sendClickEvent,
prefetch,
}: {
hit: GroupedHit;
Expand All @@ -79,6 +100,7 @@ function CommandHit({
* @param path - the path to navigate to via nextjs router
*/
onSelect: (path: string) => void;
sendClickEvent?: (eventName?: string | undefined, additionalData?: Record<string, any> | undefined) => void;
prefetch?: (path: string) => Promise<void>;
}) {
if (!hit.record) {
Expand All @@ -93,6 +115,7 @@ function CommandHit({
prefetch={prefetch}
onSelect={onSelect}
domain={domain}
sendClickEvent={sendClickEvent}
>
<PageIcon
icon={hit.icon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ export const CommandLink = forwardRef<
target?: string;
rel?: string;
prefetch?: (href: string) => Promise<void>;
sendClickEvent?: (eventName?: string | undefined, additionalData?: Record<string, any> | undefined) => void;
}
>(({ href, target, rel, onSelect, prefetch, domain, ...props }, forwardedRef) => {
>(({ href, target, rel, onSelect, prefetch, domain, sendClickEvent, ...props }, forwardedRef) => {
const ref = useRef<HTMLAnchorElement>(null);
const isSelected = Command.useCommandState((state) => state.value === href) as boolean;
const handleSelect = useCallback(() => {
Expand Down Expand Up @@ -50,10 +51,13 @@ export const CommandLink = forwardRef<
if (!element) {
return;
}
const listener = () => handleSelect();
const listener = () => {
handleSelect();
sendClickEvent?.("onselect");
};
element.addEventListener(Command.SELECT_EVENT, listener);
return () => element.removeEventListener(Command.SELECT_EVENT, listener);
}, [handleSelect]);
}, [handleSelect, sendClickEvent]);

const Comp = props.asChild ? Slot : "a";
// Note: `onSelect` is purposely not passed in here because these command items must be rendered as
Expand All @@ -72,6 +76,8 @@ export const CommandLink = forwardRef<
}
}}
onClick={(e) => {
sendClickEvent?.("onclick");

// if the user clicked this link without any modifier keys, and it's a left click, then we want to
// navigate to the link using the `onSelect` handler to defer the behavior to the NextJS router.
if (!e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey && (e.button === 0 || e.button === 1)) {
Expand Down
28 changes: 0 additions & 28 deletions packages/ui/fern-docs-search-ui/src/components/shared/hits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,13 @@ export interface GroupedHits {
hits: GroupedHit[];
}

// type SegmentType = "markdown" | "changelog" | "parameter" | "http" | "webhook" | "websocket";
// const SEGMENT_DISPLAY_NAMES: Record<SegmentType, string> = {
// markdown: "Guides",
// changelog: "Changelog",
// parameter: "Parameters",
// http: "Endpoints",
// webhook: "Webhooks",
// websocket: "WebSockets",
// };

export function generateHits(items: AlgoliaRecordHit[]): GroupedHits[] {
// return Object.entries(
// groupBy(items, (item): SegmentType => {
// if (item.type === "api-reference") {
// return item.api_type;
// }
// return item.type;
// }),
// ).map(([type, hits]) => ({
// title: SEGMENT_DISPLAY_NAMES[type as SegmentType] ?? type,
// hits: hits.map((hit) => ({
// title: hit.title,
// path: `${hit.pathname}${hit.hash ?? ""}`,
// icon: hit.icon,
// record: hit,
// })),
// }));

return [
{
title: "Results",
hits: items.map((hit) => ({
title: hit.title,
path: `${hit.pathname}${hit.hash ?? ""}`,
// category: SEGMENT_DISPLAY_NAMES[hit.type === "api-reference" ? hit.api_type : hit.type],
icon: hit.icon,
record: hit,
})),
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/fern-docs-search-ui/src/hooks/use-search-hits.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AlgoliaRecord } from "@fern-ui/fern-docs-search-server/algolia/types";
import type { SendEventForHits } from "instantsearch.js/es/lib/utils";
import { useEffect, useLayoutEffect, useState } from "react";
import { useHits } from "react-instantsearch";

Expand All @@ -11,6 +12,11 @@ export function useSearchHits(): AlgoliaRecordHit[] {
return items;
}

export function useSendEvent(): SendEventForHits {
const { sendEvent } = useHits<AlgoliaRecord>();
return sendEvent;
}

// this is a hack to force the `<Command>` component to re-render when the hits change, so that it can change its selected hit
// this doesn't work all the time, but slightly improves the UX otherwise the selection is sometimes 1-render behind.
export function useSearchHitsRerender(): void {
Expand Down

0 comments on commit d467f60

Please sign in to comment.