Skip to content

feat(wren-ui): support cache dashboard item#1594

Merged
onlyjackfrost merged 17 commits into
mainfrom
feat/query-cache
Apr 30, 2025
Merged

feat(wren-ui): support cache dashboard item#1594
onlyjackfrost merged 17 commits into
mainfrom
feat/query-cache

Conversation

@onlyjackfrost

@onlyjackfrost onlyjackfrost commented Apr 25, 2025

Copy link
Copy Markdown
Contributor

support cache dashboard item

Summary by CodeRabbit

  • New Features

    • Introduced dashboard cache scheduling with enable/disable toggle and refresh schedules (daily, weekly, custom cron, never).
    • Added cache settings drawer for configuring cache refresh intervals, timezone handling, and next-run previews.
    • Implemented dashboard header showing schedule details and next refresh time with tooltip.
    • Enabled manual "refresh all caches" and individual cache refresh actions.
    • Displayed last cache refresh time for dashboard items.
    • Added background service for automatic cache refresh based on schedules.
    • Extended GraphQL API to query and update dashboard cache schedules and return cache metadata with preview data.
  • Improvements

    • Enhanced dashboard grid and pinned items with ref-based imperative refresh controls.
    • Optimized dashboard item deletion to update UI instantly without full reload.
    • Improved validation and error messages for cache settings and cron expressions.
    • Ensured consistent timezone handling for scheduled cache refreshes.
  • Chores

    • Added new database tables and repository for managing dashboard cache refresh jobs.
    • Updated dependencies and scripts to support timezone configuration and cron parsing.
    • Integrated new background tracker and repository into application context and initialization.
  • Tests

    • Added comprehensive tests covering dashboard scheduling, timezone conversions, cron validation, and cache refresh job management.

@coderabbitai

coderabbitai Bot commented Apr 25, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This change introduces comprehensive support for dashboard cache scheduling and management. It adds database migrations for new scheduling and refresh job tables, extends backend models and repositories to include cache and scheduling fields, and implements a background tracker to handle scheduled cache refreshes. GraphQL schema, resolvers, and services are updated to support querying and mutating dashboard cache schedules, with robust timezone and cron expression handling. On the frontend, new components and UI elements are added for configuring cache schedules, displaying schedule info, and triggering cache refreshes. Utilities for cron validation, timezone formatting, and error messaging are also included, along with extensive tests for scheduling logic.

Changes

File(s) / Path(s) Change Summary
wren-ui/migrations/20250422000000_alter_dashboard_table.js
wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js
Add/modify database tables for dashboard cache scheduling and refresh jobs.
wren-ui/package.json Add cron-parser dependency; set TZ=UTC for scripts.
wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts
wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts
Extend query options and responses for cache metadata; add related tests.
wren-ui/src/apollo/server/models/dashboard.ts
wren-ui/src/apollo/server/models/index.ts
Add dashboard scheduling and cache models; export from models index.
wren-ui/src/apollo/server/repositories/dashboardRepository.ts
wren-ui/src/apollo/server/repositories/dashboardItemRefreshJobRepository.ts
wren-ui/src/apollo/server/repositories/index.ts
Extend dashboard repository for cache/schedule fields; add new repository for refresh jobs; export from index.
wren-ui/src/apollo/server/resolvers.ts
wren-ui/src/apollo/server/resolvers/dashboardResolver.ts
Add resolvers for dashboard schedule queries/mutations; enhance preview logic for cache.
wren-ui/src/apollo/server/schema.ts Extend GraphQL schema with cache scheduling types, inputs, enums, and fields.
wren-ui/src/apollo/server/services/dashboardService.ts
wren-ui/src/apollo/server/services/queryService.ts
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts
Implement scheduling logic, cron handling, timezone conversion, and extensive tests.
wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts
wren-ui/src/apollo/server/backgrounds/index.ts
Add background tracker for scheduled dashboard cache refreshes; export from index.
wren-ui/src/apollo/server/types/context.ts
wren-ui/src/common.ts
wren-ui/src/pages/api/graphql.ts
Integrate new repository and background tracker into app context and server bootstrap.
wren-ui/src/components/diagram/CustomDropdown.tsx
wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx
wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx
wren-ui/src/components/pages/home/dashboardGrid/index.tsx
Add/modify dropdowns and drawers for cache settings; add header and imperative refresh controls.
wren-ui/src/import/antd.ts Export TimePicker from Ant Design.
wren-ui/src/pages/home/dashboard.tsx Integrate cache settings UI, loading wrapper, and grid refresh logic.
wren-ui/src/styles/antd-variables.less Add tooltip background variable.
wren-ui/src/utils/enum/dropdown.ts
wren-ui/src/utils/error/dictionary.ts
Add enum for cache settings action; add error messages for cron and cache settings validation.
wren-ui/src/utils/time.ts
wren-ui/src/apollo/server/utils/timezone.ts
Add utilities for browser timezone and UTC offset formatting.
wren-ui/src/utils/validator/cronValidator.ts
wren-ui/src/utils/validator/index.ts
Add cron expression validator and export from validator index.
wren-ui/src/utils/errorHandler.tsx Add error handler for dashboard schedule setting errors.
wren-ui/src/apollo/client/graphql/__types__.ts
wren-ui/src/apollo/client/graphql/dashboard.generated.ts
wren-ui/src/apollo/client/graphql/dashboard.ts
Add GraphQL types, queries, and mutations for dashboard scheduling and cache metadata.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI (DashboardHeader/Drawer)
    participant GraphQL API
    participant DashboardResolver
    participant DashboardService
    participant DB

    User->>UI (DashboardHeader/Drawer): Open cache settings / set schedule
    UI (DashboardHeader/Drawer)->>GraphQL API: Mutation setDashboardSchedule
    GraphQL API->>DashboardResolver: setDashboardSchedule
    DashboardResolver->>DashboardService: setDashboardSchedule
    DashboardService->>DB: Update dashboard cache/schedule fields
    DashboardService-->>DashboardResolver: Updated dashboard
    DashboardResolver-->>GraphQL API: Updated dashboard
    GraphQL API-->>UI (DashboardHeader/Drawer): Updated dashboard info

    Note over DashboardCacheBackgroundTracker: Runs every minute
    DashboardCacheBackgroundTracker->>DB: Query dashboards with caching enabled and due
    DashboardCacheBackgroundTracker->>DashboardService: Parse cron, get items
    loop For each dashboard item
        DashboardCacheBackgroundTracker->>DB: Create refresh job (IN_PROGRESS)
        DashboardCacheBackgroundTracker->>QueryService: preview (refresh, cacheEnabled)
        QueryService->>DB: Run query, update cache
        DashboardCacheBackgroundTracker->>DB: Update job (SUCCESS/FAILED)
    end
    DashboardCacheBackgroundTracker->>DB: Update dashboard nextScheduledAt
Loading

Possibly related PRs

  • Canner/WrenAI#1587: Modifies the same UI components and enums for dashboard cache settings, including dropdowns, cache settings drawer, and header, directly relating to this PR's backend and scheduling logic enhancements.

Suggested reviewers

  • wwwy3y3

Poem

In the warren where dashboards dwell,
Rabbits set schedules, and caches swell.
With crons and timezones, the tables align,
Refresh jobs hop in, right on time.
UI drawers open, settings appear—
Cache bunnies rejoice, for springtime is here!
🐇⏰✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@onlyjackfrost onlyjackfrost marked this pull request as draft April 25, 2025 02:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 9

🔭 Outside diff range comments (1)
wren-ui/src/apollo/server/services/queryService.ts (1)

214-223: 🛠️ Refactor suggestion

refresh / cacheEnabled should be optional in the signature

preview() forwards refresh and cacheEnabled directly from PreviewOptions, where both are declared as optional (?:).
With strictNullChecks enabled, passing undefined to a parameter typed as plain boolean is a compilation error.

-  private async ibisQuery(
+  private async ibisQuery(
     sql: string,
     dataSource: DataSourceName,
     connectionInfo: any,
     mdl: Manifest,
     limit: number,
-    refresh: boolean,
-    cacheEnabled: boolean,
+    refresh?: boolean,
+    cacheEnabled?: boolean,
   ): Promise<PreviewDataResponse> {
🧹 Nitpick comments (13)
wren-ui/src/apollo/server/utils/timezone.ts (1)

8-8: Remove non-English comment

The comment "轉換為分鐘" (Convert to minutes) should be in English for consistency.

-  return (tzDate - utcDate) / 60000; // 轉換為分鐘
+  return (tzDate - utcDate) / 60000; // Convert to minutes
wren-ui/src/apollo/server/repositories/dashboardRepository.ts (1)

16-25: Consider updating repository methods for new fields

The interface has been updated, but no changes were made to the repository class implementation. Consider whether helper methods are needed for schedule-related operations.

export class DashboardRepository
  extends BaseRepository<Dashboard>
  implements IDashboardRepository
{
  constructor(knexPg: Knex) {
    super({ knexPg, tableName: 'dashboard' });
  }
  
+  // Fetch dashboards that are due for scheduled refresh
+  async findDashboardsDueForScheduledRun(): Promise<Dashboard[]> {
+    return this.knexTable
+      .where('cache_enabled', true)
+      .whereNotNull('next_scheduled_at')
+      .where('next_scheduled_at', '<=', new Date())
+      .select('*');
+  }
+  
+  // Update next scheduled run time
+  async updateNextScheduledRun(id: number, nextScheduledAt: Date | null): Promise<void> {
+    await this.knexTable
+      .where({ id })
+      .update({ next_scheduled_at: nextScheduledAt });
+  }
}
wren-ui/migrations/20250422000000_alter_dashboard_table.js (1)

7-11: Consider if cache should be enabled by default for all dashboards

The migration sets cache_enabled to true by default, which means that caching will be automatically enabled for all existing dashboards after this migration runs. Consider if this is the intended behavior, or if it would be better to start with caching disabled until users explicitly enable it.

The columns for scheduling functionality are well-structured with appropriate default values and nullability.

If you decide that caching should not be enabled by default, you could modify this line:

-    table.boolean('cache_enabled').defaultTo(true);
+    table.boolean('cache_enabled').defaultTo(false);
wren-ui/src/apollo/server/models/dashboard.ts (1)

1-1: Prefer alias import to stay consistent with the rest of the server code

Everywhere else in @server/* modules the repository layer is imported through the path alias @server/repositories.
Using a relative path here introduces an unnecessary discrepancy and may break if the directory structure is refactored.

-import { DashboardItem } from '../repositories';
+import { DashboardItem } from '@server/repositories';
wren-ui/src/apollo/server/services/dashboardService.ts (2)

487-489: Remove leftover debugging statement

console.log({ parts }); slipped into committed code.
Logging raw cron parts in production clutters logs and may expose internal scheduling details.

-        console.log({ parts });

107-115: Reset schedule fields when caching is disabled

If cacheEnabled is false, the persisted schedule still keeps the last chosen cron & next run time, which can be misleading when the user later re-enables caching.

Consider nulling these columns when caching is turned off.

-      await this.dashboardRepository.updateOne(dashboardId, {
-        cacheEnabled: data.cacheEnabled,
-        scheduleFrequency: data.schedule.frequency,
-        scheduleTimezone: data.schedule.timezone,
-        scheduleCron: cronExpression,
-        nextScheduledAt,
-      });
+      await this.dashboardRepository.updateOne(dashboardId, {
+        cacheEnabled: data.cacheEnabled,
+        scheduleFrequency: data.cacheEnabled ? data.schedule.frequency : null,
+        scheduleTimezone: data.cacheEnabled ? data.schedule.timezone : null,
+        scheduleCron: data.cacheEnabled ? cronExpression : null,
+        nextScheduledAt: data.cacheEnabled ? nextScheduledAt : null,
+      });
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (3)

72-82: Use enum constant instead of raw string for type-safety

day is declared as the domain enum CacheScheduleDayEnum, but in this test we pass the raw string "MON".
With strictNullChecks/noImplicitAny on, this will silently widen to string and lose the protection the enum gives. Using the enum keeps the test aligned with production signatures and prevents typos from compiling.

-      const schedule = {
-        frequency: ScheduleFrequencyEnum.Weekly,
-        day: 'MON',
+      const schedule = {
+        frequency: ScheduleFrequencyEnum.Weekly,
+        day: CacheScheduleDayEnum.MON,

60-71: Prefer exercising the public API over reaching into private helpers

The test invokes private helpers through (dashboardService as any).
While this works, it tightly couples tests to implementation details and will break on harmless refactors (e.g., renaming a helper). Consider:

  • Calling setDashboardSchedule and asserting the persisted scheduleCron instead of generateCronExpression.
  • Parsing/formatting using the exposed parseCronExpression (if public).

This yields more stable, behaviour-focused tests.


151-170: Flaky assertion – relies on wall-clock time

expect(result?.getTime()).toBeGreaterThan(new Date().getTime()); can flicker if the test host is under heavy load or the clock moves backwards.
Replace with a deterministic fake time (you already use jest.useFakeTimers elsewhere) or compare deltas within a buffer around an injected base time.

Example:

jest.useFakeTimers().setSystemTime(new Date('2024-04-22T10:00:00Z'));

...

const now = Date.now();
expect(result!.getTime()).toBeGreaterThan(now);
wren-ui/src/apollo/server/resolvers/dashboardResolver.ts (2)

48-55: Duplicate null-check – one is sufficient

dashboard is checked twice in succession. The second branch is unreachable and can be removed for clarity.

-    if (!dashboard) {
-      throw new Error('Dashboard not found.');
-    }
-    const schedule = ctx.dashboardService.parseCronExpression(dashboard);
-    if (!dashboard) {
-      throw new Error('Dashboard not found.');
-    }
+    if (!dashboard) {
+      throw new Error('Dashboard not found.');
+    }
+    const schedule = ctx.dashboardService.parseCronExpression(dashboard);

198-215: Mutation always returns true even when repository update fails

setDashboardSchedule returns true after calling the service, but the service may throw inside yet caller wraps it in try/catch and still returns true for unexpected, non-error paths (e.g., repository returns 0 affected rows).

Consider returning the boolean result of dashboardService.setDashboardSchedule (which could be void→undefined) or querying the update count to reflect success/failure.

wren-ui/src/apollo/server/schema.ts (2)

889-890: refresh argument should be non-null with a default value

GraphQL optional booleans become null rather than false.
If the resolver treats undefined and false the same, define a default:

input PreviewItemSQLInput {
  itemId: Int!
  limit: Int
  refresh: Boolean = false
}

924-929: Enum casing inconsistent with existing style

Existing enums use screaming-snake case (e.g., ONBOARDING_FINISHED).
ScheduleFrequencyEnum introduces Daily, Weekly, … in PascalCase, reducing visual consistency.

Consider:

enum ScheduleFrequencyEnum {
  DAILY
  WEEKLY
  CUSTOM
  NEVER
}
🛑 Comments failed to post (9)
wren-ui/src/apollo/server/utils/timezone.ts (3)

1-19: 🛠️ Refactor suggestion

Add input validation to both functions

Both functions lack input validation which might lead to runtime errors with invalid inputs.

export function getUTCOffsetMinutes(timeZone: string) {
+  if (!timeZone) {
+    throw new Error('Timezone is required');
+  }
+  
+  try {
    const date = new Date();
    const utcDate = new Date(
      date.toLocaleString('en-US', { timeZone: 'UTC' }),
    ) as any;
    const tzDate = new Date(date.toLocaleString('en-US', { timeZone })) as any;

    return (tzDate - utcDate) / 60000; // 轉換為分鐘
+  } catch (error) {
+    throw new Error(`Invalid timezone: ${timeZone}`);
+  }
}

export function formatUTCOffset(offsetMinutes: number) {
+  if (typeof offsetMinutes !== 'number' || isNaN(offsetMinutes)) {
+    throw new Error('Offset minutes must be a valid number');
+  }
+  
  const sign = offsetMinutes >= 0 ? '+' : '-';
  const absOffset = Math.abs(offsetMinutes);
  const hours = Math.floor(absOffset / 60)
    .toString()
    .padStart(2, '0');
  const minutes = (absOffset % 60).toString().padStart(2, '0');
  return `UTC${sign}${hours}:${minutes}`;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export function getUTCOffsetMinutes(timeZone: string) {
  if (!timeZone) {
    throw new Error('Timezone is required');
  }

  try {
    const date = new Date();
    const utcDate = new Date(
      date.toLocaleString('en-US', { timeZone: 'UTC' }),
    ) as any;
    const tzDate = new Date(
      date.toLocaleString('en-US', { timeZone }),
    ) as any;

    return (tzDate.getTime() - utcDate.getTime()) / 60000; // 轉換為分鐘
  } catch (error) {
    throw new Error(`Invalid timezone: ${timeZone}`);
  }
}

export function formatUTCOffset(offsetMinutes: number) {
  if (typeof offsetMinutes !== 'number' || isNaN(offsetMinutes)) {
    throw new Error('Offset minutes must be a valid number');
  }

  const sign = offsetMinutes >= 0 ? '+' : '-';
  const absOffset = Math.abs(offsetMinutes);
  const hours = Math.floor(absOffset / 60)
    .toString()
    .padStart(2, '0');
  const minutes = (absOffset % 60).toString().padStart(2, '0');
  return `UTC${sign}${hours}:${minutes}`;
}

1-19: 💡 Verification agent

🧩 Analysis chain

Add unit tests for timezone utility functions

These utility functions are critical for scheduling functionality and should have thorough test coverage.


🏁 Script executed:

#!/bin/bash
# Check if there are any tests for the timezone utilities

echo "Searching for timezone utility tests..."
fd -t f "timezone.*test.*\.(ts|js)" src

echo "Checking imports of timezone utilities in test files..."
rg -l "getUTCOffsetMinutes|formatUTCOffset" --type ts --type js "**/*.test.ts" "**/*.test.js" "**/__tests__/**"

Length of output: 618


#!/bin/bash
set -e

echo "Searching for timezone utility test files across the repo..."
fd -t f "timezone.*test.*\.(ts|js|tsx|jsx)" .

echo "Searching for any test importing the timezone utilities..."
rg -l "getUTCOffsetMinutes|formatUTCOffset" \
   --type ts --type tsx --type js --type jsx \
   --glob "*.test.*" --glob "*.spec.*"

#!/bin/bash
set -e

echo "Searching for timezone utility test files under wren-ui/src..."
fd -t f "timezone.*\.(test|spec)\.(ts|js|tsx|jsx)" wren-ui/src

echo "Searching for any test importing the timezone utilities..."
rg -l "getUTCOffsetMinutes|formatUTCOffset" \
   --glob "wren-ui/**/*.test.*.{ts,tsx,js,jsx}" \
   --glob "wren-ui/**/*.spec.*.{ts,tsx,js,jsx}"

🏁 Script executed:

#!/bin/bash
set -e

echo "Searching for timezone utility test files under wren-ui..."
fd --regex 'timezone.*\.(test|spec)\.(ts|js|tsx|jsx)$' wren-ui

echo "Searching for timezone functions in any test/spec files under wren-ui..."
rg -n 'getUTCOffsetMinutes|formatUTCOffset' --glob 'wren-ui/**/*test.*' --glob 'wren-ui/**/*spec.*'

Length of output: 457


Add unit tests for timezone utility functions

No existing tests cover getUTCOffsetMinutes or formatUTCOffset. Please add a new test file, for example:

  • File: wren-ui/src/apollo/server/utils/timezone.test.ts
  • Tests to include:
    • Verify getUTCOffsetMinutes returns correct offsets for known zones (e.g., “UTC”, “America/New_York”, “Asia/Tokyo”), including during DST transitions.
    • Verify formatUTCOffset formats positive, negative, and zero offsets correctly (e.g., UTC+00:00, UTC-05:30, UTC+09:00).
    • Edge cases: non-existent timeZone strings (expecting thrown error or fallback).

Sample Jest scaffold:

import { getUTCOffsetMinutes, formatUTCOffset } from "./timezone";

describe("timezone utilities", () => {
  test("UTC offset for UTC is 0", () => {
    expect(getUTCOffsetMinutes("UTC")).toBe(0);
    expect(formatUTCOffset(0)).toBe("UTC+00:00");
  });

  test("format negative offset", () => {
    expect(formatUTCOffset(-330)).toBe("UTC-05:30");
  });

  // add more cases...
});

1-9: 🛠️ Refactor suggestion

Improve timezone offset calculation for better reliability

The current implementation of getUTCOffsetMinutes uses Date object string conversion and subtraction which can be error-prone. The method relies on converting dates to locale strings and back to Date objects, which might lead to inconsistencies across environments.

Consider using the more reliable Intl.DateTimeFormat API instead:

export function getUTCOffsetMinutes(timeZone: string) {
-  const date = new Date();
-  const utcDate = new Date(
-    date.toLocaleString('en-US', { timeZone: 'UTC' }),
-  ) as any;
-  const tzDate = new Date(date.toLocaleString('en-US', { timeZone })) as any;
-
-  return (tzDate - utcDate) / 60000; // 轉換為分鐘
+  const date = new Date();
+  const formatter = new Intl.DateTimeFormat('en-US', {
+    timeZone,
+    year: 'numeric',
+    month: 'numeric',
+    day: 'numeric',
+    hour: 'numeric',
+    minute: 'numeric',
+    second: 'numeric',
+    hour12: false
+  });
+  
+  const parts = formatter.formatToParts(date);
+  const formattedDate = new Date(
+    `${parts.find(p => p.type === 'year')?.value}-${parts.find(p => p.type === 'month')?.value}-${parts.find(p => p.type === 'day')?.value}T${parts.find(p => p.type === 'hour')?.value}:${parts.find(p => p.type === 'minute')?.value}:${parts.find(p => p.type === 'second')?.value}`
+  );
+  
+  return (formattedDate.getTime() - date.getTime()) / 60000;
}

Alternatively, consider leveraging the new cron-parser dependency which might have timezone utilities built-in.

Committable suggestion skipped: line range outside the PR's diff.

wren-ui/src/apollo/server/repositories/dashboardRepository.ts (1)

9-13: 💡 Verification agent

🧩 Analysis chain

Verify database schema migration for new fields

The interface has been updated with new fields, but we should verify that a corresponding database migration exists to add these columns to the table.


🏁 Script executed:

#!/bin/bash
# Check for migration files that might add cache and schedule columns to dashboard table
echo "Looking for relevant migration files..."
fd -t f -e js ".*dashboard.*" "migrations" | sort -r | head -5
echo "-----------------"

echo "Checking content of recent migrations for dashboard table modifications..."
rg -l "cache_enabled|schedule_frequency|schedule_timezone|schedule_cron|next_scheduled_at" --type js "migrations/*.js" | xargs cat

Length of output: 640


Add missing database migration for new dashboard fields

It appears no migration in the repo updates the dashboard table to include the new columns (cache_enabled, schedule_frequency, schedule_timezone, schedule_cron, next_scheduled_at). Please:

  • Create a migration that adds these columns (with matching names and types) to the dashboard table.
  • Verify the migration runs cleanly and the new fields are present in your local/staging databases.
  • Confirm your ORM models/interfaces stay in sync with the schema changes.
wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts (1)

547-557: ⚠️ Potential issue

Fix inconsistency in query parameter names

There's an inconsistency between the parameter names used in the buildQueryString method and those expected in the tests. This could lead to the backend not recognizing the parameters correctly.

  private buildQueryString(options: IbisQueryOptions) {
    if (!options.cacheEnabled) {
      return '';
    }
    const queryString = [];
-   queryString.push('cacheEnable=true');
+   queryString.push('cacheEnabled=true');
    if (options.refresh) {
-     queryString.push('overrideCache=true');
+     queryString.push('cacheOverride=true');
    }
    return `?${queryString.join('&')}`;
  }

The tests at lines 514 and 549 expect cacheEnabled=true and cacheOverride=true, but the method uses cacheEnable=true and overrideCache=true. These should be made consistent.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private buildQueryString(options: IbisQueryOptions) {
    if (!options.cacheEnabled) {
      return '';
    }
    const queryString = [];
    queryString.push('cacheEnabled=true');
    if (options.refresh) {
      queryString.push('cacheOverride=true');
    }
    return `?${queryString.join('&')}`;
  }
wren-ui/src/apollo/server/models/dashboard.ts (1)

28-35: 🛠️ Refactor suggestion

day should be nullable / optional

DashboardSchedule.day is only meaningful for Weekly schedules.
Current callers (generateCronExpression, validateScheduleInput, parseCronExpression) already treat it as potentially null, so the type should reflect that to avoid type-casting and accidental misuse.

-export interface DashboardSchedule {
+export interface DashboardSchedule {
   frequency: ScheduleFrequencyEnum;
   timezone: string;
-  day: CacheScheduleDayEnum;
+  day: CacheScheduleDayEnum | null;
   hour: number;
   minute: number;
   cron: string;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export interface DashboardSchedule {
  frequency: ScheduleFrequencyEnum;
  timezone: string;
  day: CacheScheduleDayEnum | null;
  hour: number;
  minute: number;
  cron: string;
}
wren-ui/src/apollo/server/resolvers/dashboardResolver.ts (1)

161-170: ⚠️ Potential issue

Possible runtime NPE when dashboard is undefined

const { cacheEnabled } = await ctx.dashboardService.getCurrentDashboard(); destructures without verifying the dashboard exists. If the call returns null, the resolver crashes with Cannot read properties of null.

Add the same guard you use elsewhere:

-      const { cacheEnabled } = await ctx.dashboardService.getCurrentDashboard();
+      const dashboard = await ctx.dashboardService.getCurrentDashboard();
+      if (!dashboard) {
+        throw new Error('Dashboard not found.');
+      }
+      const { cacheEnabled } = dashboard;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    try {
      const item = await ctx.dashboardService.getDashboardItem(itemId);
      const dashboard = await ctx.dashboardService.getCurrentDashboard();
      if (!dashboard) {
        throw new Error('Dashboard not found.');
      }
      const { cacheEnabled } = dashboard;
      const project = await ctx.projectService.getCurrentProject();
      const deployment = await ctx.deployService.getLastDeployment(project.id);
      const mdl = deployment.manifest;
      const data = (await ctx.queryService.preview(item.detail.sql, {
wren-ui/src/apollo/server/schema.ts (2)

901-904: 🛠️ Refactor suggestion

schedule should be optional when caching is disabled

SetDashboardScheduleInput requires schedule even if cacheEnabled is false, leading to confusing API usage:

input SetDashboardScheduleInput {
  cacheEnabled: Boolean!
  schedule: SetDashboardScheduleData
}

Resolver/service can then ignore schedule if cacheEnabled is false.


900-922: 🛠️ Refactor suggestion

Schedule input forces hour/minute even when unused

For Custom and Never frequencies only cron (or nothing) is meaningful, yet hour/minute are declared Int!. This burdens clients with dummy data and complicates validation logic already handled in the service layer.

Recommended shape:

input SetDashboardScheduleData {
  frequency: ScheduleFrequencyEnum!
  hour: Int
  minute: Int
  day: CacheScheduleDayEnum
  timezone: String
  cron: String
}

The corresponding DashboardSchedule type should mirror the nullable fields.

-type DashboardSchedule {
-  frequency: ScheduleFrequencyEnum!
-  hour: Int!
-  minute: Int!
+type DashboardSchedule {
+  frequency: ScheduleFrequencyEnum!
+  hour: Int
+  minute: Int
 ...

@onlyjackfrost onlyjackfrost marked this pull request as ready for review April 25, 2025 05:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🧹 Nitpick comments (7)
wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (1)

9-9: Consider using enum values for status comment.

The comment describes possible values as 'success', 'failed', 'in_progress', but the actual values used in the code (DashboardCacheRefreshStatus enum) are slightly different with underscores between words.

-    table.string('status').notNullable(); // 'success', 'failed', 'in_progress'
+    table.string('status').notNullable(); // Values: 'success', 'failed', 'in_progress' - see DashboardCacheRefreshStatus enum
wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (3)

61-88: Consider adding rate limiting or batching for large dashboard sets.

The current implementation processes all cached dashboards sequentially in a single interval run. If many dashboards have similar schedules, this could cause performance issues.

Consider adding:

  1. A limit on how many dashboards to process per interval
  2. A batching mechanism to spread the load
  3. A more sophisticated scheduling approach
  private async checkAndRefreshCaches(): Promise<void> {
    try {
      // Get all dashboards with cache enabled
      const dashboards = await this.dashboardRepository.findAllBy({
        cacheEnabled: true,
      });

+     // Limit the number of dashboards to process per interval
+     const MAX_DASHBOARDS_PER_INTERVAL = 5;
+     let processedCount = 0;
+
      for (const dashboard of dashboards) {
        if (!dashboard.scheduleCron || !dashboard.nextScheduledAt) {
          continue;
        }

        const now = new Date();
        const nextScheduledAt = new Date(dashboard.nextScheduledAt);

        // Check if it's time to refresh
        if (now >= nextScheduledAt) {
          logger.info(`Start Refreshing cache for dashboard ${dashboard.id}`);
          await this.refreshDashboardCache(dashboard);
          logger.info(
            `Finished Refreshing cache for dashboard ${dashboard.id}`,
          );
+
+         processedCount++;
+         if (processedCount >= MAX_DASHBOARDS_PER_INTERVAL) {
+           logger.info(`Reached maximum dashboard processing limit (${MAX_DASHBOARDS_PER_INTERVAL}). Remaining will be processed in next interval.`);
+           break;
+         }
        }
      }
    } catch (error) {
      logger.error(`Error checking dashboard caches: ${error.message}`);
    }
  }

125-154: Add retry mechanism for failed cache refresh operations.

Currently, if a cache refresh fails, the error is logged but there's no retry mechanism. Consider implementing a retry strategy for transient failures.

          try {
            await this.queryService.preview(item.detail.sql, {
              project,
              manifest: mdl,
              cacheEnabled: true,
              refresh: true,
            });

            // Update the record with success
            await this.dashboardCacheRefreshRepository.updateOne(
              refreshJob.id,
              {
                finishedAt: new Date(),
                status: DashboardCacheRefreshStatus.SUCCESS,
              },
            );
          } catch (error) {
+           // Check if error is potentially transient and worth retrying
+           const isTransientError = this.isTransientError(error);
+           const status = isTransientError 
+             ? DashboardCacheRefreshStatus.FAILED_RETRYABLE 
+             : DashboardCacheRefreshStatus.FAILED;
+
            // Update the record with failure
            await this.dashboardCacheRefreshRepository.updateOne(
              refreshJob.id,
              {
                finishedAt: new Date(),
-               status: DashboardCacheRefreshStatus.FAILED,
+               status,
                errorMessage: error.message,
              },
            );
            logger.debug(
              `Error refreshing cache for item ${item.id}: ${error.message}`,
            );
          }

You would also need to:

  1. Add a new status to the enum in the repository
  2. Implement the isTransientError method to identify errors worth retrying
  3. Add scheduled retry logic for failed-but-retryable jobs

183-193: Consider caching successfully parsed cron expressions.

Each call to CronExpressionParser.parse incurs some overhead. Since the same cron expressions are likely used repeatedly, consider caching the parsed results.

+ private cronCache = new Map<string, any>();

  private calculateNextRunTime(cronExpression: string): Date | null {
    try {
+     // Check if we already parsed this expression
+     if (this.cronCache.has(cronExpression)) {
+       const interval = this.cronCache.get(cronExpression);
+       // Create a fresh iterator starting from now
+       return interval.clone({ currentDate: new Date() }).next().toDate();
+     }
+
      const interval = CronExpressionParser.parse(cronExpression, {
        currentDate: new Date(),
      });
+     // Cache the parsed expression
+     this.cronCache.set(cronExpression, interval);
      return interval.next().toDate();
    } catch (error) {
      logger.error(`Failed to parse cron expression: ${error.message}`);
      return null;
    }
  }
wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts (3)

48-57: Consider adding limit parameter to findLatestByDashboardId.

For dashboards with many refresh operations, retrieving all records and then taking the first could be inefficient. Consider adding a limit parameter with a default of 1 for better performance.

  public async findLatestByDashboardId(
    dashboardId: number,
+   limit: number = 1
  ): Promise<DashboardCacheRefresh | null> {
    const result = await this.knex
      .table(this.tableName)
      .where({ dashboardId })
      .orderBy('createdAt', 'desc')
+     .limit(limit)
      .first();
    return result ? this.transformFromDBData(result) : null;
  }

70-81: Add pagination support for findInProgressByDashboardId.

This method could potentially return a large number of records for dashboards with many in-progress jobs. Adding pagination would improve performance and usability.

  public async findInProgressByDashboardId(
    dashboardId: number,
+   page: number = 1,
+   pageSize: number = 20
  ): Promise<DashboardCacheRefresh[]> {
    const results = await this.knex
      .table(this.tableName)
      .where({
        dashboardId,
        status: DashboardCacheRefreshStatus.IN_PROGRESS,
      })
      .orderBy('createdAt', 'desc')
+     .offset((page - 1) * pageSize)
+     .limit(pageSize);
    return results.map((result) => this.transformFromDBData(result));
  }

Apply similar changes to findInProgressByDashboardItemId for consistency.


96-102: Add method to find all records by hash.

The current findByHash method only returns a single record, but the hash is used to group related refresh operations. Adding a method to find all records with the same hash would be useful for tracking batch operations.

+ public async findAllByHash(hash: string): Promise<DashboardCacheRefresh[]> {
+   const results = await this.knex
+     .table(this.tableName)
+     .where({ hash })
+     .orderBy('createdAt', 'desc');
+   return results.map((result) => this.transformFromDBData(result));
+ }

  public async findByHash(hash: string): Promise<DashboardCacheRefresh | null> {
    const result = await this.knex
      .table(this.tableName)
      .where({ hash })
      .first();
    return result ? this.transformFromDBData(result) : null;
  }
🛑 Comments failed to post (2)
wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (2)

90-90: 🛠️ Refactor suggestion

Replace 'any' type with explicit dashboard interface.

Using 'any' type reduces type safety. Since this is TypeScript, use a specific dashboard interface type that includes the fields you're using.

- private async refreshDashboardCache(dashboard: any): Promise<void> {
+ private async refreshDashboardCache(dashboard: { 
+   id: number; 
+   scheduleCron: string | null; 
+   nextScheduledAt: Date | string | null 
+ }): Promise<void> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private async refreshDashboardCache(dashboard: { 
    id: number; 
    scheduleCron: string | null; 
    nextScheduledAt: Date | string | null 
  }): Promise<void> {

19-52: 🛠️ Refactor suggestion

Add a method to stop the background tracker for graceful shutdown.

The class starts an interval timer in the constructor but doesn't provide a way to stop it. This could lead to memory leaks if the application needs to shut down gracefully or if you need to replace the tracker instance.

export class DashboardCacheBackgroundTracker {
  private intervalTime: number;
  private dashboardRepository: IDashboardRepository;
  private dashboardItemRepository: IDashboardItemRepository;
  private dashboardCacheRefreshRepository: IDashboardCacheRefreshRepository;
  private projectService: IProjectService;
  private deployService: IDeployService;
  private queryService: IQueryService;
  private runningJobs = new Set<number>();
+ private intervalId: NodeJS.Timeout | null = null;

  constructor({
    dashboardRepository,
    dashboardItemRepository,
    dashboardCacheRefreshRepository,
    projectService,
    deployService,
    queryService,
  }: {
    dashboardRepository: IDashboardRepository;
    dashboardItemRepository: IDashboardItemRepository;
    dashboardCacheRefreshRepository: IDashboardCacheRefreshRepository;
    projectService: IProjectService;
    deployService: IDeployService;
    queryService: IQueryService;
  }) {
    this.dashboardRepository = dashboardRepository;
    this.dashboardItemRepository = dashboardItemRepository;
    this.dashboardCacheRefreshRepository = dashboardCacheRefreshRepository;
    this.projectService = projectService;
    this.deployService = deployService;
    this.queryService = queryService;
    this.intervalTime = 60000; // 1 minute
    this.start();
  }

  private start(): void {
    logger.info('Dashboard cache background tracker started');
-   setInterval(() => {
+   this.intervalId = setInterval(() => {
      this.checkAndRefreshCaches();
    }, this.intervalTime);
  }
  
+ public stop(): void {
+   if (this.intervalId) {
+     clearInterval(this.intervalId);
+     this.intervalId = null;
+     logger.info('Dashboard cache background tracker stopped');
+   }
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export class DashboardCacheBackgroundTracker {
  private intervalTime: number;
  private dashboardRepository: IDashboardRepository;
  private dashboardItemRepository: IDashboardItemRepository;
  private dashboardCacheRefreshRepository: IDashboardCacheRefreshRepository;
  private projectService: IProjectService;
  private deployService: IDeployService;
  private queryService: IQueryService;
  private runningJobs = new Set<number>();
  private intervalId: NodeJS.Timeout | null = null;

  constructor({
    dashboardRepository,
    dashboardItemRepository,
    dashboardCacheRefreshRepository,
    projectService,
    deployService,
    queryService,
  }: {
    dashboardRepository: IDashboardRepository;
    dashboardItemRepository: IDashboardItemRepository;
    dashboardCacheRefreshRepository: IDashboardCacheRefreshRepository;
    projectService: IProjectService;
    deployService: IDeployService;
    queryService: IQueryService;
  }) {
    this.dashboardRepository = dashboardRepository;
    this.dashboardItemRepository = dashboardItemRepository;
    this.dashboardCacheRefreshRepository = dashboardCacheRefreshRepository;
    this.projectService = projectService;
    this.deployService = deployService;
    this.queryService = queryService;
    this.intervalTime = 60000; // 1 minute
    this.start();
  }

  private start(): void {
    logger.info('Dashboard cache background tracker started');
    this.intervalId = setInterval(() => {
      this.checkAndRefreshCaches();
    }, this.intervalTime);
  }

  public stop(): void {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
      logger.info('Dashboard cache background tracker stopped');
    }
  }
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
wren-ui/src/apollo/server/services/dashboardService.ts (2)

389-406: Cron expression portability & validation

day comes from CacheScheduleDayEnum (e.g. "MON"). While cron-parser accepts three-letter day names, many cron engines (e.g. Linux crond) expect numeric values (0-6). If the backend ever forwards these expressions to external systems, consider normalising to numbers to avoid portability issues.

At minimum, document the expectation and validate day against the allowed set (MON-SUN) before constructing the expression.


257-319: Timezone arithmetic is brittle – lean on battle-tested libraries

Manual offset math is hard to prove correct for DST transitions and historical timezone changes. A lightweight library such as luxon (already tree-shake-friendly and no moment-style bloat) can convert between zones safely:

import { DateTime } from 'luxon';

private toUTC(schedule: DashboardSchedule): DashboardSchedule {
  if (!schedule.timezone || schedule.frequency === ScheduleFrequencyEnum.CUSTOM)
    return schedule;

  const local = DateTime.fromObject(
    { hour: schedule.hour, minute: schedule.minute },
    { zone: schedule.timezone },
  );
  const utc = local.toUTC();
  return { ...schedule, hour: utc.hour, minute: utc.minute };
}

This removes ~130 LOC of intricate offset handling and future-proofs DST edge-cases.

Also applies to: 321-387

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53e03d5 and ce35f92.

⛔ Files ignored due to path filters (1)
  • wren-ui/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (22)
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js (1 hunks)
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (1 hunks)
  • wren-ui/package.json (1 hunks)
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts (5 hunks)
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts (2 hunks)
  • wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (1 hunks)
  • wren-ui/src/apollo/server/backgrounds/index.ts (1 hunks)
  • wren-ui/src/apollo/server/models/dashboard.ts (1 hunks)
  • wren-ui/src/apollo/server/models/index.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/index.ts (1 hunks)
  • wren-ui/src/apollo/server/resolvers.ts (2 hunks)
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts (4 hunks)
  • wren-ui/src/apollo/server/schema.ts (4 hunks)
  • wren-ui/src/apollo/server/services/dashboardService.ts (5 hunks)
  • wren-ui/src/apollo/server/services/queryService.ts (6 hunks)
  • wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (1 hunks)
  • wren-ui/src/apollo/server/types/context.ts (3 hunks)
  • wren-ui/src/apollo/server/utils/timezone.ts (1 hunks)
  • wren-ui/src/common.ts (6 hunks)
  • wren-ui/src/pages/api/graphql.ts (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • wren-ui/src/apollo/server/models/index.ts
  • wren-ui/package.json
🚧 Files skipped from review as they are similar to previous changes (19)
  • wren-ui/src/apollo/server/repositories/index.ts
  • wren-ui/src/apollo/server/backgrounds/index.ts
  • wren-ui/src/apollo/server/types/context.ts
  • wren-ui/src/apollo/server/resolvers.ts
  • wren-ui/src/apollo/server/utils/timezone.ts
  • wren-ui/src/pages/api/graphql.ts
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js
  • wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts
  • wren-ui/src/common.ts
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts
  • wren-ui/src/apollo/server/repositories/dashboardRepository.ts
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts
  • wren-ui/src/apollo/server/services/tests/dashboardService.test.ts
  • wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts
  • wren-ui/src/apollo/server/models/dashboard.ts
  • wren-ui/src/apollo/server/services/queryService.ts
  • wren-ui/src/apollo/server/schema.ts
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js
🔇 Additional comments (2)
wren-ui/src/apollo/server/services/dashboardService.ts (2)

20-20: ⚠️ Potential issue

Incorrect import style for cron-parser

The cron-parser package exports a default module whose API exposes parseExpression().
CronExpressionParser is not an exported symbol, therefore import { CronExpressionParser } will be undefined at runtime and every subsequent call to CronExpressionParser.parse() will throw.

-import { CronExpressionParser } from 'cron-parser';
+import cronParser from 'cron-parser';  // default export

Update the two call-sites below from CronExpressionParser.parse to cronParser.parseExpression.
Failing to do so will break schedule creation and every dashboard page load that touches parseCronExpression().

⛔ Skipped due to learnings
Learnt from: andreashimin
PR: Canner/WrenAI#1587
File: wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx:3-4
Timestamp: 2025-04-23T09:21:38.365Z
Learning: The cron-parser package (v5+) provides a named export `CronExpressionParser` with a `parse` method that should be used like: `import { CronExpressionParser } from 'cron-parser'; const interval = CronExpressionParser.parse(cronExpression, options);`

408-418: ⚠️ Potential issue

Use parseExpression() instead of the non-existent parse()

After fixing the import, adjust the parser invocations:

-const interval = CronExpressionParser.parse(cronExpression, { currentDate: new Date() });
+const interval = cronParser.parseExpression(cronExpression, { currentDate: new Date() });

and

-const baseInterval = CronExpressionParser.parse(schedule.cron, { currentDate: new Date() });
+const baseInterval = cronParser.parseExpression(schedule.cron, { currentDate: new Date() });
 ...
-const nextInterval = CronExpressionParser.parse(schedule.cron, { currentDate: baseDate });
+const nextInterval = cronParser.parseExpression(schedule.cron, { currentDate: baseDate });

These changes are mandatory: the current code will throw TypeError: CronExpressionParser.parse is not a function, preventing any cache schedule from being saved or validated.

Also applies to: 449-461

⛔ Skipped due to learnings
Learnt from: andreashimin
PR: Canner/WrenAI#1587
File: wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx:3-4
Timestamp: 2025-04-23T09:21:38.365Z
Learning: The cron-parser package (v5+) provides a named export `CronExpressionParser` with a `parse` method that should be used like: `import { CronExpressionParser } from 'cron-parser'; const interval = CronExpressionParser.parse(cronExpression, options);`

Comment on lines +148 to 147
return { ...dashboard };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against null dashboards

findOneBy() returns null when no record matches.
Spreading null results in {} which silently hides the error and may cascade into “undefined property” crashes elsewhere.

-    const dashboard = await this.dashboardRepository.findOneBy({ projectId: project.id });
-    return { ...dashboard };
+    const dashboard = await this.dashboardRepository.findOneBy({ projectId: project.id });
+    if (!dashboard) {
+      throw new Error(`Dashboard for project ${project.id} not found`);
+    }
+    return { ...dashboard };

Fail fast here to surface configuration problems early.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return { ...dashboard };
}
const dashboard = await this.dashboardRepository.findOneBy({ projectId: project.id });
if (!dashboard) {
throw new Error(`Dashboard for project ${project.id} not found`);
}
return { ...dashboard };

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
wren-ui/src/apollo/server/services/dashboardService.ts (1)

148-148: ⚠️ Potential issue

Guard against null dashboards.

Your implementation of getCurrentDashboard() spreads the dashboard without checking if it exists first. If no dashboard is found, this will spread null resulting in {} which silently hides the error and may cascade into "undefined property" crashes elsewhere.

Apply this fix to fail fast and surface configuration problems early:

public async getCurrentDashboard(): Promise<Dashboard> {
  const project = await this.projectService.getCurrentProject();
  const dashboard = await this.dashboardRepository.findOneBy({
    projectId: project.id,
  });
-  return { ...dashboard };
+  if (!dashboard) {
+    throw new Error(`Dashboard for project ${project.id} not found`);
+  }
+  return { ...dashboard };
}
🧹 Nitpick comments (4)
wren-ui/src/apollo/server/services/dashboardService.ts (3)

429-449: Skip validation in dev mode but enforce in production.

Your validation logic for custom cron expressions includes special handling for development mode, but the comment doesn't fully explain the reasoning.

Improve the comment to clearly explain why we're skipping this validation in development:

if (schedule.frequency === ScheduleFrequencyEnum.CUSTOM) {
-  // can not less than 10 minutes, skip if is local
+  // Skip minimum interval validation in development for easier testing,
+  // but enforce 10+ minute intervals in production to prevent excessive load
  if (process.env.NODE_ENV === 'development') return;
  const baseInterval = CronExpressionParser.parse(schedule.cron, {
    currentDate: new Date(),
  });
  // Rest of validation...

447-465: Refactor cron interval validation for better maintainability.

The validation logic for checking minimum cron interval is embedded within the main validation method. Extracting this to a separate method would improve readability and maintainability.

Extract the custom cron minimum interval validation to a separate method:

protected validateScheduleInput(data: SetDashboardCacheData): void {
  const { schedule } = data;

  // Other validations...

  if (schedule.frequency === ScheduleFrequencyEnum.CUSTOM) {
    // can not less than 10 minutes, skip if is local
    if (process.env.NODE_ENV === 'development') return;
-   const baseInterval = CronExpressionParser.parse(schedule.cron, {
-     currentDate: new Date(),
-   });
-   const baseDate = baseInterval.next().toDate();
-
-   const nextInterval = CronExpressionParser.parse(schedule.cron, {
-     currentDate: baseDate,
-   });
-   const nextDate = nextInterval.next().toDate();
-   const diff = nextDate.getTime() - baseDate.getTime();
-   if (diff < 10 * 60 * 1000) {
-     throw new Error(
-       'Custom cron expression must be at least 10 minutes apart',
-     );
-   }
+   this.validateCronMinimumInterval(schedule.cron);
  }
}

+ protected validateCronMinimumInterval(cronExpression: string): void {
+   const baseInterval = CronExpressionParser.parse(cronExpression, {
+     currentDate: new Date(),
+   });
+   const baseDate = baseInterval.next().toDate();
+ 
+   const nextInterval = CronExpressionParser.parse(cronExpression, {
+     currentDate: baseDate,
+   });
+   const nextDate = nextInterval.next().toDate();
+   const diff = nextDate.getTime() - baseDate.getTime();
+   if (diff < 10 * 60 * 1000) {
+     throw new Error(
+       'Custom cron expression must be at least 10 minutes apart',
+     );
+   }
+ }

468-521: Improve type safety in the parseCronExpression method.

The implementation uses type assertions (as DashboardSchedule) multiple times, which can mask potential errors.

Refactor to use safer type handling:

public parseCronExpression(dashboard: Dashboard): DashboardSchedule {
  if (!dashboard.scheduleCron) {
-    return {
+    const baseSchedule: DashboardSchedule = {
      frequency: dashboard.scheduleFrequency,
      hour: 0,
      minute: 0,
      day: null,
      timezone: dashboard.scheduleTimezone || '',
      cron: '',
-    } as DashboardSchedule;
+    };
+    return baseSchedule;
  }
  
  // ... rest of the method
  
  case ScheduleFrequencyEnum.NEVER: {
-    return {
+    const neverSchedule: DashboardSchedule = {
      frequency: ScheduleFrequencyEnum.NEVER,
      hour: null,
      minute: null,
      day: null,
      timezone: dashboard.scheduleTimezone || '',
      cron: null,
-    } as DashboardSchedule;
+    };
+    return neverSchedule;
  }

  // ... similarly for other cases
}
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (1)

29-31: Remove unnecessary return statement from void function.

The testValidateScheduleInput method returns a value despite having a void return type, which is causing a static analysis warning.

public testValidateScheduleInput(data: SetDashboardCacheData): void {
-  return this.validateScheduleInput(data);
+  this.validateScheduleInput(data);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce35f92 and 6c9ed82.

📒 Files selected for processing (2)
  • wren-ui/src/apollo/server/services/dashboardService.ts (5 hunks)
  • wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (2)
wren-ui/src/apollo/server/services/dashboardService.ts (1)
  • DashboardService (60-523)
wren-ui/src/apollo/server/models/dashboard.ts (2)
  • DashboardSchedule (28-35)
  • SetDashboardCacheData (37-40)
🪛 Biome (1.9.4)
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts

[error] 30-30: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (8)
wren-ui/src/apollo/server/services/dashboardService.ts (2)

258-387: Well-implemented timezone conversion with day adjustment.

The timezone conversion logic correctly handles complex cases including:

  • Minute and hour carryover
  • Day adjustments for weekly schedules
  • Various timezone offsets around the world

This is a robust implementation that accounts for all edge cases when converting between UTC and local time.


79-128: Well-designed dashboard schedule implementation.

The setDashboardSchedule method is well-structured with:

  • Proper input validation
  • Null checks for the dashboard
  • Clear logic for enabled/disabled caching
  • Appropriate error handling
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (6)

88-144: Great test coverage for cron expression generation.

The tests comprehensively verify cron expression generation for all supported schedule frequencies (daily, weekly, custom, never). Each test case uses clear inputs and expected outputs.


146-217: Thorough testing of next run time calculation.

These tests effectively verify:

  • Handling of invalid cron expressions
  • Calculation of correct next run times for different schedules
  • Time component verification (day of week, hour patterns)
  • Timezone considerations

The tests include both basic functionality and edge cases.


219-405: Comprehensive timezone conversion tests.

The timezone conversion tests cover an impressive range of scenarios:

  • UTC to local time and local time to UTC conversions
  • Handling of timezones with different UTC offsets
  • Day boundary cases (midnight crossing)
  • Day of week adjustments for weekly schedules
  • Non-hour offsets (like India's UTC+5:30)

This thorough testing ensures the complex timezone logic works correctly across global use cases.


407-593: Comprehensive timezone-to-local conversion tests.

The tests for converting from UTC to local timezone are equally thorough, covering:

  • Edge cases like day wrapping (Friday to Saturday)
  • Various UTC offsets
  • Minute and hour adjustments
  • Special cases like custom schedules

This excellent test coverage ensures the timezone conversion logic is robust.


595-775: Thorough input validation testing.

The tests for schedule input validation include:

  • All error cases (missing day for weekly schedule, missing cron expression, invalid hour/minute/timezone)
  • Minimum interval requirements for custom schedules
  • Valid cases for each schedule type

This ensures the validation logic correctly enforces all constraints.


777-976: Complete testing of the dashboard schedule update workflow.

The tests for setDashboardSchedule verify the entire workflow including:

  • Error handling for missing dashboards
  • Correct updating of daily/weekly/custom schedules
  • Handling of disabled cache
  • Timezone conversions during updates
  • Error handling during repository operations

This thorough testing ensures the end-to-end scheduling functionality works as expected.

@@ -0,0 +1,35 @@
exports.up = function (knex) {
return knex.schema.createTable('dashboard_cache_refresh', (table) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: maybe dashboard_item_refresh_job ? to better describe it's the job result of a dashboard item refresh.

Comment thread wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js Outdated
Comment thread wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts Outdated
Comment thread wren-ui/src/apollo/server/resolvers/dashboardResolver.ts
Comment thread wren-ui/src/apollo/server/services/dashboardService.ts
Comment thread wren-ui/src/apollo/server/services/dashboardService.ts
Comment thread wren-ui/src/apollo/server/services/dashboardService.ts Outdated
Comment thread wren-ui/src/apollo/server/schema.ts Outdated
Comment thread wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts Outdated
Comment thread wren-ui/migrations/20250422000000_alter_dashboard_table.js Outdated
Comment thread wren-ui/src/apollo/server/schema.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
wren-ui/src/apollo/server/schema.ts (2)

892-898: Rename cacheOverrideAt to cacheOverrodeAt for grammatical correctness.

The field name should use past tense since it represents a timestamp when an action occurred in the past.

  type PreviewItemResponse {
    data: JSON!
    cacheHit: Boolean!
    cacheCreatedAt: String
-   cacheOverrideAt: String
+   cacheOverrodeAt: String
    override: Boolean!
  }

972-980: Missing default value for cacheEnabled in DetailedDashboard.

The cacheEnabled field in DetailedDashboard needs a default value as mentioned in previous review comments.

Also, consider how the schedule field (which is required with !) behaves when cacheEnabled is false. It might be clearer to make this field optional or provide a default "disabled" schedule when caching is turned off.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e55129f and e9928ad.

📒 Files selected for processing (2)
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js (1 hunks)
  • wren-ui/src/apollo/server/schema.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (5)
wren-ui/src/apollo/server/schema.ts (5)

886-890: Good addition of the refresh parameter to control cache behavior.

The refresh flag with a default value of false is a clean way to allow clients to force a refresh when needed while defaulting to using the cache.


923-938: LGTM! Well-structured enums for scheduling.

The enums for schedule frequency and days of the week are well-defined and cover all necessary cases.


961-970: LGTM! Clear and comprehensive Dashboard type definition.

The Dashboard type provides all the necessary fields for managing cache settings and scheduling.


1084-1084: LGTM! New dashboard query.

This query endpoint will allow clients to fetch the detailed dashboard information including cached items.


1216-1217: LGTM! Well-structured mutations for cache management.

The updated return type for previewItemSQL and the new setDashboardSchedule mutation are well-designed, providing all necessary information for cache management.

Comment on lines +900 to +961
input SetDashboardScheduleInput {
cacheEnabled: Boolean!
schedule: SetDashboardScheduleData!
}

type DashboardSchedule {
frequency: ScheduleFrequencyEnum!
hour: Int!
minute: Int!
day: CacheScheduleDayEnum
timezone: String
cron: String
}

input SetDashboardScheduleData {
frequency: ScheduleFrequencyEnum!
hour: Int
minute: Int
day: CacheScheduleDayEnum
timezone: String
cron: String
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistency between input and output schedule types.

There's an inconsistency between DashboardSchedule (output) and SetDashboardScheduleData (input):

  • In the output type, hour and minute are required fields.
  • In the input type, they are optional.

This could lead to confusion when implementing resolvers that must handle these potentially missing fields.

Consider either:

  1. Making both consistent (both required or both optional)
  2. Adding validations in the resolver to ensure hour and minute are provided when needed
  3. Renaming for clarity (e.g., DashboardScheduleInput and DashboardSchedule)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (7)
wren-ui/src/utils/validator/cronValidator.ts (1)

7-13: Document the unused parameter and its purpose

The cronValidator function takes a first parameter _ that is unused. Consider documenting why this parameter exists but is unused, particularly for developers not familiar with the form validation library's contract.

-export const cronValidator = (_, value: string) => {
+/**
+ * Validates a cron expression for form validation
+ * @param _ - Unused parameter (required by form validator interface)
+ * @param value - The cron expression string to validate
+ * @returns A Promise that resolves if valid or rejects with error message if invalid
+ */
+export const cronValidator = (_, value: string) => {
  if (!value) return Promise.reject(ERROR_TEXTS.CRON.REQUIRED);
  if (!isValidCron(value)) {
    return Promise.reject(ERROR_TEXTS.CRON.INVALID);
  }
  return Promise.resolve();
};
wren-ui/src/pages/home/dashboard.tsx (1)

22-22: Consider adding null checking for the dashboard grid ref

While the current optional chaining approach works, consider adding more robust error handling for accessing the onRefreshAll method.

-  const dashboardGridRef = useRef<{ onRefreshAll: () => void }>(null);
+  const dashboardGridRef = useRef<{ onRefreshAll: () => void }>(null);
+  
+  const refreshAllDashboardItems = () => {
+    if (dashboardGridRef.current) {
+      dashboardGridRef.current.onRefreshAll();
+    } else {
+      console.warn('Dashboard grid reference not available');
+    }
+  };
wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (3)

12-17: Add JSDoc documentation for better code understanding

Add proper documentation to the props interface to explain each property's purpose and requirements.

+/**
+ * Props for the DashboardHeader component
+ */
 interface Props {
+  /** ISO timestamp string for the next scheduled cache refresh */
   nextScheduleTime?: string;
+  /** Schedule configuration object containing frequency and timing details */
   schedule?: Schedule;
+  /** Callback fired when the cache settings action is triggered */
   onCacheSettings?: () => void;
+  /** Callback fired when the refresh all action is triggered */
   onRefreshAll?: () => void;
 }

42-47: Replace empty div with proper CSS for spacing

The empty div at line 44 is being used as a spacer. Consider using CSS for proper layout alignment.

-    <StyledHeader>
-      <div />
-      <div>
+    <StyledHeader>
+      <div>

Update the StyledHeader styled component:

 const StyledHeader = styled.div`
   display: flex;
   align-items: center;
-  justify-content: space-between;
+  justify-content: flex-end;
   min-height: 49px;
   padding: 8px 16px;
   background-color: white;
   border-bottom: 1px solid var(--gray-4);
 `;

72-74: Add accessibility attributes to the dropdown button

The dropdown button lacks proper accessibility attributes like aria-label to help screen reader users understand its purpose.

-                <Button type="text" icon={<MoreIcon />}></Button>
+                <Button 
+                  type="text" 
+                  icon={<MoreIcon />} 
+                  aria-label="Dashboard cache options"
+                  title="Dashboard cache options"
+                ></Button>
wren-ui/src/components/pages/home/dashboardGrid/index.tsx (2)

286-288: Hard-coded lastRefreshTime placeholder will surface in production

A fixed ISO string will mislead users once the feature ships.
Until the backend field arrives, at least guard it behind a feature flag or leave it empty.


292-298: Hook order feels okay but warrants a sanity check

useImperativeHandle closes over previewItemSQL, which is declared after the hook call. Though JavaScript captures by reference and the later assignment is visible, the unusual ordering can be confusing.

Consider moving the usePreviewItemSqlMutation declaration above useImperativeHandle for clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f6a77a and b455042.

📒 Files selected for processing (13)
  • wren-ui/package.json (4 hunks)
  • wren-ui/src/components/diagram/CustomDropdown.tsx (3 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/index.tsx (4 hunks)
  • wren-ui/src/import/antd.ts (1 hunks)
  • wren-ui/src/pages/home/dashboard.tsx (3 hunks)
  • wren-ui/src/styles/antd-variables.less (1 hunks)
  • wren-ui/src/utils/enum/dropdown.ts (1 hunks)
  • wren-ui/src/utils/error/dictionary.ts (1 hunks)
  • wren-ui/src/utils/time.ts (1 hunks)
  • wren-ui/src/utils/validator/cronValidator.ts (1 hunks)
  • wren-ui/src/utils/validator/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (6)
  • wren-ui/src/utils/validator/index.ts
  • wren-ui/src/utils/time.ts
  • wren-ui/src/utils/enum/dropdown.ts
  • wren-ui/src/styles/antd-variables.less
  • wren-ui/src/utils/error/dictionary.ts
  • wren-ui/src/import/antd.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • wren-ui/package.json
🧰 Additional context used
🧬 Code Graph Analysis (2)
wren-ui/src/utils/validator/cronValidator.ts (1)
wren-ui/src/utils/error/dictionary.ts (1)
  • ERROR_TEXTS (1-153)
wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (4)
wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (2)
  • Schedule (27-34)
  • getScheduleText (98-129)
wren-ui/src/utils/time.ts (1)
  • getCompactTime (21-23)
wren-ui/src/components/diagram/CustomDropdown.tsx (1)
  • DashboardDropdown (137-162)
wren-ui/src/utils/icons.ts (1)
  • MoreIcon (90-90)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (8)
wren-ui/src/components/diagram/CustomDropdown.tsx (3)

11-11: LGTM - Import for new icon added correctly

The DatabaseOutlined icon import is properly added to support the new cache settings UI element.


137-162: LGTM - New DashboardDropdown follows established patterns

The new DashboardDropdown component correctly follows the existing pattern using the makeDropdown factory function and properly implements the cache management actions.


186-186: LGTM - Label update improves clarity

Changing the label from "Refresh" to "Refresh cache" improves clarity about what's being refreshed.

wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (1)

19-27: LGTM - Clean styled component implementation

The styling follows the project's conventions and provides a clean layout for the header.

wren-ui/src/components/pages/home/dashboardGrid/index.tsx (1)

162-172: Missing dependency on itemRefs breaks onRefreshAll after items update

useImperativeHandle captures itemRefs.current only once.
If items change (add/remove items) the exposed onRefreshAll keeps the old snapshot and skips new refs.

Add itemRefs.current (or simply Object.keys(itemRefs.current).length) to the dependency list.

-  }, [items]);
+  }, [items, itemRefs.current]);
wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (3)

116-118: Possible undefined access when day is absent

capitalize(schedule.day.toLowerCase()) assumes schedule.day is defined.
For safety:

- return `Cache refreshes every ${capitalize(schedule.day.toLowerCase())} at ${time}`;
+ return schedule.day
+   ? `Cache refreshes every ${capitalize(schedule.day.toLowerCase())} at ${time}`
+   : '';

165-175: 🛠️ Refactor suggestion

getNextScheduleByCron uses a non-existent parse method

After fixing the import, use cronParser.parseExpression:

-  try {
-    const interval = CronExpressionParser.parse(cron, { tz: 'UTC' });
+  try {
+    const interval = cronParser.parseExpression(cron, { tz: 'UTC' });

Also, consider caching the parser instance or surrounding with stricter error handling to avoid unhandled exceptions on invalid cron strings.

⛔ Skipped due to learnings
Learnt from: andreashimin
PR: Canner/WrenAI#1587
File: wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx:3-4
Timestamp: 2025-04-23T09:21:38.365Z
Learning: The cron-parser package (v5+) provides a named export `CronExpressionParser` with a `parse` method that should be used like: `import { CronExpressionParser } from 'cron-parser'; const interval = CronExpressionParser.parse(cronExpression, options);`

3-4: ⚠️ Potential issue

Incorrect import – cron-parser has no named export CronExpressionParser

cron-parser exposes parseExpression (and is typically imported as the default or namespace).
This import will be undefined at runtime and the subsequent .parse call will throw.

-import { CronExpressionParser } from 'cron-parser';
+import * as cronParser from 'cron-parser';
⛔ Skipped due to learnings
Learnt from: andreashimin
PR: Canner/WrenAI#1587
File: wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx:3-4
Timestamp: 2025-04-23T09:21:38.365Z
Learning: The cron-parser package (v5+) provides a named export `CronExpressionParser` with a `parse` method that should be used like: `import { CronExpressionParser } from 'cron-parser'; const interval = CronExpressionParser.parse(cronExpression, options);`

Comment thread wren-ui/src/utils/validator/cronValidator.ts
Comment thread wren-ui/src/pages/home/dashboard.tsx Outdated
Comment thread wren-ui/src/pages/home/dashboard.tsx Outdated
Comment thread wren-ui/src/components/pages/home/dashboardGrid/index.tsx
Comment thread wren-ui/src/components/pages/home/dashboardGrid/index.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (5)
wren-ui/src/apollo/server/services/dashboardService.ts (3)

387-404: The cron expression generation logic could be moved to a utility file

This method is a good candidate for extraction to improve modularity.

Cron expression utilities like this might be needed in other parts of the application as it grows.


466-520: Consider extracting parseCronExpression to a utility file

This method contains complex cron parsing logic that could be useful elsewhere.

Extracting cron-related functions to a dedicated utility file would improve code organization and reusability.


146-146: ⚠️ Potential issue

Guard against null dashboards

Spreading null results in {} which silently hides errors and may cause "undefined property" crashes elsewhere.

-    return { ...dashboard };
+    if (!dashboard) {
+      throw new Error(`Dashboard for project ${project.id} not found`);
+    }
+    return { ...dashboard };
wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (2)

16-17: Consider adjusting logger level for production

Setting the logger level to 'debug' may generate excessive logs in production.

-logger.level = 'debug';
+logger.level = process.env.NODE_ENV === 'production' ? 'info' : 'debug';

111-162: Consider implementing batch processing for dashboard items

Using Promise.all for all items might cause performance issues with a large number of items.

Implement a batching mechanism to process dashboard items in smaller groups, preventing potential resource exhaustion:

// Example batching implementation
const batchSize = 5;
const itemBatches = [];

for (let i = 0; i < items.length; i += batchSize) {
  itemBatches.push(items.slice(i, i + batchSize));
}

for (const batch of itemBatches) {
  await Promise.all(
    batch.map(async (item) => {
      // existing processing logic
    })
  );
}
🧹 Nitpick comments (4)
wren-ui/src/apollo/server/services/dashboardService.ts (3)

256-317: Consider moving timezone conversion methods to a utility file

The toUTC method contains complex timezone conversion logic that would be better placed in a utility file for better testability and reuse.

These timezone conversion methods are complex and could be reused elsewhere in the application. Consider extracting them to a dedicated utility file like timezone-utils.ts.


319-385: Consider extracting toTimezone to a utility file

This method has similar complexity to toUTC and both handle timezone conversions that might be needed elsewhere.

Both timezone conversion methods contain similar logic with opposite operations. Extracting them to a utility file would improve modularity and testability.


445-463: Enhance validation error message for custom cron expressions

The error message for minimum interval could be more specific.

-          'Custom cron expression must be at least 10 minutes apart',
+          `Custom cron expression must have intervals of at least 10 minutes (current: ${Math.floor(diff / 60000)} minutes)`,

This would provide the user with more actionable information about how close they are to the minimum requirement.

wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (1)

152-160: Consider using higher log level for refresh failures

Debug level might be too low for important refresh failures, making it harder to detect issues in production.

-              logger.debug(
+              logger.info(
                 `Error refreshing cache for item ${item.id}: ${error.message}`,
               );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b455042 and 8257de6.

📒 Files selected for processing (13)
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (1 hunks)
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts (5 hunks)
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts (2 hunks)
  • wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (1 hunks)
  • wren-ui/src/apollo/server/models/dashboard.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts (4 hunks)
  • wren-ui/src/apollo/server/schema.ts (4 hunks)
  • wren-ui/src/apollo/server/services/dashboardService.ts (5 hunks)
  • wren-ui/src/apollo/server/services/queryService.ts (6 hunks)
  • wren-ui/src/apollo/server/types/context.ts (3 hunks)
  • wren-ui/src/common.ts (6 hunks)
  • wren-ui/src/pages/api/graphql.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • wren-ui/src/pages/api/graphql.ts
  • wren-ui/src/apollo/server/types/context.ts
  • wren-ui/src/apollo/server/models/dashboard.ts
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts
  • wren-ui/src/common.ts
  • wren-ui/src/apollo/server/services/queryService.ts
  • wren-ui/src/apollo/server/schema.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)

Comment on lines +477 to +516
switch (dashboard.scheduleFrequency) {
case ScheduleFrequencyEnum.CUSTOM:
return {
frequency: ScheduleFrequencyEnum.CUSTOM,
hour: 0,
minute: 0,
day: null,
timezone: dashboard.scheduleTimezone || '',
cron: dashboard.scheduleCron,
};
case ScheduleFrequencyEnum.DAILY:
case ScheduleFrequencyEnum.WEEKLY: {
const parts = dashboard.scheduleCron.split(' ');
if (parts.length !== 5) {
throw new Error('Invalid cron expression format');
}
const [minute, hour, , , dayOfWeek] = parts;
return this.toTimezone({
frequency: dashboard.scheduleFrequency,
hour: parseInt(hour, 10),
minute: parseInt(minute, 10),
day:
dashboard.scheduleFrequency === ScheduleFrequencyEnum.WEEKLY
? (dayOfWeek as CacheScheduleDayEnum)
: null,
timezone: dashboard.scheduleTimezone || '',
cron: null,
} as DashboardSchedule);
}
case ScheduleFrequencyEnum.NEVER: {
return {
frequency: ScheduleFrequencyEnum.NEVER,
hour: null,
minute: null,
day: null,
timezone: dashboard.scheduleTimezone || '',
cron: null,
} as DashboardSchedule;
}
default: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Add test cases for edge cases in cron expression parsing

The switch statement handles different schedule frequencies, but edge cases should be thoroughly tested.

Please verify that all edge cases are covered in tests, particularly:

  • What happens with malformed cron expressions
  • Handling of timezone edge cases when crossing day boundaries
  • Conversion between different schedule frequencies

🏁 Script executed:

#!/bin/bash
# Check if there are tests for the parseCronExpression method
rg -A 3 "test.*parseCronExpression" --type ts

Length of output: 47


Add comprehensive tests for cron expression parsing in dashboardService

It appears there are no existing tests covering the inline cron‐parsing logic in dashboardService.ts. We should add specs for:

  • Malformed cron strings (e.g. wrong field count, non-numeric values)
  • Timezone conversions around day boundaries (UTC→local and vice versa)
  • Frequency conversions (CUSTOM, DAILY, WEEKLY, NEVER) and their expected outputs

Areas to cover:

  • The dashboard.scheduleCron.split(' ') path in DAILY/WEEKLY
  • The error path when parts.length !== 5
  • toTimezone(...) adjustments for WEEKLY dayOfWeek

Please add these test cases under the existing service‐spec file or create a new dashboardService.spec.ts.

Comment on lines +185 to +195
private calculateNextRunTime(cronExpression: string): Date | null {
try {
const interval = CronExpressionParser.parse(cronExpression, {
currentDate: new Date(),
});
return interval.next().toDate();
} catch (error) {
logger.error(`Failed to parse cron expression: ${error.message}`);
return null;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract calculateNextRunTime to a shared utility

This method duplicates logic that also exists in the dashboard service.

This method is identical to the one in dashboardService.ts. Consider extracting it to a shared utility file to avoid duplication and ensure consistent behavior.

}
}

private async refreshDashboardCache(dashboard: any): Promise<void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace any type with a proper type for dashboard parameter

Using any type reduces type safety and can lead to runtime errors.

-  private async refreshDashboardCache(dashboard: any): Promise<void> {
+  private async refreshDashboardCache(dashboard: { id: number; scheduleCron: string }): Promise<void> {

Or preferably use the Dashboard type from your repositories:

-  private async refreshDashboardCache(dashboard: any): Promise<void> {
+  private async refreshDashboardCache(dashboard: Dashboard): Promise<void> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async refreshDashboardCache(dashboard: any): Promise<void> {
private async refreshDashboardCache(dashboard: Dashboard): Promise<void> {
Suggested change
private async refreshDashboardCache(dashboard: any): Promise<void> {
private async refreshDashboardCache(dashboard: { id: number; scheduleCron: string }): Promise<void> {

Comment on lines +29 to +52
constructor({
dashboardRepository,
dashboardItemRepository,
dashboardItemRefreshJobRepository,
projectService,
deployService,
queryService,
}: {
dashboardRepository: IDashboardRepository;
dashboardItemRepository: IDashboardItemRepository;
dashboardItemRefreshJobRepository: IDashboardItemRefreshJobRepository;
projectService: IProjectService;
deployService: IDeployService;
queryService: IQueryService;
}) {
this.dashboardRepository = dashboardRepository;
this.dashboardItemRepository = dashboardItemRepository;
this.dashboardItemRefreshJobRepository = dashboardItemRefreshJobRepository;
this.projectService = projectService;
this.deployService = deployService;
this.queryService = queryService;
this.intervalTime = 60000; // 1 minute
this.start();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add a method to stop the background tracker

The tracker starts in the constructor but has no way to stop it, which can cause issues during testing or application shutdown.

+  private interval: NodeJS.Timeout;
   constructor({
     // ...existing code
   }) {
     // ...existing code
-    this.start();
   }

+  public start(): void {
+    if (this.interval) {
+      clearInterval(this.interval);
+    }
+    logger.info('Dashboard cache background tracker started');
+    this.interval = setInterval(() => {
+      this.checkAndRefreshCaches();
+    }, this.intervalTime);
+  }
+
+  public stop(): void {
+    if (this.interval) {
+      clearInterval(this.interval);
+      this.interval = null;
+      logger.info('Dashboard cache background tracker stopped');
+    }
+  }

-  private start(): void {
-    logger.info('Dashboard cache background tracker started');
-    setInterval(() => {
-      this.checkAndRefreshCaches();
-    }, this.intervalTime);
-  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constructor({
dashboardRepository,
dashboardItemRepository,
dashboardItemRefreshJobRepository,
projectService,
deployService,
queryService,
}: {
dashboardRepository: IDashboardRepository;
dashboardItemRepository: IDashboardItemRepository;
dashboardItemRefreshJobRepository: IDashboardItemRefreshJobRepository;
projectService: IProjectService;
deployService: IDeployService;
queryService: IQueryService;
}) {
this.dashboardRepository = dashboardRepository;
this.dashboardItemRepository = dashboardItemRepository;
this.dashboardItemRefreshJobRepository = dashboardItemRefreshJobRepository;
this.projectService = projectService;
this.deployService = deployService;
this.queryService = queryService;
this.intervalTime = 60000; // 1 minute
this.start();
}
// add a field to keep track of the interval
private interval: NodeJS.Timeout;
constructor({
dashboardRepository,
dashboardItemRepository,
dashboardItemRefreshJobRepository,
projectService,
deployService,
queryService,
}: {
dashboardRepository: IDashboardRepository;
dashboardItemRepository: IDashboardItemRepository;
dashboardItemRefreshJobRepository: IDashboardItemRefreshJobRepository;
projectService: IProjectService;
deployService: IDeployService;
queryService: IQueryService;
}) {
this.dashboardRepository = dashboardRepository;
this.dashboardItemRepository = dashboardItemRepository;
this.dashboardItemRefreshJobRepository = dashboardItemRefreshJobRepository;
this.projectService = projectService;
this.deployService = deployService;
this.queryService = queryService;
this.intervalTime = 60000; // 1 minute
// removed automatic start here to defer to the new public start()
}
public start(): void {
if (this.interval) {
clearInterval(this.interval);
}
logger.info('Dashboard cache background tracker started');
this.interval = setInterval(() => {
this.checkAndRefreshCaches();
}, this.intervalTime);
}
public stop(): void {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
logger.info('Dashboard cache background tracker stopped');
}
}
// removed the old private start(), since it's been promoted to public start()

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (1)

40-62: Reduce duplication by re-using createScheduleData

Many tests manually craft DashboardSchedule objects, duplicating defaults and, as seen, sometimes introducing type errors. Using createScheduleData (or extracting a similar factory for raw schedules) would:

  • centralise default handling,
  • enforce type-correct values,
  • cut ~100 LOC of repetitive setup.

This also future-proofs tests against interface changes.

wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (2)

153-153: Fix typo in comment

There's a typo in the comment: "postpond" should be "postpone".

-  // postpond the time if it's already passed
+  // postpone the time if it's already passed

207-228: Improve error handling during form submission

Currently, errors during form submission are only logged to the console, which provides no feedback to users. Consider adding a notification or error message in the UI.

  const submit = () => {
    form
      .validateFields()
      .then(async (values) => {
        const { schedule } = values;
-        await onSubmit({
-          ...values,
-          schedule: values.enabled
-            ? {
-                frequency: schedule?.frequency,
-                day: schedule?.day,
-                hour: schedule?.time?.hour(),
-                minute: schedule?.time?.minute(),
-                cron: schedule?.cron,
-                timezone: browserTimeZone,
-              }
-            : null,
-        });
-        onClose();
+        try {
+          await onSubmit({
+            ...values,
+            schedule: values.enabled
+              ? {
+                  frequency: schedule?.frequency,
+                  day: schedule?.day,
+                  hour: schedule?.time?.hour(),
+                  minute: schedule?.time?.minute(),
+                  cron: schedule?.cron,
+                  timezone: browserTimeZone,
+                }
+              : null,
+          });
+          onClose();
+        } catch (error) {
+          console.error(error);
+          // Add UI notification here, e.g.:
+          // notification.error({ message: 'Failed to save settings', description: error.message });
+        }
      })
      .catch(console.error);
  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8257de6 and 628bbad.

⛔ Files ignored due to path filters (1)
  • wren-ui/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (34)
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js (1 hunks)
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (1 hunks)
  • wren-ui/package.json (4 hunks)
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts (5 hunks)
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts (2 hunks)
  • wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (1 hunks)
  • wren-ui/src/apollo/server/backgrounds/index.ts (1 hunks)
  • wren-ui/src/apollo/server/models/dashboard.ts (1 hunks)
  • wren-ui/src/apollo/server/models/index.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/index.ts (1 hunks)
  • wren-ui/src/apollo/server/resolvers.ts (2 hunks)
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts (4 hunks)
  • wren-ui/src/apollo/server/schema.ts (4 hunks)
  • wren-ui/src/apollo/server/services/dashboardService.ts (5 hunks)
  • wren-ui/src/apollo/server/services/queryService.ts (6 hunks)
  • wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (1 hunks)
  • wren-ui/src/apollo/server/types/context.ts (3 hunks)
  • wren-ui/src/apollo/server/utils/timezone.ts (1 hunks)
  • wren-ui/src/common.ts (6 hunks)
  • wren-ui/src/components/diagram/CustomDropdown.tsx (3 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/index.tsx (4 hunks)
  • wren-ui/src/import/antd.ts (1 hunks)
  • wren-ui/src/pages/api/graphql.ts (3 hunks)
  • wren-ui/src/pages/home/dashboard.tsx (3 hunks)
  • wren-ui/src/styles/antd-variables.less (1 hunks)
  • wren-ui/src/utils/enum/dropdown.ts (1 hunks)
  • wren-ui/src/utils/error/dictionary.ts (1 hunks)
  • wren-ui/src/utils/time.ts (1 hunks)
  • wren-ui/src/utils/validator/cronValidator.ts (1 hunks)
  • wren-ui/src/utils/validator/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • wren-ui/src/utils/validator/cronValidator.ts
  • wren-ui/src/import/antd.ts
🚧 Files skipped from review as they are similar to previous changes (30)
  • wren-ui/src/utils/time.ts
  • wren-ui/src/utils/validator/index.ts
  • wren-ui/src/apollo/server/models/index.ts
  • wren-ui/package.json
  • wren-ui/src/utils/enum/dropdown.ts
  • wren-ui/src/styles/antd-variables.less
  • wren-ui/src/apollo/server/repositories/index.ts
  • wren-ui/src/apollo/server/utils/timezone.ts
  • wren-ui/src/apollo/server/types/context.ts
  • wren-ui/src/pages/api/graphql.ts
  • wren-ui/src/apollo/server/repositories/dashboardRepository.ts
  • wren-ui/src/components/diagram/CustomDropdown.tsx
  • wren-ui/src/pages/home/dashboard.tsx
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js
  • wren-ui/src/apollo/server/backgrounds/index.ts
  • wren-ui/src/common.ts
  • wren-ui/src/utils/error/dictionary.ts
  • wren-ui/src/apollo/server/repositories/dashboardCacheRefreshRepository.ts
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js
  • wren-ui/src/apollo/server/models/dashboard.ts
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts
  • wren-ui/src/apollo/server/services/queryService.ts
  • wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts
  • wren-ui/src/apollo/server/resolvers.ts
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts
  • wren-ui/src/components/pages/home/dashboardGrid/index.tsx
  • wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx
  • wren-ui/src/apollo/server/schema.ts
  • wren-ui/src/apollo/server/services/dashboardService.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (2)
wren-ui/src/apollo/server/services/dashboardService.ts (1)
  • DashboardService (60-521)
wren-ui/src/apollo/server/models/dashboard.ts (2)
  • DashboardSchedule (28-35)
  • SetDashboardCacheData (37-40)
🪛 Biome (1.9.4)
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts

[error] 30-30: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (1)

152-169: Potential timezone-dependent flakiness in date assertion

The expected timestamp is derived from new Date() without freezing the clock or forcing a timezone. On CI runners that are not UTC, cron-parser and Date may interpret 12:00 differently, leading to sporadic failures.

Consider:

-const expectedTime = new Date();
-expectedTime.setHours(12, 0, 0, 0);
+// Freeze time to a known instant to remove host-timezone influence
+jest.useFakeTimers().setSystemTime(new Date('2024-04-22T00:00:00Z'));
+const expectedTime = new Date('2024-04-22T12:00:00Z'); // explicit UTC

Remember to call jest.useRealTimers() in afterEach. This makes the assertion deterministic across environments.

wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (2)

165-175: Good implementation of cron parsing with timezone handling

The function correctly handles cron expressions, using UTC for parsing (which aligns with server-side scheduling) and converting to local time for display. It also properly validates the cron expression and handles errors gracefully.


314-321: Good implementation of next schedule display

The UI thoughtfully shows the next scheduled refresh time with the user's timezone, providing clear feedback about when the cache will be refreshed next. This helps users understand the impact of their schedule configuration.

Comment on lines +88 to +144
describe('generateCronExpression', () => {
it('should generate correct cron expression for daily schedule', () => {
const schedule = {
frequency: ScheduleFrequencyEnum.DAILY,
hour: 14,
minute: 30,
timezone: '',
day: null,
cron: null,
};

const result = dashboardService.testGenerateCronExpression(schedule);
expect(result).toBe('30 14 * * *');
});

it('should generate correct cron expression for weekly schedule', () => {
const schedule = {
frequency: ScheduleFrequencyEnum.WEEKLY,
day: CacheScheduleDayEnum.MON,
hour: 9,
minute: 0,
timezone: '',
cron: null,
};

const result = dashboardService.testGenerateCronExpression(schedule);
expect(result).toBe('0 9 * * MON');
});

it('should return custom cron expression for custom schedule', () => {
const schedule = {
frequency: ScheduleFrequencyEnum.CUSTOM,
cron: '0 0 * * *',
timezone: '',
day: null,
hour: 0,
minute: 0,
};

const result = dashboardService.testGenerateCronExpression(schedule);
expect(result).toBe('0 0 * * *');
});

it('should return null for never schedule', () => {
const schedule = {
frequency: ScheduleFrequencyEnum.NEVER,
timezone: '',
day: null,
hour: 0,
minute: 0,
cron: null,
};

const result = dashboardService.testGenerateCronExpression(schedule);
expect(result).toBeNull();
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

null values break the DashboardSchedule contract

DashboardSchedule marks day and cron as required, non-nullable strings/enums:

day: CacheScheduleDayEnum;
cron: string;

Directly constructing objects with
day: null or cron: null (e.g. lines 90-97, 104-111, 132-139, 196-205, 418-425) causes TypeScript compile errors and defeats strict-null checks.

Prefer one of the following:

  1. Use the already-defined createScheduleData helper – it always supplies valid defaults.
  2. If you must inline objects, provide dummy but type-correct values:
- day: null,
- cron: null,
+ day: CacheScheduleDayEnum.MON, // ignored for DAILY/CUSTOM
+ cron: '',                      // ignored when not CUSTOM

Refactor all occurrences to keep the test suite compiling under strictNullChecks.

Also applies to: 196-206, 417-427

Comment on lines +29 to +31
public testValidateScheduleInput(data: SetDashboardCacheData): void {
return this.validateScheduleInput(data);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid returning a value from a void-typed helper

validateScheduleInput returns void, yet the wrapper method returns its (non-existent) value. Besides triggering the Biome warning, this can confuse future readers about the intent.

-  public testValidateScheduleInput(data: SetDashboardCacheData): void {
-    return this.validateScheduleInput(data);
-  }
+  public testValidateScheduleInput(data: SetDashboardCacheData): void {
+    // simply delegate – nothing to return
+    this.validateScheduleInput(data);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public testValidateScheduleInput(data: SetDashboardCacheData): void {
return this.validateScheduleInput(data);
}
public testValidateScheduleInput(data: SetDashboardCacheData): void {
// simply delegate – nothing to return
this.validateScheduleInput(data);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

Comment thread wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (1)

9-9: Consider using an enum for the status field

The inline comment indicates this field has specific valid values ('success', 'failed', 'in_progress'). Consider adding a CHECK constraint to ensure only these values can be inserted, matching the enum defined in the repository.

- table.string('status').notNullable(); // 'success', 'failed', 'in_progress'
+ table.string('status').notNullable().checkIn(['success', 'failed', 'in_progress']); // Enforces valid status values
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 628bbad and 8526943.

📒 Files selected for processing (3)
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardItemRefreshJobRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wren-ui/src/apollo/server/repositories/index.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
wren-ui/src/apollo/server/repositories/dashboardItemRefreshJobRepository.ts (1)
wren-ui/src/apollo/server/repositories/baseRepository.ts (2)
  • IBasicRepository (10-42)
  • BaseRepository (44-185)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
wren-ui/src/apollo/server/repositories/dashboardItemRefreshJobRepository.ts (1)

1-33: LGTM - Well-structured repository implementation

The repository follows the established pattern in the codebase, with clear naming and proper inheritance from BaseRepository. The enum, interface, and class definitions are well-organized and provide a complete representation of dashboard item refresh jobs.

wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (2)

1-31: Table schema looks solid with proper constraints

The migration creates a well-structured table with appropriate fields, constraints, and indexes for tracking dashboard item refresh jobs. Good job adding the comment on the hash field to explain its purpose.


33-35: LGTM - Clean down migration

The down migration properly drops the table to revert the changes.

- Implemented `setDashboardSchedule` resolver to manage dashboard scheduling and caching settings.
- Added migration to alter the dashboard table for new scheduling fields.
- Updated relevant services and models to support caching and scheduling logic.
- Enhanced IbisAdaptor to handle cache-related query parameters.
onlyjackfrost and others added 15 commits April 28, 2025 10:49
- Added `schedule_timezone` field to the dashboard table migration.
- Updated GraphQL schema to reflect changes in the `previewItemSQL` return type.
- Modified `IbisAdaptor` to correctly handle cache-related headers.
- Refactored `dashboardRepository` and `dashboardService` to utilize the new `scheduleTimezone` field.
- Adjusted `dashboardResolver` to format `nextScheduledAt` correctly.
- Set default value for `refresh` in `PreviewItemSQLInput` to `false`.
- Updated `SetDashboardScheduleData` to allow optional `hour` and `minute` fields.
- Standardized `ScheduleFrequencyEnum` values to uppercase.
- Introduced `Dashboard` type in GraphQL schema for enhanced dashboard management.
- Modified `setDashboardSchedule` resolver to return the updated `Dashboard` object.
- Adjusted `dashboardService` to handle new scheduling logic and return types.
- Updated tests to reflect changes in scheduling frequency constants and input handling.
- Added migration to create `dashboard_cache_refresh` table for tracking cache refresh jobs.
- Introduced `DashboardCacheRefreshRepository` for managing cache refresh records.
- Implemented `DashboardCacheBackgroundTracker` to periodically refresh dashboard caches based on scheduling.
- Updated context and service layers to integrate new cache refresh functionality.
- Enhanced GraphQL API to support the new caching features.
…otected

- Updated methods in `DashboardService` to be `protected`, allowing access in subclasses.
- Introduced a `TestDashboardService` class in tests to facilitate unit testing of these methods.
- Adjusted test cases to utilize the new test class for invoking protected methods directly.
- Replaced sequential processing of cache refresh jobs with concurrent execution using Promise.all.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (6)
wren-ui/src/apollo/server/services/dashboardService.ts (2)

418-464: Move validation utilities to a dedicated module

The validation logic could be extracted to a utility file for better testability and reuse. This would allow it to be tested independently and potentially reused elsewhere.

As mentioned in previous comments, moving validateScheduleInput (and possibly other helper methods like parseCronExpression and toTimezone) to a utility module would improve maintainability and testability.


146-146: ⚠️ Potential issue

Guard against null dashboards

The implementation spreads a potentially null dashboard object without checking, which could lead to runtime errors. This doesn't fully fix the issue noted in the previous review.

-    return { ...dashboard };
+    if (!dashboard) {
+      throw new Error(`Dashboard for project ${project.id} not found`);
+    }
+    return { ...dashboard };

Fail fast to prevent subtle errors when a dashboard doesn't exist.

wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (2)

1-4: Plan to migrate away from moment.js

Moment.js is in maintenance mode and no longer recommended for new development. Consider migrating to a more modern alternative like date-fns, luxon, or day.js in a future update.


117-117: ⚠️ Potential issue

Fix potential runtime error with undefined day

The code assumes schedule.day is always defined in the WEEKLY frequency case, which could cause a runtime error.

-      return `Cache refreshes every ${capitalize(schedule.day.toLowerCase())} at ${time}`;
+      return `Cache refreshes every ${capitalize(schedule.day?.toLowerCase() || '')} at ${time}`;
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (2)

29-31: ⚠️ Potential issue

Fix return in void-typed function

The function returns a value despite having a void return type.

public testValidateScheduleInput(data: SetDashboardCacheData): void {
-  return this.validateScheduleInput(data);
+  this.validateScheduleInput(data);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


88-144: ⚠️ Potential issue

Update test data to match type constraints

The test data uses null values for properties that are defined as non-nullable in the DashboardSchedule type.

Instead of using null for required fields like day and cron, provide dummy but type-correct values using the createScheduleData helper or with appropriate defaults.

🧹 Nitpick comments (1)
wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (1)

165-175: Improve error handling in getNextScheduleByCron

The error is only logged to console but not communicated to the user. Consider adding more robust error handling.

try {
  const interval = CronExpressionParser.parse(cron, { tz: 'UTC' });
  const targetTime = moment.utc(interval.next().toDate()).local();
  return targetTime.isValid() ? targetTime.format('YYYY-MM-DD HH:mm') : null;
} catch (error) {
  console.warn(error);
+  // Consider returning a specific error value or using a notification system
+  // to alert the user about cron parsing errors
  return null;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8526943 and 34ba7b8.

⛔ Files ignored due to path filters (1)
  • wren-ui/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (34)
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js (1 hunks)
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js (1 hunks)
  • wren-ui/package.json (4 hunks)
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts (5 hunks)
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts (2 hunks)
  • wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts (1 hunks)
  • wren-ui/src/apollo/server/backgrounds/index.ts (1 hunks)
  • wren-ui/src/apollo/server/models/dashboard.ts (1 hunks)
  • wren-ui/src/apollo/server/models/index.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardItemRefreshJobRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/dashboardRepository.ts (1 hunks)
  • wren-ui/src/apollo/server/repositories/index.ts (1 hunks)
  • wren-ui/src/apollo/server/resolvers.ts (2 hunks)
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts (4 hunks)
  • wren-ui/src/apollo/server/schema.ts (4 hunks)
  • wren-ui/src/apollo/server/services/dashboardService.ts (5 hunks)
  • wren-ui/src/apollo/server/services/queryService.ts (6 hunks)
  • wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (1 hunks)
  • wren-ui/src/apollo/server/types/context.ts (3 hunks)
  • wren-ui/src/apollo/server/utils/timezone.ts (1 hunks)
  • wren-ui/src/common.ts (6 hunks)
  • wren-ui/src/components/diagram/CustomDropdown.tsx (3 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/index.tsx (4 hunks)
  • wren-ui/src/import/antd.ts (1 hunks)
  • wren-ui/src/pages/api/graphql.ts (3 hunks)
  • wren-ui/src/pages/home/dashboard.tsx (3 hunks)
  • wren-ui/src/styles/antd-variables.less (1 hunks)
  • wren-ui/src/utils/enum/dropdown.ts (1 hunks)
  • wren-ui/src/utils/error/dictionary.ts (1 hunks)
  • wren-ui/src/utils/time.ts (1 hunks)
  • wren-ui/src/utils/validator/cronValidator.ts (1 hunks)
  • wren-ui/src/utils/validator/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • wren-ui/src/apollo/server/models/index.ts
  • wren-ui/src/import/antd.ts
  • wren-ui/src/apollo/server/backgrounds/index.ts
🚧 Files skipped from review as they are similar to previous changes (28)
  • wren-ui/src/utils/validator/index.ts
  • wren-ui/src/utils/time.ts
  • wren-ui/src/apollo/server/repositories/index.ts
  • wren-ui/src/utils/enum/dropdown.ts
  • wren-ui/src/pages/api/graphql.ts
  • wren-ui/src/styles/antd-variables.less
  • wren-ui/src/utils/validator/cronValidator.ts
  • wren-ui/src/apollo/server/resolvers.ts
  • wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx
  • wren-ui/src/apollo/server/repositories/dashboardRepository.ts
  • wren-ui/src/utils/error/dictionary.ts
  • wren-ui/package.json
  • wren-ui/src/apollo/server/types/context.ts
  • wren-ui/migrations/20250422000000_alter_dashboard_table.js
  • wren-ui/src/apollo/server/utils/timezone.ts
  • wren-ui/src/components/diagram/CustomDropdown.tsx
  • wren-ui/src/apollo/server/backgrounds/dashboardCacheBackgroundTracker.ts
  • wren-ui/src/apollo/server/repositories/dashboardItemRefreshJobRepository.ts
  • wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts
  • wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js
  • wren-ui/src/apollo/server/adaptors/ibisAdaptor.ts
  • wren-ui/src/apollo/server/resolvers/dashboardResolver.ts
  • wren-ui/src/common.ts
  • wren-ui/src/pages/home/dashboard.tsx
  • wren-ui/src/apollo/server/models/dashboard.ts
  • wren-ui/src/components/pages/home/dashboardGrid/index.tsx
  • wren-ui/src/apollo/server/services/queryService.ts
  • wren-ui/src/apollo/server/schema.ts
🧰 Additional context used
🪛 Biome (1.9.4)
wren-ui/src/apollo/server/services/tests/dashboardService.test.ts

[error] 30-30: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (13)
wren-ui/src/apollo/server/services/dashboardService.ts (5)

79-126: Comprehensive implementation of dashboard scheduling

The implementation is thorough, handling input validation, dashboard verification, and conditional logic for caching state. Error handling is proper with try/catch and logging.


256-317: Well-implemented timezone conversion for UTC

The timezone conversion logic handles all edge cases including minute/hour carry-over and day adjustments for weekly schedules. Particularly impressed with handling edge cases for day boundaries.


319-385: Well-implemented conversion from UTC to timezone

The implementation mirrors the UTC conversion logic but in reverse direction, maintaining consistency in how timezone offsets are applied. The code properly handles all edge cases.


387-404: Clean implementation of cron expression generation

The method correctly converts schedules to UTC before generating cron expressions and handles all schedule frequency types appropriately.


466-520: Comprehensive cron expression parsing logic

The implementation handles all schedule frequency types and properly parses cron parts for time-based schedules. The timezone conversion is correctly applied.

Please consider adding tests for malformed cron expressions as mentioned in a previous comment, especially for cases like invalid field counts and handling timezone edge cases.

wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (3)

98-129: Well-structured schedule text generation

The implementation effectively handles all schedule frequency types, generating human-readable descriptions. The time formatting is consistent.


177-269: Clean implementation of the CacheSettingsDrawer component

The component properly handles form state, validation, and form submission. The form reset on drawer close prevents stale data.


271-324: Well-designed Schedule component

The Schedule component dynamically renders the appropriate input controls based on the selected frequency and calculates the next schedule preview.

wren-ui/src/apollo/server/services/tests/dashboardService.test.ts (5)

40-62: Well-designed test helper for creating schedule data

The createScheduleData helper function is well-designed, providing sensible defaults and flexibility for test cases.


146-217: Comprehensive tests for calculateNextRunTime

The tests thoroughly cover different schedule types, invalid inputs, and timezone handling.


219-405: Excellent timezone conversion tests

The tests are comprehensive, covering various edge cases including day boundary transitions, timezone offsets, and weekly schedule day adjustments.


595-774: Thorough validation testing

The tests verify all validation rules, error messages, and valid input scenarios.


777-975: Complete test coverage for setDashboardSchedule

The tests verify database interactions, error handling, and all schedule types.

Comment thread wren-ui/package.json
"private": true,
"scripts": {
"dev": "next dev",
"dev": "TZ=UTC next dev",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why TZ env ?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
wren-ui/src/components/pages/home/dashboardGrid/index.tsx (2)

147-149: forwardRef parameter type still incorrect
This was flagged earlier: the second parameter of forwardRef is a Ref<{ … }>not a RefObject.


156-162: Recreate item-refs only when needed & prune stale ones
Re-initialising every ref on each items change churns memory and breaks existing imperative handles. The previous review suggested a fix that is still outstanding.

-  useEffect(() => {
-    items.forEach((item) => {
-      itemRefs.current[item.id] = createRef();
-    });
-  }, [items]);
+  useEffect(() => {
+    const next: typeof itemRefs.current = {};
+    items.forEach((item) => {
+      next[item.id] = itemRefs.current[item.id] ?? createRef();
+    });
+    // remove refs for deleted items
+    Object.keys(itemRefs.current).forEach((key) => {
+      if (!next[key]) delete itemRefs.current[key];
+    });
+    itemRefs.current = next;
+  }, [items]);
🧹 Nitpick comments (6)
wren-ui/src/apollo/client/graphql/dashboard.ts (2)

70-75: Consider returning only the fields the UI really needs
data may contain a large result-set for some items. By always requesting the full payload together with the cache-metadata you increase network traffic and GC pressure on the client. If the UI only needs the first N rows, summary counts, etc., expose a dedicated field (or argument) instead of the entire result and request that here.


79-92: Extract a ScheduleFields fragment to avoid duplicated selections
The groups of fields (scheduleFrequency, scheduleTimezone, scheduleCron, nextScheduledAt, …) are repeated in SET_DASHBOARD_SCHEDULE and DASHBOARD. A small fragment keeps the query documents shorter and guarantees both places stay in sync.

+export const SCHEDULE_FIELDS = gql`
+  fragment ScheduleFields on Dashboard {
+    cacheEnabled
+    scheduleFrequency
+    scheduleTimezone
+    scheduleCron
+    nextScheduledAt
+  }
+`;
+
 export const SET_DASHBOARD_SCHEDULE = gql`
   mutation SetDashboardSchedule($data: SetDashboardScheduleInput!) {
     setDashboardSchedule(data: $data) {
       id
       projectId
       name
-      cacheEnabled
-      scheduleFrequency
-      scheduleTimezone
-      scheduleCron
-      nextScheduledAt
+      ...ScheduleFields
     }
   }
   ${SCHEDULE_FIELDS}
 `;
wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (2)

41-47: onMoreClick doesn’t perform any awaits – drop the async keyword
The function body never awaits, therefore the returned Promise is unnecessary and may mislead readers into assuming asynchronous work happens here.

-  const onMoreClick = async (action: MORE_ACTION) => {
+  const onMoreClick = (action: MORE_ACTION) => {

55-81: Remove the unnecessary fragment – static-analysis warning
Biome correctly reports that the fragment adds no value because it only wraps a single <Tooltip> / scheduleTime element. Removing it saves a few bytes and silences the linter.

-              <>
                 {nextScheduleTime ? (
                   <Tooltip
@@
                   scheduleTime
-              </>
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-80: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

wren-ui/src/pages/home/dashboard.tsx (1)

61-67: Surface mutation errors to the user
setDashboardSchedule only logs errors to the console. A toast similar to the success case (“Failed to update dashboard schedule”) would give immediate feedback in production.

-    onError: (error) => console.error(error),
+    onError: () => {
+      message.error('Failed to update dashboard schedule.');
+    },
wren-ui/src/apollo/client/graphql/__types__.ts (1)

378-387: Consider making schedule field optional in DetailedDashboard.

The schedule field in DetailedDashboard is not marked as optional, but there might be cases where a dashboard doesn't have a schedule configured yet (especially if cacheEnabled is false).

export type DetailedDashboard = {
  __typename?: 'DetailedDashboard';
  cacheEnabled: Scalars['Boolean'];
  description?: Maybe<Scalars['String']>;
  id: Scalars['Int'];
  items: Array<DashboardItem>;
  name: Scalars['String'];
  nextScheduledAt?: Maybe<Scalars['String']>;
-  schedule: DashboardSchedule;
+  schedule?: Maybe<DashboardSchedule>;
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34ba7b8 and 0e238e8.

📒 Files selected for processing (10)
  • wren-ui/package.json (3 hunks)
  • wren-ui/src/apollo/client/graphql/__types__.ts (12 hunks)
  • wren-ui/src/apollo/client/graphql/dashboard.generated.ts (3 hunks)
  • wren-ui/src/apollo/client/graphql/dashboard.ts (1 hunks)
  • wren-ui/src/components/diagram/CustomDropdown.tsx (3 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (1 hunks)
  • wren-ui/src/components/pages/home/dashboardGrid/index.tsx (4 hunks)
  • wren-ui/src/pages/home/dashboard.tsx (2 hunks)
  • wren-ui/src/utils/errorHandler.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • wren-ui/src/utils/errorHandler.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • wren-ui/package.json
  • wren-ui/src/components/diagram/CustomDropdown.tsx
  • wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
wren-ui/src/apollo/client/graphql/dashboard.generated.ts (1)
wren-ui/src/apollo/client/graphql/__types__.ts (2)
  • Exact (4-4)
  • SetDashboardScheduleInput (1329-1332)
wren-ui/src/apollo/client/graphql/__types__.ts (2)
wren-ui/src/pages/home/dashboard.tsx (1)
  • Dashboard (33-143)
wren-ui/src/apollo/server/repositories/dashboardRepository.ts (1)
  • Dashboard (5-14)
wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx (4)
wren-ui/src/components/pages/home/dashboardGrid/CacheSettingsDrawer.tsx (2)
  • Schedule (28-35)
  • getScheduleText (113-144)
wren-ui/src/utils/time.ts (1)
  • getCompactTime (21-23)
wren-ui/src/components/diagram/CustomDropdown.tsx (1)
  • DashboardDropdown (137-162)
wren-ui/src/utils/icons.ts (1)
  • MoreIcon (91-91)
🪛 Biome (1.9.4)
wren-ui/src/components/pages/home/dashboardGrid/DashboardHeader.tsx

[error] 56-80: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (11)
wren-ui/src/apollo/client/graphql/__types__.ts (7)

122-130: Well structured day enumeration for scheduling.

This enum provides a clean way to represent days of the week for scheduling. The three-letter abbreviations follow standard convention and make it easy to understand in both code and UI.


241-251: Good extension of Dashboard type with caching and scheduling fields.

The Dashboard type has been properly extended with all necessary fields for caching functionality:

  • cacheEnabled flag controls whether caching is active
  • Schedule-related fields (scheduleFrequency, scheduleTimezone, scheduleCron) store the caching schedule configuration
  • nextScheduledAt helps track when the next cache refresh will occur

These fields align well with the server-side Dashboard entity as shown in the relevant code snippets.


293-301: Clean DashboardSchedule type implementation.

This type effectively encapsulates all the schedule configuration details with appropriate optionality markers. The frequency is correctly required while other fields may be optional depending on the selected frequency type.


1073-1080: Good design for cache metadata in PreviewItemResponse.

This response type now includes useful cache metadata fields:

  • cacheHit indicates whether data came from cache
  • cacheCreatedAt shows when the cache was created
  • cacheOverrodeAt tracks manual cache refresh timestamps
  • override indicates if this was a forced cache override

These fields will be valuable for showing cache status to users and making caching behavior transparent.


1085-1085: Effective refresh parameter addition.

Adding an optional refresh parameter to PreviewItemSqlInput is a clean way to allow clients to request a cache refresh when needed. This maintains backward compatibility while adding new functionality.


1299-1304: Comprehensive schedule frequency enumeration.

The ScheduleFrequencyEnum provides a good set of frequency options:

  • NEVER for disabling scheduled refresh
  • DAILY and WEEKLY for common use cases
  • CUSTOM for advanced scheduling needs

This covers the common scheduling patterns users would need for dashboard caching.


1320-1333: Well-structured input types for dashboard scheduling.

The input types for setting dashboard schedules are well-designed:

  • SetDashboardScheduleData contains all the schedule configuration fields
  • SetDashboardScheduleInput wraps the schedule data with the cacheEnabled flag

Making schedule optional in SetDashboardScheduleInput allows turning off caching without requiring schedule data, which is a good design choice.

wren-ui/src/apollo/client/graphql/dashboard.generated.ts (4)

47-47: Excellent typed response for PreviewItemSQL mutation.

The mutation return type now uses the structured PreviewItemResponse type instead of relying on generic JSON responses. This provides better type safety and makes cache-related information available to the frontend.


246-252: Well-defined GraphQL query fields for cache metadata.

The GraphQL query now explicitly requests all relevant cache metadata fields, ensuring the client receives complete information about cache status for each dashboard item.


49-54: Complete implementation of SetDashboardSchedule mutation.

The mutation is properly defined with appropriate variable types and return fields. The GraphQL document requests all the necessary fields from the server response, and the generated hooks follow Apollo best practices with thorough JSDoc comments.

Also applies to: 281-320


56-59: Comprehensive Dashboard query implementation.

The Dashboard query is well implemented to fetch all necessary dashboard data including:

  • Basic dashboard information (id, name, description)
  • Cache settings (cacheEnabled, nextScheduledAt)
  • Complete schedule configuration
  • All dashboard items using the existing fragment

The generated hooks are properly typed and documented, making them easy to use in React components.

This aligns well with the usage in the dashboard component shown in the relevant code snippets, where the query results are used to populate UI components for displaying and managing cache settings.

Also applies to: 322-369

Comment on lines +48 to +49
updateQuery: updateDashboardQuery,
} = useDashboardQuery({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid the deprecated updateQuery, switch to the modern cache.modify API
updateQuery has been soft-deprecated since Apollo Client 3. Using cache.modify gives finer-grained control, avoids re-executing the query in some edge cases and keeps the code aligned with the current docs.

🤖 Prompt for AI Agents (early access)
In wren-ui/src/pages/home/dashboard.tsx around lines 48 to 49, replace the deprecated `updateQuery` usage with Apollo Client's modern `cache.modify` API. Remove the `updateQuery` option from the `useDashboardQuery` hook and implement cache updates using `cache.modify` to directly modify the cached fields. This approach provides finer control over cache updates and aligns with current Apollo Client best practices.

@onlyjackfrost onlyjackfrost merged commit f6710a6 into main Apr 30, 2025
@onlyjackfrost onlyjackfrost deleted the feat/query-cache branch April 30, 2025 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants