Skip to content
Snippets Groups Projects
Unverified Commit da97222e authored by Cam Saul's avatar Cam Saul Committed by GitHub
Browse files

EE API endpoint code reorganization (#17913)

* EE API endpoint code reorganization

* Remove accidental commit

* test fixes :wrench:

* Remove unused namespace

* Remove unused var

* Rename metabase-enterprise.audit.* -> metabase-enterprise.audit-app.*

* Move the new delete subscriptions endpoint from :advanced-config -> :audit

* Prettier
parent 6035c777
Branches
Tags
No related merge requests found
Showing
with 186 additions and 46 deletions
### EE Code Structure Notes
EE namespaces follow the pattern work like this.
EE namespace = take the equivalent OSS namespace and replace `metabase.` with `metabase-enterprise.<feature>` where
`<feature>` is the premium token feature that one must have to use this feature.
For example, Sandboxing-related API endpoints for Tables go in `metabase-enterprise.sandboxes.api.table` and
Sandboxing-related models (e.g. GTAP) go in `metabase-enterprise.sandboxes.models`. Sandboxing-specific code for
existing models follow this same pattern, e.g. Sandboxing-specific code for Tables goes in
`metabase-enterprise.sandboxes.models.table`.
Groups of API routes should be defined in namespaces like we do in OSS, for example
`metabase-enterprise.content-management.api.review` for ModerationReview-related endpoints. All endpoints for a
specific feature are combined into a single `routes` handler in a `metabase-enterprise.<feature>.api.routes` namespace
similar to how OSS routes are combined in `metabase.api.routes`. Finally, all EE routes are combined into a single
handler in `metabase-enterprise.api.routes`; this handler is included in `metabase.api.routes/routes` if EE code is
available.
Please keep these rules in mind when adding new EE namespaces. In general, new namespaces **SHOULD NOT** be added
directly under `metabase-enterprise` unless they apply to the Enterprise codebase as a whole; put them under the
appropriate `metabase-enterprise.<feature>` directory instead.
### Naming EE API routes
To make things consistent EE-only API routes should follow the same pattern and be given route names that correspond
to their namespaces (i.e., are prefixed with `ee/<feature>`). For example, an `:advanced-config`-only
route to delete User subscriptions should be named something like
```
DELETE /api/ee/advanced-config/user/:id/subscriptions
```
rather than
```
DELETE /api/user/:id/subscriptions
```
Not all EE endpoints follow this pattern yet, but they should; please feel free to fix stuff as you come across it if
I don't get to it first.
### Questions :interrobang:
Ping me (`@cam`) if you have any questions.
(ns metabase-enterprise.api.routes
"API routes that are only available when running Metabase® Enterprise Edition™. Even tho these routes are available,
not all routes might work unless we have a valid premium features token to enable those features.
These routes should generally live under prefixes like `/api/ee/<feature>/` -- see the
`enterprise/backend/README.md` for more details."
(:require [compojure.core :as compojure]
[metabase-enterprise.api.routes.common :as ee.api.common]
[metabase-enterprise.audit-app.api.routes :as audit-app]
[metabase-enterprise.content-management.api.routes :as content-management]
[metabase-enterprise.sandbox.api.routes :as sandbox]))
(compojure/defroutes ^{:doc "API routes only available when running Metabase® Enterprise Edition™."} routes
;; The following routes are NAUGHTY and do not follow the naming convention (i.e., they do not start with
;; `/ee/<feature>/`).
;;
;; TODO -- Please fix them!
content-management/routes
sandbox/routes
;; The following routes are NICE and do follow the `/ee/<feature>/` naming convention. Please add new routes here
;; and follow the convention.
(compojure/context
"/ee" []
(compojure/context
"/audit-app" []
(ee.api.common/+require-premium-feature :audit-app audit-app/routes))))
(ns metabase-enterprise.api.routes.common
"Shared stuff used by various EE-only API routes."
(:require [metabase.public-settings.premium-features :as premium-features]
[metabase.util.i18n :refer [tru]]))
(defn +require-premium-feature
"Wraps Ring `handler`. Check that we have a premium token with `feature` (a keyword; see [[metabase.public-settings.premium-features]] for a
current known features) or return a 401 if it is not.
(context \"/whatever\" [] (+require-premium-feature :sandboxes whatever/routes))
Very important! Make sure you only wrap handlers inside [[compojure.core/context]] forms with this middleware (as in
example above). Otherwise it can end up causing requests the handler would not have handled anyway to fail.
Use [[when-premium-feature]] instead if you want the handler to apply if we have the premium feature but pass-thru
if we do not."
[feature handler]
(fn [request respond raise]
(if-not (premium-features/has-feature? feature)
(respond {:body (tru "This API endpoint is only enabled if you have a premium token with the {0} feature."
feature)
;; 402 Payment Required
:status 402})
(handler request respond raise))))
(defn ^:deprecated +when-premium-feature
"Wraps Ring `handler`. Only applies handler if we have a premium token with `feature`; if not, passes thru to the next
handler.
(+when-premium-feature :sandboxes (+auth table/routes))
This is typically used to _replace_ OSS versions of API endpoints with special implementations that live in EE-land.
If the endpoint **only** exists in EE you should use [[+require-premium-feature]] instead which will give the API
user a useful error message if the endpoint is not available because they do not have the token feature in
question, rather than a generic 'endpoint does not exist' 404 error.
In general, it's probably better NOT to swap out API endpoints, because it's not obvious at all that it happened,
and it makes it hard for us to nicely structure our contexts in [[metabase-enterprise.api.routes/routes]]. So only
do this if there's absolutely no other way (which is probably not the case)."
[feature handler]
(fn [request respond raise]
(if-not (premium-features/has-feature? feature)
(respond nil)
(handler request respond raise))))
(ns metabase-enterprise.audit-app.api.routes
"API endpoints that are only enabled if we have a premium token with the `:audit-app` feature. These live under
`/api/ee/audit-app/`. Feature-flagging for these routes happens in [[metabase-enterprise.api.routes/routes]]."
(:require [compojure.core :as compojure]
[metabase-enterprise.audit-app.api.user :as user]
[metabase.api.routes.common :refer [+auth]]))
(compojure/defroutes ^{:doc "Ring routes for mt API endpoints."} routes
(compojure/context "/user" [] (+auth user/routes)))
(ns metabase-enterprise.audit-app.api.user
"`/api/ee/audit-app/user` endpoints. These only work if you have a premium token with the `:audit-app` feature."
(:require [compojure.core :refer [DELETE]]
[metabase.api.common :as api]
[metabase.api.user :as api.user]
[metabase.models.pulse-channel-recipient :refer [PulseChannelRecipient]]
[toucan.db :as db]))
(api/defendpoint DELETE "/:id/subscriptions"
"Delete all Alert and DashboardSubscription subscriptions for a User. Only allowed for admins or for the current
user."
[id]
(api.user/check-self-or-superuser id)
(db/delete! PulseChannelRecipient :user_id id)
api/generic-204-no-content)
(api/define-routes)
(ns metabase-enterprise.audit.interface
(ns metabase-enterprise.audit-app.interface
(:require [metabase.plugins.classloader :as classloader]
[metabase.util.i18n :refer [tru]]
[metabase.util.schema :as su]
......@@ -14,7 +14,7 @@
(defmulti internal-query
"Define a new internal query type. Conventionally `query-type` should be a namespaced keyword with the namespace in
which the method is defined. See docstring
for [[metabase-enterprise.audit.query-processor.middleware.handle-audit-queries]] for a description of what this
for [[metabase-enterprise.audit-app.query-processor.middleware.handle-audit-queries]] for a description of what this
method should return."
{:arglists '([query-type & args])}
(fn [query-type & _]
......
(ns metabase-enterprise.audit.pages.common
(ns metabase-enterprise.audit-app.pages.common
"Shared functions used by audit internal queries across different namespaces."
(:require [clojure.core.async :as a]
[clojure.core.memoize :as memoize]
......@@ -10,7 +10,7 @@
[honeysql.helpers :as h]
[java-time :as t]
[medley.core :as m]
[metabase-enterprise.audit.query-processor.middleware.handle-audit-queries :as qp.middleware.audit]
[metabase-enterprise.audit-app.query-processor.middleware.handle-audit-queries :as qp.middleware.audit]
[metabase.db :as mdb]
[metabase.driver.sql-jdbc.execute :as sql-jdbc.execute]
[metabase.driver.sql-jdbc.sync :as sql-jdbc.sync]
......@@ -130,7 +130,7 @@
(defn query
"Run a internal audit query, automatically including limits and offsets for paging. This function returns results
directly as a series of maps (the 'legacy results' format as described in
`metabase-enterprise.audit.query-processor.middleware.handle-audit-queries.internal-queries`)"
`metabase-enterprise.audit-app.query-processor.middleware.handle-audit-queries.internal-queries`)"
[honeysql-query]
(let [context {:canceled-chan (a/promise-chan)}
rff (fn [{:keys [cols]}]
......
(ns metabase-enterprise.audit.pages.common.card-and-dashboard-detail
(ns metabase-enterprise.audit-app.pages.common.card-and-dashboard-detail
"Common queries used by both Card (Question) and Dashboard detail pages."
(:require [honeysql.core :as hsql]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.models.card :refer [Card]]
[metabase.models.dashboard :refer [Dashboard]]
[metabase.models.revision :as revision]
......
(ns metabase-enterprise.audit.pages.common.cards
(:require [metabase-enterprise.audit.pages.common :as common]
(ns metabase-enterprise.audit-app.pages.common.cards
(:require [metabase-enterprise.audit-app.pages.common :as common]
[metabase.util.honeysql-extensions :as hx]))
(def avg-exec-time
......
(ns metabase-enterprise.audit.pages.common.dashboards
(ns metabase-enterprise.audit-app.pages.common.dashboards
(:require [honeysql.core :as hsql]
[honeysql.helpers :as h]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.util.honeysql-extensions :as hx]
[metabase.util.urls :as urls]))
......
(ns metabase-enterprise.audit.pages.dashboard-detail
(ns metabase-enterprise.audit-app.pages.dashboard-detail
"Detail page for a single dashboard."
(:require [metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit.pages.common.card-and-dashboard-detail :as card-and-dash-detail]
[metabase-enterprise.audit.pages.common.cards :as cards]
(:require [metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase-enterprise.audit-app.pages.common.card-and-dashboard-detail :as card-and-dash-detail]
[metabase-enterprise.audit-app.pages.common.cards :as cards]
[metabase.models.dashboard :refer [Dashboard]]
[metabase.util.schema :as su]
[schema.core :as s]))
......
(ns metabase-enterprise.audit.pages.dashboards
(ns metabase-enterprise.audit-app.pages.dashboards
"Dashboards overview page."
(:require [metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit.pages.common.dashboards :as dashboards]
(:require [metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase-enterprise.audit-app.pages.common.dashboards :as dashboards]
[metabase.util.honeysql-extensions :as hx]
[schema.core :as s]))
......
(ns metabase-enterprise.audit.pages.database-detail
(:require [metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
(ns metabase-enterprise.audit-app.pages.database-detail
(:require [metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.util.schema :as su]
[ring.util.codec :as codec]
[schema.core :as s]))
......
(ns metabase-enterprise.audit.pages.databases
(ns metabase-enterprise.audit-app.pages.databases
(:require [honeysql.core :as hsql]
[metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.util.cron :as cron]
[schema.core :as s]))
......
(ns metabase-enterprise.audit.pages.downloads
(ns metabase-enterprise.audit-app.pages.downloads
"Audit queries returning info about query downloads. Query downloads are any query executions whose results are returned
as CSV/JSON/XLS."
(:require [honeysql.core :as hsql]
[metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.db :as mdb]
[metabase.driver.sql.query-processor :as sql.qp]
[metabase.util.honeysql-extensions :as hx]))
......
(ns metabase-enterprise.audit.pages.queries
(ns metabase-enterprise.audit-app.pages.queries
(:require [honeysql.core :as hsql]
[metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit.pages.common.cards :as cards]
[metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase-enterprise.audit-app.pages.common.cards :as cards]
[metabase.util.honeysql-extensions :as hx]))
;; DEPRECATED Query that returns data for a two-series timeseries chart with number of queries ran and average query
......
(ns metabase-enterprise.audit.pages.query-detail
(ns metabase-enterprise.audit-app.pages.query-detail
"Queries to show details about a (presumably ad-hoc) query."
(:require [cheshire.core :as json]
[metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.util.schema :as su]
[ring.util.codec :as codec]
[schema.core :as s]))
......
(ns metabase-enterprise.audit.pages.question-detail
(ns metabase-enterprise.audit-app.pages.question-detail
"Detail page for a single Card (Question)."
(:require [metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
[metabase-enterprise.audit.pages.common.card-and-dashboard-detail :as card-and-dash-detail]
(:require [metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase-enterprise.audit-app.pages.common.card-and-dashboard-detail :as card-and-dash-detail]
[metabase.models.card :refer [Card]]
[metabase.util.schema :as su]
[schema.core :as s]))
......
(ns metabase-enterprise.audit.pages.schemas
(:require [metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
(ns metabase-enterprise.audit-app.pages.schemas
(:require [metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.util.honeysql-extensions :as hx]
[schema.core :as s]))
......
(ns metabase-enterprise.audit.pages.table-detail
(:require [metabase-enterprise.audit.interface :as audit.i]
[metabase-enterprise.audit.pages.common :as common]
(ns metabase-enterprise.audit-app.pages.table-detail
(:require [metabase-enterprise.audit-app.interface :as audit.i]
[metabase-enterprise.audit-app.pages.common :as common]
[metabase.util.schema :as su]
[ring.util.codec :as codec]
[schema.core :as s]))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment