diff --git a/backend/plugins/jira/tasks/issue_extractor.go b/backend/plugins/jira/tasks/issue_extractor.go index 7d21b010cdf..0c0b7eaf720 100644 --- a/backend/plugins/jira/tasks/issue_extractor.go +++ b/backend/plugins/jira/tasks/issue_extractor.go @@ -158,6 +158,19 @@ func extractIssues(data *JiraTaskData, mappings *typeMappings, apiIssue *apiv2mo if issue.StdType == "" { issue.StdType = strings.ToUpper(issue.Type) } + + // AGENDRIX: Check if this is a Bug issue with Blocker custom field - mark as INCIDENT for DORA metrics + if issue.Type == "Bug" { + if customField, exists := apiIssue.Fields.AllFields["customfield_10070"]; exists { + if customFieldMap, ok := customField.(map[string]interface{}); ok { + if value, valueExists := customFieldMap["value"]; valueExists { + if valueStr, isString := value.(string); isString && valueStr == "Blocker" { + issue.StdType = "INCIDENT" + } + } + } + } + } issue.StdStatus = getStdStatus(issue.StatusKey) if value, ok := mappings.StandardStatusMappings[issue.Type][issue.StatusKey]; ok { issue.StdStatus = value.StandardStatus diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index fd335e867fa..34afc43ef12 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -13,12 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version: "3" services: mysql: image: mysql:8 volumes: - mysql-storage:/var/lib/mysql + - ./mysql-config/my.cnf:/etc/mysql/conf.d/custom.cnf restart: always ports: - 3306:3306 @@ -27,23 +27,23 @@ services: MYSQL_DATABASE: lake MYSQL_USER: merico MYSQL_PASSWORD: merico - TZ: UTC + TZ: America/New_York command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-log-bin - postgres: - image: postgres:14.2 - volumes: - - postgres-storage:/var/lib/postgresql - restart: always - ports: - - 5432:5432 - environment: - POSTGRES_DB: lake - POSTGRES_USER: merico - POSTGRES_PASSWORD: merico - TZ: UTC + # postgres: + # image: postgres:14.2 + # volumes: + # - postgres-storage:/var/lib/postgresql + # restart: always + # ports: + # - 5432:5432 + # environment: + # POSTGRES_DB: lake + # POSTGRES_USER: merico + # POSTGRES_PASSWORD: merico + # TZ: America/New_York grafana: image: devlake.docker.scarf.sh/apache/devlake-dashboard:latest @@ -54,7 +54,7 @@ services: volumes: - grafana-storage:/var/lib/grafana environment: - GF_SERVER_ROOT_URL: "http://localhost:4000/grafana" + GF_SERVER_ROOT_URL: "http://localhost:4001/grafana" GF_USERS_DEFAULT_THEME: "light" MYSQL_URL: mysql:3306 MYSQL_DATABASE: lake @@ -91,7 +91,7 @@ services: build: context: "config-ui" ports: - - 4000:4000 + - 4001:4000 env_file: - ./.env environment: @@ -121,13 +121,13 @@ services: # OAUTH2_PROXY_OIDC_JWKS_URL: # OAUTH2_PROXY_CLIENT_ID: # OAUTH2_PROXY_CLIENT_SECRET: - # OAUTH2_PROXY_UPSTREAMS: http://localhost:4000 + # OAUTH2_PROXY_UPSTREAMS: http://localhost:4001 # OAUTH2_PROXY_HTTP_ADDRESS: http://0.0.0.0:4180 # OAUTH2_PROXY_REVERSE_PROXY: 'true' # OAUTH2_PROXY_SKIP_AUTH_ROUTES: ^/grafana.* volumes: mysql-storage: + # postgres-storage: grafana-storage: - postgres-storage: devlake-log: \ No newline at end of file diff --git a/grafana/dashboards/TeamWeekly.json b/grafana/dashboards/TeamWeekly.json new file mode 100644 index 00000000000..385a66b0eee --- /dev/null +++ b/grafana/dashboards/TeamWeekly.json @@ -0,0 +1,494 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 44, + "links": [], + "panels": [ + { + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "always", + "stacking": "none", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": -45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "dataset": "lake", + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n a.user_name as reviewer,\n COUNT(DISTINCT prc.pull_request_id) as total_prs_reviewed\nFROM \n pull_request_comments prc\n JOIN accounts a ON prc.account_id = a.id\n JOIN pull_requests pr ON prc.pull_request_id = pr.id\n JOIN project_mapping pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos'\nWHERE \n prc.created_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)\n AND pm.project_name in ('Agendrix-web')\n AND a.user_name IS NOT NULL\n AND a.user_name NOT IN ('agendrix-bot')\nGROUP BY \n prc.account_id, a.user_name, a.full_name\nORDER BY \n total_prs_reviewed DESC;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "PR review count in the last 30 days", + "type": "barchart" + }, + { + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "days" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 0, + "y": 9 + }, + "id": 3, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "vertical", + "showValue": "always", + "stacking": "none", + "text": { + "valueSize": 1 + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": -45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "dataset": "lake", + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n pr.author_name as developer,\n AVG(TIMESTAMPDIFF(MINUTE, pr.created_date, pr.merged_date) / 1440) as avg_pr_time_to_merge_days\nFROM \n pull_requests pr\n JOIN project_mapping pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos'\nWHERE \n pr.merged_date IS NOT NULL\n AND pr.merged_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)\n AND pr.author_name NOT IN ('agendrix-bot')\nGROUP BY \n pr.author_name\nHAVING \n COUNT(DISTINCT pr.id) >= 1 -- Only include developers with at least 1 merged PR\nORDER BY \n avg_pr_time_to_merge_days asc;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Avg time to merge for the last 30 days (days)", + "type": "barchart" + }, + { + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 0, + "y": 17 + }, + "id": 2, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.6, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "always", + "stacking": "none", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "dataset": "lake", + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "with _merged_prs as(\n select\n DATE_ADD(date(pr.merged_date), INTERVAL -DAY(date(pr.merged_date))+1 DAY) as time,\n count(distinct pr.id) as pr_merged_count\n from\n pull_requests pr\n join project_mapping pm on pr.base_repo_id = pm.row_id and pm.table = 'repos' \n where\n pm.project_name in ('Agendrix-web')\n and pr.merged_date is not null\n and $__timeFilter(pr.merged_date)\n and pr.merged_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH)\n group by time\n)\n\nselect\n date_format(time,'%M %Y') as month,\n pr_merged_count\nfrom _merged_prs\norder by time asc", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "PRs Merged over Month", + "type": "barchart" + }, + { + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "description": "PR Time to Merge = PR Pickup Time + PR Review Time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "displayName": "PR Time to merge (days)", + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "days" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 0, + "y": 24 + }, + "id": 1, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "vertical", + "showValue": "always", + "stacking": "none", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xField": "month", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "dataset": "lake", + "datasource": { + "type": "mysql", + "uid": "P430005175C4C7810" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "with _merged_prs as(\n \n select\n DATE_ADD(date(pr.created_date), INTERVAL -DAY(date(pr.created_date))+1 DAY) as time,\n AVG(TIMESTAMPDIFF(MINUTE, pr.created_date, pr.merged_date) / 1440) as pr_time_to_merge_in_days\n from\n pull_requests pr\n join project_mapping pm on pr.base_repo_id = pm.row_id and pm.table = 'repos' \n where\n pm.project_name in ('Agendrix-web') and\n pr.merged_date is not null\n and $__timeFilter(pr.created_date)\n and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH)\n group by time\n)\n\nselect\n date_format(time,'%M %Y') as month,\n pr_time_to_merge_in_days\nfrom _merged_prs\norder by time asc", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "PR Time to merge", + "type": "barchart" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [ + "Developer" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-1y", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Team Weekly", + "uid": "ceqn4y6ax7ri8b", + "version": 21 +} \ No newline at end of file diff --git a/mysql-config/my.cnf b/mysql-config/my.cnf new file mode 100644 index 00000000000..f4e1a060dcb --- /dev/null +++ b/mysql-config/my.cnf @@ -0,0 +1,2 @@ +[mysqld] +sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION \ No newline at end of file