Fix metabase list user

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-05-28 20:12:24 -07:00
parent 1853e5af39
commit 0e40938158
3 changed files with 122 additions and 5 deletions

View File

@@ -46,6 +46,15 @@ type metabaseUser struct {
DateJoined string `json:"date_joined"`
}
type metabaseUsersResponse struct {
Data []metabaseUser `json:"data"`
Total int `json:"total"`
Limit *int `json:"limit"`
Offset *int `json:"offset"`
}
const metabaseUsersPageLimit = 50
func NewMetabaseDriver(httpClient *http.Client, instanceURL string) *MetabaseDriver {
return &MetabaseDriver{
httpClient: httpClient,
@@ -98,9 +107,36 @@ func (d *MetabaseDriver) queryUsers(ctx context.Context) ([]metabaseUser, error)
return nil, fmt.Errorf("cannot parse metabase instance url: %w", err)
}
var users []metabaseUser
offset := 0
for {
page, err := d.queryUsersPage(ctx, baseURL, offset)
if err != nil {
return nil, err
}
users = append(users, page.Data...)
offset += len(page.Data)
if len(page.Data) == 0 || offset >= page.Total {
break
}
}
return users, nil
}
func (d *MetabaseDriver) queryUsersPage(
ctx context.Context,
baseURL *url.URL,
offset int,
) (*metabaseUsersResponse, error) {
endpoint := baseURL.JoinPath("api", "user")
q := endpoint.Query()
q.Set("status", "all")
q.Set("limit", strconv.Itoa(metabaseUsersPageLimit))
q.Set("offset", strconv.Itoa(offset))
endpoint.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
@@ -123,12 +159,12 @@ func (d *MetabaseDriver) queryUsers(ctx context.Context) ([]metabaseUser, error)
return nil, fmt.Errorf("cannot fetch metabase users: unexpected status %d", httpResp.StatusCode)
}
var users []metabaseUser
if err := json.NewDecoder(httpResp.Body).Decode(&users); err != nil {
var resp metabaseUsersResponse
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
return nil, fmt.Errorf("cannot decode metabase users response: %w", err)
}
return users, nil
return &resp, nil
}
func metabaseFullName(u metabaseUser) string {
@@ -147,6 +183,63 @@ func metabaseRole(isSuperuser bool) string {
return "User"
}
// metabaseNameResolver resolves the Metabase site name by querying
// /api/session/properties on the configured Metabase instance, which
// exposes the site-name setting to any authenticated session.
type metabaseNameResolver struct {
httpClient *http.Client
instanceURL string
}
var _ NameResolver = (*metabaseNameResolver)(nil)
type metabaseSessionProperties struct {
SiteName string `json:"site-name"`
}
func NewMetabaseNameResolver(httpClient *http.Client, instanceURL string) NameResolver {
return &metabaseNameResolver{
httpClient: httpClient,
instanceURL: instanceURL,
}
}
func (r *metabaseNameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
baseURL, err := url.Parse(r.instanceURL)
if err != nil {
return "", fmt.Errorf("cannot parse metabase instance url: %w", err)
}
endpoint := baseURL.JoinPath("api", "session", "properties")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return "", fmt.Errorf("cannot create metabase session properties request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute metabase session properties request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
return "", fmt.Errorf("cannot fetch metabase session properties: unexpected status %d", httpResp.StatusCode)
}
var props metabaseSessionProperties
if err := json.NewDecoder(httpResp.Body).Decode(&props); err != nil {
return "", fmt.Errorf("cannot decode metabase session properties response: %w", err)
}
return strings.TrimSpace(props.SiteName), nil
}
func parseMetabaseTimestamp(value string) (time.Time, bool) {
if value == "" {
return time.Time{}, false

View File

@@ -9,12 +9,16 @@ interactions:
content_length: 0
host: k7.metabaseapp.com
form:
limit:
- "50"
offset:
- "0"
status:
- all
headers:
Accept:
- application/json
url: https://k7.metabaseapp.com/api/user?status=all
url: https://k7.metabaseapp.com/api/user?limit=50&offset=0&status=all
method: GET
response:
proto: HTTP/2.0
@@ -22,7 +26,7 @@ interactions:
proto_minor: 0
content_length: -1
uncompressed: true
body: '[{"id":1,"email":"alice@example.com","first_name":"Alice","last_name":"Admin","common_name":"Alice A.","is_active":true,"is_superuser":true,"last_login":"2026-05-20T10:11:12.345678Z","date_joined":"2026-01-02T03:04:05Z"},{"id":2,"email":"bob@example.com","first_name":"Bob","last_name":"Builder","is_active":false,"is_superuser":false,"last_login":"","date_joined":"2026-02-03T04:05:06Z"},{"id":3,"email":"","first_name":"No","last_name":"Email","is_active":true,"is_superuser":false}]'
body: '{"data":[{"id":1,"email":"alice@example.com","first_name":"Alice","last_name":"Admin","common_name":"Alice A.","is_active":true,"is_superuser":true,"last_login":"2026-05-20T10:11:12.345678Z","date_joined":"2026-01-02T03:04:05Z"},{"id":2,"email":"bob@example.com","first_name":"Bob","last_name":"Builder","is_active":false,"is_superuser":false,"last_login":"","date_joined":"2026-02-03T04:05:06Z"},{"id":3,"email":"","first_name":"No","last_name":"Email","is_active":true,"is_superuser":false}],"total":3,"limit":50,"offset":0}'
headers:
Content-Type:
- application/json; charset=utf-8

View File

@@ -52,6 +52,26 @@ func metabaseRegistration() *Registration {
return drivers.NewMetabaseDriver(c, instanceURL), nil
},
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
settings, err := coredata.ConnectorSettings[coredata.MetabaseConnectorSettings](conn)
if err != nil {
logger.ErrorCtx(ctx, "cannot read metabase connector settings", log.Error(err))
return nil
}
instanceURL := strings.TrimSpace(settings.InstanceURL)
if instanceURL == "" {
logger.ErrorCtx(ctx, "missing metabase instance url in connector settings")
return nil
}
if err := validateMetabaseInstanceURL(instanceURL); err != nil {
logger.ErrorCtx(ctx, "invalid metabase instance url in connector settings", log.Error(err))
return nil
}
return drivers.NewMetabaseNameResolver(c, instanceURL)
},
}
}