From e562ec980c45ecc9a22f445349f39f526162e78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Mon, 9 Mar 2026 14:22:26 +0100 Subject: [PATCH 1/9] fix: formatting studio pt3 --- studio/src/components/analytics/barlist.tsx | 47 +- studio/src/components/analytics/charts.tsx | 83 +--- .../analytics/data-table-faceted-filter.tsx | 192 +++------ .../analytics/data-table-group-menu.tsx | 14 +- .../analytics/data-table-pagination.tsx | 26 +- .../data-table-primary-filter-menu.tsx | 41 +- .../src/components/analytics/data-table.tsx | 261 ++++-------- .../src/components/analytics/delta-badge.tsx | 48 +-- .../src/components/analytics/field-usage.tsx | 195 +++------ studio/src/components/analytics/filters.tsx | 38 +- .../components/analytics/getColumnData.tsx | 136 ++---- studio/src/components/analytics/metrics.tsx | 361 +++++----------- .../components/analytics/refresh-interval.tsx | 21 +- studio/src/components/analytics/toolbar.tsx | 53 +-- studio/src/components/analytics/trace.tsx | 201 +++------ .../components/analytics/use-apply-params.tsx | 12 +- studio/src/components/analytics/use-range.tsx | 6 +- studio/src/components/app-provider.tsx | 100 ++--- studio/src/components/audit-log-table.tsx | 142 ++----- .../src/components/auth/auth-components.tsx | 122 +++--- .../components/cache/cache-details-sheet.tsx | 119 ++---- .../components/cache/cache-warmer-config.tsx | 68 ++- .../src/components/cache/operations-table.tsx | 191 ++++----- studio/src/components/changelog/changelog.tsx | 102 ++--- studio/src/components/changelog/changes.tsx | 95 ++--- studio/src/components/check-badge-icon.tsx | 13 +- .../check-extensions-config.tsx | 218 +++++----- .../src/components/checks/changes-table.tsx | 218 ++++------ .../src/components/checks/checks-config.tsx | 82 ++-- .../components/checks/checks-filter-menu.tsx | 28 +- .../checks/composed-schema-changes-table.tsx | 57 +-- .../checks/graph-pruning-issues-table.tsx | 92 ++-- .../components/checks/lint-issues-table.tsx | 91 ++-- .../components/checks/operation-content.tsx | 47 +- studio/src/components/checks/operations.tsx | 402 +++++++----------- studio/src/components/checks/override.tsx | 279 +++++------- .../checks/proposal-matches-table.tsx | 30 +- .../checks/selected-checks-filters.tsx | 38 +- .../checks/subgraph-check-extension.tsx | 42 +- .../delete-persisted-operation-dialog.tsx | 56 +-- studio/src/components/code-viewer.tsx | 101 ++--- studio/src/components/compose-status-bulb.tsx | 18 +- studio/src/components/compose-status.tsx | 22 +- .../components/composition-errors-banner.tsx | 22 +- .../components/composition-errors-dialog.tsx | 19 +- studio/src/components/create-graph.tsx | 173 +++----- .../components/dashboard/NewFeaturesPopup.tsx | 28 +- .../dashboard/graph-command-group.tsx | 118 +++-- .../components/dashboard/graph-selector.tsx | 24 +- .../dashboard/namespace-selector.tsx | 78 ++-- .../dashboard/workspace-command-wrapper.tsx | 50 +-- .../dashboard/workspace-provider.tsx | 71 ++-- .../dashboard/workspace-selector.tsx | 60 +-- .../src/components/date-picker-with-range.tsx | 136 +++--- studio/src/components/date-range-picker.tsx | 42 +- studio/src/components/empty-state.tsx | 17 +- studio/src/components/error-fallback.tsx | 19 +- .../src/components/feature-flag-details.tsx | 142 ++----- studio/src/components/feature-flags-table.tsx | 203 ++++----- .../src/components/federatedgraphs-cards.tsx | 285 +++++-------- studio/src/components/graph-visualization.tsx | 126 ++---- studio/src/components/group-select.tsx | 40 +- studio/src/components/info-tooltip.tsx | 18 +- .../analytics/active-campaign-script.tsx | 4 +- .../layout/analytics/gtm-script.tsx | 20 +- .../components/layout/dashboard-layout.tsx | 164 ++++--- studio/src/components/layout/footer.tsx | 28 +- studio/src/components/layout/graph-layout.tsx | 163 +++---- studio/src/components/layout/head.tsx | 10 +- studio/src/components/layout/layout.tsx | 2 +- .../src/components/layout/settings-layout.tsx | 37 +- studio/src/components/layout/sidenav.tsx | 124 ++---- .../src/components/layout/subgraph-layout.tsx | 102 ++--- studio/src/components/layout/title-layout.tsx | 36 +- 74 files changed, 2537 insertions(+), 4332 deletions(-) diff --git a/studio/src/components/analytics/barlist.tsx b/studio/src/components/analytics/barlist.tsx index ffcfb859dd..70f509b034 100644 --- a/studio/src/components/analytics/barlist.tsx +++ b/studio/src/components/analytics/barlist.tsx @@ -1,7 +1,7 @@ -import { cn } from "@/lib/utils"; -import React from "react"; -import Link from "next/link"; -import { Url } from "next/dist/shared/lib/router/router"; +import { cn } from '@/lib/utils'; +import React from 'react'; +import Link from 'next/link'; +import { Url } from 'next/dist/shared/lib/router/router'; type Bar = { key: string; @@ -65,12 +65,8 @@ const BarList = React.forwardRef((props, ref) => { } return ( -
-
+
+
{data.map((item, idx) => { const Icon = item.icon; @@ -78,25 +74,20 @@ const BarList = React.forwardRef((props, ref) => {
-
- {Icon ? : null} +
+ {Icon ? : null} {item.href ? ( - + {item.name} ) : ( @@ -107,19 +98,13 @@ const BarList = React.forwardRef((props, ref) => { ); })}
-
+
{data.map((item, idx) => (
-

- {valueFormatter(item.value)} -

+

{valueFormatter(item.value)}

))}
@@ -127,6 +112,6 @@ const BarList = React.forwardRef((props, ref) => { ); }); -BarList.displayName = "BarList"; +BarList.displayName = 'BarList'; export default BarList; diff --git a/studio/src/components/analytics/charts.tsx b/studio/src/components/analytics/charts.tsx index 5ba2624a22..adefba368d 100644 --- a/studio/src/components/analytics/charts.tsx +++ b/studio/src/components/analytics/charts.tsx @@ -9,40 +9,33 @@ import { TooltipProps, XAxis, YAxis, -} from "recharts"; -import React from "react"; -import useWindowSize from "@/hooks/use-window-size"; -import { formatDateTime } from "@/lib/format-date"; +} from 'recharts'; +import React from 'react'; +import useWindowSize from '@/hooks/use-window-size'; +import { formatDateTime } from '@/lib/format-date'; const labelFormatter = (label: number, utc?: boolean) => { - return utc - ? new Date(label).toUTCString() - : label - ? formatDateTime(label) - : label; + return utc ? new Date(label).toUTCString() : label ? formatDateTime(label) : label; }; -export const valueFormatter = (tick: number) => - tick === 0 || tick % 1 != 0 ? "" : `${tick}`; +export const valueFormatter = (tick: number) => (tick === 0 || tick % 1 != 0 ? '' : `${tick}`); -type TimeSetting = "relative" | "local" | "utc"; +type TimeSetting = 'relative' | 'local' | 'utc'; export const nanoTimestampToTime = (nano: number) => { let ms = (nano / 1000000).toFixed(1); if (parseFloat(ms) > 1000) { let seconds = (nano / 1000000000).toFixed(1); // Converting nano to seconds - return seconds + " s"; + return seconds + ' s'; } - return ms + " ms"; + return ms + ' ms'; }; export const tooltipWrapperClassName = - "rounded-md border !border-popover !bg-popover/60 p-2 text-sm shadow-md outline-0 backdrop-blur-lg"; + 'rounded-md border !border-popover !bg-popover/60 p-2 text-sm shadow-md outline-0 backdrop-blur-lg'; -export const ChartTooltip = ( - props: TooltipProps & { utc?: boolean }, -) => { +export const ChartTooltip = (props: TooltipProps & { utc?: boolean }) => { const { utc, ...rest } = props; return ( { +export const CustomTooltip = ({ active, payload, label, p95, utc, valueLabel = 'Value' }: any) => { if (active && payload && payload.length) { return (

{labelFormatter(label, utc)}

- {valueLabel}:{" "} - {p95 ? nanoTimestampToTime(payload[0].value) : payload[0].value} + {valueLabel}: {p95 ? nanoTimestampToTime(payload[0].value) : payload[0].value}

); @@ -101,12 +86,7 @@ export const BarChartComponent = ({ return ( - + - - + + ); @@ -165,19 +139,10 @@ export const LineChartComponent = ({ className?: string; }) => { return ( - + {cartesianGrid && ( - + )} - + - + ); diff --git a/studio/src/components/analytics/data-table-faceted-filter.tsx b/studio/src/components/analytics/data-table-faceted-filter.tsx index 0672a20133..1ab896729a 100644 --- a/studio/src/components/analytics/data-table-faceted-filter.tsx +++ b/studio/src/components/analytics/data-table-faceted-filter.tsx @@ -1,31 +1,20 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Separator } from "@/components/ui/separator"; -import { cn } from "@/lib/utils"; -import { PlusCircleIcon, XCircleIcon } from "@heroicons/react/24/outline"; -import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons"; -import { Column } from "@tanstack/react-table"; -import { CustomOptions } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { ComponentType, useEffect, useMemo, useState } from "react"; -import { MdTextRotationNone } from "react-icons/md"; -import { Input } from "../ui/input"; -import { Slider } from "../ui/slider"; -import { Toggle } from "../ui/toggle"; -import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; -import { AnalyticsFilter } from "./filters"; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Separator } from '@/components/ui/separator'; +import { cn } from '@/lib/utils'; +import { PlusCircleIcon, XCircleIcon } from '@heroicons/react/24/outline'; +import { CheckIcon, PlusCircledIcon } from '@radix-ui/react-icons'; +import { Column } from '@tanstack/react-table'; +import { CustomOptions } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { ComponentType, useEffect, useMemo, useState } from 'react'; +import { MdTextRotationNone } from 'react-icons/md'; +import { Input } from '../ui/input'; +import { Slider } from '../ui/slider'; +import { Toggle } from '../ui/toggle'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; +import { AnalyticsFilter } from './filters'; interface Option { label: string; @@ -50,16 +39,10 @@ const SliderWithOptions = ({ onValueChange, }: { defaultRange: { start: number; end: number }; - onValueChange: ({ - rangeValue, - }: { - rangeValue: { start: number; end: number }; - }) => void; + onValueChange: ({ rangeValue }: { rangeValue: { start: number; end: number } }) => void; unit: string; }) => { - const [range, setRange] = useState<{ start: number; end: number }>( - defaultRange, - ); + const [range, setRange] = useState<{ start: number; end: number }>(defaultRange); return (
@@ -91,35 +74,29 @@ const SliderWithOptions = ({ if (Number(e.target.value) > range.end) { setRange({ start: range.end, - end: - Number(e.target.value) > 60 ? 60 : Number(e.target.value), + end: Number(e.target.value) > 60 ? 60 : Number(e.target.value), }); onValueChange({ rangeValue: { start: range.end, - end: - Number(e.target.value) > 60 ? 60 : Number(e.target.value), + end: Number(e.target.value) > 60 ? 60 : Number(e.target.value), }, }); } else { setRange({ ...range, - start: - Number(e.target.value) > 0 ? Number(e.target.value) : 0, + start: Number(e.target.value) > 0 ? Number(e.target.value) : 0, }); onValueChange({ rangeValue: { ...range, - start: - Number(e.target.value) > 0 ? Number(e.target.value) : 0, + start: Number(e.target.value) > 0 ? Number(e.target.value) : 0, }, }); } }} /> - - {unit} - + {unit}
range.start) { setRange({ ...range, - end: - Number(e.target.value) > 60 ? 60 : Number(e.target.value), + end: Number(e.target.value) > 60 ? 60 : Number(e.target.value), }); onValueChange({ rangeValue: { ...range, - end: - Number(e.target.value) > 60 ? 60 : Number(e.target.value), + end: Number(e.target.value) > 60 ? 60 : Number(e.target.value), }, }); } else { setRange({ - start: - Number(e.target.value) > 0 ? Number(e.target.value) : 0, + start: Number(e.target.value) > 0 ? Number(e.target.value) : 0, end: range.start, }); onValueChange({ rangeValue: { - start: - Number(e.target.value) > 0 ? Number(e.target.value) : 0, + start: Number(e.target.value) > 0 ? Number(e.target.value) : 0, end: range.start, }, }); } }} /> - - {unit} - + {unit}
@@ -189,11 +160,8 @@ export function DataTableFilterCommands({ // Memoized Set for efficient operations and automatic deduplication // - Use selectedValues (Set) for: display checks (size, has), iteration (Array.from) // - Use selectedOptions (Array) for: building new filter arrays to pass to onSelect - const selectedValues = useMemo( - () => new Set(selectedOptions ?? []), - [selectedOptions], - ); - const [input, setInput] = useState(""); + const selectedValues = useMemo(() => new Set(selectedOptions ?? []), [selectedOptions]); + const [input, setInput] = useState(''); const [range, setRange] = useState<{ start: number; end: number }>({ start: 0, end: 10, @@ -204,12 +172,7 @@ export function DataTableFilterCommands({ const [searchValue, setSearchValue] = useState(undefined); useEffect(() => { - if ( - !selectedOptions || - selectedOptions.length === 0 || - customOptions !== CustomOptions.Range - ) - return; + if (!selectedOptions || selectedOptions.length === 0 || customOptions !== CustomOptions.Range) return; const option1 = JSON.parse(selectedOptions[0]).value; const option2 = JSON.parse(selectedOptions[1]).value; @@ -222,11 +185,7 @@ export function DataTableFilterCommands({ } }, [customOptions, selectedOptions]); - const updateRangeFilters = ({ - rangeValue, - }: { - rangeValue: { start: number; end: number }; - }) => { + const updateRangeFilters = ({ rangeValue }: { rangeValue: { start: number; end: number } }) => { setRange({ start: rangeValue.start, end: rangeValue.end, @@ -273,7 +232,7 @@ export function DataTableFilterCommands({ // Check if already exists using Set for O(1) lookup if (selectedValues.has(newValue)) { - setInput(""); // Clear input for duplicate + setInput(''); // Clear input for duplicate return; // Already exists, don't add duplicate } @@ -288,7 +247,7 @@ export function DataTableFilterCommands({ } onSelect?.(filterValues); - setInput(""); + setInput(''); }} > @@ -299,15 +258,10 @@ export function DataTableFilterCommands({ <>
{Array.from(selectedValues).map((val) => { - const selected = JSON.parse( - val, - ) as AnalyticsFilter["options"][number]; + const selected = JSON.parse(val) as AnalyticsFilter['options'][number]; return ( -
+
{selected.label} @@ -41,9 +41,7 @@ export function DataTableGroupMenu({ className="flex flex-row items-center justify-between" > {item.label} - {value === item.value ? ( - - ) : null} + {value === item.value ? : null} ); })} diff --git a/studio/src/components/analytics/data-table-pagination.tsx b/studio/src/components/analytics/data-table-pagination.tsx index 9586973d2e..9e2a7d7494 100644 --- a/studio/src/components/analytics/data-table-pagination.tsx +++ b/studio/src/components/analytics/data-table-pagination.tsx @@ -1,27 +1,14 @@ -import { - ChevronLeftIcon, - ChevronRightIcon, - DoubleArrowLeftIcon, - DoubleArrowRightIcon, -} from "@radix-ui/react-icons"; -import { Table } from "@tanstack/react-table"; +import { ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, DoubleArrowRightIcon } from '@radix-ui/react-icons'; +import { Table } from '@tanstack/react-table'; -import { Button } from "@/components/ui/button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Button } from '@/components/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; interface DataTablePaginationProps { table: Table; } -export function DataTablePagination({ - table, -}: DataTablePaginationProps) { +export function DataTablePagination({ table }: DataTablePaginationProps) { const pageIndex = table.getState().pagination.pageIndex ?? 0; const total = table.getPageCount(); if (!total) { @@ -52,8 +39,7 @@ export function DataTablePagination({
- Page {Math.min(pageIndex + 1, table.getPageCount())} of{" "} - {table.getPageCount()} + Page {Math.min(pageIndex + 1, table.getPageCount())} of {table.getPageCount()}
- + {isMobile ? ( - + {filters.map((filter, index) => { return ( @@ -69,13 +57,10 @@ export function DataTablePrimaryFilterMenu({ return ( 0 - } + checked={filter.selectedOptions && filter.selectedOptions.length > 0} checkboxPosition="right" onCheckedChange={(checked) => { - filter.onSelect?.(checked ? ["true"] : undefined); + filter.onSelect?.(checked ? ['true'] : undefined); }} onSelect={(e) => e.preventDefault()} > diff --git a/studio/src/components/analytics/data-table.tsx b/studio/src/components/analytics/data-table.tsx index e8d600e3fc..98a8440efe 100644 --- a/studio/src/components/analytics/data-table.tsx +++ b/studio/src/components/analytics/data-table.tsx @@ -1,27 +1,16 @@ -import { Button } from "@/components/ui/button"; +import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, - TableWrapper, -} from "@/components/ui/table"; -import { useFeatureLimit } from "@/hooks/use-feature-limit"; -import { useSessionStorage } from "@/hooks/use-session-storage"; -import { cn } from "@/lib/utils"; -import { - ChevronDownIcon, - ExclamationTriangleIcon, -} from "@heroicons/react/24/outline"; -import { UpdateIcon } from "@radix-ui/react-icons"; +} from '@/components/ui/dropdown-menu'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableWrapper } from '@/components/ui/table'; +import { useFeatureLimit } from '@/hooks/use-feature-limit'; +import { useSessionStorage } from '@/hooks/use-session-storage'; +import { cn } from '@/lib/utils'; +import { ChevronDownIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; +import { UpdateIcon } from '@radix-ui/react-icons'; import { ColumnFiltersState, PaginationState, @@ -36,52 +25,31 @@ import { getPaginationRowModel, getSortedRowModel, useReactTable, -} from "@tanstack/react-table"; +} from '@tanstack/react-table'; import { AnalyticsViewColumn, AnalyticsViewFilterOperator, AnalyticsViewGroupName, AnalyticsViewResultFilter, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { formatISO, subHours } from "date-fns"; -import { useRouter } from "next/router"; -import { - ReactNode, - useEffect, - useImperativeHandle, - useMemo, - useState, - useCallback, -} from "react"; -import useDeepCompareEffect from "use-deep-compare-effect"; -import { - DatePickerWithRange, - DateRange, - DateRangePickerChangeHandler, - Range, -} from "../date-picker-with-range"; -import { Loader } from "../ui/loader"; -import { DataTableGroupMenu } from "./data-table-group-menu"; -import { DataTablePagination } from "./data-table-pagination"; -import { AnalyticsFilters, AnalyticsSelectedFilters } from "./filters"; -import { getColumnData } from "./getColumnData"; -import { getDataTableFilters } from "./getDataTableFilters"; -import { RefreshInterval, refreshIntervals } from "./refresh-interval"; -import { useApplyParams } from "./use-apply-params"; -import { getDefaultSort, useSyncTableWithQuery } from "./useSyncTableWithQuery"; -import { HiOutlineCheck } from "react-icons/hi2"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { useToast } from "@/components/ui/use-toast"; -import { - calculateUrlLength, - checkFilterLimits, - MAX_URL_LENGTH, -} from "./metrics"; +} from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { formatISO, subHours } from 'date-fns'; +import { useRouter } from 'next/router'; +import { ReactNode, useEffect, useImperativeHandle, useMemo, useState, useCallback } from 'react'; +import useDeepCompareEffect from 'use-deep-compare-effect'; +import { DatePickerWithRange, DateRange, DateRangePickerChangeHandler, Range } from '../date-picker-with-range'; +import { Loader } from '../ui/loader'; +import { DataTableGroupMenu } from './data-table-group-menu'; +import { DataTablePagination } from './data-table-pagination'; +import { AnalyticsFilters, AnalyticsSelectedFilters } from './filters'; +import { getColumnData } from './getColumnData'; +import { getDataTableFilters } from './getDataTableFilters'; +import { RefreshInterval, refreshIntervals } from './refresh-interval'; +import { useApplyParams } from './use-apply-params'; +import { getDefaultSort, useSyncTableWithQuery } from './useSyncTableWithQuery'; +import { HiOutlineCheck } from 'react-icons/hi2'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { useToast } from '@/components/ui/use-toast'; +import { calculateUrlLength, checkFilterLimits, MAX_URL_LENGTH } from './metrics'; export function AnalyticsDataTable({ tableRef, @@ -104,20 +72,14 @@ export function AnalyticsDataTable({ }) { const router = useRouter(); - const [, setRouteCache] = useSessionStorage("analytics.route", router.query); + const [, setRouteCache] = useSessionStorage('analytics.route', router.query); - const [refreshInterval, setRefreshInterval] = useState( - refreshIntervals[0].value, - ); + const [refreshInterval, setRefreshInterval] = useState(refreshIntervals[0].value); - const [sorting, setSorting] = useState( - getDefaultSort(router.query.group?.toString()), - ); + const [sorting, setSorting] = useState(getDefaultSort(router.query.group?.toString())); const [columnFilters, setColumnFilters] = useState([]); - const [selectedGroup, setSelectedGroup] = useState( - AnalyticsViewGroupName.None, - ); + const [selectedGroup, setSelectedGroup] = useState(AnalyticsViewGroupName.None); const [selectedRange, setRange] = useState(); const [selectedDateRange, setDateRange] = useState({ @@ -135,8 +97,7 @@ export function AnalyticsDataTable({ return acc; }, {}); - const [columnVisibility, setColumnVisibility] = - useState(defaultHiddenColumns); + const [columnVisibility, setColumnVisibility] = useState(defaultHiddenColumns); useDeepCompareEffect(() => { setColumnVisibility(defaultHiddenColumns); @@ -174,14 +135,11 @@ export function AnalyticsDataTable({ useEffect(() => { if (!router.isReady || !router.query.filterState) return; - const { exceeded, reason } = checkFilterLimits( - router, - router.query.filterState as string, - ); + const { exceeded, reason } = checkFilterLimits(router, router.query.filterState as string); if (exceeded) { toast({ - title: "Filter limit reached", + title: 'Filter limit reached', description: `${reason}. Filters have been reset.`, }); @@ -213,7 +171,7 @@ export function AnalyticsDataTable({ onColumnVisibilityChange: setColumnVisibility, onRowSelectionChange: setRowSelection, onPaginationChange: (t) => { - if (typeof t === "function") { + if (typeof t === 'function') { const newVal = functionalUpdate(t, state.pagination); applyNewParams({ page: newVal.pageIndex.toString(), @@ -222,7 +180,7 @@ export function AnalyticsDataTable({ } }, onColumnFiltersChange: (t) => { - if (typeof t === "function") { + if (typeof t === 'function') { const newVal = functionalUpdate(t, state.columnFilters); // Check if we're removing filters (allow) vs adding (check limit) @@ -235,27 +193,22 @@ export function AnalyticsDataTable({ const val = f.value as string[] | undefined; return sum + (val?.length ?? 0); }, 0); - const isRemoving = - newTotalValues < oldTotalValues || - newVal.length < state.columnFilters.length; + const isRemoving = newTotalValues < oldTotalValues || newVal.length < state.columnFilters.length; let stringifiedFilters; try { stringifiedFilters = JSON.stringify(newVal); } catch { - stringifiedFilters = "[]"; + stringifiedFilters = '[]'; } // Check URL length and filter size before applying (only if adding/modifying, not removing) if (!isRemoving) { - const { exceeded, reason } = checkFilterLimits( - router, - stringifiedFilters, - ); + const { exceeded, reason } = checkFilterLimits(router, stringifiedFilters); if (exceeded) { toast({ - title: "Filter limit reached", + title: 'Filter limit reached', description: `${reason}. Please remove some filters before adding new ones.`, }); return; // Early return prevents filter from being applied @@ -268,24 +221,24 @@ export function AnalyticsDataTable({ } }, onSortingChange: (t) => { - if (typeof t === "function") { + if (typeof t === 'function') { const newVal = functionalUpdate(t, state.sorting); const defaultSort = getDefaultSort(); if (newVal.length) { applyNewParams({ sort: newVal[0]?.id, - sortDir: newVal[0]?.desc ? "desc" : "asc", + sortDir: newVal[0]?.desc ? 'desc' : 'asc', }); } else if (defaultSort[0].id === state.sorting[0].id) { applyNewParams( { sort: defaultSort[0].id, - sortDir: defaultSort[0]?.desc ? "asc" : "desc", + sortDir: defaultSort[0]?.desc ? 'asc' : 'desc', }, - ["sort", "sortDir"], + ['sort', 'sortDir'], ); } else { - applyNewParams({}, ["sort", "sortDir"]); + applyNewParams({}, ['sort', 'sortDir']); } } }, @@ -298,14 +251,11 @@ export function AnalyticsDataTable({ { group: AnalyticsViewGroupName[val], }, - ["sort", "sortDir"], + ['sort', 'sortDir'], ); }; - const onDateRangeChange: DateRangePickerChangeHandler = ({ - range, - dateRange, - }) => { + const onDateRangeChange: DateRangePickerChangeHandler = ({ range, dateRange }) => { if (range) { applyNewParams({ dateRange: null, @@ -362,17 +312,14 @@ export function AnalyticsDataTable({ try { stringifiedFilters = JSON.stringify(newSelectedFilters); } catch { - stringifiedFilters = "[]"; + stringifiedFilters = '[]'; } - const { exceeded, reason } = checkFilterLimits( - router, - stringifiedFilters, - ); + const { exceeded, reason } = checkFilterLimits(router, stringifiedFilters); if (exceeded) { toast({ - title: "Filter limit reached", + title: 'Filter limit reached', description: `${reason}. Please remove some filters before adding new ones.`, }); return false; // Validation failed - prevents onSelect from being called @@ -408,21 +355,21 @@ export function AnalyticsDataTable({ const setFilterOn = (columnName: string) => { const col = table.getColumn(columnName); - const val = row.getValue(col?.id ?? "") as string; + const val = row.getValue(col?.id ?? '') as string; const stringifiedFilters = JSON.stringify([ { id: columnName, value: [ JSON.stringify({ - label: val || "-", + label: val || '-', operator: AnalyticsViewFilterOperator.EQUALS, - value: val || "", + value: val || '', }), ], }, ]); - newQueryParams["filterState"] = stringifiedFilters; + newQueryParams['filterState'] = stringifiedFilters; }; switch (selectedGroup) { @@ -431,30 +378,29 @@ export function AnalyticsDataTable({ setRouteCache(router.query); applyNewParams({ - traceID: row.getValue("traceId"), - spanID: row.getValue("spanId"), + traceID: row.getValue('traceId'), + spanID: row.getValue('spanId'), }); return; } case AnalyticsViewGroupName.Client: { - setFilterOn("clientName"); + setFilterOn('clientName'); break; } case AnalyticsViewGroupName.HttpStatusCode: { - setFilterOn("httpStatusCode"); + setFilterOn('httpStatusCode'); break; } default: { - setFilterOn("operationName"); + setFilterOn('operationName'); } } - newQueryParams["group"] = - AnalyticsViewGroupName[AnalyticsViewGroupName.None]; + newQueryParams['group'] = AnalyticsViewGroupName[AnalyticsViewGroupName.None]; - applyNewParams(newQueryParams, ["sort", "sortDir"]); + applyNewParams(newQueryParams, ['sort', 'sortDir']); }; - const tracingRetention = useFeatureLimit("tracing-retention", 7); + const tracingRetention = useFeatureLimit('tracing-retention', 7); return (
@@ -475,9 +421,8 @@ export function AnalyticsDataTable({
- Maximum URL length of {MAX_URL_LENGTH.toLocaleString()}{" "} - characters reached. Please remove some filters before adding new - ones. + Maximum URL length of {MAX_URL_LENGTH.toLocaleString()} characters reached. Please remove some filters + before adding new ones. )} @@ -488,19 +433,19 @@ export function AnalyticsDataTable({ onChange={onGroupChange} items={[ { - label: "None", + label: 'None', value: AnalyticsViewGroupName.None, }, { - label: "Operation Name", + label: 'Operation Name', value: AnalyticsViewGroupName.OperationName, }, { - label: "Client", + label: 'Client', value: AnalyticsViewGroupName.Client, }, { - label: "HTTP Status Code", + label: 'HTTP Status Code', value: AnalyticsViewGroupName.HttpStatusCode, }, ]} @@ -520,29 +465,18 @@ export function AnalyticsDataTable({ - column.toggleVisibility(!!value) - } + onCheckedChange={(value) => column.toggleVisibility(!!value)} > - {columnsList.find((each) => each.name === column.id) - ?.title ?? column.id} + {columnsList.find((each) => each.name === column.id)?.title ?? column.id} ); })}
- - +
@@ -560,12 +494,7 @@ export function AnalyticsDataTable({ {headerGroup.headers.map((header) => { return ( - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} ); })} @@ -578,34 +507,27 @@ export function AnalyticsDataTable({ return ( relinkTable(row)} - className={cn( - "group cursor-pointer hover:bg-secondary/30", - { - "bg-secondary/50": - row.original.traceId === router.query.traceID && - row.original.spanId === router.query.spanID, - "bg-destructive/10": - row.original.statusCode === "STATUS_CODE_ERROR", - }, - )} + className={cn('group cursor-pointer hover:bg-secondary/30', { + 'bg-secondary/50': + row.original.traceId === router.query.traceID && row.original.spanId === router.query.spanID, + 'bg-destructive/10': row.original.statusCode === 'STATUS_CODE_ERROR', + })} > {row.getVisibleCells().map((cell) => { let icon = null; - let text: ReactNode = ""; + let text: ReactNode = ''; - if (cell.column.id === "statusCode") { - if (cell.getValue() === "STATUS_CODE_ERROR") { + if (cell.column.id === 'statusCode') { + if (cell.getValue() === 'STATUS_CODE_ERROR') { icon = ( - - {row.getValue("statusMessage")} - + {row.getValue('statusMessage')} ); @@ -613,10 +535,7 @@ export function AnalyticsDataTable({ icon = ; } } else { - text = flexRender( - cell.column.columnDef.cell, - cell.getContext(), - ); + text = flexRender(cell.column.columnDef.cell, cell.getContext()); } return ( @@ -633,19 +552,13 @@ export function AnalyticsDataTable({ }) ) : isLoading ? ( - + ) : ( - + No results. diff --git a/studio/src/components/analytics/delta-badge.tsx b/studio/src/components/analytics/delta-badge.tsx index bfafa0ccd7..1a4e6f7651 100644 --- a/studio/src/components/analytics/delta-badge.tsx +++ b/studio/src/components/analytics/delta-badge.tsx @@ -1,52 +1,38 @@ -import { cva, type VariantProps } from "class-variance-authority"; +import { cva, type VariantProps } from 'class-variance-authority'; -import { Badge } from "../ui/badge"; -import { FiArrowDown, FiArrowRight, FiArrowUp } from "react-icons/fi"; -import { cn } from "@/lib/utils"; +import { Badge } from '../ui/badge'; +import { FiArrowDown, FiArrowRight, FiArrowUp } from 'react-icons/fi'; +import { cn } from '@/lib/utils'; const deltaBadgeVariants = cva( - "inline-flex items-center gap-2 rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + 'inline-flex items-center gap-2 rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', { variants: { type: { - neutral: "border-transparent bg-muted text-muted-foreground", - "increase-neutral": "border-transparent bg-muted text-muted-foreground", - "decrease-neutral": "border-transparent bg-muted text-muted-foreground", - "increase-positive": "border-transparent bg-success/10 text-success", - "increase-negative": - "border-transparent bg-destructive/10 text-destructive", - "decrease-positive": "border-transparent bg-success/10 text-success", - "decrease-negative": - "border-transparent bg-destructive/10 text-destructive", + neutral: 'border-transparent bg-muted text-muted-foreground', + 'increase-neutral': 'border-transparent bg-muted text-muted-foreground', + 'decrease-neutral': 'border-transparent bg-muted text-muted-foreground', + 'increase-positive': 'border-transparent bg-success/10 text-success', + 'increase-negative': 'border-transparent bg-destructive/10 text-destructive', + 'decrease-positive': 'border-transparent bg-success/10 text-success', + 'decrease-negative': 'border-transparent bg-destructive/10 text-destructive', }, }, defaultVariants: { - type: "neutral", + type: 'neutral', }, - } + }, ); -export interface DeltaBadgeProps - extends React.HTMLAttributes, - VariantProps { +export interface DeltaBadgeProps extends React.HTMLAttributes, VariantProps { value: string; } export const DeltaBadge: React.FC = (props) => { const { type, className, ...rest } = props; return ( - - {props.type === "neutral" ? ( - - ) : props.type?.match("increase") ? ( - - ) : ( - - )} + + {props.type === 'neutral' ? : props.type?.match('increase') ? : } {props.value} ); diff --git a/studio/src/components/analytics/field-usage.tsx b/studio/src/components/analytics/field-usage.tsx index ffcdd0dfbb..8f78a7782a 100644 --- a/studio/src/components/analytics/field-usage.tsx +++ b/studio/src/components/analytics/field-usage.tsx @@ -1,72 +1,36 @@ -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, - TableWrapper, -} from "@/components/ui/table"; -import useWindowSize from "@/hooks/use-window-size"; -import { formatMetric } from "@/lib/format-metric"; -import { - createStringifiedDateRange, - useChartData, -} from "@/lib/insights-helpers"; -import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; -import { CubeIcon } from "@radix-ui/react-icons"; -import { useQuery } from "@connectrpc/connect-query"; -import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; -import { getFieldUsage } from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; -import { GetFieldUsageResponse } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { differenceInHours, format, formatISO, fromUnixTime } from "date-fns"; -import Link from "next/link"; -import { useSearchParams } from "next/navigation"; -import { useRouter } from "next/router"; -import { useContext, useId, useMemo } from "react"; -import { - Area, - AreaChart, - CartesianGrid, - ResponsiveContainer, - XAxis, - YAxis, -} from "recharts"; -import { - DatePickerWithRange, - DateRangePickerChangeHandler, - getRange, -} from "../date-picker-with-range"; -import { EmptyState } from "../empty-state"; -import { GraphContext } from "../layout/graph-layout"; -import { Badge } from "../ui/badge"; -import { Button } from "../ui/button"; -import { Loader } from "../ui/loader"; -import { ChartTooltip } from "./charts"; -import { createFilterState } from "./constructAnalyticsTableQueryState"; -import { useApplyParams } from "./use-apply-params"; -import { useAnalyticsQueryState } from "./useAnalyticsQueryState"; -import { useFeatureLimit } from "@/hooks/use-feature-limit"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useCurrentOrganization } from "@/hooks/use-current-organization"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableWrapper } from '@/components/ui/table'; +import useWindowSize from '@/hooks/use-window-size'; +import { formatMetric } from '@/lib/format-metric'; +import { createStringifiedDateRange, useChartData } from '@/lib/insights-helpers'; +import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'; +import { CubeIcon } from '@radix-ui/react-icons'; +import { useQuery } from '@connectrpc/connect-query'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; +import { getFieldUsage } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; +import { GetFieldUsageResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { differenceInHours, format, formatISO, fromUnixTime } from 'date-fns'; +import Link from 'next/link'; +import { useSearchParams } from 'next/navigation'; +import { useRouter } from 'next/router'; +import { useContext, useId, useMemo } from 'react'; +import { Area, AreaChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis } from 'recharts'; +import { DatePickerWithRange, DateRangePickerChangeHandler, getRange } from '../date-picker-with-range'; +import { EmptyState } from '../empty-state'; +import { GraphContext } from '../layout/graph-layout'; +import { Badge } from '../ui/badge'; +import { Button } from '../ui/button'; +import { Loader } from '../ui/loader'; +import { ChartTooltip } from './charts'; +import { createFilterState } from './constructAnalyticsTableQueryState'; +import { useApplyParams } from './use-apply-params'; +import { useAnalyticsQueryState } from './useAnalyticsQueryState'; +import { useFeatureLimit } from '@/hooks/use-feature-limit'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useCurrentOrganization } from '@/hooks/use-current-organization'; -export const FieldUsage = ({ - usageData, -}: { - usageData: GetFieldUsageResponse; -}) => { +export const FieldUsage = ({ usageData }: { usageData: GetFieldUsageResponse }) => { const router = useRouter(); const { namespace: { name: namespace }, @@ -90,12 +54,9 @@ export const FieldUsage = ({ const applyParams = useApplyParams(); - const analyticsRetention = useFeatureLimit("analytics-retention", 7); + const analyticsRetention = useFeatureLimit('analytics-retention', 7); - const onDateRangeChange: DateRangePickerChangeHandler = ({ - range, - dateRange, - }) => { + const onDateRangeChange: DateRangePickerChangeHandler = ({ range, dateRange }) => { if (range) { applyParams({ range: range.toString(), @@ -137,10 +98,7 @@ export const FieldUsage = ({
- + @@ -198,31 +156,24 @@ export const FieldUsage = ({

Clients and Operations

Used by {usageData.clients.length} client - {usageData.clients.length === 1 ? "" : "s"} and {totalOpsCount}{" "} - operation - {totalOpsCount === 1 ? "" : "s"} + {usageData.clients.length === 1 ? '' : 's'} and {totalOpsCount} operation + {totalOpsCount === 1 ? '' : 's'}

{usageData.clients.map((client) => { - const clientName = client.name || "unknown"; - const clientVersion = client.version || "n/a"; + const clientName = client.name || 'unknown'; + const clientVersion = client.version || 'n/a'; const totalRequests = client.operations.reduce((acc, op) => { acc += op.count; return acc; }, 0); return ( - +
- {clientName}{" "} - - (version: {clientVersion}) - + {clientName} (version: {clientVersion}) {totalRequests} Requests @@ -237,9 +188,7 @@ export const FieldUsage = ({ Hash Operation Name - - Requests - + Requests @@ -260,9 +209,7 @@ export const FieldUsage = ({ operationHash: op.hash, }), dateRange: range - ? createStringifiedDateRange( - getRange(range), - ) + ? createStringifiedDateRange(getRange(range)) : JSON.stringify({ start: formatISO(dateRange.start), end: formatISO(dateRange.end), @@ -271,12 +218,10 @@ export const FieldUsage = ({ }} className="text-primary" > - {op.name || "-"} + {op.name || '-'} - - {op.count} - + {op.count} ); })} @@ -316,26 +261,18 @@ export const FieldUsage = ({
)} - {usageData.meta.firstSeenTimestamp !== "0" && - usageData.meta.latestSeenTimestamp !== "0" && ( -
-

Timestamps

-

- First used:{" "} - {format( - fromUnixTime(Number(usageData.meta.firstSeenTimestamp)), - "MMM dd yyyy HH:mm:ss", - )}{" "} -

-

- Latest used:{" "} - {format( - fromUnixTime(Number(usageData.meta.latestSeenTimestamp)), - "MMM dd yyyy HH:mm:ss", - )} -

-
- )} + {usageData.meta.firstSeenTimestamp !== '0' && usageData.meta.latestSeenTimestamp !== '0' && ( +
+

Timestamps

+

+ First used:{' '} + {format(fromUnixTime(Number(usageData.meta.firstSeenTimestamp)), 'MMM dd yyyy HH:mm:ss')}{' '} +

+

+ Latest used: {format(fromUnixTime(Number(usageData.meta.latestSeenTimestamp)), 'MMM dd yyyy HH:mm:ss')} +

+
+ )}
)}
@@ -348,16 +285,16 @@ export const FieldUsageSheet = () => { const searchParams = useSearchParams(); const { range, dateRange } = useAnalyticsQueryState(); - const isNamedType = searchParams.get("isNamedType") === "true"; - const showUsage = searchParams.get("showUsage"); + const isNamedType = searchParams.get('isNamedType') === 'true'; + const showUsage = searchParams.get('showUsage'); - const [type, field] = showUsage?.split(".") ?? []; + const [type, field] = showUsage?.split('.') ?? []; const graph = useContext(GraphContext); const featureFlagName = router.query.featureFlag as string; const category = router.query.category as string; - const isInput = category === "inputs"; + const isInput = category === 'inputs'; const { data, error, isLoading, refetch } = useQuery( getFieldUsage, @@ -391,9 +328,7 @@ export const FieldUsageSheet = () => { } title="Could not retrieve your usage data" - description={ - data?.response?.details || error?.message || "Please try again" - } + description={data?.response?.details || error?.message || 'Please try again'} actions={} />
@@ -409,8 +344,8 @@ export const FieldUsageSheet = () => { onOpenChange={(isOpen) => { if (!isOpen) { const newQuery = { ...router.query }; - delete newQuery["showUsage"]; - delete newQuery["isNamedType"]; + delete newQuery['showUsage']; + delete newQuery['isNamedType']; router.replace({ query: newQuery, }); @@ -420,7 +355,7 @@ export const FieldUsageSheet = () => { - Field Usage for{" "} + Field Usage for{' '} {type} {field && `.${field}`} diff --git a/studio/src/components/analytics/filters.tsx b/studio/src/components/analytics/filters.tsx index eae4a47470..c0ebae4d30 100644 --- a/studio/src/components/analytics/filters.tsx +++ b/studio/src/components/analytics/filters.tsx @@ -1,10 +1,10 @@ -import { DataTableFacetedFilter } from "./data-table-faceted-filter"; -import { DataTablePrimaryFilterMenu } from "./data-table-primary-filter-menu"; -import { ColumnFiltersState } from "@tanstack/react-table"; -import { Button } from "../ui/button"; -import { Cross2Icon } from "@radix-ui/react-icons"; -import React from "react"; -import { CustomOptions } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; +import { DataTableFacetedFilter } from './data-table-faceted-filter'; +import { DataTablePrimaryFilterMenu } from './data-table-primary-filter-menu'; +import { ColumnFiltersState } from '@tanstack/react-table'; +import { Button } from '../ui/button'; +import { Cross2Icon } from '@radix-ui/react-icons'; +import React from 'react'; +import { CustomOptions } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; export interface AnalyticsFilter { id: string; @@ -27,11 +27,7 @@ export interface AnalyticsFiltersProps { export const AnalyticsFilters: React.FC = (props) => { const { filters = [], className } = props; - return ( - <> - {filters.length > 0 && } - - ); + return <>{filters.length > 0 && }; }; export interface AnalyticsSelectedFiltersProps { @@ -40,14 +36,10 @@ export interface AnalyticsSelectedFiltersProps { onReset?: () => void; } -export const AnalyticsSelectedFilters: React.FC< - AnalyticsSelectedFiltersProps -> = (props) => { +export const AnalyticsSelectedFilters: React.FC = (props) => { const { filters, selectedFilters = [], onReset } = props; const availableFilters = filters.map(({ id }) => id); - const isFiltered = - selectedFilters.filter(({ id }) => availableFilters.includes(id)).length > - 0; + const isFiltered = selectedFilters.filter(({ id }) => availableFilters.includes(id)).length > 0; if (!filters.length) { return null; @@ -56,9 +48,7 @@ export const AnalyticsSelectedFilters: React.FC< return (
{filters.map((filter, index) => { - const isSelected = !!selectedFilters.find( - (each) => each.id === filter.id, - ); + const isSelected = !!selectedFilters.find((each) => each.id === filter.id); if (!isSelected) { return null; @@ -71,11 +61,7 @@ export const AnalyticsSelectedFilters: React.FC< ); })} {isFiltered && ( - diff --git a/studio/src/components/analytics/getColumnData.tsx b/studio/src/components/analytics/getColumnData.tsx index dd7e956338..4cd8c943b8 100644 --- a/studio/src/components/analytics/getColumnData.tsx +++ b/studio/src/components/analytics/getColumnData.tsx @@ -1,43 +1,26 @@ -import { cn } from "@/lib/utils"; -import { - ChevronDownIcon, - ChevronUpIcon, - EllipsisVerticalIcon, -} from "@heroicons/react/24/outline"; -import { ClipboardCopyIcon } from "@radix-ui/react-icons"; -import { ColumnDef } from "@tanstack/react-table"; -import { - AnalyticsViewColumn, - Unit, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import copy from "copy-to-clipboard"; -import { formatInTimeZone } from "date-fns-tz"; -import compact from "lodash/compact"; -import React, { ReactNode, useState } from "react"; -import { CodeViewer } from "../code-viewer"; -import { Button } from "../ui/button"; -import { Dialog2, Dialog2Content, Dialog2Title } from "../ui/dialog2"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "../ui/dropdown-menu"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "../ui/tooltip"; -import { useToast } from "../ui/use-toast"; -import { nanoTimestampToTime } from "./charts"; -import { defaultFilterFn } from "./defaultFilterFunction"; -import { InfoTooltip } from "@/components/info-tooltip"; +import { cn } from '@/lib/utils'; +import { ChevronDownIcon, ChevronUpIcon, EllipsisVerticalIcon } from '@heroicons/react/24/outline'; +import { ClipboardCopyIcon } from '@radix-ui/react-icons'; +import { ColumnDef } from '@tanstack/react-table'; +import { AnalyticsViewColumn, Unit } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import copy from 'copy-to-clipboard'; +import { formatInTimeZone } from 'date-fns-tz'; +import compact from 'lodash/compact'; +import React, { ReactNode, useState } from 'react'; +import { CodeViewer } from '../code-viewer'; +import { Button } from '../ui/button'; +import { Dialog2, Dialog2Content, Dialog2Title } from '../ui/dialog2'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui/dropdown-menu'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'; +import { useToast } from '../ui/use-toast'; +import { nanoTimestampToTime } from './charts'; +import { defaultFilterFn } from './defaultFilterFunction'; +import { InfoTooltip } from '@/components/info-tooltip'; export const mapStatusCode: Record = { - STATUS_CODE_UNSET: "OK", - STATUS_CODE_OK: "OK", - STATUS_CODE_ERROR: "Error", + STATUS_CODE_UNSET: 'OK', + STATUS_CODE_OK: 'OK', + STATUS_CODE_ERROR: 'Error', }; const columnConfig: Record< @@ -54,28 +37,28 @@ const columnConfig: Record< > = { traceId: { header: { - className: "w-[80]px", + className: 'w-[80]px', }, }, unixTimestamp: { header: { - className: "w-[150px]", + className: 'w-[150px]', }, }, operationName: { cell: { - className: "w-[200px] lg:w-[320px] truncate", + className: 'w-[200px] lg:w-[320px] truncate', }, }, actions: { header: { - className: "w-[100]px", + className: 'w-[100]px', }, }, }; const formatColumnData = (data: string | number, type: Unit): ReactNode => { - if (!data) return "-"; + if (!data) return '-'; // If the type is unspecified, we just return the data as is in string format // In that way, we format e.g. boolean values as "true" or "false" @@ -94,9 +77,7 @@ const formatColumnData = (data: string | number, type: Unit): ReactNode => { -
- {data.toString().substring(0, 7)} -
+
{data.toString().substring(0, 7)}
{data} @@ -111,13 +92,7 @@ const formatColumnData = (data: string | number, type: Unit): ReactNode => { - - {formatInTimeZone( - Number(data) * 1000, - "UTC", - "MMM dd yyyy HH:mm:ss", - )} - + {formatInTimeZone(Number(data) * 1000, 'UTC', 'MMM dd yyyy HH:mm:ss')} {data} @@ -146,23 +121,13 @@ const ActionDialog = (props: { {title} - {unit === Unit.CodeBlock ? ( - - ) : ( - - )} + {unit === Unit.CodeBlock ? : } ); }; -const ActionsDropdown = ({ - actionColumns, - rowData, -}: { - actionColumns: AnalyticsViewColumn[]; - rowData: any; -}) => { +const ActionsDropdown = ({ actionColumns, rowData }: { actionColumns: AnalyticsViewColumn[]; rowData: any }) => { const [isOpen, setIsOpen] = useState(false); const [data, setData] = useState< | { @@ -204,9 +169,7 @@ const ActionsDropdown = ({ })} - {data && isOpen && ( - - )} + {data && isOpen && } ); }; @@ -233,18 +196,12 @@ const TextContent = ({ text }: { text: string }) => { ); }; -const DialogActions = ({ - text, - className, -}: { - text: string; - className?: string; -}) => { +const DialogActions = ({ text, className }: { text: string; className?: string }) => { const { toast, dismiss } = useToast(); const copyText = () => { copy(text); - const { id } = toast({ description: "Copied to clipboard" }); + const { id } = toast({ description: 'Copied to clipboard' }); const t = setTimeout(() => { dismiss(id); @@ -254,12 +211,7 @@ const DialogActions = ({ }; return ( -
+
} /> ); @@ -1022,15 +923,11 @@ export const ErrorRateOverTimeCard = ({ syncId }: { syncId?: string }) => { } else { content = ( - + - - + + { type="number" interval="preserveStart" minTickGap={60} - tick={{ fill: "hsl(var(--muted-foreground))", fontSize: "13px" }} + tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: '13px' }} /> - + - + @@ -1089,9 +979,7 @@ export const ErrorRateOverTimeCard = ({ syncId }: { syncId?: string }) => {
Error rate over time - - Error rate per minute in {getInfoTip(range)} - + Error rate per minute in {getInfoTip(range)}
@@ -1100,13 +988,7 @@ export const ErrorRateOverTimeCard = ({ syncId }: { syncId?: string }) => { ); }; -export const LatencyDistributionCard = ({ - series, - syncId, -}: { - series: any[]; - syncId?: string; -}) => { +export const LatencyDistributionCard = ({ series, syncId }: { series: any[]; syncId?: string }) => { const [activeLatencies, setActiveLatencies] = useState({ p50: false, p90: false, @@ -1120,14 +1002,11 @@ export const LatencyDistributionCard = ({ }; const { isMobile } = useWindowSize(); - const { data, ticks, domain, timeFormatter } = useChartData( - timeRange, - series, - ); + const { data, ticks, domain, timeFormatter } = useChartData(timeRange, series); - const p50StrokeColor = "hsl(var(--chart-primary))"; - const p90StrokeColor = "hsl(var(--warning))"; - const p99StrokeColor = "hsl(var(--destructive))"; + const p50StrokeColor = 'hsl(var(--chart-primary))'; + const p90StrokeColor = 'hsl(var(--warning))'; + const p99StrokeColor = 'hsl(var(--destructive))'; return ( @@ -1140,11 +1019,7 @@ export const LatencyDistributionCard = ({ - + { setActiveLatencies({ ...activeLatencies, diff --git a/studio/src/components/analytics/refresh-interval.tsx b/studio/src/components/analytics/refresh-interval.tsx index 5cf321d5fc..b20b7c0a18 100644 --- a/studio/src/components/analytics/refresh-interval.tsx +++ b/studio/src/components/analytics/refresh-interval.tsx @@ -1,31 +1,26 @@ -import { ClockIcon } from "@radix-ui/react-icons"; -import { Button } from "../ui/button"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, -} from "../ui/dropdown-menu"; +import { ClockIcon } from '@radix-ui/react-icons'; +import { Button } from '../ui/button'; +import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger } from '../ui/dropdown-menu'; export const refreshIntervals = [ { - label: "Off", + label: 'Off', value: undefined, }, { - label: "10s", + label: '10s', value: 10 * 1000, }, { - label: "30s", + label: '30s', value: 30 * 1000, }, { - label: "1m", + label: '1m', value: 60 * 1000, }, { - label: "5m", + label: '5m', value: 5 * 60 * 1000, }, ]; diff --git a/studio/src/components/analytics/toolbar.tsx b/studio/src/components/analytics/toolbar.tsx index e88dcea443..6ae82df3fa 100644 --- a/studio/src/components/analytics/toolbar.tsx +++ b/studio/src/components/analytics/toolbar.tsx @@ -1,31 +1,28 @@ -import { useFeature } from "@/hooks/use-feature"; -import { useSessionStorage } from "@/hooks/use-session-storage"; -import { calURL } from "@/lib/constants"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { ParsedUrlQueryInput } from "querystring"; -import { BiAnalyse } from "react-icons/bi"; -import { BsQuestionCircle } from "react-icons/bs"; -import { IoBarcodeSharp } from "react-icons/io5"; -import { Button } from "../ui/button"; -import { Spacer } from "../ui/spacer"; -import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; -import { Toolbar } from "../ui/toolbar"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "../ui/tooltip"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useCurrentOrganization } from "@/hooks/use-current-organization"; +import { useFeature } from '@/hooks/use-feature'; +import { useSessionStorage } from '@/hooks/use-session-storage'; +import { calURL } from '@/lib/constants'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { ParsedUrlQueryInput } from 'querystring'; +import { BiAnalyse } from 'react-icons/bi'; +import { BsQuestionCircle } from 'react-icons/bs'; +import { IoBarcodeSharp } from 'react-icons/io5'; +import { Button } from '../ui/button'; +import { Spacer } from '../ui/spacer'; +import { Tabs, TabsList, TabsTrigger } from '../ui/tabs'; +import { Toolbar } from '../ui/toolbar'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useCurrentOrganization } from '@/hooks/use-current-organization'; export const AnalyticsToolbar: React.FC<{ tab: string; children?: React.ReactNode; }> = (props) => { const router = useRouter(); - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); const organizationSlug = useCurrentOrganization()?.slug; const query: ParsedUrlQueryInput = { @@ -46,9 +43,7 @@ export const AnalyticsToolbar: React.FC<{ query.dateRange = router.query.dateRange; } - const [tracesRoute, setTracesRoute] = useSessionStorage< - ParsedUrlQueryInput | undefined - >("analytics.route", query); + const [tracesRoute, setTracesRoute] = useSessionStorage('analytics.route', query); const isTracePage = router.query.traceID; @@ -58,7 +53,7 @@ export const AnalyticsToolbar: React.FC<{ } }; - const retention = useFeature("analytics-retention"); + const retention = useFeature('analytics-retention'); return ( @@ -67,8 +62,7 @@ export const AnalyticsToolbar: React.FC<{ {
- {name}{" "} - ={" "} + {name} ={' '} {value}
@@ -75,32 +58,19 @@ function Node({ }) { const [showDetails, setShowDetails] = useState(false); const hasChildren = span.children && span.children.length > 0; - const parentChildrenCount = parentSpan?.children - ? parentSpan.children.length - : 0; + const parentChildrenCount = parentSpan?.children ? parentSpan.children.length : 0; // Work with smaller units (picosecond) on numerator to circumvent bigint division const elapsedDurationPs = (span.timestamp - globalStartTime) * bigintE3; const spanDurationPs = span.duration * bigintE3; - const visualOffsetPercentage = Number( - ((elapsedDurationPs / globalDuration) * bigintE2) / bigintE3, - ); - const visualWidthPercentage = Number( - ((spanDurationPs / globalDuration) * bigintE2) / bigintE3, - ); + const visualOffsetPercentage = Number(((elapsedDurationPs / globalDuration) * bigintE2) / bigintE3); + const visualWidthPercentage = Number(((spanDurationPs / globalDuration) * bigintE2) / bigintE3); - const [isOpen, setIsOpen] = useState( - () => level <= initialCollapsedSpanDepth, - ); - const service = services.find( - (service) => service.name === mapServiceName(span.serviceName), - ); + const [isOpen, setIsOpen] = useState(() => level <= initialCollapsedSpanDepth); + const service = services.find((service) => service.name === mapServiceName(span.serviceName)); const hasChildrenError = (span: SpanNode) => { - if ( - span.statusCode === "STATUS_CODE_ERROR" || - !!span.attributes?.httpStatusCode.startsWith("4") - ) { + if (span.statusCode === 'STATUS_CODE_ERROR' || !!span.attributes?.httpStatusCode.startsWith('4')) { return true; } if (span.children) { @@ -111,8 +81,8 @@ function Node({ const [isError, setIsError] = useState( () => - span.statusCode === "STATUS_CODE_ERROR" || - !!span.attributes?.httpStatusCode.startsWith("4") || + span.statusCode === 'STATUS_CODE_ERROR' || + !!span.attributes?.httpStatusCode.startsWith('4') || (!isOpen && hasChildrenError(span)), ); @@ -135,10 +105,7 @@ function Node({ if (prevOpen) { setIsError(hasChildrenError(span)); } else { - setIsError( - span.statusCode === "STATUS_CODE_ERROR" || - !!span.attributes?.httpStatusCode.startsWith("4"), - ); + setIsError(span.statusCode === 'STATUS_CODE_ERROR' || !!span.attributes?.httpStatusCode.startsWith('4')); } } return !prevOpen; @@ -151,18 +118,15 @@ function Node({ marginLeft: `${16}px`, minWidth: `${1024 - level * 32}px`, }} - className={clsx( - `trace-ul relative before:-top-4 before:h-[34px] lg:max-w-none`, - { - "before:top-0 before:h-[18px]": isParentDetailsOpen, - "before:!h-full": parentChildrenCount > 1, - "pl-4": level > 1, - }, - )} + className={clsx(`trace-ul relative before:-top-4 before:h-[34px] lg:max-w-none`, { + 'before:top-0 before:h-[18px]': isParentDetailsOpen, + 'before:!h-full': parentChildrenCount > 1, + 'pl-4': level > 1, + })} >
  • @@ -177,20 +141,13 @@ function Node({ variant="outline" onClick={toggleTree} disabled={!hasChildren} - className={clsx( - "mt-1.5 h-min w-min rounded-sm border border-input p-px", - { - "border-none": !hasChildren, - }, - )} + className={clsx('mt-1.5 h-min w-min rounded-sm border border-input p-px', { + 'border-none': !hasChildren, + })} > <> - {hasChildren && isOpen && ( - - )} - {hasChildren && !isOpen && ( - - )} + {hasChildren && isOpen && } + {hasChildren && !isOpen && } {!hasChildren && } @@ -204,20 +161,16 @@ function Node({
    - {isError && ( - - )} + {isError && }
    - {span.attributes?.operationProtocol === "grpc" && ( + {span.attributes?.operationProtocol === 'grpc' && ( )} -
    {service?.name}
    {" "} +
    {service?.name}
    {' '}
    -
    - {mapSpanKind[span.spanKind]} -
    +
    {mapSpanKind[span.spanKind]}
    @@ -233,9 +186,9 @@ function Node({
    {span.spanName}
    - {span.attributes?.operationProtocol === "grpc" && ( + {span.attributes?.operationProtocol === 'grpc' && (
    - Cosmo Connect{" "} + Cosmo Connect{' '}
    )}
    @@ -252,8 +205,8 @@ function Node({
    = 8, + className={clsx('z-8 absolute bg-transparent text-xs', { + 'px-2': visualWidthPercentage < 8, + '!text-white': visualWidthPercentage >= 8, })} > {nsToTime(span.duration)} @@ -285,32 +238,14 @@ function Node({ - {span.statusCode && ( - - )} + {span.statusCode && }
    - {span.statusMessage && ( - - )} + {span.statusMessage && } {Object.entries(span.attributes ?? {}) - .sort((a, b) => - a[0].toUpperCase().localeCompare(b[0].toUpperCase()), - ) + .sort((a, b) => a[0].toUpperCase().localeCompare(b[0].toUpperCase())) .filter(([key, value], _) => !!value) .map(([key, value]) => { return ; @@ -371,16 +306,16 @@ const Trace = ({ spans }: { spans: Span[] }) => { if (!moveData.moving) { setPaneWidth((width) => width + moveData.delta.x); - document.body.classList.remove("select-none"); + document.body.classList.remove('select-none'); } else { - document.body.classList.add("select-none"); + document.body.classList.add('select-none'); } }, []); const ref = useMovable({ onChange: handleChange, - axis: "x", - bounds: "parent", + axis: 'x', + bounds: 'parent', }); useEffect(() => { @@ -456,19 +391,14 @@ const Trace = ({ spans }: { spans: Span[] }) => { const tree = buildSpanTree(spans); setTraceTree(tree); - setMissingRootSpan( - !!tree.parentSpanID && - !spans.find((span) => tree.parentSpanID === span.spanID), - ); + setMissingRootSpan(!!tree.parentSpanID && !spans.find((span) => tree.parentSpanID === span.spanID)); setGlobalDuration(gEndTimeNano - gStartTimeNano); setGlobalStartTime(gStartTimeNano); }, [spans]); const verticalResizeStyle = { - left: mouseState.moving - ? paneWidth + mouseState.delta?.x - : mouseState.position.x, + left: mouseState.moving ? paneWidth + mouseState.delta?.x : mouseState.position.x, }; return ( @@ -505,12 +435,9 @@ const Trace = ({ spans }: { spans: Span[] }) => {
    {detectedService.map((service) => { return ( - + diff --git a/studio/src/components/analytics/use-apply-params.tsx b/studio/src/components/analytics/use-apply-params.tsx index d86ddc528d..613696589a 100644 --- a/studio/src/components/analytics/use-apply-params.tsx +++ b/studio/src/components/analytics/use-apply-params.tsx @@ -1,18 +1,14 @@ -import { useRouter } from "next/router"; -import { useCallback } from "react"; +import { useRouter } from 'next/router'; +import { useCallback } from 'react'; export const useApplyParams = () => { const router = useRouter(); return useCallback( (newParams: Record, unset?: string[]) => { const q = Object.fromEntries( - Object.entries(router.query).filter( - ([key]) => !unset?.includes(key) && newParams[key] !== null, - ), - ); - const params = Object.fromEntries( - Object.entries(newParams).filter(([_, value]) => value !== null), + Object.entries(router.query).filter(([key]) => !unset?.includes(key) && newParams[key] !== null), ); + const params = Object.fromEntries(Object.entries(newParams).filter(([_, value]) => value !== null)); router.push({ query: { ...q, diff --git a/studio/src/components/analytics/use-range.tsx b/studio/src/components/analytics/use-range.tsx index 7ad008df09..fafc3fd283 100644 --- a/studio/src/components/analytics/use-range.tsx +++ b/studio/src/components/analytics/use-range.tsx @@ -1,11 +1,9 @@ -import { useRouter } from "next/router"; +import { useRouter } from 'next/router'; export const useRange = () => { const router = useRouter(); - const range = router.query.range - ? parseInt(router.query.range?.toString()) - : 24; + const range = router.query.range ? parseInt(router.query.range?.toString()) : 24; switch (range) { case 24: diff --git a/studio/src/components/app-provider.tsx b/studio/src/components/app-provider.tsx index 283682923f..12a2f4f185 100644 --- a/studio/src/components/app-provider.tsx +++ b/studio/src/components/app-provider.tsx @@ -1,22 +1,21 @@ -import { identify, resetTracking } from "@/lib/track"; -import { Transport } from "@connectrpc/connect"; -import { TransportProvider } from "@connectrpc/connect-query"; -import { createConnectTransport } from "@connectrpc/connect-web"; -import { QueryClient, useQuery, useQueryClient, } from "@tanstack/react-query"; -import { useRouter } from "next/router"; -import { ReactNode, createContext, useEffect, useState } from "react"; -import { useCookieOrganization } from "@/hooks/use-cookie-organization"; -import { setUser as setSentryUser } from "@sentry/nextjs"; -import { OrganizationRole } from "@/lib/constants"; -import { WorkspaceProvider } from "@/components/dashboard/workspace-provider"; +import { identify, resetTracking } from '@/lib/track'; +import { Transport } from '@connectrpc/connect'; +import { TransportProvider } from '@connectrpc/connect-query'; +import { createConnectTransport } from '@connectrpc/connect-web'; +import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; +import { ReactNode, createContext, useEffect, useState } from 'react'; +import { useCookieOrganization } from '@/hooks/use-cookie-organization'; +import { setUser as setSentryUser } from '@sentry/nextjs'; +import { OrganizationRole } from '@/lib/constants'; +import { WorkspaceProvider } from '@/components/dashboard/workspace-provider'; const sessionQueryClient = new QueryClient(); export const UserContext = createContext(undefined); -export const SessionClientContext = - createContext(sessionQueryClient); +export const SessionClientContext = createContext(sessionQueryClient); -const publicPaths = ["/login", "/signup"]; +const publicPaths = ['/login', '/signup']; export interface User { id: string; @@ -57,13 +56,7 @@ export interface Organization { email?: string; }; subscription?: { - status: - | "active" - | "canceled" - | "trialing" - | "incomplete" - | "incomplete_expired" - | "past_due"; + status: 'active' | 'canceled' | 'trialing' | 'incomplete' | 'incomplete_expired' | 'past_due'; currentPeriodEnd: string; cancelAtPeriodEnd: boolean; trialEnd: string; @@ -88,20 +81,17 @@ export interface Session { export class UnauthorizedError extends Error { constructor() { super(); - this.name = "UnauthorizedError"; + this.name = 'UnauthorizedError'; } } const fetchSession = async () => { try { - const response = await fetch( - process.env.NEXT_PUBLIC_COSMO_CP_URL + "/v1/auth/session", - { - method: "GET", - mode: "cors", - credentials: "include", - }, - ); + const response = await fetch(process.env.NEXT_PUBLIC_COSMO_CP_URL + '/v1/auth/session', { + method: 'GET', + mode: 'cors', + credentials: 'include', + }); if (response.status === 200) { const body = await response.json(); return body; @@ -125,8 +115,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { // On initial load or page reload, the transport is set and available already. // So only when the transport changes again, we need to reset queries. // URL slug changes -> update cookie -> update verified slug -> changes transport -> updates reset counter -> resets queries - const [verifiedOrganizationSlug, setVerifiedOrganizationSlug] = - useState(); + const [verifiedOrganizationSlug, setVerifiedOrganizationSlug] = useState(); const [transport, setTransport] = useState(); const [queryResetCounter, setQueryResetCounter] = useState(-1); const queryClient = useQueryClient(); @@ -135,17 +124,14 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { if (!router.isReady) return; - if (currentOrgSlug && typeof currentOrgSlug === "string") { + if (currentOrgSlug && typeof currentOrgSlug === 'string') { setOrgSlugCookie(currentOrgSlug); } }, [currentOrgSlug, router, setOrgSlugCookie]); - const { data, error, isFetching } = useQuery< - Session | null, - UnauthorizedError | Error - >( + const { data, error, isFetching } = useQuery( { - queryKey: ["user", router.asPath], + queryKey: ['user', router.asPath], queryFn: () => fetchSession(), retry(failureCount, error) { if (error instanceof UnauthorizedError) return false; @@ -157,17 +143,11 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { if (isFetching || !router.isReady) return; - if ( - error && - error instanceof UnauthorizedError && - !publicPaths.includes(router.pathname) - ) { + if (error && error instanceof UnauthorizedError && !publicPaths.includes(router.pathname)) { const redirectURL = `${process.env.NEXT_PUBLIC_COSMO_STUDIO_URL}${router.asPath}`; router.replace(`/login?redirectURL=${redirectURL}`); } else if (data && !error) { - const currentOrg = data.organizations.find( - (org) => org.slug === cookieOrgSlug, - ); + const currentOrg = data.organizations.find((org) => org.slug === cookieOrgSlug); const organization = currentOrg || data.organizations[0]; @@ -205,20 +185,12 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { setVerifiedOrganizationSlug(organization.slug); if ( - (router.pathname === "/" || - router.pathname === "/login" || - !currentOrg) && - router.pathname !== "/account/invitations" + (router.pathname === '/' || router.pathname === '/login' || !currentOrg) && + router.pathname !== '/account/invitations' ) { - const url = new URL( - window.location.origin + router.basePath + router.asPath, - ); + const url = new URL(window.location.origin + router.basePath + router.asPath); const params = new URLSearchParams(url.search); - router.replace( - params.size !== 0 - ? `/${organization.slug}?${params}` - : `/${organization.slug}`, - ); + router.replace(params.size !== 0 ? `/${organization.slug}?${params}` : `/${organization.slug}`); } } }, [router, data, isFetching, error, cookieOrgSlug]); @@ -233,12 +205,12 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { useHttpGet: true, interceptors: [ (next) => async (req) => { - req.header.set("cosmo-org-slug", verifiedOrganizationSlug); + req.header.set('cosmo-org-slug', verifiedOrganizationSlug); return await next(req); }, ], // Allow cookies to be sent to the server - credentials: "include", + credentials: 'include', }); setTransport(newTransport); @@ -263,9 +235,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { return ( - - {children} - + {children} ); @@ -275,9 +245,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { - - {children} - + {children} diff --git a/studio/src/components/audit-log-table.tsx b/studio/src/components/audit-log-table.tsx index 14369388ec..df6849679f 100644 --- a/studio/src/components/audit-log-table.tsx +++ b/studio/src/components/audit-log-table.tsx @@ -1,30 +1,17 @@ -import { docsBaseURL } from "@/lib/constants"; -import { AuditLog } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { EmptyState } from "./empty-state"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, - TableWrapper, -} from "./ui/table"; -import { formatDateTime } from "@/lib/format-date"; -import { capitalize } from "@/lib/utils"; -import { pascalCase } from "change-case"; -import { AiOutlineAudit } from "react-icons/ai"; -import { PiKeyBold, PiRobotFill, PiUserBold } from "react-icons/pi"; -import { Badge } from "./ui/badge"; +import { docsBaseURL } from '@/lib/constants'; +import { AuditLog } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { EmptyState } from './empty-state'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableWrapper } from './ui/table'; +import { formatDateTime } from '@/lib/format-date'; +import { capitalize } from '@/lib/utils'; +import { pascalCase } from 'change-case'; +import { AiOutlineAudit } from 'react-icons/ai'; +import { PiKeyBold, PiRobotFill, PiUserBold } from 'react-icons/pi'; +import { Badge } from './ui/badge'; export const Empty = (params: { unauthorized: boolean }) => { if (params.unauthorized) { - return ( - - ); + return ; } return ( @@ -34,12 +21,7 @@ export const Empty = (params: { unauthorized: boolean }) => { description={ @@ -77,27 +59,29 @@ export const AuditLogTable = ({ logs }: { logs?: AuditLog[] }) => { let preParagraph = null; let postParagraph = null; - if (auditAction === "organization_invitation.created" || - auditAction === "scim.organization_invitation_created" || - action === "queued_deletion") { - postParagraph = "for"; - } else if (auditAction === "member_role.updated") { - preParagraph = "role for"; - postParagraph = "to"; - } else if (auditAction === "member_group.updated" || auditAction === "api_key.group_updated") { - preParagraph = "group for"; - postParagraph = "to"; - } else if (auditAction === "member_group.added" ) { - postParagraph = "to group"; - } else if (auditAction === "member_group.removed") { - postParagraph = "from group"; - } else if (auditAction === "group.members_moved") { - preParagraph = "members from group"; - postParagraph = "to "; - } else if (action === "moved") { + if ( + auditAction === 'organization_invitation.created' || + auditAction === 'scim.organization_invitation_created' || + action === 'queued_deletion' + ) { + postParagraph = 'for'; + } else if (auditAction === 'member_role.updated') { + preParagraph = 'role for'; + postParagraph = 'to'; + } else if (auditAction === 'member_group.updated' || auditAction === 'api_key.group_updated') { + preParagraph = 'group for'; + postParagraph = 'to'; + } else if (auditAction === 'member_group.added') { + postParagraph = 'to group'; + } else if (auditAction === 'member_group.removed') { + postParagraph = 'from group'; + } else if (auditAction === 'group.members_moved') { + preParagraph = 'members from group'; + postParagraph = 'to '; + } else if (action === 'moved') { postParagraph = `to ${targetNamespaceDisplayName} namespace,`; } else if (auditableDisplayName) { - preParagraph = "in"; + preParagraph = 'in'; if (!!targetNamespaceDisplayName) { postParagraph = `in ${targetNamespaceDisplayName} namespace,`; } @@ -107,19 +91,10 @@ export const AuditLogTable = ({ logs }: { logs?: AuditLog[] }) => { if (targetDisplayName) { label = ( <> - {preParagraph && ( - - {preParagraph} - - )} + {preParagraph && {preParagraph}} - - - {targetDisplayName} - + + {targetDisplayName} ); @@ -127,46 +102,23 @@ export const AuditLogTable = ({ logs }: { logs?: AuditLog[] }) => { const actionView = ( <> - - {capitalize(action.replaceAll('_', ' '))} - + {capitalize(action.replaceAll('_', ' '))} {label} {auditableDisplayName && ( <> - {postParagraph && ( - - {postParagraph} - - )} - - {auditableDisplayName} - + {postParagraph && {postParagraph}} + {auditableDisplayName} )} ); return ( - + - {actorType === "api_key" && ( - - )} - {actorType === "user" && ( - - )} - {actorType === "system" && ( - - )} + {actorType === 'api_key' && } + {actorType === 'user' && } + {actorType === 'system' && } {apiKeyName ? `${apiKeyName} (${actorDisplayName})` : `${actorDisplayName}`} @@ -174,17 +126,13 @@ export const AuditLogTable = ({ logs }: { logs?: AuditLog[] }) => {
    -
    - {actionView} -
    +
    {actionView}
    {auditAction}
    - - {formatDateTime(new Date(createdAt))} - + {formatDateTime(new Date(createdAt))}
    ); }, diff --git a/studio/src/components/auth/auth-components.tsx b/studio/src/components/auth/auth-components.tsx index 8e035fb653..12b49b2535 100644 --- a/studio/src/components/auth/auth-components.tsx +++ b/studio/src/components/auth/auth-components.tsx @@ -1,8 +1,8 @@ -import { cn } from "@/lib/utils"; -import Image from "next/image"; -import Link from "next/link"; -import { Logo } from "../logo"; -import { ReactNode } from "react"; +import { cn } from '@/lib/utils'; +import Image from 'next/image'; +import Link from 'next/link'; +import { Logo } from '../logo'; +import { ReactNode } from 'react'; import { BoltIcon, CodeBracketIcon, @@ -10,8 +10,8 @@ import { ShareIcon, MagnifyingGlassIcon, RocketLaunchIcon, -} from "@heroicons/react/24/outline"; -import { getSignupContent, type SignupVariant } from "@/lib/signup-content"; +} from '@heroicons/react/24/outline'; +import { getSignupContent, type SignupVariant } from '@/lib/signup-content'; /** * Auth Card - The card container for auth forms @@ -25,8 +25,8 @@ export const AuthCard = ({ children, className }: AuthCardProps) => { return (
    {children} @@ -41,9 +41,7 @@ export const AuthLogoHeader = () => { return ( - - WunderGraph Cosmo - + WunderGraph Cosmo ); }; @@ -57,24 +55,24 @@ export const AuthFooter = () => { const footerLinks = [ { - href: "https://wundergraph.com/privacy-policy", - label: "Privacy Policy", + href: 'https://wundergraph.com/privacy-policy', + label: 'Privacy Policy', }, { - href: "https://trust.wundergraph.com/", - label: "Trust Center", + href: 'https://trust.wundergraph.com/', + label: 'Trust Center', }, { - href: "https://wundergraph.com/terms", - label: "Website Terms of Use", + href: 'https://wundergraph.com/terms', + label: 'Website Terms of Use', }, { - href: "https://wundergraph.com/cosmo-managed-service-terms", - label: "Cosmo Managed Service Terms", + href: 'https://wundergraph.com/cosmo-managed-service-terms', + label: 'Cosmo Managed Service Terms', }, { - href: "https://wundergraph.com/pricing", - label: "Pricing", + href: 'https://wundergraph.com/pricing', + label: 'Pricing', }, ]; @@ -103,9 +101,9 @@ export const AuthFooter = () => { */ export const TrustedCompanies = () => { const companies = [ - { name: "Shutterstock", logo: "https://wundergraph.com/images/logos/shutterstock.svg", width: 120, height: 24 }, - { name: "eBay", logo: "https://wundergraph.com/images/logos/ebay.svg", width: 60, height: 24 }, - { name: "SoundCloud", logo: "https://wundergraph.com/images/logos/soundcloud.svg", width: 120, height: 24 }, + { name: 'Shutterstock', logo: 'https://wundergraph.com/images/logos/shutterstock.svg', width: 120, height: 24 }, + { name: 'eBay', logo: 'https://wundergraph.com/images/logos/ebay.svg', width: 60, height: 24 }, + { name: 'SoundCloud', logo: 'https://wundergraph.com/images/logos/soundcloud.svg', width: 120, height: 24 }, ]; return ( @@ -130,16 +128,10 @@ export const TrustedCompanies = () => { /** * Marketing Header - Title and description for the right side */ -export const MarketingHeader = ({ - title, - description, -}: { - title?: string; - description?: string; -}) => { - const defaultTitle = "Cosmo: Open-Source\nGraphQL Federation Solution"; +export const MarketingHeader = ({ title, description }: { title?: string; description?: string }) => { + const defaultTitle = 'Cosmo: Open-Source\nGraphQL Federation Solution'; const defaultDescription = - "Unify distributed APIs into one federated graph. Platform teams get observability and control. Service teams ship independently."; + 'Unify distributed APIs into one federated graph. Platform teams get observability and control. Service teams ship independently.'; const displayTitle = title || defaultTitle; const displayDescription = description || defaultDescription; @@ -147,10 +139,10 @@ export const MarketingHeader = ({ return (

    - {displayTitle.split("\n").map((line, index) => ( + {displayTitle.split('\n').map((line, index) => ( {line} - {index < displayTitle.split("\n").length - 1 &&
    } + {index < displayTitle.split('\n').length - 1 &&
    }
    ))}

    @@ -162,29 +154,19 @@ export const MarketingHeader = ({ /** * Feature Item - Individual feature with icon tile (glossy, border highlight) */ -const FeatureItem = ({ - icon, - title, - description, -}: { - icon: React.ReactNode; - title: string; - description: string; -}) => ( +const FeatureItem = ({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) => (
    {/* Glossy highlight (top edge) */}
    {/* Border highlight (static tilt) */} @@ -202,27 +184,27 @@ const FeatureItem = ({ * Product Cosmo Stack - Marketing content with features list */ export const ProductCosmoStack = ({ - variant = "login", + variant = 'login', signupVariant, }: { - variant?: "login" | "signup"; - signupVariant?: "default" | "apollo"; + variant?: 'login' | 'signup'; + signupVariant?: 'default' | 'apollo'; }) => { // Icon mapping function (larger icons for feature tiles) const getIcon = (iconName: string) => { - const iconClass = "h-8 w-8 text-purple-400"; + const iconClass = 'h-8 w-8 text-purple-400'; switch (iconName) { - case "bolt": + case 'bolt': return ; - case "code-bracket": + case 'code-bracket': return ; - case "shield-check": + case 'shield-check': return ; - case "share": + case 'share': return ; - case "magnifying-glass": + case 'magnifying-glass': return ; - case "rocket-launch": + case 'rocket-launch': return ; default: return ; @@ -232,19 +214,19 @@ export const ProductCosmoStack = ({ const loginFeatures = [ { icon: , - title: "Real time subscriptions without new infrastructure", + title: 'Real time subscriptions without new infrastructure', description: - "Cosmo Streams turns existing event streams into GraphQL subscriptions by handling authorization, filtering, and fan out in the Cosmo Router, keeping subgraphs stateless and avoiding a separate service.", + 'Cosmo Streams turns existing event streams into GraphQL subscriptions by handling authorization, filtering, and fan out in the Cosmo Router, keeping subgraphs stateless and avoiding a separate service.', }, { icon: , - title: "Extend the router with TypeScript", + title: 'Extend the router with TypeScript', description: - "With TypeScript plugin support in Cosmo Connect, you can extend the Cosmo Router using TypeScript and run custom logic directly inside the router, without deploying separate services.", + 'With TypeScript plugin support in Cosmo Connect, you can extend the Cosmo Router using TypeScript and run custom logic directly inside the router, without deploying separate services.', }, { icon: , - title: "Enforce custom schema rules before deploy", + title: 'Enforce custom schema rules before deploy', description: "With Subgraph Check Extensions, you can run your own validation logic as part of Cosmo's subgraph checks, enforcing custom schema rules before changes are deployed.", }, @@ -253,8 +235,7 @@ export const ProductCosmoStack = ({ // Signup content always from content map (single source of truth) let marketingTitle: string | undefined; let marketingDescription: string | undefined; - const signupContent = - variant === "signup" ? getSignupContent(signupVariant ?? "default") : null; + const signupContent = variant === 'signup' ? getSignupContent(signupVariant ?? 'default') : null; if (signupContent) { marketingTitle = signupContent.marketingTitle; marketingDescription = signupContent.marketingDescription; @@ -266,19 +247,14 @@ export const ProductCosmoStack = ({ description: feature.description, })) ?? []; - const features = variant === "login" ? loginFeatures : signupFeatures; + const features = variant === 'login' ? loginFeatures : signupFeatures; return (
    {features.map((feature, index) => ( - + ))}
    diff --git a/studio/src/components/cache/cache-details-sheet.tsx b/studio/src/components/cache/cache-details-sheet.tsx index 864e9d876f..b53054bf1b 100644 --- a/studio/src/components/cache/cache-details-sheet.tsx +++ b/studio/src/components/cache/cache-details-sheet.tsx @@ -1,46 +1,38 @@ -import { PlayIcon } from "@radix-ui/react-icons"; -import { useHotkeys } from "@saas-ui/use-hotkeys"; -import { CacheWarmerOperation } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { FiChevronDown, FiChevronUp } from "react-icons/fi"; -import { CodeViewer } from "../code-viewer"; -import { Button } from "../ui/button"; -import { CopyButton } from "../ui/copy-button"; -import { Kbd } from "../ui/kbd"; -import { Sheet, SheetContent, SheetHeader, SheetTitle } from "../ui/sheet"; -import { Spacer } from "../ui/spacer"; -import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; -import { useUser } from "@/hooks/use-user"; -import { useContext, useEffect, useState } from "react"; -import { GraphContext } from "../layout/graph-layout"; - -export const CacheDetailsSheet: React.FC = ({ - operations, -}: { - operations: CacheWarmerOperation[]; -}) => { +import { PlayIcon } from '@radix-ui/react-icons'; +import { useHotkeys } from '@saas-ui/use-hotkeys'; +import { CacheWarmerOperation } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { FiChevronDown, FiChevronUp } from 'react-icons/fi'; +import { CodeViewer } from '../code-viewer'; +import { Button } from '../ui/button'; +import { CopyButton } from '../ui/copy-button'; +import { Kbd } from '../ui/kbd'; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '../ui/sheet'; +import { Spacer } from '../ui/spacer'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; +import { useUser } from '@/hooks/use-user'; +import { useContext, useEffect, useState } from 'react'; +import { GraphContext } from '../layout/graph-layout'; + +export const CacheDetailsSheet: React.FC = ({ operations }: { operations: CacheWarmerOperation[] }) => { const router = useRouter(); const operationId = router.query.operationId as string; - const [index, setIndex] = useState( - operations.findIndex((r: CacheWarmerOperation) => r.id === operationId), - ); + const [index, setIndex] = useState(operations.findIndex((r: CacheWarmerOperation) => r.id === operationId)); useEffect(() => { if (!operationId) { return; } - const operationIndex = operations.findIndex( - (r: CacheWarmerOperation) => r.id === operationId, - ); + const operationIndex = operations.findIndex((r: CacheWarmerOperation) => r.id === operationId); setIndex(operationIndex); }, [operationId, operations]); const nextTrace = () => { if (index + 1 < operations.length) { const newQuery = { ...router.query }; - newQuery["operationId"] = operations[index + 1].id; + newQuery['operationId'] = operations[index + 1].id; router.replace({ query: newQuery, }); @@ -50,7 +42,7 @@ export const CacheDetailsSheet: React.FC = ({ const previousTrace = () => { if (index - 1 >= 0) { const newQuery = { ...router.query }; - newQuery["operationId"] = operations[index - 1].id; + newQuery['operationId'] = operations[index - 1].id; router.replace({ query: newQuery, }); @@ -58,7 +50,7 @@ export const CacheDetailsSheet: React.FC = ({ }; useHotkeys( - "K", + 'K', () => { previousTrace(); }, @@ -67,7 +59,7 @@ export const CacheDetailsSheet: React.FC = ({ ); useHotkeys( - "J", + 'J', () => { nextTrace(); }, @@ -86,7 +78,7 @@ export const CacheDetailsSheet: React.FC = ({ onOpenChange={(isOpen) => { if (!isOpen) { const newQuery = { ...router.query }; - delete newQuery["operationId"]; + delete newQuery['operationId']; router.replace({ query: newQuery, }); @@ -103,12 +95,7 @@ export const CacheDetailsSheet: React.FC = ({
    - @@ -135,41 +122,23 @@ export const CacheDetailsSheet: React.FC = ({
    - - {operationId} - - + {operationId} + - {operationId && index !== -1 && ( - - )} + {operationId && index !== -1 && } ); }; -export const CacheOperationDetails = ({ - operation, -}: { - operation: CacheWarmerOperation; -}) => { +export const CacheOperationDetails = ({ operation }: { operation: CacheWarmerOperation }) => { const user = useUser(); const graphData = useContext(GraphContext); - const { - operationContent, - clientName, - clientVersion, - operationHash, - operationName, - operationPersistedId, - } = operation; + const { operationContent, clientName, clientVersion, operationHash, operationName, operationPersistedId } = operation; return (
    @@ -184,7 +153,7 @@ export const CacheOperationDetails = ({
    : - {operationName || "-"} + {operationName || '-'}
    @@ -193,9 +162,7 @@ export const CacheOperationDetails = ({
    : -
    - {operationHash || "-"} -
    +
    {operationHash || '-'}
    @@ -204,9 +171,7 @@ export const CacheOperationDetails = ({
    : -
    - {operationPersistedId || "-"} -
    +
    {operationPersistedId || '-'}
    @@ -215,9 +180,7 @@ export const CacheOperationDetails = ({
    : -
    - {clientName || "-"} -
    +
    {clientName || '-'}
    @@ -226,9 +189,7 @@ export const CacheOperationDetails = ({
    : -
    - {clientVersion || "-"} -
    +
    {clientVersion || '-'}
    @@ -254,11 +215,9 @@ export const CacheOperationDetails = ({
    - + -
    - - - )} + }, + ); + setOpenDeleteDialog(false); + }} + > + Delete + +
    + + + )} @@ -129,28 +105,15 @@ export const CacheOperationsTable = ({ {operations.length ? ( operations.map( - ({ - id, - operationName, - operationPersistedId, - createdAt, - createdBy, - planningTime, - isManuallyAdded, - }) => ( - - - {operationName || "-"} - + ({ id, operationName, operationPersistedId, createdAt, createdBy, planningTime, isManuallyAdded }) => ( + + {operationName || '-'} {formatDistanceToNow(new Date(createdAt), { addSuffix: true, })} - {createdBy || "-"} + {createdBy || '-'}
    {operationPersistedId ? ( @@ -170,16 +133,10 @@ export const CacheOperationsTable = ({
    - {planningTime - ? nanoTimestampToTime(planningTime * 1000000) - : "-"} + {planningTime ? nanoTimestampToTime(planningTime * 1000000) : '-'} - - {path - ? "Open in Explorer" - : "Cannot open in explorer. Path to type unavailable"} + {path ? 'Open in Explorer' : 'Cannot open in explorer. Path to type unavailable'} diff --git a/studio/src/components/checks/checks-config.tsx b/studio/src/components/checks/checks-config.tsx index 8abb543a37..ab0a464904 100644 --- a/studio/src/components/checks/checks-config.tsx +++ b/studio/src/components/checks/checks-config.tsx @@ -1,24 +1,13 @@ -import { useMutation } from "@connectrpc/connect-query"; -import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; -import { - updateNamespaceChecksConfig, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; -import { - GetNamespaceChecksConfigurationResponse, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { useToast } from "../ui/use-toast"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "../ui/select"; -import { Button } from "../ui/button"; -import { Card, CardContent } from "../ui/card"; -import { useState, useEffect } from "react"; -import { useCheckUserAccess } from "@/hooks/use-check-user-access"; +import { useMutation } from '@connectrpc/connect-query'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; +import { updateNamespaceChecksConfig } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; +import { GetNamespaceChecksConfigurationResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { useToast } from '../ui/use-toast'; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; +import { Button } from '../ui/button'; +import { Card, CardContent } from '../ui/card'; +import { useState, useEffect } from 'react'; +import { useCheckUserAccess } from '@/hooks/use-check-user-access'; const TimeframeDropdown = ({ onChange, @@ -32,15 +21,15 @@ const TimeframeDropdown = ({ disabled: boolean; }) => { const selectOptions = [ - { label: "1 day", value: 1 }, - { label: "3 days", value: 3 }, - { label: "7 days", value: 7 }, - { label: "10 days", value: 10 }, - { label: "14 days", value: 14 }, - { label: "30 days", value: 30 }, - { label: "45 days", value: 45 }, - { label: "60 days", value: 60 }, - { label: "90 days", value: 90 }, + { label: '1 day', value: 1 }, + { label: '3 days', value: 3 }, + { label: '7 days', value: 7 }, + { label: '10 days', value: 10 }, + { label: '14 days', value: 14 }, + { label: '30 days', value: 30 }, + { label: '45 days', value: 45 }, + { label: '60 days', value: 60 }, + { label: '90 days', value: 90 }, ]; return ( @@ -56,11 +45,13 @@ const TimeframeDropdown = ({ - {selectOptions.filter((option) => option.value <= limit).map((option) => ( - - {option.label} - - ))} + {selectOptions + .filter((option) => option.value <= limit) + .map((option) => ( + + {option.label} + + ))} @@ -82,7 +73,7 @@ export const ChecksConfig = ({ const { toast } = useToast(); - const isAdminOrDeveloper = checkUserAccess({ rolesToBe: ["organization-admin", "organization-developer"] }); + const isAdminOrDeveloper = checkUserAccess({ rolesToBe: ['organization-admin', 'organization-developer'] }); return (
    @@ -99,12 +90,12 @@ export const ChecksConfig = ({ disabled={!isAdminOrDeveloper} onClick={() => { mutate( - { namespace, timeframeInDays, }, + { namespace, timeframeInDays }, { onSuccess: (d) => { if (d.response?.code === EnumStatusCode.OK) { toast({ - description: "Schema checks updated successfully.", + description: 'Schema checks updated successfully.', duration: 3000, }); } else if (d.response?.details) { @@ -113,12 +104,11 @@ export const ChecksConfig = ({ }, onError: () => { toast({ - description: - "Could not update the schema checks. Please try again.", - duration: 3000 + description: 'Could not update the schema checks. Please try again.', + duration: 3000, }); }, - } + }, ); }} > @@ -128,14 +118,10 @@ export const ChecksConfig = ({ -
    +
    - + Check days to consider diff --git a/studio/src/components/checks/checks-filter-menu.tsx b/studio/src/components/checks/checks-filter-menu.tsx index 6bb910ba76..09bf547bd3 100644 --- a/studio/src/components/checks/checks-filter-menu.tsx +++ b/studio/src/components/checks/checks-filter-menu.tsx @@ -1,17 +1,17 @@ -import { useContext } from "react"; -import { useApplyParams } from "@/components/analytics/use-apply-params"; -import { DataTablePrimaryFilterMenu } from "@/components/analytics/data-table-primary-filter-menu"; -import { GraphContext } from "@/components/layout/graph-layout"; -import { useRouter } from "next/router"; -import { SelectedChecksFilters } from "./selected-checks-filters"; +import { useContext } from 'react'; +import { useApplyParams } from '@/components/analytics/use-apply-params'; +import { DataTablePrimaryFilterMenu } from '@/components/analytics/data-table-primary-filter-menu'; +import { GraphContext } from '@/components/layout/graph-layout'; +import { useRouter } from 'next/router'; +import { SelectedChecksFilters } from './selected-checks-filters'; export const parseSelectedSubgraphs = (value: unknown) => { - if (typeof value === "string") { - return value.split(",").filter(Boolean); + if (typeof value === 'string') { + return value.split(',').filter(Boolean); } return []; -} +}; export function ChecksFilterMenu() { const router = useRouter(); @@ -26,17 +26,17 @@ export function ChecksFilterMenu() { { applyParams({ subgraphs: selected?.join(',') ?? null }); }, options: subgraphs.map((sg) => ({ label: sg.name, - value: sg.id + value: sg.id, })), - } + }, ]} /> @@ -47,4 +47,4 @@ export function ChecksFilterMenu() {
    ); -} \ No newline at end of file +} diff --git a/studio/src/components/checks/composed-schema-changes-table.tsx b/studio/src/components/checks/composed-schema-changes-table.tsx index 40820dbd30..26cf198d62 100644 --- a/studio/src/components/checks/composed-schema-changes-table.tsx +++ b/studio/src/components/checks/composed-schema-changes-table.tsx @@ -1,29 +1,15 @@ -import { cn } from "@/lib/utils"; -import { - BarChartIcon, - CheckIcon, - Cross1Icon, - GlobeIcon, -} from "@radix-ui/react-icons"; -import { FederatedGraphSchemaChange } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { Badge } from "../ui/badge"; -import { Button } from "../ui/button"; -import { - Table, - TableBody, - TableCaption, - TableCell, - TableHead, - TableHeader, - TableRow, - TableWrapper, -} from "../ui/table"; -import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useCurrentOrganization } from "@/hooks/use-current-organization"; -import { useOpenUsage } from "./use-open-usage"; +import { cn } from '@/lib/utils'; +import { BarChartIcon, CheckIcon, Cross1Icon, GlobeIcon } from '@radix-ui/react-icons'; +import { FederatedGraphSchemaChange } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { Badge } from '../ui/badge'; +import { Button } from '../ui/button'; +import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, TableWrapper } from '../ui/table'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useCurrentOrganization } from '@/hooks/use-current-organization'; +import { useOpenUsage } from './use-open-usage'; export const ComposedSchemaChangesTable = ({ changes, @@ -90,11 +76,7 @@ const Row = ({ return ( - +
    {isBreaking ? : } @@ -108,12 +90,7 @@ const Row = ({ {path ? ( - } /> ); @@ -125,12 +117,7 @@ export const OperationContentDialog = ({ Operation Content - + ); diff --git a/studio/src/components/checks/operations.tsx b/studio/src/components/checks/operations.tsx index fc07d939a4..298adfa2c9 100644 --- a/studio/src/components/checks/operations.tsx +++ b/studio/src/components/checks/operations.tsx @@ -1,41 +1,26 @@ -import { FieldUsageSheet } from "@/components/analytics/field-usage"; -import { ChangesTable } from "@/components/checks/changes-table"; -import { EmptyState } from "@/components/empty-state"; -import { GraphContext } from "@/components/layout/graph-layout"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { FieldUsageSheet } from '@/components/analytics/field-usage'; +import { ChangesTable } from '@/components/checks/changes-table'; +import { EmptyState } from '@/components/empty-state'; +import { GraphContext } from '@/components/layout/graph-layout'; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { Loader } from "@/components/ui/loader"; -import { useToast } from "@/components/ui/use-toast"; -import { formatDateTime } from "@/lib/format-date"; -import { cn } from "@/lib/utils"; -import { useMutation, useQuery } from "@connectrpc/connect-query"; -import { - CheckCircleIcon, - ExclamationTriangleIcon, - NoSymbolIcon, -} from "@heroicons/react/24/outline"; -import { - ChevronDownIcon, - Cross1Icon, - InfoCircledIcon, - MagnifyingGlassIcon, - Share1Icon, -} from "@radix-ui/react-icons"; -import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; +} from '@/components/ui/dropdown-menu'; +import { Input } from '@/components/ui/input'; +import { Loader } from '@/components/ui/loader'; +import { useToast } from '@/components/ui/use-toast'; +import { formatDateTime } from '@/lib/format-date'; +import { cn } from '@/lib/utils'; +import { useMutation, useQuery } from '@connectrpc/connect-query'; +import { CheckCircleIcon, ExclamationTriangleIcon, NoSymbolIcon } from '@heroicons/react/24/outline'; +import { ChevronDownIcon, Cross1Icon, InfoCircledIcon, MagnifyingGlassIcon, Share1Icon } from '@radix-ui/react-icons'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; import { createIgnoreOverridesForAllOperations, createOperationIgnoreAllOverride, @@ -44,25 +29,23 @@ import { removeOperationIgnoreAllOverride, removeOperationOverrides, toggleChangeOverridesForAllOperations, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; -import copy from "copy-to-clipboard"; -import Fuse from "fuse.js"; -import { useRouter } from "next/router"; -import { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { useApplyParams } from "../analytics/use-apply-params"; -import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; -import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; -import { OperationContentDialog } from "./operation-content"; -import { Pagination } from "../ui/pagination"; -import { useDebounce } from "use-debounce"; -import { Switch } from "../ui/switch"; -import { Label } from "../ui/label"; +} from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; +import copy from 'copy-to-clipboard'; +import Fuse from 'fuse.js'; +import { useRouter } from 'next/router'; +import { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { useApplyParams } from '../analytics/use-apply-params'; +import { Alert, AlertDescription, AlertTitle } from '../ui/alert'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; +import { OperationContentDialog } from './operation-content'; +import { Pagination } from '../ui/pagination'; +import { useDebounce } from 'use-debounce'; +import { Switch } from '../ui/switch'; +import { Label } from '../ui/label'; const CopyableOperationHash = ({ hash }: { hash: string }) => { const [copied, setCopied] = useState(false); - const [tooltipOpen, setTooltipOpen] = useState( - undefined, - ); + const [tooltipOpen, setTooltipOpen] = useState(undefined); const timeoutRef = useRef(null); const secondTimeoutRef = useRef(null); @@ -120,7 +103,7 @@ const CopyableOperationHash = ({ hash }: { hash: string }) => { -

    {copied ? "Copied!" : "Copy Operation Hash"}

    +

    {copied ? 'Copied!' : 'Copy Operation Hash'}

    ); @@ -130,10 +113,8 @@ export const CheckOperations = () => { const graphContext = useContext(GraphContext); const router = useRouter(); const { toast } = useToast(); - const pageNumber = router.query.page - ? parseInt(router.query.page as string) - : 1; - const limit = Number.parseInt((router.query.pageSize as string) || "10"); + const pageNumber = router.query.page ? parseInt(router.query.page as string) : 1; + const limit = Number.parseInt((router.query.pageSize as string) || '10'); const id = router.query.checkId as string; @@ -156,174 +137,145 @@ export const CheckOperations = () => { }, ); - const { mutate: createOverrides, isPending: creatingOverrides } = useMutation( - createOperationOverrides, - { - onSuccess: (d) => { - if (d.response?.code === EnumStatusCode.OK) { - refetch(); - } else { - toast({ - description: - d.response?.details ?? - "Could not update overrides. Please try again.", - duration: 3000, - }); - } - }, - onError: () => { + const { mutate: createOverrides, isPending: creatingOverrides } = useMutation(createOperationOverrides, { + onSuccess: (d) => { + if (d.response?.code === EnumStatusCode.OK) { + refetch(); + } else { toast({ - description: "Could not update overrides. Please try again.", + description: d.response?.details ?? 'Could not update overrides. Please try again.', duration: 3000, }); - }, + } }, - ); + onError: () => { + toast({ + description: 'Could not update overrides. Please try again.', + duration: 3000, + }); + }, + }); - const { mutate: removeIgnoreAll, isPending: removing } = useMutation( - removeOperationIgnoreAllOverride, - { - onSuccess: (d) => { - if (d.response?.code === EnumStatusCode.OK) { - refetch(); - } else { - toast({ - description: - d.response?.details ?? - "Could not remove ignore all override. Please try again.", - duration: 3000, - }); - } - }, - onError: () => { + const { mutate: removeIgnoreAll, isPending: removing } = useMutation(removeOperationIgnoreAllOverride, { + onSuccess: (d) => { + if (d.response?.code === EnumStatusCode.OK) { + refetch(); + } else { toast({ - description: - "Could not remove ignore all override. Please try again.", + description: d.response?.details ?? 'Could not remove ignore all override. Please try again.', duration: 3000, }); - }, + } }, - ); + onError: () => { + toast({ + description: 'Could not remove ignore all override. Please try again.', + duration: 3000, + }); + }, + }); - const { mutate: createIgnoreAll, isPending: ignoring } = useMutation( - createOperationIgnoreAllOverride, + const { mutate: createIgnoreAll, isPending: ignoring } = useMutation(createOperationIgnoreAllOverride, { + onSuccess: (d) => { + if (d.response?.code === EnumStatusCode.OK) { + refetch(); + } else { + toast({ + description: d.response?.details ?? 'Could not create ignore all override. Please try again.', + duration: 3000, + }); + } + }, + onError: () => { + toast({ + description: 'Could not create ignore all override. Please try again.', + duration: 3000, + }); + }, + }); + + const { mutate: removeOverrides, isPending: removingOverrides } = useMutation(removeOperationOverrides, { + onSuccess: (d) => { + if (d.response?.code === EnumStatusCode.OK) { + refetch(); + } else { + toast({ + description: d.response?.details ?? 'Could not remove override. Please try again.', + duration: 3000, + }); + } + }, + onError: () => { + toast({ + description: 'Could not remove override. Please try again.', + duration: 3000, + }); + }, + }); + + const { mutate: toggleGlobalChangeOverrides, isPending: togglingGlobalChangeOverrides } = useMutation( + toggleChangeOverridesForAllOperations, { onSuccess: (d) => { if (d.response?.code === EnumStatusCode.OK) { + toast({ + description: 'All overrides have been toggled successfully', + }); refetch(); } else { toast({ - description: - d.response?.details ?? - "Could not create ignore all override. Please try again.", + description: d.response?.details ?? 'Could not toggle overrides. Please try again.', duration: 3000, }); } }, onError: () => { toast({ - description: - "Could not create ignore all override. Please try again.", + description: 'Could not toggle override. Please try again.', duration: 3000, }); }, }, ); - const { mutate: removeOverrides, isPending: removingOverrides } = useMutation( - removeOperationOverrides, + const { mutate: createGlobalIgnoreOverrides, isPending: creatingGlobalIgnoreOverrides } = useMutation( + createIgnoreOverridesForAllOperations, { onSuccess: (d) => { if (d.response?.code === EnumStatusCode.OK) { + toast({ + description: 'All listed operations will now be ignored for future checks', + }); refetch(); } else { toast({ - description: - d.response?.details ?? - "Could not remove override. Please try again.", + description: d.response?.details ?? 'Could not toggle overrides. Please try again.', duration: 3000, }); } }, onError: () => { toast({ - description: "Could not remove override. Please try again.", + description: 'Could not toggle override. Please try again.', duration: 3000, }); }, }, ); - const { - mutate: toggleGlobalChangeOverrides, - isPending: togglingGlobalChangeOverrides, - } = useMutation(toggleChangeOverridesForAllOperations, { - onSuccess: (d) => { - if (d.response?.code === EnumStatusCode.OK) { - toast({ - description: "All overrides have been toggled successfully", - }); - refetch(); - } else { - toast({ - description: - d.response?.details ?? - "Could not toggle overrides. Please try again.", - duration: 3000, - }); - } - }, - onError: () => { - toast({ - description: "Could not toggle override. Please try again.", - duration: 3000, - }); - }, - }); - - const { - mutate: createGlobalIgnoreOverrides, - isPending: creatingGlobalIgnoreOverrides, - } = useMutation(createIgnoreOverridesForAllOperations, { - onSuccess: (d) => { - if (d.response?.code === EnumStatusCode.OK) { - toast({ - description: - "All listed operations will now be ignored for future checks", - }); - refetch(); - } else { - toast({ - description: - d.response?.details ?? - "Could not toggle overrides. Please try again.", - duration: 3000, - }); - } - }, - onError: () => { - toast({ - description: "Could not toggle override. Please try again.", - duration: 3000, - }); - }, - }); - const applyParams = useApplyParams(); const copyLink = (hash: string) => { - const [base, _] = window.location.href.split("?"); + const [base, _] = window.location.href.split('?'); const link = base + `?search=${hash.slice(0, 6)}`; copy(link); - toast({ description: "Copied link to clipboard" }); + toast({ description: 'Copied link to clipboard' }); }; const operations = data?.operations || []; - const doAllOperationsHaveIgnoreAllOverride = - data?.doAllOperationsHaveIgnoreAllOverride; - const doAllOperationsHaveAllTheirChangesMarkedSafe = - data?.doAllOperationsHaveAllTheirChangesMarkedSafe; + const doAllOperationsHaveIgnoreAllOverride = data?.doAllOperationsHaveIgnoreAllOverride; + const doAllOperationsHaveAllTheirChangesMarkedSafe = data?.doAllOperationsHaveAllTheirChangesMarkedSafe; if (isLoading) return ; @@ -332,9 +284,7 @@ export const CheckOperations = () => { } title="Could not retrieve affected operations" - description={ - data?.response?.details || error?.message || "Please try again" - } + description={data?.response?.details || error?.message || 'Please try again'} actions={} /> ); @@ -380,7 +330,7 @@ export const CheckOperations = () => { variant="ghost" className="absolute bottom-0 right-0 top-0 my-auto rounded-l-none" onClick={() => { - setSearch(""); + setSearch(''); applyParams({ search: null }); }} > @@ -409,21 +359,16 @@ export const CheckOperations = () => { }} className="cursor-pointer flex-col items-start gap-1" > - {doAllOperationsHaveAllTheirChangesMarkedSafe - ? "Remove all changes overrides" - : "Ignore all changes"} + {doAllOperationsHaveAllTheirChangesMarkedSafe ? 'Remove all changes overrides' : 'Ignore all changes'}

    {doAllOperationsHaveAllTheirChangesMarkedSafe - ? "Future checks will fail if any of these changes are breaking." - : "Toggle overrides on so future checks will not treat the listed changes as breaking."} + ? 'Future checks will fail if any of these changes are breaking.' + : 'Toggle overrides on so future checks will not treat the listed changes as breaking.'}

    { createGlobalIgnoreOverrides({ checkId: id, @@ -440,20 +385,14 @@ export const CheckOperations = () => {

    {doAllOperationsHaveIgnoreAllOverride && (

    - All listed operations - are already ignored + All listed operations are already ignored

    )}
    - - + +
    @@ -465,19 +404,8 @@ export const CheckOperations = () => { className="scrollbar-custom mt-4 max-h-[calc(100%_-_96px)] w-full overflow-auto" > {operations.map( - ({ - hash, - name, - type, - firstSeenAt, - lastSeenAt, - impactingChanges, - hasIgnoreAllOverride, - isSafe, - }) => { - const doAllChangesHaveOverrides = !impactingChanges.some( - (c) => !c.hasOverride, - ); + ({ hash, name, type, firstSeenAt, lastSeenAt, impactingChanges, hasIgnoreAllOverride, isSafe }) => { + const doAllChangesHaveOverrides = !impactingChanges.some((c) => !c.hasOverride); const firstSeenFormatted = formatDateTime(new Date(firstSeenAt)); const lastSeenAtFormatted = formatDateTime(new Date(lastSeenAt)); @@ -489,24 +417,18 @@ export const CheckOperations = () => {

    - {name || "unnamed operation"} + {name || 'unnamed operation'}

    - + {type} {isSafe && ( - + ignored in this check )} @@ -523,16 +445,12 @@ export const CheckOperations = () => {
    - @@ -548,36 +466,30 @@ export const CheckOperations = () => { { doAllChangesHaveOverrides ? removeOverrides({ - graphName: graphContext?.graph?.name, - namespace: - graphContext?.graph?.namespace, - operationHash: hash, - changes: impactingChanges, - }) + graphName: graphContext?.graph?.name, + namespace: graphContext?.graph?.namespace, + operationHash: hash, + changes: impactingChanges, + }) : createOverrides({ - graphName: graphContext?.graph?.name, - namespace: - graphContext?.graph?.namespace, - operationHash: hash, - operationName: name, - changes: impactingChanges, - }); + graphName: graphContext?.graph?.name, + namespace: graphContext?.graph?.namespace, + operationHash: hash, + operationName: name, + changes: impactingChanges, + }); }} className="cursor-pointer flex-col items-start gap-1" > - {doAllChangesHaveOverrides - ? "Remove changes overrides" - : "Ignore changes"} + {doAllChangesHaveOverrides ? 'Remove changes overrides' : 'Ignore changes'}

    {doAllChangesHaveOverrides - ? "Disable overrides so future checks will fail on breaking listed changes." - : "Enable overrides so future checks will not fail on breaking listed changes."} + ? 'Disable overrides so future checks will fail on breaking listed changes.' + : 'Enable overrides so future checks will not fail on breaking listed changes.'}

    @@ -605,12 +517,10 @@ export const CheckOperations = () => {
    {hasIgnoreAllOverride && ( - - Operation ignored in future checks - + Operation ignored in future checks - This operation will be excluded from future checks. - Remove the override to include it again and manage individual change overrides. + This operation will be excluded from future checks. Remove the override to include it again + and manage individual change overrides. - {path - ? "Open in Explorer" - : "Cannot open in explorer. Path to type unavailable"} + {path ? 'Open in Explorer' : 'Cannot open in explorer. Path to type unavailable'} {isAdminOrDeveloper && ( - @@ -172,8 +135,7 @@ const Override = ({ Are you sure? - Future checks will fail if this breaking change is detected - for this operation. + Future checks will fail if this breaking change is detected for this operation. @@ -209,7 +171,7 @@ const Override = ({ export const ConfigureOverride = () => { const graphContext = useContext(GraphContext); const checkUserAccess = useCheckUserAccess(); - const isAdminOrDeveloper = checkUserAccess({ rolesToBe: ['organization-admin', 'organization-developer'] }) + const isAdminOrDeveloper = checkUserAccess({ rolesToBe: ['organization-admin', 'organization-developer'] }); const router = useRouter(); const operationHash = router.query.override as string; @@ -242,57 +204,45 @@ export const ConfigureOverride = () => { }); }; - const { mutate: removeIgnoreAll, isPending: removing } = useMutation( - removeOperationIgnoreAllOverride, - { - onSuccess: (d) => { - if (d.response?.code === EnumStatusCode.OK) { - refetch(); - invalidateOverrides(); - } else { - toast({ - description: - d.response?.details ?? - "Could not remove ignore all override. Please try again.", - duration: 3000, - }); - } - }, - onError: () => { + const { mutate: removeIgnoreAll, isPending: removing } = useMutation(removeOperationIgnoreAllOverride, { + onSuccess: (d) => { + if (d.response?.code === EnumStatusCode.OK) { + refetch(); + invalidateOverrides(); + } else { toast({ - description: - "Could not remove ignore all override. Please try again.", + description: d.response?.details ?? 'Could not remove ignore all override. Please try again.', duration: 3000, }); - }, + } }, - ); + onError: () => { + toast({ + description: 'Could not remove ignore all override. Please try again.', + duration: 3000, + }); + }, + }); - const { mutate: createIgnoreAll, isPending: ignoring } = useMutation( - createOperationIgnoreAllOverride, - { - onSuccess: (d) => { - if (d.response?.code === EnumStatusCode.OK) { - refetch(); - invalidateOverrides(); - } else { - toast({ - description: - d.response?.details ?? - "Could not create ignore all override. Please try again.", - duration: 3000, - }); - } - }, - onError: () => { + const { mutate: createIgnoreAll, isPending: ignoring } = useMutation(createOperationIgnoreAllOverride, { + onSuccess: (d) => { + if (d.response?.code === EnumStatusCode.OK) { + refetch(); + invalidateOverrides(); + } else { toast({ - description: - "Could not create ignore all override. Please try again.", + description: d.response?.details ?? 'Could not create ignore all override. Please try again.', duration: 3000, }); - }, + } }, - ); + onError: () => { + toast({ + description: 'Could not create ignore all override. Please try again.', + duration: 3000, + }); + }, + }); let content; @@ -303,9 +253,7 @@ export const ConfigureOverride = () => { } title="Could not retrieve overrides" - description={ - data?.response?.details || error?.message || "Please try again" - } + description={data?.response?.details || error?.message || 'Please try again'} actions={} /> ); @@ -317,9 +265,7 @@ export const ConfigureOverride = () => {
    -

    - This operation will not be used in future checks. -

    +

    This operation will not be used in future checks.

    { onCheckedChange={() => data.ignoreAll ? removeIgnoreAll({ - graphName: graphContext?.graph?.name, - namespace: graphContext?.graph?.namespace, - operationHash, - }) + graphName: graphContext?.graph?.name, + namespace: graphContext?.graph?.namespace, + operationHash, + }) : createIgnoreAll({ - operationHash, - operationName, - graphName: graphContext?.graph?.name, - namespace: graphContext?.graph?.namespace, - }) + operationHash, + operationName, + graphName: graphContext?.graph?.name, + namespace: graphContext?.graph?.namespace, + }) } />
    @@ -350,18 +296,11 @@ export const ConfigureOverride = () => { {data.ignoreAll && (
    - } - title="Operation ignored" - /> + } title="Operation ignored" />
    )} {data.changes.length === 0 ? ( - } - title="No overrides found" - /> + } title="No overrides found" /> ) : (
    @@ -409,17 +348,17 @@ export const ConfigureOverride = () => { - Overrides for{" "} + Overrides for{' '} - {operationName || "unnamed operation"} + {operationName || 'unnamed operation'} - Configure override for the operation with hash {operationHash}{" "} + Configure override for the operation with hash {operationHash}{' '}
    } - federatedGraphName={graphContext?.graph?.name ?? ""} - namespace={graphContext?.graph?.namespace ?? ""} + federatedGraphName={graphContext?.graph?.name ?? ''} + namespace={graphContext?.graph?.namespace ?? ''} />
    diff --git a/studio/src/components/checks/proposal-matches-table.tsx b/studio/src/components/checks/proposal-matches-table.tsx index 34d6bde6cb..4a6a4c2fb4 100644 --- a/studio/src/components/checks/proposal-matches-table.tsx +++ b/studio/src/components/checks/proposal-matches-table.tsx @@ -1,5 +1,5 @@ -import { EmptyState } from "@/components/empty-state"; -import { Button } from "@/components/ui/button"; +import { EmptyState } from '@/components/empty-state'; +import { Button } from '@/components/ui/button'; import { Table, TableBody, @@ -9,18 +9,14 @@ import { TableHeader, TableRow, TableWrapper, -} from "@/components/ui/table"; -import { useUser } from "@/hooks/use-user"; -import { - CheckCircleIcon, - ExclamationCircleIcon, - NoSymbolIcon, -} from "@heroicons/react/24/outline"; -import { CrossCircledIcon } from "@radix-ui/react-icons"; -import { GetCheckSummaryResponse_ProposalSchemaMatch } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { useRouter } from "next/router"; -import React, { useContext } from "react"; -import { GraphContext } from "../layout/graph-layout"; +} from '@/components/ui/table'; +import { useUser } from '@/hooks/use-user'; +import { CheckCircleIcon, ExclamationCircleIcon, NoSymbolIcon } from '@heroicons/react/24/outline'; +import { CrossCircledIcon } from '@radix-ui/react-icons'; +import { GetCheckSummaryResponse_ProposalSchemaMatch } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { useRouter } from 'next/router'; +import React, { useContext } from 'react'; +import { GraphContext } from '../layout/graph-layout'; export const ProposalMatchesTable = ({ proposalMatches, @@ -68,7 +64,7 @@ export const ProposalMatchesTable = ({ ); } - if (proposalMatch === "error" && proposalMatches.length === 0) { + if (proposalMatch === 'error' && proposalMatches.length === 0) { return ( } @@ -78,7 +74,7 @@ export const ProposalMatchesTable = ({ ); } - if (proposalMatch === "warn" && proposalMatches.length === 0) { + if (proposalMatch === 'warn' && proposalMatches.length === 0) { return ( } @@ -109,7 +105,7 @@ export const ProposalMatchesTable = ({ ) : ( - {proposalMatch === "error" ? ( + {proposalMatch === 'error' ? ( ) : ( diff --git a/studio/src/components/checks/selected-checks-filters.tsx b/studio/src/components/checks/selected-checks-filters.tsx index f80735bf6b..765e0974e8 100644 --- a/studio/src/components/checks/selected-checks-filters.tsx +++ b/studio/src/components/checks/selected-checks-filters.tsx @@ -1,17 +1,11 @@ -import React, { useContext } from "react"; -import { GraphContext } from "@/components/layout/graph-layout"; -import { useApplyParams } from "@/components/analytics/use-apply-params"; -import { Button } from "@/components/ui/button"; -import { Cross2Icon } from "@radix-ui/react-icons"; -import { DataTableFacetedFilter } from "@/components/analytics/data-table-faceted-filter"; +import React, { useContext } from 'react'; +import { GraphContext } from '@/components/layout/graph-layout'; +import { useApplyParams } from '@/components/analytics/use-apply-params'; +import { Button } from '@/components/ui/button'; +import { Cross2Icon } from '@radix-ui/react-icons'; +import { DataTableFacetedFilter } from '@/components/analytics/data-table-faceted-filter'; -export function SelectedChecksFilters({ - selectedSubgraphs -} : - { - selectedSubgraphs: string[]; - } -) { +export function SelectedChecksFilters({ selectedSubgraphs }: { selectedSubgraphs: string[] }) { const applyParams = useApplyParams(); const { subgraphs = [] } = useContext(GraphContext) ?? {}; @@ -30,15 +24,17 @@ export function SelectedChecksFilters({ subgraphs.find((sg) => sg.id === id)!) - .filter(Boolean) - .map((sg) => JSON.stringify({ label: sg.name, value: sg.id })) - } + selectedOptions={selectedSubgraphs + .map((id) => subgraphs.find((sg) => sg.id === id)!) + .filter(Boolean) + .map((sg) => JSON.stringify({ label: sg.name, value: sg.id }))} onSelect={(value) => { applyParams({ - subgraphs: value?.map(JSON.parse).map((sg: { value: string; }) => sg.value).join(',') ?? null, + subgraphs: + value + ?.map(JSON.parse) + .map((sg: { value: string }) => sg.value) + .join(',') ?? null, }); }} options={subgraphOptions} @@ -54,4 +50,4 @@ export function SelectedChecksFilters({ ); -} \ No newline at end of file +} diff --git a/studio/src/components/checks/subgraph-check-extension.tsx b/studio/src/components/checks/subgraph-check-extension.tsx index 23110f2ac7..5b015db067 100644 --- a/studio/src/components/checks/subgraph-check-extension.tsx +++ b/studio/src/components/checks/subgraph-check-extension.tsx @@ -1,13 +1,13 @@ -import { EmptyState } from "@/components/empty-state"; -import { CheckCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline"; -import { Button } from "@/components/ui/button"; -import { useRouter } from "next/router"; -import { useUser } from "@/hooks/use-user"; -import React, { useContext, useState } from "react"; -import { GraphContext } from "@/components/layout/graph-layout"; -import { WebhookDeliveryDetails } from "@/components/webhook-delivery-details"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { CrossCircledIcon } from "@radix-ui/react-icons"; +import { EmptyState } from '@/components/empty-state'; +import { CheckCircleIcon, NoSymbolIcon } from '@heroicons/react/24/outline'; +import { Button } from '@/components/ui/button'; +import { useRouter } from 'next/router'; +import { useUser } from '@/hooks/use-user'; +import React, { useContext, useState } from 'react'; +import { GraphContext } from '@/components/layout/graph-layout'; +import { WebhookDeliveryDetails } from '@/components/webhook-delivery-details'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { CrossCircledIcon } from '@radix-ui/react-icons'; export interface SubgraphCheckExtensionProps { enabled: boolean; @@ -24,18 +24,18 @@ export function SubgraphCheckExtension({ enabled, deliveryId, errorMessage }: Su if (!deliveryId) { return ( } + icon={} title="Subgraph Check Extension Skipped" description={ !enabled - ? "Subgraph check extension was skipped for this run." - : "Subgraph check extension is not configured for this namespace. Enable it to execute subgraph check extensions in your schema." + ? 'Subgraph check extension was skipped for this run.' + : 'Subgraph check extension is not configured for this namespace. Enable it to execute subgraph check extensions in your schema.' } actions={

    )} - ) : ( + ) : ( } title="Subgraph Check Extension Successful" description="The subgraph check extension completed successfully." - actions={deliveryId && ()} + actions={ + deliveryId && + } /> )} @@ -87,4 +85,4 @@ export function SubgraphCheckExtension({ enabled, deliveryId, errorMessage }: Su /> ); -} \ No newline at end of file +} diff --git a/studio/src/components/clients/delete-persisted-operation-dialog.tsx b/studio/src/components/clients/delete-persisted-operation-dialog.tsx index 4524a11b2f..2278229064 100644 --- a/studio/src/components/clients/delete-persisted-operation-dialog.tsx +++ b/studio/src/components/clients/delete-persisted-operation-dialog.tsx @@ -1,18 +1,9 @@ -import type { SyntheticEvent } from "react"; -import { Link } from "@/components/ui/link"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Alert } from "@/components/ui/alert"; +import type { SyntheticEvent } from 'react'; +import { Link } from '@/components/ui/link'; +import { Button } from '@/components/ui/button'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; +import { Alert } from '@/components/ui/alert'; export const DeletePersistedOperationDialog = ({ isOpen, @@ -30,7 +21,7 @@ export const DeletePersistedOperationDialog = ({ onClose?: () => void; }) => { const isPlural = operationNames.length > 1; - const pluralizedOperation = isPlural ? "operations" : "operation"; + const pluralizedOperation = isPlural ? 'operations' : 'operation'; return ( {operationHasTraffic ? ( - The {pluralizedOperation} {isPlural ? "are" : "is"}{" "} - receiving traffic. Visit{" "} + The {pluralizedOperation} {isPlural ? 'are' : 'is'} receiving traffic + . Visit{' '} metrics - {" "} + {' '} to learn more. ) : ( - If you are not sending us analytics, we{" "} - cannot guarantee that that - existing clients won't break. If you are not sure, check the{" "} + If you are not sending us analytics, we cannot guarantee that that + existing clients won't break. If you are not sure, check the{' '} metrics @@ -68,22 +58,14 @@ export const DeletePersistedOperationDialog = ({

    - Are you sure you want to{" "} - - delete the following {pluralizedOperation} - + Are you sure you want to delete the following {pluralizedOperation} ?

    - @@ -94,14 +76,10 @@ export const DeletePersistedOperationDialog = ({ const OperationLabel = ({ names }: { names: string[] }) => ( - - {names.length > 4 ? names.slice(0, 4).join("\n") : names.join("\n")} + + {names.length > 4 ? names.slice(0, 4).join('\n') : names.join('\n')} - {names.join(",")} + {names.join(',')} ); diff --git a/studio/src/components/code-viewer.tsx b/studio/src/components/code-viewer.tsx index 6559b5e2d4..40d0db36ca 100644 --- a/studio/src/components/code-viewer.tsx +++ b/studio/src/components/code-viewer.tsx @@ -1,50 +1,46 @@ -import { useResolvedTheme } from "@/hooks/use-resolved-theme"; -import { downloadStringAsFile } from "@/lib/download-string-as-file"; -import { cn } from "@/lib/utils"; -import { ClipboardCopyIcon, DownloadIcon } from "@radix-ui/react-icons"; -import copy from "copy-to-clipboard"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { Highlight, themes } from "prism-react-renderer"; -import { Button } from "./ui/button"; -import { useToast } from "./ui/use-toast"; -import { useEffect, useState } from "react"; -import graphQLPlugin from "prettier/plugins/graphql"; -import babelPlugin from "prettier/plugins/babel"; -import estreePlugin from "prettier/plugins/estree"; -import * as prettier from "prettier/standalone"; -import * as Prism from "prismjs"; -import "prismjs/components/prism-json"; -import "prismjs/components/prism-graphql"; +import { useResolvedTheme } from '@/hooks/use-resolved-theme'; +import { downloadStringAsFile } from '@/lib/download-string-as-file'; +import { cn } from '@/lib/utils'; +import { ClipboardCopyIcon, DownloadIcon } from '@radix-ui/react-icons'; +import copy from 'copy-to-clipboard'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { Highlight, themes } from 'prism-react-renderer'; +import { Button } from './ui/button'; +import { useToast } from './ui/use-toast'; +import { useEffect, useState } from 'react'; +import graphQLPlugin from 'prettier/plugins/graphql'; +import babelPlugin from 'prettier/plugins/babel'; +import estreePlugin from 'prettier/plugins/estree'; +import * as prettier from 'prettier/standalone'; +import * as Prism from 'prismjs'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-graphql'; export const CodeViewerActions = ({ code, subgraphName, className, - variant = "secondary", - size = "default", - extension = "graphql", + variant = 'secondary', + size = 'default', + extension = 'graphql', }: { code: string; subgraphName: string; className?: string; variant?: any; size?: any; - extension?: "graphql" | "json"; + extension?: 'graphql' | 'json'; }) => { const { toast, dismiss } = useToast(); const downloadSDL = () => { - downloadStringAsFile( - code, - `${subgraphName}.${extension}`, - `application/${extension}`, - ); + downloadStringAsFile(code, `${subgraphName}.${extension}`, `application/${extension}`); }; const copySDL = () => { copy(code); - const { id } = toast({ description: "Copied contents to clipboard" }); + const { id } = toast({ description: 'Copied contents to clipboard' }); const t = setTimeout(() => { dismiss(id); @@ -54,24 +50,12 @@ export const CodeViewerActions = ({ }; return ( -
    - - @@ -84,19 +68,19 @@ export const CodeViewer = ({ disableLinking, className, prettyPrint = true, - language = "graphql", + language = 'graphql', }: { code: string; disableLinking?: boolean; className?: string; prettyPrint?: boolean; - language?: "graphql" | "json"; + language?: 'graphql' | 'json'; }) => { const router = useRouter(); - const pathname = router.asPath.split("#")[0]; - const hash = router.asPath.split("#")?.[1]; + const pathname = router.asPath.split('#')[0]; + const hash = router.asPath.split('#')?.[1]; - const [content, setContent] = useState(""); + const [content, setContent] = useState(''); useEffect(() => { const set = async (source: string) => { @@ -123,41 +107,38 @@ export const CodeViewer = ({ return ( {({ style, tokens, getLineProps, getTokenProps }) => ( -
    +        
               {tokens.map((line, i, allLines) => {
                 const numberSectionWidth =
                   allLines.length > 10
                     ? allLines.length > 100
                       ? allLines.length > 1000
    -                    ? "w-18"
    -                    : "w-16"
    -                  : "w-12"
    -                : "w-8";
    +                    ? 'w-18'
    +                    : 'w-16'
    +                  : 'w-12'
    +                : 'w-8';
     
                 const lineNo = `L${i + 1}`;
     
    -            const href = disableLinking ? "#" : pathname + `#${lineNo}`;
    +            const href = disableLinking ? '#' : pathname + `#${lineNo}`;
     
                 return (
                   
    diff --git a/studio/src/components/compose-status-bulb.tsx b/studio/src/components/compose-status-bulb.tsx index ecd877503b..d416a975de 100644 --- a/studio/src/components/compose-status-bulb.tsx +++ b/studio/src/components/compose-status-bulb.tsx @@ -1,21 +1,11 @@ -import clsx from "clsx"; +import clsx from 'clsx'; -export const ComposeStatusBulb = ({ - validGraph, - emptyGraph, -}: { - validGraph: boolean; - emptyGraph: boolean; -}) => { +export const ComposeStatusBulb = ({ validGraph, emptyGraph }: { validGraph: boolean; emptyGraph: boolean }) => { return (
    ); diff --git a/studio/src/components/compose-status.tsx b/studio/src/components/compose-status.tsx index 115e893871..806d05cb3b 100644 --- a/studio/src/components/compose-status.tsx +++ b/studio/src/components/compose-status.tsx @@ -1,16 +1,6 @@ -import { - BoltIcon, - BoltSlashIcon, - CircleStackIcon, -} from "@heroicons/react/24/outline"; +import { BoltIcon, BoltSlashIcon, CircleStackIcon } from '@heroicons/react/24/outline'; -export const ComposeStatus = ({ - validGraph, - emptyGraph, -}: { - validGraph: boolean; - emptyGraph: boolean; -}) => { +export const ComposeStatus = ({ validGraph, emptyGraph }: { validGraph: boolean; emptyGraph: boolean }) => { if (emptyGraph) { return ( @@ -60,9 +50,7 @@ export const ComposeStatusMessage = ({ return ( -
    - This version of the graph is not ready because the composition failed. -
    +
    This version of the graph is not ready because the composition failed.
    ); } @@ -70,8 +58,8 @@ export const ComposeStatusMessage = ({ if (isContract) { return ( - No valid schema found in source graph. Once a schema is composed by the - source graph, this contract will be updated automatically. + No valid schema found in source graph. Once a schema is composed by the source graph, this contract will be + updated automatically. ); } diff --git a/studio/src/components/composition-errors-banner.tsx b/studio/src/components/composition-errors-banner.tsx index 7c23275e1c..360becee3f 100644 --- a/studio/src/components/composition-errors-banner.tsx +++ b/studio/src/components/composition-errors-banner.tsx @@ -1,19 +1,13 @@ -import { BoltSlashIcon } from "@heroicons/react/24/outline"; -import { CompositionErrorsDialog } from "@/components/composition-errors-dialog"; -import React from "react"; -import { cn } from "@/lib/utils"; +import { BoltSlashIcon } from '@heroicons/react/24/outline'; +import { CompositionErrorsDialog } from '@/components/composition-errors-dialog'; +import React from 'react'; +import { cn } from '@/lib/utils'; -export const CompositionErrorsBanner = ({ - errors, - className, -}: { - errors?: string; - className?: string; -}) => { +export const CompositionErrorsBanner = ({ errors, className }: { errors?: string; className?: string }) => { return (
    @@ -22,8 +16,8 @@ export const CompositionErrorsBanner = ({
    - This version of the API schema does not include the latest from some - of your subgraphs because the composition failed. + This version of the API schema does not include the latest from some of your subgraphs because the composition + failed.
    {errors && } diff --git a/studio/src/components/composition-errors-dialog.tsx b/studio/src/components/composition-errors-dialog.tsx index bbd911236d..3c5df9f30f 100644 --- a/studio/src/components/composition-errors-dialog.tsx +++ b/studio/src/components/composition-errors-dialog.tsx @@ -1,17 +1,11 @@ -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; export const CompositionErrorsDialog = ({ errors }: { errors: string }) => { return ( - @@ -22,10 +16,9 @@ export const CompositionErrorsDialog = ({ errors }: { errors: string }) => {

    - This version of the API schema does not include the latest from - some of your subgraphs because the composition failed. The router - will continue to serve the latest valid version of the graph. - Please fix the following errors: + This version of the API schema does not include the latest from some of your subgraphs because the + composition failed. The router will continue to serve the latest valid version of the graph. Please fix + the following errors:

    diff --git a/studio/src/components/create-graph.tsx b/studio/src/components/create-graph.tsx index cabe325db0..f7a5392b55 100644 --- a/studio/src/components/create-graph.tsx +++ b/studio/src/components/create-graph.tsx @@ -1,55 +1,40 @@ -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Button } from "@/components/ui/button"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Separator } from "@/components/ui/separator"; -import { Tag, TagInput } from "@/components/ui/tag-input/tag-input"; -import { useToast } from "@/components/ui/use-toast"; -import { SubmitHandler, useZodForm } from "@/hooks/use-form"; -import { useUser } from "@/hooks/use-user"; -import { docsBaseURL } from "@/lib/constants"; -import { useMutation, createConnectQueryKey } from "@connectrpc/connect-query"; -import { CheckCircleIcon } from "@heroicons/react/24/outline"; -import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; +import { Button } from '@/components/ui/button'; +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Separator } from '@/components/ui/separator'; +import { Tag, TagInput } from '@/components/ui/tag-input/tag-input'; +import { useToast } from '@/components/ui/use-toast'; +import { SubmitHandler, useZodForm } from '@/hooks/use-form'; +import { useUser } from '@/hooks/use-user'; +import { docsBaseURL } from '@/lib/constants'; +import { useMutation, createConnectQueryKey } from '@connectrpc/connect-query'; +import { CheckCircleIcon } from '@heroicons/react/24/outline'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; import { createFederatedGraph, createMonograph, - getWorkspace -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { useState, useEffect } from "react"; -import { z } from "zod"; -import { EmptyState } from "./empty-state"; -import { cn } from "@/lib/utils"; + getWorkspace, +} from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useState, useEffect } from 'react'; +import { z } from 'zod'; +import { EmptyState } from './empty-state'; +import { cn } from '@/lib/utils'; import { CreateFederatedGraphResponse, CreateMonographResponse, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useQueryClient } from "@tanstack/react-query"; +} from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useQueryClient } from '@tanstack/react-query'; -export const CreateGraphForm = ({ - isMonograph = false, -}: { - isMonograph?: boolean; -}) => { +export const CreateGraphForm = ({ isMonograph = false }: { isMonograph?: boolean }) => { const router = useRouter(); const user = useUser(); - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); const queryClient = useQueryClient(); const [tags, setTags] = useState([]); @@ -61,11 +46,7 @@ export const CreateGraphForm = ({ data: federatedGraphData, } = useMutation(createFederatedGraph); - const { - mutate: mutateMonograph, - isPending: creatingMonograph, - data: monographData, - } = useMutation(createMonograph); + const { mutate: mutateMonograph, isPending: creatingMonograph, data: monographData } = useMutation(createMonograph); const isPending = creatingFederatedGraph || creatingMonograph; @@ -73,29 +54,27 @@ export const CreateGraphForm = ({ .string() .url() .min(1, { - message: "The routing url cannot be empty", + message: 'The routing url cannot be empty', }) .refine( (url) => - process.env.NODE_ENV === "production" - ? url.startsWith("https://") - : url.startsWith("http://") || url.startsWith("https://"), - process.env.NODE_ENV === "production" - ? "The endpoint must use https" - : "The endpoint must use http or https", + process.env.NODE_ENV === 'production' + ? url.startsWith('https://') + : url.startsWith('http://') || url.startsWith('https://'), + process.env.NODE_ENV === 'production' ? 'The endpoint must use https' : 'The endpoint must use http or https', ); const schema = z.object({ name: z .string() .min(1, { - message: "The name cannot be empty", + message: 'The name cannot be empty', }) .max(100, { - message: "The name must be at most 100 characters long", + message: 'The name must be at most 100 characters long', }) .regex( - new RegExp("^[a-zA-Z0-9]+(?:[_.@/-][a-zA-Z0-9]+)*$"), + new RegExp('^[a-zA-Z0-9]+(?:[_.@/-][a-zA-Z0-9]+)*$'), "Name should start and end with an alphanumeric character. Only '.', '_', '@', '/', and '-' are allowed as separators in between.", ), routingUrl: urlSchema, @@ -116,12 +95,12 @@ export const CreateGraphForm = ({ const form = useZodForm({ schema, - mode: "onChange", + mode: 'onChange', }); // Sync form value when tags change (handles both direct updates and functional updaters) useEffect(() => { - form.setValue("labelMatchers", tags as [Tag, ...Tag[]], { + form.setValue('labelMatchers', tags as [Tag, ...Tag[]], { shouldValidate: true, }); }, [tags, form]); @@ -130,22 +109,18 @@ export const CreateGraphForm = ({ const onSubmit: SubmitHandler = (data) => { const responseHandlers = { - onSuccess: async ( - d: CreateFederatedGraphResponse | CreateMonographResponse, - ) => { + onSuccess: async (d: CreateFederatedGraphResponse | CreateMonographResponse) => { if (d.response?.code === EnumStatusCode.OK) { // We need to refresh the workspace after creating a graph await queryClient.refetchQueries({ queryKey: createConnectQueryKey(getWorkspace) }); - router.replace( - `/${user?.currentOrganization.slug}/${namespace}/graph/${data.name}`, - ); + router.replace(`/${user?.currentOrganization.slug}/${namespace}/graph/${data.name}`); } else if (d.response?.details) { toast({ description: d.response.details, duration: 3000 }); } }, onError: () => { toast({ - description: "Could not create graph. Please try again.", + description: 'Could not create graph. Please try again.', duration: 3000, }); }, @@ -182,14 +157,11 @@ export const CreateGraphForm = ({ } }; - if ( - federatedGraphData?.response?.code === EnumStatusCode.OK || - monographData?.response?.code === EnumStatusCode.OK - ) { + if (federatedGraphData?.response?.code === EnumStatusCode.OK || monographData?.response?.code === EnumStatusCode.OK) { return ( } - title={`${isMonograph ? "Monograph" : "Federated Graph"} created`} + title={`${isMonograph ? 'Monograph' : 'Federated Graph'} created`} description="You will be now be redirected to your new graph" /> ); @@ -197,10 +169,7 @@ export const CreateGraphForm = ({ return (
    - + - - This is used to uniquely identify your graph. - + This is used to uniquely identify your graph. )} @@ -226,9 +193,7 @@ export const CreateGraphForm = ({ - - This is the URL that the router will be accessible at. - + This is the URL that the router will be accessible at. )} @@ -237,15 +202,12 @@ export const CreateGraphForm = ({ control={form.control} name="graphUrl" render={({ field }) => ( - + Graph URL - - The endpoint of your GraphQL server that is accessible from the - router. - + The endpoint of your GraphQL server that is accessible from the router. )} @@ -260,10 +222,8 @@ export const CreateGraphForm = ({ control={form.control} name="labelMatchers" render={({ field }) => ( - - - Label Matchers{" "} - + + Label Matchers - Label matchers are used to select which subgraphs participate in this federated graph composition. - Enter space-separated key-value pairs in the format key=value. - To specify multiple values for the same key (OR condition), use commas within a single matcher (e.g., team=A,team=B matches subgraphs where team is either A or B). - {" "} + Label matchers are used to select which subgraphs participate in this federated graph + composition. Enter space-separated key-value pairs in the format key=value. To + specify multiple values for the same key (OR condition), use commas within a single matcher + (e.g., team=A,team=B matches subgraphs where team is either A or B).{' '} - The endpoint used to implement admission control for the - graph. Learn more{" "} + The endpoint used to implement admission control for the graph. Learn more{' '} - - This is used to sign requests made to the above webhook. - + This is used to sign requests made to the above webhook. )} @@ -351,12 +305,7 @@ export const CreateGraphForm = ({ - diff --git a/studio/src/components/dashboard/NewFeaturesPopup.tsx b/studio/src/components/dashboard/NewFeaturesPopup.tsx index 1097ee461d..4a0cc1625b 100644 --- a/studio/src/components/dashboard/NewFeaturesPopup.tsx +++ b/studio/src/components/dashboard/NewFeaturesPopup.tsx @@ -1,9 +1,9 @@ -import React from "react"; -import Link from "next/link"; -import { Cross2Icon } from "@radix-ui/react-icons"; -import { Button } from "../ui/button"; -import { MdArrowOutward } from "react-icons/md"; -import { useNewFeaturesPopupDisabled } from "@/hooks/use-new-features-popup-disabled"; +import React from 'react'; +import Link from 'next/link'; +import { Cross2Icon } from '@radix-ui/react-icons'; +import { Button } from '../ui/button'; +import { MdArrowOutward } from 'react-icons/md'; +import { useNewFeaturesPopupDisabled } from '@/hooks/use-new-features-popup-disabled'; export default function NewFeaturesPopup() { const [isPopupDisabled, setDisablePopup] = useNewFeaturesPopupDisabled(); @@ -11,7 +11,7 @@ export default function NewFeaturesPopup() { const handleClosePopup = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - setDisablePopup("true"); + setDisablePopup('true'); }; // Don't render the popup if it's been dismissed @@ -38,8 +38,8 @@ export default function NewFeaturesPopup() { className="absolute inset-0" style={{ background: - "linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.03) 35%, rgba(255,255,255,0.12) 50%, rgba(255,255,255,0.03) 65%, transparent 100%)", - animation: "popup-shine 3s ease-in-out 2 forwards", + 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.03) 35%, rgba(255,255,255,0.12) 50%, rgba(255,255,255,0.03) 65%, transparent 100%)', + animation: 'popup-shine 3s ease-in-out 2 forwards', }} />
    @@ -59,8 +59,7 @@ export default function NewFeaturesPopup() {

    - A smarter way to design schemas, collaborate, and govern changes — - all in one place. + A smarter way to design schemas, collaborate, and govern changes — all in one place.

    @@ -206,12 +205,7 @@ export default function NewFeaturesPopup() { - + diff --git a/studio/src/components/dashboard/graph-command-group.tsx b/studio/src/components/dashboard/graph-command-group.tsx index 6e12f4f203..037cf633f0 100644 --- a/studio/src/components/dashboard/graph-command-group.tsx +++ b/studio/src/components/dashboard/graph-command-group.tsx @@ -1,12 +1,12 @@ -import * as React from "react"; -import { WorkspaceNamespace } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { CommandGroup, CommandItem } from "@/components/ui/command"; -import { useRouter } from "next/router"; -import { useMemo } from "react"; -import { cn } from "@/lib/utils"; -import { Badge } from "@/components/ui/badge"; -import { CheckIcon } from "@radix-ui/react-icons"; -import { useCurrentOrganization } from "@/hooks/use-current-organization"; +import * as React from 'react'; +import { WorkspaceNamespace } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { CommandGroup, CommandItem } from '@/components/ui/command'; +import { useRouter } from 'next/router'; +import { useMemo } from 'react'; +import { cn } from '@/lib/utils'; +import { Badge } from '@/components/ui/badge'; +import { CheckIcon } from '@radix-ui/react-icons'; +import { useCurrentOrganization } from '@/hooks/use-current-organization'; type GraphCommandGroupProps = { isFiltering: boolean; @@ -15,7 +15,7 @@ type GraphCommandGroupProps = { activeGraphId?: string; activeSubgraphId?: string; setNamespace(namespace: string): void; -} +}; export function GraphCommandGroup({ isFiltering, @@ -38,18 +38,19 @@ export function GraphCommandGroup({ setNamespace={setNamespace} /> - {(isFiltering || activeSubgraphId) && subgraphs.map((subgraph, subgraphIndex) => ( - - ))} + {(isFiltering || activeSubgraphId) && + subgraphs.map((subgraph, subgraphIndex) => ( + + ))} ))} @@ -73,7 +74,7 @@ const graphAreasWithParameters: readonly string[] = [ 'checks', 'compositions', 'feature-flags', - 'proposals' + 'proposals', ]; function GraphCommandItem({ @@ -89,60 +90,51 @@ function GraphCommandItem({ const router = useRouter(); const organizationSlug = useCurrentOrganization()?.slug; - const pathname = useMemo( - () => { - const segmentSplit = router.pathname.split('/'); - const segment = segmentSplit[3]?.toLowerCase(); - if (isSubgraph) { - return segment === 'subgraph' - ? router.pathname - : `/[organizationSlug]/[namespace]/subgraph/[subgraphSlug]`; - } + const pathname = useMemo(() => { + const segmentSplit = router.pathname.split('/'); + const segment = segmentSplit[3]?.toLowerCase(); + if (isSubgraph) { + return segment === 'subgraph' ? router.pathname : `/[organizationSlug]/[namespace]/subgraph/[subgraphSlug]`; + } - if (segment !== 'graph') { - return defaultGraphTemplate; - } + if (segment !== 'graph') { + return defaultGraphTemplate; + } - const areaSegment = segmentSplit[5]?.toLowerCase(); - return areaSegment && graphAreasWithParameters.includes(areaSegment) && segmentSplit.length > 5 - ? `${defaultGraphTemplate}/${areaSegment}` - : router.pathname; - }, - [router.pathname, isSubgraph], - ); + const areaSegment = segmentSplit[5]?.toLowerCase(); + return areaSegment && graphAreasWithParameters.includes(areaSegment) && segmentSplit.length > 5 + ? `${defaultGraphTemplate}/${areaSegment}` + : router.pathname; + }, [router.pathname, isSubgraph]); return ( { - router.push({ - pathname, - query: { - organizationSlug, - namespace: namespace.name, - ...(isSubgraph ? { subgraphSlug: name } : { slug: name }), - } - }).finally(() => setNamespace(namespace.name)); + router + .push({ + pathname, + query: { + organizationSlug, + namespace: namespace.name, + ...(isSubgraph ? { subgraphSlug: name } : { slug: name }), + }, + }) + .finally(() => setNamespace(namespace.name)); }} > - + {name} {!isSubgraph && isContract && ( - contract + + contract + )} - + ); -} \ No newline at end of file +} diff --git a/studio/src/components/dashboard/graph-selector.tsx b/studio/src/components/dashboard/graph-selector.tsx index 8e174a7bf4..00b01a994f 100644 --- a/studio/src/components/dashboard/graph-selector.tsx +++ b/studio/src/components/dashboard/graph-selector.tsx @@ -1,10 +1,10 @@ -import { useState } from "react"; -import { Popover, PopoverTrigger } from "@/components/ui/popover"; -import { WorkspaceCommandWrapper } from "./workspace-command-wrapper" -import { Button } from "@/components/ui/button"; -import { CaretSortIcon } from "@radix-ui/react-icons"; -import * as React from "react"; -import { WorkspaceFederatedGraph, WorkspaceSubgraph } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; +import { useState } from 'react'; +import { Popover, PopoverTrigger } from '@/components/ui/popover'; +import { WorkspaceCommandWrapper } from './workspace-command-wrapper'; +import { Button } from '@/components/ui/button'; +import { CaretSortIcon } from '@radix-ui/react-icons'; +import * as React from 'react'; +import { WorkspaceFederatedGraph, WorkspaceSubgraph } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; interface GraphSelectorProps { activeGraph: WorkspaceFederatedGraph | undefined; @@ -20,7 +20,7 @@ export function GraphSelector({ activeGraph, activeSubgraph }: GraphSelectorProp return ( <> - / + / - @@ -55,4 +53,4 @@ export function GraphSelector({ activeGraph, activeSubgraph }: GraphSelectorProp ); -} \ No newline at end of file +} diff --git a/studio/src/components/dashboard/namespace-selector.tsx b/studio/src/components/dashboard/namespace-selector.tsx index a7abf058e2..3df8745e98 100644 --- a/studio/src/components/dashboard/namespace-selector.tsx +++ b/studio/src/components/dashboard/namespace-selector.tsx @@ -1,15 +1,15 @@ -import { CommandItem, CommandGroup, CommandSeparator } from "@/components/ui/command"; -import { Popover, PopoverTrigger } from "@/components/ui/popover"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useRouter } from "next/router"; -import { useMemo, useState } from "react"; -import Link from "next/link"; -import { cn } from "@/lib/utils"; -import * as React from "react"; -import { CheckIcon, CaretSortIcon } from "@radix-ui/react-icons"; -import { docsBaseURL } from "@/lib/constants"; -import { WorkspaceCommandWrapper } from "./workspace-command-wrapper" -import { useCurrentOrganization } from "@/hooks/use-current-organization"; +import { CommandItem, CommandGroup, CommandSeparator } from '@/components/ui/command'; +import { Popover, PopoverTrigger } from '@/components/ui/popover'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useRouter } from 'next/router'; +import { useMemo, useState } from 'react'; +import Link from 'next/link'; +import { cn } from '@/lib/utils'; +import * as React from 'react'; +import { CheckIcon, CaretSortIcon } from '@radix-ui/react-icons'; +import { docsBaseURL } from '@/lib/constants'; +import { WorkspaceCommandWrapper } from './workspace-command-wrapper'; +import { useCurrentOrganization } from '@/hooks/use-current-organization'; interface NamespaceSelectorProps { isViewingGraphOrSubgraph: boolean; @@ -24,23 +24,15 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace const router = useRouter(); const organizationSlug = useCurrentOrganization()?.slug; const pathname = useMemo( - () => router.pathname.split('/').length === 3 ? router.pathname : '/[organizationSlug]/graphs', - [router.pathname] + () => (router.pathname.split('/').length === 3 ? router.pathname : '/[organizationSlug]/graphs'), + [router.pathname], ); const namespaces = Array.from(namespaceByName.keys()); if (isLoading) { return ( - - - {namespace.name} - + + {namespace.name} ); @@ -56,14 +48,14 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace query: { organizationSlug, namespace: namespace.name }, }} className={cn( - "bg-primary/15 hover:bg-primary/30 text-primary transition-colors duration-150 pl-3 pr-2 py-1.5 rounded-l-lg text-sm flex-shrink-0", - truncateNamespace && "max-w-[180px] lg:max-w-xs truncate" + 'flex-shrink-0 rounded-l-lg bg-primary/15 py-1.5 pl-3 pr-2 text-sm text-primary transition-colors duration-150 hover:bg-primary/30', + truncateNamespace && 'max-w-[180px] truncate lg:max-w-xs', )} onClick={() => setNamespace(namespace.name, false)} > {namespace.name} -
    +
    )} {!isViewingGraphOrSubgraph && ( - - {namespace.name} - + {namespace.name} )} @@ -110,12 +98,8 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace

    Namespaces

    - Easily switch between namespaces. Learn more{" "} - + Easily switch between namespaces. Learn more{' '} + here.

    @@ -127,7 +111,7 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace {namespaces.map((ns) => ( { router.push({ @@ -137,14 +121,12 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace setOpen(false); setNamespace(ns, false); - }}> + }} + > {ns} ))} @@ -155,4 +137,4 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace
    ); -} \ No newline at end of file +} diff --git a/studio/src/components/dashboard/workspace-command-wrapper.tsx b/studio/src/components/dashboard/workspace-command-wrapper.tsx index 11e73767cc..2a2ec86e17 100644 --- a/studio/src/components/dashboard/workspace-command-wrapper.tsx +++ b/studio/src/components/dashboard/workspace-command-wrapper.tsx @@ -1,16 +1,16 @@ -import { PopoverContentWithScrollableContent } from "@/components/popover-content-with-scrollable-content"; -import { Command, CommandInput } from "@/components/ui/command"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useMemo } from "react"; +import { PopoverContentWithScrollableContent } from '@/components/popover-content-with-scrollable-content'; +import { Command, CommandInput } from '@/components/ui/command'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useMemo } from 'react'; import { WorkspaceNamespace, WorkspaceFederatedGraph, - WorkspaceSubgraph -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import Fuse from "fuse.js"; -import * as React from "react"; + WorkspaceSubgraph, +} from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import Fuse from 'fuse.js'; +import * as React from 'react'; -import { GraphCommandGroup } from "./graph-command-group"; +import { GraphCommandGroup } from './graph-command-group'; interface WorkspacePopoverContentProps { children?: React.ReactNode; @@ -101,27 +101,20 @@ export function WorkspaceCommandWrapper({ const isFiltering = filter.trim().length > 0; return ( - - - {showFilter && ()} + + + {showFilter && ( + + )}
    {isFiltering || !children ? ( <> {filteredGraphs.length === 0 ? ( -
    +
    No namespace, graph or subgraph matches your criteria.
    - ) : filteredGraphs.map((wns, index) => ( + ) : ( + filteredGraphs.map((wns, index) => ( - ))} + )) + )} - ) : children} + ) : ( + children + )}
    ); -} \ No newline at end of file +} diff --git a/studio/src/components/dashboard/workspace-provider.tsx b/studio/src/components/dashboard/workspace-provider.tsx index 5e2310864c..32d254d2d3 100644 --- a/studio/src/components/dashboard/workspace-provider.tsx +++ b/studio/src/components/dashboard/workspace-provider.tsx @@ -1,13 +1,11 @@ -import { useQuery } from "@connectrpc/connect-query"; -import { - getWorkspace, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; -import { createContext, useCallback, useEffect, useMemo, useState } from "react"; -import { WorkspaceNamespace } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { useRouter } from "next/router"; -import { useApplyParams } from "@/components/analytics/use-apply-params"; -import { useLocalStorage } from "@/hooks/use-local-storage"; -import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; +import { useQuery } from '@connectrpc/connect-query'; +import { getWorkspace } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; +import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import { WorkspaceNamespace } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { useRouter } from 'next/router'; +import { useApplyParams } from '@/components/analytics/use-apply-params'; +import { useLocalStorage } from '@/hooks/use-local-storage'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; const DEFAULT_NAMESPACE_NAME = 'default'; @@ -27,7 +25,7 @@ export function WorkspaceProvider({ children }: React.PropsWithChildren) { // Initialize the namespace const namespaceParam = router.query.namespace as string; - const [storedNamespace, setStoredNamespace] = useLocalStorage("wg-namespace", DEFAULT_NAMESPACE_NAME); + const [storedNamespace, setStoredNamespace] = useLocalStorage('wg-namespace', DEFAULT_NAMESPACE_NAME); const [namespace, setNamespace] = useState(namespaceParam || storedNamespace || DEFAULT_NAMESPACE_NAME); const [namespaces, setNamespaces] = useState([DEFAULT_NAMESPACE_NAME]); @@ -68,39 +66,42 @@ export function WorkspaceProvider({ children }: React.PropsWithChildren) { ]); // Memoize context components - const currentNamespace= useMemo( - () => isLoading - ? new WorkspaceNamespace({ id: '', name: namespace, graphs: [] }) - : data?.namespaces.find((wns) => wns.name === namespace) ?? new WorkspaceNamespace({ - id: '', - name: DEFAULT_NAMESPACE_NAME, - graphs: [], - }), + const currentNamespace = useMemo( + () => + isLoading + ? new WorkspaceNamespace({ id: '', name: namespace, graphs: [] }) + : (data?.namespaces.find((wns) => wns.name === namespace) ?? + new WorkspaceNamespace({ + id: '', + name: DEFAULT_NAMESPACE_NAME, + graphs: [], + })), [isLoading, data?.namespaces, namespace], ); const namespaceByName = useMemo( - () => data?.namespaces.reduce( - (acc, wns) => { + () => + data?.namespaces.reduce((acc, wns) => { acc.set(wns.name, wns); return acc; - }, - new Map(), - ) ?? new Map(), + }, new Map()) ?? new Map(), [data?.namespaces], ); - const setNamespaceCallback = useCallback((ns: string, applyRouteParams: boolean) => { - if (!ns || namespace === ns || !namespaces.some((ns) => ns.toLowerCase() === ns.toLowerCase())) { - return; - } + const setNamespaceCallback = useCallback( + (ns: string, applyRouteParams: boolean) => { + if (!ns || namespace === ns || !namespaces.some((ns) => ns.toLowerCase() === ns.toLowerCase())) { + return; + } - setNamespace(ns); - setStoredNamespace(ns); - if (applyRouteParams) { - applyParams({namespace: ns}); - } - }, [namespace, namespaces, setStoredNamespace, applyParams]); + setNamespace(ns); + setStoredNamespace(ns); + if (applyRouteParams) { + applyParams({ namespace: ns }); + } + }, + [namespace, namespaces, setStoredNamespace, applyParams], + ); // Finally, render :) return ( @@ -115,4 +116,4 @@ export function WorkspaceProvider({ children }: React.PropsWithChildren) { {children} ); -} \ No newline at end of file +} diff --git a/studio/src/components/dashboard/workspace-selector.tsx b/studio/src/components/dashboard/workspace-selector.tsx index b5feac0db5..8e1368dec3 100644 --- a/studio/src/components/dashboard/workspace-selector.tsx +++ b/studio/src/components/dashboard/workspace-selector.tsx @@ -1,12 +1,12 @@ -import * as React from "react"; +import * as React from 'react'; -import { NamespaceSelector } from "./namespace-selector"; -import { GraphSelector } from "./graph-selector"; -import { useSubgraph } from "@/hooks/use-subgraph"; -import { useMemo } from "react"; -import { useRouter } from "next/router"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { cn } from "@/lib/utils"; +import { NamespaceSelector } from './namespace-selector'; +import { GraphSelector } from './graph-selector'; +import { useSubgraph } from '@/hooks/use-subgraph'; +import { useMemo } from 'react'; +import { useRouter } from 'next/router'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { cn } from '@/lib/utils'; export interface WorkspaceSelectorProps { children?: React.ReactNode; @@ -18,41 +18,25 @@ export function WorkspaceSelector({ children, truncateNamespace = true }: Worksp const subgraphContext = useSubgraph(); const { namespace } = useWorkspace(); - const [activeGraph, activeSubgraph] = useMemo( - () => { - const routeSegment = router.asPath.split("/")[3]?.toLowerCase(); - const currentSlug = (router.query.slug as string)?.toLowerCase(); - return [ - routeSegment === "graph" - ? namespace.graphs.find((graph) => graph.name.toLowerCase() === currentSlug) - : undefined, - !!subgraphContext?.subgraph?.id - ? namespace.graphs + const [activeGraph, activeSubgraph] = useMemo(() => { + const routeSegment = router.asPath.split('/')[3]?.toLowerCase(); + const currentSlug = (router.query.slug as string)?.toLowerCase(); + return [ + routeSegment === 'graph' ? namespace.graphs.find((graph) => graph.name.toLowerCase() === currentSlug) : undefined, + !!subgraphContext?.subgraph?.id + ? namespace.graphs .flatMap((graph) => graph.subgraphs) .find((subgraph) => subgraph.id === subgraphContext?.subgraph?.id) - : undefined, - ]; - }, - [namespace, router.asPath, router.query.slug, subgraphContext?.subgraph?.id], - ); + : undefined, + ]; + }, [namespace, router.asPath, router.query.slug, subgraphContext?.subgraph?.id]); const isViewingGraphOrSubgraph = !!activeGraph || !!activeSubgraph; return ( -
    - - -
    - {children} -
    +
    + + +
    {children}
    ); } diff --git a/studio/src/components/date-picker-with-range.tsx b/studio/src/components/date-picker-with-range.tsx index c2117fb300..66b3ebe46f 100644 --- a/studio/src/components/date-picker-with-range.tsx +++ b/studio/src/components/date-picker-with-range.tsx @@ -1,33 +1,27 @@ -import { Button, ButtonProps } from "@/components/ui/button"; -import { Calendar } from "@/components/ui/calendar"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import useWindowSize from "@/hooks/use-window-size"; -import { formatDate } from "@/lib/format-date"; -import { cn } from "@/lib/utils"; -import CalendarIcon from "@heroicons/react/24/outline/CalendarIcon"; -import { addDays, addYears, subHours, subYears } from "date-fns"; -import { useCallback, useEffect, useState } from "react"; -import { Input } from "./ui/input"; +import { Button, ButtonProps } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import useWindowSize from '@/hooks/use-window-size'; +import { formatDate } from '@/lib/format-date'; +import { cn } from '@/lib/utils'; +import CalendarIcon from '@heroicons/react/24/outline/CalendarIcon'; +import { addDays, addYears, subHours, subYears } from 'date-fns'; +import { useCallback, useEffect, useState } from 'react'; +import { Input } from './ui/input'; const ranges = { - 1: "Last hour", - 4: "Last 4 hours", - 24: "Last day", - 72: "Last 3 days", - 168: "Last week", - 720: "Last month", + 1: 'Last hour', + 4: 'Last 4 hours', + 24: 'Last day', + 72: 'Last 3 days', + 168: 'Last week', + 720: 'Last month', } as const; export type Range = keyof typeof ranges; export const getRange = (range?: string | number): Range => { - return range && Number(range) in ranges - ? (Number(range) as Range) - : defaultRange; + return range && Number(range) in ranges ? (Number(range) as Range) : defaultRange; }; const defaultRange = 24; @@ -37,40 +31,21 @@ export type DateRange = { end?: Date; }; -const getFromDate = (from: Date, time = "00:00") => { - const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10)); - return new Date( - from.getFullYear(), - from.getMonth(), - from.getDate(), - hours, - minutes, - ); +const getFromDate = (from: Date, time = '00:00') => { + const [hours, minutes] = time.split(':').map((str) => parseInt(str, 10)); + return new Date(from.getFullYear(), from.getMonth(), from.getDate(), hours, minutes); }; -const getToDate = (to: Date, time = "23:59") => { - const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10)); - return new Date( - to.getFullYear(), - to.getMonth(), - to.getDate(), - hours, - minutes, - ); +const getToDate = (to: Date, time = '23:59') => { + const [hours, minutes] = time.split(':').map((str) => parseInt(str, 10)); + return new Date(to.getFullYear(), to.getMonth(), to.getDate(), hours, minutes); }; const getFormattedTime = (date: Date) => { - return ( - date.getHours().toString().padStart(2, "0") + - ":" + - date.getMinutes().toString().padStart(2, "0") - ); + return date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0'); }; -export type DateRangePickerChangeHandler = (newVal: { - dateRange?: DateRange; - range?: Range; -}) => void; +export type DateRangePickerChangeHandler = (newVal: { dateRange?: DateRange; range?: Range }) => void; export function DatePickerWithRange({ range, @@ -78,27 +53,27 @@ export function DatePickerWithRange({ onChange, onCancel, className, - align = "start", + align = 'start', size, calendarDaysLimit, -}: Omit, "onChange"> & { +}: Omit, 'onChange'> & { range?: Range; dateRange: DateRange; onChange: DateRangePickerChangeHandler; onCancel?: () => void; - align?: "start" | "center" | "end"; - size?: ButtonProps["size"]; + align?: 'start' | 'center' | 'end'; + size?: ButtonProps['size']; calendarDaysLimit: number; }) { const { isMobile } = useWindowSize(); const [selected, setSelectedDateRange] = useState(dateRange); const [selectedRange, setSelectedRange] = useState(range); - const [startTime, setStartTime] = useState(""); - const [endTime, setEndTime] = useState(""); + const [startTime, setStartTime] = useState(''); + const [endTime, setEndTime] = useState(''); - const setTime = (field: "start" | "end", time: string) => { - if (field === "start") { + const setTime = (field: 'start' | 'end', time: string) => { + if (field === 'start') { setStartTime(time); } else { setEndTime(time); @@ -113,7 +88,7 @@ export function DatePickerWithRange({ setSelectedRange(range); if (!range) { setStartTime(getFormattedTime(dateRange.start)); - setEndTime(dateRange.end ? getFormattedTime(dateRange.end) : ""); + setEndTime(dateRange.end ? getFormattedTime(dateRange.end) : ''); } else { const end = new Date(); setStartTime(getFormattedTime(subHours(end, range))); @@ -147,13 +122,8 @@ export function DatePickerWithRange({ const getValue = (): DateRange => { return { - start: getFromDate( - selected.start, - startTime === "" ? "00:00" : startTime, - ), - end: - selected?.end && - getToDate(selected.end, endTime === "" ? "23:59" : endTime), + start: getFromDate(selected.start, startTime === '' ? '00:00' : startTime), + end: selected?.end && getToDate(selected.end, endTime === '' ? '23:59' : endTime), }; }; @@ -169,7 +139,7 @@ export function DatePickerWithRange({ }; const handleTimeChange = - (field: "start" | "end"): React.ChangeEventHandler => + (field: 'start' | 'end'): React.ChangeEventHandler => (e) => { const time = e.target.value; setTime(field, time); @@ -190,12 +160,12 @@ export function DatePickerWithRange({
    diff --git a/studio/src/components/date-range-picker.tsx b/studio/src/components/date-range-picker.tsx index 00db0951d1..c073d575d2 100644 --- a/studio/src/components/date-range-picker.tsx +++ b/studio/src/components/date-range-picker.tsx @@ -1,30 +1,26 @@ -import { Button, ButtonProps } from "@/components/ui/button"; -import { Calendar } from "@/components/ui/calendar"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import useWindowSize from "@/hooks/use-window-size"; -import { formatDate } from "@/lib/format-date"; -import { cn } from "@/lib/utils"; -import CalendarIcon from "@heroicons/react/24/outline/CalendarIcon"; -import { addDays, addYears, subHours, subYears } from "date-fns"; -import { useEffect, useState } from "react"; -import { DateRange } from "react-day-picker"; +import { Button, ButtonProps } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import useWindowSize from '@/hooks/use-window-size'; +import { formatDate } from '@/lib/format-date'; +import { cn } from '@/lib/utils'; +import CalendarIcon from '@heroicons/react/24/outline/CalendarIcon'; +import { addDays, addYears, subHours, subYears } from 'date-fns'; +import { useEffect, useState } from 'react'; +import { DateRange } from 'react-day-picker'; export function DateRangePicker({ selectedDateRange, onDateRangeChange, className, - align = "start", + align = 'start', size, calendarDaysLimit, }: React.HTMLAttributes & { selectedDateRange: DateRange; onDateRangeChange: (newVal: DateRange) => unknown; - align?: "start" | "center" | "end"; - size?: ButtonProps["size"]; + align?: 'start' | 'center' | 'end'; + size?: ButtonProps['size']; calendarDaysLimit: number; }) { const { isMobile } = useWindowSize(); @@ -52,12 +48,12 @@ export function DateRangePicker({ } /> diff --git a/studio/src/components/feature-flag-details.tsx b/studio/src/components/feature-flag-details.tsx index 4d949d9a54..aa2da4e3de 100644 --- a/studio/src/components/feature-flag-details.tsx +++ b/studio/src/components/feature-flag-details.tsx @@ -1,28 +1,20 @@ -import { formatDateTime } from "@/lib/format-date"; -import { FederatedGraphsTable } from "@/pages/[organizationSlug]/[namespace]/subgraph/[subgraphSlug]/graphs"; -import { - CheckCircleIcon, - ExclamationTriangleIcon, - InformationCircleIcon, -} from "@heroicons/react/24/outline"; -import { Component1Icon, HomeIcon } from "@radix-ui/react-icons"; -import { - FeatureFlag, - FederatedGraph, - Subgraph, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { formatDistanceToNow } from "date-fns"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { PiGraphLight } from "react-icons/pi"; -import { EmptyState } from "./empty-state"; -import { SubgraphsTable } from "./subgraphs-table"; -import { Badge } from "./ui/badge"; -import { CLI, CLISteps } from "./ui/cli"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"; -import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useCurrentOrganization } from "@/hooks/use-current-organization"; +import { formatDateTime } from '@/lib/format-date'; +import { FederatedGraphsTable } from '@/pages/[organizationSlug]/[namespace]/subgraph/[subgraphSlug]/graphs'; +import { CheckCircleIcon, ExclamationTriangleIcon, InformationCircleIcon } from '@heroicons/react/24/outline'; +import { Component1Icon, HomeIcon } from '@radix-ui/react-icons'; +import { FeatureFlag, FederatedGraph, Subgraph } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { formatDistanceToNow } from 'date-fns'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { PiGraphLight } from 'react-icons/pi'; +import { EmptyState } from './empty-state'; +import { SubgraphsTable } from './subgraphs-table'; +import { Badge } from './ui/badge'; +import { CLI, CLISteps } from './ui/cli'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'; +import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useCurrentOrganization } from '@/hooks/use-current-organization'; const FeatureFlagOverview = ({ federatedGraphs, @@ -34,7 +26,9 @@ const FeatureFlagOverview = ({ isEnabled: boolean; }) => { const router = useRouter(); - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); const currentOrg = useCurrentOrganization(); const slug = router.query.slug as string; @@ -54,13 +48,11 @@ const FeatureFlagOverview = ({ --namespace ${namespace} -r --subgraph `, }, { - description: - "Update your feature subgraphs of this feature flag.", + description: 'Update your feature subgraphs of this feature flag.', command: `npx wgc feature-flag update --namespace ${namespace} --feature-subgraphs `, }, ]} @@ -73,32 +65,20 @@ const FeatureFlagOverview = ({ } title="Feature flag is not used for composition." - description={ - <> - Feature flag is disabled. To enable the feature flag, use the below - command. - - } - actions={ - --namespace `} - /> - } + description={<>Feature flag is disabled. To enable the feature flag, use the below command.} + actions={ --namespace `} />} /> ); - } else if ( - federatedGraphs.find((f) => f.federatedGraph.name === slug)?.isConnected - ) { - if (featureSubgraphs.some((fs) => fs.lastUpdatedAt !== "")) { + } else if (federatedGraphs.find((f) => f.federatedGraph.name === slug)?.isConnected) { + if (featureSubgraphs.some((fs) => fs.lastUpdatedAt !== '')) { content = ( } title="Feature flag is active." description={ <> - This feature flag will be a part of compositions of this federated - graph. Once the feature flag is composed successfully, you can - query the feature flag in the{" "} + This feature flag will be a part of compositions of this federated graph. Once the feature flag is + composed successfully, you can query the feature flag in the{' '} - None of the feature subgraphs which are part of this feature flag - are published. Please publish the feature subgraphs using the - command below. Publish the feature subgraphs using the below - command. + None of the feature subgraphs which are part of this feature flag are published. Please publish the + feature subgraphs using the command below. Publish the feature subgraphs using the below command. } actions={ @@ -139,10 +117,9 @@ const FeatureFlagOverview = ({ title="Feature flag is not used for composition." description={ <> - The labels of this feature flag match to that of this federated - graph. But to be used for composition, the feature subgraphs which - are part of this feature flag should have their respective base - subgraphs be a part of this federated graph.{" "} + The labels of this feature flag match to that of this federated graph. But to be used for composition, the + feature subgraphs which are part of this feature flag should have their respective base subgraphs be a part + of this federated graph.{' '} } actions={[]} @@ -179,9 +156,7 @@ export const FeatureFlagDetails = ({
    Enabled
    - - {isEnabled ? "Enabled" : "Disabled"} - + {isEnabled ? 'Enabled' : 'Disabled'}
    @@ -192,10 +167,7 @@ export const FeatureFlagDetails = ({ {labels.length === 0 && ( - - - Only graphs with empty label matchers will compose this - subgraph - + Only graphs with empty label matchers will compose this subgraph )} {labels.map(({ key, value }) => { @@ -210,9 +182,7 @@ export const FeatureFlagDetails = ({
    Created By
    -
    - {createdBy || "unknown user"} -
    +
    {createdBy || 'unknown user'}
    Created At
    @@ -224,9 +194,7 @@ export const FeatureFlagDetails = ({ })} - - {formatDateTime(new Date(createdAt))} - + {formatDateTime(new Date(createdAt))}
    @@ -234,46 +202,29 @@ export const FeatureFlagDetails = ({
    - +
    {!slug && ( - - + + Federated Graphs )} {slug && ( - - + + Overview )} - + @@ -297,9 +248,8 @@ export const FeatureFlagDetails = ({ title="No associated federated graphs found." description={ <> - To associate a federated graph with this feature - flag, please try updating the labels of the feature - flag to match the required federated graphs.{" "} + To associate a federated graph with this feature flag, please try updating the labels of the + feature flag to match the required federated graphs.{' '} } actions={[]} diff --git a/studio/src/components/feature-flags-table.tsx b/studio/src/components/feature-flags-table.tsx index 61a98a6944..06307fb7e3 100644 --- a/studio/src/components/feature-flags-table.tsx +++ b/studio/src/components/feature-flags-table.tsx @@ -1,37 +1,28 @@ -import { useUser } from "@/hooks/use-user"; -import { docsBaseURL } from "@/lib/constants"; -import { CommandLineIcon } from "@heroicons/react/24/outline"; -import { TooltipContent, TooltipTrigger } from "@radix-ui/react-tooltip"; -import { - FeatureFlag, - FederatedGraph, -} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { formatDistanceToNow } from "date-fns"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { EmptyState } from "./empty-state"; -import { Badge } from "./ui/badge"; -import { Button } from "./ui/button"; -import { CLISteps } from "./ui/cli"; -import { Pagination } from "./ui/pagination"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, - TableWrapper, -} from "./ui/table"; -import { Tooltip } from "./ui/tooltip"; -import { useWorkspace } from "@/hooks/use-workspace"; +import { useUser } from '@/hooks/use-user'; +import { docsBaseURL } from '@/lib/constants'; +import { CommandLineIcon } from '@heroicons/react/24/outline'; +import { TooltipContent, TooltipTrigger } from '@radix-ui/react-tooltip'; +import { FeatureFlag, FederatedGraph } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { formatDistanceToNow } from 'date-fns'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { EmptyState } from './empty-state'; +import { Badge } from './ui/badge'; +import { Button } from './ui/button'; +import { CLISteps } from './ui/cli'; +import { Pagination } from './ui/pagination'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TableWrapper } from './ui/table'; +import { Tooltip } from './ui/tooltip'; +import { useWorkspace } from '@/hooks/use-workspace'; export const Empty = ({ graph }: { graph?: FederatedGraph }) => { - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); - let label = "[labels...]"; + let label = '[labels...]'; if (graph?.labelMatchers && graph.labelMatchers.length > 0) { - label = graph.labelMatchers[0].split(",")[0]; + label = graph.labelMatchers[0].split(',')[0]; } return ( { title="Create feature flag using CLI" description={ <> - No feature flags found. Use the CLI tool to create one.{" "} + No feature flags found. Use the CLI tool to create one.{' '} Learn more. @@ -54,16 +45,15 @@ export const Empty = ({ graph }: { graph?: FederatedGraph }) => { --namespace ${namespace} -r --subgraph `, }, { - description: - "Publish a feature subgraph using the below command.", + description: 'Publish a feature subgraph using the below command.', command: `npx wgc subgraph publish --namespace ${namespace} --schema `, }, { - description: "Create a feature flag using the below command.", + description: 'Create a feature flag using the below command.', command: `npx wgc feature-flag create --namespace ${namespace} --label ${label} --enabled --feature-subgraphs `, }, ]} @@ -86,14 +76,11 @@ export const FeatureFlagsTable = ({ const router = useRouter(); const organizationSlug = user?.currentOrganization.slug; - const pageNumber = router.query.page - ? parseInt(router.query.page as string) - : 1; - const limit = Number.parseInt((router.query.pageSize as string) || "10"); + const pageNumber = router.query.page ? parseInt(router.query.page as string) : 1; + const limit = Number.parseInt((router.query.pageSize as string) || '10'); const noOfPages = Math.ceil(totalCount / limit); - if (!featureFlags || featureFlags.length === 0) - return ; + if (!featureFlags || featureFlags.length === 0) return ; return ( <> @@ -111,83 +98,63 @@ export const FeatureFlagsTable = ({ - {featureFlags.map( - ({ - name, - labels, - createdAt, - updatedAt, - createdBy, - namespace, - isEnabled, - }) => { - const path = graph - ? `${router.asPath.split("?")[0]}/${name}` - : `/${organizationSlug}/feature-flags/${name}?namespace=${namespace}`; + {featureFlags.map(({ name, labels, createdAt, updatedAt, createdBy, namespace, isEnabled }) => { + const path = graph + ? `${router.asPath.split('?')[0]}/${name}` + : `/${organizationSlug}/feature-flags/${name}?namespace=${namespace}`; - return ( - router.push(path)} - > - {name} - - - {isEnabled ? "Enabled" : "Disabled"} - - - -
    - {labels.length === 0 && ( - - - - - Only graphs with empty label matchers will compose - this subgraph - - - )} - {labels.map(({ key, value }) => { - return ( - - {key}={value} - - ); - })} -
    -
    - - {createdBy || 'unknown user'} - - - {createdAt - ? formatDistanceToNow(new Date(createdAt), { - addSuffix: true, - }) - : "Never"} - - - {updatedAt - ? formatDistanceToNow(new Date(updatedAt), { - addSuffix: true, - }) - : "Never"} - - - - -
    - ); - }, - )} + return ( + router.push(path)} + > + {name} + + {isEnabled ? 'Enabled' : 'Disabled'} + + +
    + {labels.length === 0 && ( + + - + + Only graphs with empty label matchers will compose this subgraph + + + )} + {labels.map(({ key, value }) => { + return ( + + {key}={value} + + ); + })} +
    +
    + {createdBy || 'unknown user'} + + {createdAt + ? formatDistanceToNow(new Date(createdAt), { + addSuffix: true, + }) + : 'Never'} + + + {updatedAt + ? formatDistanceToNow(new Date(updatedAt), { + addSuffix: true, + }) + : 'Never'} + + + + +
    + ); + })}
    diff --git a/studio/src/components/federatedgraphs-cards.tsx b/studio/src/components/federatedgraphs-cards.tsx index 6510c074fc..0a54a35be5 100644 --- a/studio/src/components/federatedgraphs-cards.tsx +++ b/studio/src/components/federatedgraphs-cards.tsx @@ -1,64 +1,43 @@ -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useFireworks } from "@/hooks/use-fireworks"; -import { SubmitHandler, useZodForm } from "@/hooks/use-form"; -import { docsBaseURL } from "@/lib/constants"; -import { formatMetric } from "@/lib/format-metric"; -import { useChartData } from "@/lib/insights-helpers"; -import { cn } from "@/lib/utils"; -import { - ChevronDoubleRightIcon, - CommandLineIcon, - DocumentArrowDownIcon, -} from "@heroicons/react/24/outline"; -import { Component2Icon } from "@radix-ui/react-icons"; -import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; -import { migrateFromApollo } from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; -import { FederatedGraph } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import copy from "copy-to-clipboard"; -import { getTime, parseISO, subDays } from "date-fns"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { - Dispatch, - SetStateAction, - useContext, - useEffect, - useState, -} from "react"; -import { FiCheck, FiCopy } from "react-icons/fi"; -import { LuSquareDot } from "react-icons/lu"; -import { MdNearbyError } from "react-icons/md"; -import { SiApollographql } from "react-icons/si"; -import { Line, LineChart, ResponsiveContainer, XAxis } from "recharts"; -import { z } from "zod"; -import { UserContext } from "./app-provider"; -import { ComposeStatusMessage } from "./compose-status"; -import { ComposeStatusBulb } from "./compose-status-bulb"; -import { EmptyState } from "./empty-state"; -import { Logo } from "./logo"; -import { TimeAgo } from "./time-ago"; -import { Button } from "./ui/button"; -import { Card } from "./ui/card"; -import { CLI } from "./ui/cli"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "./ui/dialog"; -import { Input } from "./ui/input"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "./ui/tooltip"; -import { useToast } from "./ui/use-toast"; -import { useMutation } from "@connectrpc/connect-query"; -import { useCheckUserAccess } from "@/hooks/use-check-user-access"; -import { useWorkspace } from "@/hooks/use-workspace"; -import { useCurrentOrganization } from "@/hooks/use-current-organization"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { useFireworks } from '@/hooks/use-fireworks'; +import { SubmitHandler, useZodForm } from '@/hooks/use-form'; +import { docsBaseURL } from '@/lib/constants'; +import { formatMetric } from '@/lib/format-metric'; +import { useChartData } from '@/lib/insights-helpers'; +import { cn } from '@/lib/utils'; +import { ChevronDoubleRightIcon, CommandLineIcon, DocumentArrowDownIcon } from '@heroicons/react/24/outline'; +import { Component2Icon } from '@radix-ui/react-icons'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; +import { migrateFromApollo } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; +import { FederatedGraph } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import copy from 'copy-to-clipboard'; +import { getTime, parseISO, subDays } from 'date-fns'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react'; +import { FiCheck, FiCopy } from 'react-icons/fi'; +import { LuSquareDot } from 'react-icons/lu'; +import { MdNearbyError } from 'react-icons/md'; +import { SiApollographql } from 'react-icons/si'; +import { Line, LineChart, ResponsiveContainer, XAxis } from 'recharts'; +import { z } from 'zod'; +import { UserContext } from './app-provider'; +import { ComposeStatusMessage } from './compose-status'; +import { ComposeStatusBulb } from './compose-status-bulb'; +import { EmptyState } from './empty-state'; +import { Logo } from './logo'; +import { TimeAgo } from './time-ago'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { CLI } from './ui/cli'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from './ui/dialog'; +import { Input } from './ui/input'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'; +import { useToast } from './ui/use-toast'; +import { useMutation } from '@connectrpc/connect-query'; +import { useCheckUserAccess } from '@/hooks/use-check-user-access'; +import { useWorkspace } from '@/hooks/use-workspace'; +import { useCurrentOrganization } from '@/hooks/use-current-organization'; // this is required to render a blank line with LineChart const fallbackData = [ @@ -88,17 +67,15 @@ const MigrationDialog = ({ isEmptyState?: boolean; }) => { const router = useRouter(); - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); const organizationSlug = useCurrentOrganization()?.slug; const migrate = !!router.query.migrate; const migrateInputSchema = z.object({ - apiKey: z - .string() - .min(1, { message: "API Key must contain at least 1 character." }), - variantName: z - .string() - .min(1, { message: "Variant name must contain at least 1 character." }), + apiKey: z.string().min(1, { message: 'API Key must contain at least 1 character.' }), + variantName: z.string().min(1, { message: 'Variant name must contain at least 1 character.' }), }); type MigrateInput = z.infer; @@ -109,7 +86,7 @@ const MigrationDialog = ({ handleSubmit, reset, } = useZodForm({ - mode: "onBlur", + mode: 'onBlur', schema: migrateInputSchema, }); @@ -135,7 +112,7 @@ const MigrationDialog = ({ d.response?.code === EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED ) { toast({ - description: "Successfully migrated the graph.", + description: 'Successfully migrated the graph.', duration: 2000, }); refetch(); @@ -149,7 +126,7 @@ const MigrationDialog = ({ }, onError: (_) => { toast({ - description: "Could not migrate the graph. Please try again.", + description: 'Could not migrate the graph. Please try again.', duration: 3000, }); setOpen(false); @@ -165,7 +142,7 @@ const MigrationDialog = ({ @@ -187,61 +164,40 @@ const MigrationDialog = ({

    - The Graph API Key is the api key associated to the graph which - has to be migrated and it should be obtained from Apollo Studio. + The Graph API Key is the api key associated to the graph which has to be migrated and it should be + obtained from Apollo Studio.

    - Click{" "} + Click{' '} here - {" "} + {' '} to find the steps to obtain the key.

    - Note: This key is not stored and only used to fetch the - subgraphs. + Note: This key is not stored and only used to fetch the subgraphs.

    -
    +
    Graph API Key - - {errors.apiKey && ( - - {errors.apiKey.message} - - )} + + {errors.apiKey && {errors.apiKey.message}}
    - - Graph Variant Name - - + Graph Variant Name + {errors.variantName && ( - - {errors.variantName.message} - + {errors.variantName.message} )}
    -
    @@ -296,7 +252,7 @@ export const RunRouterCommand = ({ -e DEV_MODE=true \\ -e DEMO_MODE=true \\ -e LISTEN_ADDR=0.0.0.0:3002 \\ - -e GRAPH_API_TOKEN=${token ? token : ""} \\ + -e GRAPH_API_TOKEN=${token ? token : ''} \\ ghcr.io/wundergraph/cosmo/router:latest`; const dockerRunCmdElement = ( @@ -313,23 +269,14 @@ export const RunRouterCommand = ({ {` -e GRAPH_API_TOKEN=`} - {token ? ( - token - ) : ( - - {""} - - )}{" "} - \ + {token ? token : {''}} \ {` ghcr.io/wundergraph/cosmo/router:latest`}
    ); - const createTokenCommand = `npx wgc router token create ${ - namespace ? `-n ${namespace}` : "" - } -g ${graphName}`; + const createTokenCommand = `npx wgc router token create ${namespace ? `-n ${namespace}` : ''} -g ${graphName}`; const [copyDockerCommand, setCopyDockerCommand] = useState(false); const [copyTokenCommand, setCopyTokenCommand] = useState(false); @@ -377,7 +324,7 @@ export const RunRouterCommand = ({

    {`1. Create a Graph API Token using the below command. `} {`npx wgc router token create `} - - {""} - - {` ${namespace ? `-n ${namespace}` : ""} -g ${graphName}`} + {''} + {` ${namespace ? `-n ${namespace}` : ''} -g ${graphName}`}

    @@ -414,11 +353,11 @@ export const RunRouterCommand = ({

    {token - ? "Use the below command to initiate the router. " + ? 'Use the below command to initiate the router. ' : `2. Pass the token as GRAPH_API_TOKEN and run the below command to initiate the router. `} setCopyDockerCommand(true)} className="cursor-pointer" > -

    - {copyDockerCommand ? ( - - ) : ( - - )} -
    +
    {copyDockerCommand ? : }
    - {hint && ( -

    {`Hint: ${hint}`}

    - )} + {hint &&

    {`Hint: ${hint}`}

    }
    @@ -470,9 +401,11 @@ export const Empty = ({ setIsMigrating: Dispatch>; }) => { const checkUserAccess = useCheckUserAccess(); - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); - let labels = "team=A"; + let labels = 'team=A'; return ( - Use the CLI tool to create either a federated graph ({" "} + Use the CLI tool to create either a federated graph ({' '} docs - {" "} - ) or a monograph ({" "} - + {' '} + ) or a monograph ({' '} + docs - {" "} + {' '} ). } @@ -520,7 +448,7 @@ export const Empty = ({ - {checkUserAccess({ rolesToBe: ["organization-admin", "organization-developer"] }) && ( + {checkUserAccess({ rolesToBe: ['organization-admin', 'organization-developer'] }) && ( <> OR { graph.requestSeries.length > 0 ? graph.requestSeries : fallbackData, ); - const totalRequests = graph.requestSeries.reduce( - (total, r) => total + r.totalRequests, - 0, - ); + const totalRequests = graph.requestSeries.reduce((total, r) => total + r.totalRequests, 0); - const totalErrors = graph.requestSeries.reduce( - (total, r) => total + r.erroredRequests, - 0, - ); + const totalErrors = graph.requestSeries.reduce((total, r) => total + r.erroredRequests, 0); const parsedURL = () => { try { if (!graph.routingURL) { - return "No endpoint provided"; + return 'No endpoint provided'; } const { host, pathname } = new URL(graph.routingURL); - return host + (pathname === "/" ? "" : pathname); + return host + (pathname === '/' ? '' : pathname); } catch {} }; @@ -605,12 +527,9 @@ const GraphCard = ({ graph }: { graph: FederatedGraph }) => {

    {parsedURL()}

    @@ -627,7 +546,7 @@ const GraphCard = ({ graph }: { graph: FederatedGraph }) => { {graph.supportsFederation ? (

    {`${formatMetric(graph.connectedSubgraphs)} ${ - graph.connectedSubgraphs === 1 ? "subgraph" : "subgraphs" + graph.connectedSubgraphs === 1 ? 'subgraph' : 'subgraphs' }`}

    ) : ( @@ -641,7 +560,7 @@ const GraphCard = ({ graph }: { graph: FederatedGraph }) => {

    {`${formatMetric(totalErrors)} ${ - totalErrors === 1 ? "error" : "errors" + totalErrors === 1 ? 'error' : 'errors' }`}

    @@ -669,14 +588,10 @@ const GraphCard = ({ graph }: { graph: FederatedGraph }) => {

    {graph.lastUpdatedAt ? ( <> - Schema last updated{" "} - + Schema last updated ) : ( - "Not ready" + 'Not ready' )}

    @@ -696,13 +611,7 @@ const GraphCard = ({ graph }: { graph: FederatedGraph }) => { ); }; -export const FederatedGraphsCards = ({ - graphs, - refetch, -}: { - graphs?: FederatedGraph[]; - refetch: () => void; -}) => { +export const FederatedGraphsCards = ({ graphs, refetch }: { graphs?: FederatedGraph[]; refetch: () => void }) => { const [isMigrationSuccess, setIsMigrationSuccess] = useState(false); const [token, setToken] = useState(); const [isMigrating, setIsMigrating] = useState(false); @@ -746,7 +655,7 @@ export const FederatedGraphsCards = ({ {graphs.map((graph, graphIndex) => { return ; })} - {checkUserAccess({ rolesToBe: ["organization-admin", "organization-developer"] }) && ( + {checkUserAccess({ rolesToBe: ['organization-admin', 'organization-developer'] }) && ( { +const getLayoutedElements = (dagreGraph: dagre.graphlib.Graph, nodes: Node[], edges: Edge[]) => { dagreGraph.setGraph({ - rankdir: "LR", + rankdir: 'LR', nodesep: 30, ranksep: 20, }); @@ -119,7 +107,7 @@ function GraphVisualization({ const [edges, setEdges] = useState([]); const [showAll, setShowAll] = useState(false); - const [topCategory, setTopCategory] = useState("latency"); + const [topCategory, setTopCategory] = useState('latency'); const subgraphs = useMemo(() => { let tempSubgraphs = [...(graphData?.subgraphs ?? [])]; @@ -132,10 +120,10 @@ function GraphVisualization({ return 0; } - if (topCategory === "latency") { + if (topCategory === 'latency') { return metricB.latency - metricA.latency; } - if (topCategory === "errorRate") { + if (topCategory === 'errorRate') { return metricB.errorRate - metricA.errorRate; } @@ -153,14 +141,14 @@ function GraphVisualization({ if (!graphData?.graph) return; const buildGraphs = (subgraphs: Subgraph[]): Graph[] => { - const rootName = supportsFederation ? graphData.graph?.name : "router"; + const rootName = supportsFederation ? graphData.graph?.name : 'router'; const graphs: Graph[] = [ { id: `root-${rootName}`, - kind: "graph", + kind: 'graph', name: rootName!, - parentId: "", + parentId: '', errorRate: federatedGraphMetrics?.errorRate, requestRate: federatedGraphMetrics?.requestRate, }, @@ -169,7 +157,7 @@ function GraphVisualization({ graphs.push({ id: `root-${graphData.graph?.name}-${subgraph.name}}`, subgraphId: subgraph.id, - kind: "subgraph", + kind: 'subgraph', name: subgraph.name, parentId: graphs[0].id, }); @@ -181,10 +169,10 @@ function GraphVisualization({ const buildNodes = (spans: Graph[]): Node[] => { return spans.map((span, index) => { - if (span.kind === "graph") { + if (span.kind === 'graph') { return { id: span.id, - type: "span", + type: 'span', data: { label: span.name, kind: span.kind, @@ -200,12 +188,10 @@ function GraphVisualization({ }, }; } - const sm = subgraphMetrics?.find( - (x) => x.subgraphID === span.subgraphId, - ); + const sm = subgraphMetrics?.find((x) => x.subgraphID === span.subgraphId); return { id: span.id, - type: "span", + type: 'span', data: { label: span.name, kind: span.kind, @@ -227,15 +213,13 @@ function GraphVisualization({ return spans .filter((s) => !!s.parentId) .map((span, index) => { - const sm = subgraphMetrics?.find( - (x) => x.subgraphID === span.subgraphId, - ); + const sm = subgraphMetrics?.find((x) => x.subgraphID === span.subgraphId); return { id: span.id, source: span.parentId, animated: true, target: span.id, - type: "metricsEdge", + type: 'metricsEdge', data: { latency: sm?.latency, }, @@ -258,33 +242,18 @@ function GraphVisualization({ return { minlen: 5, weight: 1 }; }); - const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( - dagreGraph, - n, - e, - ); + const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(dagreGraph, n, e); setNodes(layoutedNodes); setEdges(layoutedEdges); - }, [ - graphData?.graph, - subgraphs, - subgraphMetrics, - federatedGraphMetrics, - supportsFederation, - ]); + }, [graphData?.graph, subgraphs, subgraphMetrics, federatedGraphMetrics, supportsFederation]); const [nodeStates, setNodeStates, onNodesChange] = useNodesState(nodes); const [edgeStates, setEdgeStates, onEdgesChange] = useEdgesState(edges); const onConnect = useCallback( (params: Edge) => - setEdges((eds) => - addEdge( - { ...params, type: ConnectionLineType.SmoothStep, animated: true }, - eds, - ), - ), + setEdges((eds) => addEdge({ ...params, type: ConnectionLineType.SmoothStep, animated: true }, eds)), [], ); @@ -317,25 +286,18 @@ function GraphVisualization({ edgeTypes={edgeTypes} > - +

    - - Graph Metrics - + Graph Metrics

    - - Latency & Request Per Minute (RPM) - + Latency & Request Per Minute (RPM)
    { - if (v === "all") { + if (v === 'all') { setShowAll(true); } else { setShowAll(false); @@ -362,15 +324,11 @@ function GraphVisualization({
    - reactFlowInstance.fitView(defaultZoom)} - > + reactFlowInstance.fitView(defaultZoom)}> diff --git a/studio/src/components/group-select.tsx b/studio/src/components/group-select.tsx index 0422cbfd5a..762594f4c9 100644 --- a/studio/src/components/group-select.tsx +++ b/studio/src/components/group-select.tsx @@ -1,12 +1,18 @@ -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { OrganizationGroup } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; -import { useQuery } from "@connectrpc/connect-query"; -import { getOrganizationGroups } from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; -import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; -import { Button } from "@/components/ui/button"; -import { useIsAdmin } from "@/hooks/use-is-admin"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { OrganizationGroup } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { useQuery } from '@connectrpc/connect-query'; +import { getOrganizationGroups } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; +import { Button } from '@/components/ui/button'; +import { useIsAdmin } from '@/hooks/use-is-admin'; -export function GroupSelect({ id, value, disabled = false, groups, onValueChange }: { +export function GroupSelect({ + id, + value, + disabled = false, + groups, + onValueChange, +}: { id?: string; value?: string; disabled?: boolean; @@ -16,22 +22,12 @@ export function GroupSelect({ id, value, disabled = false, groups, onValueChange const isAdmin = useIsAdmin(); const { data, isPending, error, refetch } = useQuery(getOrganizationGroups, {}, { enabled: groups === undefined }); if (isPending) { - return ( - ); @@ -39,7 +35,7 @@ export function GroupSelect({ id, value, disabled = false, groups, onValueChange const availableGroups = groups ?? data?.groups ?? []; const activeGroup = availableGroups.find((group) => group.groupId === value); - const groupLabel = activeGroup?.name ?? "Select a group"; + const groupLabel = activeGroup?.name ?? 'Select a group'; return ( ); -} \ No newline at end of file +} diff --git a/studio/src/components/info-tooltip.tsx b/studio/src/components/info-tooltip.tsx index 3f9c1d79c8..ac153827fb 100644 --- a/studio/src/components/info-tooltip.tsx +++ b/studio/src/components/info-tooltip.tsx @@ -1,11 +1,6 @@ -import { FiInfo } from "react-icons/fi"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, - TooltipArrow, -} from "./ui/tooltip"; -import { cn } from "@/lib/utils"; +import { FiInfo } from 'react-icons/fi'; +import { Tooltip, TooltipContent, TooltipTrigger, TooltipArrow } from './ui/tooltip'; +import { cn } from '@/lib/utils'; export interface InfoTooltipProps { children: React.ReactNode; @@ -18,12 +13,7 @@ export const InfoTooltip: React.FC = (props) => { return ( - + diff --git a/studio/src/components/layout/analytics/active-campaign-script.tsx b/studio/src/components/layout/analytics/active-campaign-script.tsx index 92da8e2018..31839f8ea1 100644 --- a/studio/src/components/layout/analytics/active-campaign-script.tsx +++ b/studio/src/components/layout/analytics/active-campaign-script.tsx @@ -1,4 +1,4 @@ -import Script from "next/script"; +import Script from 'next/script'; const ACTIVE_CAMPAIGN_SCRIPT_SRC = 'https://diffuser-cdn.app-us1.com/diffuser/diffuser.js'; @@ -57,4 +57,4 @@ export function ActiveCampaignScript() { })(); `} ); -} \ No newline at end of file +} diff --git a/studio/src/components/layout/analytics/gtm-script.tsx b/studio/src/components/layout/analytics/gtm-script.tsx index eaf2919348..c3f607c7f0 100644 --- a/studio/src/components/layout/analytics/gtm-script.tsx +++ b/studio/src/components/layout/analytics/gtm-script.tsx @@ -1,6 +1,6 @@ export interface GtmScriptProps { gtmId: string | undefined; -}; +} export function GtmScript({ gtmId }: GtmScriptProps) { if (!gtmId) { @@ -11,7 +11,8 @@ export function GtmScript({ gtmId }: GtmScriptProps) { <>