Skip to content

Waitpoint tokens page, wait.listTokens() and wait.retrieveToken() #1824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 53 commits into from
Mar 26, 2025

Conversation

matt-aitken
Copy link
Member

@matt-aitken matt-aitken commented Mar 26, 2025

Dashboard Waitpoint Tokens page

Added a Waitpoints/Tokens page where you can see all of your waitpoint tokens. You can filter them and select one to view the details.

Hello World Tokens

Completing waitpoints from the dashboard

You can complete a waitpoint from the dashboard. This is really useful when testing, or even in production where you have some workflows where you haven't built a UI yet and want to do approval flows.

CleanShot 2025-03-26 at 16 15 58

You can also "timeout" a token from the dashboard. When you create a token you can provide a timeout period or date. This is useful if there is a deadline for an approval, and if that date passes it should fail (which would continue runs that are blocked so you can act accordingly using the error).

CleanShot 2025-03-26 at 16 16 14

wait.listTokens() and wait.retrieveToken()

You can get waitpoint tokens using the SDK.

// Get all tokens that match the filters
const tokens = await wait.listTokens({ tags, status: ["COMPLETED"] });
// This will loop through every single match, without needing to think about pagination
for await (const token of tokens) {
  logger.log("Token2", token);
}
// Get an individual token including the output (or error)
const token = await wait.retrieveToken(token.id);

Misc fixes/improvements

  • Removed some old v2 files.
  • Improved the tags getting squashed.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Launched a dedicated waitpoint tokens page with enhanced filtering, navigation, and responsive tables.
    • Introduced new interactive UI elements including updated icons, copyable text, and enriched run tags.
    • Added support for tagging waitpoints to improve organization.
    • Added new components for displaying waitpoint details and statuses, enhancing user feedback.
    • Introduced methods for listing and retrieving waitpoint tokens through the API.
    • Enhanced the Avatar component with a size-based prop system for improved rendering.
    • Introduced new functions for constructing URLs related to waitpoint tokens.
    • Added new utility functions for managing waitpoint tokens and their states.
    • Implemented a new PacketDisplay component for displaying various data types.
    • Introduced a WaitpointDetailTable component for detailed waitpoint information.
    • Enhanced the RunTag component with copy functionality and improved tooltip interactions.
    • Added new API endpoints for managing waitpoint tokens and their statuses.
  • Refactor
    • Improved animations, loading states, and overall performance for a smoother user experience.
    • Streamlined the structure of the Avatar component by consolidating size management.
    • Updated the RunFilters component to simplify icon rendering.
  • Bug Fixes
    • Enhanced error handling to prevent duplicate tag conflicts and ensure more reliable token operations.

# Conflicts:
#	apps/webapp/app/components/navigation/SideMenu.tsx
#	apps/webapp/app/presenters/v3/SpanPresenter.server.ts
Copy link

changeset-bot bot commented Mar 26, 2025

⚠️ No Changeset found

Latest commit: 0e4a759

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Mar 26, 2025

Walkthrough

This pull request introduces several new features and updates related to waitpoint tokens, status, and tagging. The changes span various layers, including UI components, backend presenters, API routes, database migrations, and core SDK methods. New components and hooks have been added for improved waitpoint management and token filtering, while existing functionality has been refactored for better type safety and control flow. Schema and migration modifications support new relationships and tagging functionality across the system.

Changes

File(s) Change Summary
.gitignore Added entries to ignore specific package.json files in core, trigger-sdk, and python packages.
apps/webapp/app/assets/icons/*.tsx Introduced three new icon components: StatusIcon, TriggerIcon, and WaitpointTokenIcon.
apps/webapp/app/components/* UI components.
- BlankStatePanels.tsx: Added NoWaitpointTokens.
- CodeBlock.tsx: Added isLoaded state and useEffect for language loading.
- SideMenu.tsx: Added a waitpoints section with tokens.
- AnimatedNumber.tsx: Switched from spring to motion value animation.
- CopyableText.tsx: Introduced new copy-to-clipboard component.
- Runs/v3: New/updated components (PacketDisplay, RunFilters, RunIcon, RunTag, TaskRunsTable, WaitpointDetails, WaitpointStatus, WaitpointTokenFilters).
apps/webapp/app/hooks/useNewCustomerSubscribed.ts Removed the confetti subscription hook.
apps/webapp/app/models/*.server.ts Database models.
- taskRunTag.server.ts: Added retry logic for unique constraint violations.
- waitpointTag.server.ts: Introduced new function for creating waitpoint tags.
apps/webapp/app/presenters/v3/*.server.ts Presenters.
- New presenters: ApiWaitpointPresenter, WaitpointPresenter, WaitpointTagListPresenter, WaitpointTokenListPresenter, and ApiWaitpointTokenListPresenter.
- Updated presenters (ApiRunList, QueueList, RunList, SpanPresenter) to improve type usage and parameter handling.
apps/webapp/app/routes/**/* Routes.
- Updated run loader to use runIds array.
- Added new routes for waitpoint tokens and tags.
- Adjusted API routes for waitpoint tokens, including error handling and releaseConcurrency support.
- Updated waitpoint form routes to use id instead of friendlyId.
apps/webapp/app/utils/* Added new URL builders (v3WaitpointTokensPath, v3WaitpointTokenPath) and a CoercedDate utility in zod.
internal-packages/database/prisma/migrations/* Migrations.
- Added indexes to the Waitpoint table.
- Created a new WaitpointTag table and altered tag columns (renaming waitpointTags to tags).
- Introduced a connection table for waitpoints and task runs.
internal-packages/database/prisma/schema.prisma Updated schema with a new WaitpointTag model and new relations for waitpoint tags and connected runs.
internal-packages/run-engine/src/engine/* Updated createManualWaitpoint methods in both RunEngine and WaitpointSystem to accept a tags parameter and include retry logic.
packages/core/src/v3/apiClient/* Added methods for waitpoint tokens: listWaitpointTokens, retrieveWaitpointToken, and updated waitForWaitpointToken to accept grouped parameters; also added a query builder function.
packages/core/src/v3/schemas/api.ts
packages/core/src/v3/apiClient/types.ts
Introduced new types and schemas for waitpoint token management and tagging functionality.
packages/trigger-sdk/src/v3/wait.ts Added new functions: listTokens, retrieveToken; updated completeToken and forToken to support tags and a releaseConcurrency option.
references/hello-world/src/trigger/waits.ts Updated waitToken task to accept tags and to call wait.forToken with releaseConcurrency enabled.
apps/webapp/app/components/primitives/Avatar.tsx Updated Avatar component to replace className prop with size prop for consistent sizing.
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx Modified LogoForm to pass size prop instead of className to Avatar components.
apps/webapp/app/routes/storybook.avatar/route.tsx Updated Avatar component usage to replace className with size prop for defining avatar sizes.

Suggested reviewers

  • ericallam

Poem

I'm a rabbit with a hop so light,
Coding through changes day and night.
New tokens and tags, oh what a thrill,
With retry loops and icons that chill.
I nibble on code and dance with delight,
Bugs may hide, but I keep them in sight.
Hoppy coding, my joy takes flight!


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between aeb4a58 and 0e4a759.

📒 Files selected for processing (1)
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / 🧪 Unit Tests
  • GitHub Check: Analyze (javascript-typescript)

🪧 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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (32)
apps/webapp/app/components/runs/v3/TaskRunsTable.tsx (1)

387-387: Consider documenting the reason for the large right padding.

The pr-16 class adds a significant 4rem (64px) of right padding to the tags cell. While this is likely intentional for UI design reasons, it would be helpful to add a comment explaining the reasoning behind such a large padding value to prevent future developers from modifying it without understanding its purpose.

apps/webapp/app/models/waitpointTag.server.ts (3)

7-50: Add exponential backoff between retry attempts.

The retry logic correctly handles database conflicts, but currently has no delay between attempts. For better resilience and to avoid hammering the database, consider implementing an exponential backoff strategy.

-  let attempts = 0;
+  let attempts = 0;
+  let backoffMs = 50; // Starting with 50ms delay

   while (attempts < MAX_RETRIES) {
     try {
       return await prisma.waitpointTag.upsert({
         // ... existing code
       });
     } catch (error) {
       if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
         // Handle unique constraint violation (conflict)
         attempts++;
+        if (attempts < MAX_RETRIES) {
+          // Exponential backoff with jitter
+          const jitter = Math.random() * 0.1 * backoffMs;
+          await new Promise(resolve => setTimeout(resolve, backoffMs + jitter));
+          backoffMs *= 2; // Exponential increase for next attempt
+        }
         if (attempts >= MAX_RETRIES) {

16-16: Improve early return documentation.

The early return for empty tags is a valid optimization, but it's not immediately obvious why this behavior exists. Consider adding a comment to explain the rationale.

-  if (tag.trim().length === 0) return;
+  // Skip empty tags as they're not meaningful
+  if (tag.trim().length === 0) return;

41-44: Include the tag name in the error message.

When throwing an error after maximum retries, including the tag name would make debugging easier.

-          throw new Error(
-            `Failed to create waitpoint tag after ${MAX_RETRIES} attempts due to conflicts.`
-          );
+          throw new Error(
+            `Failed to create waitpoint tag "${tag}" after ${MAX_RETRIES} attempts due to conflicts.`
+          );
apps/webapp/app/routes/api.v1.waitpoints.tokens.$waitpointFriendlyId.ts (1)

13-13: Clarify the purpose of the dummy findResource function

The findResource function is currently implemented as a dummy that always returns 1, with a comment indicating it's not needed. Consider either:

  1. Removing it if it's genuinely not required
  2. Implementing it properly if resource finding is needed
  3. Adding a more detailed comment explaining why this workaround is necessary

This would make the code easier to understand and maintain.

apps/webapp/app/presenters/v3/ApiWaitpointPresenter.server.ts (1)

57-80: Consider refactoring the timeout detection logic

The timeout detection logic is overly simplified and may not accurately detect all timeout cases.

-  let isTimeout = false;
-  if (waitpoint.outputIsError && waitpoint.output) {
-    isTimeout = true;
-  }
+  // Determine if this is a timeout based on output error
+  const isTimeout = Boolean(waitpoint.outputIsError && waitpoint.output);

Also, the isTimeout variable is calculated but never used. Either remove it or use it in the return object if it's intended for future use.

references/hello-world/src/trigger/waits.ts (1)

67-76: Consider optimizing duplicate token retrieval.

The same token (token.id) is retrieved twice at lines 56 and 74 without any changes in between. While this might be for demonstration purposes, in production code you could optimize by reusing the first retrieval result.

- const retrievedToken2 = await wait.retrieveToken(token.id);
- logger.log("Retrieved token2", retrievedToken2);
+ logger.log("Retrieved token2", retrievedToken);
apps/webapp/app/components/primitives/AnimatedNumber.tsx (1)

1-1: Remove unused import.

The useSpring import is no longer used in the component after your changes but is still being imported.

- import { motion, useSpring, useTransform, useMotionValue, animate } from "framer-motion";
+ import { motion, useTransform, useMotionValue, animate } from "framer-motion";
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tags.ts (1)

28-36: Add error handling for URI decoding.

Consider adding error handling around the decodeURIComponent call to handle potential errors if the name parameter contains invalid URI-encoded characters.

  const search = new URL(request.url).searchParams;
  const name = search.get("name");

+ let decodedName;
+ if (name) {
+   try {
+     decodedName = decodeURIComponent(name);
+   } catch (error) {
+     throw new Response("Invalid search parameter", { status: 400 });
+   }
+ }
+
  const presenter = new WaitpointTagListPresenter();
  const result = await presenter.call({
    environmentId: environment.id,
-   names: name ? [decodeURIComponent(name)] : undefined,
+   names: name ? [decodedName] : undefined,
  });
apps/webapp/app/models/taskRunTag.server.ts (1)

15-16: Maintain consistent friendlyId usage across attempts.

Reusing the same friendlyId during retries ensures the tag retains a stable user-friendly identifier, but it raises the possibility of consecutive conflicts if the record truly collides. Reassessing the need for a stable ID versus using a new ID per retry may be worthwhile if collisions are frequent.

apps/webapp/app/components/primitives/CopyableText.tsx (1)

10-21: Handle potential clipboard write errors.

The navigator.clipboard.writeText(value) call returns a Promise. Consider leveraging .then/.catch or using await in a try-catch block to gracefully handle errors (e.g., denied permissions). This prevents silent failures and provides a better user experience.

  const copy = useCallback(
    async (e: React.MouseEvent) => {
      e.preventDefault();
      e.stopPropagation();
-     navigator.clipboard.writeText(value);
+     try {
+       await navigator.clipboard.writeText(value);
+       setCopied(true);
+       setTimeout(() => {
+         setCopied(false);
+       }, 1500);
+     } catch (err) {
+       console.error("Failed to copy text:", err);
+     }
    },
    [value]
  );
apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts (1)

55-61: Ensure large outputs are handled efficiently.

Using prettyPrintPacket to render the output is fine for small to moderate payloads. For extremely large outputs, consider implementing pagination, streaming, or storing partial results to avoid potential performance bottlenecks.

apps/webapp/app/components/runs/v3/WaitpointDetails.tsx (1)

121-127: Simplify complex conditional rendering

The nested ternary operators make this section difficult to read and maintain. Consider extracting this logic to a separate function for clarity.

-      {waitpoint.status === "WAITING" ? null : waitpoint.status === "TIMED_OUT" ? (
-        <></>
-      ) : waitpoint.output ? (
-        <PacketDisplay title="Output" data={waitpoint.output} dataType={waitpoint.outputType} />
-      ) : waitpoint.completedAfter ? null : (
-        "Completed with no output"
-      )}
+      {renderOutputSection(waitpoint)}

// Add this function above the return statement:
+ function renderOutputSection(waitpoint: WaitpointDetail) {
+   if (waitpoint.status === "WAITING") {
+     return null;
+   }
+   
+   if (waitpoint.status === "TIMED_OUT") {
+     return null;
+   }
+   
+   if (waitpoint.output) {
+     return <PacketDisplay title="Output" data={waitpoint.output} dataType={waitpoint.outputType} />;
+   }
+   
+   if (waitpoint.completedAfter) {
+     return null;
+   }
+   
+   return "Completed with no output";
+ }
apps/webapp/app/presenters/v3/ApiWaitpointTokenListPresenter.server.ts (1)

13-62: Validate potential usability issues in filter parameters.

The filter[status] field expects multiple comma-separated statuses, which is valid. However, note that using additional filtering fields (e.g., filter[idempotencyKey], filter[tags]) might inadvertently complicate query performance if the underlying datastore queries become too fragmented. Ensure that the underlying queries remain efficient.

Consider exposing a more conventional search parameter format (e.g., repeated query parameters) or limit the complexity of multi-field filters to maintain clarity and performance.

internal-packages/run-engine/src/engine/systems/waitpointSystem.ts (1)

255-255: Validate tag usage constraints at the call site.

Although the consumer likely validates the length and contents of the tags array before calling createManualWaitpoint, adding optional server-side validation could provide more robust safeguards against unexpected usage. This helps ensure data integrity when multiple consumers call this method.

apps/webapp/app/routes/api.v1.waitpoints.tokens.ts (1)

81-95: Return a descriptive error response.

The catch block handles validation errors distinctly from general errors and returns appropriate HTTP status codes, ensuring the client can differentiate the error cause. This clarity is beneficial for client-side debugging and error handling.

You might add optional structured error details to the JSON body to help the client localize exactly which request part is invalid.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens.$waitpointParam/route.tsx (2)

26-69: Validate user access to WaitpointPresenter outputs.

The loader function correctly verifies user and environment existence before invoking WaitpointPresenter. However, if future enhancements of WaitpointPresenter perform sensitive operations, ensure additional checks (e.g., verifying the user’s role or permissions) are introduced to guard any extension logic or details that might leak.


71-133: Consider adding unit tests for waitpoint status transitions.

The page conditionally renders the CompleteWaitpointForm if waitpoint.status is "WAITING". It'd be valuable to verify that correct UI elements render (or do not render) across different statuses. This ensures robust coverage of the waitpoint lifecycle.

Would you like me to propose a basic Jest/RTL test suite for this?

apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (3)

321-411: Optimize tag lookups for large data sets.

TagsDropdown uses matchSorter in the UI and calls the tags endpoint on every keystroke. If the list of tags grows significantly, consider debouncing requests or implementing a more advanced caching mechanism.


443-529: Add user feedback if partial ID is entered.

The WaitpointIdDropdown enforces an exact 35-character ID with waitpoint_ prefix, and displays an error otherwise. Consider offering user suggestions (e.g., partial matches) or a more explicit message about the supported format for a better user experience.


561-645: Clarify error message for idempotency key length.

Currently, if idempotencyKey.length === 0, the error states it must be at least 1 character. For clarity, you may want to handle a case where the user might type only whitespace or apply more robust trimming.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx (3)

53-103: Streamline search parameters.

When parsing searchParams in the loader, consider defaulting empty arrays or strings to undefined in one place (e.g., a utility function) to reduce repetitive checks and potential confusion about empty vs. undefined filters.


105-247: Encourage user guidance when no tokens exist.

In the page UI, the blank state references NoWaitpointTokens. If a user may be unaware of how to create or manage waitpoints, consider adding a tips or help link to direct them toward the relevant docs or next steps.


105-247: Consider concurrency or TTL usage.

When tokens have idempotencyKeyExpiresAt, the UI displays a tooltip for expiration. Ensure that relevant code or background tasks handle token cleanup or re-validation once tokens expire to avoid stale data.

apps/webapp/app/components/runs/v3/RunTag.tsx (1)

65-79: Consider handling component unmount with useEffect cleanup

The setTimeout to reset the copied state could potentially cause issues if the component unmounts before the timeout completes.

-  const copy = useCallback(
-    (e: React.MouseEvent) => {
-      e.preventDefault();
-      e.stopPropagation();
-      navigator.clipboard.writeText(textToCopy);
-      setCopied(true);
-      setTimeout(() => {
-        setCopied(false);
-      }, 1500);
-    },
-    [textToCopy]
-  );
+  const copy = useCallback(
+    (e: React.MouseEvent) => {
+      e.preventDefault();
+      e.stopPropagation();
+      navigator.clipboard.writeText(textToCopy);
+      setCopied(true);
+    },
+    [textToCopy]
+  );
+
+  useEffect(() => {
+    if (copied) {
+      const timeout = setTimeout(() => {
+        setCopied(false);
+      }, 1500);
+      return () => clearTimeout(timeout);
+    }
+  }, [copied]);
apps/webapp/app/components/runs/v3/WaitpointStatus.tsx (2)

31-49: Consider using a mapping object for icon components

The switch statement works well, but as you add more statuses, a mapping object might be more maintainable.

- export function WaitpointStatusIcon({
-   status,
-   className,
- }: {
-   status: WaitpointTokenStatus;
-   className: string;
- }) {
-   switch (status) {
-     case "WAITING":
-       return <Spinner className={cn(waitpointStatusClassNameColor(status), className)} />;
-     case "TIMED_OUT":
-       return <TimedOutIcon className={cn(waitpointStatusClassNameColor(status), className)} />;
-     case "COMPLETED":
-       return <CheckCircleIcon className={cn(waitpointStatusClassNameColor(status), className)} />;
-     default: {
-       assertNever(status);
-     }
-   }
- }
+ export function WaitpointStatusIcon({
+   status,
+   className,
+ }: {
+   status: WaitpointTokenStatus;
+   className: string;
+ }) {
+   const statusIcons: Record<WaitpointTokenStatus, React.ReactNode> = {
+     WAITING: <Spinner className={cn(waitpointStatusClassNameColor(status), className)} />,
+     TIMED_OUT: <TimedOutIcon className={cn(waitpointStatusClassNameColor(status), className)} />,
+     COMPLETED: <CheckCircleIcon className={cn(waitpointStatusClassNameColor(status), className)} />,
+   };
+   
+   const icon = statusIcons[status];
+   if (!icon) {
+     assertNever(status);
+   }
+   
+   return icon;
+ }

51-64: Consider consolidating color and title mappings

Similar to the icon component, these utility functions could be more maintainable as mapping objects, especially if you expect to add more statuses in the future.

- export function waitpointStatusClassNameColor(status: WaitpointTokenStatus): string {
-   switch (status) {
-     case "WAITING":
-       return "text-blue-500";
-     case "TIMED_OUT":
-       return "text-error";
-     case "COMPLETED": {
-       return "text-success";
-     }
-     default: {
-       assertNever(status);
-     }
-   }
- }
+ const STATUS_COLORS: Record<WaitpointTokenStatus, string> = {
+   WAITING: "text-blue-500",
+   TIMED_OUT: "text-error",
+   COMPLETED: "text-success",
+ };
+ 
+ export function waitpointStatusClassNameColor(status: WaitpointTokenStatus): string {
+   const color = STATUS_COLORS[status];
+   if (!color) {
+     assertNever(status);
+   }
+   return color;
+ }
apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (1)

78-92: Consider adding unit tests or integration tests.

This method is core to listing tokens with filtering and pagination. Adding targeted tests will help safeguard against future regressions and ensure the raw SQL logic is correct.

apps/webapp/app/presenters/v3/SpanPresenter.server.ts (1)

459-467: Wrap switch-case declarations in a block to avoid scope leaks.

Biom’s lint rule suggests that declarations within a switch case may be accessible in other case clauses. Wrap your case body in braces to limit scope:

         switch (span.entity.type) {
           case "waitpoint":
-            if (!span.entity.id) {
-              logger.error(`SpanPresenter: No waitpoint id`, { ... });
-              return { ...data, entity: null };
-            }
-            const presenter = new WaitpointPresenter();
-            const waitpoint = await presenter.call({...});
-            ...
+            {
+              if (!span.entity.id) {
+                logger.error(`SpanPresenter: No waitpoint id`, { ... });
+                return { ...data, entity: null };
+              }
+              const presenter = new WaitpointPresenter();
+              const waitpoint = await presenter.call({...});
+              ...
+            }
             break;
           default:
             return { ...data, entity: null };
         }
🧰 Tools
🪛 Biome (1.9.4)

[error] 467-467: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/trigger-sdk/src/v3/wait.ts (1)

239-241: Handle potential undefined data for error construction.

If result.output is missing or malformed, data may be undefined, causing an error when accessing data.message. Safely check for data?.message:

-if (result.outputIsError) {
-  error = new WaitpointTimeoutError(data.message);
-} else {
+if (result.outputIsError) {
+  if (!data?.message) {
+    error = new WaitpointTimeoutError("Waitpoint timed out");
+  } else {
+    error = new WaitpointTimeoutError(data.message);
+  }
+} else {
   output = data as T;
 }
packages/core/src/v3/apiClient/index.ts (1)

728-749: Improved parameter structure with object destructuring

Changing the method signature to use an object parameter with destructuring improves readability and maintainability, especially as the number of parameters grows.

Consider adopting this pattern for other methods with multiple parameters to maintain consistency across the codebase.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx (1)

197-216: Inconsistency between id and friendlyId properties

While the FormWaitpoint type uses id, the CompleteDateTimeWaitpointForm still expects friendlyId. This creates a mapping in the code that could be confusing.

Consider updating CompleteDateTimeWaitpointForm to use id consistently:

function CompleteDateTimeWaitpointForm({
  waitpoint,
}: {
-  waitpoint: { friendlyId: string; completedAfter: Date };
+  waitpoint: { id: string; completedAfter: Date };
}) {
  // ...

  return (
    <Form
-      action={`/resources/orgs/${organization.slug}/projects/${project.slug}/env/${environment.slug}/waitpoints/${waitpoint.friendlyId}/complete`}
+      action={`/resources/orgs/${organization.slug}/projects/${project.slug}/env/${environment.slug}/waitpoints/${waitpoint.id}/complete`}
      method="post"
      // ...
    >
      // ...
    </Form>
  );
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c08c3b4 and 9ad0508.

⛔ Files ignored due to path filters (5)
  • apps/webapp/public/images/logo-banner.png is excluded by !**/*.png
  • apps/webapp/public/images/readme/workflow-demo.gif is excluded by !**/*.gif
  • apps/webapp/public/images/templates/github-stars-template-bg.png is excluded by !**/*.png
  • apps/webapp/public/images/templates/resend-slack-template-bg.png is excluded by !**/*.png
  • apps/webapp/public/images/templates/shopify-template-bg.png is excluded by !**/*.png
📒 Files selected for processing (55)
  • .gitignore (1 hunks)
  • apps/webapp/app/assets/icons/StatusIcon.tsx (1 hunks)
  • apps/webapp/app/assets/icons/TriggerIcon.tsx (1 hunks)
  • apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx (1 hunks)
  • apps/webapp/app/components/BlankStatePanels.tsx (2 hunks)
  • apps/webapp/app/components/code/CodeBlock.tsx (2 hunks)
  • apps/webapp/app/components/navigation/SideMenu.tsx (3 hunks)
  • apps/webapp/app/components/primitives/AnimatedNumber.tsx (1 hunks)
  • apps/webapp/app/components/primitives/CopyableText.tsx (1 hunks)
  • apps/webapp/app/components/runs/v3/PacketDisplay.tsx (1 hunks)
  • apps/webapp/app/components/runs/v3/RunFilters.tsx (2 hunks)
  • apps/webapp/app/components/runs/v3/RunIcon.tsx (2 hunks)
  • apps/webapp/app/components/runs/v3/RunTag.tsx (1 hunks)
  • apps/webapp/app/components/runs/v3/TaskRunsTable.tsx (1 hunks)
  • apps/webapp/app/components/runs/v3/WaitpointDetails.tsx (1 hunks)
  • apps/webapp/app/components/runs/v3/WaitpointStatus.tsx (1 hunks)
  • apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (1 hunks)
  • apps/webapp/app/hooks/useNewCustomerSubscribed.ts (0 hunks)
  • apps/webapp/app/models/taskRunTag.server.ts (1 hunks)
  • apps/webapp/app/models/waitpointTag.server.ts (1 hunks)
  • apps/webapp/app/presenters/v3/ApiRunListPresenter.server.ts (1 hunks)
  • apps/webapp/app/presenters/v3/ApiWaitpointPresenter.server.ts (1 hunks)
  • apps/webapp/app/presenters/v3/ApiWaitpointTokenListPresenter.server.ts (1 hunks)
  • apps/webapp/app/presenters/v3/DevPresenceStream.server.ts (0 hunks)
  • apps/webapp/app/presenters/v3/QueueListPresenter.server.ts (1 hunks)
  • apps/webapp/app/presenters/v3/RunListPresenter.server.ts (5 hunks)
  • apps/webapp/app/presenters/v3/SpanPresenter.server.ts (7 hunks)
  • apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts (1 hunks)
  • apps/webapp/app/presenters/v3/WaitpointTagListPresenter.server.ts (1 hunks)
  • apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (1 hunks)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx (1 hunks)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens.$waitpointParam/route.tsx (1 hunks)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx (1 hunks)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx (0 hunks)
  • apps/webapp/app/routes/api.v1.waitpoints.tokens.$waitpointFriendlyId.complete.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.waitpoints.tokens.$waitpointFriendlyId.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.waitpoints.tokens.ts (2 hunks)
  • apps/webapp/app/routes/engine.v1.runs.$runFriendlyId.waitpoints.tokens.$waitpointFriendlyId.wait.ts (3 hunks)
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx (3 hunks)
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx (6 hunks)
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tags.ts (1 hunks)
  • apps/webapp/app/utils/pathBuilder.ts (2 hunks)
  • apps/webapp/app/utils/zod.ts (1 hunks)
  • internal-packages/database/prisma/migrations/20250320105905_waitpoint_add_indexes_for_dashboard/migration.sql (1 hunks)
  • internal-packages/database/prisma/migrations/20250320152314_waitpoint_tags/migration.sql (1 hunks)
  • internal-packages/database/prisma/migrations/20250320152806_waitpoint_renamed_tags/migration.sql (1 hunks)
  • internal-packages/database/prisma/migrations/20250325124348_added_connected_runs_to_waitpoints/migration.sql (1 hunks)
  • internal-packages/database/prisma/schema.prisma (7 hunks)
  • internal-packages/run-engine/src/engine/index.ts (1 hunks)
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts (3 hunks)
  • packages/core/src/v3/apiClient/index.ts (6 hunks)
  • packages/core/src/v3/apiClient/types.ts (2 hunks)
  • packages/core/src/v3/schemas/api.ts (3 hunks)
  • packages/trigger-sdk/src/v3/wait.ts (5 hunks)
  • references/hello-world/src/trigger/waits.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • apps/webapp/app/presenters/v3/DevPresenceStream.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/hooks/useNewCustomerSubscribed.ts
🧰 Additional context used
🧬 Code Definitions (17)
apps/webapp/app/components/BlankStatePanels.tsx (1)
apps/webapp/app/components/primitives/InfoPanel.tsx (1)
  • InfoPanel (30-68)
apps/webapp/app/components/runs/v3/RunIcon.tsx (1)
apps/webapp/app/assets/icons/TriggerIcon.tsx (1)
  • TriggerIcon (3-5)
apps/webapp/app/components/runs/v3/TaskRunsTable.tsx (1)
apps/webapp/app/components/primitives/Table.tsx (1)
  • TableCell (193-270)
apps/webapp/app/components/runs/v3/WaitpointDetails.tsx (5)
apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts (1)
  • WaitpointDetail (7-7)
apps/webapp/app/components/runs/v3/WaitpointStatus.tsx (1)
  • WaitpointStatusCombo (8-23)
apps/webapp/app/utils/pathBuilder.ts (2)
  • v3WaitpointTokenPath (325-335)
  • v3WaitpointTokensPath (314-323)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx (1)
  • ForceTimeout (380-414)
apps/webapp/app/components/runs/v3/PacketDisplay.tsx (1)
  • PacketDisplay (6-51)
apps/webapp/app/routes/api.v1.waitpoints.tokens.$waitpointFriendlyId.ts (2)
apps/webapp/app/presenters/v3/ApiWaitpointPresenter.server.ts (1)
  • ApiWaitpointPresenter (8-81)
packages/core/src/v3/schemas/api.ts (2)
  • WaitpointRetrieveTokenResponse (973-979)
  • WaitpointRetrieveTokenResponse (980-980)
apps/webapp/app/components/navigation/SideMenu.tsx (4)
apps/webapp/app/components/navigation/SideMenuSection.tsx (1)
  • SideMenuSection (15-85)
apps/webapp/app/components/navigation/SideMenuItem.tsx (1)
  • SideMenuItem (7-55)
apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx (1)
  • WaitpointTokenIcon (1-12)
apps/webapp/app/utils/pathBuilder.ts (1)
  • v3WaitpointTokensPath (314-323)
packages/core/src/v3/apiClient/types.ts (1)
packages/core/src/v3/schemas/api.ts (2)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
apps/webapp/app/components/runs/v3/RunFilters.tsx (1)
apps/webapp/app/assets/icons/StatusIcon.tsx (1)
  • StatusIcon (3-9)
apps/webapp/app/routes/api.v1.waitpoints.tokens.ts (3)
apps/webapp/app/presenters/v3/ApiWaitpointTokenListPresenter.server.ts (2)
  • ApiWaitpointTokenListSearchParams (13-62)
  • ApiWaitpointTokenListPresenter (66-134)
apps/webapp/app/models/waitpointTag.server.ts (2)
  • MAX_TAGS_PER_WAITPOINT (4-4)
  • createWaitpointTag (7-50)
packages/core/src/v3/schemas/api.ts (2)
  • CreateWaitpointTokenResponseBody (945-948)
  • CreateWaitpointTokenResponseBody (949-949)
apps/webapp/app/components/runs/v3/WaitpointStatus.tsx (1)
packages/core/src/v3/schemas/api.ts (2)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (3)
packages/core/src/v3/schemas/api.ts (3)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
  • waitpointTokenStatuses (951-951)
apps/webapp/app/assets/icons/StatusIcon.tsx (1)
  • StatusIcon (3-9)
apps/webapp/app/components/runs/v3/WaitpointStatus.tsx (2)
  • waitpointStatusTitle (66-79)
  • WaitpointStatusCombo (8-23)
apps/webapp/app/presenters/v3/ApiWaitpointPresenter.server.ts (2)
packages/core/src/v3/schemas/api.ts (1)
  • RunEngineVersion (7-7)
apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (1)
  • waitpointStatusToApiStatus (280-290)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx (3)
apps/webapp/app/components/runs/v3/RunTag.tsx (1)
  • RunTag (10-63)
apps/webapp/app/utils/pathBuilder.ts (1)
  • v3RunsPath (225-234)
apps/webapp/app/components/runs/v3/WaitpointDetails.tsx (1)
  • WaitpointDetailTable (15-130)
apps/webapp/app/presenters/v3/ApiWaitpointTokenListPresenter.server.ts (3)
packages/core/src/v3/schemas/api.ts (3)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
  • RunEngineVersion (7-7)
apps/webapp/app/utils/zod.ts (1)
  • CoercedDate (3-22)
apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (2)
  • WaitpointTokenListOptions (17-38)
  • WaitpointTokenListPresenter (64-278)
apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (2)
packages/core/src/v3/schemas/api.ts (5)
  • RunEngineVersion (7-7)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
  • WaitpointTokenItem (955-965)
  • WaitpointTokenItem (966-966)
apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (1)
  • WaitpointSearchParams (63-63)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx (1)
packages/core/src/v3/schemas/api.ts (2)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
packages/core/src/v3/apiClient/index.ts (4)
packages/core/src/v3/apiClient/core.ts (6)
  • params (479-487)
  • params (528-538)
  • ZodFetchOptions (30-38)
  • CursorPagePromise (459-502)
  • zodfetchCursorPage (79-114)
  • zodfetch (70-77)
packages/core/src/v3/apiClient/types.ts (1)
  • ListWaitpointTokensQueryParams (46-53)
packages/core/src/v3/index.ts (1)
  • CursorPagePromise (4-4)
packages/core/src/v3/schemas/api.ts (4)
  • WaitpointTokenItem (955-965)
  • WaitpointTokenItem (966-966)
  • WaitpointRetrieveTokenResponse (973-979)
  • WaitpointRetrieveTokenResponse (980-980)
🪛 Biome (1.9.4)
apps/webapp/app/presenters/v3/SpanPresenter.server.ts

[error] 467-467: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: units / 🧪 Unit Tests
🔇 Additional comments (107)
apps/webapp/app/assets/icons/StatusIcon.tsx (1)

1-9: Clean and well-structured component implementation.

The StatusIcon component follows good practices with a clean implementation:

  • Uses the cn utility for class name composition
  • Accepts an optional className prop for flexibility
  • Implements a visually consistent circular status indicator

This will be useful for showing status visually in the new Waitpoints/Tokens page mentioned in the PR.

.gitignore (1)

63-65: Correctly ignoring source package.json files.

These additions properly ignore package.json files in the source directories of the packages, which is consistent with the existing patterns in the file (like line 60 and 62).

This will prevent these automatically generated files from being tracked in version control.

internal-packages/database/prisma/schema.prisma (7)

444-444: Good addition of waitpointTags relation to RuntimeEnvironment.

This addition allows environment-specific tagging of waitpoints, supporting the new filtering functionality mentioned in the PR.


510-510: Good addition of waitpointTags relation to Project.

This enables project-level organization of waitpoint tags, working in conjunction with the environment-level tags.


1797-1797: Enhanced TaskRun-Waitpoint relationship model.

The changes improve the relationship between TaskRuns and Waitpoints:

  • Named relation for the completing run
  • New connectedWaitpoints field to track all waitpoints that have blocked a run

This bidirectional relationship will make it easier to query related waitpoints from a task run.

Also applies to: 1802-1803


2115-2115: Improved Waitpoint-TaskRun relationship tracking.

The enhanced relationship will:

  • Better track which run completed a waitpoint
  • Record all runs connected to a waitpoint for historical reference

This supports the dashboard functionality to view detailed waitpoint activity.

Also applies to: 2128-2129


2148-2149: Good addition of tags field to Waitpoint model.

The tags field allows categorizing waitpoints, directly supporting the filtering functionality mentioned in the PR objectives.


2157-2159: Helpful indexing for performance optimization.

These indexes will improve query performance for:

  • Time period filtering (by creation date)
  • Status filtering

This is important for efficiently displaying and filtering waitpoints in the UI.


2206-2219: Well-designed WaitpointTag model.

The new WaitpointTag model:

  • Has proper relations to environments and projects
  • Includes a unique constraint to prevent duplicate tags in an environment
  • Provides a good structure for organizing and filtering waitpoints

This implementation fully supports the tag-based filtering mentioned in the PR requirements.

apps/webapp/app/assets/icons/TriggerIcon.tsx (1)

1-5: Simple and effective icon component implementation.

The TriggerIcon component:

  • Reuses the existing BoltIcon from Heroicons
  • Provides a consistent way to utilize this icon across the application
  • Maintains the same prop pattern as other icon components

This follows the project's pattern for icon components and helps maintain UI consistency.

apps/webapp/app/components/runs/v3/RunFilters.tsx (2)

64-64: Import of StatusIcon improves component consistency.

The addition of this import is part of a design pattern that centralizes icon components, making the codebase more maintainable.


152-152: Appropriate usage of StatusIcon component.

Replacing the previous icon implementation with the standardized StatusIcon component improves consistency across the UI and simplifies the code in this filter type definition.

apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx (1)

1-12: Well-structured icon component.

This new icon component follows best practices by:

  • Accepting an optional className for flexible styling
  • Using currentColor to inherit color from parent elements
  • Implementing proper SVG attributes and path definitions
  • Following the project's component naming conventions

This component will be valuable for the new Waitpoints/Tokens page mentioned in the PR objectives.

internal-packages/database/prisma/migrations/20250320152806_waitpoint_renamed_tags/migration.sql (1)

1-10:

❓ Verification inconclusive

Migration properly renames and changes the tags column structure.

The migration appropriately warns about data loss and changes the column from waitpointTags to tags with a TEXT[] type. This change aligns with the PR's objective to enhance tag management for waitpoint tokens.

A few considerations:

  • Ensure any existing code that referenced the old column name has been updated
  • Verify that data migration (if needed) is handled elsewhere in the codebase
  • Confirm that the new TEXT[] type is properly handled in all queries

🏁 Script executed:

#!/bin/bash
# Check for any remaining references to the old column name
rg -i "waitpointTags" --type ts --type tsx --type js --type jsx

Length of output: 91


Action Required: Reverify "waitpointTags" References

The migration correctly renames the waitpointTags column to tags (with the new TEXT[] type) and warns appropriately about data loss. Please ensure that:

  • All references to the old column name have been updated across the codebase.
  • Any required data migration is addressed outside this SQL migration.
  • The new TEXT[] type is correctly handled in all queries.

Note: The initial search command returned an error (unrecognized file type: tsx). To ensure a proper check for any lingering references to waitpointTags, please run the adjusted search command below:

#!/bin/bash
# Custom search for "waitpointTags" across TypeScript, TSX, JavaScript, and JSX files
rg -i "waitpointTags" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx"

After confirming that no references remain, you may proceed with confidence.

internal-packages/database/prisma/migrations/20250320105905_waitpoint_add_indexes_for_dashboard/migration.sql (1)

1-5: Performance optimization through appropriate indexing.

These indexes will improve query performance for the new Waitpoints/Tokens page by:

  1. Optimizing filtering by environment, type, and creation date (with DESC for newest-first sorting)
  2. Speeding up status-based filtering within environment and type contexts

These indexes align perfectly with the PR objectives for implementing the new tokens page with filtering capabilities.

apps/webapp/app/components/runs/v3/RunIcon.tsx (2)

16-16: Added new import for TriggerIcon

This import is correctly added to support the new case in the switch statement below.


69-70: Added new case for "trigger" icon

The implementation follows the established pattern of other icon cases, using the appropriate component and styling. The orange color helps distinguish this icon type visually.

apps/webapp/app/components/BlankStatePanels.tsx (2)

38-38: Added import for WaitpointTokenIcon

This import is properly added to support the new NoWaitpointTokens component.


416-438: Added NoWaitpointTokens component for empty state

The new component follows the same pattern as other blank state panels in this file, providing consistent user experience. The explanatory text clearly describes the purpose of waitpoint tokens.

apps/webapp/app/utils/zod.ts (1)

1-22: Added utility for Date coercion with robust type handling

This utility function properly handles different input types for date conversion, with appropriate type safety via Zod. The approach of handling different input types (null/undefined, numbers, strings) separately provides comprehensive conversion support.

The implementation correctly:

  1. Returns undefined for null/undefined values
  2. Directly converts numbers to Date objects
  3. Attempts to parse strings as numbers first, then falls back to direct string parsing
  4. Preserves the original input for other types
apps/webapp/app/components/navigation/SideMenu.tsx (3)

57-57: Added path import for waitpoint tokens

This import is correctly added to support the new navigation item for waitpoint tokens.


84-84: Added import for WaitpointTokenIcon

This import is properly added to support the new sidebar menu item for waitpoint tokens.


216-223: Added new Waitpoints section with Tokens menu item

The implementation creates a new collapsible section in the sidebar for Waitpoints, following the established pattern of other sections. The menu item uses the appropriate icon and styling, with the correct routing path.

The placement between the existing menu items and the "Manage" section is logical in the navigation hierarchy.

packages/core/src/v3/apiClient/types.ts (1)

46-53: Well-structured interface for filtering waitpoint tokens.

The new ListWaitpointTokensQueryParams interface is cleanly designed and follows the established pattern from the existing ListRunsQueryParams interface. It correctly extends CursorPageParams for pagination support.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx (1)

137-137: Good update for supporting multiple run IDs.

The change from passing a single runId to an array of runIds improves the flexibility of the filtering mechanism. This allows for potential future expansion to filter by multiple run IDs simultaneously.

apps/webapp/app/models/waitpointTag.server.ts (1)

4-5: LGTM: Good constants with clear naming.

The constants are well-named and provide clear limits for tags per waitpoint and retry attempts.

apps/webapp/app/routes/api.v1.waitpoints.tokens.$waitpointFriendlyId.complete.ts (1)

4-4: Good use of TypeScript's type-only import

Using the type keyword for importing CompleteWaitpointTokenResponseBody follows best practices in modern TypeScript. This improves type safety, enables better tree-shaking, and clearly indicates this is only used for type checking rather than at runtime.

apps/webapp/app/presenters/v3/QueueListPresenter.server.ts (2)

29-29: Good consistency improvement with version filter

Adding the version: "V2" filter to the count query makes it consistent with the filter used in the getQueuesWithPagination method. This ensures the total count matches the actual queues that would be returned.


36-56: Enhanced error handling for engine version compatibility

This is a good improvement to the error handling logic. The code now provides more accurate information when the engine version is V1 by checking for the existence of old V1 queues and adjusting the totalQueues value accordingly.

apps/webapp/app/routes/engine.v1.runs.$runFriendlyId.waitpoints.tokens.$waitpointFriendlyId.wait.ts (3)

2-2: Good use of TypeScript's type-only import

Using the type keyword for importing WaitForWaitpointTokenResponseBody follows best practices in modern TypeScript. This improves type safety and clearly indicates this is only used for type checking.


16-18: Proper schema validation for new releaseConcurrency parameter

The Zod schema is correctly updated to include the optional releaseConcurrency boolean parameter. This ensures proper validation of the request body.


45-45: Successfully implemented releaseConcurrency feature

The new releaseConcurrency parameter is now properly passed to the engine method, completing the feature implementation and giving users more control over concurrency when working with waitpoints.

apps/webapp/app/components/runs/v3/PacketDisplay.tsx (2)

6-14: Well-structured component with clear responsibilities

The PacketDisplay component has a clean interface with well-typed props, creating a reusable component that can handle different types of data display based on the dataType parameter.


15-50: Good conditional rendering pattern with exhaustive switch statement

The component handles three distinct rendering paths using a clean switch statement pattern with appropriate default case. The conditional logic is easy to follow and each case is properly enclosed in its own block scope.

internal-packages/run-engine/src/engine/index.ts (1)

805-828: Appropriate parameter addition for tag support

The addition of the optional tags parameter to createManualWaitpoint enables the filtering functionality described in the PR objectives. The parameter is properly typed as an optional string array and correctly passed down to the waitpoint system.

apps/webapp/app/presenters/v3/ApiWaitpointPresenter.server.ts (2)

8-56: Robust API presenter with appropriate error handling

The presenter has a well-structured call method that properly handles database queries and error cases. The ServiceValidationError is appropriately thrown when a waitpoint is not found.


62-78: Good transformation of database objects to API responses

The presenter properly transforms database fields to API response format, handling optional values appropriately with nullish coalescing operators. This creates a clean API boundary.

apps/webapp/app/components/code/CodeBlock.tsx (1)

425-449: Good enhancement to handle code highlighting loading state

The addition of the loading state for language definitions prevents rendering issues when the PrismJS components haven't fully loaded. This improves the user experience by showing a usable fallback while language definitions are being loaded.

However, consider refactoring the loading implementation to avoid redundant imports:

useEffect(() => {
  // This ensures the language definitions are loaded
-  Promise.all([
-    //@ts-ignore
-    import("prismjs/components/prism-json"),
-    //@ts-ignore
-    import("prismjs/components/prism-typescript"),
-  ]).then(() => setIsLoaded(true));
+  // Since these imports are already called at file level in setup(),
+  // we can just set isLoaded after a short delay to ensure they've completed
+  const timer = setTimeout(() => setIsLoaded(true), 50);
+  return () => clearTimeout(timer);
}, []);

This would prevent importing the same modules twice, as they're already imported in the setup() function at the top of the file.

references/hello-world/src/trigger/waits.ts (4)

15-15: LGTM: Added tags parameter to waitToken task.

The tags parameter is correctly added to both the function signature and type definition.

Also applies to: 22-22


30-30: LGTM: Consistently passing tags to createToken calls.

The tags parameter is properly passed to both token creation calls.

Also applies to: 38-38


49-58: Good implementation of token listing and retrieval.

The implementation correctly uses async iteration with for await to process all tokens, which is an efficient pattern for handling potentially large result sets. The logger.trace wrapper also helps with performance by only computing the log messages when tracing is enabled.


60-60: LGTM: Added releaseConcurrency option.

The releaseConcurrency flag will release running concurrency limits while waiting for the token, which can improve system throughput.

internal-packages/database/prisma/migrations/20250325124348_added_connected_runs_to_waitpoints/migration.sql (1)

1-15: LGTM: Well-designed migration for many-to-many relationship.

This migration properly establishes a many-to-many relationship between TaskRun and Waitpoint tables with:

  • A junction table with appropriate columns
  • A unique index to prevent duplicate connections
  • An index on column B for efficient lookups
  • Proper foreign key constraints with cascade behavior

This design follows database best practices for relationship modeling.

apps/webapp/app/components/primitives/AnimatedNumber.tsx (2)

5-12: Good simplification of animation approach.

Replacing the spring physics model with a direct animation using animate() is a good simplification. This approach with explicit duration and easing makes the animation behavior more predictable while maintaining a smooth user experience.


13-13: Proper dependency array.

The dependency array correctly includes only the value prop, ensuring the animation is triggered only when the value changes.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tags.ts (2)

8-12: Good use of Zod for parameter validation.

Using Zod to define and validate route parameters is a good practice that ensures type safety and proper error handling.


14-27: Well-structured authentication and resource loading.

The loader function correctly:

  1. Requires authentication
  2. Validates route parameters
  3. Loads the necessary resources (project and environment)
  4. Returns appropriate 404 responses when resources aren't found

This follows best practices for Remix loaders.

apps/webapp/app/models/taskRunTag.server.ts (1)

7-7: Good introduction of a retry limit.

Defining a clear MAX_RETRIES = 3 is helpful to prevent infinite loops or excessively long blocking. This addition improves resilience.

apps/webapp/app/components/primitives/CopyableText.tsx (1)

23-61: Excellent reusable component.

The tooltip-based hover behavior and icon change on successful copy provide a clear user interface. Overall, this is a well-structured and maintainable approach for copy-to-clipboard functionality.

apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts (3)

62-67: Timed-out error logic is well defined.

The check for isWaitpointOutputTimeout(output) correctly flags timeouts. This helps distinguish normal errors from those indicating a waitpoint has expired.


72-81: Good approach for linked run lookups.

Fetching connected runs only if they exist, limiting to 5, avoids unnecessary database overhead. This is an efficient partial retrieval strategy.


83-101: Comprehensive return structure.

The final return object offers a thorough and well-organized set of fields for consumers. This makes the waitpoint detail easy to consume by other parts of the application.

internal-packages/database/prisma/migrations/20250320152314_waitpoint_tags/migration.sql (4)

1-3: Well-structured ALTER TABLE for waitpoint tags

The migration correctly adds a waitpointTags column of TEXT[] type to the Waitpoint table, which will store an array of tag identifiers. This approach allows for efficient tag storage without requiring a separate join table.


4-13: Properly designed WaitpointTag table schema

The WaitpointTag table is well structured with appropriate columns:

  • Primary key with id
  • Required name field for tag content
  • Association columns (environmentId, projectId)
  • Automatic timestamp tracking with createdAt

This design supports the tag management requirements outlined in the PR objectives.


15-16: Good use of a composite unique index

The unique index on (environmentId, name) ensures that tag names are unique within each environment, preventing duplicate tags while still allowing the same tag name to exist in different environments.


18-22: Appropriate foreign key constraints with CASCADE behavior

The foreign key constraints correctly establish the relationship between WaitpointTag and both RuntimeEnvironment and Project tables. The CASCADE behavior on DELETE and UPDATE ensures referential integrity by automatically handling dependent records when parent records are modified or removed.

apps/webapp/app/presenters/v3/RunListPresenter.server.ts (4)

27-27: Good update to support multiple run IDs

Adding runIds?: string[] to the RunListOptions type correctly extends the filtering capabilities to support multiple run identifiers.


55-55: Consistent parameter update throughout the method signature

The parameter has been correctly updated from runId to runIds in the method signature and filter condition check, ensuring type consistency throughout the component.

Also applies to: 75-75


185-185: Improved rootOnly handling

The condition has been updated to check for runIds?.length when determining whether to override the rootOnly parameter, which is a logical enhancement for filtering when specific runs are requested.


264-264: Effective SQL query modification

The SQL query has been properly updated to use the IN operator with the runIds array, allowing efficient filtering of multiple run identifiers. The use of Prisma.join() ensures safe parameter binding.

apps/webapp/app/components/runs/v3/WaitpointDetails.tsx (4)

15-28: Well-structured component with proper type definitions

The WaitpointDetailTable component is well defined with appropriate props and types. The use of optional props with default values (linkToList = false) follows React best practices. The expiration check logic is clean and readable.


29-52: Good implementation of ID and status display

The status and ID sections are well implemented with appropriate conditional rendering. The use of TextLink for navigation when linkToList is true provides good UX while maintaining code flexibility.


53-72: Clear presentation of idempotency key information

The idempotency key section effectively handles various states, including displaying expiration information when available and handling user-provided keys appropriately.


73-114: Comprehensive timeout and tags implementation for manual waitpoints

The component correctly handles the special case for manual waitpoints, displaying timeout information and the force timeout action when appropriate. The tag rendering with links to filtered views is particularly useful for navigation.

apps/webapp/app/utils/pathBuilder.ts (3)

7-7: Appropriate import for WaitpointSearchParams

The import of WaitpointSearchParams type is correctly added to support the filter parameters in the new path builder functions.


314-323: Well-implemented path builder for waitpoint tokens list

The v3WaitpointTokensPath function follows the established pattern of other path builders in the codebase. It correctly handles optional filter parameters and maintains consistency with the existing URL structure.


325-335: Efficient implementation of individual token path builder

The v3WaitpointTokenPath function reuses the v3WaitpointTokensPath function to build the base URL, which promotes code reuse and ensures consistent URL structure. It correctly appends the token ID and optional query parameters.

apps/webapp/app/presenters/v3/ApiWaitpointTokenListPresenter.server.ts (1)

66-134: Structured and robust approach for listing tokens.

Creating a typed options object (WaitpointTokenListOptions) and mapping it from validated search parameters is good for maintainability and clarity. Integrating pagination and filter logic here is cleanly separated from the lower-level or database-intensive code, promoting a single point of truth for listing tokens.

internal-packages/run-engine/src/engine/systems/waitpointSystem.ts (2)

248-249: Include tags in "createManualWaitpoint" parameters.

Adding the tags parameter here aligns it with the rest of the codebase’s tagging feature. This ensures consistency and fosters future extension for advanced token or waitpoint metadata handling.


291-344:

❓ Verification inconclusive

Leverage retry logic for concurrent upsert conflicts.

The introduction of a retry mechanism (up to 5 attempts) on unique constraint violations is a practical way to handle concurrency issues. However, be mindful that repeated collisions can degrade performance under high contention.

Consider searching the codebase for other upsert patterns. Harmonizing concurrency strategies across all upsert flows may improve consistency. Below is a script to locate upsert calls:


🏁 Script executed:

#!/bin/bash
# Searching for "upsert(" usage across the repository to ensure consistent concurrency patterns
rg 'upsert\s*\(' -A 10

Length of output: 20205


Standardize Upsert Concurrency Handling

The retry mechanism in internal-packages/run-engine/src/engine/systems/waitpointSystem.ts (lines 291–344) effectively addresses unique constraint violations by retrying up to 5 times. This approach is practical for handling concurrency issues; however, note that repeated collisions under high load might impact performance.

Our repository search shows multiple upsert patterns (in tests, seed scripts, etc.) that do not incorporate such retry logic. While these cases might not face the same contention, it’s worth reviewing whether similar concurrency safeguards should be applied elsewhere for consistency.

  • File under review: internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
    • Implements a retry loop to handle Prisma's P2002 errors.
  • Other occurrences: Upsert flows in various modules (e.g., task queue, user creation, promotions) lack similar retry logic.

Please verify if additional upsert flows may benefit from a standardized retry strategy and adjust accordingly if similar high-contention scenarios are expected.

apps/webapp/app/routes/api.v1.waitpoints.tokens.ts (4)

21-31: Loader route for listing waitpoint tokens looks good.

The usage of ApiWaitpointTokenListPresenter to centralize logic is consistent with the rest of the architecture. Error handling isn’t explicitly provided here, but the container route or the presenter can handle any exceptions as needed.


41-47: Double-check request body size limit.

Enforcing maxContentLength: 1024 * 10 (10 KB) is a beneficial measure. However, consider validating overall input sizes more holistically if there's a possibility that tags or other request fields could exceed 10 KB, especially in future expansions.


48-70: Robust tag creation logic with concurrency handling.

Your approach ensures that each tag is created or retrieved via createWaitpointTag. The limit of MAX_TAGS_PER_WAITPOINT properly guards the system against excessive tagging. This is consistent with best practices for controlling user input scale and concurrency.


71-80: Pass tags to the engine.

Attaching bodyTags to the engine.createManualWaitpoint() call ensures the entire waitpoint, including tags, is managed in a single place. This is well-structured and consistent with the service-locating approach.

apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (2)

49-63: Great use of Zod for enforcing typed search parameters.

Defining the schema via WaitpointSearchParamsSchema ensures strong validation of filter inputs. This is beneficial for consistent data shapes, preventing invalid queries from reaching backend logic.


69-93:

❓ Verification inconclusive

Ensure “Clear all” filters action is robust.

Inside WaitpointTokenFilters, the “Clear all” button triggers a form submission without specifying an action. Confirm that this submission effectively resets all filters in all scenarios (e.g., preserving existing pagination queries, if needed).


🏁 Script executed:

#!/bin/bash
# Verify that the "Clear all" button triggers correct filter reset
rg -A 5 "Clear all"

Length of output: 2697


Attention: Verify “Clear all” Filters Behavior

Please confirm that the “Clear all” button in WaitpointTokenFilters.tsx reliably resets all filter-related query parameters. The current implementation uses a <Form> without an explicit action, following a similar pattern seen in RunFilters.tsx, ScheduleFilters.tsx, and BatchFilters.tsx. Ensure that on submission:

  • All filter parameters (e.g., statuses, period, tags, from, to, id, idempotencyKey) are cleared.
  • Any non-filter query parameters (such as pagination settings, if applicable) are preserved if required.

If the desired behavior is to maintain pagination or other state, additional handling or tests may be necessary.

apps/webapp/app/presenters/v3/WaitpointTagListPresenter.server.ts (4)

1-15: Well-structured type definitions and imports

The type definitions for TagListOptions, TagList, and TagListItem are clear and well-structured. I particularly like how you've derived the TagList and TagListItem types from the return type of the presenter's call method, which ensures type consistency throughout the codebase.


17-24: LGTM! Good class structure

The presenter extends BasePresenter and provides a well-structured method with appropriate parameters.


26-39: Good query implementation with filter and pagination

The query implementation is robust, with:

  • Proper use of case-insensitive filtering
  • Effective pagination using skip/take pattern
  • Fetching one extra item to determine if there are more pages

The OR condition handles the case when names filter is empty which is a good defensive programming practice.


41-51: Clean and efficient response structure

The response object is well-structured, providing all necessary metadata for pagination and filtering. The slicing of the tags array to the requested page size is a clean way to handle the "hasMore" pagination pattern.

apps/webapp/app/components/runs/v3/RunTag.tsx (6)

1-7: Clean import organization

The imports are well-organized, with React hooks first, followed by component imports. The addition of the Clipboard icons will support the new copy functionality.


10-12: Enhanced component API with new props

The addition of to and tooltip props enhances the component's flexibility, allowing it to act as a navigation element and to provide contextual information.


14-38: Good extraction of rendering logic

Extracting the render logic into a separate function improves code readability and maintainability. The function handles both string and object tag types appropriately.


40-55: Solid implementation of conditional Link wrapping

Good implementation of conditionally wrapping the tag content in a Link component when to prop is provided. The SimpleTooltip integration adds a nice UX touch.


57-62: Clean component composition

The main component structure is clean, with good use of composition by including the new CopyButton component.


81-105: Excellent UX for copy button

The implementation of the copy button with visual feedback (changing icon and tooltip) provides a great user experience. The conditional display based on hover state is a nice touch that keeps the UI clean.

apps/webapp/app/presenters/v3/ApiRunListPresenter.server.ts (2)

1-11: Good use of explicit type imports

The use of the type keyword for import statements clarifies that these are type imports rather than value imports, which improves code clarity and can help with tree-shaking during build.


13-13: Type import consistency

Consistent application of the type keyword for imports, maintaining the codebase style.

apps/webapp/app/components/runs/v3/WaitpointStatus.tsx (4)

1-7: Good import organization with type safety

The imports are well-organized and include assertNever for exhaustive type checking, which is a great practice for enum-based switch statements.


8-23: Well-composed status component

The WaitpointStatusCombo component nicely composes the icon and label components with clean prop drilling. The use of cn utility for class name composition is a good practice.


25-29: Simple and effective label component

The WaitpointStatusLabel component is concise and leverages utility functions for consistency.


66-79:

✅ Verification successful

Verify enum coverage for future-proofing

The current implementation handles three statuses. Verify this covers all possible WaitpointTokenStatus values, or ensure you have a process to update these functions when new statuses are added.

Run this script to verify all enum values are accounted for:


🏁 Script executed:

#!/bin/bash
# Check for WaitpointTokenStatus enum values
grep -A 10 "export const waitpointTokenStatuses" packages/core/src/v3/schemas/api.ts

Length of output: 580


Enum Coverage Verified
The implementation correctly handles all values defined in the WaitpointTokenStatus enum: "WAITING", "COMPLETED", and "TIMED_OUT". The default assertion with assertNever(status) further ensures that any future additions to the enum will be caught during development. No changes are required at this time.

apps/webapp/app/presenters/v3/SpanPresenter.server.ts (1)

45-45: No concerns.

Exposing projectId in the parent run selection looks consistent with the rest of the logic.

packages/trigger-sdk/src/v3/wait.ts (1)

123-142: Great addition of listTokens.

Perfectly lays out filterable and paginated iteration for waitpoint tokens. The usage examples are clear and well documented.

packages/core/src/v3/schemas/api.ts (4)

928-941: Strong enhancement with tags for waitpoints

The addition of the tags property to CreateWaitpointTokenRequestBody allows better organization and filtering of waitpoints. The documentation is thorough with clear constraints and usage examples.


951-954: Well-structured waitpoint token status definition

Using a const array with as const alongside zod's enum and type inference creates a robust type-safe enumeration for token statuses that will provide consistency throughout the codebase.


955-980: Comprehensive token schemas with clear hierarchy

The progressive schema building with WaitpointTokenItemWaitpointListTokenItemWaitpointRetrieveTokenResponse creates a well-structured type hierarchy that supports different token retrieval use cases.


1011-1018: Helpful documentation for releaseConcurrency parameter

The added documentation explains the purpose and default value of the releaseConcurrency parameter clearly, helping developers understand when and why to use this feature.

packages/core/src/v3/apiClient/index.ts (3)

676-697: Well-implemented token listing with pagination support

The listWaitpointTokens method follows the established patterns in the codebase for paginated API requests, properly utilizing zodfetchCursorPage and handling all query parameters.


699-709: Simple and consistent token retrieval implementation

The retrieveWaitpointToken method follows the API client's conventions for GET requests and uses the appropriate response schema.


1066-1110: Well-structured query builder for token listing

The createSearchQueryForListWaitpointTokens function handles all filter cases properly, following the same pattern as other query builders in the codebase. The implementation handles array vs. single value parameters correctly.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx (3)

76-78: Good component imports for waitpoint display

Importing specialized components for waitpoint display improves code organization and reusability.


623-629: Improved tag component usage with explicit properties

The updated code uses RunTag with clear and explicit properties instead of wrapping it in a SimpleTooltip, which simplifies the component hierarchy and improves readability.


953-959: Simplified waitpoint detail rendering with specialized component

Replacing custom property rendering with the dedicated WaitpointDetailTable component improves consistency and reduces code duplication. The status check update from "PENDING" to "WAITING" properly aligns with the schema definition.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx (3)

193-195: Updated FormWaitpoint type with better structure

Adding the status property with the WaitpointTokenStatus type enhances type safety and aligns with the schema changes.


287-317: Updated parameter type with id property

The CompleteManualWaitpointForm component has been properly updated to use id instead of friendlyId.


380-413: Consistent updates to ForceTimeout component

The ForceTimeout component has been properly updated to use id instead of friendlyId for waitpoint identification.

Comment on lines +8 to +23
export const loader = createLoaderApiRoute(
{
params: z.object({
waitpointFriendlyId: z.string(),
}),
findResource: async () => 1, // This is a dummy function, we don't need to find a resource
},
async ({ params, authentication }) => {
const presenter = new ApiWaitpointPresenter();
const result: WaitpointRetrieveTokenResponse = await presenter.call(
authentication.environment,
WaitpointId.toId(params.waitpointFriendlyId)
);
return json(result);
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling to the API loader

The implementation of this new API endpoint for retrieving waitpoint tokens looks good overall. However, it lacks error handling around the presenter call, which could lead to unhandled exceptions.

Consider adding a try/catch block to properly handle errors:

export const loader = createLoaderApiRoute(
  {
    params: z.object({
      waitpointFriendlyId: z.string(),
    }),
    findResource: async () => 1, // This is a dummy function, we don't need to find a resource
  },
  async ({ params, authentication }) => {
+   try {
      const presenter = new ApiWaitpointPresenter();
      const result: WaitpointRetrieveTokenResponse = await presenter.call(
        authentication.environment,
        WaitpointId.toId(params.waitpointFriendlyId)
      );
      return json(result);
+   } catch (error) {
+     if (error instanceof ServiceValidationError) {
+       return json({ error: error.message }, { status: 404 });
+     }
+     return json({ error: "Failed to retrieve waitpoint token" }, { status: 500 });
+   }
  }
);
📝 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
export const loader = createLoaderApiRoute(
{
params: z.object({
waitpointFriendlyId: z.string(),
}),
findResource: async () => 1, // This is a dummy function, we don't need to find a resource
},
async ({ params, authentication }) => {
const presenter = new ApiWaitpointPresenter();
const result: WaitpointRetrieveTokenResponse = await presenter.call(
authentication.environment,
WaitpointId.toId(params.waitpointFriendlyId)
);
return json(result);
}
);
export const loader = createLoaderApiRoute(
{
params: z.object({
waitpointFriendlyId: z.string(),
}),
findResource: async () => 1, // This is a dummy function, we don't need to find a resource
},
async ({ params, authentication }) => {
try {
const presenter = new ApiWaitpointPresenter();
const result: WaitpointRetrieveTokenResponse = await presenter.call(
authentication.environment,
WaitpointId.toId(params.waitpointFriendlyId)
);
return json(result);
} catch (error) {
if (error instanceof ServiceValidationError) {
return json({ error: error.message }, { status: 404 });
}
return json({ error: "Failed to retrieve waitpoint token" }, { status: 500 });
}
}
);

Comment on lines +18 to +45
while (attempts < MAX_RETRIES) {
try {
return await prisma.taskRunTag.upsert({
where: {
projectId_name: {
projectId,
name: tag,
},
},
create: {
friendlyId,
name: tag,
projectId,
},
update: {},
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
// Handle unique constraint violation (conflict)
attempts++;
if (attempts >= MAX_RETRIES) {
throw new Error(`Failed to create tag after ${MAX_RETRIES} attempts due to conflicts.`);
}
} else {
throw error; // Re-throw other errors
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding a small delay or exponential backoff on retry.

While the while loop handles P2002 errors effectively to mitigate unique constraint conflicts, the retries occur immediately. Introducing a brief delay or exponential backoff can help reduce contention in multi-tenant or high-load scenarios.

+ // Example: Introducing a small delay before each retry
+ const BASE_DELAY_MS = 100;
...
while (attempts < MAX_RETRIES) {
  try {
    ...
  } catch (error) {
    ...
    if (attempts < MAX_RETRIES) {
+     await new Promise(resolve => setTimeout(resolve, BASE_DELAY_MS * 2 ** attempts));
    }
  }
}
📝 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
while (attempts < MAX_RETRIES) {
try {
return await prisma.taskRunTag.upsert({
where: {
projectId_name: {
projectId,
name: tag,
},
},
create: {
friendlyId,
name: tag,
projectId,
},
update: {},
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
// Handle unique constraint violation (conflict)
attempts++;
if (attempts >= MAX_RETRIES) {
throw new Error(`Failed to create tag after ${MAX_RETRIES} attempts due to conflicts.`);
}
} else {
throw error; // Re-throw other errors
}
}
}
// Example: Introducing a small delay before each retry
const BASE_DELAY_MS = 100;
while (attempts < MAX_RETRIES) {
try {
return await prisma.taskRunTag.upsert({
where: {
projectId_name: {
projectId,
name: tag,
},
},
create: {
friendlyId,
name: tag,
projectId,
},
update: {},
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
// Handle unique constraint violation (conflict)
attempts++;
if (attempts >= MAX_RETRIES) {
throw new Error(`Failed to create tag after ${MAX_RETRIES} attempts due to conflicts.`);
}
if (attempts < MAX_RETRIES) {
await new Promise(resolve => setTimeout(resolve, BASE_DELAY_MS * 2 ** attempts));
}
} else {
throw error; // Re-throw other errors
}
}
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (1)

280-291: 🛠️ Refactor suggestion

Add a fallback case for unmapped statuses.

The switch statement only covers "PENDING" and "COMPLETED." If a future database status arises, this function may return undefined.

Apply this patch to add a default fallback:

 export function waitpointStatusToApiStatus(
   status: WaitpointStatus,
   outputIsError: boolean
 ): WaitpointTokenStatus {
   switch (status) {
     case "PENDING":
       return "WAITING";
     case "COMPLETED":
       return outputIsError ? "TIMED_OUT" : "COMPLETED";
+    default:
+      // Fallback to an existing status (e.g., WAITING or some new placeholder).
+      return "WAITING";
   }
 }
🧹 Nitpick comments (5)
apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (4)

346-356: Add debouncing to tag search to prevent excessive API calls.

The useEffect hook triggers an API request on every change to searchValue without debouncing. This could lead to excessive API calls during rapid typing, potentially causing performance issues.

+  const [debouncedSearchValue, setDebouncedSearchValue] = useState(searchValue);
+
+  useEffect(() => {
+    const timer = setTimeout(() => {
+      setDebouncedSearchValue(searchValue);
+    }, 300);
+    
+    return () => clearTimeout(timer);
+  }, [searchValue]);
+
   useEffect(() => {
     const searchParams = new URLSearchParams();
-    if (searchValue) {
-      searchParams.set("name", encodeURIComponent(searchValue));
+    if (debouncedSearchValue) {
+      searchParams.set("name", encodeURIComponent(debouncedSearchValue));
     }
     fetcher.load(
       `/resources/orgs/${organization.slug}/projects/${project.slug}/env/${environment.slug}/waitpoints/tags?${searchParams}`
     );
-  }, [searchValue]);
+  }, [debouncedSearchValue, organization.slug, project.slug, environment.slug]);

499-499: Fix inconsistent placeholder for Waitpoint ID input.

The placeholder says "run_" but the validation logic on lines 473-474 checks if the ID starts with "waitpoint_". This inconsistency could confuse users about the expected format.

-              placeholder="run_"
+              placeholder="waitpoint_"

616-616: Update placeholder for Idempotency key input.

The placeholder currently shows "waitpoint_" which is misleading for an idempotency key field. It should be something more relevant to idempotency keys.

-              placeholder="waitpoint_"
+              placeholder="my-idempotency-key"

358-371: Consider optimizing the filtered tags calculation.

The current implementation of filtered can be optimized to avoid unnecessary array operations.

  const filtered = useMemo(() => {
    let items: string[] = [];
    if (searchValue === "") {
      items = values("tags");
    }

    if (fetcher.data === undefined) {
      return matchSorter(items, searchValue);
    }

-   items.push(...fetcher.data.tags.map((t) => t.name));
-
-   return matchSorter(Array.from(new Set(items)), searchValue);
+   // Combine existing tags and fetched tags efficiently
+   const uniqueTags = new Set([...items, ...fetcher.data.tags.map((t) => t.name)]);
+   return matchSorter(Array.from(uniqueTags), searchValue);
  }, [searchValue, fetcher.data]);
apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (1)

132-215: Solid raw SQL approach, but watch out for indexing.

This raw query with multiple filters is efficient, but ensure the relevant columns (e.g., createdAt, environmentId, id) are properly indexed to maintain performance.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fe040df and 0f293a7.

📒 Files selected for processing (2)
  • apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (1 hunks)
  • apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (3)
packages/core/src/v3/schemas/api.ts (3)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
  • waitpointTokenStatuses (951-951)
apps/webapp/app/assets/icons/StatusIcon.tsx (1)
  • StatusIcon (3-9)
apps/webapp/app/components/runs/v3/WaitpointStatus.tsx (2)
  • waitpointStatusTitle (66-79)
  • WaitpointStatusCombo (8-23)
apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (2)
packages/core/src/v3/schemas/api.ts (5)
  • RunEngineVersion (7-7)
  • WaitpointTokenStatus (952-952)
  • WaitpointTokenStatus (953-953)
  • WaitpointTokenItem (955-965)
  • WaitpointTokenItem (966-966)
apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (1)
  • WaitpointSearchParams (63-63)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / 🧪 Unit Tests
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (9)
apps/webapp/app/components/runs/v3/WaitpointTokenFilters.tsx (1)

49-63: LGTM: Well-structured search parameters schema.

The WaitpointSearchParamsSchema is well-designed with appropriate types and preprocessing for different parameter types. The preprocessing for array values and handling of the "all" period value are good approaches.

apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts (8)

1-16: Imports and initial setup look good.

No major issues spotted in the initial imports and constants. Good modular structure and usage of external libraries.


17-38: Configuration object is well-defined.

WaitpointTokenListOptions comprehensively covers filter and pagination parameters. The optional fields allow flexible usage.


40-62: Robust success/failure result type.

The unified structure for success and failure responses is clear and easy to consume. This pattern promotes consistent error handling.


64-92: Graceful handling of deprecated engine versions.

Returning an error response when users attempt to fetch tokens on Engine V1 is transparent. The messaging is adequate to guide upgrades to the new feature set.


94-115: Logical conditional checks for statuses.

The use of filterOutputIsError based on matching a single status is straightforward. If multiple statuses or more nuanced conditions are needed in the future (e.g., both "COMPLETED" and "TIMED_OUT"), consider extending this logic.


217-238: Cursor-based pagination is well-structured.

Although the logic for handling forward/backward pagination with reversed arrays appears correct, it’s relatively complex. Ensure complete test coverage for edge cases (e.g., minimal items, exact page size, reversing order).

Please confirm your test suite covers backward pagination thoroughly.


240-278: Response shaping is consistent.

The final transformation of raw SQL rows into the WaitpointTokenItem format is clear. Sorting tags alphabetically is a nice touch for readability.


117-127:

✅ Verification successful

Verify mapping of "TIMED_OUT" to "COMPLETED" in DB queries.

Currently, "TIMED_OUT" maps to the database status "COMPLETED" with outputIsError = true. While this may be intentional, please confirm this aligns with all existing waitpoint statuses and any newer statuses introduced in the system.

You can run the following script to see if there are any references to alternate DB statuses that might not be mapped here:


🏁 Script executed:

#!/bin/bash
# Searches the codebase for references to WaitpointStatus
ast-grep --pattern $'type WaitpointStatus = $_' --json

Length of output: 13934


DB Status Mapping Verified

The mapping of "TIMED_OUT" to "COMPLETED" in the presenter (lines 117–127 in apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts) appears consistent with the current status definitions. Our search for WaitpointStatus in the codebase (notably in packages/core/src/v3/schemas/runEngine.ts) and a review of related DB migration files did not reveal any conflicting mappings or alternate handling that would suggest a divergence from this behavior.

  • The inferred type definition for WaitpointStatus does not indicate any special treatment for "TIMED_OUT" aside from mapping it to "COMPLETED".
  • No alternate mappings or conditions were found in SQL migrations or queries that override this logic.

Please ensure that any future additions of waitpoint statuses follow the same mapping pattern and that this behavior (including using outputIsError = true for the "TIMED_OUT" case) remains intentional.

Comment on lines +85 to +91
{hasFilters && (
<Form className="h-6">
<Button variant="minimal/small" LeadingIcon={TrashIcon}>
Clear all
</Button>
</Form>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement form submission for the "Clear all" button.

The "Clear all" button is inside a Form component, but the form has no action or submission handler. To properly clear the filters, you should implement a form submission handler that resets all search parameters.

-<Form className="h-6">
-  <Button variant="minimal/small" LeadingIcon={TrashIcon}>
+<Form method="get" action="." className="h-6">
+  <Button variant="minimal/small" LeadingIcon={TrashIcon} type="submit">
     Clear all
   </Button>
 </Form>
📝 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
{hasFilters && (
<Form className="h-6">
<Button variant="minimal/small" LeadingIcon={TrashIcon}>
Clear all
</Button>
</Form>
)}
{hasFilters && (
<Form method="get" action="." className="h-6">
<Button variant="minimal/small" LeadingIcon={TrashIcon} type="submit">
Clear all
</Button>
</Form>
)}

@matt-aitken matt-aitken merged commit 00586ff into main Mar 26, 2025
12 checks passed
@matt-aitken matt-aitken deleted the waitpoint-tokens-page branch March 26, 2025 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants