Compare commits

...

3 Commits

Author SHA1 Message Date
Aleksandr Lesnenko
6292c4a132 Fix duplicate chart metrics when removing a filter 2025-04-03 22:47:30 -04:00
Aleksandr Lesnenko
53f8c2b626 add more info to claude.md 2025-04-03 22:30:06 -04:00
Filipe Silva
19b88404d6 devex: add claude tooling 2025-04-03 11:07:20 +01:00
9 changed files with 168 additions and 3 deletions

View File

@@ -0,0 +1 @@
Fix flakey test $ARGUMENTS

View File

@@ -0,0 +1 @@
Fix issue $ARGUMENTS

View File

@@ -0,0 +1 @@
Fetch unresolved comments for this branch's PR, then fix them

View File

@@ -0,0 +1 @@
Repro issue $ARGUMENTS in a failing test

1
.gitignore vendored
View File

@@ -132,3 +132,4 @@ modules/drivers/*/.lsp/*
.aider*
/config.yml
CLAUDE.local.md

111
CLAUDE.md Normal file
View File

@@ -0,0 +1,111 @@
# Metabase Development Guide
## Autonomous Development Workflow
- Do not attempt to read or edit files outside the project folder
- Add failing tests first, then fix them
- Work autonomously in small, testable increments
- Run targeted tests, and lint continuously during development
- Prioritize understanding existing patterns before implementing
- Don't commit changes, leave it for the user to review and make commits
## Quick Commands
### JavaScript/TypeScript
- **Lint:** `yarn lint-eslint`
- **Test:** `yarn test-unit path/to/file.unit.spec.js` or `yarn test-unit -t "pattern"`
- **Watch:** `yarn test-unit-watch path/to/file.unit.spec.js`
- **Format:** `yarn prettier`
- **Type Check:** `yarn type-check`
### Clojure
- **Lint:** `./bin/mage kondo [path]`
- **Format:** `./bin/mage cljfmt-files [path]`
- **Test file:** `clojure -X:dev:test :only namespace/test-name`
### ClojureScript
- **Test:** `yarn test-cljs`
## Codebase
### Frontend Technologies
- **React** - Main frontend library
- **Redux Toolkit** - State management library
- **Mantine UI** - Customized UI component library, components are reexported from `metabase/ui`
- **Styling** - Prefer Mantine style props; only when necessary, use CSS modules
- **ECharts** - Visualization library for charts
## Testing Conventions
- Use isolated unit tests when the change is reasonable to test without complex dependencies
- Tests must not mock internal details of components/functions being tested
- Use helper factories like `createMockCard()` to create test objects
- For more complex cases, write e2e Cypress tests in `e2e/test/scenarios/`
### Mock Object Factories
- Use mock factories to create test objects
- Example: `createMockCard()` for Card objects
- Located in `frontend/src/metabase-types/api/mocks/`
```javascript
import { createMockCard } from "metabase-types/api/mocks";
const card = createMockCard({
id: 1,
name: "Test Card",
// Override other properties as needed
});
```
### Clojure Testing
- Tests are in `test/metabase/` matching the namespace structure of `src/`
- Use `deftest` for test definitions
- Use `testing` for organizing test groups
- Use `is` for assertions
```clojure
(deftest my-function-test
(testing "should handle normal case"
(is (= expected (my-function input)))))
```
## Code Style Conventions
### JavaScript/TypeScript
- Using TypeScript for new code is preferred
- Components should be in their own directories with related files
- React functional components with hooks are preferred over class components
- Use `.tsx` extension for React components with TypeScript
- Use `.unit.spec.tsx` for component tests
### Clojure/ClojureScript
- Functions should have docstrings
- Use `cljfmt` for consistent formatting
- Use ClojureScript for frontend-facing code that needs to interoperate with the Clojure backend
## Development Workflow Tips
- Every bug fix or feature should include automated tests
- The codebase uses a combination of Clojure (backend) and JavaScript/TypeScript (frontend)
- Tests should be comprehensive and match the existing patterns
- Use the provided factories for creating test objects
- Follow existing patterns when adding new features
## Application Entities
- **Card/Question** - Saved questions/visualizations that can be displayed in dashboards
- **Dashboard** - Collections of cards/visualizations
- **Collection** - Organizational units for Cards, Dashboards, etc.
- **Database** - Connected data sources
- **Field** - Column in a database table
- **Table** - Database table
- **Segment** - Saved filters that can be applied to queries
- **Metric** - Saved aggregations that can be used in queries

11
bin/claude-prompt Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: claude-prompt <prompt>"
echo " Launches Claude in non-interactive mode with Metabase-specific tools and the given prompt."
exit 1
fi
set -o xtrace
claude -p "$(printf "%q " "$@")" --allowedTools "Bash(./bin/mage:*)" "Bash(clj-kondo:*)" "Bash(clojure:*)", "Bash(grep:*)" "Bash(ls:*)" "Bash(yarn lint:*)" "Bash(yarn test-unit:*)" "WebFetchTool(domain:github.com)" Edit

View File

@@ -126,21 +126,30 @@ function syncColumns<T>({
const oldNameByKey = Object.fromEntries(
oldColumns.map((column) => [column.key, column.name]),
);
// Track which column keys we've already processed to avoid duplicates
const processedKeys = new Set<string>();
const remappedSettings = settings.reduce((settings: T[], setting) => {
const oldName = getColumnName(setting);
const oldKey = oldName && oldKeyByName[oldName];
const newName = oldKey && newNameByKey[oldKey];
if (!oldKey) {
settings.push(setting);
} else if (newName) {
// Mark this key as processed to avoid duplicates
processedKeys.add(oldKey);
settings.push(setColumnName(setting, newName));
}
return settings;
}, []);
const remappedNames = new Set(remappedSettings.map(getColumnName));
const addedSettings = newColumns
.filter(
(column) =>
!processedKeys.has(column.key) && // Skip columns we've already processed
!oldNameByKey[column.key] &&
!remappedNames.has(column.name) &&
shouldCreateSetting(column),
@@ -163,8 +172,10 @@ function syncColumnNames({
oldColumns,
shouldCreateSetting,
}: SyncColumnNamesOpts) {
return syncColumns({
settings,
// Use a Set to keep track of metric names we've already seen
const uniqueSettings = new Set(settings);
const result = syncColumns({
settings: Array.from(uniqueSettings),
newColumns,
oldColumns,
getColumnName: (setting) => setting,
@@ -172,6 +183,9 @@ function syncColumnNames({
createSetting: (column) => column.name,
shouldCreateSetting,
});
// Ensure no duplicates in the final result as well
return Array.from(new Set(result));
}
function syncTableColumns(
@@ -234,10 +248,13 @@ function syncGraphMetrics(
return settings;
}
// Remove any potential duplicates from graphMetrics before syncing
const uniqueGraphMetrics = Array.from(new Set(graphMetrics));
return {
...settings,
"graph.metrics": syncColumnNames({
settings: graphMetrics,
settings: uniqueGraphMetrics,
newColumns,
oldColumns,
shouldCreateSetting: (column) => column.isAggregation,

View File

@@ -350,6 +350,27 @@ describe("syncVizSettings", () => {
"graph.metrics": ["ID", "TAX"],
});
});
it("should handle duplicate metrics and ensure they are deduplicated when filter is removed", () => {
// This test simulates the issue reported in metabase#42447
const oldColumns: ColumnInfo[] = [
{ name: "Category", key: "Category" },
{ name: "count", key: "count", isAggregation: true },
];
const newColumns: ColumnInfo[] = [
{ name: "Category", key: "Category" },
{ name: "count", key: "count", isAggregation: true },
];
const oldSettings = createMockVisualizationSettings({
// Simulates the duplicated metrics that can happen when filter is removed
"graph.metrics": ["count", "count"],
});
const newSettings = syncVizSettings(oldSettings, newColumns, oldColumns);
expect(newSettings).toEqual({
"graph.metrics": ["count"],
});
});
});
describe("pivot_table", () => {