bf2343b7e4
Integration Tests - MySQL + Elasticsearch / Detect Changes (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / integration-tests-mysql-elasticsearch (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / integration-tests-postgres-elasticsearch-redis (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / integration-tests-postgres-opensearch (push) Has been cancelled
Java Checkstyle / java-checkstyle (push) Has been cancelled
Maven Collate Tests / maven-collate-ci (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status (push) Has been cancelled
Publish Package to Maven Central Repository / publish-maven-packages (push) Has been cancelled
OpenMetadata Service Unit Tests / Detect Changes (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / k8s_operator-unit-tests (push) Has been cancelled
559 lines
31 KiB
Markdown
559 lines
31 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## About OpenMetadata
|
||
|
||
OpenMetadata is a unified metadata platform for data discovery, data observability, and data governance. This is a multi-module project with Java backend services, React frontend, Python ingestion framework, and comprehensive Docker infrastructure.
|
||
|
||
For architecture deep dives, entity/repository/resource patterns, and end-to-end checklists for adding new entities or connectors, see [DEVELOPER.md](DEVELOPER.md).
|
||
|
||
## Architecture Overview
|
||
|
||
- **Backend**: Java 21 + Dropwizard REST API framework, multi-module Maven project
|
||
- **Frontend**: React + TypeScript, built with Webpack and Yarn; component library via `openmetadata-ui-core-components` (Tailwind CSS v4 with `tw:` prefix, react-aria-components foundation)
|
||
- **Ingestion**: Python 3.10-3.11 with Pydantic 2.x, 75+ data source connectors
|
||
- **Database**: MySQL (default) or PostgreSQL with Flyway migrations
|
||
- **Search**: Elasticsearch 7.17+ or OpenSearch 2.6+ for metadata discovery
|
||
- **Infrastructure**: Apache Airflow for workflow orchestration
|
||
|
||
## Environment Setup
|
||
|
||
### Python Virtual Environment (REQUIRED)
|
||
|
||
**You MUST activate the Python venv before any Python work.** OpenMetadata supports Python 3.10-3.11; 3.11 is recommended.
|
||
|
||
```bash
|
||
# First-time setup (creates venv at repo root):
|
||
# python3.11 -m venv env
|
||
|
||
# ALWAYS activate before running Python, make generate, make install_dev, etc:
|
||
source env/bin/activate
|
||
|
||
# Verify:
|
||
python --version # Should show Python 3.10.x or 3.11.x
|
||
```
|
||
|
||
**In worktrees**: When Claude Code creates a Git worktree, the venv from the main repo is NOT copied. You need to either:
|
||
- Create a new venv in the worktree: `python3.11 -m venv env && source env/bin/activate && cd ingestion && make install_dev`
|
||
- Or symlink the main repo's venv: `ln -s /path/to/main-repo/env env`
|
||
|
||
### Initial Dev Environment Setup
|
||
|
||
After activating the venv, install all dependencies:
|
||
|
||
```bash
|
||
source env/bin/activate
|
||
|
||
# Install ingestion module with all dev dependencies (required before make generate)
|
||
cd ingestion
|
||
make install_dev_env # Full dev environment (edit mode + all extras)
|
||
# OR for lighter install:
|
||
make install_dev # Just dev dependencies
|
||
cd ..
|
||
|
||
# Generate Pydantic models from JSON schemas (required after schema changes)
|
||
make generate
|
||
|
||
# Install UI dependencies
|
||
make yarn_install_cache
|
||
```
|
||
|
||
### Other Environment Notes
|
||
|
||
- **Java**: Java 21 required. Use `mvn` (Maven) for backend builds.
|
||
- **Node/Yarn**: Use `yarn` (not `npm`) for frontend. Frontend root is `openmetadata-ui/src/main/resources/ui/`.
|
||
- **Docker services**: Development services (MySQL, Elasticsearch, etc.) run via `docker/development/docker-compose.yml`:
|
||
```bash
|
||
docker compose -f docker/development/docker-compose.yml up -d
|
||
```
|
||
|
||
## Essential Development Commands
|
||
|
||
### Prerequisites and Setup
|
||
```bash
|
||
make prerequisites # Check system requirements
|
||
source env/bin/activate # ALWAYS activate venv first
|
||
cd ingestion && make install_dev_env # Install Python dev dependencies
|
||
make generate # Generate Pydantic models from JSON schemas
|
||
make yarn_install_cache # Install UI dependencies
|
||
```
|
||
|
||
### Frontend Development
|
||
```bash
|
||
cd openmetadata-ui/src/main/resources/ui
|
||
yarn start # Start development server on localhost:3000
|
||
yarn test # Run Jest unit tests
|
||
yarn test path/to/test.spec.ts # Run a specific test file
|
||
yarn test:watch # Run tests in watch mode
|
||
yarn playwright:run # Run E2E tests
|
||
yarn lint # ESLint check
|
||
yarn lint:fix # ESLint with auto-fix
|
||
yarn build # Production build
|
||
```
|
||
|
||
### Frontend CI Checkstyle (run before PR to match CI)
|
||
```bash
|
||
cd openmetadata-ui/src/main/resources/ui
|
||
yarn ui-checkstyle:changed # One-shot checkstyle for changed files (excludes tsc)
|
||
yarn organize-imports:cli <files> # Sort and organize imports
|
||
yarn lint:fix # ESLint auto-fix
|
||
yarn pretty:base --write <files> # Prettier formatting
|
||
yarn license-header-fix <files> # Add Apache 2.0 license headers
|
||
yarn i18n # Sync all 17 locale files with en-us.json
|
||
yarn generate:app-docs # Regenerate application documentation
|
||
npx tsc --noEmit # TypeScript type check (catches errors early)
|
||
```
|
||
|
||
### Backend Development
|
||
```bash
|
||
mvn clean package -DskipTests # Build without tests
|
||
mvn clean package -DonlyBackend -pl !openmetadata-ui # Backend only
|
||
mvn test # Run unit tests
|
||
mvn verify # Run integration tests
|
||
mvn spotless:apply # Format Java code
|
||
```
|
||
|
||
### Python Ingestion Development
|
||
```bash
|
||
cd ingestion
|
||
make install_dev_env # Install in development mode
|
||
make generate # Generate Pydantic models from JSON schemas
|
||
make unit_ingestion_dev_env # Run unit tests
|
||
make py_format # Apply ruff lint-fix + format
|
||
make py_format_check # Verify lint + format (matches CI; catches non-auto-fixable issues)
|
||
make static-checks # Run type checking with basedpyright
|
||
```
|
||
|
||
### Full Local Environment
|
||
```bash
|
||
./docker/run_local_docker.sh -m ui -d mysql # Complete local setup with UI
|
||
./docker/run_local_docker.sh -m no-ui -d postgresql # Backend only with PostgreSQL
|
||
./docker/run_local_docker.sh -s true # Skip Maven build step
|
||
```
|
||
|
||
### Testing
|
||
```bash
|
||
make run_e2e_tests # Full E2E test suite
|
||
make unit_ingestion # Python unit tests with coverage
|
||
yarn test:coverage # Frontend test coverage
|
||
```
|
||
|
||
### Backend Integration Tests
|
||
All backend API integration tests MUST be placed in `openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/` directory. Tests should:
|
||
- Use naming convention `*IT.java` (Integration Test)
|
||
- Extend `BaseEntityIT<T, K>` for entity CRUD tests
|
||
- Be designed to run concurrently (use `@Execution(ExecutionMode.CONCURRENT)`)
|
||
- Use `TestNamespace` for test isolation
|
||
- Use `SdkClients` for API calls (e.g., `SdkClients.adminClient().tables().create(...)`)
|
||
|
||
```bash
|
||
# Run a specific integration test
|
||
mvn test -pl openmetadata-integration-tests -Dtest=TaskResourceIT
|
||
|
||
# Run all integration tests
|
||
mvn test -pl openmetadata-integration-tests
|
||
```
|
||
|
||
## Code Generation and Schemas
|
||
|
||
OpenMetadata uses a schema-first approach with JSON Schema definitions driving code generation:
|
||
|
||
```bash
|
||
make generate # Generate all models from schemas
|
||
make py_antlr # Generate Python ANTLR parsers
|
||
make js_antlr # Generate JavaScript ANTLR parsers
|
||
yarn parse-schema # Parse JSON schemas for frontend (connection and ingestion schemas)
|
||
```
|
||
|
||
### Schema Architecture
|
||
- **Source schemas** in `openmetadata-spec/` define the canonical data models
|
||
- **Connection schemas** are pre-processed at build time via `parseSchemas.js` to resolve all `$ref` references
|
||
- **Application schemas** in `openmetadata-ui/.../ApplicationSchemas/` are resolved at runtime using `schemaResolver.ts`
|
||
- JSON schemas with `$ref` references to external files require resolution before use in forms
|
||
|
||
## Key Directories
|
||
|
||
- `openmetadata-service/` - Core Java backend services and REST APIs
|
||
- `openmetadata-ui/src/main/resources/ui/` - React frontend application
|
||
- `ingestion/` - Python ingestion framework with connectors
|
||
- `openmetadata-spec/` - JSON Schema specifications for all entities
|
||
- `bootstrap/sql/` - Database schema migrations and sample data
|
||
- `conf/` - Configuration files for different environments
|
||
- `docker/` - Docker configurations for local and production deployment
|
||
|
||
## Development Workflow
|
||
|
||
1. **Schema Changes**: Modify JSON schemas in `openmetadata-spec/`, then run `mvn clean install` on openmetadata-spec to update models
|
||
2. **Backend**: Develop in Java using Dropwizard patterns, test with `mvn test`, format with `mvn spotless:apply`
|
||
3. **Frontend**: Use React/TypeScript with components from `openmetadata-ui-core-components`, test with Jest/Playwright
|
||
4. **Ingestion**: Python connectors follow plugin pattern, use `make install_dev_env` for development
|
||
5. **Full Testing**: Use `make run_e2e_tests` before major changes
|
||
|
||
## Frontend Architecture Patterns
|
||
|
||
### React Component Patterns
|
||
- **File Naming**: Components use `ComponentName.component.tsx`, interfaces use `ComponentName.interface.ts`
|
||
- **State Management**: Use `useState` with proper typing, avoid `any`
|
||
- **Side Effects**: Use `useEffect` with proper dependency arrays
|
||
- **Performance**: Use `useCallback` for event handlers, `useMemo` for expensive computations
|
||
- **Custom Hooks**: Prefix with `use`, place in `src/hooks/`, return typed objects
|
||
- **Internationalization**: Use `useTranslation` hook from react-i18next, access with `t('key')`
|
||
- **Component Structure**: Functional components only, no class components
|
||
- **Props**: Define interfaces for all component props, place in `.interface.ts` files
|
||
- **Loading States**: Use object state for multiple loading states: `useState<Record<string, boolean>>({})`
|
||
- **Error Handling**: Use `showErrorToast` and `showSuccessToast` utilities from ToastUtils
|
||
- **Navigation**: Use `useNavigate` from react-router-dom, not direct history manipulation
|
||
- **Data Fetching**: Async functions with try-catch blocks, update loading states appropriately
|
||
|
||
### State Management
|
||
- Use Zustand stores for global state (e.g., `useLimitStore`, `useWelcomeStore`)
|
||
- Keep component state local when possible with `useState`
|
||
- Use context providers for feature-specific shared state (e.g., `ApplicationsProvider`)
|
||
|
||
### Forms
|
||
|
||
- **Building forms**: New forms use the `react-hook-form` + `react-aria` stack from `@openmetadata/ui-core-components` (`getField`/`FieldProp`/`FieldTypes`/`HookForm`/`FormFields`). A form is `FieldProp[]` config objects + RHF state + a pure values→payload transform. The full reference is [`openmetadata-ui/src/main/resources/ui/docs/formutils.md`](openmetadata-ui/src/main/resources/ui/docs/formutils.md). Do not use the legacy Ant Design `getField`/`generateFormFields` from `@utils/formUtils` for new forms.
|
||
|
||
### Styling
|
||
|
||
- **Component Library**: Use components from `openmetadata-ui-core-components` for all new UI work. This is the canonical component library — do not use MUI or introduce new MUI dependencies.
|
||
- **Available Components**: Button, Input, Select, Modal, Table, Tabs, Pagination, Badge, Avatar, Checkbox, Dropdown, Form, Card, Tooltip, Toggle, Slider, Textarea, Tags, and more — all in `openmetadata-ui-core-components/src/main/resources/ui/src/components/`
|
||
- **Tailwind Classes**: All Tailwind utility classes must use the `tw:` prefix (e.g., `tw:flex`, `tw:text-sm`, `tw:bg-blue-500`) to avoid conflicts with existing Ant Design/Less styles
|
||
- **Design Tokens**: Use CSS custom properties defined in `openmetadata-ui-core-components/src/main/resources/ui/src/styles/globals.css`. Never use hardcoded color or spacing values. Semantic tokens include:
|
||
- Text: `--color-text-primary`, `--color-text-secondary`, `--color-text-tertiary`, `--color-text-error-primary`, etc.
|
||
- Border: `--color-border-primary`, `--color-border-secondary`, `--color-border-error`, `--color-border-brand`, etc.
|
||
- Background: `--color-bg-primary`, `--color-bg-secondary`, `--color-bg-error-primary`, `--color-bg-brand-solid`, etc.
|
||
- Shadows: `--shadow-xs` through `--shadow-3xl`
|
||
- Border radius: `--radius-none` through `--radius-full`
|
||
- **Color Usage**: Full token reference, dark mode guide, and anti-pattern cheat sheet: [`openmetadata-ui/src/main/resources/ui/docs/colors.md`](openmetadata-ui/src/main/resources/ui/docs/colors.md). Always consult this before choosing any color class.
|
||
- **MUI**: Do not use MUI — we are actively removing MUI from the codebase. Do not import from `@mui/*` or `@emotion/*`
|
||
- **Legacy**: Ant Design components remain in existing code but should be replaced with `openmetadata-ui-core-components` equivalents when refactoring
|
||
- Do not add unnecessary spacing between logs and code.
|
||
- In Java, avoid wildcards imports (e.g., use `import java.util.List;` instead of `import java.util.*;`)
|
||
- Custom styles in `.less` files with component-specific naming (legacy pattern, avoid for new code)
|
||
- Follow BEM naming convention for custom CSS classes when writing raw CSS
|
||
|
||
### UI considerations
|
||
|
||
- Do not use string literals at any place. You should use useTranslation hook and use it like const {t} = useTranslation(). And for example if you want to have "Run" as string, you should be using { t('label.run') }, this label is defined in locales.
|
||
|
||
|
||
### Application Configuration
|
||
- Applications use `ApplicationsClassBase` for schema loading and configuration
|
||
- Dynamic imports handle application-specific schemas and assets
|
||
- Form schemas use React JSON Schema Form (RJSF) with custom UI widgets
|
||
|
||
### Service Utilities
|
||
- Each service type has dedicated utility files (e.g., `DatabaseServiceUtils.tsx`)
|
||
- Connection schemas are imported statically and pre-resolved
|
||
- Service configurations use switch statements to map types to schemas
|
||
|
||
### Type Safety
|
||
- All API responses have generated TypeScript interfaces in `generated/`
|
||
- Custom types extend base interfaces when needed
|
||
- Avoid type assertions unless absolutely necessary
|
||
- Use discriminated unions for action types and state variants
|
||
|
||
## Database and Migrations
|
||
|
||
- Flyway handles schema migrations in `bootstrap/sql/migrations/`
|
||
- Use Docker containers for local database setup
|
||
- Default MySQL, PostgreSQL supported as alternative
|
||
- Sample data loaded automatically in development environment
|
||
|
||
## Security and Authentication
|
||
|
||
- JWT-based authentication with OAuth2/SAML support
|
||
- Role-based access control defined in Java entities
|
||
- Security configurations in `conf/openmetadata.yaml`
|
||
- Never commit secrets - use environment variables or secure vaults
|
||
|
||
## Code Generation Standards
|
||
|
||
### Comments Policy
|
||
- **Do NOT add unnecessary comments** - write self-documenting code
|
||
- **NEVER add single-line comments that describe what the code obviously does**
|
||
- Only include comments for:
|
||
- Complex business logic that isn't obvious
|
||
- Non-obvious algorithms or workarounds
|
||
- Public API JavaDoc documentation
|
||
- TODO/FIXME with ticket references
|
||
- Bad examples (NEVER do this):
|
||
- `// Create user` before `createUser()`
|
||
- `// Get client` before `SdkClients.adminClient()`
|
||
- `// Verify domain is set` before `assertNotNull(entity.getDomain())`
|
||
- `// User names are lowercased` when the code `toLowerCase()` makes it obvious
|
||
- If the code needs a comment to be understood, refactor the code to be clearer instead
|
||
|
||
### Java Code Requirements
|
||
|
||
**Always run `mvn spotless:apply` before you finish any task that touched
|
||
`.java` files.** CI runs `mvn spotless:check` and will fail the PR otherwise —
|
||
the bot's exact suggestion is "Please run `mvn spotless:apply` in the root of
|
||
your repository and commit the changes to this PR." Scope the run with
|
||
`-pl <module>` for speed if only one module changed. When asked to "fix
|
||
checkstyle" / "fix Java formatting" / "apply spotless", invoke the
|
||
`java-checkstyle` skill (see `.claude/skills/java-checkstyle/`) rather than
|
||
hand-editing formatting.
|
||
|
||
#### Method Size and Complexity (Kafka-Grade Standards)
|
||
- **Methods must be small and focused — aim for 15 lines or fewer** (excluding blank lines and braces). A method longer than that is almost always hiding multiple responsibilities; break it into smaller methods with descriptive names. "Meaningful" means each method does one nameable thing — if you can't fit the body comfortably on a screen, it's too big.
|
||
- **One return statement per method, placed at the end.** No early-return guard clauses, no scattered returns in the middle. Initialize a `result` variable, structure the work as `if/else`, or extract a helper — the control flow then stays linear and easy to reason about. (Returns inside `lambda` bodies, `switch` expressions, and anonymous classes are scoped to those constructs and don't count against the outer method.)
|
||
```java
|
||
// BAD: four scattered early returns
|
||
Map<UUID, X> compute(List<EntityInterface> entities) {
|
||
if (entities == null) return Collections.emptyMap();
|
||
if (entities.isEmpty()) return Collections.emptyMap();
|
||
if (!supportsX(entities.get(0))) return null;
|
||
Map<UUID, X> prefetched = doWork(entities);
|
||
if (prefetched.isEmpty()) return null;
|
||
return prefetched;
|
||
}
|
||
|
||
// GOOD: single trailing return; guards become extracted helpers + a result variable
|
||
Map<UUID, X> compute(List<EntityInterface> entities) {
|
||
Map<UUID, X> result = null;
|
||
if (entities != null && !entities.isEmpty() && supportsX(entities.get(0))) {
|
||
Map<UUID, X> prefetched = doWork(entities);
|
||
if (!prefetched.isEmpty()) {
|
||
result = prefetched;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
```
|
||
- **Maximum 3 levels of nesting.** Don't flatten by sprinkling early returns — extract a named helper or combine conditions into a single boolean:
|
||
```java
|
||
// BAD: deeply nested
|
||
if (entity != null) {
|
||
if (entity.isActive()) {
|
||
if (hasPermission(entity)) {
|
||
process(entity);
|
||
}
|
||
}
|
||
}
|
||
|
||
// GOOD: extract the eligibility check
|
||
if (isEligibleForProcessing(entity)) {
|
||
process(entity);
|
||
}
|
||
|
||
private boolean isEligibleForProcessing(Entity entity) {
|
||
return entity != null && entity.isActive() && hasPermission(entity);
|
||
}
|
||
```
|
||
- **Maximum 10 cyclomatic complexity.** Extract complex conditions into named methods:
|
||
```java
|
||
// BAD: complex inline boolean
|
||
if (entity.getStatus() == ACTIVE && entity.getOwner() != null
|
||
&& !entity.isDeleted() && entity.getVersion() > 0.1) { ... }
|
||
|
||
// GOOD: self-documenting
|
||
if (isEligibleForProcessing(entity)) { ... }
|
||
```
|
||
- **Maximum 5 parameters.** Introduce a parameter object or builder for more.
|
||
- **Each method does one thing.** If you can describe what a method does using "and" or "then", it should be two methods.
|
||
|
||
#### Naming and Readability
|
||
- Names should make code read like prose — if you need a comment, the name isn't good enough
|
||
- **Methods**: verb phrases — `calculateScore()`, `findByName()`, `isValid()`
|
||
- **Booleans**: question-form — `isActive`, `hasPermission`, `canRetry` (never `flag`, `status`, `check`)
|
||
- **Variables**: descriptive, no abbreviations — `entityReference` not `er`, `retryCount` not `rc`
|
||
- **Constants**: `UPPER_SNAKE_CASE` — `MAX_RETRY_COUNT`, `DEFAULT_PAGE_SIZE`
|
||
- **No single-letter variables** except in short lambdas or loop indices
|
||
|
||
#### Immutability and Defensive Design
|
||
- Use `final` on local variables and parameters that don't change (which is most of them)
|
||
- Use `final` on fields set in the constructor
|
||
- Return `Collections.unmodifiableList()` / `List.copyOf()` from public methods, never expose internal mutable collections
|
||
- Utility classes must be `final` with a private constructor
|
||
- Prefer `record` for immutable data carriers where appropriate
|
||
|
||
#### Error Handling
|
||
- **No empty catch blocks** — at minimum, log the exception
|
||
- **No `catch (Exception e)`** — catch the specific type you expect
|
||
- **No `e.printStackTrace()`** — use the logger
|
||
- **Error messages must include context**: `"Table '%s' not found in database '%s'"` not just `"Not found"`
|
||
- **No `throw` or `return` inside `finally` blocks** — they mask the original exception
|
||
- **No exceptions for flow control** — use conditionals for expected cases
|
||
|
||
#### No Magic Strings — Define Constants
|
||
- **Never use raw string literals in `.equals()`, `.contains()`, or `switch` cases** — define a constant or use an existing enum
|
||
- If an enum already exists in `openmetadata-spec/` schemas for those values, use it
|
||
- If the same string appears in more than one place, it must be a named constant
|
||
- **One definition, one location** — don't define the same constant in multiple classes
|
||
- Prefer enums over string constants when the values form a closed set:
|
||
```java
|
||
// BAD: magic strings scattered everywhere
|
||
if (taskStatus.equals("Open")) { ... }
|
||
if (config.getResources().get(0).equals("all")) { ... }
|
||
|
||
// GOOD: use existing enums or define constants
|
||
if (taskStatus == TaskStatus.OPEN) { ... }
|
||
private static final String RESOURCE_ALL = "all";
|
||
```
|
||
|
||
#### No Convoluted if/else Chains
|
||
- **More than 3 `else if` branches means the structure is wrong — refactor:**
|
||
- `else if` chain on `instanceof` → `switch` with pattern matching (Java 21)
|
||
- `else if` chain on enum values → `switch` expression
|
||
- `else if` chain on `.equals("string")` → `Map` dispatch or enum lookup
|
||
- `else if` chain on `.contains("string")` → `Map` or list of predicates
|
||
- **Repeated compound conditions** (same multi-part `&&`/`||` expression in multiple places) → extract into a named method or `Set.contains()`
|
||
```java
|
||
// BAD: 3-part condition repeated 3 times across the file
|
||
if (!tenantId.equals("common") && !tenantId.equals("organizations")
|
||
&& !tenantId.equals("consumers")) { ... }
|
||
|
||
// GOOD: define once, use everywhere
|
||
private static final Set<String> MULTI_TENANT_IDS =
|
||
Set.of("common", "organizations", "consumers");
|
||
|
||
private boolean isSingleTenant(String tenantId) {
|
||
return !MULTI_TENANT_IDS.contains(tenantId);
|
||
}
|
||
```
|
||
|
||
#### No Code Duplication
|
||
- If the same logic exists in two places, extract to a shared method
|
||
- Near-identical methods (e.g., same logic for OpenSearch and ElasticSearch) should share a common implementation with only the engine-specific parts varying
|
||
- Copy-pasted blocks within the same file should be extracted into a parameterized method
|
||
|
||
#### Class Size
|
||
- **Classes should be under 500 lines.** Over 1000 lines is a design problem.
|
||
- If a class is large, look for clusters of methods that operate on the same subset of fields — extract them into a new focused class
|
||
- Resource classes should be thin orchestrators
|
||
- Repository classes handle data access, not business logic
|
||
|
||
#### Modern Java (Java 21)
|
||
- Use try-with-resources for all `AutoCloseable` objects
|
||
- Use diamond operator `<>` — `new ArrayList<>()` not `new ArrayList<String>()`
|
||
- Use pattern matching: `if (obj instanceof String s)` instead of cast
|
||
- Use `switch` expressions instead of `if/else if` chains on enums or types
|
||
- Use `List.of()`, `Map.of()`, `Set.of()` for immutable collection literals
|
||
- Use `Optional` correctly: never as a field type, never as a parameter, never assign `null` to it
|
||
- Use text blocks `"""` for multi-line strings
|
||
- **Use `SequencedCollection` accessors on Lists/Deques** — `list.getFirst()` / `list.getLast()` (Java 21) instead of `list.get(0)` / `list.get(list.size() - 1)`. Same for `removeFirst()` / `removeLast()`. Reads more clearly and avoids off-by-one indexing.
|
||
- **Collection emptiness: use the project's `nullOrEmpty(...)` helper** from `org.openmetadata.common.utils.CommonUtil` instead of hand-rolling `coll != null && !coll.isEmpty()` (or its negation). It's the established idiom across this codebase, handles `null` correctly, and reads as a single semantic check. Same applies to `String` checks — use `nullOrEmpty(str)` not `str != null && !str.isEmpty()`.
|
||
```java
|
||
// BAD
|
||
if (entities != null && !entities.isEmpty()) {
|
||
process(entities.get(0));
|
||
}
|
||
|
||
// GOOD
|
||
if (!nullOrEmpty(entities)) {
|
||
process(entities.getFirst());
|
||
}
|
||
```
|
||
|
||
#### Common Bug Patterns to Avoid
|
||
- `equals()` without `hashCode()` (or vice versa)
|
||
- `equals()` on arrays — use `Arrays.equals()`
|
||
- Ignoring return values of `String.replace()`, `File.delete()`
|
||
- `collection.size() == 0` — use `collection.isEmpty()`
|
||
- String concatenation inside loops — use `StringBuilder`
|
||
- `synchronized` on non-final fields — the lock reference can change
|
||
- `toLowerCase()` without `Locale` — always use `toLowerCase(Locale.ROOT)`
|
||
- Double map lookups — use `computeIfAbsent()` or `getOrDefault()`
|
||
|
||
#### Testing
|
||
- Generate production-ready code, not tutorial code
|
||
- Create integration tests in `openmetadata-integration-tests` for new API endpoints
|
||
- **Never use `Thread.sleep()` in tests** — use condition-based waiting or `Awaitility`
|
||
- Bug fixes must include a test that fails without the fix
|
||
- 90% line coverage target on changed classes
|
||
|
||
#### Structure
|
||
- Do not use Fully Qualified Names in code (e.g., `org.openmetadata.schema.type.Status`) — import the class instead
|
||
- Do not import wildcard packages — import exactly the required classes
|
||
- No commented-out code — version control maintains history
|
||
- No TODOs without a ticket reference
|
||
- One statement per line — no `if (x) return y;` on one line
|
||
|
||
### TypeScript/Frontend Code Requirements
|
||
|
||
**Always run the UI checkstyle sequence before you finish any task that
|
||
touched `.ts`/`.tsx`/`.js`/`.jsx`/`.json` under
|
||
`openmetadata-ui/src/main/resources/ui/src/`, `.../playwright/`, or
|
||
`openmetadata-ui-core-components/src/main/resources/ui/src/`.** CI's
|
||
`UI Checkstyle / lint-src|lint-playwright|lint-core-components` jobs fail the
|
||
PR otherwise. The order matters — run `organize-imports-cli`, then
|
||
`eslint --fix`, then `prettier --write`; reversing organize-imports and
|
||
prettier leaves a dirty diff (organize-imports uses 4-space indentation,
|
||
prettier uses 2 + trailing commas). When asked to "fix UI checkstyle" / "run
|
||
prettier" / "fix UI lint", invoke the `ui-checkstyle` skill (see
|
||
`.claude/skills/ui-checkstyle/`) rather than hand-editing formatting.
|
||
|
||
- **NEVER use `any` type** in TypeScript code - always use proper types
|
||
- Use `unknown` when the type is truly unknown and add type guards
|
||
- Import types from existing type definitions (e.g., `RJSFSchema` from `@rjsf/utils`)
|
||
- Add `// eslint-disable-next-line` comments only when absolutely necessary
|
||
- **Import Organization** — use `yarn organize-imports:cli` to auto-sort. Order:
|
||
1. External libraries (React, etc.)
|
||
2. Internal absolute imports from `generated/`, `constants/`, `hooks/`, etc.
|
||
3. Relative imports for utilities and components
|
||
4. Asset imports (SVGs, styles)
|
||
5. Type imports grouped separately when needed
|
||
|
||
#### CI Checkstyle Rules (enforced on every PR)
|
||
These checks run automatically in CI. Code that violates them **will not merge**.
|
||
- **No `console.log/warn/error`** — `no-console` rule is enforced. Use the logger or remove.
|
||
- **Use `===` not `==`** — `eqeqeq` (smart mode, except for `null` checks)
|
||
- **Max 200 characters per line** — break long lines
|
||
- **Self-closing components** — `<Div />` not `<Div></Div>`
|
||
- **Sort JSX props alphabetically** — callbacks last
|
||
- **Space after `//` in comments** — `// comment` not `//comment`
|
||
- **Blank lines** before `function`, `class`, `export`, `return` statements
|
||
- **Use `it()` consistently in tests** — don't mix `test()` and `it()`
|
||
- **Blank lines around `describe`, `it`, `beforeEach`** in test files
|
||
- **JSON keys sorted alphabetically** in locale files (`src/locale/**/*.json`)
|
||
- **Apache 2.0 license header** on every new source file — run `yarn license-header-fix`
|
||
- **i18n keys synced** — after adding keys to `en-us.json`, run `yarn i18n` to sync all 17 locales
|
||
- **Prettier formatting** — 2-space indent, single quotes, strict HTML whitespace
|
||
|
||
#### Playwright Test Rules (lint-playwright)
|
||
- **No `waitForLoadState('networkidle')`** — flaky, use web-first assertions
|
||
- **No `page.pause()`** — remove before committing
|
||
- **No `.only` on tests** — blocks all other tests in CI
|
||
- Prefer `expect(locator).toBeVisible()` over manual `waitForSelector` checks
|
||
- Don't use `{ force: true }` — fix the locator instead
|
||
- Use locators, not element handles
|
||
|
||
### Python Code Requirements
|
||
- **Use pytest, not unittest** - write tests using pytest style with plain `assert` statements
|
||
- Use pytest fixtures for test setup instead of `setUp`/`tearDown` methods
|
||
- Use `unittest.mock` for mocking (MagicMock, patch) - this is compatible with pytest
|
||
- Test classes should not inherit from `TestCase` - use plain classes prefixed with `Test`
|
||
- Use `assert x == y` instead of `self.assertEqual(x, y)`
|
||
- Use `assert x is None` instead of `self.assertIsNone(x)`
|
||
- Use `assert "text" in string` instead of `self.assertIn("text", string)`
|
||
|
||
### Python Ingestion Connector Guidelines
|
||
- **Keep connector-specific logic in connector-specific files**, not in generic/shared files like `builders.py`
|
||
- Example: Redshift IAM auth should be in `ingestion/src/metadata/ingestion/source/database/redshift/connection.py`, not in `ingestion/src/metadata/ingestion/connections/builders.py`
|
||
- This keeps the codebase modular and prevents generic utilities from becoming cluttered with connector-specific edge cases
|
||
- **Use `model_str()` for Pydantic RootModel to string conversion** — OpenMetadata schema types like `ColumnName`, `EntityName`, `FullyQualifiedEntityName`, and `UUID` are Pydantic `RootModel[str]` subclasses where `str()` returns `"root='value'"` instead of the raw value. Always use `model_str()` from `metadata.ingestion.ometa.utils` instead of manual `hasattr(x, "root")` / `str(x.root)` checks.
|
||
|
||
### Caching
|
||
- **All caches MUST be bounded.** Never use a bare `dict` / `HashMap` / `Map` as a cache without an explicit size cap — they grow with the input and cause OOMs on large catalogs/ingestions. The only exception is when the user explicitly asks for an unbounded cache for a specific case.
|
||
- Pick a sane default (typically 100–1000 entries depending on entity size); if you're unsure, ask the user.
|
||
- **Python**: use `collections.OrderedDict` with `popitem(last=False)` eviction after insert, `@functools.lru_cache(maxsize=N)`, or `cachetools.LRUCache`. Cache both hits and misses (negative caching) — repeated unresolvable lookups are a common hot path.
|
||
- **Java**: use Caffeine (`Caffeine.newBuilder().maximumSize(N).build()`) or Guava `CacheBuilder.newBuilder().maximumSize(N).build()`. Never a bare `HashMap`.
|
||
- **TypeScript**: use `lru-cache` — never a bare `Map` or plain object.
|
||
- **Before adding a cache, check whether the underlying call is already cached at a lower layer.** Example: `OpenMetadata._search_es_entity` is `@lru_cache(maxsize=512)`, so wrapping `get_entity_from_es` / `es_search_container_by_path` calls in a local dict cache is redundant — drop the local cache and rely on the existing LRU.
|
||
|
||
### Testing Philosophy
|
||
- **Test real behavior, not mock wiring** - if a test requires mocking 3+ classes just to verify a method call, it's testing the wrong thing
|
||
- **Prefer integration tests** over heavily-mocked unit tests. This project has full integration test infrastructure (OpenMetadataApplicationTest, Docker containers, real OpenSearch). Use it.
|
||
- **Mocks are for boundaries, not internals** - mock external services (HTTP clients, third-party APIs), not your own classes. If you're mocking static methods left and right to test internal plumbing, write an integration test instead.
|
||
- **A test that mocks everything proves nothing** - it only verifies that your mocks are wired correctly, not that the system works
|
||
- **Ask "what breaks if this test passes but the code is wrong?"** - if the answer is "nothing, because everything real is mocked out", delete the test and write a better one
|
||
- **Test the outcome, not the implementation** - assert on observable results (API responses, database state, stats values) rather than verifying internal method calls with `verify()`
|
||
|
||
### Response Format
|
||
- Provide clean code blocks without unnecessary explanations
|
||
- Assume readers are experienced developers
|
||
- Focus on functionality over education
|