diff --git a/backend/plugins/taiga/models/connection.go b/backend/plugins/taiga/models/connection.go index fab82d3b891..e97979331fb 100644 --- a/backend/plugins/taiga/models/connection.go +++ b/backend/plugins/taiga/models/connection.go @@ -18,7 +18,12 @@ limitations under the License. package models import ( + "bytes" + "encoding/json" + "fmt" + "io" "net/http" + "strings" "github.com/apache/incubator-devlake/core/errors" "github.com/apache/incubator-devlake/core/utils" @@ -39,14 +44,65 @@ func (tc *TaigaConn) Sanitize() TaigaConn { return *tc } -// SetupAuthentication sets up the HTTP request with authentication +// SetupAuthentication sets up the HTTP request with authentication. +// If Token is set directly, use it. Otherwise exchange Username+Password for a token. func (tc *TaigaConn) SetupAuthentication(req *http.Request) errors.Error { if tc.Token != "" { req.Header.Set("Authorization", "Bearer "+tc.Token) + return nil + } + if tc.Username != "" && tc.Password != "" { + token, err := tc.fetchToken() + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+token) } return nil } +// fetchToken exchanges username+password for a Taiga auth token via POST /auth +func (tc *TaigaConn) fetchToken() (string, errors.Error) { + endpoint := strings.TrimSuffix(tc.Endpoint, "/") + // strip /api/v1 suffix to get base, then re-add /api/v1/auth + authURL := endpoint + "/auth" + + body, e := json.Marshal(map[string]string{ + "type": "normal", + "username": tc.Username, + "password": tc.Password, + }) + if e != nil { + return "", errors.Default.WrapRaw(e) + } + + resp, e := http.Post(authURL, "application/json", bytes.NewReader(body)) //nolint:noctx + if e != nil { + return "", errors.Default.WrapRaw(e) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", errors.Default.New(fmt.Sprintf("taiga auth failed with status %d", resp.StatusCode)) + } + + var result map[string]interface{} + if e := json.NewDecoder(resp.Body).Decode(&result); e != nil { + return "", errors.Default.WrapRaw(e) + } + // Taiga returns auth_token (v5) or token (v6) + for _, key := range []string{"auth_token", "token"} { + if t, ok := result[key]; ok { + if token, ok := t.(string); ok && token != "" { + return token, nil + } + } + } + // fallback: read raw body hint + raw, _ := io.ReadAll(bytes.NewReader(body)) + return "", errors.Default.New(fmt.Sprintf("taiga auth response missing token field, body: %s", string(raw))) +} + // TaigaConnection holds TaigaConn plus ID/Name for database storage type TaigaConnection struct { helper.BaseConnection `mapstructure:",squash"` diff --git a/config-ui/src/plugins/register/taiga/connection-fields/auth.tsx b/config-ui/src/plugins/register/taiga/connection-fields/auth.tsx index c9d08207662..358eb0458dd 100644 --- a/config-ui/src/plugins/register/taiga/connection-fields/auth.tsx +++ b/config-ui/src/plugins/register/taiga/connection-fields/auth.tsx @@ -16,7 +16,7 @@ * */ -import { useState, useEffect } from 'react'; +import { useEffect } from 'react'; import { Input } from 'antd'; interface Props { @@ -72,7 +72,7 @@ export const Auth = ({ type, initialValues, values, setValues, setErrors }: Prop @@ -85,7 +85,12 @@ export const Auth = ({ type, initialValues, values, setValues, setErrors }: Prop Username * - +
diff --git a/grafana/dashboards/DeveloperTelemetry.json b/grafana/dashboards/DeveloperTelemetry.json index ddb9bcb0cc2..91be4a8ce67 100644 --- a/grafana/dashboards/DeveloperTelemetry.json +++ b/grafana/dashboards/DeveloperTelemetry.json @@ -104,7 +104,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT\n date as time,\n developer_id as metric,\n active_hours as value\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\nORDER BY date", + "rawSql": "SELECT\n date as time,\n developer_id as metric,\n active_hours as value\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\nORDER BY date", "refId": "A", "sql": { "columns": [ @@ -209,7 +209,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT\n UNIX_TIMESTAMP(date) as time_sec,\n developer_id as metric,\n CAST(JSON_EXTRACT(git_activity, '$.total_commits') AS UNSIGNED) as value\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\n AND JSON_EXTRACT(git_activity, '$.total_commits') IS NOT NULL\nORDER BY time_sec", + "rawSql": "SELECT\n UNIX_TIMESTAMP(date) as time_sec,\n developer_id as metric,\n CAST(JSON_EXTRACT(git_activity, '$.total_commits') AS UNSIGNED) as value\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\n AND JSON_EXTRACT(git_activity, '$.total_commits') IS NOT NULL\nORDER BY time_sec", "refId": "A" } ], @@ -285,7 +285,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n developer_id,\n CAST(SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) AS UNSIGNED) as lines_added\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\nGROUP BY developer_id\nORDER BY lines_added DESC", + "rawSql": "SELECT\n developer_id,\n CAST(SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) AS UNSIGNED) as lines_added\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\nGROUP BY developer_id\nORDER BY lines_added DESC", "refId": "A" } ], @@ -381,7 +381,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n developer_id,\n CAST(SUM(active_hours) AS UNSIGNED) as hours\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\nGROUP BY developer_id\nORDER BY hours DESC\nLIMIT 15", + "rawSql": "SELECT\n developer_id,\n CAST(SUM(active_hours) AS UNSIGNED) as hours\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\nGROUP BY developer_id\nORDER BY hours DESC\nLIMIT 15", "refId": "A" } ], @@ -468,7 +468,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n DAYNAME(date) as day_name,\n AVG(active_hours) as avg_hours\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\nGROUP BY DAYOFWEEK(date), DAYNAME(date)\nORDER BY DAYOFWEEK(date)", + "rawSql": "SELECT\n DAYNAME(date) as day_name,\n AVG(active_hours) as avg_hours\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\nGROUP BY DAYOFWEEK(date), DAYNAME(date)\nORDER BY DAYOFWEEK(date)", "refId": "A" } ], @@ -507,7 +507,10 @@ "id": 6, "options": { "legend": { - "calcs": ["mean", "max"], + "calcs": [ + "mean", + "max" + ], "displayMode": "table", "placement": "right", "showLegend": true @@ -527,7 +530,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "WITH top_devs AS (\n SELECT developer_id\n FROM _tool_developer_metrics\n WHERE connection_id = 2 AND $__timeFilter(date)\n GROUP BY developer_id\n ORDER BY SUM(CAST(JSON_EXTRACT(git_activity, '$.total_commits') AS UNSIGNED)) DESC\n LIMIT 10\n)\nSELECT\n UNIX_TIMESTAMP(date) as time_sec,\n developer_id as metric,\n CAST(JSON_EXTRACT(git_activity, '$.total_commits') AS UNSIGNED) as value\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\n AND developer_id IN (SELECT developer_id FROM top_devs)\nORDER BY time_sec", + "rawSql": "WITH top_devs AS (\n SELECT developer_id\n FROM _tool_developer_metrics\n WHERE connection_id = ${connection_id} AND $__timeFilter(date)\n GROUP BY developer_id\n ORDER BY SUM(CAST(JSON_EXTRACT(git_activity, '$.total_commits') AS UNSIGNED)) DESC\n LIMIT 10\n)\nSELECT\n UNIX_TIMESTAMP(date) as time_sec,\n developer_id as metric,\n CAST(JSON_EXTRACT(git_activity, '$.total_commits') AS UNSIGNED) as value\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\n AND developer_id IN (SELECT developer_id FROM top_devs)\nORDER BY time_sec", "refId": "A" } ], @@ -650,7 +653,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT\n date as time,\n 'lines_added' as metric,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) as value\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\nGROUP BY date\nUNION ALL\nSELECT\n date as time,\n 'lines_deleted' as metric,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_deleted')) as value\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\nGROUP BY date\nORDER BY time", + "rawSql": "SELECT\n date as time,\n 'lines_added' as metric,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) as value\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\nGROUP BY date\nUNION ALL\nSELECT\n date as time,\n 'lines_deleted' as metric,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_deleted')) as value\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\nGROUP BY date\nORDER BY time", "refId": "A" } ], @@ -719,7 +722,9 @@ "id": 8, "options": { "legend": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "displayMode": "table", "placement": "right", "showLegend": true @@ -738,7 +743,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT\n UNIX_TIMESTAMP(date) as time_sec,\n developer_id as metric,\n CAST(JSON_EXTRACT(development_activity, '$.test_runs_detected') AS UNSIGNED) as value\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\n AND JSON_EXTRACT(development_activity, '$.test_runs_detected') > 0\nORDER BY time_sec", + "rawSql": "SELECT\n UNIX_TIMESTAMP(date) as time_sec,\n developer_id as metric,\n CAST(JSON_EXTRACT(development_activity, '$.test_runs_detected') AS UNSIGNED) as value\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\n AND JSON_EXTRACT(development_activity, '$.test_runs_detected') > 0\nORDER BY time_sec", "refId": "A" } ], @@ -827,7 +832,9 @@ "footer": { "countRows": false, "fields": "", - "reducer": ["sum"], + "reducer": [ + "sum" + ], "show": false }, "showHeader": true, @@ -848,7 +855,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n developer_id,\n name,\n email,\n SUM(JSON_EXTRACT(git_activity, '$.total_commits')) as total_commits,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) as total_lines_added,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_deleted')) as total_lines_deleted,\n AVG(active_hours) as avg_active_hours,\n SUM(active_hours) as total_active_hours\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)\nGROUP BY developer_id, name, email\nORDER BY total_commits DESC", + "rawSql": "SELECT\n developer_id,\n name,\n email,\n SUM(JSON_EXTRACT(git_activity, '$.total_commits')) as total_commits,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) as total_lines_added,\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_deleted')) as total_lines_deleted,\n AVG(active_hours) as avg_active_hours,\n SUM(active_hours) as total_active_hours\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)\nGROUP BY developer_id, name, email\nORDER BY total_commits DESC", "refId": "A" } ], @@ -893,7 +900,9 @@ "orientation": "auto", "reduceOptions": { "values": false, - "calcs": ["sum"], + "calcs": [ + "sum" + ], "fields": "" }, "textMode": "auto" @@ -908,7 +917,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n SUM(JSON_EXTRACT(git_activity, '$.total_commits')) as total\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)", + "rawSql": "SELECT\n SUM(JSON_EXTRACT(git_activity, '$.total_commits')) as total\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)", "refId": "A" } ], @@ -953,7 +962,9 @@ "orientation": "auto", "reduceOptions": { "values": false, - "calcs": ["sum"], + "calcs": [ + "sum" + ], "fields": "" }, "textMode": "auto" @@ -968,7 +979,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n SUM(active_hours) as total\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)", + "rawSql": "SELECT\n SUM(active_hours) as total\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)", "refId": "A" } ], @@ -1013,7 +1024,9 @@ "orientation": "auto", "reduceOptions": { "values": false, - "calcs": ["sum"], + "calcs": [ + "sum" + ], "fields": "" }, "textMode": "auto" @@ -1028,7 +1041,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) as total\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)", + "rawSql": "SELECT\n SUM(JSON_EXTRACT(git_activity, '$.total_lines_added')) as total\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)", "refId": "A" } ], @@ -1073,7 +1086,9 @@ "orientation": "auto", "reduceOptions": { "values": false, - "calcs": ["lastNotNull"], + "calcs": [ + "lastNotNull" + ], "fields": "" }, "textMode": "auto" @@ -1088,7 +1103,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT\n COUNT(DISTINCT developer_id) as total\nFROM _tool_developer_metrics\nWHERE connection_id = 2\n AND $__timeFilter(date)", + "rawSql": "SELECT\n COUNT(DISTINCT developer_id) as total\nFROM _tool_developer_metrics\nWHERE connection_id = ${connection_id}\n AND $__timeFilter(date)", "refId": "A" } ], @@ -1099,9 +1114,38 @@ "refresh": "", "schemaVersion": 38, "style": "dark", - "tags": ["developer", "telemetry", "productivity"], + "tags": [ + "developer", + "telemetry", + "productivity" + ], "templating": { - "list": [] + "list": [ + { + "current": { + "selected": true, + "text": "4", + "value": "4" + }, + "datasource": { + "type": "mysql", + "uid": "mysql" + }, + "definition": "SELECT DISTINCT connection_id FROM _tool_developer_metrics ORDER BY connection_id", + "hide": 0, + "includeAll": false, + "label": "Connection ID", + "multi": false, + "name": "connection_id", + "options": [], + "query": "SELECT DISTINCT connection_id FROM _tool_developer_metrics ORDER BY connection_id", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] }, "time": { "from": "now-6w", @@ -1113,4 +1157,4 @@ "uid": "developer-telemetry", "version": 0, "weekStart": "" -} +} \ No newline at end of file