From 24a78aef6826644ea520a53cce85fd0a62e82a68 Mon Sep 17 00:00:00 2001 From: ashrafchowdury Date: Thu, 17 Oct 2024 20:52:25 +0600 Subject: [PATCH 01/16] feat(frontend): implemented filters in observability --- .../observability/TableHeader/Filters.tsx | 153 ++++++++++++++++++ .../pages/observability/TableHeader/Sort.tsx | 43 +++++ agenta-web/src/lib/Types.ts | 6 + agenta-web/src/lib/helpers/utils.ts | 94 +++++++++++ .../apps/[app_id]/observability/index.tsx | 72 ++++++++- 5 files changed, 364 insertions(+), 4 deletions(-) create mode 100644 agenta-web/src/components/pages/observability/TableHeader/Filters.tsx create mode 100644 agenta-web/src/components/pages/observability/TableHeader/Sort.tsx diff --git a/agenta-web/src/components/pages/observability/TableHeader/Filters.tsx b/agenta-web/src/components/pages/observability/TableHeader/Filters.tsx new file mode 100644 index 0000000000..50ec47f6ef --- /dev/null +++ b/agenta-web/src/components/pages/observability/TableHeader/Filters.tsx @@ -0,0 +1,153 @@ +import React, {useState} from "react" +import {Filter, JSSTheme} from "@/lib/Types" +import {ArrowCounterClockwise, CaretDown, Funnel, X} from "@phosphor-icons/react" +import {Button, Divider, Input, Popover, Select, Space, Typography} from "antd" +import {createUseStyles} from "react-jss" + +const useStyles = createUseStyles((theme: JSSTheme) => ({ + popover: { + "& .ant-popover-inner": { + width: "600px !important", + padding: `0px ${theme.paddingXS}px ${theme.paddingXS}px ${theme.padding}px`, + }, + }, + filterHeading: { + fontSize: theme.fontSizeHeading5, + lineHeight: theme.lineHeightHeading5, + fontWeight: theme.fontWeightMedium, + }, + filterContainer: { + padding: 8, + gap: 8, + borderRadius: theme.borderRadius, + backgroundColor: "#f5f7fa", + marginTop: 8, + }, +})) + +type Props = { + setFilterValue: React.Dispatch> + columns: {column: string; mapping: string}[] +} + +const Filters: React.FC = ({setFilterValue, columns}) => { + const classes = useStyles() + const [filter, setFilter] = useState({} as Filter) + const [isFilterOpen, setIsFilterOpen] = useState(false) + + const conditions = [ + "contains", + "does not contain", + "starts with", + "ends with", + "exists", + "does not exist", + "=", + ">", + "<", + ">=", + "<=", + ] + + const clearFilter = () => { + setFilter({} as Filter) + setFilterValue({} as Filter) + } + + return ( + setIsFilterOpen(false)} + open={isFilterOpen} + placement="bottomLeft" + content={ +
+
+ Filter + + +
+ +
+ +
+ +
+ + Where + + } + onChange={(value) => + setFilter({...filter, condition: value}) + } + popupMatchSelectWidth={250} + value={filter.condition} + options={conditions.map((item) => { + return {value: item, label: item.toUpperCase()} + })} + /> + + + setFilter({...filter, keyword: e.target.value}) + } + /> + + )} + +
+ +
+ +
+
+ } + > + +
+ ) +} + +export default Filters diff --git a/agenta-web/src/components/pages/observability/TableHeader/Sort.tsx b/agenta-web/src/components/pages/observability/TableHeader/Sort.tsx new file mode 100644 index 0000000000..3e50b8de15 --- /dev/null +++ b/agenta-web/src/components/pages/observability/TableHeader/Sort.tsx @@ -0,0 +1,43 @@ +import React, {useState} from "react" +import {Hourglass} from "@phosphor-icons/react" +import {Select} from "antd" + +type Props = { + setSort: React.Dispatch> + sort?: string +} + +const Sort: React.FC = ({setSort, sort}) => { + return ( + Date: Tue, 22 Oct 2024 12:23:05 +0600 Subject: [PATCH 05/16] fix(frontend): obserbility filter component logic --- agenta-web/src/components/Filters/Filters.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/agenta-web/src/components/Filters/Filters.tsx b/agenta-web/src/components/Filters/Filters.tsx index 28518b4784..dff2b4f4c5 100644 --- a/agenta-web/src/components/Filters/Filters.tsx +++ b/agenta-web/src/components/Filters/Filters.tsx @@ -71,7 +71,6 @@ const Filters: React.FC = ({filterValue, setFilterValue, columns}) => { } const onDeleteFilter = (index: number) => { - if (index === 0) return setFilter(filter.filter((_, idx) => idx !== index)) } @@ -173,7 +172,7 @@ const Filters: React.FC = ({filterValue, setFilterValue, columns}) => { /> )} - {idx > 0 && ( + {filter.length > 1 && ( From 3ba157b675ba42bfdbbb2311882af825d7fb0c3c Mon Sep 17 00:00:00 2001 From: ashrafchowdury Date: Tue, 22 Oct 2024 23:51:57 +0600 Subject: [PATCH 06/16] feat(frontend): added edit column and export data feature --- .../src/components/Filters/EditColumns.tsx | 110 ++++++++++++ agenta-web/src/components/Filters/Filters.tsx | 31 ++-- agenta-web/src/components/Filters/Sort.tsx | 30 ++-- agenta-web/src/lib/Types.ts | 13 ++ agenta-web/src/lib/helpers/utils.ts | 61 ------- .../apps/[app_id]/observability/index.tsx | 157 ++++++++++++------ 6 files changed, 262 insertions(+), 140 deletions(-) create mode 100644 agenta-web/src/components/Filters/EditColumns.tsx diff --git a/agenta-web/src/components/Filters/EditColumns.tsx b/agenta-web/src/components/Filters/EditColumns.tsx new file mode 100644 index 0000000000..507f7a3c3e --- /dev/null +++ b/agenta-web/src/components/Filters/EditColumns.tsx @@ -0,0 +1,110 @@ +import {Button, Dropdown, Space, Checkbox} from "antd" +import React, {useState} from "react" +import {createUseStyles} from "react-jss" +import {Columns} from "@phosphor-icons/react" +import {ColumnsType} from "antd/es/table" +import type {MenuProps} from "antd" + +const useStyles = createUseStyles((theme) => ({ + dropdownMenu: { + "&>.ant-dropdown-menu-item": { + "& .anticon-check": { + display: "none", + }, + }, + "&>.ant-dropdown-menu-item-selected": { + "&:not(:hover)": { + backgroundColor: "transparent !important", + }, + "& .anticon-check": { + display: "inline-flex !important", + }, + }, + }, + button: { + display: "flex", + alignItems: "center", + }, +})) + +interface EditColumnsProps { + isOpen: boolean + handleOpenChange: (open: boolean) => void + selectedKeys: string[] // Default selected column keys + columns: ColumnsType + onChange: (key: string) => void + excludes?: string[] // Array of column keys to exclude + buttonText?: string +} + +const EditColumns: React.FC = ({ + isOpen, + handleOpenChange, + selectedKeys, + columns, + onChange, + excludes = [], + buttonText = "Edit Columns", +}) => { + const classes = useStyles() + const [open, setOpen] = useState(isOpen) + + const handleDropdownChange = (newOpen: boolean) => { + setOpen(newOpen) + if (!newOpen) handleOpenChange(newOpen) // Only trigger close action if dropdown is closing + } + + const generateEditItems = (): MenuProps["items"] => { + return columns + .filter((col) => !excludes.includes(col.key as string)) + .flatMap((col) => [ + { + key: col.key as React.Key, + label: ( + e.stopPropagation()}> + onChange(col.key as string)} + /> + {col.title as string} + + ), + }, + ...(("children" in col && + col.children?.map((child) => ({ + key: child.key as React.Key, + label: ( + e.stopPropagation()}> + onChange(child.key as string)} + /> + {child.title as string} + + ), + }))) || + []), + ]) + } + + return ( + + + + ) +} + +export default EditColumns diff --git a/agenta-web/src/components/Filters/Filters.tsx b/agenta-web/src/components/Filters/Filters.tsx index dff2b4f4c5..be795bedcf 100644 --- a/agenta-web/src/components/Filters/Filters.tsx +++ b/agenta-web/src/components/Filters/Filters.tsx @@ -28,18 +28,16 @@ const useStyles = createUseStyles((theme: JSSTheme) => ({ })) type Props = { - filterValue: Filter[] - setFilterValue: React.Dispatch> columns: {column: string; mapping: string}[] + onApplyFilter: (filters: Filter[]) => void + onClearFilter: (filters: Filter[]) => void } -const Filters: React.FC = ({filterValue, setFilterValue, columns}) => { +const Filters: React.FC = ({columns, onApplyFilter, onClearFilter}) => { const classes = useStyles() const emptyFilter = [{condition: "", column: "", keyword: ""}] as Filter[] - const [filter, setFilter] = useState(() => - filterValue.length > 0 ? filterValue : emptyFilter, - ) + const [filter, setFilter] = useState(emptyFilter) const [isFilterOpen, setIsFilterOpen] = useState(false) const conditions = [ @@ -80,7 +78,7 @@ const Filters: React.FC = ({filterValue, setFilterValue, columns}) => { const clearFilter = () => { setFilter(emptyFilter) - setFilterValue(emptyFilter) + onClearFilter(emptyFilter) } const applyFilter = () => { @@ -88,7 +86,7 @@ const Filters: React.FC = ({filterValue, setFilterValue, columns}) => { ({column, condition, keyword}) => column && condition && keyword, ) - setFilterValue(sanitizedFilters) + onApplyFilter(sanitizedFilters) setIsFilterOpen(false) } @@ -129,12 +127,10 @@ const Filters: React.FC = ({filterValue, setFilterValue, columns}) => { onFilterChange({columnName: "column", value, idx}) } value={item.column} - options={columns.map((col) => { - return { - value: col.mapping, - label: col.column, - } - })} + options={columns.map((col) => ({ + value: col.mapping, + label: col.column, + }))} /> {item.column && ( <> @@ -153,9 +149,10 @@ const Filters: React.FC = ({filterValue, setFilterValue, columns}) => { } popupMatchSelectWidth={250} value={item.condition} - options={conditions.map((con) => { - return {value: con, label: con} - })} + options={conditions.map((con) => ({ + value: con, + label: con, + }))} /> > - sort?: string + onSortApply: (sort: SortTypes) => void + defaultSortValue: SortTypes } -const Sort: React.FC = ({setSort, sort}) => { +const Sort: React.FC = ({onSortApply, defaultSortValue}) => { + const [sort, setSort] = useState(defaultSortValue) + return ( (!label.value ? "Column" : label.label)} - style={{width: 88}} - popupMatchSelectWidth={120} + style={{width: 100}} + popupMatchSelectWidth={150} suffixIcon={} onChange={(value) => - onFilterChange({columnName: "column", value, idx}) + onFilterChange({columnName: "key", value, idx}) + } + filterOption={(input, option) => + (option?.label ?? "") + .toLowerCase() + .includes(input.toLowerCase()) } - value={item.column} - options={columns.map((col) => ({ - value: col.mapping, - label: col.column, + value={item.key} + options={filteredOptions.map((col) => ({ + value: col.value, + label: col.label, }))} /> - {item.column && ( + {item.key && ( <> onFilterChange({ - columnName: "keyword", + columnName: "value", value: e.target.value, idx, }) @@ -191,7 +218,7 @@ const Filters: React.FC = ({columns, onApplyFilter, onClearFilter}) => {
-
@@ -203,7 +230,7 @@ const Filters: React.FC = ({columns, onApplyFilter, onClearFilter}) => { onClick={() => setIsFilterOpen(true)} className="flex items-center gap-2" > - Filters {filter[0]?.keyword && } + Filters {filter[0]?.value && } ) diff --git a/agenta-web/src/components/Filters/Sort.tsx b/agenta-web/src/components/Filters/Sort.tsx index 8b0c8a0faf..6e80c6abab 100644 --- a/agenta-web/src/components/Filters/Sort.tsx +++ b/agenta-web/src/components/Filters/Sort.tsx @@ -1,50 +1,233 @@ import React, {useState} from "react" -import {Hourglass} from "@phosphor-icons/react" -import {Select} from "antd" -import {SortTypes} from "@/lib/Types" +import {CaretRight, Clock, Hourglass} from "@phosphor-icons/react" +import {DatePicker, Button, Typography, Divider, Popover} from "antd" +import {JSSTheme, SortTypes} from "@/lib/Types" +import {Dayjs} from "dayjs" +import type {SelectProps} from "antd" +import {createUseStyles} from "react-jss" + +const useStyles = createUseStyles((theme: JSSTheme) => ({ + title: { + fontSize: theme.fontSizeLG, + fontWeight: theme.fontWeightMedium, + padding: theme.paddingXS, + }, + customDateContainer: { + flex: 1, + padding: theme.paddingXS, + gap: 16, + display: "flex", + flexDirection: "column", + }, + popover: { + "& .ant-popover-inner": { + transition: "width 0.3s ease", + padding: 4, + }, + }, + popupItems: { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + padding: `5px ${theme.paddingContentHorizontal}px`, + gap: theme.marginXS, + borderRadius: theme.borderRadiusSM, + cursor: "pointer", + "&:hover": { + backgroundColor: theme.controlItemBgActive, + }, + }, + popupSelectedItem: { + backgroundColor: theme.controlItemBgActive, + }, +})) type Props = { - onSortApply: (sort: SortTypes) => void + onSortApply: ({ + sortData, + customSortData, + }: { + sortData: SortTypes + customSortData?: CustomTimeRange + }) => void defaultSortValue: SortTypes } +export type CustomTimeRange = { + startTime: Dayjs | null + endTime: Dayjs | null +} const Sort: React.FC = ({onSortApply, defaultSortValue}) => { + const classes = useStyles() + const [sort, setSort] = useState(defaultSortValue) + const [customTime, setCustomTime] = useState({startTime: null, endTime: null}) + const [dropdownVisible, setDropdownVisible] = useState(false) + const [customOptionSelected, setCustomOptionSelected] = useState( + customTime.startTime == null ? false : true, + ) + + const handleApplyCustomRange = () => { + if (customTime.startTime && customTime.endTime) { + onSortApply({sortData: sort, customSortData: customTime}) + setDropdownVisible(false) + } + } + + const options: SelectProps["options"] = [ + {value: "30 minutes", label: "30 mins"}, + {value: "1 hour", label: "1 hour"}, + {value: "6 hours", label: "6 hours"}, + {value: "24 hours", label: "24 hours"}, + {value: "3 days", label: "3 days"}, + {value: "7 days", label: "7 days"}, + {value: "14 days", label: "14 days"}, + {value: "1 month", label: "1 month"}, + {value: "3 months", label: "3 months"}, + {value: "all time", label: "All time"}, + ] return ( -