Compare commits

...

3 Commits

Author SHA1 Message Date
Chris Truter
27ede26aad Assign Metabot's modules to @metabase/metabot in CODEOWNERS 2026-09-17 05:04:09 -04:00
Chris Truter
85c2472a29 Regenerate CODEOWNERS for the modules master added
Master declared `embedding-hub` and `warehouse-schema-overlay` while this branch sat below it, so the
generated block gains their (commented-out) stanzas.

The `:discouraged-namespace` budget is re-seeded at 70: master's shrink workflow lowered it to 69
in the meantime, and this branch adds one ignore, for cheshire in `mage.modules`.
2026-09-17 02:02:57 -04:00
Chris Truter
4009a23334 Generate CODEOWNERS from module ownership
`.github/CODEOWNERS` grows a block between BEGIN/END markers, one stanza per module, built from the
module's `:team` in `config.edn` and that team's `:assignee` handle in `team.json`. `./bin/mage
update-codeowners` rewrites it; `--check` exits non-zero when it is stale, for CI. Lines outside the
markers stay hand-maintained.

A stanza is live only when its team has an `:assignee` and the module is not `:suppress-codeowners`.
Everything else is emitted commented out, carrying the reason, so enabling a team is a matter of adding
its handle rather than working out which paths it owns. Two teams have handles today, Querying Platform
and Graphy, which makes 47 of the 209 modules live.

The `:discouraged-namespace` budget goes 71 -> 72 for cheshire: mage runs under Babashka, where
`metabase.util.json` is not on the classpath, which is why `mage.jar-download` carries the same ignore.
2026-09-17 01:55:07 -04:00
6 changed files with 1083 additions and 1 deletions

View File

@@ -16,7 +16,7 @@
:deprecated-namespace 95
:deprecated-var 95
:discouraged-java-method 4
:discouraged-namespace 69
:discouraged-namespace 70
:discouraged-var 130
:duplicate-require 1
:equals-true 1

840
.github/CODEOWNERS vendored
View File

@@ -67,3 +67,843 @@ frontend/src/metabase/plugins/oss/custom-viz.ts @metabase/core-frontend-gadget
frontend/src/metabase/visualizations/custom-visualizations @metabase/core-frontend-gadget
frontend/test/__support__/custom-viz-fixtures @metabase/core-frontend-gadget
.github/workflows/release-custom-viz.yml @metabase/devexp @metabase/core-frontend-gadget
# ===== BEGIN generated module ownership (./bin/mage update-codeowners) =====
#
# Generated by `./bin/mage update-codeowners`. Do not edit by hand.
# Ownership comes from each module's :team (.clj-kondo/config/modules/config.edn) and that
# team's :assignee handle (.github/team.json). A stanza is live when its team has an :assignee
# and the module is not :suppress-codeowners; otherwise it is commented out, ready to enable.
#
# actions (Gadget, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/action_v2
# enterprise/backend/test/metabase_enterprise/action_v2
# advanced-config (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/advanced_config
# enterprise/backend/test/metabase_enterprise/advanced_config
# permissions (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/advanced_permissions
# enterprise/backend/test/metabase_enterprise/advanced_permissions
# metabot.agent-api (Metabot)
enterprise/backend/src/metabase_enterprise/agent_api @metabase/metabot
enterprise/backend/test/metabase_enterprise/agent_api @metabase/metabot
# analytics (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/analytics
# enterprise/backend/test/metabase_enterprise/analytics
# api (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/api
# enterprise/backend/test/metabase_enterprise/api
# api-routes (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/api_routes
# audit-app (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/audit_app
# enterprise/backend/test/metabase_enterprise/audit_app
# sso.auth-identity (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/auth_identity
# enterprise/backend/test/metabase_enterprise/auth_identity
# auth-provider (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/auth_provider
# enterprise/backend/test/metabase_enterprise/auth_provider
# billing (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/billing
# enterprise/backend/test/metabase_enterprise/billing
# cache (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/cache
# enterprise/backend/test/metabase_enterprise/cache
# cloud.add-ons (Cloud, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/cloud_add_ons
# enterprise/backend/test/metabase_enterprise/cloud_add_ons
# cloud.proxy (Cloud, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/cloud_proxy
# enterprise/backend/test/metabase_enterprise/cloud_proxy
# connection-overlays (Graphy)
enterprise/backend/src/metabase_enterprise/connection_overlays @metabase/graphy
# content-translation (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/content_translation
# enterprise/backend/test/metabase_enterprise/content_translation
# content-verification (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/content_verification
# enterprise/backend/test/metabase_enterprise/content_verification
# core (DevEx, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/core
# custom-viz-plugin (Gadget, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/custom_viz_plugin
# enterprise/backend/test/metabase_enterprise/custom_viz_plugin
# pulse (Graphy)
enterprise/backend/src/metabase_enterprise/dashboard_subscription_filters @metabase/graphy
enterprise/backend/test/metabase_enterprise/dashboard_subscription_filters @metabase/graphy
# data-apps (Embedding, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/data_apps
# enterprise/backend/test/metabase_enterprise/data_apps
# metabot.data-complexity-score (Metabot)
enterprise/backend/src/metabase_enterprise/data_complexity_score @metabase/metabot
enterprise/backend/test/metabase_enterprise/data_complexity_score @metabase/metabot
# data-studio (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/data_studio
# enterprise/backend/test/metabase_enterprise/data_studio
# cloud.database-replication (Cloud, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/database_replication
# enterprise/backend/test/metabase_enterprise/database_replication
# database-routing (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/database_routing
# enterprise/backend/test/metabase_enterprise/database_routing
# dependencies (Graphy)
enterprise/backend/src/metabase_enterprise/dependencies @metabase/graphy
enterprise/backend/test/metabase_enterprise/dependencies @metabase/graphy
# email (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/email
# enterprise/backend/test/metabase_enterprise/email
# embedding (Embedding, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/embedding_hub
# enterprise/backend/test/metabase_enterprise/embedding_hub
# search.embeddings (Metabot)
enterprise/backend/src/metabase_enterprise/embeddings @metabase/metabot
enterprise/backend/test/metabase_enterprise/embeddings @metabase/metabot
# search.entity-retrieval (Metabot)
enterprise/backend/src/metabase_enterprise/entity_retrieval @metabase/metabot
enterprise/backend/test/metabase_enterprise/entity_retrieval @metabase/metabot
# warehouses (Gadget, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/erd
# enterprise/backend/test/metabase_enterprise/erd
# cloud.gsheets (Cloud, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/gsheets
# enterprise/backend/test/metabase_enterprise/gsheets
# cloud (Cloud, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/harbormaster
# enterprise/backend/test/metabase_enterprise/harbormaster
# permissions.impersonation (Graphy)
enterprise/backend/src/metabase_enterprise/impersonation @metabase/graphy
enterprise/backend/test/metabase_enterprise/impersonation @metabase/graphy
# analytics.internal-stats (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/internal_stats
# enterprise/backend/test/metabase_enterprise/internal_stats
# library (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/library
# enterprise/backend/test/metabase_enterprise/library
# mcp (Metabot)
enterprise/backend/src/metabase_enterprise/mcp @metabase/metabot
enterprise/backend/test/metabase_enterprise/mcp @metabase/metabot
# memoize-monitor (Graphy)
enterprise/backend/src/metabase_enterprise/memoize_monitor @metabase/graphy
enterprise/backend/test/metabase_enterprise/memoize_monitor @metabase/graphy
# metabot (Metabot)
enterprise/backend/src/metabase_enterprise/metabot @metabase/metabot
enterprise/backend/test/metabase_enterprise/metabot @metabase/metabot
# metabot.analytics (Metabot)
enterprise/backend/src/metabase_enterprise/metabot_analytics @metabase/metabot
enterprise/backend/test/metabase_enterprise/metabot_analytics @metabase/metabot
# mfa (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/mfa
# enterprise/backend/test/metabase_enterprise/mfa
# permissions.debug (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/permission_debug
# enterprise/backend/test/metabase_enterprise/permission_debug
# premium-features (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/premium_features
# enterprise/backend/test/metabase_enterprise/premium_features
# remote-sync (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/remote_sync
# enterprise/backend/test/metabase_enterprise/remote_sync
# dependencies.replacement (Graphy)
enterprise/backend/src/metabase_enterprise/replacement @metabase/graphy
enterprise/backend/test/metabase_enterprise/replacement @metabase/graphy
# permissions.sandbox (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/sandbox
# enterprise/backend/test/metabase_enterprise/sandbox
# sso.scim (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/scim
# enterprise/backend/test/metabase_enterprise/scim
# search (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/search
# enterprise/backend/test/metabase_enterprise/search
# security-center (Gadget, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/security_center
# enterprise/backend/test/metabase_enterprise/security_center
# search.semantic (Metabot)
enterprise/backend/src/metabase_enterprise/semantic_search @metabase/metabot
enterprise/backend/test/metabase_enterprise/semantic_search @metabase/metabot
# serialization (Graphy)
enterprise/backend/src/metabase_enterprise/serialization @metabase/graphy
enterprise/backend/test/metabase_enterprise/serialization @metabase/graphy
# native-query-snippets (Graphy)
enterprise/backend/src/metabase_enterprise/snippet_collections @metabase/graphy
enterprise/backend/test/metabase_enterprise/snippet_collections @metabase/graphy
# sso (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/sso
# enterprise/backend/test/metabase_enterprise/sso
# staleness (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/stale
# enterprise/backend/test/metabase_enterprise/stale
# support-access-grants (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/support_access_grants
# enterprise/backend/test/metabase_enterprise/support_access_grants
# tenants (Admin Webapp, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/tenants
# enterprise/backend/test/metabase_enterprise/tenants
# transforms (Graphy)
enterprise/backend/src/metabase_enterprise/transforms @metabase/graphy
enterprise/backend/test/metabase_enterprise/transforms @metabase/graphy
# transforms.inspector (Gadget, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/transforms_inspector
# enterprise/backend/test/metabase_enterprise/transforms_inspector
# transforms.python (Graphy)
enterprise/backend/src/metabase_enterprise/transforms_python @metabase/graphy
enterprise/backend/test/metabase_enterprise/transforms_python @metabase/graphy
# upload (UX West, no :assignee in team.json)
# enterprise/backend/src/metabase_enterprise/upload_management
# enterprise/backend/test/metabase_enterprise/upload_management
# actions (Gadget, no :assignee in team.json)
# src/metabase/actions
# test/metabase/actions
# actions.rest (Gadget, no :assignee in team.json)
# src/metabase/actions_rest
# test/metabase/actions_rest
# activity-feed (UX West, no :assignee in team.json)
# src/metabase/activity_feed
# test/metabase/activity_feed
# metabot.agent-api (Metabot)
src/metabase/agent_api @metabase/metabot
test/metabase/agent_api @metabase/metabot
# lib.agent-lib (Metabot)
src/metabase/agent_lib @metabase/metabot
test/metabase/agent_lib @metabase/metabot
# ai-tracing (Metabot)
src/metabase/ai_tracing @metabase/metabot
test/metabase/ai_tracing @metabase/metabot
# analytics (UX West, no :assignee in team.json)
# src/metabase/analytics
# test/metabase/analytics
# analytics.interface (Querying Platform)
src/metabase/analytics_interface @metabase/core-backend-querying-platform
test/metabase/analytics_interface @metabase/core-backend-querying-platform
# sync.analyze (Graphy)
src/metabase/analyze @metabase/graphy
test/metabase/analyze @metabase/graphy
# api (DevEx, no :assignee in team.json)
# src/metabase/api
# test/metabase/api
# api-keys (UX West, no :assignee in team.json)
# src/metabase/api_keys
# test/metabase/api_keys
# api-routes (UX West, no :assignee in team.json)
# src/metabase/api_routes
# test/metabase/api_routes
# api-scope (Metabot)
src/metabase/api_scope @metabase/metabot
# app-db (UX West, no :assignee in team.json)
# src/metabase/app_db
# test/metabase/app_db
# appearance (UX West, no :assignee in team.json)
# src/metabase/appearance
# test/metabase/appearance
# audit-app (UX West, no :assignee in team.json)
# src/metabase/audit_app
# test/metabase/audit_app
# sso.auth-identity (UX West, no :assignee in team.json)
# src/metabase/auth_identity
# test/metabase/auth_identity
# auth-provider (UX West, no :assignee in team.json)
# src/metabase/auth_provider
# test/metabase/auth_provider
# batch-processing (UX West, no :assignee in team.json)
# src/metabase/batch_processing
# test/metabase/batch_processing
# bookmarks (UX West, no :assignee in team.json)
# src/metabase/bookmarks
# test/metabase/bookmarks
# bug-reporting (UX West, no :assignee in team.json)
# src/metabase/bug_reporting
# test/metabase/bug_reporting
# cache (UX West, no :assignee in team.json)
# src/metabase/cache
# test/metabase/cache
# channel (Gadget, no :assignee in team.json)
# src/metabase/channel
# test/metabase/channel
# util.classloader (DevEx, no :assignee in team.json)
# src/metabase/classloader
# test/metabase/classloader
# cloud-migration (UX West, no :assignee in team.json)
# src/metabase/cloud_migration
# test/metabase/cloud_migration
# core.cmd (UX West, no :assignee in team.json)
# src/metabase/cmd
# test/metabase/cmd
# collections (UX West, no :assignee in team.json)
# src/metabase/collections
# test/metabase/collections
# collections.rest (UX West, no :assignee in team.json)
# src/metabase/collections_rest
# test/metabase/collections_rest
# comments (UX West, no :assignee in team.json)
# src/metabase/comments
# test/metabase/comments
# util.config (DevEx, no :assignee in team.json)
# src/metabase/config
# content-translation (UX West, no :assignee in team.json)
# src/metabase/content_translation
# test/metabase/content_translation
# content-verification (UX West, no :assignee in team.json)
# src/metabase/content_verification
# test/metabase/content_verification
# explorations.contextual-interestingness (UX West, no :assignee in team.json)
# src/metabase/contextual_interestingness
# test/metabase/contextual_interestingness
# core (UX West, no :assignee in team.json)
# src/metabase/core
# test/metabase/core
# dashboards (UX West, no :assignee in team.json)
# src/metabase/dashboards
# test/metabase/dashboards
# dashboards.rest (UX West, no :assignee in team.json)
# src/metabase/dashboards_rest
# test/metabase/dashboards_rest
# data-studio (Gadget, no :assignee in team.json)
# src/metabase/data_studio
# test/metabase/data_studio
# database-routing (UX West, no :assignee in team.json)
# src/metabase/database_routing
# documents (UX West, no :assignee in team.json)
# src/metabase/documents
# test/metabase/documents
# driver (Querying Platform)
src/metabase/driver @metabase/core-backend-querying-platform
test/metabase/driver @metabase/core-backend-querying-platform
# query-processor.driver-api (Querying Platform)
src/metabase/driver_api @metabase/core-backend-querying-platform
test/metabase/driver_api @metabase/core-backend-querying-platform
# api.eid-translation (UX West, no :assignee in team.json)
# src/metabase/eid_translation
# test/metabase/eid_translation
# embedding (Embedding, no :assignee in team.json)
# src/metabase/embedding
# test/metabase/embedding
# embedding-hub (Embedding, no :assignee in team.json)
# src/metabase/embedding_hub
# test/metabase/embedding_hub
# embedding.rest (Embedding, no :assignee in team.json)
# src/metabase/embedding_rest
# test/metabase/embedding_rest
# search.embeddings (Metabot)
src/metabase/embeddings @metabase/metabot
test/metabase/embeddings @metabase/metabot
# search.entity-retrieval (Metabot)
src/metabase/entity_retrieval @metabase/metabot
test/metabase/entity_retrieval @metabase/metabot
# events (DevEx, no :assignee in team.json)
# src/metabase/events
# test/metabase/events
# explorations (UX West, no :assignee in team.json)
# src/metabase/explorations
# test/metabase/explorations
# formatter (UX West, no :assignee in team.json)
# src/metabase/formatter
# test/metabase/formatter
# frontend-errors (UX West, no :assignee in team.json)
# src/metabase/frontend_errors
# test/metabase/frontend_errors
# geojson (UX West, no :assignee in team.json)
# src/metabase/geojson
# test/metabase/geojson
# glossary (Gadget, no :assignee in team.json)
# src/metabase/glossary
# test/metabase/glossary
# util.graph (Graphy)
src/metabase/graph @metabase/graphy
test/metabase/graph @metabase/graphy
# health-inspector (Querying Platform)
src/metabase/health_inspector @metabase/core-backend-querying-platform
test/metabase/health_inspector @metabase/core-backend-querying-platform
# indexed-entities (UX West, no :assignee in team.json)
# src/metabase/indexed_entities
# test/metabase/indexed_entities
# transforms.indexes (Gadget, no :assignee in team.json)
# src/metabase/indexes
# test/metabase/indexes
# transforms.indexes.rest (Gadget, no :assignee in team.json)
# src/metabase/indexes_rest
# test/metabase/indexes_rest
# core.initialization-status (UX West, no :assignee in team.json)
# src/metabase/initialization_status
# interestingness (UX West, no :assignee in team.json)
# src/metabase/interestingness
# test/metabase/interestingness
# analytics.internal-stats (UX West, no :assignee in team.json)
# src/metabase/internal_stats
# test/metabase/internal_stats
# lib.legacy-mbql (Querying Platform)
src/metabase/legacy_mbql @metabase/core-backend-querying-platform
test/metabase/legacy_mbql @metabase/core-backend-querying-platform
# lib (Querying Platform)
src/metabase/lib @metabase/core-backend-querying-platform
test/metabase/lib @metabase/core-backend-querying-platform
# lib.metadata (Querying Platform)
src/metabase/lib/metadata @metabase/core-backend-querying-platform
test/metabase/lib/metadata @metabase/core-backend-querying-platform
# lib.schema (Querying Platform)
src/metabase/lib/schema @metabase/core-backend-querying-platform
test/metabase/lib/schema @metabase/core-backend-querying-platform
# lib.be (Querying Platform)
src/metabase/lib_be @metabase/core-backend-querying-platform
test/metabase/lib_be @metabase/core-backend-querying-platform
# lib.metric (UX West, no :assignee in team.json)
# src/metabase/lib_metric
# test/metabase/lib_metric
# metabot.llm (Metabot)
src/metabase/llm @metabase/metabot
test/metabase/llm @metabase/metabot
# logger (DevEx, no :assignee in team.json)
# src/metabase/logger
# test/metabase/logger
# session.login-history (UX West, no :assignee in team.json)
# src/metabase/login_history
# test/metabase/login_history
# mcp (Metabot)
src/metabase/mcp @metabase/metabot
test/metabase/mcp @metabase/metabot
# measures (Graphy)
src/metabase/measures @metabase/graphy
test/metabase/measures @metabase/graphy
# metabot (Metabot)
src/metabase/metabot @metabase/metabot
test/metabase/metabot @metabase/metabot
# metrics (UX West, no :assignee in team.json)
# src/metabase/metrics
# test/metabase/metrics
# mfa (UX West, no :assignee in team.json)
# src/metabase/mfa
# model-persistence (Graphy)
src/metabase/model_persistence @metabase/graphy
test/metabase/model_persistence @metabase/graphy
# models (DevEx, no :assignee in team.json)
# src/metabase/models
# test/metabase/models
# mq (UX West, no :assignee in team.json)
# src/metabase/mq
# test/metabase/mq
# native-query-snippets (Graphy)
src/metabase/native_query_snippets @metabase/graphy
test/metabase/native_query_snippets @metabase/graphy
# notification (Gadget, no :assignee in team.json)
# src/metabase/notification
# test/metabase/notification
# mcp.oauth-server (UX West, no :assignee in team.json)
# src/metabase/oauth_server
# test/metabase/oauth_server
# osi (Metabot)
src/metabase/osi @metabase/metabot
test/metabase/osi @metabase/metabot
# parameters (Graphy)
src/metabase/parameters @metabase/graphy
test/metabase/parameters @metabase/graphy
# permissions (UX West, no :assignee in team.json)
# src/metabase/permissions
# test/metabase/permissions
# permissions.rest (UX West, no :assignee in team.json)
# src/metabase/permissions_rest
# test/metabase/permissions_rest
# query-processor.pivot (UX West, no :assignee in team.json)
# src/metabase/pivot
# test/metabase/pivot
# plugins (DevEx, no :assignee in team.json)
# src/metabase/plugins
# test/metabase/plugins
# premium-features (UX West, no :assignee in team.json)
# src/metabase/premium_features
# test/metabase/premium_features
# product-feedback (UX West, no :assignee in team.json)
# src/metabase/product_feedback
# test/metabase/product_feedback
# public-sharing (UX West, no :assignee in team.json)
# src/metabase/public_sharing
# public-sharing.rest (UX West, no :assignee in team.json)
# src/metabase/public_sharing_rest
# test/metabase/public_sharing_rest
# pulse (Gadget, no :assignee in team.json)
# src/metabase/pulse
# test/metabase/pulse
# queries (Graphy)
src/metabase/queries @metabase/graphy
test/metabase/queries @metabase/graphy
# queries.rest (Querying Platform)
src/metabase/queries_rest @metabase/core-backend-querying-platform
test/metabase/queries_rest @metabase/core-backend-querying-platform
# query-permissions (UX West, no :assignee in team.json)
# src/metabase/query_permissions
# test/metabase/query_permissions
# query-processor (Querying Platform)
src/metabase/query_processor @metabase/core-backend-querying-platform
test/metabase/query_processor @metabase/core-backend-querying-platform
# query-processor.cache-backend (Querying Platform)
src/metabase/query_processor/middleware/cache_backend @metabase/core-backend-querying-platform
test/metabase/query_processor/middleware/cache_backend @metabase/core-backend-querying-platform
# remote-sync (UX West, no :assignee in team.json)
# src/metabase/remote_sync
# test/metabase/remote_sync
# request (UX West, no :assignee in team.json)
# src/metabase/request
# test/metabase/request
# revisions (UX West, no :assignee in team.json)
# src/metabase/revisions
# test/metabase/revisions
# run-tracking (Gadget, no :assignee in team.json)
# src/metabase/run_tracking
# test/metabase/run_tracking
# sample-data (UX West, no :assignee in team.json)
# src/metabase/sample_data
# test/metabase/sample_data
# search (UX West, no :assignee in team.json)
# src/metabase/search
# test/metabase/search
# warehouses.secrets (Graphy)
src/metabase/secrets @metabase/graphy
test/metabase/secrets @metabase/graphy
# security-center (Gadget, no :assignee in team.json)
# src/metabase/security_center
# test/metabase/security_center
# segments (Graphy)
src/metabase/segments @metabase/graphy
test/metabase/segments @metabase/graphy
# server (UX West, no :assignee in team.json)
# src/metabase/server
# test/metabase/server
# session (UX West, no :assignee in team.json)
# src/metabase/session
# test/metabase/session
# settings (DevEx, no :assignee in team.json)
# src/metabase/settings
# test/metabase/settings
# settings.rest (DevEx, no :assignee in team.json)
# src/metabase/settings_rest
# test/metabase/settings_rest
# setup (UX West, no :assignee in team.json)
# src/metabase/setup
# test/metabase/setup
# setup.rest (UX West, no :assignee in team.json)
# src/metabase/setup_rest
# test/metabase/setup_rest
# metabot.slackbot (Metabot)
src/metabase/slackbot @metabase/metabot
test/metabase/slackbot @metabase/metabot
# lib.source-swap (Graphy)
src/metabase/source_swap @metabase/graphy
test/metabase/source_swap @metabase/graphy
# sql-tools.parsing (DevEx, no :assignee in team.json)
# src/metabase/sql_parsing
# test/metabase/sql_parsing
# sql-tools (DevEx, no :assignee in team.json)
# src/metabase/sql_tools
# test/metabase/sql_tools
# sso (UX West, no :assignee in team.json)
# src/metabase/sso
# test/metabase/sso
# staleness (UX West, no :assignee in team.json)
# src/metabase/staleness
# test/metabase/staleness
# startup (DevEx, no :assignee in team.json)
# src/metabase/startup
# test/metabase/startup
# store-api (Cloud, no :assignee in team.json)
# src/metabase/store_api
# sync (Graphy)
src/metabase/sync @metabase/graphy
test/metabase/sync @metabase/graphy
# system (UX West, no :assignee in team.json)
# src/metabase/system
# test/metabase/system
# task (UX West, no :assignee in team.json)
# src/metabase/task
# test/metabase/task
# task.history (UX West, no :assignee in team.json)
# src/metabase/task_history
# test/metabase/task_history
# users.tenants (Admin Webapp, no :assignee in team.json)
# src/metabase/tenants
# test/metabase/tenants
# testing-api (DevEx, no :assignee in team.json)
# src/metabase/testing_api
# test/metabase/testing_api
# tiles (UX West, no :assignee in team.json)
# src/metabase/tiles
# test/metabase/tiles
# timeline (UX West, no :assignee in team.json)
# src/metabase/timeline
# test/metabase/timeline
# tracing (DevEx, no :assignee in team.json)
# src/metabase/tracing
# test/metabase/tracing
# transforms (Graphy)
src/metabase/transforms @metabase/graphy
test/metabase/transforms @metabase/graphy
# transforms.base (Graphy)
src/metabase/transforms_base @metabase/graphy
test/metabase/transforms_base @metabase/graphy
# transforms.inspector (Gadget, no :assignee in team.json)
# src/metabase/transforms_inspector
# test/metabase/transforms_inspector
# transforms.rest (Gadget, no :assignee in team.json)
# src/metabase/transforms_rest
# test/metabase/transforms_rest
# typed-schemas (Embedding, no :assignee in team.json)
# src/metabase/typed_schemas
# test/metabase/typed_schemas
# typed-schemas.rest (Embedding, no :assignee in team.json)
# src/metabase/typed_schemas_rest
# test/metabase/typed_schemas_rest
# lib.types (Querying Platform)
src/metabase/types @metabase/core-backend-querying-platform
test/metabase/types @metabase/core-backend-querying-platform
# upload (Graphy)
src/metabase/upload @metabase/graphy
test/metabase/upload @metabase/graphy
# usage-metadata (UX West, no :assignee in team.json)
# src/metabase/usage_metadata
# test/metabase/usage_metadata
# user-key-value (UX West, no :assignee in team.json)
# src/metabase/user_key_value
# test/metabase/user_key_value
# users (UX West, no :assignee in team.json)
# src/metabase/users
# test/metabase/users
# users.rest (UX West, no :assignee in team.json)
# src/metabase/users_rest
# test/metabase/users_rest
# util (DevEx, no :assignee in team.json)
# src/metabase/util
# test/metabase/util
# core.version (UX West, no :assignee in team.json)
# src/metabase/version
# test/metabase/version
# audit-app.view-log (UX West, no :assignee in team.json)
# src/metabase/view_log
# test/metabase/view_log
# warehouses.schema (UX West, no :assignee in team.json)
# src/metabase/warehouse_schema
# test/metabase/warehouse_schema
# warehouse-schema-overlay (UX West, no :assignee in team.json)
# src/metabase/warehouse_schema_overlay
# test/metabase/warehouse_schema_overlay
# warehouses.schema.rest (UX West, no :assignee in team.json)
# src/metabase/warehouse_schema_rest
# test/metabase/warehouse_schema_rest
# warehouses (UX West, no :assignee in team.json)
# src/metabase/warehouses
# test/metabase/warehouses
# warehouses.rest (UX West, no :assignee in team.json)
# src/metabase/warehouses_rest
# test/metabase/warehouses_rest
# xrays (Graphy)
src/metabase/xrays @metabase/graphy
test/metabase/xrays @metabase/graphy
# ===== END generated module ownership =====

3
.github/team.json vendored
View File

@@ -3,6 +3,7 @@
{
"name": "Querying Platform",
"label": ".Team/QueryingPlatform",
"assignee": "@metabase/core-backend-querying-platform",
"members": [
"metamben",
"rileythomp",
@@ -13,6 +14,7 @@
{
"name": "Graphy",
"label": ".Team/Graphy",
"assignee": "@metabase/graphy",
"members": [
"ranquild",
"ericnormand",
@@ -54,6 +56,7 @@
{
"name": "Metabot",
"label": ".Team/Metabot",
"assignee": "@metabase/metabot",
"members": [
"appleby",
"retro",

8
bb.edn
View File

@@ -261,6 +261,14 @@
:requires [[mage.modules]]
:task (task! (mage.modules/cli-print-affected-modules (:arguments parsed)))}
update-codeowners
{:doc "Regenerate the module-ownership block in .github/CODEOWNERS from module :team + team.json :assignee."
:examples [["./bin/mage update-codeowners" "Rewrite the generated block in .github/CODEOWNERS"]
["./bin/mage update-codeowners --check" "Exit non-zero if the block is out of date (for CI)"]]
:options [[nil "--check" "Exit non-zero if CODEOWNERS is out of date instead of rewriting it"]]
:requires [[mage.modules]]
:task (task! (mage.modules/cli-update-codeowners (cli/parse! (current-task))))}
modules-tree
{:doc "Print the module hierarchy, nesting enterprise extensions under their OSS module"
:examples [["./bin/mage modules-tree" "Print the full tree; * marks a custom :ns-prefix"]

View File

@@ -1,5 +1,8 @@
(ns mage.modules
(:require
;; mage runs under babashka, which bundles cheshire; metabase.util.json isn't on its classpath
^{:clj-kondo/ignore [:discouraged-namespace]}
[cheshire.core :as json]
[clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.set :as set]
@@ -261,6 +264,139 @@
(flush)
(u/exit 0)))
;;;; =============================================================================
;;;; CODEOWNERS
;;;; =============================================================================
;;;; `.github/CODEOWNERS` gets a generated block between BEGIN/END markers, one stanza per module.
;;;; A module's src and test dirs are owned when its `:team` (config.edn) has an `:assignee` handle
;;;; (team.json) and the module isn't flagged `:suppress-codeowners`. Otherwise the stanza is commented
;;;; out, ready to enable. Lines outside the markers are hand-maintained (frontend, e2e, docs).
(def ^:private codeowners-path ".github/CODEOWNERS")
(def ^:private team-config-path ".github/team.json")
(def ^:private codeowners-begin-marker
"# ===== BEGIN generated module ownership (./bin/mage update-codeowners) =====")
(def ^:private codeowners-end-marker
"# ===== END generated module ownership =====")
(defn- team-name->assignee
"Team name => GitHub CODEOWNERS handle, from the `assignee` field in team.json.
Teams without an `assignee` are omitted; their modules only ever render commented out."
[]
(into {}
(keep (fn [{:keys [name assignee]}]
(when assignee
[name assignee])))
(:teams (json/parse-string (slurp team-config-path) true))))
(defn- module->src-path-prefix
"Backend source directory for `module`. Src-tree mirror of [[module->test-path-prefix]]."
[modules-config module]
(let [ns-prefix (modules/module-ns-prefix modules-config module)]
(str (when (str/starts-with? ns-prefix "metabase-enterprise.") "enterprise/backend/")
"src/"
(-> ns-prefix (str/replace "." "/") (str/replace "-" "_")))))
(defn- module->owned-dirs
"Existing src and test directories `module` contributes, in CODEOWNERS pattern form (repo-relative,
no leading slash), src first. Directories missing on disk are skipped."
[modules-config module]
(into []
(filter #(.isDirectory (io/file ^String %)))
[(module->src-path-prefix modules-config module)
(module->test-path-prefix modules-config module)]))
(defn- codeowners-stanza-lines
"Lines for one module's stanza: a `# module (team)` header then one line per owned directory.
`nil` when `dirs` is empty. Lines are live when the team has an `assignee` and the module isn't
suppressed; otherwise commented out, keeping the handle when known so they're ready to uncomment."
[{:keys [module team handle suppress? dirs]}]
(when (seq dirs)
(let [active? (and (some? handle) (not suppress?))
note (cond
active? nil
suppress? "suppressed via :suppress-codeowners"
(nil? handle) "no :assignee in team.json")
header (format "# %s (%s%s)" (name module) team (if note (str ", " note) ""))
body (for [dir dirs
:let [line (str dir (when handle (str " " handle)))]]
(if active? line (str "# " line)))]
(into [header] body))))
(defn- module-codeowners-stanza
"Resolve `module` against the config and assignees, then render its stanza.
`nil` when the module owns no existing directory."
[modules-config assignees module]
(let [cfg (get modules-config module)]
(codeowners-stanza-lines
{:module module
:team (:team cfg)
:handle (get assignees (:team cfg))
:suppress? (boolean (:suppress-codeowners cfg))
:dirs (module->owned-dirs modules-config module)})))
(defn- codeowners-block
"Generated block as one string, marker to marker. One stanza per module, sorted by source path so a
nested child's dirs sort after its parent's (CODEOWNERS applies the last matching pattern)."
[modules-config assignees]
(let [stanzas (->> (keys modules-config)
(sort-by #(module->src-path-prefix modules-config %))
(keep #(module-codeowners-stanza modules-config assignees %)))]
(str/join
"\n"
(concat
[codeowners-begin-marker
"#"
"# Generated by `./bin/mage update-codeowners`. Do not edit by hand."
"# Ownership comes from each module's :team (.clj-kondo/config/modules/config.edn) and that"
"# team's :assignee handle (.github/team.json). A stanza is live when its team has an :assignee"
"# and the module is not :suppress-codeowners; otherwise it is commented out, ready to enable."
"#"]
(mapcat #(cons "" %) stanzas)
[""
codeowners-end-marker]))))
(defn- splice-codeowners
"Replace the marked region (inclusive) in `existing` with `block`; append `block` if the markers aren't
there yet. Returns text ending in one newline."
[existing block]
(let [begin (str/index-of existing codeowners-begin-marker)
end (str/index-of existing codeowners-end-marker)
spliced (if (and begin end (< begin end))
(str (str/trimr (subs existing 0 begin))
"\n\n" block
(subs existing (+ end (count codeowners-end-marker))))
(str (str/trimr existing) "\n\n" block))]
(str (str/trimr spliced) "\n")))
(defn- desired-codeowners []
(splice-codeowners (slurp codeowners-path)
(codeowners-block (read-modules-config) (team-name->assignee))))
(defn cli-update-codeowners
"Regenerate the module-ownership block in `.github/CODEOWNERS`.
`--check` exits non-zero when the file is stale instead of writing it (for CI)."
[cli-args]
(let [check? (get-in cli-args [:options :check])
existing (slurp codeowners-path)
desired (desired-codeowners)]
(cond
(= existing desired)
(do (println (c/green "CODEOWNERS module ownership is up to date."))
(u/exit 0))
check?
(do (println (c/red "CODEOWNERS module ownership is out of date. Run ./bin/mage update-codeowners."))
(u/exit 1))
:else
(do (spit codeowners-path desired)
(println (c/green (str "Updated " codeowners-path ".")))
(u/exit 0)))))
;;;; =============================================================================
;;;; Module tree
;;;; =============================================================================

View File

@@ -449,3 +449,98 @@
(is (= 'driver (#'mage.modules/file->module prefix->module "src/metabase/driver.clj")))
(is (nil? (#'mage.modules/file->module prefix->module "test/metabase/test_runner.clj")))
(is (nil? (#'mage.modules/file->module prefix->module "src/metabase/DO_NOT_ADD_NEW_FILES_HERE.txt"))))))
;;; =============================================================================
;;; CODEOWNERS generation
;;; =============================================================================
(deftest codeowners-stanza-active-test
(testing "a module whose team has an :assignee and no suppression gets live owner lines"
(is (= ["# lib (Querying Platform)"
"src/metabase/lib @metabase/qp"
"test/metabase/lib @metabase/qp"]
(#'mage.modules/codeowners-stanza-lines
{:module 'lib :team "Querying Platform" :handle "@metabase/qp" :suppress? false
:dirs ["src/metabase/lib" "test/metabase/lib"]})))))
(deftest codeowners-stanza-suppressed-test
(testing "a suppressed module is commented out but keeps its known handle, ready to uncomment"
(is (= ["# driver (Querying Platform, suppressed via :suppress-codeowners)"
"# src/metabase/driver @metabase/qp"]
(#'mage.modules/codeowners-stanza-lines
{:module 'driver :team "Querying Platform" :handle "@metabase/qp" :suppress? true
:dirs ["src/metabase/driver"]})))))
(deftest codeowners-stanza-no-assignee-test
(testing "a module whose team has no :assignee is commented out with no handle"
(is (= ["# audit-app (UX West, no :assignee in team.json)"
"# enterprise/backend/src/metabase_enterprise/audit_app"]
(#'mage.modules/codeowners-stanza-lines
{:module 'enterprise/audit-app :team "UX West" :handle nil :suppress? false
:dirs ["enterprise/backend/src/metabase_enterprise/audit_app"]})))))
(deftest codeowners-stanza-no-dirs-test
(testing "a module that owns no existing directory produces no stanza"
(is (nil? (#'mage.modules/codeowners-stanza-lines
{:module 'ghost :team "UX West" :handle nil :suppress? false :dirs []})))))
(deftest codeowners-splice-idempotent-test
(testing "splicing a block in replaces only the marked region and is idempotent"
(let [begin @#'mage.modules/codeowners-begin-marker
end @#'mage.modules/codeowners-end-marker
splice @#'mage.modules/splice-codeowners
block (str begin "\nfoo/bar @team\n" end)
base "# hand-maintained\ndocs @writers\n"
once (splice base block)
twice (splice once block)]
(is (str/includes? once "# hand-maintained"))
(is (str/includes? once "foo/bar @team"))
(is (= once twice))))
(testing "text on BOTH sides of the marked region survives, with the block still between them"
;; This is the normal layout: the generated block sits above the hand-maintained rules, so the
;; trailing-content branch of the in-place splice is load-bearing. The idempotence case above
;; leaves an empty tail and would not notice it being dropped.
(let [begin @#'mage.modules/codeowners-begin-marker
end @#'mage.modules/codeowners-end-marker
splice @#'mage.modules/splice-codeowners
base (str "# header\n" begin "\nold/path @old\n" end "\n\n# exceptions\nsrc/a.clj @owner\n")
block (str begin "\nnew/path @new\n" end)
out (splice base block)]
(is (str/includes? out "# header") "leading content survives")
(is (str/includes? out "src/a.clj @owner") "trailing content survives")
(is (str/includes? out "new/path @new") "the block was replaced")
(is (not (str/includes? out "old/path @old")) "the previous block is gone")
(is (< (str/index-of out "# header")
(str/index-of out "new/path @new")
(str/index-of out "src/a.clj @owner"))
"and the block still sits between the two")
(is (= out (splice out block)) "still idempotent with content on both sides"))))
(deftest codeowners-block-orders-children-after-parents
(testing (str "Stanzas sort by source path so a nested child's dirs come after its parent's. "
"CODEOWNERS applies the LAST matching pattern, so reversing this would let the "
"parent's module-wide rule swallow its child's ownership.")
;; `lib` and `lib.schema` are used because the generator only emits directories that exist on
;; disk, so an invented nested path would silently drop its stanza and the ordering assertion
;; would pass on a one-element list.
(let [config '{lib {:team "Querying Platform"}
lib.schema {:team "Querying Platform"}}
assignees {"Querying Platform" "@metabase/querying-platform"}
block (#'mage.modules/codeowners-block config assignees)
lines (->> (str/split-lines block)
(filter #(str/starts-with? % "src/metabase/lib")))]
(is (= 2 (count lines)) "both parent and child stanzas are emitted")
(is (= ["src/metabase/lib @metabase/querying-platform"
"src/metabase/lib/schema @metabase/querying-platform"]
lines)))))
(deftest codeowners-block-comments-out-unknown-teams
(testing (str "A module whose :team has no matching team.json entry degrades to a commented "
"stanza rather than emitting a bad handle or being dropped silently.")
(let [config '{lib {:team "Nonexistent Team"}}
block (#'mage.modules/codeowners-block config {"Querying Platform" "@metabase/querying-platform"})
src-lines (->> (str/split-lines block)
(filter #(str/includes? % "src/metabase/lib")))]
(is (seq src-lines) "the module still appears in the block")
(is (every? #(str/starts-with? % "#") src-lines)
"but every one of its path lines is commented out"))))