This is an automated email from the ASF dual-hosted git repository.
zky pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new e856b603e SonarCloud API support (#8132)
e856b603e is described below
commit e856b603ef96b62a23c9834dc5ff4b7901115e3b
Author: Klesh Wong <[email protected]>
AuthorDate: Fri Oct 11 14:36:49 2024 +0800
SonarCloud API support (#8132)
* feat(sonarcloud): add org to the connections table
* feat: add sonarqube cloud connection config
* fix: sonarcloud now working
* fix: linting
* fix: copywriting for sonarqube cloud
* feat: added cq_issue_impacts table
* fix: unit test
* fix: adjust sonarqube cloud config
* feat: add sonarcloud dashboard (#8140)
Co-authored-by: Startrekzky <[email protected]>
---------
Co-authored-by: mintsweet <[email protected]>
Co-authored-by: Louis.z <[email protected]>
Co-authored-by: Startrekzky <[email protected]>
---
.../models/domainlayer/codequality/cq_issues.go | 11 +
.../models/domainlayer/domaininfo/domaininfo.go | 1 +
.../20241010_add_ca_issue_impacts.go | 49 +
backend/core/models/migrationscripts/register.go | 1 +
backend/helpers/pluginhelper/api/api_client.go | 21 +-
backend/plugins/sonarqube/impl/impl.go | 2 +
backend/plugins/sonarqube/models/connection.go | 25 +-
...register.go => 20240930_add_connection_orgs.go} | 50 +-
.../migrationscripts/20241010_add_issue_impacts.go | 55 +
.../sonarqube/models/migrationscripts/register.go | 2 +
.../plugins/sonarqube/models/sonarqube_issue.go | 12 +
.../sonarqube/tasks/issue_impacts_convertor.go | 75 +
.../plugins/sonarqube/tasks/issues_extractor.go | 14 +
.../components/connection-form/fields/endpoint.tsx | 4 +-
.../src/plugins/register/sonarqube/config.tsx | 23 +-
.../sonarqube/connection-fields/organization.tsx | 71 +
grafana/dashboards/SonarQubeCloud.json | 1503 ++++++++++++++++++++
17 files changed, 1895 insertions(+), 24 deletions(-)
diff --git a/backend/core/models/domainlayer/codequality/cq_issues.go
b/backend/core/models/domainlayer/codequality/cq_issues.go
index 6856b8f7c..1e9ea7134 100644
--- a/backend/core/models/domainlayer/codequality/cq_issues.go
+++ b/backend/core/models/domainlayer/codequality/cq_issues.go
@@ -52,3 +52,14 @@ type CqIssue struct {
func (CqIssue) TableName() string {
return "cq_issues"
}
+
+type CqIssueImpact struct {
+ common.NoPKModel
+ CqIssueId string `gorm:"primaryKey;type:varchar(255)"`
+ SoftwareQuality string `gorm:"primaryKey;type:varchar(255)"`
+ Severity string `gorm:"type:varchar(100)"`
+}
+
+func (CqIssueImpact) TableName() string {
+ return "cq_issue_impacts"
+}
diff --git a/backend/core/models/domainlayer/domaininfo/domaininfo.go
b/backend/core/models/domainlayer/domaininfo/domaininfo.go
index 6837126f8..3bec3b810 100644
--- a/backend/core/models/domainlayer/domaininfo/domaininfo.go
+++ b/backend/core/models/domainlayer/domaininfo/domaininfo.go
@@ -53,6 +53,7 @@ func GetDomainTablesInfo() []dal.Tabler {
&codequality.CqFileMetrics{},
&codequality.CqIssueCodeBlock{},
&codequality.CqIssue{},
+ &codequality.CqIssueImpact{},
&codequality.CqProject{},
// crossdomain
&crossdomain.Account{},
diff --git
a/backend/core/models/migrationscripts/20241010_add_ca_issue_impacts.go
b/backend/core/models/migrationscripts/20241010_add_ca_issue_impacts.go
new file mode 100644
index 000000000..307df0147
--- /dev/null
+++ b/backend/core/models/migrationscripts/20241010_add_ca_issue_impacts.go
@@ -0,0 +1,49 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+ "github.com/apache/incubator-devlake/core/context"
+ "github.com/apache/incubator-devlake/core/errors"
+
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+ "github.com/apache/incubator-devlake/core/plugin"
+)
+
+var _ plugin.MigrationScript = (*addCqIssueImpacts)(nil)
+
+type cqIssueImpacts struct {
+ archived.NoPKModel
+ CqIssueId string `gorm:"primaryKey;type:varchar(255)"`
+ SoftwareQuality string `gorm:"primaryKey;type:varchar(255)"`
+ Severity string `gorm:"type:varchar(100)"`
+}
+
+type addCqIssueImpacts struct {
+}
+
+func (script *addCqIssueImpacts) Up(basicRes context.BasicRes) errors.Error {
+ return basicRes.GetDal().AutoMigrate(&cqIssueImpacts{})
+}
+
+func (*addCqIssueImpacts) Version() uint64 {
+ return 20241010162658
+}
+
+func (*addCqIssueImpacts) Name() string {
+ return "add cq_issue_impacts table"
+}
diff --git a/backend/core/models/migrationscripts/register.go
b/backend/core/models/migrationscripts/register.go
index 709099d59..abdcde958 100644
--- a/backend/core/models/migrationscripts/register.go
+++ b/backend/core/models/migrationscripts/register.go
@@ -134,5 +134,6 @@ func All() []plugin.MigrationScript {
new(addIsSubtaskToIssue),
new(addIsChildToCicdPipeline),
new(increaseCqIssueComponentLength),
+ new(addCqIssueImpacts),
}
}
diff --git a/backend/helpers/pluginhelper/api/api_client.go
b/backend/helpers/pluginhelper/api/api_client.go
index 5e1535de2..1e7e57d44 100644
--- a/backend/helpers/pluginhelper/api/api_client.go
+++ b/backend/helpers/pluginhelper/api/api_client.go
@@ -62,6 +62,7 @@ type ApiClient struct {
data map[string]interface{}
data_mutex sync.Mutex
+ authFunc plugin.ApiClientBeforeRequest
beforeRequest plugin.ApiClientBeforeRequest
afterResponse plugin.ApiClientAfterResponse
ctx gocontext.Context
@@ -92,7 +93,7 @@ func NewApiClientFromConnection(
// if connection requires authorization
if authenticator, ok := connection.(plugin.ApiAuthenticator); ok {
- apiClient.SetBeforeFunction(func(req *http.Request)
errors.Error {
+ apiClient.SetAuthFunction(func(req *http.Request) errors.Error {
return authenticator.SetupAuthentication(req)
})
}
@@ -255,6 +256,16 @@ func (apiClient *ApiClient) SetBeforeFunction(callback
plugin.ApiClientBeforeReq
apiClient.beforeRequest = callback
}
+// GetAuthFunction
+func (apiClient *ApiClient) GetAuthFunction() plugin.ApiClientBeforeRequest {
+ return apiClient.authFunc
+}
+
+// SetAuthFunction
+func (apiClient *ApiClient) SetAuthFunction(callback
plugin.ApiClientBeforeRequest) {
+ apiClient.authFunc = callback
+}
+
// GetAfterFunction return afterResponseFunction
func (apiClient *ApiClient) GetAfterFunction() plugin.ApiClientAfterResponse {
return apiClient.afterResponse
@@ -345,6 +356,14 @@ func (apiClient *ApiClient) Do(
}
var res *http.Response
+ // authFunc
+ if apiClient.authFunc != nil {
+ err = apiClient.authFunc(req)
+ if err != nil {
+ apiClient.logError(err, "[api-client] authFunc returned
error for %s", req.URL.String())
+ return nil, err
+ }
+ }
// before send
if apiClient.beforeRequest != nil {
err = apiClient.beforeRequest(req)
diff --git a/backend/plugins/sonarqube/impl/impl.go
b/backend/plugins/sonarqube/impl/impl.go
index f0da89812..9aae5247b 100644
--- a/backend/plugins/sonarqube/impl/impl.go
+++ b/backend/plugins/sonarqube/impl/impl.go
@@ -80,6 +80,7 @@ func (p Sonarqube) GetTablesInfo() []dal.Tabler {
&models.SonarqubeConnection{},
&models.SonarqubeProject{},
&models.SonarqubeIssue{},
+ &models.SonarqubeIssueImpact{},
&models.SonarqubeIssueCodeBlock{},
&models.SonarqubeHotspot{},
&models.SonarqubeFileMetrics{},
@@ -102,6 +103,7 @@ func (p Sonarqube) SubTaskMetas() []plugin.SubTaskMeta {
tasks.ExtractAccountsMeta,
tasks.ConvertProjectsMeta,
tasks.ConvertIssuesMeta,
+ tasks.ConvertIssueImpactsMeta,
tasks.ConvertIssueCodeBlocksMeta,
tasks.ConvertHotspotsMeta,
tasks.ConvertFileMetricsMeta,
diff --git a/backend/plugins/sonarqube/models/connection.go
b/backend/plugins/sonarqube/models/connection.go
index e4113e306..66c32f57c 100644
--- a/backend/plugins/sonarqube/models/connection.go
+++ b/backend/plugins/sonarqube/models/connection.go
@@ -20,9 +20,10 @@ package models
import (
"encoding/base64"
"fmt"
- "github.com/apache/incubator-devlake/core/utils"
"net/http"
+ "github.com/apache/incubator-devlake/core/utils"
+
"github.com/apache/incubator-devlake/core/errors"
"github.com/apache/incubator-devlake/core/plugin"
helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
@@ -49,6 +50,7 @@ func (sat SonarqubeAccessToken) GetEncodedToken() string {
type SonarqubeConn struct {
helper.RestConnection `mapstructure:",squash"`
SonarqubeAccessToken `mapstructure:",squash"`
+ Organization string `gorm:"serializer:json" json:"org"
mapstructure:"org"`
}
func (connection SonarqubeConn) Sanitize() SonarqubeConn {
@@ -89,3 +91,24 @@ func (connection *SonarqubeConnection)
MergeFromRequest(target *SonarqubeConnect
}
return nil
}
+
+func (connection *SonarqubeConnection) IsCloud() bool {
+ return connection.Endpoint == "https://sonarcloud.io/api/"
+}
+
+const ORG = "org"
+
+func (connection *SonarqubeConn) PrepareApiClient(apiClient plugin.ApiClient)
errors.Error {
+ apiClient.SetData(ORG, connection.Organization)
+ apiClient.SetBeforeFunction(func(req *http.Request) errors.Error {
+ org := apiClient.GetData(ORG).(string)
+ if org != "" {
+ query := req.URL.Query()
+ query.Add("organization", org)
+ req.URL.RawQuery = query.Encode()
+ }
+ return nil
+ })
+
+ return nil
+}
diff --git a/backend/plugins/sonarqube/models/migrationscripts/register.go
b/backend/plugins/sonarqube/models/migrationscripts/20240930_add_connection_orgs.go
similarity index 50%
copy from backend/plugins/sonarqube/models/migrationscripts/register.go
copy to
backend/plugins/sonarqube/models/migrationscripts/20240930_add_connection_orgs.go
index b11549cb8..87f64a1d4 100644
--- a/backend/plugins/sonarqube/models/migrationscripts/register.go
+++
b/backend/plugins/sonarqube/models/migrationscripts/20240930_add_connection_orgs.go
@@ -17,24 +17,34 @@ limitations under the License.
package migrationscripts
-import "github.com/apache/incubator-devlake/core/plugin"
-
-// All return all the migration scripts
-func All() []plugin.MigrationScript {
- return []plugin.MigrationScript{
- new(addInitTables),
- new(modifyCharacterSet),
- new(expandProjectKey20230206),
- new(addRawParamTableForScope),
- new(addScopeConfigIdToProject),
- new(modifyFileMetricsKeyLength),
- new(modifyComponentLength),
- new(addSonarQubeScopeConfig20231214),
- new(modifyCommitCharacterType),
- new(modifyCommitCharacterType0508),
- new(updateSonarQubeScopeConfig20240614),
- new(modifyNameLength),
- new(changeIssueComponentType),
- new(increaseProjectKeyLength),
- }
+import (
+ "github.com/apache/incubator-devlake/core/context"
+ "github.com/apache/incubator-devlake/core/errors"
+ "github.com/apache/incubator-devlake/core/plugin"
+ "github.com/apache/incubator-devlake/helpers/migrationhelper"
+)
+
+var _ plugin.MigrationScript = (*addOrgToConn)(nil)
+
+type connection20240930 struct {
+ Organization string
+}
+
+func (connection20240930) TableName() string {
+ return "_tool_sonarqube_connections"
+}
+
+type addOrgToConn struct {
+}
+
+func (script *addOrgToConn) Up(basicRes context.BasicRes) errors.Error {
+ return migrationhelper.AutoMigrateTables(basicRes,
&connection20240930{})
+}
+
+func (*addOrgToConn) Version() uint64 {
+ return 20240930151715
+}
+
+func (*addOrgToConn) Name() string {
+ return "add organizations to the connections table"
}
diff --git
a/backend/plugins/sonarqube/models/migrationscripts/20241010_add_issue_impacts.go
b/backend/plugins/sonarqube/models/migrationscripts/20241010_add_issue_impacts.go
new file mode 100644
index 000000000..00ed24358
--- /dev/null
+++
b/backend/plugins/sonarqube/models/migrationscripts/20241010_add_issue_impacts.go
@@ -0,0 +1,55 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+ "github.com/apache/incubator-devlake/core/context"
+ "github.com/apache/incubator-devlake/core/errors"
+
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+ "github.com/apache/incubator-devlake/core/plugin"
+ "github.com/apache/incubator-devlake/helpers/migrationhelper"
+)
+
+var _ plugin.MigrationScript = (*addIssueImpacts)(nil)
+
+type issueImpacts20241010 struct {
+ ConnectionId uint64 `gorm:"primaryKey"`
+ IssueKey string `gorm:"primaryKey;type:varchar(100)"`
+ SoftwareQuality string `gorm:"primaryKey;type:varchar(255)"`
+ Severity string `gorm:"type:varchar(100)"`
+ archived.NoPKModel
+}
+
+func (issueImpacts20241010) TableName() string {
+ return "_tool_sonarqube_issue_impacts"
+}
+
+type addIssueImpacts struct {
+}
+
+func (script *addIssueImpacts) Up(basicRes context.BasicRes) errors.Error {
+ return migrationhelper.AutoMigrateTables(basicRes,
&issueImpacts20241010{})
+}
+
+func (*addIssueImpacts) Version() uint64 {
+ return 20241010162943
+}
+
+func (*addIssueImpacts) Name() string {
+ return "add issue_impacts table for sonarcloud"
+}
diff --git a/backend/plugins/sonarqube/models/migrationscripts/register.go
b/backend/plugins/sonarqube/models/migrationscripts/register.go
index b11549cb8..849f11248 100644
--- a/backend/plugins/sonarqube/models/migrationscripts/register.go
+++ b/backend/plugins/sonarqube/models/migrationscripts/register.go
@@ -36,5 +36,7 @@ func All() []plugin.MigrationScript {
new(modifyNameLength),
new(changeIssueComponentType),
new(increaseProjectKeyLength),
+ new(addOrgToConn),
+ new(addIssueImpacts),
}
}
diff --git a/backend/plugins/sonarqube/models/sonarqube_issue.go
b/backend/plugins/sonarqube/models/sonarqube_issue.go
index fefd75721..0c8df2292 100644
--- a/backend/plugins/sonarqube/models/sonarqube_issue.go
+++ b/backend/plugins/sonarqube/models/sonarqube_issue.go
@@ -50,3 +50,15 @@ type SonarqubeIssue struct {
func (SonarqubeIssue) TableName() string {
return "_tool_sonarqube_issues"
}
+
+type SonarqubeIssueImpact struct {
+ ConnectionId uint64 `gorm:"primaryKey"`
+ IssueKey string `gorm:"primaryKey;type:varchar(100)"`
+ SoftwareQuality string `gorm:"primaryKey;type:varchar(255)"`
+ Severity string `gorm:"type:varchar(100)"`
+ common.NoPKModel
+}
+
+func (SonarqubeIssueImpact) TableName() string {
+ return "_tool_sonarqube_issue_impacts"
+}
diff --git a/backend/plugins/sonarqube/tasks/issue_impacts_convertor.go
b/backend/plugins/sonarqube/tasks/issue_impacts_convertor.go
new file mode 100644
index 000000000..be8ab451b
--- /dev/null
+++ b/backend/plugins/sonarqube/tasks/issue_impacts_convertor.go
@@ -0,0 +1,75 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+ "reflect"
+
+ "github.com/apache/incubator-devlake/core/dal"
+ "github.com/apache/incubator-devlake/core/errors"
+
"github.com/apache/incubator-devlake/core/models/domainlayer/codequality"
+ "github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
+ "github.com/apache/incubator-devlake/core/plugin"
+ "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+ sonarqubeModels
"github.com/apache/incubator-devlake/plugins/sonarqube/models"
+)
+
+var ConvertIssueImpactsMeta = plugin.SubTaskMeta{
+ Name: "convertIssueImpacts",
+ EntryPoint: ConvertIssueImpacts,
+ EnabledByDefault: true,
+ Description: "Convert tool layer table sonarqube_issue_impacts
into domain layer table cq_issue_impacts",
+ DomainTypes: []string{plugin.DOMAIN_TYPE_CODE_QUALITY},
+}
+
+func ConvertIssueImpacts(taskCtx plugin.SubTaskContext) errors.Error {
+ db := taskCtx.GetDal()
+ rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx,
RAW_ISSUES_TABLE)
+ cursor, err := db.Cursor(
+ dal.From("_tool_sonarqube_issue_impacts p"),
+ dal.Join("LEFT JOIN _tool_sonarqube_issues i ON
(i.connection_id = p.connection_id AND i.issue_key = p.issue_key)"),
+ dal.Where("i.connection_id = ? AND i.project_key = ?",
data.Options.ConnectionId, data.Options.ProjectKey))
+ if err != nil {
+ return err
+ }
+ defer cursor.Close()
+
+ issueIdGen :=
didgen.NewDomainIdGenerator(&sonarqubeModels.SonarqubeIssue{})
+ converter, err := api.NewDataConverter(api.DataConverterArgs{
+ InputRowType:
reflect.TypeOf(sonarqubeModels.SonarqubeIssueImpact{}),
+ Input: cursor,
+ RawDataSubTaskArgs: *rawDataSubTaskArgs,
+ Convert: func(inputRow interface{}) ([]interface{},
errors.Error) {
+ impact :=
inputRow.(*sonarqubeModels.SonarqubeIssueImpact)
+ domainIssueImpact := &codequality.CqIssueImpact{
+ CqIssueId:
issueIdGen.Generate(data.Options.ConnectionId, impact.IssueKey),
+ SoftwareQuality: impact.SoftwareQuality,
+ Severity: impact.Severity,
+ }
+ return []interface{}{
+ domainIssueImpact,
+ }, nil
+ },
+ })
+
+ if err != nil {
+ return err
+ }
+
+ return converter.Execute()
+}
diff --git a/backend/plugins/sonarqube/tasks/issues_extractor.go
b/backend/plugins/sonarqube/tasks/issues_extractor.go
index 80c1193c9..1cd9a93c0 100644
--- a/backend/plugins/sonarqube/tasks/issues_extractor.go
+++ b/backend/plugins/sonarqube/tasks/issues_extractor.go
@@ -107,6 +107,16 @@ func ExtractIssues(taskCtx plugin.SubTaskContext)
errors.Error {
results = append(results, codeBlock)
}
}
+
+ for _, v := range body.Impacts {
+ impact := &models.SonarqubeIssueImpact{
+ ConnectionId:
data.Options.ConnectionId,
+ IssueKey:
sonarqubeIssue.IssueKey,
+ SoftwareQuality: v.SoftwareQuality,
+ Severity: v.Severity,
+ }
+ results = append(results, impact)
+ }
return results, nil
},
})
@@ -151,6 +161,10 @@ type IssuesResponse struct {
Type string `json:"type"`
Scope string `json:"scope"`
QuickFixAvailable bool `json:"quickFixAvailable"`
+ Impacts []struct {
+ SoftwareQuality string `json:"softwareQuality"`
+ Severity string `json:"severity"`
+ } `json:"impacts"`
}
type flow struct {
diff --git
a/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
b/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
index 882951dab..4639f65e2 100644
--- a/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
@@ -29,6 +29,7 @@ interface Props {
disabled?: boolean;
name: string;
multipleVersions?: Record<VersionType, string>;
+ cloudName?: string;
initialValue: string;
value: string;
error: string;
@@ -41,6 +42,7 @@ export const ConnectionEndpoint = ({
disabled = false,
name,
multipleVersions,
+ cloudName,
initialValue,
value,
setValue,
@@ -79,7 +81,7 @@ export const ConnectionEndpoint = ({
<>
<Block title={`${name} Version`} required>
<Radio.Group value={version} onChange={handleChange}>
- <Radio value="cloud">{name} Cloud</Radio>
+ <Radio value="cloud">{cloudName ? cloudName : `${name}
Cloud`}</Radio>
<Radio value="server" disabled={!multipleVersions.server}>
{name} Server {multipleVersions.server ? multipleVersions.server
: '(to be supported)'}
</Radio>
diff --git a/config-ui/src/plugins/register/sonarqube/config.tsx
b/config-ui/src/plugins/register/sonarqube/config.tsx
index a2922a4ee..60d1d4837 100644
--- a/config-ui/src/plugins/register/sonarqube/config.tsx
+++ b/config-ui/src/plugins/register/sonarqube/config.tsx
@@ -21,6 +21,8 @@ import { IPluginConfig } from '@/types';
import Icon from './assets/icon.svg?react';
+import { Organization } from './connection-fields/organization';
+
export const SonarQubeConfig: IPluginConfig = {
plugin: 'sonarqube',
name: 'SonarQube',
@@ -28,12 +30,31 @@ export const SonarQubeConfig: IPluginConfig = {
sort: 11,
connection: {
docLink: DOC_URL.PLUGIN.SONARQUBE.BASIS,
+ initialValues: {
+ endpoint: 'https://sonarcloud.io/api/',
+ },
fields: [
'name',
{
key: 'endpoint',
- subLabel: 'Provide the SonarQube instance API endpoint. E.g.
http://<host>:<port>/api/',
+ multipleVersions: {
+ cloud: 'https://sonarcloud.io/api/',
+ server: ' ',
+ },
+ cloudName: 'SonarCloud',
+ subLabel: 'The URL should be `http://<sonarqube-host>:<port>/api/`',
},
+ ({ type, initialValues, values, errors, setValues, setErrors }: any) => (
+ <Organization
+ key="organization"
+ type={type}
+ initialValues={initialValues}
+ values={values}
+ errors={errors}
+ setValues={setValues}
+ setErrors={setErrors}
+ />
+ ),
'token',
'proxy',
{
diff --git
a/config-ui/src/plugins/register/sonarqube/connection-fields/organization.tsx
b/config-ui/src/plugins/register/sonarqube/connection-fields/organization.tsx
new file mode 100644
index 000000000..4537ed1fd
--- /dev/null
+++
b/config-ui/src/plugins/register/sonarqube/connection-fields/organization.tsx
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Input } from 'antd';
+
+import { Block, ExternalLink } from '@/components';
+import { useEffect } from 'react';
+
+interface Props {
+ type: 'create' | 'update';
+ initialValues: any;
+ values: any;
+ errors: any;
+ setValues: (value: any) => void;
+ setErrors: (value: any) => void;
+}
+
+export const Organization = ({ initialValues, values, setValues, setErrors }:
Props) => {
+ const { endpoint } = values;
+
+ useEffect(() => {
+ setValues({ org: initialValues.org });
+ }, [initialValues.org]);
+
+ useEffect(() => {
+ setErrors({
+ org: values.org ? '' : 'organization is required',
+ });
+ }, [values.org]);
+
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ setValues({
+ org: e.target.value,
+ });
+ };
+
+ if (endpoint !== 'https://sonarcloud.io/api/') {
+ return null;
+ }
+
+ return (
+ <Block
+ title="Organization"
+ description={
+ <>
+ Copy the organization key at{' '}
+ <ExternalLink
link="https://sonarcloud.io/account/organizations">here</ExternalLink>. If you
have more than
+ one, please create another connection.
+ </>
+ }
+ required
+ >
+ <Input style={{ width: 386 }} placeholder="e.g. org-1"
value={values.org} onChange={handleChange} />
+ </Block>
+ );
+};
diff --git a/grafana/dashboards/SonarQubeCloud.json
b/grafana/dashboards/SonarQubeCloud.json
new file mode 100644
index 000000000..1a2907038
--- /dev/null
+++ b/grafana/dashboards/SonarQubeCloud.json
@@ -0,0 +1,1503 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": 40,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 19,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "SonarQube",
+ "url": "https://devlake.apache.org/docs/Plugins/sonarqube"
+ }
+ ],
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false
+ },
+ "content": "- Use Cases: This dashboard shows the code quality metrics
from SonarCloud.\n- Data Source Required: SonarCloud\n- This dashboard does not
honor the time filter on the top-right side as SonarQube metrics are all from
the latest scan.",
+ "mode": "markdown"
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "queryType": "randomWalk",
+ "refId": "A"
+ }
+ ],
+ "title": "Dashboard Introduction",
+ "type": "text"
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 4
+ },
+ "id": 16,
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Software Quality",
+ "type": "row"
+ },
+ {
+ "datasource": "mysql",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 0,
+ "y": 5
+ },
+ "id": 3,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Issue Count",
+ "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "value",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n count(distinct ci.id)\nFROM \n cq_issues ci\n
join cq_issue_impacts cii on ci.id = cii.cq_issue_id\nWHERE\n ci.project_key
in (${project_id})\n and cii.software_quality = 'SECURITY'\n and ci.severity
in (${severity})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Security",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 8,
+ "y": 5
+ },
+ "id": 2,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Issue Count",
+ "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "vertical",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "value",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n count(distinct ci.id)\nFROM \n cq_issues ci\n
join cq_issue_impacts cii on ci.id = cii.cq_issue_id\nWHERE\n ci.project_key
in (${project_id})\n and cii.software_quality = 'RELIABILITY'\n and
ci.severity in (${severity})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Reliability",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 16,
+ "y": 5
+ },
+ "id": 20,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Issue Count",
+ "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "vertical",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "value",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n count(distinct ci.id)\nFROM \n cq_issues ci\n
join cq_issue_impacts cii on ci.id = cii.cq_issue_id\nWHERE\n ci.project_key
in (${project_id})\n and cii.software_quality = 'MAINTAINABILITY'\n and
ci.severity in (${severity})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Maintainability",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 10
+ },
+ "id": 21,
+ "panels": [],
+ "title": "Security Review",
+ "type": "row"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 0,
+ "y": 11
+ },
+ "id": 4,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Issue Count",
+ "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n COUNT(distinct id) AS 'Security Hotspots'\nFROM
cq_issues\nWHERE\n project_key in (${project_id})\n and type = 'HOTSPOTS'\n
and severity in (${severity})\n",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Security Hotspots",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 8,
+ "y": 11
+ },
+ "id": 13,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Issue Count",
+ "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n CONCAT(ROUND(COUNT(IF(status != 'TO_REVIEW',
id, NULL)) / COUNT(distinct id) * 100, 2), '%') AS 'Reviewed'\nFROM
cq_issues\nWHERE\n project_key in (${project_id})\n and type = 'HOTSPOTS'\n
and severity in (${severity})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Hotspots Reviewed",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 16
+ },
+ "id": 12,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Test & Maintainability",
+ "type": "row"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 0,
+ "y": 17
+ },
+ "id": 8,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Test",
+ "url": "https://devlake.apache.org/docs/Metrics/CQTest"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n CONCAT(ROUND((sum(lines_to_cover) -
sum(uncovered_lines)) / sum(lines_to_cover) * 100, 1), '% ', 'Coverage on ',
ROUND(sum(lines_to_cover) / 1000, 0),'k Lines to cover')\nFROM
cq_file_metrics\nWHERE\n project_key in (${project_id})\n",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Test Coverage",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "code smells"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 8,
+ "y": 17
+ },
+ "id": 14,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Maintainability-Debt",
+ "url":
"https://devlake.apache.org/docs/Metrics/CQMaintainability-Debt"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "value",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n\tCOUNT(distinct id) as 'Code Smells'\nFROM
cq_issues\nWHERE\n project_key in (${project_id})\n and type = 'CODE_SMELL'\n
and severity in (${severity})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Maintainability - Code Smells",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "string"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 16,
+ "y": 17
+ },
+ "id": 7,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Maintainability-Debt",
+ "url":
"https://devlake.apache.org/docs/Metrics/CQMaintainability-Debt"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "value",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n concat(FLOOR(SUM(debt)/8/60), \" day(s) \",
FLOOR((SUM(debt)%480)/60), \" hour(s) \") AS 'Debt'\nFROM cq_issues\nWHERE\n
project_key in (${project_id})\n and type = 'CODE_SMELL'\n and severity in
(${severity})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Maintainability - Debt",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 22
+ },
+ "id": 6,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Duplications",
+ "type": "row"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 0,
+ "y": 23
+ },
+ "id": 10,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Duplicated Blocks",
+ "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n sum(duplicated_blocks)\nFROM
cq_file_metrics\nWHERE\n project_key in (${project_id})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Duplicated Blocks",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 8,
+ "y": 23
+ },
+ "id": 9,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Duplicated Lines",
+ "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedLines"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n CONCAT(ROUND(sum(duplicated_lines) /
sum(num_of_lines) * 100, 1), '% ', 'Duplications on ', ROUND(sum(ncloc) / 1000,
0),'k Lines')\nFROM cq_file_metrics\nWHERE\n project_key in (${project_id})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Duplicated Lines",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 28
+ },
+ "id": 22,
+ "panels": [],
+ "title": "Size",
+ "type": "row"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 0,
+ "y": 29
+ },
+ "id": 23,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Duplicated Blocks",
+ "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n sum(ncloc)\nFROM cq_file_metrics\nWHERE\n
project_key in (${project_id})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Lines of Code",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 8,
+ "y": 29
+ },
+ "id": 24,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Duplicated Blocks",
+ "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n sum(num_of_lines)\nFROM
cq_file_metrics\nWHERE\n project_key in (${project_id})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Lines",
+ "type": "stat"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 8,
+ "x": 16,
+ "y": 29
+ },
+ "id": 25,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Duplicated Blocks",
+ "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks"
+ }
+ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "/.*/",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n count(distinct file_path)\nFROM
cq_file_metrics\nWHERE\n project_key in (${project_id})",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Files",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 34
+ },
+ "id": 26,
+ "panels": [],
+ "title": "Overall Code Quality Metrics",
+ "type": "row"
+ },
+ {
+ "datasource": "mysql",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "color-text"
+ },
+ "filterable": false,
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 24,
+ "x": 0,
+ "y": 35
+ },
+ "id": 17,
+ "links": [
+ {
+ "targetBlank": true,
+ "title": "Code Quality Issue Count",
+ "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount"
+ }
+ ],
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "showHeader": true
+ },
+ "pluginVersion": "11.2.0",
+ "targets": [
+ {
+ "dataset": "lake",
+ "datasource": "mysql",
+ "editorMode": "code",
+ "format": "table",
+ "rawQuery": true,
+ "rawSql": "SELECT\n\tfile_name, num_of_lines as 'Lines of Code',
bugs as 'Bugs', vulnerabilities as 'Vulnerabilities', code_smells as 'Code
Smells', \n\tsecurity_hotspots as 'Security Hotspots', CONCAT(ROUND(coverage,
2), '%') as 'Coverage', CONCAT(ROUND(duplicated_lines_density, 2), '%') as
'Duplications'\nFROM cq_file_metrics\nWHERE\n project_key in
(${project_id})\nORDER BY bugs desc\nlimit 20",
+ "refId": "A",
+ "sql": {
+ "columns": [
+ {
+ "parameters": [],
+ "type": "function"
+ }
+ ],
+ "groupBy": [
+ {
+ "property": {
+ "type": "string"
+ },
+ "type": "groupBy"
+ }
+ ],
+ "limit": 50
+ }
+ }
+ ],
+ "title": "Code Quality Metrics by Files (Top 20 order by Bugs)",
+ "type": "table"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 39,
+ "tags": [
+ "Data Source Dashboard",
+ "Stable Data Sources"
+ ],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": true,
+ "text": [
+ "All"
+ ],
+ "value": [
+ "$__all"
+ ]
+ },
+ "datasource": "mysql",
+ "definition": "select concat(name, '--', id) as text from cq_projects",
+ "hide": 0,
+ "includeAll": true,
+ "label": "SonarQube Project",
+ "multi": true,
+ "name": "project_id",
+ "options": [],
+ "query": "select concat(name, '--', id) as text from cq_projects",
+ "refresh": 1,
+ "regex": "/^(?<text>.*)--(?<value>.*)$/",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "current": {
+ "selected": true,
+ "text": [
+ "All"
+ ],
+ "value": [
+ "$__all"
+ ]
+ },
+ "datasource": "mysql",
+ "definition": "select distinct severity from cq_issues where id like
'sonar%'",
+ "hide": 0,
+ "includeAll": true,
+ "label": "Severity",
+ "multi": true,
+ "name": "severity",
+ "options": [],
+ "query": "select distinct severity from cq_issues where id like
'sonar%'",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ }
+ ]
+ },
+ "time": {
+ "from": "now",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "utc",
+ "title": "SonarQube Cloud",
+ "uid": "WA0qbuJ4l",
+ "version": 1,
+ "weekStart": ""
+}
\ No newline at end of file