Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ export class MetricsRepository {
WHERE Timestamp >= startDate AND Timestamp <= endDate
AND OrganizationID = {organizationId:String}
AND FederatedGraphID = {federatedGraphId:String}
AND OperationHash IS NOT NULL AND OperationHash != ''
Comment thread
coderabbitai[bot] marked this conversation as resolved.
${whereSql ? `AND ${whereSql}` : ''}
${searchSql}
${introspectionFilter}
Expand Down Expand Up @@ -901,6 +902,7 @@ export class MetricsRepository {
WHERE Timestamp >= startDate AND Timestamp <= endDate
AND OrganizationID = {organizationId:String}
AND FederatedGraphID = {federatedGraphId:String}
AND OperationHash IS NOT NULL AND OperationHash != ''
${whereSql ? `AND ${whereSql}` : ''}
${searchSql}
${introspectionFilter}
Expand Down Expand Up @@ -939,6 +941,7 @@ export class MetricsRepository {
WHERE Timestamp >= startDate AND Timestamp <= endDate
AND OrganizationID = {organizationId:String}
AND FederatedGraphID = {federatedGraphId:String}
AND OperationHash IS NOT NULL AND OperationHash != ''
${whereSql ? `AND ${whereSql}` : ''}
${searchSql}
${introspectionFilter}
Expand Down Expand Up @@ -1053,6 +1056,7 @@ export class MetricsRepository {
WHERE Timestamp >= startDate AND Timestamp <= endDate
AND OrganizationID = {organizationId:String}
AND FederatedGraphID = {federatedGraphId:String}
AND OperationHash IS NOT NULL AND OperationHash != ''
${whereSql ? `AND ${whereSql}` : ''}
${searchSql}
${introspectionFilter}
Expand Down Expand Up @@ -1092,6 +1096,7 @@ export class MetricsRepository {
WHERE Timestamp >= startDate AND Timestamp <= endDate
AND OrganizationID = {organizationId:String}
AND FederatedGraphID = {federatedGraphId:String}
AND OperationHash IS NOT NULL AND OperationHash != ''
${whereSql ? `AND ${whereSql}` : ''}
${searchSql}
${introspectionFilter}
Expand Down
2 changes: 1 addition & 1 deletion studio/src/components/operations/client-usage-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const ClientUsageTable = ({
</TableCell>
<TableCell className="w-[25%]">
<Badge variant="outline" className="text-xs">
{client.version}
{client.version || '-'}
</Badge>
</TableCell>
<TableCell className="w-[15%] text-center">
Expand Down
94 changes: 58 additions & 36 deletions studio/src/components/operations/deprecated-fields-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
TableWrapper,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { useRouter } from "next/router";
import { Separator } from "../ui/separator";

interface DeprecatedField {
fieldName: string;
Expand Down Expand Up @@ -96,48 +96,70 @@ export const DeprecatedFieldsTable = ({
<Table>
<TableHeader>
<TableRow>
<TableHead>Field Path</TableHead>
<TableHead className="w-[150px]">Actions</TableHead>
<TableHead className="w-[80%]">Field Path</TableHead>
<TableHead className="w-[20%] text-center">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={2} className="h-24 text-center">
<Loader />
</TableCell>
</TableRow>
) : hasDeprecatedFields ? (
deprecatedFields.map((field, index) => (
<TableRow key={`${field.path}-${index}`}>
<TableCell>
<code className="rounded bg-muted px-2 py-1 font-mono text-sm">
{field.path}
</code>
</Table>
<div className="scrollbar-custom max-h-[232px] flex-1 overflow-y-auto">
<Table>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={2} className="h-24 text-center">
<Loader />
</TableCell>
<TableCell>
<Button
variant="outline"
size="sm"
onClick={() => handleShowUsage(field)}
>
Show Usage
</Button>
</TableRow>
) : hasDeprecatedFields ? (
deprecatedFields.map((field, index) => (
<TableRow key={`${field.path}-${index}`}>
<TableCell className="w-[80%]">
<code className="rounded bg-muted px-2 py-1 font-mono text-sm">
{field.path}
</code>
</TableCell>
<TableCell className="w-[20%] text-right">
<Button
variant="outline"
size="sm"
onClick={() => handleShowUsage(field)}
>
Show Usage
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={2}
className="text-center text-muted-foreground"
>
No deprecated fields found
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={2}
className="text-center text-muted-foreground"
>
No deprecated fields found
)}
</TableBody>
</Table>
</div>
{!isLoading && hasDeprecatedFields && (
<Table className="w-full border-t">
<TableFooter>
<TableRow className="border-b-0 bg-background hover:bg-background">
<TableCell colSpan={2}>
<div className="flex items-center justify-center space-x-1 text-xs text-muted-foreground">
<span>
Found {deprecatedFields.length}{" "}
{deprecatedFields.length === 1
? "deprecated field"
: "deprecated fields"}
</span>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableFooter>
</Table>
)}
</TableWrapper>
</div>
);
Expand Down
75 changes: 65 additions & 10 deletions studio/src/components/operations/operations-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,14 @@ const OperationItem = ({
case "errors":
return operation.errorRate && operation.errorRate > 0
? `${operation.errorRate.toFixed(2)}%`
: null;
: "-";
default:
return null;
}
};

const selectedMetric = getSelectedMetric();
const isLatencyAtCap = sortField === "latency" && operation.latency >= 10000;

return (
<div
Expand Down Expand Up @@ -211,8 +212,21 @@ const OperationItem = ({
)}
</div>
{selectedMetric && (
<div className="flex-shrink-0 whitespace-nowrap text-xs font-medium text-muted-foreground">
{selectedMetric}
<div className="flex flex-shrink-0 items-center gap-1 whitespace-nowrap text-xs font-medium text-muted-foreground">
<span>{selectedMetric}</span>
{isLatencyAtCap && (
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<span className="cursor-help">*</span>
</TooltipTrigger>
<TooltipContent>
<p>
This operation may have taken longer than 10s. The displayed
10s represents the maximum latency bucket (10s+).
</p>
</TooltipContent>
</Tooltip>
)}
</div>
)}
</div>
Expand All @@ -229,6 +243,38 @@ export const OperationsList = ({
className,
sortField = "requests",
}: OperationsListProps) => {
// Optimistic local state for immediate UI feedback
const [optimisticSelection, setOptimisticSelection] = useState<{
hash: string;
name: string;
} | null>(null);

// Use ref to track previous selectedOperation values to compare
const prevHashRef = useRef<string | undefined>(undefined);
const prevNameRef = useRef<string | undefined>(undefined);

// Only update if values actually changed to prevent unnecessary re-renders
useEffect(() => {
const currentHash = selectedOperation?.hash;
const currentName = selectedOperation?.name;

// Check if values actually changed
const hasChanged =
prevHashRef.current !== currentHash ||
prevNameRef.current !== currentName;

if (hasChanged) {
if (selectedOperation) {
setOptimisticSelection(selectedOperation);
} else {
// Clear optimistic selection when prop is cleared
setOptimisticSelection(null);
}
prevHashRef.current = currentHash;
prevNameRef.current = currentName;
}
}, [selectedOperation]);

if (isLoading) {
return (
<div className={cn("flex h-64 items-center justify-center", className)}>
Expand Down Expand Up @@ -265,21 +311,30 @@ export const OperationsList = ({
// In that case, only match operations with empty name
// If selectedOperationName has a value, match operations with that exact name
const operationName = operation.name || "";

// Use optimistic selection for immediate feedback, fallback to prop
const currentSelection = optimisticSelection || selectedOperation;
const isSelected =
selectedOperation?.hash === operation.hash &&
(selectedOperation?.name === null ||
selectedOperation?.name === undefined
currentSelection?.hash === operation.hash &&
(currentSelection?.name === null ||
currentSelection?.name === undefined
? true // No name filter set, match by hash only
: selectedOperation?.name === operationName); // Name filter exists, match exact name
: currentSelection?.name === operationName); // Name filter exists, match exact name

return (
<OperationItem
key={`${operation.hash}-${operation.name || ""}`}
operation={operation}
isSelected={isSelected}
onClick={() =>
onOperationSelect(operation.hash, operation.name || "")
}
onClick={() => {
// Update optimistic state immediately for instant UI feedback
setOptimisticSelection({
hash: operation.hash,
name: operationName,
});
// Then update URL (which will eventually sync back via prop)
onOperationSelect(operation.hash, operationName);
}}
searchQuery={searchQuery}
sortField={sortField}
/>
Expand Down
44 changes: 29 additions & 15 deletions studio/src/components/operations/operations-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
MagnifyingGlassIcon,
XMarkIcon,
Expand Down Expand Up @@ -222,21 +227,30 @@ export const OperationsSearch = ({

{/* Sort Controls */}
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
onClick={handleSortDirectionToggle}
className="h-6 w-6"
aria-label={`Sort ${
sortDirection === "desc" ? "ascending" : "descending"
}`}
>
{sortDirection === "desc" ? (
<BarsArrowDownIcon className="h-4 w-4" />
) : (
<BarsArrowUpIcon className="h-4 w-4" />
)}
</Button>
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={handleSortDirectionToggle}
className="h-6 w-6"
aria-label={`Sort ${
sortDirection === "desc" ? "ascending" : "descending"
}`}
>
{sortDirection === "desc" ? (
<BarsArrowDownIcon className="h-4 w-4" />
) : (
<BarsArrowUpIcon className="h-4 w-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>
Sort {sortDirection === "desc" ? "ascending" : "descending"}
</p>
</TooltipContent>
</Tooltip>
<Select
value={fetchBasedOn.toString()}
onValueChange={(value) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ const OperationsPage: NextPageWithLayout = () => {
placeholderData: keepPreviousData,
// Ensure query refetches when filters change
refetchOnMount: true,
// Disable refetch on window focus
refetchOnWindowFocus: false,
// Prevent stale data accumulation
staleTime: 0,
gcTime: 0,
Expand Down
Loading