Skip to content

Commit

Permalink
feat(traces): span table column visibility controls (#1687)
Browse files Browse the repository at this point in the history
* WIP

* feat(traces): span table column visibility controls

* fix package lock

* Switch ignore pattern
  • Loading branch information
mikeldking authored Oct 31, 2023
1 parent b796cb4 commit 559852f
Show file tree
Hide file tree
Showing 7 changed files with 2,776 additions and 2,950 deletions.
5,495 changes: 2,568 additions & 2,927 deletions app/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"license": "None",
"private": true,
"dependencies": {
"@arizeai/components": "^1.0.1",
"@arizeai/components": "^1.0.2",
"@arizeai/point-cloud": "^3.0.4",
"@codemirror/autocomplete": "^6.9.2",
"@codemirror/lang-json": "^6.0.1",
Expand Down
138 changes: 138 additions & 0 deletions app/src/pages/tracing/SpanColumnSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { ChangeEvent, useCallback, useMemo } from "react";
import { Column, VisibilityState } from "@tanstack/react-table";
import { css } from "@emotion/react";

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

const UN_HIDABLE_COLUMN_IDS = ["spanKind", "name"];

type SpanColumnSelectorProps = {
/**
* The columns that can be displayed in the span table
* This could be made more generic to support other tables
* but for now working on the span tables to figure out the right interface
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
columns: Column<any>[];
/**
* Map of the column id to the visibility state
*/
columnVisibility: VisibilityState;
/*
* Callback to set the visibility state of a column
*/
onColumnVisibilityChange: (visibilityState: VisibilityState) => void;
};

export function SpanColumnSelector(props: SpanColumnSelectorProps) {
return (
<Dropdown
menu={<ColumnSelectorMenu {...props} />}
triggerProps={{
placement: "bottom end",
}}
>
<Flex direction="row" alignItems="center" gap="size-100">
<Icon svg={<Icons.Column />} />
Columns
</Flex>
</Dropdown>
);
}

const columCheckboxItemCSS = css`
padding: var(--ac-global-dimension-static-size-50)
var(--ac-global-dimension-static-size-100);
label {
display: flex;
align-items: center;
gap: var(--ac-global-dimension-static-size-100);
}
`;

function ColumnSelectorMenu(props: SpanColumnSelectorProps) {
const {
columns: propsColumns,
columnVisibility,
onColumnVisibilityChange,
} = props;

const columns = useMemo(() => {
return propsColumns.filter((column) => {
return !UN_HIDABLE_COLUMN_IDS.includes(column.id);
});
}, [propsColumns]);

const allVisible = useMemo(() => {
return columns.every((column) => {
const stateValue = columnVisibility[column.id];
const isVisible = stateValue == null ? true : stateValue;
return isVisible;
});
}, [columns, columnVisibility]);

const onCheckboxChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const { name, checked } = event.target;
onColumnVisibilityChange({ ...columnVisibility, [name]: checked });
},
[columnVisibility, onColumnVisibilityChange]
);

const onToggleAll = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const { checked } = event.target;
const newVisibilityState = columns.reduce((acc, column) => {
return { ...acc, [column.id]: checked };
}, {});
onColumnVisibilityChange(newVisibilityState);
},
[columns, onColumnVisibilityChange]
);

return (
<View paddingTop="size-50" paddingBottom="size-50">
<View
borderBottomColor="dark"
borderBottomWidth="thin"
paddingBottom="size-50"
>
<div css={columCheckboxItemCSS}>
<label>
<input
type="checkbox"
name={"toggle-all"}
checked={allVisible}
onChange={onToggleAll}
/>
toggle all
</label>
</div>
</View>

<ul>
{columns.map((column) => {
const stateValue = columnVisibility[column.id];
const isVisible = stateValue == null ? true : stateValue;
const name =
typeof column.columnDef.header == "string"
? column.columnDef.header
: column.id;
return (
<li key={column.id} css={columCheckboxItemCSS}>
<label>
<input
type="checkbox"
name={column.id}
checked={isVisible}
onChange={onCheckboxChange}
/>
{name}
</label>
</li>
);
})}
</ul>
</View>
);
}
7 changes: 6 additions & 1 deletion app/src/pages/tracing/SpanFilterConditionField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,12 @@ export function SpanFilterConditionField(props: SpanFilterConditionFieldProps) {
const hasError = errorMessage !== "";
const hasCondition = filterCondition !== "";
return (
<div data-is-focused={isFocused} data-is-invalid={hasError} css={fieldCSS}>
<div
data-is-focused={isFocused}
data-is-invalid={hasError}
className="span-filter-condition-field"
css={fieldCSS}
>
<Flex direction="row">
<AddonBefore>
<Icon svg={<Icons.Search />} />
Expand Down
34 changes: 23 additions & 11 deletions app/src/pages/tracing/SpansTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import {
getSortedRowModel,
SortingState,
useReactTable,
VisibilityState,
} from "@tanstack/react-table";
import { css } from "@emotion/react";

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

import { Link } from "@phoenix/components/Link";
import { selectableTableCSS } from "@phoenix/components/table/styles";
Expand All @@ -37,7 +38,9 @@ import {
SpanSort,
SpansTableSpansQuery,
} from "./__generated__/SpansTableSpansQuery.graphql";
import { SpanColumnSelector } from "./SpanColumnSelector";
import { SpanFilterConditionField } from "./SpanFilterConditionField";
import { spansTableCSS } from "./styles";
import { TokenCount } from "./TokenCount";
type SpansTableProps = {
query: SpansTable_spans$key;
Expand All @@ -54,6 +57,7 @@ export function SpansTable(props: SpansTableProps) {
const tableContainerRef = useRef<HTMLDivElement>(null);
const [sorting, setSorting] = useState<SortingState>([]);
const [filterCondition, setFilterCondition] = useState<string>("");
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const navigate = useNavigate();
const { data, loadNext, hasNext, isLoadingNext, refetch } =
usePaginationFragment<SpansTableSpansQuery, SpansTable_spans$key>(
Expand Down Expand Up @@ -238,17 +242,25 @@ export function SpansTable(props: SpansTableProps) {
});
const rows = table.getRowModel().rows;
const isEmpty = rows.length === 0;
const computedColumns = table.getAllColumns();
return (
<div
css={css`
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: hidden;
`}
>
<View padding="size-100" backgroundColor="grey-200" flex="none">
<SpanFilterConditionField onValidCondition={setFilterCondition} />
<div css={spansTableCSS}>
<View
paddingTop="size-100"
paddingBottom="size-100"
paddingStart="size-200"
paddingEnd="size-200"
backgroundColor="grey-200"
flex="none"
>
<Flex direction="row" gap="size-100" width="100%" alignItems="center">
<SpanFilterConditionField onValidCondition={setFilterCondition} />
<SpanColumnSelector
columns={computedColumns}
columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility}
/>
</Flex>
</View>

<div
Expand Down
34 changes: 24 additions & 10 deletions app/src/pages/tracing/TracesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getSortedRowModel,
SortingState,
useReactTable,
VisibilityState,
} from "@tanstack/react-table";
import { css } from "@emotion/react";

Expand All @@ -43,7 +44,9 @@ import {
SpanSort,
TracesTableQuery,
} from "./__generated__/TracesTableQuery.graphql";
import { SpanColumnSelector } from "./SpanColumnSelector";
import { SpanFilterConditionField } from "./SpanFilterConditionField";
import { spansTableCSS } from "./styles";
import { TokenCount } from "./TokenCount";
type TracesTableProps = {
query: TracesTable_spans$key;
Expand Down Expand Up @@ -324,6 +327,7 @@ export function TracesTable(props: TracesTableProps) {
[hasNext, isLoadingNext, loadNext]
);
const [expanded, setExpanded] = useState<ExpandedState>({});
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const table = useReactTable<TableRow>({
columns,
data: tableData,
Expand All @@ -332,6 +336,7 @@ export function TracesTable(props: TracesTableProps) {
state: {
sorting,
expanded,
columnVisibility,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
Expand All @@ -340,17 +345,26 @@ export function TracesTable(props: TracesTableProps) {
});
const rows = table.getRowModel().rows;
const isEmpty = rows.length === 0;
const computedColumns = table.getAllColumns();

return (
<div
css={css`
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: hidden;
`}
>
<View padding="size-100" backgroundColor="grey-200" flex="none">
<SpanFilterConditionField onValidCondition={setFilterCondition} />
<div css={spansTableCSS}>
<View
paddingTop="size-100"
paddingBottom="size-100"
paddingStart="size-200"
paddingEnd="size-200"
backgroundColor="grey-200"
flex="none"
>
<Flex direction="row" gap="size-100" width="100%" alignItems="center">
<SpanFilterConditionField onValidCondition={setFilterCondition} />
<SpanColumnSelector
columns={computedColumns}
columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility}
/>
</Flex>
</View>
<div
css={css`
Expand Down
16 changes: 16 additions & 0 deletions app/src/pages/tracing/styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { css } from "@emotion/react";

export const spansTableCSS = css`
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: hidden;
.span-filter-condition-field {
flex: 1 1 auto;
}
// Style the column selector
.ac-dropdown-button {
min-width: var(--ac-global-dimension-static-size-300);
}
`;

0 comments on commit 559852f

Please sign in to comment.