Skip to content

Commit

Permalink
feat(sessions): filter sessions via substring search on root span inp…
Browse files Browse the repository at this point in the history
…ut output values (#5257)
  • Loading branch information
RogerHYang authored Nov 4, 2024
1 parent 7e9b4b4 commit 1ecd28e
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 71 deletions.
2 changes: 1 addition & 1 deletion app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,7 @@ type Project implements Node {
spanLatencyMsQuantile(probability: Float!, timeRange: TimeRange, filterCondition: String): Float
trace(traceId: ID!): Trace
spans(timeRange: TimeRange, first: Int = 50, last: Int, after: String, before: String, sort: SpanSort, rootSpansOnly: Boolean, filterCondition: String): SpanConnection!
sessions(timeRange: TimeRange, first: Int = 50, after: String): ProjectSessionConnection!
sessions(timeRange: TimeRange, first: Int = 50, after: String, filterIoSubstring: String): ProjectSessionConnection!

"""
Names of all available annotations for traces. (The list contains no duplicates.)
Expand Down
5 changes: 3 additions & 2 deletions app/src/pages/project/ProjectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ProjectPageQuery } from "./__generated__/ProjectPageQuery.graphql";
import { ProjectPageSessionsQuery as ProjectPageSessionsQueryType } from "./__generated__/ProjectPageSessionsQuery.graphql";
import { ProjectPageSpansQuery as ProjectPageSpansQueryType } from "./__generated__/ProjectPageSpansQuery.graphql";
import { ProjectPageHeader } from "./ProjectPageHeader";
import { SessionSearchProvider } from "./SessionSearchContext";
import { SessionsTable } from "./SessionsTable";
import { SpanFilterConditionProvider } from "./SpanFilterConditionContext";
import { SpansTable } from "./SpansTable";
Expand Down Expand Up @@ -193,13 +194,13 @@ export function ProjectPageContent({
return (
isSelected &&
sessionsQueryReference && (
<SpanFilterConditionProvider>
<SessionSearchProvider>
<Suspense>
<SessionsTabContent
queryReference={sessionsQueryReference}
/>
</Suspense>
</SpanFilterConditionProvider>
</SessionSearchProvider>
)
);
}}
Expand Down
45 changes: 45 additions & 0 deletions app/src/pages/project/SessionSearchContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, {
createContext,
PropsWithChildren,
startTransition,
useCallback,
useContext,
useState,
} from "react";

export type SessionSearchContextType = {
filterIoSubstring: string;
setFilterIoSubstring: (condition: string) => void;
};

export const SessionSearchContext =
createContext<SessionSearchContextType | null>(null);

export function useSessionSearchContext() {
const context = useContext(SessionSearchContext);
if (context === null) {
throw new Error(
"useSessionSubstring must be used within a SessionSubstringProvider"
);
}
return context;
}

export function SessionSearchProvider(props: PropsWithChildren) {
const [substring, _setSubstring] = useState<string>("");
const setSubstring = useCallback((condition: string) => {
startTransition(() => {
_setSubstring(condition);
});
}, []);
return (
<SessionSearchContext.Provider
value={{
filterIoSubstring: substring,
setFilterIoSubstring: setSubstring,
}}
>
{props.children}
</SessionSearchContext.Provider>
);
}
103 changes: 103 additions & 0 deletions app/src/pages/project/SessionSearchField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useState } from "react";
import { nord } from "@uiw/codemirror-theme-nord";
import CodeMirror from "@uiw/react-codemirror";
import { css } from "@emotion/react";

import { AddonBefore, Flex, Icon, Icons } from "@arizeai/components";

import { useTheme } from "../../contexts";

import { useSessionSearchContext } from "./SessionSearchContext";

const codeMirrorCSS = css`
flex: 1 1 auto;
.cm-content {
padding: var(--ac-global-dimension-static-size-100) 0;
}
.cm-editor {
background-color: transparent;
}
.cm-focused {
outline: none;
}
.cm-selectionLayer .cm-selectionBackground {
background: var(--ac-global-color-cyan-400) !important;
}
`;

const fieldCSS = css`
border-width: var(--ac-global-border-size-thin);
border-style: solid;
border-color: var(--ac-global-input-field-border-color);
border-radius: var(--ac-global-rounding-small);
background-color: var(--ac-global-input-field-background-color);
transition: all 0.2s ease-in-out;
overflow-x: hidden;
&:hover,
&[data-is-focused="true"] {
border-color: var(--ac-global-input-field-border-color-active);
background-color: var(--ac-global-input-field-background-color-active);
}
&[data-is-invalid="true"] {
border-color: var(--ac-global-color-danger);
}
box-sizing: border-box;
`;

type SessionsSubstringFieldProps = {
placeholder?: string;
};
export function SessionSearchField(props: SessionsSubstringFieldProps) {
const { placeholder = "Search messages" } = props;
const [isFocused, setIsFocused] = useState<boolean>(false);
const { filterIoSubstring, setFilterIoSubstring } = useSessionSearchContext();
const { theme } = useTheme();
const codeMirrorTheme = theme === "dark" ? nord : undefined;

const hasSubstring = filterIoSubstring !== "";
return (
<div
data-is-focused={isFocused}
className="sessions-substring-field"
css={fieldCSS}
>
<Flex direction="row">
<AddonBefore>
<Icon svg={<Icons.Search />} />
</AddonBefore>
<CodeMirror
css={codeMirrorCSS}
indentWithTab={false}
basicSetup={{
lineNumbers: false,
foldGutter: false,
bracketMatching: false,
syntaxHighlighting: false,
highlightActiveLine: false,
highlightActiveLineGutter: false,
defaultKeymap: false,
}}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
value={filterIoSubstring}
onChange={setFilterIoSubstring}
height="36px"
width="100%"
theme={codeMirrorTheme}
placeholder={placeholder}
/>
<button
css={css`
margin-right: var(--ac-global-dimension-static-size-100);
color: var(--ac-global-text-color-700);
visibility: ${hasSubstring ? "visible" : "hidden"};
`}
onClick={() => setFilterIoSubstring("")}
className="button--reset"
>
<Icon svg={<Icons.CloseCircleOutline />} />
</button>
</Flex>
</div>
);
}
19 changes: 13 additions & 6 deletions app/src/pages/project/SessionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import { TokenCount } from "../../components/trace/TokenCount";

import { SessionsTable_sessions$key } from "./__generated__/SessionsTable_sessions.graphql";
import { SessionsTableQuery } from "./__generated__/SessionsTableQuery.graphql";
import { useSessionSearchContext } from "./SessionSearchContext";
import { SessionSearchField } from "./SessionSearchField";
import { SessionsTableEmpty } from "./SessionsTableEmpty";
import { SpanFilterConditionField } from "./SpanFilterConditionField";
import { spansTableCSS } from "./styles";

type SessionsTableProps = {
Expand All @@ -46,7 +47,7 @@ export function SessionsTable(props: SessionsTableProps) {
// we need a reference to the scrolling element for pagination logic down below
const tableContainerRef = useRef<HTMLDivElement>(null);
const [sorting, setSorting] = useState<SortingState>([]);
const [filterCondition, setFilterCondition] = useState<string>("");
const { filterIoSubstring } = useSessionSearchContext();
const navigate = useNavigate();
const { fetchKey } = useStreamState();
const { data, loadNext, hasNext, isLoadingNext, refetch } =
Expand All @@ -57,10 +58,15 @@ export function SessionsTable(props: SessionsTableProps) {
@argumentDefinitions(
after: { type: "String", defaultValue: null }
first: { type: "Int", defaultValue: 50 }
filterIoSubstring: { type: "String", defaultValue: null }
) {
name
sessions(first: $first, after: $after, timeRange: $timeRange)
@connection(key: "SessionsTable_sessions") {
sessions(
first: $first
after: $after
filterIoSubstring: $filterIoSubstring
timeRange: $timeRange
) @connection(key: "SessionsTable_sessions") {
edges {
session: node {
id
Expand Down Expand Up @@ -154,11 +160,12 @@ export function SessionsTable(props: SessionsTableProps) {
{
after: null,
first: PAGE_SIZE,
filterIoSubstring: filterIoSubstring,
},
{ fetchPolicy: "store-and-network" }
);
});
}, [sorting, refetch, filterCondition, fetchKey]);
}, [sorting, refetch, filterIoSubstring, fetchKey]);
const fetchMoreOnBottomReached = React.useCallback(
(containerRefElement?: HTMLDivElement | null) => {
if (containerRefElement) {
Expand Down Expand Up @@ -207,7 +214,7 @@ export function SessionsTable(props: SessionsTableProps) {
borderBottomWidth="thin"
flex="none"
>
<SpanFilterConditionField onValidCondition={setFilterCondition} />
<SessionSearchField />
</View>
<div
css={css`
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1ecd28e

Please sign in to comment.