Skip to content

Conversation

@ken-zlai
Copy link
Contributor

@ken-zlai ken-zlai commented Nov 19, 2024

Summary

Hopefully the last massive PR, since we have a lot of the baseline implemented here. Here is a video walkthrough.

Main changes are noted:

  • Figma match (left sidebar, models table, observability page)
  • Geist font is used in ECharts
  • Geist font has proper smoothness to match figma
  • Custom tooltip treatment below chart on hover (hold cmd to lock and keep it open)
  • Drill down charts using sample data

Checklist

  • Added Unit Tests
  • Covered by existing CI
  • Integration tested
  • Documentation update

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced new components: ActionButtons, TrueFalseBadge, CustomEChartLegend, EChartTooltip, and InfoTooltip.
    • Enhanced date range selection with new options and improved handling.
    • Added custom tooltip functionality for charts, improving interactivity.
    • Implemented a new load function for redirecting users from the root path to /models.
  • Improvements

    • Updated styling for various components, enhancing visual consistency and user experience.
    • Refactored navigation and layout components for better usability.
    • Enhanced chart interactions and visibility management in the model performance visualization.
    • Improved color management system with new CSS custom properties.
    • Updated font size and color configurations in Tailwind CSS for better customization.
  • Bug Fixes

    • Corrected typos and improved variable naming for clarity.
  • Chores

    • Updated dependencies and improved documentation for better maintainability.

.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--background: var(--neutral-50);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love that we are parameterizing!

Copy link
Contributor

@nikhil-zlai nikhil-zlai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! but i am the wrong person to approving I think.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (6)
frontend/src/lib/components/EChartTooltip/EChartTooltip.svelte (2)

11-28: Consider extracting series type and adding validation.

Two suggestions for improvement:

  1. Extract the series type to a separate interface for reusability
  2. Add runtime validation for xValue when null

Consider this refactor:

+interface ChartSeries {
+  name: string | undefined;
+  value: number;
+  color: string;
+}

 let {
   visible,
   xValue,
   series,
   clickable = false,
   xAxisCategories = undefined
 }: {
   visible: boolean;
   xValue: number | null;
-  series: Array<{
-    name: string | undefined;
-    value: number;
-    color: string;
-  }>;
+  series: Array<ChartSeries>;
   clickable?: boolean;
   xAxisCategories?: string[];
 } = $props();

+$: if (xValue === null && visible) {
+  console.warn('Tooltip is visible but xValue is null');
+}

32-48: Enhance type safety and defensive programming.

The helper functions could benefit from:

  1. Custom event type for handleSeriesClick
  2. More defensive programming in getTooltipTitle

Consider these improvements:

+interface SeriesClickEvent {
+  componentType: 'series';
+  data: [number | null, number];
+  seriesName: string | undefined;
+}

-function handleSeriesClick(item: (typeof series)[number]) {
+function handleSeriesClick(item: (typeof series)[number]): void {
-  dispatch('click', {
+  dispatch<SeriesClickEvent>('click', {
     componentType: 'series',
     data: [xValue, item.value],
     seriesName: item.name
   });
 }

 function getTooltipTitle(xValue: number | null, xAxisCategories?: string[]): string {
   if (xValue === null) return '';

-  if (xAxisCategories && typeof xValue === 'number' && Number.isInteger(xValue)) {
+  if (xAxisCategories?.length && typeof xValue === 'number' && Number.isInteger(xValue)) {
+    if (xValue < 0 || xValue >= xAxisCategories.length) {
+      console.warn(`xValue ${xValue} is out of bounds for xAxisCategories`);
+      return '';
+    }
     return xAxisCategories[xValue];
   }

   return formatDate(xValue);
 }
frontend/src/lib/components/EChart/EChart.svelte (3)

14-16: Consider documenting the reason for the height change

The height property has been changed from '200px' to '230px'. While this change might be necessary for accommodating the new tooltip, it would be helpful to document the reason for this specific value to prevent accidental modifications in the future.


87-102: Consider cross-platform keyboard shortcuts

The keyboard event handlers check for both metaKey (Mac) and ctrlKey (Windows/Linux), which is good. However, consider extracting this check into a reusable function for better maintainability and consistency across the codebase.

+function isModifierKeyPressed(event: KeyboardEvent): boolean {
+  return event.metaKey || event.ctrlKey;
+}

 function handleKeyDown(event: KeyboardEvent) {
-  if (event.metaKey || event.ctrlKey) {
+  if (isModifierKeyPressed(event)) {
     isCommandPressed = true;
     disableChartInteractions();
   }
 }

 function handleKeyUp(event: KeyboardEvent) {
-  if (!event.metaKey && !event.ctrlKey) {
+  if (!isModifierKeyPressed(event)) {
     isCommandPressed = false;
     enableChartInteractions();
     if (!isMouseOverTooltip) {
       isTooltipVisible = false;
     }
   }
 }

324-329: Enhance chart accessibility

While adding role="application" is a good start, consider adding more ARIA attributes to improve accessibility:

 <div
   bind:this={chartDiv}
   ondblclick={handleDoubleClick}
   role="application"
+  aria-label="Interactive chart"
+  aria-description={option.title?.text?.toString() ?? 'Chart visualization'}
   style="width: 100%; height: 100%;"
 ></div>
frontend/src/routes/models/[slug]/+page.svelte (1)

1-39: Consider improving type safety for event handlers

The event handlers could benefit from stronger typing. Consider creating dedicated types for chart events and zoom events to improve type safety and maintainability.

+interface ChartZoomEvent {
+  start?: number;
+  end?: number;
+  batch?: Array<{ startValue: number; endValue: number }>;
+}
+
+interface ChartClickEvent extends ECElementEvent {
+  fromTooltip?: boolean;
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3da7141 and fe14b28.

📒 Files selected for processing (3)
  • frontend/src/lib/components/EChart/EChart.svelte (5 hunks)
  • frontend/src/lib/components/EChartTooltip/EChartTooltip.svelte (1 hunks)
  • frontend/src/routes/models/[slug]/+page.svelte (1 hunks)
🔇 Additional comments (2)
frontend/src/lib/components/EChartTooltip/EChartTooltip.svelte (1)

1-10: LGTM! Well-structured imports and TypeScript setup.

The imports are well-organized and the TypeScript setup is appropriate for the component's needs.

frontend/src/lib/components/EChart/EChart.svelte (1)

123-125: Enhance bar chart type detection

The current bar chart detection might be fragile as it only checks the first series. Consider handling cases where different series types are mixed in the same chart.

-isBarChart = series?.[0]?.type === 'bar';
+isBarChart = series?.every(s => s.type === 'bar') ?? false;

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
frontend/src/lib/components/NavigationBar/NavigationBar.svelte (5)

Line range hint 48-54: Add cleanup for debounced search function

The debounced search function should be cleaned up when the component is destroyed to prevent potential memory leaks.

Move the debounce initialization into onMount and clean it up in its cleanup function:

-const debouncedSearch = debounce(async () => {
+let debouncedSearch: Function;
+
+onMount(() => {
+  debouncedSearch = debounce(async () => {
     if (input.length > 0) {
       const response = await search(input);
       searchResults = response.items;
     } else {
       searchResults = [];
     }
-  }, 300);
+  }, 300);
+
+  return () => {
+    debouncedSearch.cancel();
+  };
+});
-onDestroy(() => {
-  debouncedSearch.cancel();
-});

90-91: Add aria-label to logo button

The logo button lacks an accessible label for screen readers.

-<Button variant="link" href="/" class="p-0 h-6 w-6">
+<Button variant="link" href="/" class="p-0 h-6 w-6" aria-label="Go to homepage">
   <img src="/logo.png" alt="Zipline Logo" />
</Button>

Line range hint 164-165: Replace alert() with a proper notification system

Using window.alert() for user interactions is not recommended as it blocks the UI and provides a poor user experience.

Consider implementing a proper notification system or toast messages. Would you like me to provide an example implementation?


Line range hint 162-166: Enhance dropdown menu accessibility

The dropdown menu items lack proper ARIA attributes and keyboard navigation support.

-<DropdownMenuContent>
+<DropdownMenuContent aria-label="User menu">
   <DropdownMenuItem
+    role="menuitem"
+    aria-label="Open settings"
     onclick={() => alert('Settings clicked')}>Settings</DropdownMenuItem>
   <DropdownMenuItem
+    role="menuitem"
+    aria-label="Sign out"
     onclick={() => alert('Sign out clicked')}>Sign out</DropdownMenuItem>
 </DropdownMenuContent>

180-191: Add tooltips to explain disabled actions

The quick actions are marked as disabled but don't provide any context to users about why they're unavailable or when they'll become available.

Consider adding tooltips to explain why these actions are disabled and what users need to do to enable them. For example:

-<CommandItem disabled>
+<CommandItem disabled title="This feature is coming soon">
   <Icon src={ExclamationTriangle} micro size="16" />
   Show only models with alerts
 </CommandItem>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fe14b28 and 019bb48.

📒 Files selected for processing (3)
  • frontend/src/lib/components/EChartTooltip/EChartTooltip.svelte (1 hunks)
  • frontend/src/lib/components/NavigationBar/NavigationBar.svelte (5 hunks)
  • frontend/src/lib/components/ui/badge/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/lib/components/EChartTooltip/EChartTooltip.svelte
  • frontend/src/lib/components/ui/badge/index.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
frontend/src/routes/models/[slug]/+page.svelte (2)

69-89: Extract color array to constants file

The color array should be moved to a separate constants file for better maintainability and reusability.

Create a new file src/lib/constants/colors.ts:

export const CHART_COLORS = [
  '#E5174B', '#E54D4A', '#E17545', '#E3994C', '#DFAF4F',
  '#87BE52', '#53B167', '#4DA67D', '#4EA797', '#4491CE',
  '#4592CC', '#4172D2', '#5B5AD1', '#785AD4', '#9055D5',
  '#BF50D3', '#CB5587'
];

Then update the code:

+import { CHART_COLORS } from '$lib/constants/colors';
+
 function createChartOption(
   customOption: Partial<EChartOption> = {},
   customColors = false
 ): EChartOption {
   const defaultOption: EChartOption = {
-    color: customColors
-      ? [
-          '#E5174B',
-          '#E54D4A',
-          // ... other colors
-          '#CB5587'
-        ]
-      : undefined,
+    color: customColors ? CHART_COLORS : undefined,

179-196: Optimize chart option computation with memoization

The chart options are recomputed on every state change. Consider memoizing the computation to improve performance.

Apply this diff to add memoization:

+import { memoize } from 'lodash';
+
+const createMemoizedChartOption = memoize(
+  (chartData: [string, number][], min: number, max: number, interval: number) =>
+    createChartOption(
+      {
+        yAxis: { min, max, interval },
+        series: [{ data: chartData, type: 'line' }]
+      },
+      true
+    ),
+  (chartData, min, max, interval) =>
+    `${JSON.stringify(chartData)}-${min}-${max}-${interval}`
+);
+
 const performanceChartOption = $derived(
-  createChartOption(
-    {
-      yAxis: {
-        min: 0,
-        max: 1,
-        interval: 0.2
-      },
-      series: [
-        {
-          data: chartData,
-          type: 'line'
-        }
-      ]
-    },
-    true
-  )
+  createMemoizedChartOption(chartData, 0, 1, 0.2)
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 019bb48 and a985fd5.

📒 Files selected for processing (1)
  • frontend/src/routes/models/[slug]/+page.svelte (1 hunks)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
frontend/src/lib/components/EChart/EChart.svelte (3)

17-17: Consider using a more specific type for markPoint.

The markPoint prop is typed as ECElementEvent, but it's only used for its data property which contains number arrays. Consider creating a more specific type to better represent the expected structure.

-markPoint?: ECElementEvent;
+type MarkPointData = {
+  data: [number, number];
+};
+markPoint?: MarkPointData;

Also applies to: 27-27


61-70: Add type safety to tooltip configuration.

The customTooltipOption object could benefit from explicit typing to ensure type safety and better IDE support.

-const customTooltipOption = {
+const customTooltipOption: EChartOption = {
   tooltip: {
     formatter: () => '',
     trigger: 'axis',
     axisPointer: {
       type: 'line'
     },
     position: 'top'
   }
 };

384-389: Enhance tooltip accessibility.

The tooltip container could benefit from additional ARIA attributes to improve screen reader support.

 <div
   class="relative z-50 inline-block"
   role="tooltip"
+  aria-live="polite"
+  aria-atomic="true"
   onmouseenter={handleTooltipMouseEnter}
   onmouseleave={handleTooltipMouseLeave}
 >
frontend/src/routes/models/[slug]/+page.svelte (1)

700-840: Add error boundary for dialog content

The dialog content contains complex chart rendering and data processing that could fail. Consider adding error boundaries to gracefully handle failures.

Consider wrapping the dialog content with an error boundary component:

 <Dialog bind:open={isSheetOpen}>
   <DialogContent class="max-w-[85vw] h-[95vh] flex flex-col p-0">
+    <ErrorBoundary
+      fallback={(error) => (
+        <div class="p-4 text-error">
+          <h3>Error loading chart details</h3>
+          <p>{error.message}</p>
+        </div>
+      )}
+    >
       <!-- existing dialog content -->
+    </ErrorBoundary>
   </DialogContent>
 </Dialog>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a985fd5 and bf02c0a.

📒 Files selected for processing (2)
  • frontend/src/lib/components/EChart/EChart.svelte (5 hunks)
  • frontend/src/routes/models/[slug]/+page.svelte (1 hunks)
🔇 Additional comments (4)
frontend/src/lib/components/EChart/EChart.svelte (2)

211-214: Improve null safety in tooltip positioning.

The code uses non-null assertion (!) which could lead to runtime errors. Consider adding proper null checks and early returns.


261-268: Add safety check for empty data array.

The reduce operation on firstSeries.data could throw if the array is empty. Consider adding a safety check.

frontend/src/routes/models/[slug]/+page.svelte (2)

358-371: 🛠️ Refactor suggestion

Improve request handling in selectSeries

The function should handle request cancellation for race conditions and add proper timeout handling.

Apply this diff to improve the implementation:

+let currentRequest: AbortController | null = null;
+
 async function selectSeries(seriesName: string | undefined) {
   selectedSeries = seriesName;
   if (seriesName) {
     const { startTimestamp, endTimestamp } = parseDateRangeParams(
       new URL($page.url).searchParams
     );
     try {
+      // Cancel any in-flight request
+      currentRequest?.abort();
+      currentRequest = new AbortController();
+      
+      const timeoutId = setTimeout(() => currentRequest?.abort(), 5000);
       percentileData = await getFeatureTimeseries(seriesName, startTimestamp, endTimestamp);
+      clearTimeout(timeoutId);
     } catch (error) {
+      if (error.name === 'AbortError') {
+        console.log('Request cancelled');
+        return;
+      }
       console.error('Error fetching percentile data:', error);
       percentileData = null;
     }
   }
 }

Likely invalid or redundant comment.


64-67: 🛠️ Refactor suggestion

Add input validation for chart options

The createChartOption function should validate its input parameters to prevent runtime errors.

Apply this diff to add validation:

 function createChartOption(
   customOption: Partial<EChartOption> = {},
   customColors = false
 ): EChartOption {
+  if (customOption.series && !Array.isArray(customOption.series) && typeof customOption.series !== 'object') {
+    throw new Error('Invalid series configuration');
+  }

Likely invalid or redundant comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
frontend/src/lib/components/EChart/EChart.svelte (2)

64-73: Consider making tooltip configuration more flexible.

The hardcoded tooltip configuration could be made more configurable to support different use cases.

Consider extracting the configuration to a separate constant or making it customizable through props:

-		const customTooltipOption = {
-			tooltip: {
-				formatter: () => '',
-				trigger: 'axis',
-				axisPointer: {
-					type: 'line'
-				},
-				position: 'top'
-			}
-		};
+		const customTooltipOption = {
+			tooltip: {
+				formatter: props.tooltipFormatter ?? (() => ''),
+				trigger: props.tooltipTrigger ?? 'axis',
+				axisPointer: {
+					type: props.axisPointerType ?? 'line'
+				},
+				position: props.tooltipPosition ?? 'top'
+			}
+		};

401-408: Simplify tooltip click handling and improve type safety.

The click event dispatch and xAxisCategories access could be more robust.

Consider these improvements:

+type TooltipClickEvent = {
+	detail: unknown;
+	fromTooltip: boolean;
+};

+function getXAxisCategories(chartInstance: echarts.ECharts | null): string[] | undefined {
+	if (!chartInstance || !isBarChart) return undefined;
+	const xAxis = chartInstance.getOption()?.xAxis;
+	return Array.isArray(xAxis) ? (xAxis[0]?.data as string[]) : undefined;
+}

-xAxisCategories={isBarChart && chartInstance
-	? ((chartInstance.getOption()?.xAxis as EChartOption.XAxis[])?.[0]?.data as string[])
-	: undefined}
-on:click={(event) =>
-	dispatch('click', {
-		detail: event.detail,
-		fromTooltip: true
-	})}
+xAxisCategories={getXAxisCategories(chartInstance)}
+on:click={(event) => dispatch<TooltipClickEvent>('click', {
+	detail: event.detail,
+	fromTooltip: true
+})}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bf02c0a and f27da59.

📒 Files selected for processing (2)
  • frontend/src/lib/components/EChart/EChart.svelte (6 hunks)
  • frontend/src/lib/components/EChartTooltip/EChartTooltip.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/lib/components/EChartTooltip/EChartTooltip.svelte

Copy link
Contributor

@piyush-zlai piyush-zlai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a look at a high level - probably will be worth doing a pass with say Sean when he's in.

@ken-zlai ken-zlai merged commit 17d47b7 into main Nov 22, 2024
8 checks passed
@ken-zlai ken-zlai deleted the figma-match branch November 22, 2024 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants