From 7c49bd1519da79e60cd9640bbeeac2fb37212516 Mon Sep 17 00:00:00 2001 From: ogzhanolguncu Date: Fri, 10 Jan 2025 18:04:20 +0300 Subject: [PATCH 1/6] feat: new filter logic --- .../components/control-cloud/index.tsx | 104 ++++++ .../components/filters-popover.tsx | 18 +- .../components/methods-filter.tsx | 90 +++-- .../logs-filters/components/paths-filter.tsx | 40 ++- .../logs-filters/components/status-filter.tsx | 79 +++-- .../components/logs-filters/index.tsx | 10 +- .../(app)/logs-v2/components/logs-client.tsx | 2 + apps/dashboard/app/(app)/logs-v2/page.tsx | 2 +- .../app/(app)/logs-v2/query-state.ts | 332 ++++++++++++++++-- 9 files changed, 577 insertions(+), 100 deletions(-) create mode 100644 apps/dashboard/app/(app)/logs-v2/components/control-cloud/index.tsx diff --git a/apps/dashboard/app/(app)/logs-v2/components/control-cloud/index.tsx b/apps/dashboard/app/(app)/logs-v2/components/control-cloud/index.tsx new file mode 100644 index 0000000000..3a38c414d8 --- /dev/null +++ b/apps/dashboard/app/(app)/logs-v2/components/control-cloud/index.tsx @@ -0,0 +1,104 @@ +import { KeyboardButton } from "@/components/keyboard-button"; +import { cn } from "@/lib/utils"; +import { XMark } from "@unkey/icons"; +import { Button } from "@unkey/ui"; +import { useCallback } from "react"; +import { useKeyboardShortcut } from "../../hooks/use-keyboard-shortcut"; +import { type FilterValue, useFilters } from "../../query-state"; + +const formatFieldName = (field: string): string => { + switch (field) { + case "status": + return "Status"; + case "paths": + return "Path"; + case "methods": + return "Method"; + case "requestId": + return "Request ID"; + default: + // Capitalize first letter + return field.charAt(0).toUpperCase() + field.slice(1); + } +}; + +const formatValue = (value: string | number): string => { + if (typeof value === "string" && /^\d+$/.test(value)) { + const statusFamily = Math.floor(Number.parseInt(value) / 100); + switch (statusFamily) { + case 5: + return "5XX (Error)"; + case 4: + return "4XX (Warning)"; + case 2: + return "2XX (Success)"; + default: + return `${statusFamily}xx`; + } + } + return String(value); +}; + +type ControlPillProps = { + filter: FilterValue; + onRemove: (id: string) => void; +}; + +const ControlPill = ({ filter, onRemove }: ControlPillProps) => { + const { field, operator, value, metadata } = filter; + + return ( +
+
+ {formatFieldName(field)} +
+
+ {operator} +
+
+ {metadata?.colorClass && ( +
+ )} + {metadata?.icon} + {formatValue(value)} +
+ +
+ ); +}; + +export const ControlCloud = () => { + const { filters, removeFilter, updateFilters } = useFilters(); + + useKeyboardShortcut({ key: "d", meta: true }, () => { + updateFilters([]); + }); + + const handleRemoveFilter = useCallback( + (id: string) => { + removeFilter(id); + }, + [removeFilter], + ); + + if (filters.length === 0) { + return null; + } + + return ( +
+ {filters.map((filter) => ( + + ))} +
+ Clear filters + +
+
+ ); +}; diff --git a/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/filters-popover.tsx b/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/filters-popover.tsx index d649590cbf..fad443b711 100644 --- a/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/filters-popover.tsx +++ b/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/filters-popover.tsx @@ -1,4 +1,5 @@ import { useKeyboardShortcut } from "@/app/(app)/logs-v2/hooks/use-keyboard-shortcut"; +import { useFilters } from "@/app/(app)/logs-v2/query-state"; import { KeyboardButton } from "@/components/keyboard-button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { CaretRight } from "@unkey/icons"; @@ -17,19 +18,19 @@ type FilterItemConfig = { const FILTER_ITEMS: FilterItemConfig[] = [ { - id: "status", + id: "responseStatus", label: "Status", shortcut: "s", component: , }, { - id: "method", + id: "methods", label: "Method", shortcut: "m", component: , }, { - id: "path", + id: "paths", label: "Path", shortcut: "p", component: , @@ -70,7 +71,8 @@ const PopoverHeader = () => { ); }; -export const FilterItem = ({ label, shortcut, component }: FilterItemConfig) => { +export const FilterItem = ({ label, shortcut, id, component }: FilterItemConfig) => { + const { filters } = useFilters(); const [open, setOpen] = useState(false); // Add keyboard shortcut for each filter item when main filter is open @@ -85,7 +87,7 @@ export const FilterItem = ({ label, shortcut, component }: FilterItemConfig) => return ( -
+
{shortcut && ( {label}
+ {filters.filter((filter) => filter.field === id).length > 0 && ( +
+ {filters.filter((filter) => filter.field === id).length} +
+ )} + diff --git a/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/methods-filter.tsx b/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/methods-filter.tsx index 2faf897976..a082642193 100644 --- a/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/methods-filter.tsx +++ b/apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-filters/components/methods-filter.tsx @@ -1,44 +1,40 @@ +import { type FilterValue, type HttpMethod, useFilters } from "@/app/(app)/logs-v2/query-state"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@unkey/ui"; -import { useState } from "react"; +import { useCallback, useEffect, useState } from "react"; interface CheckboxOption { id: number; - method: string; + method: HttpMethod; checked: boolean; } const options: CheckboxOption[] = [ - { - id: 1, - method: "GET", - checked: false, - }, - { - id: 2, - method: "POST", - checked: false, - }, - { - id: 3, - method: "PUT", - checked: false, - }, - { - id: 4, - method: "DELETE", - checked: false, - }, - { - id: 5, - method: "PATCH", - checked: false, - }, + { id: 1, method: "GET", checked: false }, + { id: 2, method: "POST", checked: false }, + { id: 3, method: "PUT", checked: false }, + { id: 4, method: "DELETE", checked: false }, + { id: 5, method: "PATCH", checked: false }, ] as const; export const MethodsFilter = () => { + const { filters, updateFilters } = useFilters(); const [checkboxes, setCheckboxes] = useState(options); + // Sync checkboxes with filters on mount and when filters change + useEffect(() => { + const methodFilters = filters + .filter((f) => f.field === "methods") + .map((f) => f.value as HttpMethod); + + setCheckboxes((prev) => + prev.map((checkbox) => ({ + ...checkbox, + checked: methodFilters.includes(checkbox.method), + })), + ); + }, [filters]); + const handleCheckboxChange = (index: number): void => { setCheckboxes((prevCheckboxes) => { const newCheckboxes = [...prevCheckboxes]; @@ -60,17 +56,36 @@ export const MethodsFilter = () => { }); }; + const handleApplyFilter = useCallback(() => { + const selectedMethods = checkboxes.filter((c) => c.checked).map((c) => c.method); + + // Keep all non-method filters and add new method filters + const otherFilters = filters.filter((f) => f.field !== "methods"); + const methodFilters: FilterValue[] = selectedMethods.map((method) => ({ + id: crypto.randomUUID(), + field: "methods", + operator: "is", + value: method, + })); + + updateFilters([...otherFilters, ...methodFilters]); + }, [checkboxes, filters, updateFilters]); + return (
- +
+ +
{checkboxes.map((checkbox, index) => (