Compare commits

..

2 Commits

Author SHA1 Message Date
Chris Truter
2076d909d9 Run the tests please 2026-03-16 16:23:48 +02:00
Chris Truter
b2c2816d86 Switch branch of python-runner-container 2026-03-16 16:12:41 +02:00
8091 changed files with 686426 additions and 950952 deletions

View File

@@ -1,4 +1,4 @@
---
--b-
name: add-tracing
description: Add OpenTelemetry tracing spans to Clojure code following Metabase tracing conventions. Use when instrumenting backend code with trace coverage.
---

View File

@@ -15,7 +15,7 @@ Analytics events in Metabase use Snowplow with typed event schemas. All events m
**Key Files:**
- `frontend/src/metabase-types/analytics/event.ts` - Event type definitions
- `frontend/src/metabase-types/analytics/schema.ts` - Schema registry
- `frontend/src/metabase/analytics/` - Core tracking functions (import from `metabase/analytics`)
- `frontend/src/metabase/lib/analytics.ts` - Core tracking functions
- Feature-specific `analytics.ts` files - Tracking function wrappers
## Quick Checklist
@@ -91,7 +91,7 @@ export type DataStudioEvent =
In your feature's `analytics.ts` file (e.g., `enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts`):
```typescript
import { trackSimpleEvent } from "metabase/analytics";
import { trackSimpleEvent } from "metabase/lib/analytics";
export const trackDataStudioTablePickerFiltersApplied = () => {
trackSimpleEvent({
@@ -471,7 +471,7 @@ Event type definitions (all in one place):
frontend/src/metabase-types/analytics/event.ts
Core tracking utilities:
frontend/src/metabase/analytics/ (import from `metabase/analytics`)
frontend/src/metabase/lib/analytics.ts
```
## Real-World Examples

View File

@@ -1,173 +0,0 @@
---
name: clojure-eval
description: Evaluate Clojure code via nREPL using clj-nrepl-eval. Use this when you need to test code, check if edited files compile, verify function behavior, or interact with a running REPL session.
---
# Clojure REPL Evaluation
## When to Use This Skill
Use this skill when you need to:
- **Verify that edited Clojure files compile and load correctly**
- Test function behavior interactively
- Check the current state of the REPL
- Debug code by evaluating expressions
- Require or load namespaces for testing
- Validate that code changes work before committing
## How It Works
The `clj-nrepl-eval` command evaluates Clojure code against an nREPL server. **Session state persists between evaluations**, so you can require a namespace in one evaluation and use it in subsequent calls. Each host:port combination maintains its own session file.
## Instructions
### 0. Discover and select nREPL server
First, discover what nREPL servers are running in the current directory:
```bash
clj-nrepl-eval --discover-ports
```
This will show all nREPL servers (Clojure, Babashka, shadow-cljs, etc.) running in the current project directory.
**Then use the AskUserQuestion tool:**
- **If ports are discovered:** Prompt user to select which nREPL port to use:
- **question:** "Which nREPL port would you like to use?"
- **header:** "nREPL Port"
- **options:** Present each discovered port as an option with:
- **label:** The port number
- **description:** The server type and status (e.g., "Clojure nREPL server in current directory")
- Include up to 4 discovered ports as options
- The user can select "Other" to enter a custom port number
- **If no ports are discovered:** Prompt user how to start an nREPL server:
- **question:** "No nREPL servers found. How would you like to start one?"
- **header:** "Start nREPL"
- **options:**
- **label:** "deps.edn alias", **description:** "Find and use an nREPL alias in deps.edn"
- **label:** "Leiningen", **description:** "Start nREPL using 'lein repl'"
- The user can select "Other" for alternative methods or if they already have a server running on a specific port
IMPORTANT: IF you start a REPL do not supply a port let the nREPL start and return the port that it was started on.
### 1. Evaluate Clojure Code
> Evaluation automatically connects to the given port
Use the `-p` flag to specify the port and pass your Clojure code.
**Recommended: Pass code as a command-line argument:**
```bash
clj-nrepl-eval -p <PORT> "(+ 1 2 3)"
```
**For multiple expressions (single line):**
```bash
clj-nrepl-eval -p <PORT> "(def x 10) (+ x 20)"
```
**Alternative: Using heredoc (may require permission approval for multiline commands):**
```bash
clj-nrepl-eval -p <PORT> <<'EOF'
(def x 10)
(+ x 20)
EOF
```
**Alternative: Via stdin pipe:**
```bash
echo "(+ 1 2 3)" | clj-nrepl-eval -p <PORT>
```
### 2. Display nREPL Sessions
**Discover all nREPL servers in current directory:**
```bash
clj-nrepl-eval --discover-ports
```
Shows all running nREPL servers in the current project directory, including their type (clj/bb/basilisp) and whether they match the current working directory.
**Check previously connected sessions:**
```bash
clj-nrepl-eval --connected-ports
```
Shows only connections you have made before (appears after first evaluation on a port).
### 3. Common Patterns
**Require a namespace (always use :reload to pick up changes):**
```bash
clj-nrepl-eval -p <PORT> "(require '[my.namespace :as ns] :reload)"
```
**Test a function after requiring:**
```bash
clj-nrepl-eval -p <PORT> "(ns/my-function arg1 arg2)"
```
**Check if a file compiles:**
```bash
clj-nrepl-eval -p <PORT> "(require 'my.namespace :reload)"
```
**Multiple expressions:**
```bash
clj-nrepl-eval -p <PORT> "(def x 10) (* x 2) (+ x 5)"
```
**Complex multiline code (using heredoc):**
```bash
clj-nrepl-eval -p <PORT> <<'EOF'
(def x 10)
(* x 2)
(+ x 5)
EOF
```
*Note: Heredoc syntax may require permission approval.*
**With custom timeout (in milliseconds):**
```bash
clj-nrepl-eval -p <PORT> --timeout 5000 "(long-running-fn)"
```
**Reset the session (clears all state):**
```bash
clj-nrepl-eval -p <PORT> --reset-session
clj-nrepl-eval -p <PORT> --reset-session "(def x 1)"
```
## Available Options
- `-p, --port PORT` - nREPL port (required)
- `-H, --host HOST` - nREPL host (default: 127.0.0.1)
- `-t, --timeout MILLISECONDS` - Timeout (default: 120000 = 2 minutes)
- `-r, --reset-session` - Reset the persistent nREPL session
- `-c, --connected-ports` - List previously connected nREPL sessions
- `-d, --discover-ports` - Discover nREPL servers in current directory
- `-h, --help` - Show help message
## Important Notes
- **Prefer command-line arguments:** Pass code as quoted strings: `clj-nrepl-eval -p <PORT> "(+ 1 2 3)"` - works with existing permissions
- **Heredoc for complex code:** Use heredoc (`<<'EOF' ... EOF`) for truly multiline code, but note it may require permission approval
- **Sessions persist:** State (vars, namespaces, loaded libraries) persists across invocations until the nREPL server restarts or `--reset-session` is used
- **Automatic delimiter repair:** The tool automatically repairs missing or mismatched parentheses
- **Always use :reload:** When requiring namespaces, use `:reload` to pick up recent changes
- **Default timeout:** 2 minutes (120000ms) - increase for long-running operations
- **Input precedence:** Command-line arguments take precedence over stdin
## Typical Workflow
1. Discover nREPL servers: `clj-nrepl-eval --discover-ports`
2. Use **AskUserQuestion** tool to prompt user to select a port
3. Require namespace:
```bash
clj-nrepl-eval -p <PORT> "(require '[my.ns :as ns] :reload)"
```
4. Test function:
```bash
clj-nrepl-eval -p <PORT> "(ns/my-fn ...)"
```
5. Iterate: Make changes, re-require with `:reload`, test again

View File

@@ -1,834 +0,0 @@
---
name: emotion-migrate
description: Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports from components.
---
# Emotion → Mantine + CSS Modules Migration Skill
Migrate Emotion styled-components (`@emotion/styled`, `@emotion/react`) to Mantine layout components with style props and CSS modules. The goal is zero Emotion imports, zero inline styles, and maximum use of design system tokens.
## Priority Order (Strict)
1. **Mantine components + style props** — `Box`, `Flex`, `Stack`, `Group`, `Text`, `Title`, `Card`. This is the DEFAULT. Every CSS property must be checked against style props FIRST.
2. **CSS modules** (`.module.css`) — ONLY for properties that Mantine style props genuinely cannot express: pseudo-selectors (`:hover`, `:focus`, `::before`), `box-shadow`, `border` shorthand, `animation`/`@keyframes`, complex selectors, `cursor`, `pointer-events`, `overflow`, `text-overflow`, `white-space`, `transition`.
3. **Inline styles ONLY for dynamic values** — `style={{ }}` is allowed only for truly dynamic runtime values (e.g., computed widths, positions, data-driven colors). All static styles must use Mantine props or CSS modules.
### Mantine-First Decision Gate (CRITICAL)
For EACH styled component, go through every CSS property and ask: "Can this be a Mantine style prop?" If yes → style prop. If no → CSS module. Do NOT dump an entire component into a CSS module just because one property needs it — split them.
**Properties that ARE style props** (use these, not CSS modules):
- `display` → `display` prop
- `color` → `c` prop (`c="brand"`, `c="text-primary"`)
- `background-color` → `bg` prop (`bg="background-primary"`)
- `font-size` → `fz` prop (`fz="md"`)
- `font-weight` → `fw` prop (`fw="bold"`)
- `line-height` → `lh` prop (`lh="md"`)
- `text-align` → `ta` prop (`ta="center"`)
- `padding` (all variants) → `p`, `px`, `py`, `pt`, `pb`, `pl`, `pr`
- `margin` (all variants) → `m`, `mx`, `my`, `mt`, `mb`, `ml`, `mr`
- `width` → `w`, `min-width` → `miw`, `max-width` → `maw`
- `height` → `h`, `min-height` → `mih`, `max-height` → `mah`
- `flex` → `flex` prop (`flex="0 0 auto"`, `flex={1}`)
- `gap` → `gap` prop (on Flex/Stack/Group)
- `align-items` → `align` prop (on Flex/Stack/Group)
- `justify-content` → `justify` prop (on Flex/Stack/Group)
- `flex-direction` → `direction` prop (on Flex)
- `flex-wrap` → `wrap` prop (on Flex)
- `position` → `pos` prop
- `top/right/bottom/left` → `top`, `right`, `bottom`, `left` props
- `opacity` → `opacity` prop
**Properties that NEED CSS modules** (no style prop equivalent):
- `:hover`, `:focus`, `:active`, `::before`, `::after` (pseudo-selectors)
- `box-shadow`, `border` (shorthand with color), `outline`
- `cursor`, `pointer-events`
- `overflow`, `text-overflow`, `white-space`
- `animation`, `transition`, `transform`
- `@media` queries (UNLESS it's simple responsive spacing/sizing — then use responsive syntax: `p={{ base: "md", lg: "xl" }}`)
**Hybrid approach** — when a component needs BOTH, put style props on the Mantine component AND add a CSS module class for the rest:
```tsx
<Flex
className={S.root} /* for :hover, box-shadow, border */
align="center" /* style prop */
gap="sm" /* style prop */
p="md" /* style prop */
bg="background-primary" /* style prop */
>
```
## CSS Module Conventions (Strict)
### Class Naming: camelCase
All CSS module class names MUST use **camelCase**. This is the dominant convention across the codebase (~830 camelCase vs ~620 PascalCase classes), used consistently in Mantine UI components, and matches standard CSS module conventions.
```css
/* CORRECT */
.root {
}
.settingsSection {
}
.dragHandle {
}
.closeIcon {
}
/* WRONG — do not use PascalCase or kebab-case */
.ItemRoot {
}
.settings-section {
}
```
Modifier/state classes also use camelCase:
```css
.selected {
}
.disabled {
}
.interactive {
}
.draggable {
}
```
### No Cascading — Direct Class Assignment
Cascading selectors are **discouraged**. Instead of styling through parent-child relationships, assign a class directly to the element that needs styling.
```css
/* WRONG — cascading/descendant selectors */
.root > input {
}
.root .label {
}
.container > div > span {
}
/* CORRECT — direct class on the target element */
.input {
}
.label {
}
.title {
}
```
The only acceptable nesting patterns are:
- **Pseudo-selectors on the same element**: `.item { &:hover { } }`
- **Modifier composition**: `.item { &.selected { } }`
- **Hover-reveal patterns** where a parent hover affects a child: `.root:hover .showOnHover { opacity: 1; }` — but only when structurally necessary (the child has no way to know about the parent's hover state)
### Import Alias
Always import the CSS module as `S`:
```tsx
import S from "./ComponentName.module.css";
```
## Step-by-Step Migration Process
### Step 1: Read and Understand
Read the `.styled.tsx` file AND every component that imports from it. Understand:
- Which styled components are used and where
- Which props drive dynamic styles
- Which styles are static vs conditional
- Which styles can map directly to Mantine style props
### Step 2: Classify Each Styled Component
For each styled component, apply the Mantine-First Decision Gate above. Then determine the migration target:
| Emotion Pattern | Migration Target |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `styled.div` with only layout/spacing/color | `Box`, `Flex`, `Stack`, or `Group` with style props. **NO CSS module needed.** |
| `styled.div` with flexbox column | `Stack` component with style props |
| `styled.div` with flexbox row | `Flex` or `Group` component with style props |
| `styled.span` / `styled.p` with color/weight/size | **`Text component="span"` with style props** (`c`, `fw`, `fz`). NO CSS module. |
| `styled.div` with hover/focus/pseudo-selectors | **Hybrid**: Mantine component with style props for expressible properties + CSS module class for pseudo-selectors only |
| `styled.div` with media queries (simple spacing/sizing) | **Responsive style props**: `p={{ base: "md", lg: "xl" }}`. NO CSS module. |
| `styled.div` with media queries (complex/non-spacing) | CSS module for the media query parts, style props for the rest |
| `styled.div` with animations/keyframes | CSS module for animation, style props for layout |
| `styled(SomeComponent)` with only color/spacing/flex | **Wrap in Mantine component** with style props: `<Box c="brand" flex="0 0 auto"><Icon /></Box>`, or pass style props if the component accepts them |
| `styled(SomeComponent)` with pseudo-selectors/complex styles | CSS module `className` on the component |
| Dynamic props `styled.div<{ isActive: boolean }>` | Mantine style props for simple toggles (`c={active ? "brand" : "text-primary"}`), `cx()` with CSS module classes for complex state combinations involving pseudo-selectors |
### Step 3: Create CSS Module (if needed)
Create `ComponentName.module.css` alongside the component file:
```css
/* Use design system tokens — NEVER raw color/spacing values */
.root {
border: 1px solid var(--mb-color-border);
border-radius: var(--mantine-radius-md);
background-color: var(--mb-color-background-primary);
}
/* Conditional states as separate classes, combined with cx() */
.active {
background-color: var(--mb-color-brand);
color: var(--mb-color-text-primary-inverse);
}
.disabled {
color: var(--mb-color-text-tertiary);
pointer-events: none;
}
/* Hover/focus/pseudo-selectors — nest with & */
.interactive {
cursor: pointer;
&:hover {
color: var(--mb-color-brand);
background-color: var(--mb-color-background-hover);
}
}
/* Hover-reveal: acceptable parent→child nesting */
.root:hover .showOnHover {
opacity: 1;
}
/* Responsive styles */
@media (--breakpoint-min-md) {
.root {
padding: var(--mantine-spacing-lg);
}
}
```
### Step 4: Update the Component TSX
```tsx
import cx from "classnames";
import CS from "metabase/css/core/index.css";
import { Box, Flex, Group, Stack, Text } from "metabase/ui";
import S from "./ComponentName.module.css";
// Dynamic props → cx() with conditional classes
<Flex
className={cx(S.root, {
[S.active]: isActive,
[S.disabled]: disabled,
})}
align="center"
gap="sm"
p="md"
w="100%"
>
```
### Step 5: Delete the `.styled.tsx` File
Remove the old styled file entirely. Remove all imports of it from other files.
### Step 6: Verify
- Confirm zero `@emotion/styled` or `@emotion/react` imports remain in the migrated files
- Confirm no static inline styles (dynamic runtime values are fine)
- Confirm all colors use tokens, not raw hex/rgb values
## Design System Token Reference
### Mantine Style Props (use directly on components)
**Spacing** (`p`, `px`, `py`, `pt`, `pb`, `pl`, `pr`, `m`, `mx`, `my`, `mt`, `mb`, `ml`, `mr`):
- `"xs"` = 4px, `"sm"` = 8px, `"md"` = 16px, `"lg"` = 24px, `"xl"` = 32px
- Custom: `rem(48)` for non-standard values (import `rem` from `metabase/ui`)
**Dimensions** (`w`, `h`, `maw`, `mah`, `miw`, `mih`):
- `"100%"`, `"100vh"`, `rem(400)`, etc.
**Colors** (`c`, `bg`):
- Text: `"text-primary"`, `"text-secondary"`, `"text-tertiary"`
- Background: `"background-primary"`, `"background-secondary"`, `"background-hover"`
- Brand: `"brand"`, `"error"`, `"success"`, `"warning"`
**Typography** (`fz`, `fw`, `lh`, `ta`, `ff`):
- Font size: `"xs"` = 11px, `"sm"` = 12px, `"md"` = 14px, `"lg"` = 17px, `"xl"` = 21px
- Font weight: `"bold"`, `"normal"`, or numeric `700`
- Line height: `"xs"` = 100%, `"sm"` = 115%, `"md"` = 122%, `"lg"` = 138%, `"xl"` = 150%
- Text align: `"center"`, `"left"`, `"right"`
**Flexbox** (`align`, `justify`, `gap`, `direction`, `wrap`, `flex`):
- `align="center"`, `justify="space-between"`, `gap="sm"`, `direction="column"`, `wrap="nowrap"`
- `flex={1}`, `flex="0 0 auto"`
**Other**: `pos` (position), `display`
**Responsive syntax**: `px={{ base: "md", md: "lg", lg: rem(48) }}`
### CSS Variable Tokens (use in `.module.css` files)
**Colors** — `var(--mb-color-<name>)`:
- `--mb-color-text-primary`, `--mb-color-text-secondary`, `--mb-color-text-tertiary`
- `--mb-color-background-primary`, `--mb-color-background-secondary`, `--mb-color-background-hover`
- `--mb-color-border`, `--mb-color-border-strong`, `--mb-color-border-subtle`
- `--mb-color-brand`, `--mb-color-brand-hover`
- `--mb-color-error`, `--mb-color-success`, `--mb-color-warning`
- `--mb-color-shadow`, `--mb-color-focus`
- `--mb-color-accent0` through `--mb-color-accent7`
**Mantine spacing** — `var(--mantine-spacing-<size>)`:
- `--mantine-spacing-xs` (4px), `--mantine-spacing-sm` (8px), `--mantine-spacing-md` (16px), `--mantine-spacing-lg` (24px), `--mantine-spacing-xl` (32px)
**Mantine radius** — `var(--mantine-radius-<size>)`:
- `--mantine-radius-xs` (4px), `--mantine-radius-sm` (6px), `--mantine-radius-md` (8px), `--mantine-radius-xl` (40px)
**Mantine font sizes** — `var(--mantine-font-size-<size>)`:
- `--mantine-font-size-xs` (11px), `--mantine-font-size-sm` (12px), `--mantine-font-size-md` (14px), `--mantine-font-size-lg` (17px), `--mantine-font-size-xl` (21px)
**Breakpoints** (in `.module.css` files):
- `@media (--breakpoint-min-sm)` (40em / 640px)
- `@media (--breakpoint-min-md)` (60em / 960px)
- `@media (--breakpoint-min-lg)` (80em / 1280px)
- `@media (--breakpoint-min-xl)` (120em / 1920px)
- Also available: `--breakpoint-max-*` variants
### Snap Hardcoded Literals to Nearest Design Token (CRITICAL)
When migrating, **never carry over hardcoded `rem`/`px` values** from the original Emotion code. Instead, snap them to the nearest design system token. The original values were often arbitrary — the migration is an opportunity to align with the design system.
**Spacing** — snap to the nearest Mantine spacing token:
| Hardcoded value | Nearest token | Style prop | CSS variable |
| --------------------------------- | ------------- | ------------ | ------------------------------ |
| `0.125rem` (2px) | `2px` | `2` (number) | `2px` (keep literal, no token) |
| `0.2rem` (3.2px), `0.25rem` (4px) | **xs** (4px) | `"xs"` | `var(--mantine-spacing-xs)` |
| `0.5rem` (8px) | **sm** (8px) | `"sm"` | `var(--mantine-spacing-sm)` |
| `0.75rem` (12px) | between sm/md | `rem(12)` | `0.75rem` (no exact token) |
| `1rem` (16px) | **md** (16px) | `"md"` | `var(--mantine-spacing-md)` |
| `1.5rem` (24px) | **lg** (24px) | `"lg"` | `var(--mantine-spacing-lg)` |
| `2rem` (32px) | **xl** (32px) | `"xl"` | `var(--mantine-spacing-xl)` |
**Border radius** — snap to the nearest Mantine radius token:
| Hardcoded value | Nearest token | CSS variable |
| ---------------- | ----------------------------- | -------------------------- |
| `0.25rem` (4px) | **xs** (4px) | `var(--mantine-radius-xs)` |
| `0.375rem` (6px) | **sm** (6px) | `var(--mantine-radius-sm)` |
| `0.5rem` (8px) | **md** (8px) | `var(--mantine-radius-md)` |
| `1rem`+ (16px+) | **xl** (40px) or keep literal | `var(--mantine-radius-xl)` |
**Font sizes** — snap to the nearest Mantine font size token:
| Hardcoded value | Nearest token | Style prop | CSS variable |
| -------------------------------- | ------------- | ---------- | ----------------------------- |
| `0.6875rem` (11px) | **xs** (11px) | `fz="xs"` | `var(--mantine-font-size-xs)` |
| `0.75rem` (12px) | **sm** (12px) | `fz="sm"` | `var(--mantine-font-size-sm)` |
| `0.875rem` (14px), `1rem` (16px) | **md** (14px) | `fz="md"` | `var(--mantine-font-size-md)` |
| `1.063rem` (17px) | **lg** (17px) | `fz="lg"` | `var(--mantine-font-size-lg)` |
| `1.3rem` (21px) | **xl** (21px) | `fz="xl"` | `var(--mantine-font-size-xl)` |
**Rules:**
- If the hardcoded value is within ~2px of a token, use the token
- If it falls exactly between two tokens, prefer the smaller one (tighter is safer)
- If no token is close (e.g., `48px` spacing), use `rem(48)` for style props or the literal value in CSS modules
- This applies everywhere: style props, CSS module values, Icon `size` props, etc.
## Common Migration Patterns
### Pattern 1: Static Layout Container → Mantine Component
**Before:**
```tsx
// Component.styled.tsx
export const Container = styled.div`
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem;
width: 100%;
`;
```
**After:**
```tsx
<Flex align="center" gap="sm" p="md" w="100%">
```
### Pattern 2: Vertical Stack → Stack Component
**Before:**
```tsx
export const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: 1.5rem;
padding: 2rem;
`;
```
**After:**
```tsx
<Stack gap="lg" p="xl">
```
### Pattern 3: Dynamic Props → cx() with CSS Module Classes
**Before:**
```tsx
export const Item = styled.div<{ isSelected: boolean; disabled: boolean }>`
color: ${(props) =>
props.disabled ? color("text-tertiary") : color("text-primary")};
background-color: ${(props) =>
props.isSelected ? color("brand") : "transparent"};
cursor: ${(props) => (props.disabled ? "default" : "pointer")};
&:hover {
color: ${(props) => !props.disabled && color("brand")};
}
`;
```
**After (CSS module):**
```css
/* Item.module.css */
.itemRoot {
color: var(--mb-color-text-primary);
cursor: pointer;
&:hover {
color: var(--mb-color-brand);
}
}
.selected {
background-color: var(--mb-color-brand);
}
.disabled {
color: var(--mb-color-text-tertiary);
cursor: default;
&:hover {
color: var(--mb-color-text-tertiary);
}
}
```
**After (TSX):**
```tsx
<Box
className={cx(S.itemRoot, {
[S.selected]: isSelected,
[S.disabled]: disabled,
})}
>
```
### Pattern 4: Extending a Component → Style Props First, CSS Module Only If Needed
When a styled component wraps another component, first check if the styles can be expressed as style props on a Mantine wrapper or on the component itself. Only use a CSS module when properties genuinely need it.
**Before:**
```tsx
export const CardIcon = styled(Icon)`
display: block;
flex: 0 0 auto;
color: var(--mb-color-brand);
`;
export const CardTitle = styled(Ellipsified)`
color: var(--mb-color-text-primary);
font-size: 1rem;
font-weight: bold;
margin-left: 1rem;
`;
```
**After — style props on Mantine wrapper (preferred when ALL properties are expressible):**
```tsx
<Box display="block" flex="0 0 auto" c="brand">
<Icon {...icon} />
</Box>
<Box c="text-primary" fz="md" fw="bold" ml="md">
<Ellipsified>{title}</Ellipsified>
</Box>
```
**After — CSS module (use only when component has pseudo-selectors or non-expressible styles):**
```css
.icon {
color: var(--mb-color-brand);
width: 1.5rem;
height: 1.5rem;
cursor: pointer;
&:hover {
color: var(--mb-color-brand-hover);
}
}
```
```tsx
<Icon className={S.icon} name="warning" />
```
### Pattern 4b: `styled.span` / `styled.p` with Text Styles → `Text` Component
**Before:**
```tsx
export const Label = styled.span`
color: var(--mb-color-text-secondary);
font-weight: bold;
`;
export const Title = styled.span`
color: var(--mb-color-text-primary);
font-size: 1rem;
font-weight: bold;
margin-left: 0.5rem;
`;
```
**After — `Text` component with style props (NO CSS module needed):**
```tsx
<Text component="span" c="text-secondary" fw="bold">
{label}
</Text>
<Text component="span" c="text-primary" fz="md" fw="bold" ml="sm">
{title}
</Text>
```
`Text` renders as `<p>` by default. Use `component="span"` to render inline. All color, typography, and spacing props work directly — no CSS module needed for pure text styling.
### Pattern 5: Keyframes Animation → CSS Module
**Before:**
```tsx
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
export const Spinner = styled.div`
animation: ${spin} 1s infinite linear;
`;
```
**After (CSS module):**
```css
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.spinner {
animation: spin 1s infinite linear;
}
```
**After (TSX):**
```tsx
<Box className={S.spinner} />
```
### Pattern 6: color() Function → CSS Variables
| Emotion (JS) | CSS Module | Mantine Style Prop |
| ----------------------- | ------------------------------------------------------------ | --------------------------- |
| `color("brand")` | `var(--mb-color-brand)` | `c="brand"` or `bg="brand"` |
| `color("text-primary")` | `var(--mb-color-text-primary)` | `c="text-primary"` |
| `color("border")` | `var(--mb-color-border)` | N/A (use CSS module) |
| `alpha("brand", 0.2)` | `color-mix(in srgb, var(--mb-color-brand), transparent 80%)` | N/A (use CSS module) |
### Pattern 7: Responsive Spacing/Sizing → Responsive Style Props (NOT CSS Media Queries)
When a styled component only changes spacing, sizing, or other style-prop-expressible values at breakpoints, use Mantine's responsive syntax instead of CSS module media queries.
**Before:**
```tsx
export const CaptionRoot = styled.div`
margin-bottom: 1.5rem;
${breakpointMinExtraLarge} {
margin-bottom: 2rem;
}
`;
```
**After — responsive style prop (NO CSS module needed):**
```tsx
<Flex mb={{ base: "lg", xl: "xl" }}>
```
Breakpoint keys: `base` (default), `xs` (40em), `sm` (48em), `md` (60em), `lg` (80em), `xl` (120em).
Only use CSS module `@media` queries when the responsive change involves non-style-prop properties (e.g., `box-shadow`, `border`, `cursor` changes at breakpoints).
### Pattern 8: Dynamic Computed Styles → Inline Styles
Inline styles are allowed **only** for truly dynamic values computed at runtime (e.g., widths, positions, colors from data). Everything else must use Mantine style props or CSS modules.
**Before:**
```tsx
export const Bar = styled.div<{ width: number }>`
width: ${(props) => props.width}%;
`;
```
**After:**
```tsx
<Box style={{ width: `${width}%` }} />
```
### Pattern 9: Core CSS Utility Classes (Discouraged)
**Do NOT introduce new `CS` utility class usage.** Core CSS utilities (`CS` from `metabase/css/core/index.css`) are legacy and discouraged for new code. Prefer Mantine style props or CSS modules instead.
If existing code already uses `CS` classes and you're not migrating that specific code, leave them alone. But when migrating Emotion → Mantine, replace with the proper alternative:
| Instead of `CS.*` | Use |
| ------------------- | ------------------------------------------------------- |
| `CS.overflowHidden` | CSS module: `overflow: hidden` |
| `CS.cursorPointer` | CSS module: `cursor: pointer` |
| `CS.flex1` | Style prop: `flex={1}` |
| `CS.flexNoShrink` | Style prop: `flex="0 0 auto"` |
| `CS.textBrandHover` | CSS module: `&:hover { color: var(--mb-color-brand); }` |
### Pattern 10: Shared Emotion Styles Across Files → Shared CSS Module + cx()
When Emotion exports a shared `css` block (like `animationStyles`) imported by many files, create ONE CSS module with the shared keyframe and a reusable class, then compose via `cx()`.
**Before (Emotion — shared animation used by 13+ skeleton files):**
```tsx
// ChartSkeleton.styled.tsx — defines shared animation
const fadingKeyframes = keyframes`
0% { opacity: 0.0625; }
50% { opacity: 0.125; }
100% { opacity: 0.0625; }
`;
export const animationStyles = css`
opacity: 0.1;
animation: ${fadingKeyframes} 1.5s infinite;
`;
// AreaSkeleton.styled.tsx — consumes it
import { animationStyles } from ".../ChartSkeleton.styled";
export const SkeletonImage = styled.svg`
${animationStyles};
flex: 1 1 0;
margin-top: 1rem;
border-bottom: 1px solid currentColor;
`;
// BarSkeleton.styled.tsx — also consumes it
import { animationStyles } from ".../ChartSkeleton.styled";
export const SkeletonImage = styled.svg`
${animationStyles};
flex: 1 1 0;
margin-top: 1rem;
`;
```
**After — shared CSS module:**
```css
/* ChartSkeleton.module.css */
@keyframes fading {
0% {
opacity: 0.0625;
}
50% {
opacity: 0.125;
}
100% {
opacity: 0.0625;
}
}
.animated {
opacity: 0.1;
animation: fading 1.5s infinite;
}
```
**After — per-component CSS modules with own styles only:**
```css
/* AreaSkeleton.module.css */
.skeletonImage {
flex: 1 1 0;
margin-top: 1rem;
border-bottom: 1px solid currentColor;
}
```
```css
/* BarSkeleton.module.css */
.skeletonImage {
flex: 1 1 0;
margin-top: 1rem;
}
```
**After — component TSX uses cx() to compose:**
```tsx
// AreaSkeleton.tsx
import cx from "classnames";
import ChartSkeletonS from "../ChartSkeleton/ChartSkeleton.module.css";
import S from "./AreaSkeleton.module.css";
const AreaSkeleton = (): JSX.Element => {
return (
<svg
className={cx(ChartSkeletonS.animated, S.skeletonImage)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 371 113"
preserveAspectRatio="none"
>
<path d="..." fill="currentColor" />
</svg>
);
};
```
The shared module owns the `@keyframes` and the `.animated` class. Each consumer composes it with its own module class via `cx()`. No duplication of the keyframe definition.
## Import Template
```tsx
import cx from "classnames"; // only if needed for conditional classes
import { Box, Flex, Group, Stack, Text, rem } from "metabase/ui";
import S from "./ComponentName.module.css"; // only if CSS module is needed
```
## Checklist Before Finishing
- [ ] **MANTINE-FIRST CHECK**: Every CSS property that has a style prop equivalent (`c`, `bg`, `p`, `m`, `fw`, `fz`, `flex`, `gap`, `align`, `w`, `h`, etc.) is expressed as a style prop, NOT in a CSS module
- [ ] `styled.span`/`styled.p` with only color/weight/size are replaced with `Text component="span"` + style props, NOT CSS module classes
- [ ] `styled.div` with only layout props are replaced with `Flex`/`Stack`/`Group`/`Box` + style props, NOT CSS module classes
- [ ] Responsive spacing/sizing uses responsive style props (`mb={{ base: "lg", xl: "xl" }}`), NOT CSS module `@media` queries
- [ ] CSS modules are used ONLY for: pseudo-selectors, box-shadow, border, cursor, overflow, animation, transition, transform, or complex selectors
- [ ] All `@emotion/styled` and `@emotion/react` imports removed from migrated files
- [ ] The `.styled.tsx` file is deleted
- [ ] All imports of the deleted styled file are updated
- [ ] No static inline styles — `style={{ }}` used only for truly dynamic runtime values
- [ ] All colors use design tokens (`c="brand"`, `var(--mb-color-brand)`), not raw hex/rgb
- [ ] All spacing uses design tokens (`p="md"`, `var(--mantine-spacing-md)`), not arbitrary px values — hardcoded `rem`/`px` literals snapped to the nearest token (see mapping table above)
- [ ] All border-radius values use radius tokens (`var(--mantine-radius-md)`), not hardcoded `0.5rem`
- [ ] All font sizes use font-size tokens (`fz="md"`, `var(--mantine-font-size-md)`), not hardcoded `1rem`
- [ ] Layout uses Mantine components (`Flex`, `Stack`, `Group`, `Box`, `Text`) not raw divs/spans with CSS
- [ ] All CSS module class names use camelCase
- [ ] No cascading/descendant selectors targeting raw elements — use direct class assignment instead
- [ ] Component renders identically to the original (visually verify if possible)
## Visual Verification with Screenshots (Optional — Only When Explicitly Asked)
When the user explicitly asks to create before/after screenshots, use the Playwright MCP tools to capture them. This is NOT done by default — only when requested.
Screenshots must be taken in **both light and dark mode** to verify token usage is correct in both themes.
### Switching Color Scheme via API
Toggle between light and dark mode using the Metabase settings API. Use Playwright's `browser_evaluate` or `browser_navigate` with fetch:
```js
// Switch to dark mode
await fetch("/api/setting/color-scheme", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ value: "dark" }),
});
// Switch to light mode
await fetch("/api/setting/color-scheme", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ value: "light" }),
});
```
After switching, reload the page for the theme to take effect.
### Process
For each state (**after** = migrated code, **before** = stashed original), capture screenshots in both themes at both viewport widths. This produces **8 screenshots total**.
1. **Take "after" screenshots first** (migrated code is already in place):
- Navigate to the page that renders the migrated components (`browser_navigate`)
- Wait for content to load (`browser_wait_for` with key text, or `textGone: "Loading..."`)
- Close the sidebar if it overlaps content on narrow viewports (`browser_click` the toggle button)
- **Light mode** (set `color-scheme` to `"light"` via API, reload):
- Desktop (1280x900): `after-light-desktop.png`
- Narrow (640x900), close sidebar: `after-light-narrow.png`
- **Dark mode** (set `color-scheme` to `"dark"` via API, reload):
- Desktop (1280x900): `after-dark-desktop.png`
- Narrow (640x900), close sidebar: `after-dark-narrow.png`
2. **Stash changes to capture "before" screenshots**:
- Run `git stash --include-untracked` to temporarily revert to the Emotion version
- Repeat the same 4 screenshots with `before-` prefix:
- `before-light-desktop.png`, `before-light-narrow.png`
- `before-dark-desktop.png`, `before-dark-narrow.png`
- Run `git stash pop` to restore the migrated code
3. **Restore the user's original color scheme** (set back to `"auto"` or whatever it was before).
4. **Display all 8 screenshots** using the `Read` tool so the user can visually compare before/after in both themes at both viewport sizes.
### Key Details
- Save screenshots in the repo root (they will be untracked — user can delete after review)
- The dev server must be running (typically `http://localhost:3000`)
- Use `browser_snapshot` to find element refs when you need to click buttons (e.g., sidebar toggle)
- The greeting text on the home page changes randomly on each load — this is expected, not a regression
- If the page being tested is not the home page, navigate to the correct URL that exercises the migrated components
- Dark mode screenshots are critical for verifying that CSS variable tokens (`var(--mb-color-*)`) are used correctly — hardcoded colors will look wrong in dark mode

View File

@@ -1,76 +0,0 @@
---
name: serdes-workflow
description: Export content from a running Metabase instance, validate with checkers, edit YAML, and import back. Use when the user wants to export, import, or run the full serdes round-trip workflow.
---
# Serdes Workflow Skill
End-to-end workflow for exporting Metabase content as YAML, validating it, editing it, and importing it back.
## Prerequisites
All commands require the `:ee` alias since export, import, and checkers live in enterprise code.
You need `MB_DB_CONNECTION_URI` set to point at the target Metabase Postgres database when importing. The running dev instance's connection string is in the worktree environment (check the nREPL or startup output).
## Export
```bash
clojure -M:run:ee export /path/to/export-dir
```
Flags:
- `-c 123,456` - export only specific collection IDs
- `-f` - include field values
- `-e` - continue on error
This writes YAML files in the serdes directory layout:
```
databases/<db-name>/<db-name>.yaml
databases/<db-name>/schemas/<schema>/tables/<table>/<table>.yaml
databases/<db-name>/schemas/<schema>/tables/<table>/fields/<field>.yaml
collections/<eid>_<slug>/cards/<eid>_<slug>.yaml
```
## Validate
Always run both checkers after any modification. They skip full Metabase startup (~8s each).
### Structural checker (fast, schema-based)
```bash
clojure -M:run:ee --mode checker --checker structural --export /path/to/export-dir
```
Validates YAML shapes against Malli schemas. Catches typos, missing required fields, wrong types.
### Cards checker (query validation)
```bash
clojure -M:run:ee --mode checker --checker cards --export /path/to/export-dir
```
Validates that card queries resolve correctly against exported metadata (tables, fields, FKs). Uses MLv2 under the hood.
Additional cards checker flags:
- `--lenient` - fabricate metadata on demand (when no database schema files exist)
- `--manifest /path/to/manifest.yaml` - write manifest of all referenced entities (lenient mode only)
- `--output /path/to/results.edn` - write detailed results to file
## Edit YAML
See the **serdes-yaml-edit** skill for guidance on editing YAML files. The critical rule: **run both checkers after every edit before proceeding**.
## Import
```bash
MB_DB_CONNECTION_URI="postgres://user:pass@localhost:5432/metabase_dbname" \
clojure -M:run:ee import /path/to/export-dir -e
```
The `-e` flag continues on error. The `MB_DB_CONNECTION_URI` must point at the same Postgres database the running Metabase instance uses - without it, import defaults to H2 which is wrong.
## Full Demo Sequence
1. Export: `clojure -M:run:ee export /tmp/metabase-export`
2. Validate baseline: run both checkers
3. Edit YAML files (see serdes-yaml-edit skill)
4. Re-validate: run both checkers again
5. Import: `MB_DB_CONNECTION_URI=... clojure -M:run:ee import /tmp/metabase-export -e`
6. Verify in browser

View File

@@ -1,243 +0,0 @@
---
name: serdes-yaml-edit
description: Edit Metabase serdes YAML files (cards, dashboards, databases) with correct portable references and structural conventions. Use when modifying exported YAML content.
---
# Serdes YAML Edit Skill
## Golden Rule
**Run both checkers after every edit.** No exceptions. Do not batch multiple edits before validating.
```bash
clojure -M:run:ee --mode checker --checker structural --export /path/to/export-dir
clojure -M:run:ee --mode checker --checker cards --export /path/to/export-dir
```
If either checker fails, fix the issue before making further edits.
## Portable References
Serdes YAML uses portable references instead of integer IDs. This is the most important concept for editing.
### Database references
String name: `"Sample Database"`
### Table references
Array of `[database, schema, table]`:
```yaml
table_id:
- Sample Database
- PUBLIC
- ACCOUNTS
```
### Field references
Array of `[database, schema, table, field]`:
```yaml
id:
- Sample Database
- PUBLIC
- ACCOUNTS
- EMAIL
```
### Field refs in queries
```yaml
field_ref:
- field
- - Sample Database
- PUBLIC
- ACCOUNTS
- EMAIL
- null
```
The outer array is `[field, <field-path>, <options>]`. The options are usually `null`.
### Card references
Entity ID string (21 characters): `"Qk5TgsNx4ubXIUtsQmT8G"`
### Source table in queries
```yaml
dataset_query:
database: Sample Database
query:
source-table:
- Sample Database
- PUBLIC
- ACCOUNTS
type: query
```
## Safe Edits (low risk)
These rarely break validation:
- `name` - card/dashboard display name
- `description` - card/dashboard description
- `display` - visualization type (table, bar, line, pie, etc.)
- `visualization_settings` - chart configuration
- `archived` - true/false
- `collection_id` - move to a different collection (use entity_id of target collection, or null for root)
## Structural Edits (must match schema)
These must use valid portable refs and will be caught by the cards checker if wrong:
- `dataset_query` - the query definition
- `result_metadata` - column metadata (must match the query's output columns)
- `table_id` - must reference a table that exists in the export
- `database_id` - must reference a database that exists in the export
## Editing result_metadata
Each entry in `result_metadata` describes an output column. When changing a query's source table or fields, you must update result_metadata to match. Each field entry needs at minimum:
- `name` - column name (e.g., `EMAIL`)
- `base_type` - Metabase type (e.g., `type/Text`, `type/Integer`, `type/DateTime`)
- `display_name` - human-readable name
- `field_ref` - portable field reference
- `id` - portable field path
- `table_id` - portable table path
- `source` - usually `fields`
## Common Operations
### Rename a card
Change `name:` at the top level. Safe, no ref changes needed.
### Change a card's source table
Update all of these consistently:
1. `table_id` - top-level
2. `dataset_query.query.source-table` - in the query
3. `result_metadata` - every field entry's `id`, `field_ref`, and `table_id`
### Add a filter to a structured query
```yaml
dataset_query:
database: Sample Database
query:
source-table:
- Sample Database
- PUBLIC
- ORDERS
filter:
- ">"
- - field
- - Sample Database
- PUBLIC
- ORDERS
- TOTAL
- null
- 100
type: query
```
### Change visualization type
```yaml
display: bar # was: table
```
Valid types: `table`, `bar`, `line`, `pie`, `scalar`, `row`, `area`, `combo`, `scatter`, `funnel`, `map`, `pivot`, `progress`, `gauge`, `waterfall`
## Looking Up Valid References
The export directory IS the reference catalog. When you need to find the correct name for a database, table, or field, look it up directly from the export.
### List valid databases
```bash
ls databases/
```
Each entry is a database name (directory for serdes format, .yaml for concise format).
### List valid tables for a database
```bash
ls databases/<db-name>/schemas/<schema>/tables/
```
Example: `ls databases/Sample\ Database/schemas/PUBLIC/tables/` shows `ACCOUNTS`, `ORDERS`, `PRODUCTS`, etc.
### List valid fields for a table
```bash
ls databases/<db-name>/schemas/<schema>/tables/<table>/fields/
```
Example: `ls databases/Sample\ Database/schemas/PUBLIC/tables/PRODUCTS/fields/` shows `CATEGORY.yaml`, `TITLE.yaml`, `PRICE.yaml`, etc. The filename (minus `.yaml`) is the field name.
### Find card entity IDs
Card filenames encode the entity ID: `<entity-id>_<slug>.yaml`. The entity ID is the part before the first underscore (21 characters).
## Self-Healing Loop
When a checker reports errors, follow this loop:
1. **Read the error message** - it tells you what's wrong and often suggests the fix
2. **Look up the correct value** from the export directory (see "Looking Up Valid References" above)
3. **Fix the YAML**
4. **Re-run both checkers**
5. **Repeat until clean**
Do not guess at fixes. Always look up the correct value from the export.
## Understanding Checker Errors
### Structural checker errors
The structural checker validates YAML shape against Malli schemas. Common errors:
**Missing required key with typo suggestion:**
```
Missing required key 'name' - found 'nameee' which may be a typo
```
Fix: rename the typo'd key back to the correct name. The checker tells you what it expected and what it found.
**Wrong type:**
```
'archived' should be a boolean, got: "yes"
```
Fix: use `true`/`false`, not strings.
**Unknown key:**
```
Unknown key 'foobar' in card
```
Fix: remove the key, or check if it's a typo of a known key.
### Cards checker errors
The cards checker validates that queries resolve against exported metadata. Error types:
**UNRESOLVED REFERENCES:**
```
UNRESOLVED REFERENCES:
- field: Sample Database.PUBLIC.PRODUCTS.CATEGORYYY
```
The dotted path tells you exactly which reference failed. Look up the correct value:
- Last segment is the field name - check `databases/.../tables/<table>/fields/`
- Third segment is the table name - check `databases/.../schemas/<schema>/tables/`
- First segment is the database name - check `databases/`
**ERROR (query construction failed):**
```
ERROR: Error creating query from legacy query: Invalid output: ...
```
Usually means the query structure is malformed. This often follows unresolved references - fix the refs first, then re-check.
**ERROR (nil name):**
```
ERROR: Invalid output: {:name ["should be a string, got: nil"]}
```
The card is missing its `name` field (likely a structural issue that also affects the cards checker).
### Exit codes
- Exit 0: all checks passed
- Exit 1: one or more failures
## File Naming Convention
Card files: `collections/<collection-path>/cards/<entity-id>_<slug>.yaml`
The filename slug should match the card name (lowercase, underscored). If you rename a card, consider renaming the file to match, though import works on entity_id not filename.
## What NOT to Edit
- `entity_id` - this is the identity of the object, changing it creates a new object on import
- `serdes/meta` - internal serdes metadata, leave it alone
- `created_at` - timestamp, no reason to change
- `creator_id` - email reference, leave it
- `metabase_version` - informational, leave it

View File

@@ -52,7 +52,6 @@
:redundant-ignore {:level :off} ; lots of false positives, see https://github.com/clj-kondo/clj-kondo/issues/2414
:unexpected-recur {:level :off} ; TODO (cam): I think we just need to tell it how to handle MBQL match and we can enable this?
:redundant-primitive-coercion {:level :off} ; there are many defensive boolean coercions, kondo looks too much into it
;;
;; globally-enabled custom linters
@@ -69,7 +68,6 @@
:metabase/modules {:level :warning} ; more config in `.clj-kondo/config/modules/config.edn`
:metabase/namespace-name {:level :warning}
:metabase/no-jsqlparser-imports {:level :warning}
:metabase/toucan-model-ns {:level :warning}
:metabase/require-shape-checker {:level :warning}
:metabase/tests-must-live-in-known-modules {:level :warning}
:metabase/validate-defendpoint-route-uses-kebab-case {:level :warning}
@@ -178,16 +176,13 @@
clojure.core.async/to-chan!
clojure.core.async/to-chan!!
metabase.channel.core/send!
metabase.warehouses.models.database/assert-not-h2!
metabase.driver.sql-jdbc.execute/execute-prepared-statement!
metabase.notification.models/create-notification!
metabase.oauth-server.core/reset-provider!
metabase.pulse.send/send-pulse!
metabase-enterprise.transforms-python.python-runner-test/execute!
;; Workspace isolation functions are safe in parallel tests because they operate on
;; isolated, test-specific workspaces and databases that don't affect other tests
metabase-enterprise.workspaces.isolation/destroy-workspace-isolation!
metabase.lib-metric.ast.plan/validate-arithmetic-ast!
metabase-enterprise.workspaces.isolation/ensure-database-isolation!
metabase-enterprise.workspaces.test-util/create-ready-ws!
;; Safe in tests, where we typically use a private, temporary index per thread.
@@ -474,15 +469,14 @@
metabase.premium-features.settings/define-premium-feature clojure.core/def
metabase.pulse.dashboard-subscription-test/with-dashboard-sub-for-card clojure.core/let
metabase.pulse.send-test/with-pulse-for-card clojure.core/let
metabase.query-processor.pipeline-queries-test/mbql5-query clojure.core/->
metabase.query-processor.pipeline-queries-test/run-mbql5-query clojure.core/->
metabase.query-processor.pipeline-queries-test/pmbql-query clojure.core/->
metabase.query-processor.pipeline-queries-test/run-pmbql-query clojure.core/->
metabase.query-processor.error-type/deferror clojure.core/def
metabase.query-processor.middleware.cache.impl/with-reducible-deserialized-results clojure.core/let
metabase.query-processor.middleware.process-userland-query-test/with-query-execution! clojure.core/let
metabase.query-processor.setup/with-qp-setup clojure.core/let
metabase.search.api-test/do-test-users clojure.core/let
metabase.settings.core/define-multi-setting clojure.core/def
metabase.api-scope.core/defscope clj-kondo.lint-as/def-catch-all
metabase.settings.core/defsetting clj-kondo.lint-as/def-catch-all
metabase.settings.models.setting.multi-setting/define-multi-setting clojure.core/def
metabase.settings.models.setting/defsetting clj-kondo.lint-as/def-catch-all
@@ -493,7 +487,6 @@
metabase.task.impl/defjob clojure.core/defn
metabase.test.data.interface/defdataset clojure.core/def
metabase.test.data.interface/defdataset-edn clojure.core/def
metabase.test.embedded-postgres.core/with-system clojure.core/let
metabase.test.gentest/defgentest clojure.test/deftest
metabase.test/defdataset clojure.core/def
metabase.test/let-url clojure.core/let
@@ -559,7 +552,6 @@
metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings
metabase-enterprise.dependencies.api-test/with-dependents-test! hooks.common/with-two-bindings
metabase-enterprise.metabot-v3.tools.dependencies-test/with-dependent-transforms! hooks.common/with-two-bindings
metabase-enterprise.metabot.tools.dependencies-test/with-dependent-transforms! hooks.common/with-two-bindings
metabase-enterprise.semantic-search.db.connection/with-migrate-tx hooks.common/let-one-with-optional-value
metabase-enterprise.serialization.api-test/with-serialization-test-data! hooks.common/with-three-bindings
metabase-enterprise.serialization.test-util/with-temp-dpc hooks.toucan2.tools.with-temp/with-temp
@@ -594,8 +586,6 @@
metabase.lib.schema.mbql-clause/define-catn-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
metabase.lib.schema.mbql-clause/define-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
metabase.lib.schema.mbql-clause/define-tuple-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
methodical.core/defmethod hooks.metabase.toucan.table-name/lint-defmethod
methodical.macros/defmethod hooks.metabase.toucan.table-name/lint-defmethod
metabase.lib.test-util.macros/$ids hooks.metabase.test.data/$ids
metabase.lib.test-util.macros/mbql-query hooks.metabase.test.data/mbql-query
metabase.lib.test-util.macros/mbql-5-query hooks.metabase.test.data/mbql-query
@@ -803,7 +793,7 @@
:private-call {:level :off}
:metabase/defsetting-must-specify-export {:level :off}
:metabase/defsetting-namespace {:level :off}
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning} ; more config in `.clj-kondo/config/test-helpers/config.edn`
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning} ; more config in `.clj-kondo/config/test-helpers/config.edn
:discouraged-var
{metabase.driver/database-supports? {:level :off}

File diff suppressed because it is too large Load Diff

View File

@@ -110,7 +110,6 @@
metabase.settings.models.setting-test/test-user-local-allowed-setting!
metabase.settings.models.setting-test/test-user-local-only-setting!
metabase.settings.models.setting.cache/restore-cache!
metabase.settings.models.setting/log-deprecated-env-var-usage!
metabase.settings.models.setting/set!
metabase.settings.models.setting/validate-settings-formatting!
metabase.setup.core/create-token!

View File

@@ -1,43 +0,0 @@
(ns hooks.metabase.toucan.table-name
"Lint that toucan2 model namespaces (those defining `t2/table-name`) live
under `metabase[-enterprise]/<module>/models[/…]`.
This wraps the methodical `defmethod` hook: we delegate to
`hooks.methodical.macros/defmethod` for the actual analysis (so arg bindings
etc. still work), and additionally check the file location when the
multimethod being defined is `t2/table-name`."
(:require
[clj-kondo.hooks-api :as hooks]))
(defn- models-file? [filename]
(boolean
(and filename
(re-find #"/(?:metabase|metabase_enterprise)/[^/]+/models(?:/|\.clj[cs]?$)"
filename))))
(defn- table-name-dispatch? [sym]
(and (symbol? sym)
(= (name sym) "table-name")
(#{"t2" "toucan2.core"} (namespace sym))))
(defn lint-defmethod
"Hook registered at the project level on `methodical.core/defmethod` (and
`methodical.macros/defmethod`). Performs the table-name check, then
delegates to the vendored methodical hook so kondo's normal analysis of the
defmethod body still runs."
[{:keys [filename node] :as input}]
(let [[_defmethod multimethod & _args] (:children node)
dispatch (when multimethod
(try (hooks/sexpr multimethod) (catch Exception _ nil)))]
(when (and (table-name-dispatch? dispatch)
(not (models-file? filename)))
(hooks/reg-finding!
(assoc (meta multimethod)
:message (format "Toucan2 `table-name` defmethods must live under `<module>/models[/…]` (got %s)"
filename)
:type :metabase/toucan-model-ns))))
;; Delegate to the vendored methodical defmethod hook so kondo's normal
;; analysis of the defmethod body still runs. Resolved lazily because
;; `hooks.methodical.macros` isn't on the classpath during `.clj-kondo/test`
;; unit-test runs.
((requiring-resolve 'hooks.methodical.macros/defmethod) input))

View File

@@ -1,46 +0,0 @@
(ns hooks.metabase.toucan.table-name-test
(:require
[clj-kondo.hooks-api :as hooks]
[clj-kondo.impl.utils]
[clojure.test :refer :all]
[hooks.metabase.toucan.table-name :as toucan.table-name]))
;; `hooks.methodical.macros` isn't on the classpath under `.clj-kondo/test`,
;; so stub it out — `lint-defmethod` delegates to it via `requiring-resolve`.
;; If it ever becomes available, warn so we remember to delete this stub.
(if (find-ns 'hooks.methodical.macros)
(println (str "WARNING: hooks.methodical.macros is now on the test classpath; "
"the stub in " *ns* " is no longer needed and can be removed."))
(do (create-ns 'hooks.methodical.macros)
(intern 'hooks.methodical.macros 'defmethod (fn [input] input))))
(defn- lint-defmethod [form filename]
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/toucan-model-ns {:level :warning}}}
:ignores (atom nil)
:findings (atom [])
:namespaces (atom {})}]
(toucan.table-name/lint-defmethod {:node (hooks/parse-string (pr-str form))
:filename filename})
@(:findings clj-kondo.impl.utils/*ctx*)))
(deftest ^:parallel table-name-must-live-in-models-namespace-test
(testing "defmethod t2/table-name outside a models namespace is flagged"
(is (=? [{:type :metabase/toucan-model-ns}]
(lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/core.clj"))))
(testing "defmethod t2/table-name inside <module>/models.clj is allowed"
(is (empty? (lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/models.clj"))))
(testing "defmethod t2/table-name inside <module>/models/<model>.clj is allowed"
(is (empty? (lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/models/bar.clj"))))
(testing "enterprise modules are also allowed"
(is (empty? (lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/enterprise/backend/src/metabase_enterprise/foo/models.clj"))))
(testing "toucan2.core/table-name is also recognized"
(is (=? [{:type :metabase/toucan-model-ns}]
(lint-defmethod '(defmethod toucan2.core/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/core.clj"))))
(testing "unrelated defmethods are ignored"
(is (empty? (lint-defmethod '(defmethod t2/primary-keys :model/Foo [_] [:id])
"/repo/src/metabase/foo/core.clj")))))

View File

@@ -188,8 +188,8 @@
metabase.query-processor.error-type/deferror [[:block 1]]
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
metabase.query-processor.pipeline-queries-test/mbql5-query [[:block 1]]
metabase.query-processor.pipeline-queries-test/run-mbql5-query [[:block 1]]
metabase.query-processor.pipeline-queries-test/pmbql-query [[:block 1]]
metabase.query-processor.pipeline-queries-test/run-pmbql-query [[:block 1]]
metabase.query-processor.store/cached [[:block 1]]
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
metabase.query-processor.streaming/streaming-response [[:block 1]]

View File

@@ -1,10 +0,0 @@
# worktrunk project config — https://worktrunk.dev
# Hooks run when switching, creating, merging, or removing worktrees.
# Setup tasks for newly-created worktrees. Run once, blocking, in the
# new worktree's cwd before post-start background tasks kick off.
[pre-start]
# Trust mise config so it loads the project's tool versions without prompting.
trust-mise = "mise trust"
# Install JS deps so husky git hooks get wired up in the new worktree.
bun-install = "bun install"

View File

@@ -31,25 +31,24 @@ runs:
- name: Prepare frontend
uses: ./.github/actions/prepare-frontend
- name: Download current uberjar
uses: ./.github/actions/fetch-artifact
uses: actions/download-artifact@v4
with:
commit: ${{ github.event.pull_request.head.sha || github.sha }}
edition: ${{ inputs.edition }}
extract-path: temp/current-uberjar
name: metabase-${{ inputs.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
path: temp/current-uberjar
- name: Extract embedding-sdk bundles from current uberjar
shell: bash
run: |
# Try new layout first (app/embedding-sdk/legacy/), fall back to old layout (app/embedding-sdk.js)
unzip -j temp/current-uberjar/metabase.jar \
unzip -j temp/current-uberjar/target/uberjar/metabase.jar \
"frontend_client/app/embedding-sdk/legacy/embedding-sdk.js" \
-d temp/current-bundle 2>/dev/null || \
unzip -j temp/current-uberjar/metabase.jar \
unzip -j temp/current-uberjar/target/uberjar/metabase.jar \
"frontend_client/app/embedding-sdk.js" \
-d temp/current-bundle
# Extract chunks directory if it exists (new layout only)
mkdir -p temp/current-chunks
unzip -j temp/current-uberjar/metabase.jar \
unzip -j temp/current-uberjar/target/uberjar/metabase.jar \
"frontend_client/app/embedding-sdk/chunks/*" \
-d temp/current-chunks 2>/dev/null || true
- name: Checkout base ref

View File

@@ -7,7 +7,9 @@ inputs:
output-name:
required: true
default: test
role-to-assume:
aws-access-key-id:
required: true
aws-secret-access-key:
required: true
aws-region:
required: true
@@ -28,7 +30,8 @@ runs:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ inputs.role-to-assume }}
aws-access-key-id: ${{ inputs.aws-access-key-id }}
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
aws-region: ${{ inputs.aws-region }}
- name: zip test results
env:
@@ -47,18 +50,13 @@ runs:
aws s3 cp ${OUTPUT_FILE}.zip s3://$BUCKET/$DATE/$GITHUB_RUN_ID/$GITHUB_RUN_ATTEMPT/
- name: Upload results to Trunk
# Important!
# 1. This step must always run because it controls job's exit code.
# 2. There's no point in running this outside the organization
if: always() && github.repository_owner == 'metabase'
if: always() # Important! This step must always run because it controls job's exit code.
uses: trunk-io/analytics-uploader@main
env:
TRUNK_REPO_URL: git@github.com:metabase/metabase.git
with:
junit-paths: ${{ inputs.input-path }}
variant: ${{ inputs.variant || inputs.output-name }}
org-slug: metabase
dry-run: ${{ (github.event_name == 'pull_request' && github.event.pull_request.draft == true) || github.repository != 'metabase/metabase' }} # quarantine, but do not upload results
dry-run: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == true }} # quarantine, but do not upload results
token: ${{ inputs.trunk-api-token }}
use-cache: true
previous-step-outcome: ${{ inputs.previous-step-outcome }}

View File

@@ -200,10 +200,6 @@ cljs:
- ".github/workflows/cljs.yml"
- "shadow-cljs.edn"
serialization_baseline:
- ".github/workflows/serialization.yml"
- "test_resources/serialization_baseline/**"
openapi:
- ".github/workflows/openapi.yml"
- "src/metabase/api/**"

View File

@@ -52,21 +52,17 @@ function buildMatrix(options, inputSpecs, inputChunks) {
);
}
const regularTests = Array.from({ length: regularChunks }, (_, index) => {
const paddedIndex = String(index + 1).padStart(2, "0");
return {
name: `e2e-group-${paddedIndex}`,
// works when specs less than 5, otherwise seems all chunks will contain
// same specs
...(!isDefaultSpecPattern && {
specs: inputSpecs
.split(",")
.slice(SPECS_PER_CHUNK * index, SPECS_PER_CHUNK * (index + 1))
.join(","),
}),
};
});
const regularTests = new Array(regularChunks).fill(1).map((files, index) => ({
name: `e2e-group-${index + 1}`,
// works when specs less than 5, otherwise seems all chunks will contain
// same specs
...(!isDefaultSpecPattern && {
specs: inputSpecs
.split(",")
.slice(SPECS_PER_CHUNK * index, SPECS_PER_CHUNK * (index + 1))
.join(","),
}),
}));
const testSets = isDefaultSpecPattern
? regularTests.concat(specialTestConfigs)

View File

@@ -3,7 +3,7 @@ const { buildMatrix } = require("./build-e2e-matrix");
describe("buildMatrix", () => {
const options = {
java: "11",
defaultRunner: "${{ vars.DEFAULT_RUNNER_KEY }}",
defaultRunner: "ubuntu-22.04",
};
const testBuildMatrix = (entry, chunks) =>
buildMatrix(options, entry, chunks);
@@ -34,9 +34,7 @@ describe("buildMatrix", () => {
);
regularTests.forEach((test, index) => {
expect(test.name).toBe(
`e2e-group-${String(index + 1).padStart(2, "0")}`,
);
expect(test.name).toBe(`e2e-group-${index + 1}`);
});
});
@@ -94,6 +92,6 @@ describe("buildMatrix", () => {
const result = testBuildMatrix("test1.cy.spec.js,test2.cy.spec.js", 50);
expect(result.config.length).toBe(1);
expect(result.config[0].name).toBe("e2e-group-01");
expect(result.config[0].name).toBe("e2e-group-1");
});
});

View File

@@ -19,7 +19,7 @@ while [[ $# -gt 0 ]]; do
done
echo "Cloning python-runner repository..."
git clone --depth 1 https://${METABASE_AUTOMATION_USER_TOKEN}@github.com/metabase/python-runner-container.git python-runner
git clone --depth 1 -b extensible-resource-schema https://${METABASE_AUTOMATION_USER_TOKEN}@github.com/metabase/python-runner-container.git python-runner
if [ "$SKIP_START" = false ]; then
echo "Starting python-runner..."

14
.github/team.json vendored
View File

@@ -1,14 +1,13 @@
{
"teams": [
{
"name": "Querying Platform",
"label": ".Team/QueryingPlatform",
"name": "Querying",
"label": ".Team/Querying",
"members": [
"bshepherdson",
"metamben",
"rileythomp",
"camsaul",
"technomancy"
"camsual"
]
},
{
@@ -92,8 +91,7 @@
"deniskaber",
"alxnddr",
"andsalves",
"kevinkelleher12",
"AndreyChernykh"
"kevinkelleher12"
]
},
{
@@ -108,9 +106,7 @@
"kamilmielnik",
"stasgavrylov",
"uladzimirdev",
"egorgrushin",
"metatyb",
"olehk-mb"
"egorgrushin"
]
},
{

View File

@@ -1,21 +0,0 @@
name: AI Benchmark
# Runs the ai-benchmark E2E test suite against a Metabase instance.
#
# NOTE: This is a stub. The real workflow — which builds the benchmark Docker
# image, boots Metabase + Postgres with a preloaded data dump, runs the
# benchmark suite, and uploads results as artifacts — is being developed on a
# feature branch. This stub exists on master so that the "Run workflow" button
# is available in the GitHub Actions UI, which lets us dispatch the real
# workflow against feature branches during development.
on:
workflow_dispatch:
jobs:
placeholder:
name: Placeholder
runs-on: ubuntu-latest
steps:
- name: Stub
run: echo "ai-benchmark workflow stub — real implementation pending"

View File

@@ -14,7 +14,7 @@ concurrency:
jobs:
be-tests-mariadb:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 40
strategy:
fail-fast: false
@@ -22,12 +22,12 @@ jobs:
version:
- name: MariaDB 10.6
junit-name: be-tests-mariadb-10-6-ee
image: metabase/mariadb:10.6 # https://github.com/metabase/metabase-mariadb-docker-image
image: circleci/mariadb:10.6
env:
enable-ssl-tests: 'false'
- name: MariaDB Latest
junit-name: be-tests-mariadb-latest-ee
image: mariadb:latest
image: circleci/mariadb:latest
env:
enable-ssl-tests: 'false'
job:
@@ -57,8 +57,8 @@ jobs:
ports:
- "3306:3306"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_DATABASE: mb_test
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: circle_test
env:
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
@@ -81,7 +81,7 @@ jobs:
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
#
@@ -120,22 +120,23 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
be-tests-mysql:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
version:
- name: MySQL 8.0
junit-name: be-tests-mysql-8-0-ee
image: mysql:8.0
- name: MySQL 8.4
junit-name: be-tests-mysql-8-4-ee
image: cimg/mysql:8.4
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
@@ -173,7 +174,7 @@ jobs:
- "3306:3306"
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: mb_test
MYSQL_DATABASE: circle_test
env:
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
@@ -196,7 +197,7 @@ jobs:
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
#
@@ -243,14 +244,15 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
be-tests-postgres:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 40
strategy:
fail-fast: false
@@ -353,7 +355,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -363,7 +366,7 @@ jobs:
- be-tests-mariadb
- be-tests-mysql
- be-tests-postgres
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
name: app-db-tests-result
if: always() && !cancelled()

View File

@@ -7,49 +7,32 @@ on:
description: 'Athena JDBC driver version to publish'
required: true
type: string
force:
description: 'Re-deploy even if version already exists'
required: false
default: false
type: boolean
permissions:
id-token: write
contents: read
jobs:
deploy:
name: Publish Athena JDBC Driver
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out the code
uses: actions/checkout@v4
- name: Set up Babashka
uses: turtlequeue/setup-babashka@2d4df498c7a578b4f3e906283f9af913590bdec7 # v1.7.0
uses: turtlequeue/setup-babashka@v1.7.0
with:
babashka-version: 1.12.197
- name: Set up AWS CLI
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_MAVEN_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_S3_MAVEN_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_MAVEN_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- name: Deploy Athena JDBC driver
run: ./bin/athena.bb "${VERSION}" --deploy ${{ github.event.inputs.force == 'true' && '--force' || '' }}
env:
VERSION: ${{ github.event.inputs.version }}
run: ./bin/athena.bb ${{ github.event.inputs.version }} --deploy
- name: Verify deployment
env:
VERSION: ${{ github.event.inputs.version }}
run: |
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc/"${VERSION}"/
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc-thin/"${VERSION}"/
echo "Verifying thin jar pom exists..."
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc-thin/"${VERSION}"/athena-jdbc-thin-"${VERSION}".pom
echo "Verifying athena-streaming..."
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-streaming/2.0/
echo "Athena JDBC driver version ${VERSION} has been deployed (uber-jar, thin jar, and athena-streaming)"
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc/${{ github.event.inputs.version }}/
echo "Athena JDBC driver version ${{ github.event.inputs.version }} has been deployed"

View File

@@ -7,10 +7,10 @@ on:
jobs:
update_pr:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
steps:
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0
- uses: tibdex/github-app-token@v2.1.0
id: generate-token
with:
app_id: ${{ secrets.METABASE_BOT_APP_ID }}

View File

@@ -20,7 +20,7 @@ jobs:
github.event.label.name == 'triple-backport'
)
)
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:

View File

@@ -17,7 +17,7 @@ on:
jobs:
setup-oss:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
outputs:
modules: ${{ steps.get-modules.outputs.modules }}
steps:
@@ -31,7 +31,7 @@ jobs:
needs: setup-oss
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
@@ -57,14 +57,14 @@ jobs:
- name: Upload coverage to codecov.io
if: always()
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./target/coverage/codecov.json
flags: back-end
setup-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
outputs:
modules: ${{ steps.get-modules.outputs.modules }}
steps:
@@ -78,7 +78,7 @@ jobs:
needs: setup-ee
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
@@ -104,7 +104,7 @@ jobs:
- name: Upload coverage to codecov.io
if: always()
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./target/coverage/codecov.json

View File

@@ -14,7 +14,7 @@ concurrency:
jobs:
be-linter-clj-kondo:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
name: Clj-Kondo
steps:
@@ -26,7 +26,7 @@ jobs:
be-linter-eastwood:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
name: Eastwood
steps:
@@ -37,20 +37,20 @@ jobs:
name: Run Eastwood linter
be-linter-eastwood-test:
if: ${{ !inputs.skip && github.ref_name == 'master' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
if: ${{ !inputs.skip }}
runs-on: ubuntu-22.04
timeout-minutes: 30
name: Eastwood (tests only)
name: Eastwood (test)
steps:
- uses: actions/checkout@v4
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- run: clojure -X:dev:ee:ee-dev:drivers:drivers-dev:test:eastwood/test
name: Run Eastwood linter on our backend tests
name: Run Eastwood linter on tests
be-tests:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 40
strategy:
fail-fast: false
@@ -138,13 +138,14 @@ jobs:
input-path: ./target/junit/
output-name: be-tests-java-${{ matrix.java-version }}-${{ matrix.job.edition }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-java-tests.outcome }}
- name: Publish Test Report (JUnit)
uses: dorny/test-reporter@3eeb9fc888e82e8be2fb356bbeec2750231672bc # v1
uses: dorny/test-reporter@v1
if: failure()
with:
path: "target/junit/**/*_test.xml"
@@ -166,7 +167,7 @@ jobs:
# as `:dev` dependencies). See #27009 for more context.
be-check:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
strategy:
matrix:
@@ -182,7 +183,7 @@ jobs:
be-cljfmt:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
name: Cljfmt
steps:
@@ -201,7 +202,7 @@ jobs:
- be-check
- be-tests
if: always() && !cancelled()
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
env:
needs: ${{ toJson(needs) }}

View File

@@ -10,10 +10,10 @@ on:
jobs:
backport-reminder:
name: Decide whether to backport or not
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: jesusvasquez333/verify-pr-label-action@657d111bbbe13e22bbd55870f1813c699bde1401 # v1.4.0
- uses: jesusvasquez333/verify-pr-label-action@v1.4.0
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'
valid-labels: 'no-backport, backport, double-backport, triple-backport'

View File

@@ -5,7 +5,7 @@ on:
jobs:
check-source-branch:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
steps:
- name: Block master branch PRs
if: github.head_ref == 'master'

View File

@@ -14,7 +14,7 @@ on:
jobs:
start-message:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
@@ -45,7 +45,7 @@ jobs:
}).catch(console.error);
trigger-milestone-check:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
needs: start-message
steps:
@@ -66,7 +66,7 @@ jobs:
check-version:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
ee: ${{ fromJson(steps.canonical_version.outputs.result).ee }}
@@ -118,7 +118,7 @@ jobs:
return versions;
check-commit:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- name: Check out the code to verify the release branch
@@ -150,7 +150,7 @@ jobs:
git checkout -
get-build-requirements:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
@@ -185,7 +185,7 @@ jobs:
build-uberjar-for-release:
needs: [check-version, check-commit, get-build-requirements]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 50
strategy:
matrix:

View File

@@ -10,7 +10,7 @@ on:
jobs:
test-build-scripts:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Prepare back-end environment

View File

@@ -39,8 +39,7 @@ concurrency:
jobs:
get-cache-keys:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Get cache keys
@@ -53,7 +52,7 @@ jobs:
cypress-cache-key: ${{ steps.get-cache-keys.outputs.cypress-cache-key }}
generate-caches:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
needs: get-cache-keys
env:
@@ -108,6 +107,11 @@ jobs:
bun run cypress verify
shell: bash
# Run Prettier to populate its cache (stored in node_modules/.cache/prettier)
# so it gets saved as part of the node_modules cache below
- name: Run Prettier to populate cache
run: bun run lint-prettier-pure
- name: Update node_modules cache
uses: ./.github/actions/update-cache
with:

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-backport-script:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

View File

@@ -15,7 +15,7 @@ on:
jobs:
set-milestone:
name: Automatically set milestone
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
env:
isNewReleaseBranch: ${{ github.event_name == 'workflow_dispatch' }}

View File

@@ -14,14 +14,14 @@ concurrency:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
frontend_all: ${{ steps.changes.outputs.frontend_all }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -30,7 +30,7 @@ jobs:
fe-chromatic:
needs: files-changed
if: contains(github.event.pull_request.labels.*.name, 'chromatic') || (github.ref_name == 'master' && needs.files-changed.outputs.frontend_all == 'true')
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -43,7 +43,7 @@ jobs:
- name: Compile CLJS
run: NODE_ENV=development bun run build-pure:cljs
- name: Publish to Chromatic
uses: chromaui/action@c03d144dc72e609acbb960489fdd1e1a4a23b34f # v10.2.0
uses: chromaui/action@v10.2.0
env:
PUBLISH_CHROMATIC: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
STORYBOOK_BUILD_TIMEOUT: 900000

View File

@@ -0,0 +1,360 @@
name: "Claude Code Bug Fix"
on:
issues:
types:
- labeled
pull_request_review:
types:
- submitted
workflow_dispatch:
inputs:
issue_number:
description: "Issue number to fix"
required: true
type: string
override_employee_check:
description: "Override employee check for issue creator"
required: false
default: false
type: boolean
jobs:
auto-fix-bug:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
if: |
(github.event_name == 'issues' && github.event.label.name == 'Claude Bug Fix' && github.event.issue.state == 'open') ||
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && startsWith(github.event.pull_request.head.ref, 'claude-fix/'))
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get issue data and check employee status
id: get_issue
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { getIssueData } = require('${{ github.workspace }}/.github/scripts/get-issue-data.js');
try {
const result = await getIssueData({
context,
github,
manualIssueNumber: '${{ github.event.inputs.issue_number }}'
});
const { issueNumber, issueData, employeeComments, prReviews, triggerType, isIssueAuthorEmployee } = result;
// Check employee status and fail if not authorized
if (!result.isIssueAuthorEmployee && '${{ github.event.inputs.override_employee_check }}' !== 'true') {
core.setFailed(`User ${result.issueData.user.login} is not a Metabase employee. Skipping bug fix automation.`);
}
core.setOutput('issue_number', issueNumber);
core.setOutput('issue_data', JSON.stringify(issueData));
core.setOutput('issue_comments', JSON.stringify(employeeComments));
core.setOutput('pr_reviews', JSON.stringify(prReviews));
core.setOutput('trigger_type', triggerType);
} catch (error) {
core.setFailed(error.message);
}
- name: Find linked Linear issue
id: find_linear
continue-on-error: true
uses: actions/github-script@v7
env:
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
ISSUE_NUMBER: ${{ steps.get_issue.outputs.issue_number }}
with:
script: |
const { findLinearIssue } = require('${{ github.workspace }}/.github/scripts/find-linear-issue.js');
const githubIssueUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/issues/${process.env.ISSUE_NUMBER}`;
const result = await findLinearIssue({
githubIssueUrl,
linearApiKey: process.env.LINEAR_API_KEY
});
if (result.success) {
console.log(`Found Linear issue: ${result.issue}`);
core.setOutput('linear_issue', result.issue);
} else {
console.log(`Could not find Linear issue: ${result.message}`);
}
- name: Set up git configuration
run: |
git config --global user.name "Claude Code[bot]"
git config --global user.email "noreply@anthropic.com"
- name: Prepare issue context and bug fix prompt
id: prepare_prompt
uses: actions/github-script@v7
env:
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
ISSUE_COMMENTS: ${{ steps.get_issue.outputs.issue_comments }}
PR_REVIEWS: ${{ steps.get_issue.outputs.pr_reviews }}
TRIGGER_TYPE: ${{ steps.get_issue.outputs.trigger_type }}
with:
script: |
const issueData = JSON.parse(process.env.ISSUE_DATA);
const issueComments = JSON.parse(process.env.ISSUE_COMMENTS);
const prReviews = JSON.parse(process.env.PR_REVIEWS);
const triggerType = process.env.TRIGGER_TYPE;
const issueNumber = '${{ steps.get_issue.outputs.issue_number }}';
// Convert issue title to kebab case and create branch name
const issueTitle = issueData.title || 'untitled';
const kebabTitle = issueTitle
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-+/g, '-')
.substring(0, 50);
const branchName = `claude-fix/issue-${issueNumber}-${kebabTitle}`;
// Format data for bash environment
const formattedComments = issueComments.length > 0
? issueComments.map(comment =>
`**Comment by @${comment.user.login} on ${comment.created_at}:**\n${comment.body}`
).join('\n\n---\n\n')
: '';
const formattedReviews = prReviews.length > 0
? prReviews.map(review =>
`**Review by @${review.user.login} on ${review.submitted_at} (Changes Requested):**\n${review.body || 'No review comment provided'}`
).join('\n\n---\n\n')
: '';
core.exportVariable('BRANCH_NAME', branchName);
core.exportVariable('ISSUE_NUMBER', issueNumber);
core.exportVariable('ISSUE_TITLE', issueData.title);
core.exportVariable('ISSUE_AUTHOR', issueData.user.login);
core.exportVariable('ISSUE_CREATED', issueData.created_at);
core.exportVariable('ISSUE_BODY', issueData.body || 'No description provided');
core.exportVariable('ISSUE_COMMENTS_FORMATTED', formattedComments);
core.exportVariable('PR_REVIEWS_FORMATTED', formattedReviews);
core.exportVariable('TRIGGER_TYPE', triggerType);
- name: Generate bug fix prompt
run: |
cat << 'EOF' >> $GITHUB_ENV
BUG_FIX_PROMPT<<PROMPT_END
You are helping to fix a bug reported in GitHub issue #${{ env.ISSUE_NUMBER }}.
## Bug Report Details
**Title:** ${{ env.ISSUE_TITLE }}
**Reporter:** ${{ env.ISSUE_AUTHOR }}
**Created:** ${{ env.ISSUE_CREATED }}
**Description:**
${{ env.ISSUE_BODY }}
**Comments:**
${{ env.ISSUE_COMMENTS_FORMATTED }}
EOF
# Add review feedback section only if this is a review trigger
if [ "${{ env.TRIGGER_TYPE }}" = "pull_request_review" ]; then
cat << 'EOF' >> $GITHUB_ENV
## Previous Fix Attempt - Review Feedback (MUST ADDRESS)
Your previous automated fix attempt received the following feedback that needs to be addressed:
${{ env.PR_REVIEWS_FORMATTED }}
EOF
fi
# Add the rest of the prompt (always the same)
cat << 'EOF' >> $GITHUB_ENV
## Instructions
1. **Analyze the issue description and comments** - Understand what the issue is describing
a. Pay particular attention to reproduction steps and if there's an agreed upon fix.
2. **Search the codebase** - Use Grep and Glob tools to find relevant files
3. **Implement a fix** - Use the appropriate write skill based on the file types:
- Use clojure-write for Clojure/ClojureScript files
- Use typescript-write for TypeScript/JavaScript files
- Follow the coding standards and patterns in each skill
4. **Test the fix** - Run appropriate tests as specified in the skills
5. **Verify the fix** - Ensure tests pass and code follows conventions
## Important Guidelines
- Use the appropriate write skill for the language you're working in
- Write failing tests first, then fix them
- Work in small, testable increments
- Don't commit changes - leave that for the workflow
- IMPORTANT: Focus only on fixing the reported bug, don't make unrelated changes
## Scratchpad Directory
A `.claude-scratchpad/` directory has been created for your temporary files:
- Use this directory for ALL temporary verification files. However, prefer using production test files rather than adhoc ones.
- Only modify actual production code in the main repository directories
- The scratchpad will be cleaned up automatically - nothing from it will be committed
Start by understanding the bug and then systematically work toward a solution.
PROMPT_END
EOF
- name: Determine branch strategy
run: |
BRANCH_NAME="${{ env.BRANCH_NAME }}"
if git branch -r --list "origin/${BRANCH_NAME}" | grep -q .; then
echo "Branch already exists: $BRANCH_NAME"
echo "Will push to existing branch and skip PR creation"
echo "BRANCH_EXISTS=true" >> $GITHUB_ENV
else
echo "Creating new branch: $BRANCH_NAME"
echo "Will create new PR"
echo "BRANCH_EXISTS=false" >> $GITHUB_ENV
fi
# Export the branch name
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
- name: Create scratchpad directory for Claude
run: |
# Create a scratchpad directory for Claude to use for temporary files
mkdir -p .claude-scratchpad
echo "Scratchpad directory created for Claude's temporary verification files"
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Generate GitHub App Token for Claude
uses: actions/create-github-app-token@v1
id: claude-app-token
with:
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
- name: Generate Bug Fix with Claude Code
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ steps.claude-app-token.outputs.token }}
prompt: ${{ env.BUG_FIX_PROMPT }}
claude_args: |
--allowedTools "Bash(bun run test-unit*),Bash(bun run lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
--disallowedTools "Bash(git commit*),Bash(git push*),Bash(gh*)"
- name: Clean up scratchpad directory
run: |
# Remove the scratchpad directory so its contents don't get committed
rm -rf .claude-scratchpad/
# Remove output file generated by Claude Code action
rm -f output.txt
echo "Scratchpad directory and action output files cleaned up"
- name: Check for changes and analysis
id: check_changes
run: |
# Now check for actual code changes (excluding analysis file)
if [ -n "$(git status --porcelain)" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Changes detected:"
git status --short
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No changes made by Claude."
fi
- name: Get issue team labels and prepare reviewers
if: steps.check_changes.outputs.has_changes == 'true'
id: prepare_reviewers
uses: actions/github-script@v7
env:
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
with:
script: |
const issueData = JSON.parse(process.env.ISSUE_DATA);
const issueLabels = issueData.labels.map(label => label.name);
const teamLabels = issueLabels.filter(label => label.startsWith('.Team/'));
const issueCreator = issueData.user.login;
core.setOutput('team_labels', teamLabels.join(','));
core.setOutput('reviewers', issueCreator);
- name: Create Pull Request
id: create_pr
if: steps.check_changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ env.BRANCH_NAME }}
base: master
commit-message: |
Fix: ${{ env.ISSUE_TITLE }}
Fixes #${{ env.ISSUE_NUMBER }}
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
committer: "Claude Code[bot] <noreply@anthropic.com>"
author: "Claude Code[bot] <noreply@anthropic.com>"
title: "Fix: ${{ env.ISSUE_TITLE }}"
body: |
This PR automatically fixes the bug reported in #${{ env.ISSUE_NUMBER }}.
${{ steps.find_linear.outputs.linear_issue && format('Closes {0}', steps.find_linear.outputs.linear_issue) || '' }}
## Bug Report
**Original Issue:** #${{ env.ISSUE_NUMBER }}
**Reporter:** @${{ env.ISSUE_AUTHOR }}
**Issue Title:** ${{ env.ISSUE_TITLE }}
## Automated Fix
🤖 This fix was generated automatically by Claude Code based on the issue description.
**Please review carefully:**
- [ ] The fix addresses the reported issue
- [ ] Tests pass and code follows conventions
- [ ] No unrelated changes were introduced
- [ ] The solution is appropriate and maintainable
## Next Steps
- Review the changes and test thoroughly
- If the fix looks good, approve and merge
- If issues are found, either fix them or close this PR
---
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
labels: |
${{ steps.prepare_reviewers.outputs.team_labels }}
automated-fix
ci skip
reviewers: ${{ steps.prepare_reviewers.outputs.reviewers }}
assignees: ${{ github.actor }}
- name: Log existing branch update
if: steps.check_changes.outputs.has_changes == 'true' && env.BRANCH_EXISTS == 'true'
uses: actions/github-script@v7
with:
script: |
console.log('🔄 Updated existing branch: ${{ env.BRANCH_NAME }}');
console.log('The changes have been pushed to the existing PR for this issue.');
- name: Log analysis for no changes
if: steps.check_changes.outputs.has_changes == 'false'
uses: actions/github-script@v7
with:
script: |
console.log('🤖 No code changes were made by Claude.');

View File

@@ -14,14 +14,14 @@ concurrency:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
cljs: ${{ steps.changes.outputs.cljs }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -30,7 +30,7 @@ jobs:
shared-tests-cljs:
needs: files-changed
if: needs.files-changed.outputs.cljs == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

View File

@@ -10,14 +10,14 @@ on:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
codeql: ${{ steps.changes.outputs.codeql }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -25,7 +25,7 @@ jobs:
analyze:
needs: files-changed
if: needs.files-changed.outputs.codeql == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4

View File

@@ -8,8 +8,7 @@ on:
jobs:
codespell:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
@@ -68,7 +67,7 @@ jobs:
- name: Fix spelling errors with Claude Code
if: steps.codespell_check.outcome == 'failure'
uses: anthropics/claude-code-action@3ac52d0da9f8ec9ca7b4dc23bb477e36ef9c77a9 # v1
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ steps.claude-app-token.outputs.token }}
@@ -83,7 +82,7 @@ jobs:
- name: Create Pull Request
if: steps.codespell_check.outcome == 'failure'
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: claude-fix/codespell-${{ steps.date.outputs.date }}

View File

@@ -48,18 +48,16 @@ on:
jobs:
containerize:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
platforms: ${{ steps.platforms.outputs.result }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
fetch-depth: 0
# we want the dockerfile from the commit we're building
- run: |
git fetch --depth=1 origin ${{ inputs.commit }}
git checkout ${{ inputs.commit }} -- bin/docker
- run: git checkout ${{ inputs.commit }} -- bin/docker
- name: Retrieve uberjar artifact
uses: ./.github/actions/fetch-artifact
with:
@@ -86,19 +84,19 @@ jobs:
- name: Move the Uberjar to the context dir
run: mv ./metabase.jar bin/docker/.
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
with:
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
if: contains(steps.platforms.outputs.result, 'arm')
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
if: contains(steps.platforms.outputs.result, 'arm')
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
uses: docker/setup-buildx-action@v3
- name: Build multi-arch container
if: contains(steps.platforms.outputs.result, 'arm')
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@v6
with:
context: bin/docker/.
platforms: ${{ steps.platforms.outputs.result }}
@@ -119,12 +117,12 @@ jobs:
bin/docker/.
verify-docker-pull:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
needs: containerize
timeout-minutes: 15
steps:
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
with:
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}
@@ -144,7 +142,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
with:
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}

View File

@@ -6,7 +6,7 @@ on:
jobs:
get-container-info:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
name: Get container info
outputs:
@@ -68,7 +68,7 @@ jobs:
strategy:
matrix:
edition: ${{ fromJson(needs.get-container-info.outputs.editions) }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
name: Run Trivy vulnerability scanner
steps:
- name: Check out the code
@@ -76,7 +76,7 @@ jobs:
with:
ref: ${{ github.event.inputs.commit || github.event.pull_request.head.sha || github.sha }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
uses: aquasecurity/trivy-action@0.29.0
env:
TRIVY_OFFLINE_SCAN: true
repo: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).repo }}
@@ -85,6 +85,7 @@ jobs:
image-ref: docker.io/${{ env.repo }}:${{ env.tag }}
format: sarif
output: trivy-results.sarif
version: "v0.57.1"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
@@ -93,7 +94,7 @@ jobs:
post-comment:
needs: [get-container-info, containerize]
if: github.event_name == 'pull_request'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
name: Post PR comment
steps:
- name: Check out the code
@@ -106,7 +107,7 @@ jobs:
edition: ee
- name: Post PR comment
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
uses: marocchino/sticky-pull-request-comment@v2
with:
header: docker-build-status
message: |

View File

@@ -8,21 +8,21 @@ on:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
frontend_embedding_sources: ${{ steps.changes.outputs.frontend_embedding_sources }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
filters: .github/file-paths.yaml
pr-title-reminder:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
permissions:
pull-requests: write

View File

@@ -7,8 +7,7 @@ on:
jobs:
fe-test-coverage:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@@ -23,7 +22,7 @@ jobs:
--coverage \
--testTimeout=60000
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
files: ./coverage/lcov.info
flags: front-end

View File

@@ -25,7 +25,7 @@ concurrency:
jobs:
migrations-h2-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
CI: 'true'
@@ -39,23 +39,23 @@ jobs:
head-ref: ${{ env.HEAD_REF }}
migrations-postgres-14-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
CI: 'true'
MB_DB_TYPE: postgres
MB_DB_PORT: 5432
MB_DB_HOST: localhost
MB_DB_DBNAME: mb_test
MB_DB_USER: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: circle_test
services:
postgres:
image: postgres:14-alpine
ports:
- "5432:5432"
env:
POSTGRES_USER: mb_test
POSTGRES_DB: mb_test
POSTGRES_USER: circle_test
POSTGRES_DB: circle_test
POSTGRES_HOST_AUTH_METHOD: trust
steps:
- uses: actions/checkout@v4
@@ -65,23 +65,23 @@ jobs:
head-ref: ${{ env.HEAD_REF }}
migrations-postgres-latest-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
CI: 'true'
MB_DB_TYPE: postgres
MB_DB_PORT: 5432
MB_DB_HOST: localhost
MB_DB_DBNAME: mb_test
MB_DB_USER: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: circle_test
services:
postgres:
image: postgres:latest
image: circleci/postgres:latest
ports:
- "5432:5432"
env:
POSTGRES_USER: mb_test
POSTGRES_DB: mb_test
POSTGRES_USER: circle_test
POSTGRES_DB: circle_test
POSTGRES_HOST_AUTH_METHOD: trust
steps:
- uses: actions/checkout@v4
@@ -90,24 +90,21 @@ jobs:
base-ref: ${{ env.BASE_REF }}
head-ref: ${{ env.HEAD_REF }}
migrations-mysql-8-0-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
migrations-mysql-8-4-ee:
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
CI: 'true'
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
services:
mysql:
image: mysql:8.0
image: cimg/mysql:8.4
ports:
- "3306:3306"
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: mb_test
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cross-branch-migration-test
@@ -116,14 +113,14 @@ jobs:
head-ref: ${{ env.HEAD_REF }}
migrations-mysql-latest-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
CI: 'true'
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
services:
@@ -131,7 +128,7 @@ jobs:
image: mysql:latest
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: mb_test
MYSQL_DATABASE: circle_test
ports:
- "3306:3306"
steps:
@@ -142,23 +139,20 @@ jobs:
head-ref: ${{ env.HEAD_REF }}
migrations-mariadb-10-6-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
CI: 'true'
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
services:
mariadb:
image: metabase/mariadb:10.6 # https://github.com/metabase/metabase-mariadb-docker-image
image: circleci/mariadb:10.6
ports:
- "3306:3306"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_DATABASE: mb_test
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cross-branch-migration-test
@@ -167,23 +161,20 @@ jobs:
head-ref: ${{ env.HEAD_REF }}
migrations-mariadb-latest-ee:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
CI: 'true'
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
services:
mariadb:
image: mariadb:latest
image: circleci/mariadb:latest
ports:
- "3306:3306"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_DATABASE: mb_test
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cross-branch-migration-test

View File

@@ -25,8 +25,8 @@ on:
jobs:
# Generate matrix for scheduled/PR runs
generate-matrix:
if: github.event_name != 'workflow_dispatch' && github.repository == 'metabase/metabase'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
if: github.event_name != 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
@@ -47,9 +47,9 @@ jobs:
# Matrix-based tests for scheduled/PR runs
test-matrix:
if: github.event_name != 'workflow_dispatch' && github.repository == 'metabase/metabase'
if: github.event_name != 'workflow_dispatch'
needs: generate-matrix
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
@@ -61,38 +61,13 @@ jobs:
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare Cypress environment
uses: ./.github/actions/prepare-cypress
- name: Run cross-version test
env:
CURRENT_VERSION: ${{ vars.CURRENT_VERSION }}
RESULT_FILE: ${{ runner.temp }}/cross-version-result-${{ matrix.source }}-${{ matrix.target }}.json
run: |
cd cross-version
./test.sh --source "${{ matrix.source }}" --target "${{ matrix.target }}"
- name: Upload failure result
uses: actions/upload-artifact@v4
if: failure()
with:
name: failure-result-${{ matrix.source }}-${{ matrix.target }}
path: ${{ runner.temp }}/cross-version-result-${{ matrix.source }}-${{ matrix.target }}.json
if-no-files-found: ignore
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-${{ matrix.source }}-${{ matrix.target }}
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: Show logs on failure
if: failure()
run: |
@@ -100,92 +75,10 @@ jobs:
docker compose logs metabase || true
docker compose logs postgres || true
# Notify Slack on nightly failures
notify-on-failure:
if: >-
always()
&& github.event_name == 'schedule'
&& github.repository == 'metabase/metabase'
&& needs.test-matrix.result == 'failure'
needs: [test-matrix]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
release
.github
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Download failure results
uses: actions/download-artifact@v4
continue-on-error: true
with:
pattern: failure-result-*
path: failure-results
merge-multiple: true
- name: Notify Slack
continue-on-error: true
uses: actions/github-script@v7
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
script: |
const fs = require('fs');
const path = require('path');
const { buildFailureResults, sendCrossVersionSlackNotification } = require('${{ github.workspace }}/release/dist/index.cjs');
// Get all failed jobs from the GitHub API
const { owner, repo } = context.repo;
const jobInfo = await github.rest.actions.listJobsForWorkflowRun({
owner,
repo,
run_id: context.runId,
per_page: 100,
});
const failedJobs = jobInfo.data.jobs
.filter(job => job.status === 'completed' && job.conclusion === 'failure' && job.name.startsWith('test-matrix'))
.map(job => ({ name: job.name, url: job.html_url }));
if (failedJobs.length === 0) {
console.log('No failed test-matrix jobs found');
return;
}
// Load artifact data for enrichment (phase)
const resultsDir = 'failure-results';
const artifactData = new Map();
if (fs.existsSync(resultsDir)) {
for (const file of fs.readdirSync(resultsDir)) {
if (!file.endsWith('.json')) continue;
try {
const result = JSON.parse(fs.readFileSync(path.join(resultsDir, file), 'utf8'));
artifactData.set(`${result.source}-${result.target}`, result);
} catch (e) {
console.log(`Skipping malformed result: ${file}`);
}
}
}
const failures = buildFailureResults(failedJobs, artifactData);
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${context.runId}`;
const slackBotToken = process.env.SLACK_BOT_TOKEN;
if (!slackBotToken) {
console.log('SLACK_BOT_TOKEN is not set; skipping Slack notification.');
return;
}
await sendCrossVersionSlackNotification(failures, runUrl, slackBotToken);
console.log('Slack notification sent');
# Manual test for workflow_dispatch
test-manual:
if: github.event_name == 'workflow_dispatch'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -194,12 +87,6 @@ jobs:
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare Cypress environment
uses: ./.github/actions/prepare-cypress
- name: Run cross-version test
env:
CURRENT_VERSION: ${{ vars.CURRENT_VERSION }}
@@ -207,16 +94,6 @@ jobs:
cd cross-version
./test.sh --source "${{ inputs.source_version }}" --target "${{ inputs.target_version }}"
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-${{ inputs.source_version }}-${{ inputs.target_version }}
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: Show logs on failure
if: failure()
run: |

View File

@@ -1,113 +0,0 @@
name: Cut Next Release Branch
run-name: Cut next release branch (current is v${{ vars.CURRENT_VERSION }})
on:
workflow_dispatch:
jobs:
cut:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
ref: master
sparse-checkout: |
release
.github
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Preflight
id: preflight
uses: actions/github-script@v7
with:
script: | # js
const { versionRequirements } = require('${{ github.workspace }}/release/dist/index.cjs');
const currentRaw = '${{ vars.CURRENT_VERSION }}';
const current = Number(currentRaw);
if (!Number.isInteger(current) || current <= 0) {
throw new Error(`vars.CURRENT_VERSION is not a valid positive integer: "${currentRaw}"`);
}
const next = current + 1;
const branch = `release-x.${next}.x`;
const needsRequirementsUpdate = !(next in versionRequirements);
try {
await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branch}`,
});
throw new Error(`Branch ${branch} already exists — nothing to cut.`);
} catch (err) {
if (err.status !== 404) {
throw err;
}
}
core.setOutput('branch', branch);
core.setOutput('next', String(next));
core.setOutput('needsRequirementsUpdate', String(needsRequirementsUpdate));
- name: Cut the branch
id: cut
run: |
BRANCH="${{ steps.preflight.outputs.branch }}"
git config user.name github-actions
git config user.email github-actions@github.com
git checkout -b "$BRANCH"
git commit --allow-empty --no-verify -m "Cut $BRANCH branch"
git push -u origin "$BRANCH"
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Bump vars.CURRENT_VERSION
id: bump
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
run: |
gh variable set CURRENT_VERSION \
--body "${{ steps.preflight.outputs.next }}" \
--repo "${{ github.repository }}"
- name: Notify Slack
uses: actions/github-script@v7
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
script: | # js
const { sendSlackMessage, mentionSlackTeam } = require('${{ github.workspace }}/release/dist/index.cjs');
const branch = '${{ steps.preflight.outputs.branch }}';
const next = '${{ steps.preflight.outputs.next }}';
const sha = '${{ steps.cut.outputs.sha }}';
const bumpFailed = '${{ steps.bump.outcome }}' === 'failure';
const needsReqs = '${{ steps.preflight.outputs.needsRequirementsUpdate }}' === 'true';
const owner = context.repo.owner;
const repo = context.repo.repo;
const shaLink = `<https://github.com/${owner}/${repo}/commit/${sha}|${sha.slice(0, 7)}>`;
const team = mentionSlackTeam('releasemanagers');
const notes = [
bumpFailed
? `:warning: Auto-bump of \`CURRENT_VERSION\` to *${next}* failed — please update it manually: <https://github.com/${owner}/${repo}/settings/variables/actions|Actions variables>`
: `\`CURRENT_VERSION\` bumped to *${next}*.`,
needsReqs
? `:warning: Please add \`versionRequirements[${next}]\` to \`release/src/version-helpers.ts\` (java + node + platforms).`
: null,
].filter(Boolean).join('\n');
const message = `:tree_evergreen: Cut new release branch *${branch}* at ${shaLink} ${team}\n${notes}`;
await sendSlackMessage({
channelName: '${{ vars.SLACK_RELEASE_CHANNEL }}',
message,
}).catch(console.error);

View File

@@ -7,8 +7,7 @@ on:
jobs:
notify-disabled-drivers:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/setup-node@v4

View File

@@ -21,7 +21,7 @@ permissions:
jobs:
update-docs:
name: Update documentation for ${{ inputs.target_branch }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
- name: Checkout code

View File

@@ -7,7 +7,7 @@ on:
jobs:
link-checker:
name: Check documentation links
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
@@ -27,7 +27,7 @@ jobs:
- name: Run link checker
id: link-check
uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 # v2.3.0
uses: lycheeverse/lychee-action@v2.3.0
with:
fail: true
args: docs --config ./.lychee/config.toml --offline

View File

@@ -2,12 +2,11 @@ name: Auto-update documentation (master)
on:
schedule:
- cron: "0 21 * * 1-5" # Every Monday through Friday at 9 PM UTC
- cron: "0 1 * * 1-5" # Every Monday through Friday at 1 AM UTC
workflow_dispatch:
jobs:
update-master-docs:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
name: Call documentation update for master
uses: ./.github/workflows/docs-generate-base.yml
with:

View File

@@ -2,21 +2,20 @@ name: Auto-update documentation (release)
on:
schedule:
- cron: "0 22 * * 1-5" # Every Monday through Friday at 10 PM UTC
- cron: "0 2 * * 1-5" # Every Monday through Friday at 2 AM UTC
workflow_dispatch:
inputs:
target_branch:
description: "Target branch for documentation update"
required: false
default: "release-x.60.x"
default: "release-x.59.x"
type: string
jobs:
update-release-docs:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
name: Call documentation update for release branch
uses: ./.github/workflows/docs-generate-base.yml
with:
target_branch: ${{ inputs.target_branch || 'release-x.60.x' }}
target_branch: ${{ inputs.target_branch || 'release-x.59.x' }}
secrets:
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}

View File

@@ -49,7 +49,7 @@ jobs:
be-tests-athena-ee:
if: inputs.driver == 'Athena'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -68,7 +68,7 @@ jobs:
be-tests-bigquery-cloud-sdk-ee:
if: inputs.driver == 'BigQuery'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -89,7 +89,7 @@ jobs:
be-tests-druid-ee:
if: inputs.driver == 'Druid'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -111,7 +111,7 @@ jobs:
be-tests-mariadb-10-6-ee:
if: inputs.driver == 'Maria DB'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -119,17 +119,14 @@ jobs:
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
services:
mariadb:
image: metabase/mariadb:10.6 # https://github.com/metabase/metabase-mariadb-docker-image
image: circleci/mariadb:10.6
ports:
- "3306:3306"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_DATABASE: mb_test
steps:
- uses: actions/checkout@v4
- name: Test MariaDB driver (10.6)
@@ -140,7 +137,7 @@ jobs:
be-tests-mariadb-latest-ee:
if: inputs.driver == 'Maria DB (latest)'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -148,17 +145,14 @@ jobs:
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
services:
mariadb:
image: mariadb:latest
image: circleci/mariadb:latest
ports:
- "3306:3306"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_DATABASE: mb_test
steps:
- uses: actions/checkout@v4
- name: Test MariaDB driver (latest)
@@ -169,7 +163,7 @@ jobs:
be-tests-mongo-4-4-ee:
if: inputs.driver == 'Mongo 4'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -191,7 +185,7 @@ jobs:
be-tests-mongo-4-4-ssl-ee:
if: inputs.driver == 'Mongo 4 (SSL)'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -225,7 +219,7 @@ jobs:
be-tests-mongo-5-0-ee:
if: inputs.driver == 'Mongo 5'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -247,7 +241,7 @@ jobs:
be-tests-mongo-5-0-ssl-ee:
if: inputs.driver == 'Mongo 5 (SSL)'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -281,7 +275,7 @@ jobs:
be-tests-mongo-latest-ee:
if: inputs.driver == 'Mongo (latest)'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -290,7 +284,7 @@ jobs:
MB_MONGO_TEST_PASSWORD: metasample123
services:
mongodb:
image: mongo:latest
image: circleci/mongo:latest
ports:
- "27017:27017"
env:
@@ -304,9 +298,9 @@ jobs:
junit-name: 'be-tests-mongo-latest-ee'
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
be-tests-mysql-8-0-ee:
be-tests-mysql-8-4-ee:
if: inputs.driver == 'MySQL'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -314,28 +308,25 @@ jobs:
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
services:
mysql:
image: mysql:8.0
image: cimg/mysql:8.4
ports:
- "3306:3306"
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: mb_test
steps:
- uses: actions/checkout@v4
- name: Test MySQL driver (8.0)
- name: Test MySQL driver (8.4)
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-mysql-8-0-ee'
junit-name: 'be-tests-mysql-8-4-ee'
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
be-tests-mysql-latest-ee:
if: inputs.driver == 'MySQL (latest)'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -343,7 +334,7 @@ jobs:
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
# set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
@@ -373,7 +364,7 @@ jobs:
be-tests-oracle-18-4-ee:
if: inputs.driver == 'Oracle 18'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -399,7 +390,7 @@ jobs:
be-tests-oracle-21-3-ee:
if: inputs.driver == 'Oracle 21'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -434,7 +425,7 @@ jobs:
be-tests-postgres-ee:
if: inputs.driver == 'Postgres'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -464,7 +455,7 @@ jobs:
be-tests-postgres-latest-ee:
if: inputs.driver == 'Postgres (latest)'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -472,20 +463,20 @@ jobs:
MB_DB_TYPE: postgres
MB_DB_PORT: 5432
MB_DB_HOST: localhost
MB_DB_DBNAME: mb_test
MB_DB_USER: mb_test
MB_POSTGRESQL_TEST_USER: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: circle_test
MB_POSTGRESQL_TEST_USER: circle_test
MB_POSTGRES_SSL_TEST_SSL: true
MB_POSTGRES_SSL_TEST_SSL_MODE: verify-full
MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH: 'test-resources/certificates/us-east-2-bundle.pem'
services:
postgres:
image: postgres:latest
image: circleci/postgres:latest
ports:
- "5432:5432"
env:
POSTGRES_USER: mb_test
POSTGRES_DB: mb_test
POSTGRES_USER: circle_test
POSTGRES_DB: circle_test
POSTGRES_HOST_AUTH_METHOD: trust
steps:
- uses: actions/checkout@v4
@@ -497,7 +488,7 @@ jobs:
be-tests-presto-jdbc-ee:
if: inputs.driver == 'Presto JDBC'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -546,7 +537,7 @@ jobs:
be-tests-redshift-ee:
if: inputs.driver == 'Redshift'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -565,7 +556,7 @@ jobs:
be-tests-snowflake-ee:
if: inputs.driver == 'Snowflake'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -586,7 +577,7 @@ jobs:
be-tests-sparksql-ee:
if: inputs.driver == 'SparkSQL'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -606,7 +597,7 @@ jobs:
be-tests-sqlite-ee:
if: inputs.driver == 'SQLite'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -621,7 +612,7 @@ jobs:
be-tests-sqlserver-2017-ee:
if: inputs.driver == 'SQL Server 2017'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -648,7 +639,7 @@ jobs:
be-tests-sqlserver-2022-ee:
if: inputs.driver == 'SQL Server 2022'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -675,7 +666,7 @@ jobs:
be-tests-starburst-ee:
if: inputs.driver == 'Starburst'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
env:
CI: 'true'
@@ -700,14 +691,14 @@ jobs:
login-to-ecr:
if: inputs.driver == 'Vertica'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 15
permissions:
id-token: write
contents: read
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
@@ -729,7 +720,7 @@ jobs:
be-tests-vertica-ee:
if: inputs.driver == 'Vertica'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
needs: login-to-ecr
timeout-minutes: 90
env:

View File

@@ -15,7 +15,7 @@ jobs:
# This job determines which driver tests should run based on PR context.
# All decision logic is consolidated in mage driver-decisions.
determine-driver-skips:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
outputs:
# All driver run decisions (from mage driver-decisions)
h2-should-run: ${{ steps.driver-decisions.outputs.h2-should-run }}
@@ -72,8 +72,8 @@ jobs:
# All decision logic is in mage - it analyzes module dependencies,
# branch context, labels, and file changes to decide what runs.
MAGE_ARGS=(
--git-ref="${BASE_REF}"
--is-master-or-release="${IS_MASTER_OR_RELEASE}"
--git-ref="${{ github.event.pull_request.base.ref || github.ref_name }}"
--is-master-or-release="${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') }}"
--pr-labels="${{ join(github.event.pull_request.labels.*.name, ',') }}"
--skip="${{ inputs.skip }}"
)
@@ -82,20 +82,14 @@ jobs:
echo ""
# First call: show human-readable output for debugging
./bin/mage -driver-decisions "${MAGE_ARGS[@]}"
echo ""
echo "Want to force-run a specific driver? Add a ci:run-<driver> or ci:run-all-drivers label to your PR."
echo "Run './bin/mage -driver-decisions -h' locally for the full list of drivers and labels."
# Second call: output only key=value lines for GITHUB_OUTPUT
./bin/mage -driver-decisions "${MAGE_ARGS[@]}" --github-output-only >> $GITHUB_OUTPUT
env:
BASE_REF: ${{ github.event.pull_request.base.ref || github.ref_name }}
IS_MASTER_OR_RELEASE: ${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') }}
shell: bash
be-tests-athena-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.athena-should-run == 'true' || needs.determine-driver-skips.outputs.athena-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -137,7 +131,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -145,7 +140,7 @@ jobs:
be-tests-bigquery-cloud-sdk-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.bigquery-should-run == 'true' || needs.determine-driver-skips.outputs.bigquery-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
@@ -202,7 +197,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -216,7 +212,7 @@ jobs:
be-tests-clickhouse-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.clickhouse-should-run == 'true' || needs.determine-driver-skips.outputs.clickhouse-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
@@ -259,7 +255,7 @@ jobs:
run: |
sudo echo "127.0.0.1 server.clickhouseconnect.test" | sudo tee -a /etc/hosts
- name: Start ClickHouse in Docker
uses: hoverkraft-tech/compose-action@802a148945af6399a338c7906c267331b39a71af # v2.0.0
uses: hoverkraft-tech/compose-action@v2.0.0
with:
compose-file: "modules/drivers/clickhouse/docker-compose.yml"
down-flags: "--volumes"
@@ -284,7 +280,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -298,7 +295,7 @@ jobs:
be-tests-druid-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.druid-should-run == 'true' || needs.determine-driver-skips.outputs.druid-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -338,7 +335,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -346,7 +344,7 @@ jobs:
be-tests-druid-jdbc-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.druid-jdbc-should-run == 'true' || needs.determine-driver-skips.outputs.druid-jdbc-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -386,7 +384,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -394,8 +393,8 @@ jobs:
be-tests-databricks-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.databricks-should-run == 'true' || needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 60
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
DRIVERS: databricks
@@ -434,7 +433,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -442,7 +442,7 @@ jobs:
be-tests-databricks-multi-catalog-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.databricks-should-run == 'true' || needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -483,7 +483,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -491,7 +492,7 @@ jobs:
be-tests-h2:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.h2-should-run == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -513,7 +514,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -521,7 +523,7 @@ jobs:
be-tests-h2-oss:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.h2-should-run == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -544,7 +546,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -552,7 +555,7 @@ jobs:
be-tests-mysql-mariadb:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.mysql-mariadb-should-run == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
@@ -560,19 +563,19 @@ jobs:
version:
- name: MariaDB 10.6
junit-name: be-tests-mariadb-10-6-ee
image: metabase/mariadb:10.6 # https://github.com/metabase/metabase-mariadb-docker-image
image: circleci/mariadb:10.6
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: MariaDB Latest
junit-name: be-tests-mariadb-latest-ee
image: mariadb:latest
image: circleci/mariadb:latest
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: MySQL 8.0
junit-name: be-tests-mysql-8-0-ee
image: mysql:8.0
- name: MySQL 8.4
junit-name: be-tests-mysql-8-4-ee
image: mysql:8.4
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
@@ -599,7 +602,7 @@ jobs:
- "3306:3306"
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: mb_test
MYSQL_DATABASE: circle_test
env:
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
@@ -622,7 +625,7 @@ jobs:
MB_DB_TYPE: mysql
MB_DB_HOST: localhost
MB_DB_PORT: 3306
MB_DB_DBNAME: mb_test
MB_DB_DBNAME: circle_test
MB_DB_USER: root
MB_MYSQL_TEST_USER: root
#
@@ -674,7 +677,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -688,7 +692,7 @@ jobs:
be-tests-mongo:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.mongo-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
@@ -754,7 +758,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -768,7 +773,7 @@ jobs:
be-tests-mongo-ssl:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.mongo-ssl-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-ssl-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
@@ -836,7 +841,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -844,7 +850,7 @@ jobs:
be-tests-mongo-sharded-cluster-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.mongo-sharded-cluster-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-sharded-cluster-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -881,7 +887,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -889,7 +896,7 @@ jobs:
be-tests-oracle:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.oracle-should-run == 'true' || needs.determine-driver-skips.outputs.oracle-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
@@ -966,7 +973,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -974,7 +982,7 @@ jobs:
be-tests-postgres:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.postgres-should-run == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
@@ -1070,7 +1078,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1084,7 +1093,7 @@ jobs:
be-tests-presto-jdbc-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.presto-jdbc-should-run == 'true' || needs.determine-driver-skips.outputs.presto-jdbc-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -1157,7 +1166,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1165,7 +1175,7 @@ jobs:
be-tests-redshift-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.redshift-should-run == 'true' || needs.determine-driver-skips.outputs.redshift-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
strategy:
fail-fast: false
@@ -1205,7 +1215,7 @@ jobs:
MB_REDSHIFT_TEST_USER: metabase_ci
MB_REDSHIFT_TEST_DB: testdb
MB_REDSHIFT_TEST_DB_ROUTING: dev
MB_REDSHIFT_TEST_HOSTS: ${{ secrets.MB_REDSHIFT_TEST_HOSTS }}
MB_REDSHIFT_TEST_HOST: ${{ secrets.MB_REDSHIFT_TEST_HOST }}
MB_REDSHIFT_TEST_PASSWORD: ${{ secrets.MB_REDSHIFT_TEST_PASSWORD }}
name: ${{ matrix.job.name }}
steps:
@@ -1241,7 +1251,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1255,7 +1266,7 @@ jobs:
be-tests-snowflake-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.snowflake-should-run == 'true' || needs.determine-driver-skips.outputs.snowflake-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1321,7 +1332,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1335,7 +1347,7 @@ jobs:
be-tests-sparksql-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.sparksql-should-run == 'true' || needs.determine-driver-skips.outputs.sparksql-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -1373,7 +1385,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1381,7 +1394,7 @@ jobs:
be-tests-sqlite-ee:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.sqlite-should-run == 'true' || needs.determine-driver-skips.outputs.sqlite-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -1414,7 +1427,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1422,7 +1436,7 @@ jobs:
be-tests-sqlserver:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.sqlserver-should-run == 'true' || needs.determine-driver-skips.outputs.sqlserver-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
@@ -1490,7 +1504,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1505,7 +1520,7 @@ jobs:
# be-tests-starburst-ee:
# needs: determine-driver-skips
# if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
# runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
# runs-on: ubuntu-latest
# timeout-minutes: 40
# env:
# CI: 'true'
@@ -1535,21 +1550,22 @@ jobs:
# input-path: ./target/junit/
# output-name: ${{ github.job }}
# bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
# role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
# aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
# aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
# aws-region: ${{ vars.AWS_REGION }}
# trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
login-to-ecr:
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.vertica-should-run == 'true' || needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
id-token: write
contents: read
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
@@ -1572,7 +1588,7 @@ jobs:
be-tests-vertica-ee:
needs: [determine-driver-skips, login-to-ecr]
if: ${{ needs.determine-driver-skips.outputs.vertica-should-run == 'true' || needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: 'true'
@@ -1615,7 +1631,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1645,7 +1662,7 @@ jobs:
# TODO(rileythomp, 2025-08-20): Re-enable vertica tests once the docker image is updated
# - be-tests-vertica-ee
- be-tests-clickhouse-ee
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
name: drivers-tests-result
if: always() && !cancelled()

View File

@@ -21,7 +21,7 @@ on:
jobs:
build-matrix:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
matrix: ${{ steps.matrix.outputs.config }}
@@ -47,7 +47,7 @@ jobs:
const inputSpecs = "${{ inputs.specs }}";
const inputChunks = Number(process.env.chunks);
const java = 21;
const defaultRunner = "${{ vars.DEFAULT_RUNNER_KEY }}";
const defaultRunner = "ubuntu-22.04";
const options = {
java,
defaultRunner,

View File

@@ -31,16 +31,11 @@ on:
type: boolean
required: false
default: true
fail_fast:
description: "Stop running remaining tests after the first failure"
type: boolean
required: false
default: false
jobs:
determine-test-type:
name: Determine test type
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
type: ${{ steps.detect.outputs.type }}
@@ -58,7 +53,7 @@ jobs:
workflow-summary:
name: Stress test inputs
needs: [determine-test-type]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Generate workflow summary
@@ -72,13 +67,12 @@ jobs:
echo '- `grep`: "${{ inputs.grep }}"' >> $GITHUB_STEP_SUMMARY
echo '- `qa_db`: "${{ inputs.qa_db }}"' >> $GITHUB_STEP_SUMMARY
echo '- `enable_network_throttling`: "${{ inputs.enable_network_throttling }}"' >> $GITHUB_STEP_SUMMARY
echo '- `fail_fast`: "${{ inputs.fail_fast }}"' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
stress-test-flake-fix:
needs: [determine-test-type]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 60
name: Stress test E2E flake fix
env:
@@ -150,13 +144,10 @@ jobs:
env:
GREP: ${{ github.event.inputs.grep }}
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
FAIL_FAST: ${{ inputs.fail_fast }}
SPEC: ${{ github.event.inputs.spec }}
BURN_IN: ${{ github.event.inputs.burn_in }}
run: |
node e2e/runner/run_cypress_ci.js ${{ needs.determine-test-type.outputs.type }} \
--spec "${SPEC}" \
--env burn="${BURN_IN}" \
--spec '${{ github.event.inputs.spec }}' \
--env burn=${{ github.event.inputs.burn_in }} \
--config-file e2e/support/cypress-stress-test.config.js
- name: Upload Cypress Artifacts upon failure

View File

@@ -21,7 +21,7 @@ on:
runner:
required: false
type: string
default: ${{ vars.DEFAULT_RUNNER_KEY }}
default: ubuntu-22.04
total_chunks: # number of chunks
required: false
type: number
@@ -166,7 +166,8 @@ jobs:
# Related to our custom upload to S3
output-name: e2e-${{ inputs.name }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
# Related to Trunk uploader
variant: e2e-tests
@@ -234,7 +235,7 @@ jobs:
uses: actions/upload-artifact@v4
if: steps.extract-timings.outcome == 'success' && steps.extract-timings.outputs.has-changes == 'true'
with:
name: timing-data-${{ inputs.name }}
name: timing-data-${{ inputs.name }}-${{ inputs.split_index }}
path: chunkTimings.json
retention-days: 1 # gets merged immediately by parent workflow

View File

@@ -24,7 +24,7 @@ jobs:
reset-e2e-test-comment:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Remove test results comment
@@ -75,7 +75,7 @@ jobs:
collect-timings:
name: Collect E2E Timings
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
needs: e2e-tests
if: always() && !cancelled() && github.ref == 'refs/heads/master'
@@ -138,7 +138,7 @@ jobs:
e2e-tests-result:
needs:
- e2e-tests
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
name: e2e-tests-result
if: always() && !cancelled()

View File

@@ -12,7 +12,7 @@ env:
jobs:
sdk-component-tests:
name: "SDK component tests (React ${{ matrix.react-version }})"
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 45
strategy:
matrix:
@@ -94,7 +94,8 @@ jobs:
input-path: ./target/junit
output-name: e2e-embedding-sdk-react-${{ matrix.react-version }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-tests.outcome }}
@@ -127,7 +128,7 @@ jobs:
await core.summary.addRaw(summary).write();
decide-target-ref:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
outputs:
target-ref: ${{ steps.get-target-ref.outputs.value }}
steps:
@@ -150,7 +151,7 @@ jobs:
sdk-component-tests-backward-compatibility:
name: "SDK component tests (Backward compatibility)"
needs: [decide-target-ref]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 45
env:
MB_RUN_MODE: "e2e"
@@ -247,7 +248,8 @@ jobs:
input-path: ./target/junit
output-name: e2e-embedding-sdk
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-tests.outcome }}

View File

@@ -12,7 +12,7 @@ env:
MB_EDITION: ee
jobs:
get-sample-apps-data:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
# we must support the last two stable releases
@@ -94,7 +94,7 @@ jobs:
environment: ["production", "development"]
if: |
!cancelled() && needs.get-sample-apps-data.result == 'success'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 45
name: e2e-sample-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
env:
@@ -106,8 +106,6 @@ jobs:
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4
@@ -116,7 +114,7 @@ jobs:
# We have to use the latest version that does not have this issue https://github.com/docker/compose/pull/12752
- name: Set up Docker Compose
uses: docker/setup-compose-action@2fe291b7677a45ee1269ec56a42604c143505e7e # v1
uses: docker/setup-compose-action@v1
with:
version: latest
@@ -203,7 +201,7 @@ jobs:
if: |
!cancelled() && needs.get-sample-apps-data.result == 'success' &&
(github.ref == 'refs/heads/master' || github.base_ref == 'master')
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 45
name: e2e-sample-app-shoppy-e2e-tests-${{ matrix.environment }}-bundle
env:
@@ -215,8 +213,6 @@ jobs:
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4
@@ -268,7 +264,7 @@ jobs:
!cancelled() && needs.get-sample-apps-data.result == 'success' &&
github.event_name == 'push' &&
github.ref == 'refs/heads/master'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 45
name: e2e-shoppy-synthetic-monitoring-tests
env:
@@ -281,7 +277,6 @@ jobs:
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4
@@ -359,7 +354,7 @@ jobs:
"angular-20-host-app-e2e",
]
environment: ["production", "development"]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 45
name: e2e-host-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
env:
@@ -367,8 +362,6 @@ jobs:
HOST_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4

View File

@@ -10,14 +10,14 @@ on:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
frontend_embedding_sdk_package_sources: ${{ steps.changes.outputs.frontend_embedding_sdk_package_sources }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -29,11 +29,11 @@ jobs:
!cancelled() &&
(needs.files-changed.outputs.frontend_embedding_sdk_package_sources == 'true')
name: Embedding SDK package release-notes/no-release-notes label reminder
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: jesusvasquez333/verify-pr-label-action@657d111bbbe13e22bbd55870f1813c699bde1401 # v1.4.0
- uses: jesusvasquez333/verify-pr-label-action@v1.4.0
id: verify-release-notes-labels
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -10,7 +10,7 @@ concurrency:
jobs:
build-sdk-package:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v4

View File

@@ -39,7 +39,7 @@ concurrency:
jobs:
embedding-sdk-cli-snippets-type-check:
if: inputs.cli-snippets-type-check && !inputs.skip
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
@@ -55,7 +55,7 @@ jobs:
embedding-sdk-docs-snippets-type-check:
if: inputs.docs-snippets-type-check && !inputs.skip
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
@@ -71,7 +71,7 @@ jobs:
embedding-sdk-api-documentation-generation-validation:
if: inputs.documentation-validation && !inputs.skip
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
@@ -131,7 +131,7 @@ jobs:
embedding-sdk-bundle-size-check:
if: inputs.bundle-size-check
continue-on-error: true
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
env:
BUNDLE_SIZE_THRESHOLD_PERCENT: 2
@@ -186,7 +186,7 @@ jobs:
- embedding-sdk-api-documentation-generation-validation
- embedding-sdk-component-tests
if: always() && !cancelled()
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
env:
needs: ${{ toJson(needs) }}

View File

@@ -1,78 +0,0 @@
# Weekly count of files importing @emotion/*, uploaded to stats.metabase.com.
# Feeds the "Emotion CSS Elimination Progress" chart tracking the migration
# off Emotion onto Mantine (+ CSS modules as a fallback).
name: Emotion Stats Update
on:
workflow_dispatch:
schedule:
- cron: 40 1 * * 2 # every Tuesday at 1:40
jobs:
emotion-stats:
if: github.repository == 'metabase/metabase'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Get Emotion file count
id: get-emotion-file-count
run: |
# Match both `import ... from '@emotion/...'` and bare side-effect imports
# (`import '@emotion/...'`, used in e.g. frontend/src/types/emotion.d.ts).
# `|| true` prevents failing on the day the count reaches 0 — git grep
# exits 1 on no matches, which cascades through bash's default pipefail.
oss=$(
git grep -l -E "(from|import) ['\"]@emotion/" -- \
'frontend/**/*.ts' \
'frontend/**/*.tsx' \
'frontend/**/*.js' \
'frontend/**/*.jsx' \
| wc -l || true
)
ee=$(
git grep -l -E "(from|import) ['\"]@emotion/" -- \
'enterprise/frontend/**/*.ts' \
'enterprise/frontend/**/*.tsx' \
'enterprise/frontend/**/*.js' \
'enterprise/frontend/**/*.jsx' \
| wc -l || true
)
echo "$(($oss+$ee)) files importing @emotion/*"
echo "EMOTION_FILES=$(($oss+$ee))" >> $GITHUB_OUTPUT
- name: Upload count to Stats
uses: actions/github-script@v7
env:
EMOTION_FILES: ${{ steps.get-emotion-file-count.outputs.EMOTION_FILES }}
API_KEY: ${{ secrets.STATS_API_KEY }}
with:
script: | # js
const { uploadCsvToMb } = require('${{ github.workspace }}/.github/scripts/csv-to-mb.js');
console.log("EMOTION_FILES", Number(process.env.EMOTION_FILES));
// Keys become CSV headers and must match the target table's columns
// (Metabase slugifies both sides, so casing/spacing is forgiving).
const data = [
{
'Date': new Date().toISOString().slice(0, 10), // YYYY-MM-DD
'Emotion Files': Number(process.env.EMOTION_FILES),
}
];
// tableId 76283 = "Emotion Files" table on stats.metabase.com.
// Auth via x-api-key, sourced from STATS_API_KEY (see env block above).
uploadCsvToMb({
baseUrl: 'https://stats.metabase.com',
tableId: 76283,
jsonData: data,
mode: 'append',
}).then(() => {
console.log('Data uploaded successfully');
}).catch((error) => {
console.error('Error uploading data:', error);
process.exit(1);
});

View File

@@ -7,7 +7,7 @@ on:
jobs:
on-escalation-label:
if: github.event.label.name == '.Escalation'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- name: Format issue title (escape quotes)
@@ -18,7 +18,7 @@ jobs:
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Send escalated issue ${{ github.event.issue.number }} to Slack
id: slack
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844 # v1.23.0
uses: slackapi/slack-github-action@v1.23.0
with:
payload: |
{

View File

@@ -19,7 +19,7 @@ on:
jobs:
workflow-summary:
name: Stress test inputs
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Generate workflow summary
@@ -34,7 +34,7 @@ jobs:
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
frontend-unit-test-stress-test-flake-fix:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 60
name: Stress test frontend unit test flake fix
steps:
@@ -47,16 +47,11 @@ jobs:
run: bun run build:cljs
- name: Stress-test ${{ github.event.inputs.spec }} ${{ github.event.inputs.burn_in }} times
run: |
for ((i=1; i<=BURN_IN; i++)); do
for i in {1..${{ github.event.inputs.burn_in }}}; do
bun run jest --runInBand --ci --silent --no-cache \
--testPathPatterns "${SPEC}" \
--testNamePattern "${GREP}"
--testPathPatterns '${{ github.event.inputs.spec }}' \
--testNamePattern '${{ github.event.inputs.grep }}'
if [[ "$?" != 0 ]]; then
echo "Failed after $i attempts" && break;
fi
done
env:
BURN_IN: ${{ github.event.inputs.burn_in }}
SPEC: ${{ github.event.inputs.spec }}
GREP: ${{ github.event.inputs.grep }}

View File

@@ -16,7 +16,7 @@ on:
type: boolean
default: false
skip-lint:
description: "Skip linting jobs (oxfmt, eslint, css variables check)"
description: "Skip linting jobs (prettier, eslint, css variables check)"
type: boolean
default: false
@@ -27,7 +27,7 @@ concurrency:
jobs:
fe-lint:
if: ${{ !inputs.skip-lint }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
@@ -49,8 +49,8 @@ jobs:
run: bun run build-pure:cljs
- name: Find CSS variables that are used but never defined
run: bun run find-never-defined-css-variables
- name: Run oxfmt formatting linter
run: bun run lint-format-pure
- name: Run Prettier formatting linter
run: bun run lint-prettier-pure
- name: Run ESLint linter
run: bun run lint-eslint-pure
- name: Run CSS linter
@@ -58,7 +58,7 @@ jobs:
fe-type-check:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
@@ -73,7 +73,7 @@ jobs:
fe-tests-unit:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
@@ -103,14 +103,15 @@ jobs:
input-path: ./target/junit
output-name: frontend-unit
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.fe-tests.outcome }}
fe-tests-timezones:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
@@ -128,7 +129,7 @@ jobs:
- fe-tests-unit
- fe-tests-timezones
if: always() && !cancelled()
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
env:
needs: ${{ toJson(needs) }}

View File

@@ -24,7 +24,7 @@ on:
# TODO: Consider renaming the job in next milestones
jobs:
generative-query-test:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 90
name: Generative Query Test
env:
@@ -47,7 +47,7 @@ jobs:
run: clojure -X:dev:ci:test:ee:ee-dev :only '${{ inputs.test }}'
- name: Publish Test Report (JUnit)
uses: dorny/test-reporter@3eeb9fc888e82e8be2fb356bbeec2750231672bc # v1
uses: dorny/test-reporter@v1
if: failure()
with:
path: "target/junit/**/*_test.xml"

View File

@@ -14,14 +14,14 @@ concurrency:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
i18n: ${{ steps.changes.outputs.i18n }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -30,22 +30,22 @@ jobs:
verify-i18n-files:
needs: files-changed
if: needs.files-changed.outputs.i18n == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Install xgettext if necessary
run: |
if ! command -v xgettext > /dev/null; then
sudo apt install gettext
fi
- name: Check xgettext version
run: xgettext --version
- name: Check i18n tags/make sure template can be built
run: ./bin/i18n/update-translation-template
- name: Verify i18n translations (.po files)
run: ./bin/i18n/build-translation-resources
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Install xgettext if necessary
run: |
if ! command -v xgettext > /dev/null; then
sudo apt install gettext
fi
- name: Check xgettext version
run: xgettext --version
- name: Check i18n tags/make sure template can be built
run: ./bin/i18n/update-translation-template
- name: Verify i18n translations (.po files)
run: ./bin/i18n/build-translation-resources

View File

@@ -7,8 +7,7 @@ on:
jobs:
js-stats:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4

View File

@@ -6,7 +6,7 @@ on:
jobs:
add-issue-references:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

View File

@@ -21,7 +21,7 @@ on:
jobs:
detect-changed-stories:
name: Detect changed stories
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
story_files: ${{ steps.detect.outputs.story_files }}
@@ -84,40 +84,32 @@ jobs:
name: Summary
needs: detect-changed-stories
if: needs.detect-changed-stories.outputs.should_run == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Generate workflow summary
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
STORY_FILES: ${{ needs.detect-changed-stories.outputs.story_files }}
BURN_IN: ${{ inputs.burn_in || 50 }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_ACTOR: ${{ github.event.pull_request.user.login }}
ACTOR: ${{ github.actor }}
run: |
echo '## Loki Stress Test' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo "**Branch:** \`${BRANCH_NAME}\`" >> $GITHUB_STEP_SUMMARY
echo '**Branch:** `${{ github.head_ref || github.ref_name }}`' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '**Story files:**' >> $GITHUB_STEP_SUMMARY
echo "${STORY_FILES}" | tr ',' '\n' | while read -r file; do
echo '${{ needs.detect-changed-stories.outputs.story_files }}' | tr ',' '\n' | while read -r file; do
echo "- \`$file\`" >> $GITHUB_STEP_SUMMARY
done
echo '' >> $GITHUB_STEP_SUMMARY
echo "**Burn-in:** ${BURN_IN} runs" >> $GITHUB_STEP_SUMMARY
echo '**Burn-in:** ${{ inputs.burn_in || 50 }} runs' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
echo "Triggered by PR #${PR_NUMBER} from @${PR_ACTOR}" >> $GITHUB_STEP_SUMMARY
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo 'Triggered by PR #${{ github.event.pull_request.number }} from @${{ github.event.pull_request.user.login }}' >> $GITHUB_STEP_SUMMARY
else
echo "Triggered manually by @${ACTOR}" >> $GITHUB_STEP_SUMMARY
echo 'Triggered manually by @${{ github.actor }}' >> $GITHUB_STEP_SUMMARY
fi
stress-test-loki:
needs: detect-changed-stories
if: needs.detect-changed-stories.outputs.should_run == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 60
name: Stress test Loki visual tests
services:

View File

@@ -7,7 +7,7 @@ on:
jobs:
check-label-and-update-snapshot:
if: contains(github.event.pull_request.labels.*.name, 'loki-update')
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

View File

@@ -7,7 +7,7 @@ on:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
frontend_ci: ${{ steps.changes.outputs.frontend_ci }}
@@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -25,7 +25,7 @@ jobs:
visual-test:
needs: files-changed
if: needs.files-changed.outputs.frontend_ci == 'true' || needs.files-changed.outputs.frontend_sources == 'true' || needs.files-changed.outputs.frontend_loki_ci == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
services:
docker:
image: docker:19.03.12

View File

@@ -6,7 +6,7 @@ on:
jobs:
# run mage tests even if the rest of be isnt needed
mage-tests:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
name: Mage
steps:
- uses: actions/checkout@v4

View File

@@ -6,7 +6,7 @@ on:
- labeled
jobs:
on-issue-labeled:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
if: |
contains(github.event.issue.labels.*.name, 'Type:New Feature')
@@ -19,7 +19,7 @@ jobs:
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Send the issue ${{ github.event.issue.number }} to Slack
id: slack
uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{

View File

@@ -8,10 +8,10 @@ on:
- unlabeled
- synchronize
paths:
- src/metabase/utils/js.cljs
- src/metabase/lib/js.cljs
jobs:
mbql-library-changelog-check:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
if: ${{ !contains( github.event.pull_request.labels.*.name, '.metabase-lib/no-change' ) }}
steps:
# - uses: jesusvasquez333/verify-pr-label-action@v1.4.0

View File

@@ -6,13 +6,11 @@ on:
- 'master'
- 'release-**'
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
lint-migrations:
if: github.event.action != 'labeled' && github.event.action != 'unlabeled'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
@@ -20,19 +18,3 @@ jobs:
uses: ./.github/actions/prepare-backend
- name: Verify Liquibase Migrations
run: ./bin/mage lint-migrations
check-migration-versions:
if: github.event_name == 'pull_request'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0
- name: Check migration versions
env:
BASE_REF: ${{ github.base_ref }}
CURRENT_VERSION: ${{ vars.CURRENT_VERSION }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
run: bin/check-migration-versions

View File

@@ -8,16 +8,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
permissions:
id-token: write
contents: read
checks: write
actions: read
jobs:
# Only run if the PR has the minimal-tests label
check-label:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 1
outputs:
should-run: ${{ steps.check.outputs.result }}
@@ -35,7 +29,7 @@ jobs:
postgres-app-db-test:
needs: check-label
if: needs.check-label.outputs.should-run == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 40
env:
CI: "true"
@@ -76,7 +70,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -85,7 +80,7 @@ jobs:
postgres-driver-test:
needs: check-label
if: needs.check-label.outputs.should-run == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CI: "true"
@@ -123,7 +118,8 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -132,7 +128,7 @@ jobs:
frontend-unit-tests:
needs: check-label
if: needs.check-label.outputs.should-run == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
env:
JEST_JUNIT_OUTPUT_DIR: ./target/junit
@@ -155,7 +151,8 @@ jobs:
input-path: ./target/junit
output-name: frontend-unit
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-tests.outcome }}
@@ -166,7 +163,7 @@ jobs:
- postgres-app-db-test
- postgres-driver-test
- frontend-unit-tests
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
name: minimal-tests-result
if: always() && !cancelled()

View File

@@ -7,12 +7,11 @@ on:
jobs:
module-boundaries-stats:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
- name: get module boundary stats
@@ -21,13 +20,6 @@ jobs:
NAMED_MODULES=$(node --input-type=module -e "
import { elements } from './frontend/lint/module-boundaries.mjs';
const named = elements.filter(e => !['shared/other', 'app/misc', 'other'].includes(e.type));
console.log(named.length);
")
ENFORCED_MODULES=$(node --input-type=module -e "
import { elements } from './frontend/lint/module-boundaries.mjs';
const enforced = new Set(elements.filter(e => e.enforceOutgoing).map(e => e.type));
console.log(enforced.size);
")
NAMED_DIRS=$(node --input-type=module -e "
@@ -37,7 +29,6 @@ jobs:
const m = el.pattern.match(/^frontend\/src\/metabase\/(\w[\w-]*)\//)
if (m) dirs.add(m[1]);
}
for (const d of dirs) console.log(d);
")
TOTAL_DIRS=$(ls -d frontend/src/metabase/*/ | xargs -I{} basename {} | wc -l | tr -d ' ')
NAMED_COUNT=$(echo "$NAMED_DIRS" | grep -c . || true)
@@ -45,17 +36,15 @@ jobs:
VIOLATIONS=$(bun run module-boundaries 2>&1 | grep -o '✖ [0-9]* problems' | grep -o '[0-9]*' || echo "0")
echo "$NAMED_MODULES modules, $ENFORCED_MODULES enforced, $UNMODULED unmoduled folders, $VIOLATIONS violations"
echo "$NAMED_MODULES modules, $UNMODULED unmoduled folders, $VIOLATIONS violations"
echo "NAMED_MODULES=$NAMED_MODULES" >> $GITHUB_OUTPUT
echo "ENFORCED_MODULES=$ENFORCED_MODULES" >> $GITHUB_OUTPUT
echo "UNMODULED=$UNMODULED" >> $GITHUB_OUTPUT
echo "VIOLATIONS=$VIOLATIONS" >> $GITHUB_OUTPUT
- name: Upload stats
uses: actions/github-script@v7
env:
NAMED_MODULES: ${{ steps.get-stats.outputs.NAMED_MODULES }}
ENFORCED_MODULES: ${{ steps.get-stats.outputs.ENFORCED_MODULES }}
UNMODULED: ${{ steps.get-stats.outputs.UNMODULED }}
VIOLATIONS: ${{ steps.get-stats.outputs.VIOLATIONS }}
API_KEY: ${{ secrets.STATS_API_KEY }}
@@ -64,7 +53,6 @@ jobs:
const { uploadCsvToMb } = require('${{ github.workspace }}/.github/scripts/csv-to-mb.js');
console.log("NAMED_MODULES", Number(process.env.NAMED_MODULES));
console.log("ENFORCED_MODULES", Number(process.env.ENFORCED_MODULES));
console.log("UNMODULED", Number(process.env.UNMODULED));
console.log("VIOLATIONS", Number(process.env.VIOLATIONS));
@@ -72,7 +60,6 @@ jobs:
{
'Date': new Date().toISOString().slice(0, 10), // YYYY-MM-DD
'Modules': Number(process.env.NAMED_MODULES),
'Enforced modules': Number(process.env.ENFORCED_MODULES),
'Unmoduled folders': Number(process.env.UNMODULED),
'Violations': Number(process.env.VIOLATIONS),
}

View File

@@ -10,7 +10,7 @@ concurrency:
jobs:
check-openapi-specs:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
if: contains(github.event.pull_request.labels.*.name, 'openapi-self-healing')
steps:

View File

@@ -14,14 +14,14 @@ concurrency:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
openapi: ${{ steps.changes.outputs.openapi }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -30,7 +30,7 @@ jobs:
validate-openapi:
needs: files-changed
if: needs.files-changed.outputs.openapi == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

View File

@@ -7,7 +7,7 @@ on:
jobs:
optimize-images:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
if: ${{ contains(github.event.pull_request.labels.*.name,'Type:Documentation') }}
steps:
- name: Checkout repository

View File

@@ -5,7 +5,7 @@ on:
workflow_call:
jobs:
build:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
name: Build Metabase Docker image
timeout-minutes: 60
@@ -17,13 +17,13 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: us-east-1
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@183a1442edf41672e66566b7fc560e297a290896 # v2
uses: aws-actions/amazon-ecr-login@v2
with:
registries: "${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}"
- name: Retrieve uberjar artifact for ee
@@ -38,7 +38,7 @@ jobs:
jar xf target/uberjar/metabase.jar
mv target/uberjar/metabase.jar bin/docker/metabase.jar
- name: Build container
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@v6
with:
context: bin/docker/
platforms: linux/amd64
@@ -47,7 +47,7 @@ jobs:
push: true
launch_perf_test:
needs: [ build ]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
name: Launch Perf Test
permissions:
id-token: write
@@ -56,7 +56,7 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Tailscale
uses: tailscale/github-action@4e4c49acaa9818630ce0bd7a564372c17e33fb4d # v2
uses: tailscale/github-action@v2
with:
oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }}
@@ -67,14 +67,12 @@ jobs:
id: create-oidc-token
shell: bash
run: |
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${PR_ENV_K8S_AUDIENCE}"
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}"
IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value)
echo "::add-mask::${IDTOKEN}"
echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT
env:
PR_ENV_K8S_AUDIENCE: ${{ secrets.PR_ENV_K8S_AUDIENCE }}
- name: Setup Kube Context
uses: azure/k8s-set-context@0b53301dcc7ddd3f6d35f97fb67228ac4e5ab2fd # v2
uses: azure/k8s-set-context@v2
with:
method: kubeconfig
kubeconfig: |
@@ -97,7 +95,7 @@ jobs:
namespace: default
user: oidc-token
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
@@ -108,9 +106,9 @@ jobs:
env:
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
id: split
run: echo "fragment=${SHA:5}" >> $GITHUB_OUTPUT
run: echo "::set-output name=fragment::${SHA:5}"
- name: Render Deployment YAML
uses: nowactions/envsubst@c4b8e3977354d294659a7ec164279ce8fba5c2e1 # v1
uses: nowactions/envsubst@v1
with:
input: ./perf-test-pr.yml.tmpl
output: ./perf-test-pr.yml

View File

@@ -14,7 +14,7 @@ on:
jobs:
approve:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
environment: pr-env-admin
steps:
- name: Approved

View File

@@ -19,7 +19,7 @@ env:
jobs:
destroy_pr:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
name: PR Review ENV Destroy
permissions:
id-token: write
@@ -28,7 +28,7 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Tailscale
uses: tailscale/github-action@4e4c49acaa9818630ce0bd7a564372c17e33fb4d # v2
uses: tailscale/github-action@v2
with:
oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }}
@@ -39,14 +39,12 @@ jobs:
id: create-oidc-token
shell: bash
run: |
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${PR_ENV_K8S_AUDIENCE}"
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}"
IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value)
echo "::add-mask::${IDTOKEN}"
echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT
env:
PR_ENV_K8S_AUDIENCE: ${{ secrets.PR_ENV_K8S_AUDIENCE }}
- name: Setup Kube Context
uses: azure/k8s-set-context@0b53301dcc7ddd3f6d35f97fb67228ac4e5ab2fd # v2
uses: azure/k8s-set-context@v2
with:
method: kubeconfig
kubeconfig: |
@@ -75,7 +73,7 @@ jobs:
kubectl delete ns hosting-pr${{ env.PR_NUMBER }}
- name: Setup Helm
if: ${{ inputs.self_hosting }}
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@v4
with:
version: 'latest'
- name: Destroy PR Review ENV (self-hosting)
@@ -89,15 +87,9 @@ jobs:
sudo apt-get install -y postgresql-client
- name: Drop app database if exists
run: |
PGPASSWORD="${PR_ENV_DB_PASSWORD}" \
PGPASSWORD='${{ secrets.PR_ENV_DB_PASSWORD }}' \
psql \
-h "${PR_ENV_DB_HOST}" \
-U "${PR_ENV_DB_USER}" \
-d "${PR_ENV_DB_NAME}" \
-h ${{ secrets.PR_ENV_DB_HOST }} \
-U ${{ secrets.PR_ENV_DB_USER }} \
-d ${{ secrets.PR_ENV_DB_NAME }} \
-c "DROP DATABASE IF EXISTS hosting_pr${{ env.PR_NUMBER }};"
env:
PR_ENV_DB_PASSWORD: ${{ secrets.PR_ENV_DB_PASSWORD }}
PR_ENV_DB_HOST: ${{ secrets.PR_ENV_DB_HOST }}
PR_ENV_DB_USER: ${{ secrets.PR_ENV_DB_USER }}
PR_ENV_DB_NAME: ${{ secrets.PR_ENV_DB_NAME }}

View File

@@ -16,7 +16,7 @@ on:
default: false
jobs:
build:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
name: Build Metabase Docker image
timeout-minutes: 60
@@ -28,13 +28,13 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: us-east-1
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@183a1442edf41672e66566b7fc560e297a290896 # v2
uses: aws-actions/amazon-ecr-login@v2
with:
registries: "${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}"
- name: Retrieve uberjar artifact for ee
@@ -49,7 +49,7 @@ jobs:
jar xf target/uberjar/metabase.jar
mv target/uberjar/metabase.jar bin/docker/metabase.jar
- name: Build container
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@v6
with:
context: bin/docker/
platforms: linux/amd64
@@ -58,7 +58,7 @@ jobs:
push: true
deploy_pr:
needs: [build]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
name: PR Review ENV
permissions:
id-token: write
@@ -67,7 +67,7 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Tailscale
uses: tailscale/github-action@4e4c49acaa9818630ce0bd7a564372c17e33fb4d # v2
uses: tailscale/github-action@v2
with:
oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }}
@@ -78,14 +78,12 @@ jobs:
id: create-oidc-token
shell: bash
run: |
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${PR_ENV_K8S_AUDIENCE}"
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}"
IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value)
echo "::add-mask::${IDTOKEN}"
echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT
env:
PR_ENV_K8S_AUDIENCE: ${{ secrets.PR_ENV_K8S_AUDIENCE }}
- name: Setup Kube Context
uses: azure/k8s-set-context@0b53301dcc7ddd3f6d35f97fb67228ac4e5ab2fd # v2
uses: azure/k8s-set-context@v2
with:
method: kubeconfig
kubeconfig: |
@@ -108,7 +106,7 @@ jobs:
namespace: default
user: oidc-token
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
@@ -119,24 +117,19 @@ jobs:
sudo apt-get install -y postgresql-client
- name: Create app database if not exists
run: |
export PGPASSWORD="${PR_ENV_DB_PASSWORD}"
export PGUSER="${PR_ENV_DB_USER}"
export PGHOST="${PR_ENV_DB_HOST}"
export PGDATABASE="${PR_ENV_DB_NAME}"
export PGPASSWORD='${{ secrets.PR_ENV_DB_PASSWORD }}'
export PGUSER=${{ secrets.PR_ENV_DB_USER }}
export PGHOST=${{ secrets.PR_ENV_DB_HOST }}
export PGDATABASE=${{ secrets.PR_ENV_DB_NAME }}
psql -tc "SELECT 1 FROM pg_database WHERE datname = 'hosting_pr${{ github.event.number }}'" \
| grep -q 1 \
|| psql -c "CREATE DATABASE hosting_pr${{ github.event.number }}"
env:
PR_ENV_DB_PASSWORD: ${{ secrets.PR_ENV_DB_PASSWORD }}
PR_ENV_DB_USER: ${{ secrets.PR_ENV_DB_USER }}
PR_ENV_DB_HOST: ${{ secrets.PR_ENV_DB_HOST }}
PR_ENV_DB_NAME: ${{ secrets.PR_ENV_DB_NAME }}
- name: Download Deployment YAML template
if: ${{ !inputs.self_hosting }}
run: aws s3 cp s3://metabase-pr-env/metabase.yml.tmpl ./metabase.yml.tmpl
- name: Render Deployment YAML
if: ${{ !inputs.self_hosting }}
uses: nowactions/envsubst@c4b8e3977354d294659a7ec164279ce8fba5c2e1 # v1
uses: nowactions/envsubst@v1
with:
input: ./metabase.yml.tmpl
output: ./metabase.yml
@@ -155,7 +148,7 @@ jobs:
run: aws s3 cp s3://metabase-pr-env/values.yaml.tmpl ./values.yaml.tmpl
- name: Render Helm values (self-hosting)
if: ${{ inputs.self_hosting }}
uses: nowactions/envsubst@c4b8e3977354d294659a7ec164279ce8fba5c2e1 # v1
uses: nowactions/envsubst@v1
with:
input: ./values.yaml.tmpl
output: ./values.yaml
@@ -175,18 +168,15 @@ jobs:
- name: Setup Helm
if: ${{ inputs.self_hosting }}
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@v4
with:
version: 'latest'
- name: Login to Quay.io
if: ${{ inputs.self_hosting }}
run: |
helm registry login quay.io \
--username "${QUAY_USERNAME}" \
--password "${QUAY_PASSWORD}"
env:
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
--username ${{ secrets.QUAY_USERNAME }} \
--password ${{ secrets.QUAY_PASSWORD }}
- name: Deploy PR Review ENV (self-hosting)
if: ${{ inputs.self_hosting }}
run: |
@@ -198,10 +188,10 @@ jobs:
--atomic \
--timeout 10m
preview_links:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
needs: [deploy_pr]
steps:
- uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
- uses: marocchino/sticky-pull-request-comment@v2
with:
recreate: true
message: |

View File

@@ -25,7 +25,7 @@ on:
jobs:
test-mode-config:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
run_mode: ${{ fromJson(steps.config.outputs.result).runMode }}
@@ -65,7 +65,7 @@ jobs:
});
release-artifact:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
strategy:
matrix:
@@ -110,7 +110,7 @@ jobs:
check-uberjar-health:
needs: [release-artifact]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
name: Is ${{ matrix.edition }} (java ${{ matrix.java-version }}) healthy?
timeout-minutes: 10
strategy:
@@ -136,7 +136,7 @@ jobs:
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
run-sanity-check:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 30
needs: [test-mode-config, release-artifact]
strategy:
@@ -258,8 +258,14 @@ jobs:
tests-complete-message:
if: always()
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
needs: [run-sanity-check, check-uberjar-health, containerize-ee, containerize-oss]
runs-on: ubuntu-22.04
needs:
[
run-sanity-check,
check-uberjar-health,
containerize-ee,
containerize-oss,
]
timeout-minutes: 5
steps:
- uses: actions/checkout@v4

View File

@@ -18,14 +18,14 @@ on:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
backend_presto_kerberos: ${{ steps.changes.outputs.backend_presto_kerberos }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -34,7 +34,7 @@ jobs:
run-presto-kerberos-test:
needs: files-changed
if: needs.files-changed.outputs.backend_presto_kerberos == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 40
steps:
- name: Install babashka

View File

@@ -10,7 +10,7 @@ jobs:
external-reminder:
name: External PR Helper
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
permissions:
pull-requests: write

View File

@@ -7,41 +7,38 @@ on:
jobs:
notify-querying-on-call:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
if: |
(github.event.label.name == 'Priority:P0' || github.event.label.name == 'Priority:P1' || github.event.label.name == '.Escalation') &&
contains(github.event.issue.labels.*.name, '.Team/Querying')
steps:
- name: Setting title
uses: actions/github-script@v7
id: vars
with:
script: |
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Trigger PagerDuty Incident
env:
ROUTING_KEY: ${{ secrets.QUERYING_PAGERDUTY_INTEGRATION_KEY }}
ISSUE_TITLE: ${{ github.event.issue.title }}
LABEL_NAME: ${{ github.event.label.name }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_URL: ${{ github.event.issue.html_url }}
REPO: ${{ github.repository }}
ALL_LABELS: ${{ join(github.event.issue.labels.*.name, ', ') }}
run: |
payload=$(jq -n \
--arg rk "$ROUTING_KEY" \
--arg dk "github-issue-$ISSUE_NUMBER" \
--arg sum "[$LABEL_NAME] $ISSUE_TITLE" \
--arg src "GitHub Issues - $REPO" \
--arg lbl "$ALL_LABELS" \
--arg url "$ISSUE_URL" \
'{
routing_key: $rk,
event_action: "trigger",
dedup_key: $dk,
payload: {
summary: $sum,
source: $src,
severity: "critical",
custom_details: { Labels: $lbl }
},
links: [{ href: $url, text: "View GitHub Issue" }]
}')
curl -X POST https://events.pagerduty.com/v2/enqueue \
-H 'Content-Type: application/json' \
-d "$payload"
-d '{
"routing_key": "${{ secrets.QUERYING_PAGERDUTY_INTEGRATION_KEY }}",
"event_action": "trigger",
"dedup_key": "github-issue-${{ github.event.issue.number }}",
"payload": {
"summary": "[${{ github.event.label.name }}] ${{ steps.vars.outputs.issue_title }}",
"source": "GitHub Issues - ${{ github.repository }}",
"severity": "critical",
"custom_details": {
"Labels": "${{ join(github.event.issue.labels.*.name, ', ') }}"
}
},
"links": [
{
"href": "${{ github.event.issue.html_url }}",
"text": "View GitHub Issue"
}
]
}'

View File

@@ -1,68 +0,0 @@
name: Release Custom Viz Package
run-name: Release @metabase/custom-viz v${{ inputs.version }} from ${{ inputs.branch }}
on:
workflow_dispatch:
inputs:
branch:
description: "Branch to release from"
type: string
required: true
default: "custom-viz"
version:
description: "Version to publish (e.g. 0.1.0)"
type: string
required: true
npm_tag:
description: "NPM dist-tag"
type: choice
required: true
default: "latest"
options:
- latest
- next
- canary
jobs:
build-and-publish:
runs-on: ubuntu-22.04
timeout-minutes: 20
defaults:
run:
working-directory: frontend/src/custom-viz
steps:
- name: Check out the code
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Setup npm registry auth
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
registry-url: "https://registry.npmjs.org"
- name: Set package version
run: npm version ${{ inputs.version }} --no-git-tag-version
- name: Build package
run: bun run build
- name: Publish to NPM
run: npm publish --tag ${{ inputs.npm_tag }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
- name: Generate workflow summary
run: |
tee -a $GITHUB_STEP_SUMMARY << EOF
## Published @metabase/custom-viz
- **Version**: ${{ inputs.version }}
- **Tag**: ${{ inputs.npm_tag }}
- **Branch**: ${{ inputs.branch }}
- **Triggered by**: @${{ github.event.sender.login }}
EOF

View File

@@ -21,7 +21,7 @@ concurrency:
jobs:
workflow-summary:
name: Log inputs
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Generate workflow summary
@@ -37,7 +37,7 @@ jobs:
send-slack-notification-start:
name: "Send Slack notification: SDK release started"
needs: determine-sdk-version
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
slackMessageId: ${{ steps.send-slack-message-start.outputs.result }}
@@ -96,7 +96,7 @@ jobs:
"publish-npm",
"git-tag",
]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
env:
slackMessageId: ${{ needs.send-slack-notification-start.outputs.slackMessageId }}
@@ -134,7 +134,7 @@ jobs:
name: "Send Slack notification: SDK release successful"
needs:
["determine-sdk-version", "send-slack-notification-start", "publish-npm"]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
env:
slackMessageId: ${{ needs.send-slack-notification-start.outputs.slackMessageId }}
@@ -168,7 +168,7 @@ jobs:
});
determine-sdk-version:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
outputs:
sdk_version: ${{ fromJson(steps.new-sdk-version.outputs.result).version }}
@@ -219,7 +219,7 @@ jobs:
check-git-tag:
needs: determine-sdk-version
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
env:
tag: embedding-sdk-${{ needs.determine-sdk-version.outputs.sdk_version }}
@@ -243,7 +243,7 @@ jobs:
test:
needs: check-git-tag
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- name: Check out the code using the provided branch
@@ -262,7 +262,7 @@ jobs:
build-sdk:
needs: [test, determine-sdk-version]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- name: Check out the code using the provided branch
@@ -297,12 +297,8 @@ jobs:
publish-npm:
needs: [build-sdk, determine-sdk-version]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
permissions:
id-token: write # OIDC handshake for npm trusted publishing
actions: read # actions/download-artifact reads the Actions API via GITHUB_TOKEN
pull-requests: write # gh pr review --approve step uses GITHUB_TOKEN
env:
sdk_version: ${{ needs.determine-sdk-version.outputs.sdk_version }}
steps:
@@ -384,20 +380,15 @@ jobs:
echo "masterTag=nightly" >> $GITHUB_OUTPUT
fi
# Trusted publishing requires npm >= 11.5.1 and Node >= 22.14.0.
# Node 22.x line bundles npm 10.x, so pin Node 24.x which ships npm 11.x.
- name: Setup Node.js for npm publish with trusted publishing
uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Publish to NPM
working-directory: sdk
run: |
echo //registry.npmjs.org/:_authToken=${{ secrets.NPM_RELEASE_TOKEN }} > .npmrc
TAG="${{ steps.compute-tag.outputs.masterTag }}"
echo "Resolved npm tag: $TAG"
npm publish --tag "$TAG"
# ⚠️ This step should only be executed on the latest stable (gold) release branch.
# Only uncomment this when the new release branch becomes stable (gold).
# - name: Add `latest` tag to the latest release branch (`release-x.55.x`) deployment
@@ -407,7 +398,7 @@ jobs:
git-tag:
needs: [publish-npm, determine-sdk-version]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 20
env:
tag: embedding-sdk-${{ needs.determine-sdk-version.outputs.sdk_version }}

View File

@@ -21,21 +21,18 @@ on:
type: number # needs to be a number to pass variables
required: true
permissions:
id-token: write
contents: read
jobs:
update-release-log:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-latest
timeout-minutes: 5
# don't run this workflow for forks
if: ${{ github.event_name != 'push' || github.repository == 'metabase/metabase' }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
@@ -73,14 +70,21 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd release && tsx ./src/release-log-run.ts $VERSION > v$VERSION.html
- name: generate release channel log
run: cd release && tsx ./src/release-channel-log.ts > channels.html
- name: upload release log to the web
run: |
aws s3 cp \
release/v$VERSION.html \
s3://${{ vars.AWS_S3_STATIC_BUCKET }}/release-log/v$VERSION.html
- name: upload release channel log to the web
run: |
aws s3 cp \
release/channels.html \
s3://${{ vars.AWS_S3_STATIC_BUCKET }}/release-log/channels.html
- name: Create cloudfront invalidation
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ vars.AWS_CLOUDFRONT_STATIC_ID }} \
--paths "/release-log/v$VERSION.html"
--paths "/release-log/v$VERSION.html" "/release-log/channels.html"

View File

@@ -14,14 +14,15 @@ on:
jobs:
check-milestone:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
permissions: write-all
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: release
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -11,7 +11,7 @@ on:
jobs:
preview-release-notes:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Fail early on the incorrect version format

View File

@@ -5,29 +5,29 @@ on:
workflow_dispatch:
inputs:
version:
description: "Major Metabase version (e.g. 45, 52, 68)"
description: 'Major Metabase version (e.g. 45, 52, 68)'
type: number
required: true
override_sha:
description: DANGEROUSLY allow releasing from this commit sha, regardless of the state of CI (can be empty)
type: string
required: false
default: ""
default: ''
schedule:
- cron: "0 21 * * 1-5" # every weekday at 4 pm EST/5 pm EDT
- cron: '0 21 * * 1-5' # every weekday at 4 pm EST/5 pm EDT
jobs:
auto-patch-trigger:
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || (github.repository == 'metabase/metabase' && vars.ENABLE_AUTO_PATCH == 'true') }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: release
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}
@@ -35,16 +35,12 @@ jobs:
run: bun install --cwd release --frozen-lockfile && bun run --cwd release build
- name: Trigger auto-patch
uses: actions/github-script@v7
env:
SLACK_RELEASE_CHANNEL: ${{ vars.SLACK_RELEASE_CHANNEL }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
script: | # js
const {
getLatestGreenCommit,
getNextPatchVersion,
hasCommitBeenReleased,
sendAutoPatchFailureMessage,
} = require('${{ github.workspace }}/release/dist/index.cjs');
const currentRelease = Number('${{ vars.CURRENT_VERSION }}');
@@ -90,25 +86,9 @@ jobs:
auto: true,
}
}).catch(console.error);
return;
} else {
console.error(`No new patch version or no green commit found for v${majorVersion}`);
}
const reason = !nextPatch
? 'no-next-patch'
: !releaseCommit
? 'no-green-commit'
: 'already-released';
console.error(`Auto-patch skipped for v${majorVersion}: ${reason}`);
await sendAutoPatchFailureMessage({
channelName: '${{ vars.SLACK_RELEASE_CHANNEL }}',
majorVersion,
reason,
runId: '${{ github.run_id }}',
owner: context.repo.owner,
repo: context.repo.repo,
}).catch(console.error);
}
if (context.eventName === 'workflow_dispatch') {

View File

@@ -9,7 +9,7 @@ jobs:
check-release-status:
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
@@ -44,7 +44,7 @@ jobs:
});
open-backport-reminder:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

View File

@@ -53,14 +53,9 @@ on:
type: boolean
default: false
permissions:
id-token: write
contents: read
actions: read
jobs:
green-check-stub:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 2
steps:
- name: Stub
@@ -68,7 +63,7 @@ jobs:
check-version:
# skips everything if the tag_name is 'none' because all later jobs depend on this job
if: ${{ inputs.tag_name != 'none' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
ee: ${{ fromJson(steps.canonical_version.outputs.result).ee }}
@@ -143,7 +138,7 @@ jobs:
throw new Error("No edition selected to tag");
copy-to-s3:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
needs: check-version
timeout-minutes: 5
strategy:
@@ -151,9 +146,10 @@ jobs:
edition: ${{ fromJson(needs.check-version.outputs.edition_matrix) }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- name: Determine the source path
uses: actions/github-script@v7
@@ -225,7 +221,7 @@ jobs:
fi
tag-docker-image:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
needs: check-version
timeout-minutes: 5
strategy:
@@ -247,7 +243,7 @@ jobs:
fi
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_RELEASE_USERNAME }}
password: ${{ secrets.DOCKERHUB_RELEASE_TOKEN }}
@@ -259,10 +255,9 @@ jobs:
$DOCKERHUB_REPO\:$DOCKERHUB_VERSION
push-git-tags:
permissions:
contents: write
permissions: write-all
needs: check-version
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
@@ -291,9 +286,9 @@ jobs:
git tag -f ${{ inputs.tag_name }} ${{ needs.check-version.outputs.oss }}
git push origin tag -f ${{ inputs.tag_name }}
update-version-info-latest:
if: ${{ inputs.tag_name == 'latest' }} # only update version-info.json for the 'latest' tag
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
update-version-info:
if: ${{ !inputs.dot-x-tag }}
runs-on: ubuntu-22.04
needs: check-version
timeout-minutes: 5
strategy:
@@ -304,9 +299,10 @@ jobs:
AWS_REGION: ${{ vars.AWS_REGION }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
@@ -315,11 +311,13 @@ jobs:
.github
- name: Prepare release scripts
uses: ./.github/actions/prepare-release-scripts
- name: Update latest in version-info.json
- name: Generate new version info
uses: actions/github-script@v7
id: new_version_info
with:
result-encoding: string
script: | # js
const { updateVersionInfoLatest } = require('${{ github.workspace }}/release/dist/index.cjs');
const { updateVersionInfoChannel } = require('${{ github.workspace }}/release/dist/index.cjs');
const fs = require('fs');
const edition = '${{ matrix.edition }}';
@@ -328,7 +326,8 @@ jobs:
? '${{ needs.check-version.outputs.ee }}'
: '${{ needs.check-version.outputs.oss }}';
const newVersionInfo = await updateVersionInfoLatest({
const newVersionInfo = await updateVersionInfoChannel({
channel: '${{ inputs.tag_name }}',
newVersion: canonical_version,
rollout: ${{ inputs.tag_rollout }},
});

View File

@@ -14,14 +14,14 @@ concurrency:
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
release_source: ${{ steps.changes.outputs.release_source }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -30,14 +30,14 @@ jobs:
release-type-check:
needs: files-changed
if: needs.files-changed.outputs.release_source == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: "release"
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}
@@ -49,14 +49,14 @@ jobs:
release-tests-unit:
needs: files-changed
if: needs.files-changed.outputs.release_source == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: "release"
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}
@@ -68,14 +68,14 @@ jobs:
release-test-build:
needs: files-changed
if: needs.files-changed.outputs.release_source == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: "release"
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -11,6 +11,14 @@ on:
commit:
description: 'A full-length commit SHA-1 hash'
required: true
release-channel:
description: 'The release channel to publish to (optional)'
type: choice
options:
- none
- latest
required: true
default: 'none'
auto:
description: 'auto-patch release DO NOT SET MANUALLY'
type: boolean
@@ -25,21 +33,22 @@ on:
description: 'A full-length commit SHA-1 hash'
type: string
required: true
release-channel:
description: 'The release channel to publish to (optional)'
type: string
# options:
# - none
# - latest
required: true
default: 'none'
auto:
description: 'auto-patch release DO NOT SET MANUALLY'
type: boolean
default: false
permissions:
id-token: write
contents: write
checks: write
issues: write
actions: read
jobs:
check-version:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
ee: ${{ fromJson(steps.canonical_version.outputs.result).ee }}
@@ -95,7 +104,7 @@ jobs:
return versions
publish-start-message:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
needs: [check-version]
timeout-minutes: 5
steps:
@@ -126,7 +135,7 @@ jobs:
download-uberjar:
needs: check-version
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
strategy:
matrix:
@@ -169,7 +178,7 @@ jobs:
./version.properties
upload-to-s3:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
needs: download-uberjar
timeout-minutes: 15
strategy:
@@ -177,9 +186,10 @@ jobs:
edition: [oss, ee]
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
@@ -220,7 +230,7 @@ jobs:
--paths "/${{ steps.version_path.outputs.result }}/*"
verify-s3-download:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
needs: upload-to-s3
timeout-minutes: 15
strategy:
@@ -275,8 +285,9 @@ jobs:
release: "true"
push-tags:
permissions: write-all
needs: [verify-s3-download, containerize-oss, containerize-ee, check-version]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
@@ -309,7 +320,7 @@ jobs:
get-extra-tags:
needs: [ check-version ]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
outputs:
extra_tags: ${{ steps.taglist.outputs.extra_tags }}
steps:
@@ -360,7 +371,7 @@ jobs:
trigger-cloud-issues:
if: ${{ !inputs.auto }}
needs: push-tags
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
@@ -412,7 +423,7 @@ jobs:
needs:
- containerize-ee
- verify-s3-download
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/github-script@v7
@@ -435,8 +446,9 @@ jobs:
draft-release-notes:
if: ${{ !inputs.auto }}
needs: add-extra-tags
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
timeout-minutes: 15
permissions: write-all
env:
AWS_S3_DOWNLOADS_BUCKET: ${{ vars.AWS_S3_DOWNLOADS_BUCKET }}
DOCKERHUB_OWNER: ${{ vars.DOCKERHUB_OWNER }}
@@ -502,7 +514,7 @@ jobs:
}).catch(console.warn);
publish-version-info:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
runs-on: ubuntu-22.04
needs: [push-tags, check-version]
timeout-minutes: 15
strategy:
@@ -518,9 +530,10 @@ jobs:
echo "skipping version info update for nightly patch"
exit 0
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
@@ -565,9 +578,20 @@ jobs:
--distribution-id ${{ vars.AWS_CLOUDFRONT_STATIC_ID }} \
--paths "/version-info.json" "/version-info-ee.json"
set-release-channel:
needs: publish-version-info
uses: ./.github/workflows/release-tag.yml
secrets: inherit
with:
version: ${{ inputs.version }}
tag_name: ${{ inputs.release-channel }}
tag_rollout: 100
tag_ee: true
tag_oss: true
publish-complete-message:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
needs: [add-extra-tags]
runs-on: ubuntu-22.04
needs: [set-release-channel, add-extra-tags]
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
@@ -598,7 +622,8 @@ jobs:
manage-milestones:
if: ${{ !inputs.auto }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
permissions: write-all
runs-on: ubuntu-22.04
needs: [draft-release-notes, publish-version-info] # this will ensure that the milestone stays open until the release notes are published
timeout-minutes: 15
steps:

Some files were not shown because too many files have changed in this diff Show More