Skip to content

Commit 9d9bdc3

Browse files
committed
Merge branch 'refs/heads/main' into feature/tri-6738-show-aggregated-logs-in-main-page
# Conflicts: # apps/webapp/app/components/primitives/DateTime.tsx # apps/webapp/app/components/primitives/Tooltip.tsx
2 parents 1508eb6 + 49df40c commit 9d9bdc3

File tree

172 files changed

+47413
-1916
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+47413
-1916
lines changed

.changeset/cool-elephants-carry.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.cursor/rules/otel-metrics.mdc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
description: Guidelines for creating OpenTelemetry metrics to avoid cardinality issues
3+
globs:
4+
- "**/*.ts"
5+
---
6+
7+
# OpenTelemetry Metrics Guidelines
8+
9+
When creating or editing OTEL metrics (counters, histograms, gauges), always ensure metric attributes have **low cardinality**.
10+
11+
## What is Cardinality?
12+
13+
Cardinality refers to the number of unique values an attribute can have. Each unique combination of attribute values creates a new time series, which consumes memory and storage in your metrics backend.
14+
15+
## Rules
16+
17+
### DO use low-cardinality attributes:
18+
- **Enums**: `environment_type` (PRODUCTION, STAGING, DEVELOPMENT, PREVIEW)
19+
- **Booleans**: `hasFailures`, `streaming`, `success`
20+
- **Bounded error codes**: A finite, controlled set of error types
21+
- **Shard IDs**: When sharding is bounded (e.g., 0-15)
22+
23+
### DO NOT use high-cardinality attributes:
24+
- **UUIDs/IDs**: `envId`, `userId`, `runId`, `projectId`, `organizationId`
25+
- **Unbounded integers**: `itemCount`, `batchSize`, `retryCount`
26+
- **Timestamps**: `createdAt`, `startTime`
27+
- **Free-form strings**: `errorMessage`, `taskName`, `queueName`
28+
29+
## Example
30+
31+
```typescript
32+
// BAD - High cardinality
33+
this.counter.add(1, {
34+
envId: options.environmentId, // UUID - unbounded
35+
itemCount: options.runCount, // Integer - unbounded
36+
});
37+
38+
// GOOD - Low cardinality
39+
this.counter.add(1, {
40+
environment_type: options.environmentType, // Enum - 4 values
41+
streaming: true, // Boolean - 2 values
42+
});
43+
```
44+
45+
## Prometheus Metric Naming
46+
47+
When metrics are exported via OTLP to Prometheus, the exporter automatically adds unit suffixes to metric names:
48+
49+
| OTel Metric Name | Unit | Prometheus Name |
50+
|------------------|------|-----------------|
51+
| `my_duration_ms` | `ms` | `my_duration_ms_milliseconds` |
52+
| `my_counter` | counter | `my_counter_total` |
53+
| `items_inserted` | counter | `items_inserted_inserts_total` |
54+
| `batch_size` | histogram | `batch_size_items_bucket` |
55+
56+
Keep this in mind when writing Grafana dashboards or Prometheus queries—the metric names in Prometheus will differ from the names defined in code.
57+
58+
## Reference
59+
60+
See the schedule engine (`internal-packages/schedule-engine/src/engine/index.ts`) for a good example of low-cardinality metric attributes.
61+
62+
High cardinality metrics can cause:
63+
- Memory bloat in metrics backends (Axiom, Prometheus, etc.)
64+
- Slow queries and dashboard timeouts
65+
- Increased costs (many backends charge per time series)
66+
- Potential data loss or crashes at scale
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Claude Code Review
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize]
6+
# Optional: Only run on specific file changes
7+
# paths:
8+
# - "src/**/*.ts"
9+
# - "src/**/*.tsx"
10+
# - "src/**/*.js"
11+
# - "src/**/*.jsx"
12+
13+
jobs:
14+
claude-review:
15+
# Optional: Filter by PR author
16+
# if: |
17+
# github.event.pull_request.user.login == 'external-contributor' ||
18+
# github.event.pull_request.user.login == 'new-developer' ||
19+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20+
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
pull-requests: read
25+
issues: read
26+
id-token: write
27+
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 1
33+
34+
- name: Run Claude Code Review
35+
id: claude-review
36+
uses: anthropics/claude-code-action@v1
37+
with:
38+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39+
prompt: |
40+
REPO: ${{ github.repository }}
41+
PR NUMBER: ${{ github.event.pull_request.number }}
42+
43+
Please review this pull request and provide feedback on:
44+
- Code quality and best practices
45+
- Potential bugs or issues
46+
- Performance considerations
47+
- Security concerns
48+
- Test coverage
49+
50+
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
51+
52+
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
53+
54+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
55+
# or https://code.claude.com/docs/en/cli-reference for available options
56+
claude_args: |
57+
--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"
58+
--model claude-opus-4-5-20251101
59+

.github/workflows/claude.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Claude Code
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, assigned]
10+
pull_request_review:
11+
types: [submitted]
12+
13+
jobs:
14+
claude:
15+
if: |
16+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
pull-requests: read
24+
issues: read
25+
id-token: write
26+
actions: read # Required for Claude to read CI results on PRs
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 1
32+
33+
- name: ⎔ Setup pnpm
34+
uses: pnpm/action-setup@v4
35+
with:
36+
version: 10.23.0
37+
38+
- name: ⎔ Setup node
39+
uses: buildjet/setup-node@v4
40+
with:
41+
node-version: 20.19.0
42+
cache: "pnpm"
43+
44+
- name: 📥 Download deps
45+
run: pnpm install --frozen-lockfile
46+
47+
- name: 📀 Generate Prisma Client
48+
run: pnpm run generate
49+
50+
- name: Run Claude Code
51+
id: claude
52+
uses: anthropics/claude-code-action@v1
53+
with:
54+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
55+
56+
# This is an optional setting that allows Claude to read CI results on PRs
57+
additional_permissions: |
58+
actions: read
59+
60+
claude_args: |
61+
--model claude-opus-4-5-20251101
62+
--allowedTools "Bash(pnpm:*),Bash(turbo:*),Bash(git:*),Bash(gh:*),Bash(npx:*),Bash(docker:*),Edit,MultiEdit,Read,Write,Glob,Grep,LS,Task"
63+
64+
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
65+
# prompt: 'Update the pull request description to include a summary of changes.'
66+
67+
# Optional: Add claude_args to customize behavior and configuration
68+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
69+
# or https://code.claude.com/docs/en/cli-reference for available options
70+
# claude_args: '--allowed-tools Bash(gh pr:*)'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function ChevronExtraSmallDown({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M15 6L9.75926 12.1142C9.36016 12.5798 8.63984 12.5798 8.24074 12.1142L3 6"
6+
stroke="currentColor"
7+
strokeWidth="2"
8+
strokeMiterlimit="1.00244"
9+
strokeLinecap="round"
10+
/>
11+
</svg>
12+
);
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function ChevronExtraSmallUp({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M3 12L8.24074 5.8858C8.63984 5.42019 9.36016 5.42019 9.75926 5.8858L15 12"
6+
stroke="currentColor"
7+
strokeWidth="2"
8+
strokeMiterlimit="1.00244"
9+
strokeLinecap="round"
10+
/>
11+
</svg>
12+
);
13+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { cn } from "~/utils/cn";
2+
import { Badge } from "./primitives/Badge";
3+
import { SimpleTooltip } from "./primitives/Tooltip";
4+
5+
export function AlphaBadge({
6+
inline = false,
7+
className,
8+
}: {
9+
inline?: boolean;
10+
className?: string;
11+
}) {
12+
return (
13+
<SimpleTooltip
14+
button={
15+
<Badge variant="extra-small" className={cn(inline ? "inline-grid" : "", className)}>
16+
Alpha
17+
</Badge>
18+
}
19+
content="This feature is in Alpha."
20+
disableHoverableContent
21+
/>
22+
);
23+
}
24+
25+
export function AlphaTitle({ children }: { children: React.ReactNode }) {
26+
return (
27+
<>
28+
<span>{children}</span>
29+
<AlphaBadge />
30+
</>
31+
);
32+
}

apps/webapp/app/components/ErrorDisplay.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { HomeIcon } from "@heroicons/react/20/solid";
22
import { isRouteErrorResponse, useRouteError } from "@remix-run/react";
3-
import { motion } from "framer-motion";
43
import { friendlyErrorDisplay } from "~/utils/httpErrors";
54
import { LinkButton } from "./primitives/Buttons";
65
import { Header1 } from "./primitives/Headers";
76
import { Paragraph } from "./primitives/Paragraph";
8-
import Spline from "@splinetool/react-spline";
7+
import { TriggerRotatingLogo } from "./TriggerRotatingLogo";
98
import { type ReactNode } from "react";
109

1110
type ErrorDisplayOptions = {
@@ -57,14 +56,7 @@ export function ErrorDisplay({ title, message, button }: DisplayOptionsProps) {
5756
{button ? button.title : "Go to homepage"}
5857
</LinkButton>
5958
</div>
60-
<motion.div
61-
className="pointer-events-none absolute inset-0 overflow-hidden"
62-
initial={{ opacity: 0 }}
63-
animate={{ opacity: 1 }}
64-
transition={{ delay: 0.5, duration: 2, ease: "easeOut" }}
65-
>
66-
<Spline scene="https://prod.spline.design/wRly8TZN-e0Twb8W/scene.splinecode" />
67-
</motion.div>
59+
<TriggerRotatingLogo />
6860
</div>
6961
);
7062
}

apps/webapp/app/components/Shortcuts.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function ShortcutContent() {
134134
<ShortcutKey shortcut={{ key: "arrowleft" }} variant="medium/bright" />
135135
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium/bright" />
136136
</Shortcut>
137-
<Shortcut name="Jump to adjacent">
137+
<Shortcut name="Jump to next/previous run">
138138
<ShortcutKey shortcut={{ key: "[" }} variant="medium/bright" />
139139
<ShortcutKey shortcut={{ key: "]" }} variant="medium/bright" />
140140
</Shortcut>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { motion } from "framer-motion";
2+
import { useEffect, useState } from "react";
3+
4+
declare global {
5+
namespace JSX {
6+
interface IntrinsicElements {
7+
"spline-viewer": React.DetailedHTMLProps<
8+
React.HTMLAttributes<HTMLElement> & {
9+
url?: string;
10+
"loading-anim-type"?: string;
11+
},
12+
HTMLElement
13+
>;
14+
}
15+
}
16+
17+
interface Window {
18+
__splineLoader?: Promise<void>;
19+
}
20+
}
21+
22+
export function TriggerRotatingLogo() {
23+
const [isSplineReady, setIsSplineReady] = useState(false);
24+
25+
useEffect(() => {
26+
// Already registered from a previous render
27+
if (customElements.get("spline-viewer")) {
28+
setIsSplineReady(true);
29+
return;
30+
}
31+
32+
// Another mount already started loading - share the same promise
33+
if (window.__splineLoader) {
34+
window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));
35+
return;
36+
}
37+
38+
// First mount: create script and shared loader promise
39+
const script = document.createElement("script");
40+
script.type = "module";
41+
// Version pinned; SRI hash omitted as unpkg doesn't guarantee hash stability across deploys
42+
script.src = "https://unpkg.com/@splinetool/[email protected]/build/spline-viewer.js";
43+
44+
window.__splineLoader = new Promise<void>((resolve, reject) => {
45+
script.onload = () => resolve();
46+
script.onerror = () => reject();
47+
});
48+
49+
window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));
50+
51+
document.head.appendChild(script);
52+
53+
// Intentionally no cleanup: once the custom element is registered globally,
54+
// removing the script would break re-mounts while providing no benefit
55+
}, []);
56+
57+
if (!isSplineReady) {
58+
return null;
59+
}
60+
61+
return (
62+
<motion.div
63+
className="pointer-events-none absolute inset-0 overflow-hidden"
64+
initial={{ opacity: 0 }}
65+
animate={{ opacity: 1 }}
66+
transition={{ delay: 0.5, duration: 2, ease: "easeOut" }}
67+
>
68+
<spline-viewer
69+
loading-anim-type="spinner-small-light"
70+
url="https://prod.spline.design/wRly8TZN-e0Twb8W/scene.splinecode"
71+
style={{ width: "100%", height: "100%" }}
72+
/>
73+
</motion.div>
74+
);
75+
}

0 commit comments

Comments
 (0)