Commit daaa377a authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch 'mf-codequality-widget-frontend-feature-move' into 'master'

Use new vuex store for code quality MR widget

See merge request gitlab-org/gitlab!36120
parents 130cc53c 196f5f53
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import { componentNames } from '~/reports/components/issue_body';
import { s__, sprintf } from '~/locale';
import ReportSection from '~/reports/components/report_section.vue';
import createStore from './store';
export default {
name: 'GroupedCodequalityReportsApp',
store: createStore(),
components: {
ReportSection,
},
props: {
headPath: {
type: String,
required: true,
},
headBlobPath: {
type: String,
required: true,
},
basePath: {
type: String,
required: false,
default: null,
},
baseBlobPath: {
type: String,
required: false,
default: null,
},
codequalityHelpPath: {
type: String,
required: true,
},
},
componentNames,
computed: {
...mapState(['newIssues', 'resolvedIssues']),
...mapGetters([
'hasCodequalityIssues',
'codequalityStatus',
'codequalityText',
'codequalityPopover',
]),
},
created() {
this.setPaths({
basePath: this.basePath,
headPath: this.headPath,
baseBlobPath: this.baseBlobPath,
headBlobPath: this.headBlobPath,
helpPath: this.codequalityHelpPath,
});
this.fetchReports();
},
methods: {
...mapActions(['fetchReports', 'setPaths']),
},
loadingText: sprintf(s__('ciReport|Loading %{reportName} report'), {
reportName: 'codeclimate',
}),
errorText: sprintf(s__('ciReport|Failed to load %{reportName} report'), {
reportName: 'codeclimate',
}),
};
</script>
<template>
<report-section
:status="codequalityStatus"
:loading-text="$options.loadingText"
:error-text="$options.errorText"
:success-text="codequalityText"
:unresolved-issues="newIssues"
:resolved-issues="resolvedIssues"
:has-issues="hasCodequalityIssues"
:component="$options.componentNames.CodequalityIssueBody"
:popover-options="codequalityPopover"
class="js-codequality-widget mr-widget-border-top mr-report"
/>
</template>
import TestIssueBody from './test_issue_body.vue'; import TestIssueBody from './test_issue_body.vue';
import AccessibilityIssueBody from '../accessibility_report/components/accessibility_issue_body.vue'; import AccessibilityIssueBody from '../accessibility_report/components/accessibility_issue_body.vue';
import CodequalityIssueBody from '../codequality_report/components/codequality_issue_body.vue';
export const components = { export const components = {
AccessibilityIssueBody, AccessibilityIssueBody,
CodequalityIssueBody,
TestIssueBody, TestIssueBody,
}; };
export const componentNames = { export const componentNames = {
AccessibilityIssueBody: AccessibilityIssueBody.name, AccessibilityIssueBody: AccessibilityIssueBody.name,
CodequalityIssueBody: CodequalityIssueBody.name,
TestIssueBody: TestIssueBody.name, TestIssueBody: TestIssueBody.name,
}; };
...@@ -37,6 +37,7 @@ import eventHub from './event_hub'; ...@@ -37,6 +37,7 @@ import eventHub from './event_hub';
import notify from '~/lib/utils/notify'; import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue'; import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue'; import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue';
import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_codequality_reports_app.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue'; import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils'; import { setFaviconOverlay } from '../lib/utils/common_utils';
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue'; import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
...@@ -75,6 +76,7 @@ export default { ...@@ -75,6 +76,7 @@ export default {
'mr-widget-auto-merge-failed': AutoMergeFailed, 'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState, 'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus, SourceBranchRemovalStatus,
GroupedCodequalityReportsApp,
GroupedTestReportsApp, GroupedTestReportsApp,
TerraformPlan, TerraformPlan,
GroupedAccessibilityReportsApp, GroupedAccessibilityReportsApp,
...@@ -111,6 +113,9 @@ export default { ...@@ -111,6 +113,9 @@ export default {
shouldSuggestPipelines() { shouldSuggestPipelines() {
return gon.features?.suggestPipeline && !this.mr.hasCI && this.mr.mergeRequestAddCiConfigPath; return gon.features?.suggestPipeline && !this.mr.hasCI && this.mr.mergeRequestAddCiConfigPath;
}, },
shouldRenderCodeQuality() {
return this.mr?.codeclimate?.head_path;
},
shouldRenderRelatedLinks() { shouldRenderRelatedLinks() {
return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState; return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState;
}, },
...@@ -380,6 +385,15 @@ export default { ...@@ -380,6 +385,15 @@ export default {
:mr="mr" :mr="mr"
/> />
<div class="mr-section-container mr-widget-workflow"> <div class="mr-section-container mr-widget-workflow">
<grouped-codequality-reports-app
v-if="shouldRenderCodeQuality"
:base-path="mr.codeclimate.base_path"
:head-path="mr.codeclimate.head_path"
:head-blob-path="mr.headBlobPath"
:base-blob-path="mr.baseBlobPath"
:codequality-help-path="mr.codequalityHelpPath"
/>
<grouped-test-reports-app <grouped-test-reports-app
v-if="mr.testResultsPath" v-if="mr.testResultsPath"
class="js-reports-container" class="js-reports-container"
......
...@@ -185,6 +185,13 @@ export default class MergeRequestStore { ...@@ -185,6 +185,13 @@ export default class MergeRequestStore {
this.pipelinesEmptySvgPath = data.pipelines_empty_svg_path; this.pipelinesEmptySvgPath = data.pipelines_empty_svg_path;
this.humanAccess = data.human_access; this.humanAccess = data.human_access;
this.newPipelinePath = data.new_project_pipeline_path; this.newPipelinePath = data.new_project_pipeline_path;
// codeclimate
const blobPath = data.blob_path || {};
this.headBlobPath = blobPath.head_path || '';
this.baseBlobPath = blobPath.base_path || '';
this.codequalityHelpPath = data.codequality_help_path;
this.codeclimate = data.codeclimate;
} }
get isNothingToMergeState() { get isNothingToMergeState() {
......
...@@ -13,5 +13,6 @@ ...@@ -13,5 +13,6 @@
window.gl.mrWidgetData.security_approvals_help_page_path = '#{help_page_path('user/application_security/index.md', anchor: 'security-approvals-in-merge-requests-ultimate')}'; window.gl.mrWidgetData.security_approvals_help_page_path = '#{help_page_path('user/application_security/index.md', anchor: 'security-approvals-in-merge-requests-ultimate')}';
window.gl.mrWidgetData.eligible_approvers_docs_path = '#{help_page_path('user/project/merge_requests/merge_request_approvals', anchor: 'eligible-approvers')}'; window.gl.mrWidgetData.eligible_approvers_docs_path = '#{help_page_path('user/project/merge_requests/merge_request_approvals', anchor: 'eligible-approvers')}';
window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}'; window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}';
window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}';
#js-vue-mr-widget.mr-widget #js-vue-mr-widget.mr-widget
---
title: Use new vuex store for code quality MR widget
merge_request: 36120
author:
type: changed
...@@ -239,7 +239,7 @@ The following documentation relates to the DevOps **Verify** stage: ...@@ -239,7 +239,7 @@ The following documentation relates to the DevOps **Verify** stage:
| Verify topics | Description | | Verify topics | Description |
|:----------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------| |:----------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------|
| [Code Quality reports](user/project/merge_requests/code_quality.md) **(STARTER)** | Analyze source code quality. | | [Code Quality reports](user/project/merge_requests/code_quality.md) | Analyze source code quality. |
| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. | | [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. |
| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. | | [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. |
| [Multi-project pipelines](ci/multi_project_pipelines.md) **(PREMIUM)** | Visualize entire pipelines that span multiple projects, including all cross-project inter-dependencies. | | [Multi-project pipelines](ci/multi_project_pipelines.md) **(PREMIUM)** | Visualize entire pipelines that span multiple projects, including all cross-project inter-dependencies. |
......
...@@ -129,7 +129,7 @@ Its feature set is listed on the table below according to DevOps stages. ...@@ -129,7 +129,7 @@ Its feature set is listed on the table below according to DevOps stages.
| [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the browser performance impact of pending code changes. | | [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the browser performance impact of pending code changes. |
| [Load Performance Testing](../user/project/merge_requests/load_performance_testing.md) | Quickly determine the server performance impact of pending code changes. | | [Load Performance Testing](../user/project/merge_requests/load_performance_testing.md) | Quickly determine the server performance impact of pending code changes. |
| [CI services](services/README.md) | Link Docker containers with your base image.| | [CI services](services/README.md) | Link Docker containers with your base image.|
| [Code Quality](../user/project/merge_requests/code_quality.md) **(STARTER)** | Analyze your source code quality. | | [Code Quality](../user/project/merge_requests/code_quality.md) | Analyze your source code quality. |
| [GitLab CI/CD for external repositories](ci_cd_for_external_repos/index.md) **(PREMIUM)** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and Bitbucket Cloud. | | [GitLab CI/CD for external repositories](ci_cd_for_external_repos/index.md) **(PREMIUM)** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and Bitbucket Cloud. |
| [Interactive Web Terminals](interactive_web_terminal/index.md) **(CORE ONLY)** | Open an interactive web terminal to debug the running jobs. | | [Interactive Web Terminals](interactive_web_terminal/index.md) **(CORE ONLY)** | Open an interactive web terminal to debug the running jobs. |
| [JUnit tests](junit_test_reports.md) | Identify script failures directly on merge requests. | | [JUnit tests](junit_test_reports.md) | Identify script failures directly on merge requests. |
......
...@@ -188,7 +188,7 @@ according to each stage (Verify, Package, Release). ...@@ -188,7 +188,7 @@ according to each stage (Verify, Package, Release).
1. **Verify**: 1. **Verify**:
- Automatically build and test your application with Continuous Integration. - Automatically build and test your application with Continuous Integration.
- Analyze your source code quality with [GitLab Code Quality](../../user/project/merge_requests/code_quality.md). **(STARTER)** - Analyze your source code quality with [GitLab Code Quality](../../user/project/merge_requests/code_quality.md).
- Determine the browser performance impact of code changes with [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md). **(PREMIUM)** - Determine the browser performance impact of code changes with [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md). **(PREMIUM)**
- Determine the server performance impact of code changes with [Load Performance Testing](../../user/project/merge_requests/load_performance_testing.md). **(PREMIUM)** - Determine the server performance impact of code changes with [Load Performance Testing](../../user/project/merge_requests/load_performance_testing.md). **(PREMIUM)**
- Perform a series of tests, such as [Container Scanning](../../user/application_security/container_scanning/index.md) **(ULTIMATE)**, [Dependency Scanning](../../user/application_security/dependency_scanning/index.md) **(ULTIMATE)**, and [JUnit tests](../junit_test_reports.md). - Perform a series of tests, such as [Container Scanning](../../user/application_security/container_scanning/index.md) **(ULTIMATE)**, [Dependency Scanning](../../user/application_security/dependency_scanning/index.md) **(ULTIMATE)**, and [JUnit tests](../junit_test_reports.md).
......
...@@ -146,9 +146,10 @@ plan report will be uploaded to GitLab as an artifact and will be automatically ...@@ -146,9 +146,10 @@ plan report will be uploaded to GitLab as an artifact and will be automatically
in merge requests. For more information, see in merge requests. For more information, see
[Output `terraform plan` information into a merge request](../../user/infrastructure/index.md#output-terraform-plan-information-into-a-merge-request). [Output `terraform plan` information into a merge request](../../user/infrastructure/index.md#output-terraform-plan-information-into-a-merge-request).
#### `artifacts:reports:codequality` **(STARTER)** #### `artifacts:reports:codequality`
> - Introduced in GitLab 11.5. > - Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5.
> - Made [available in all tiers](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) in GitLab 13.2.
> - Requires GitLab Runner 11.5 and above. > - Requires GitLab Runner 11.5 and above.
The `codequality` report collects [CodeQuality issues](../../user/project/merge_requests/code_quality.md) The `codequality` report collects [CodeQuality issues](../../user/project/merge_requests/code_quality.md)
......
...@@ -117,7 +117,7 @@ The following table lists available parameters for jobs: ...@@ -117,7 +117,7 @@ The following table lists available parameters for jobs:
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. | | [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. | | [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. |
| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. | | [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. |
| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:exclude`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, `artifacts:reports:junit`, `artifacts:reports:cobertura`, and `artifacts:reports:terraform`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:codequality`, `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_scanning`, `artifacts:reports:license_management` (removed in GitLab 13.0), `artifacts:reports:performance`, `artifacts:reports:load_performance`, and `artifacts:reports:metrics`. | | [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:exclude`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, `artifacts:reports:codequality`, `artifacts:reports:junit`, `artifacts:reports:cobertura`, and `artifacts:reports:terraform`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_scanning`, `artifacts:reports:license_management` (removed in GitLab 13.0), `artifacts:reports:performance`, `artifacts:reports:load_performance`, and `artifacts:reports:metrics`. |
| [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. | | [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. |
| [`coverage`](#coverage) | Code coverage settings for a given job. | | [`coverage`](#coverage) | Code coverage settings for a given job. |
| [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. | | [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. |
...@@ -3147,7 +3147,7 @@ These are the available report types: ...@@ -3147,7 +3147,7 @@ These are the available report types:
| [`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv) | The `dotenv` report collects a set of environment variables. | | [`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv) | The `dotenv` report collects a set of environment variables. |
| [`artifacts:reports:cobertura`](../pipelines/job_artifacts.md#artifactsreportscobertura) | The `cobertura` report collects Cobertura coverage XML files. | | [`artifacts:reports:cobertura`](../pipelines/job_artifacts.md#artifactsreportscobertura) | The `cobertura` report collects Cobertura coverage XML files. |
| [`artifacts:reports:terraform`](../pipelines/job_artifacts.md#artifactsreportsterraform) | The `terraform` report collects Terraform `tfplan.json` files. | | [`artifacts:reports:terraform`](../pipelines/job_artifacts.md#artifactsreportsterraform) | The `terraform` report collects Terraform `tfplan.json` files. |
| [`artifacts:reports:codequality`](../pipelines/job_artifacts.md#artifactsreportscodequality-starter) **(STARTER)** | The `codequality` report collects CodeQuality issues. | | [`artifacts:reports:codequality`](../pipelines/job_artifacts.md#artifactsreportscodequality) | The `codequality` report collects CodeQuality issues. |
| [`artifacts:reports:sast`](../pipelines/job_artifacts.md#artifactsreportssast-ultimate) **(ULTIMATE)** | The `sast` report collects Static Application Security Testing vulnerabilities. | | [`artifacts:reports:sast`](../pipelines/job_artifacts.md#artifactsreportssast-ultimate) **(ULTIMATE)** | The `sast` report collects Static Application Security Testing vulnerabilities. |
| [`artifacts:reports:dependency_scanning`](../pipelines/job_artifacts.md#artifactsreportsdependency_scanning-ultimate) **(ULTIMATE)** | The `dependency_scanning` report collects Dependency Scanning vulnerabilities. | | [`artifacts:reports:dependency_scanning`](../pipelines/job_artifacts.md#artifactsreportsdependency_scanning-ultimate) **(ULTIMATE)** | The `dependency_scanning` report collects Dependency Scanning vulnerabilities. |
| [`artifacts:reports:container_scanning`](../pipelines/job_artifacts.md#artifactsreportscontainer_scanning-ultimate) **(ULTIMATE)** | The `container_scanning` report collects Container Scanning vulnerabilities. | | [`artifacts:reports:container_scanning`](../pipelines/job_artifacts.md#artifactsreportscontainer_scanning-ultimate) **(ULTIMATE)** | The `container_scanning` report collects Container Scanning vulnerabilities. |
......
...@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference, howto type: reference, howto
--- ---
# Code Quality **(STARTER)** # Code Quality
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1984) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1984) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
...@@ -25,6 +25,11 @@ Code Quality: ...@@ -25,6 +25,11 @@ Code Quality:
DevOps](../../../topics/autodevops/stages.md#auto-code-quality-starter). DevOps](../../../topics/autodevops/stages.md#auto-code-quality-starter).
- Can be extended through [Analysis Plugins](https://docs.codeclimate.com/docs/list-of-engines) or a [custom tool](#implementing-a-custom-tool). - Can be extended through [Analysis Plugins](https://docs.codeclimate.com/docs/list-of-engines) or a [custom tool](#implementing-a-custom-tool).
## Code Quality Widget
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1984) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
> - Made [available in all tiers](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) in 13.2.
Going a step further, GitLab can show the Code Quality report right Going a step further, GitLab can show the Code Quality report right
in the merge request widget area if a report from the target branch is available to compare to: in the merge request widget area if a report from the target branch is available to compare to:
...@@ -82,7 +87,7 @@ include: ...@@ -82,7 +87,7 @@ include:
The above example will create a `code_quality` job in your CI/CD pipeline which The above example will create a `code_quality` job in your CI/CD pipeline which
will scan your source code for code quality issues. The report will be saved as a will scan your source code for code quality issues. The report will be saved as a
[Code Quality report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality-starter) [Code Quality report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality)
that you can later download and analyze. that you can later download and analyze.
It's also possible to override the URL to the Code Quality image by It's also possible to override the URL to the Code Quality image by
...@@ -240,7 +245,7 @@ do this: ...@@ -240,7 +245,7 @@ do this:
1. Define a job in your `.gitlab-ci.yml` file that generates the 1. Define a job in your `.gitlab-ci.yml` file that generates the
[Code Quality report [Code Quality report
artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality-starter). artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscodequality).
1. Configure your tool to generate the Code Quality report artifact as a JSON 1. Configure your tool to generate the Code Quality report artifact as a JSON
file that implements a subset of the [Code Climate file that implements a subset of the [Code Climate
spec](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types). spec](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types).
...@@ -276,11 +281,11 @@ NOTE: **Note:** ...@@ -276,11 +281,11 @@ NOTE: **Note:**
Although the Code Climate spec supports more properties, those are ignored by Although the Code Climate spec supports more properties, those are ignored by
GitLab. GitLab.
## Code Quality reports ## Code Quality reports **(STARTER)**
Once the Code Quality job has completed: Once the Code Quality job has completed:
- The full list of code quality violations generated by a pipeline is available in the - The full list of code quality violations generated by a pipeline is shown in the
Code Quality tab of the Pipeline Details page. Code Quality tab of the Pipeline Details page.
- Potential changes to code quality are shown directly in the merge request. - Potential changes to code quality are shown directly in the merge request.
The Code Quality widget in the merge request compares the reports from the base and head of the branch, The Code Quality widget in the merge request compares the reports from the base and head of the branch,
...@@ -293,7 +298,7 @@ Once the Code Quality job has completed: ...@@ -293,7 +298,7 @@ Once the Code Quality job has completed:
### Using Analysis Plugins ### Using Analysis Plugins
Should there be a need to extend the default functionality provided by Code Quality, as stated in [Code Quality](#code-quality-starter), [Analysis Plugins](https://docs.codeclimate.com/docs/list-of-engines) are available. Should there be a need to extend the default functionality provided by Code Quality, as stated in [Code Quality](#code-quality), [Analysis Plugins](https://docs.codeclimate.com/docs/list-of-engines) are available.
For example, to use the [SonarJava analyzer](https://docs.codeclimate.com/docs/sonar-java), For example, to use the [SonarJava analyzer](https://docs.codeclimate.com/docs/sonar-java),
add a file named `.codeclimate.yml` containing the [enablement code](https://docs.codeclimate.com/docs/sonar-java#enable-the-plugin) add a file named `.codeclimate.yml` containing the [enablement code](https://docs.codeclimate.com/docs/sonar-java#enable-the-plugin)
......
...@@ -19,7 +19,7 @@ A. Consider you're a software developer working in a team: ...@@ -19,7 +19,7 @@ A. Consider you're a software developer working in a team:
1. You checkout a new branch, and submit your changes through a merge request 1. You checkout a new branch, and submit your changes through a merge request
1. You gather feedback from your team 1. You gather feedback from your team
1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md) **(STARTER)** 1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md)
1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD 1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD
1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](../../compliance/license_compliance/index.md) **(ULTIMATE)** 1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](../../compliance/license_compliance/index.md) **(ULTIMATE)**
1. You request the [approval](merge_request_approvals.md) from your manager **(STARTER)** 1. You request the [approval](merge_request_approvals.md) from your manager **(STARTER)**
......
...@@ -16,7 +16,7 @@ or link to useful information directly from merge requests: ...@@ -16,7 +16,7 @@ or link to useful information directly from merge requests:
| [Accessibility Testing](accessibility_testing.md) | Automatically report A11y violations for changed pages in merge requests. | | [Accessibility Testing](accessibility_testing.md) | Automatically report A11y violations for changed pages in merge requests. |
| [Browser Performance Testing](browser_performance_testing.md) **(PREMIUM)** | Quickly determine the browser performance impact of pending code changes. | | [Browser Performance Testing](browser_performance_testing.md) **(PREMIUM)** | Quickly determine the browser performance impact of pending code changes. |
| [Load Performance Testing](load_performance_testing.md) **(PREMIUM)** | Quickly determine the server performance impact of pending code changes. | | [Load Performance Testing](load_performance_testing.md) **(PREMIUM)** | Quickly determine the server performance impact of pending code changes. |
| [Code Quality](code_quality.md) **(STARTER)** | Analyze your source code quality using the [Code Climate](https://codeclimate.com/) analyzer and show the Code Climate report right in the merge request widget area. | | [Code Quality](code_quality.md) | Analyze your source code quality using the [Code Climate](https://codeclimate.com/) analyzer and show the Code Climate report right in the merge request widget area. |
| [Display arbitrary job artifacts](../../../ci/yaml/README.md#artifactsexpose_as) | Configure CI pipelines with the `artifacts:expose_as` parameter to directly link to selected [artifacts](../../../ci/pipelines/job_artifacts.md) in merge requests. | | [Display arbitrary job artifacts](../../../ci/yaml/README.md#artifactsexpose_as) | Configure CI pipelines with the `artifacts:expose_as` parameter to directly link to selected [artifacts](../../../ci/pipelines/job_artifacts.md) in merge requests. |
| [GitLab CI/CD](../../../ci/README.md) | Build, test, and deploy your code in a per-branch basis with built-in CI/CD. | | [GitLab CI/CD](../../../ci/README.md) | Build, test, and deploy your code in a per-branch basis with built-in CI/CD. |
| [JUnit test reports](../../../ci/junit_test_reports.md) | Configure your CI jobs to use JUnit test reports, and let GitLab display a report on the merge request so that it’s easier and faster to identify the failure without having to check the entire job log. | | [JUnit test reports](../../../ci/junit_test_reports.md) | Configure your CI jobs to use JUnit test reports, and let GitLab display a report on the merge request so that it’s easier and faster to identify the failure without having to check the entire job log. |
......
...@@ -3,13 +3,13 @@ import * as types from './mutation_types'; ...@@ -3,13 +3,13 @@ import * as types from './mutation_types';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import MergeRequestStore from 'ee/vue_merge_request_widget/stores/mr_widget_store'; import { parseCodeclimateMetrics } from '~/reports/codequality_report/store/utils/codequality_comparison';
export const setPage = ({ commit }, page) => commit(types.SET_PAGE, page); export const setPage = ({ commit }, page) => commit(types.SET_PAGE, page);
export const requestReport = ({ commit }) => commit(types.REQUEST_REPORT); export const requestReport = ({ commit }) => commit(types.REQUEST_REPORT);
export const receiveReportSuccess = ({ state, commit }, data) => { export const receiveReportSuccess = ({ state, commit }, data) => {
const parsedIssues = MergeRequestStore.parseCodeclimateMetrics(data, state.blobPath); const parsedIssues = parseCodeclimateMetrics(data, state.blobPath);
commit(types.RECEIVE_REPORT_SUCCESS, parsedIssues); commit(types.RECEIVE_REPORT_SUCCESS, parsedIssues);
}; };
export const receiveReportError = ({ commit }, error) => commit(types.RECEIVE_REPORT_ERROR, error); export const receiveReportError = ({ commit }, error) => commit(types.RECEIVE_REPORT_ERROR, error);
......
import PerformanceIssueBody from 'ee/vue_merge_request_widget/components/performance_issue_body.vue'; import PerformanceIssueBody from 'ee/vue_merge_request_widget/components/performance_issue_body.vue';
import CodequalityIssueBody from 'ee/vue_merge_request_widget/components/codequality_issue_body.vue';
import BlockingMergeRequestsBody from 'ee/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_request_body.vue'; import BlockingMergeRequestsBody from 'ee/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_request_body.vue';
import LicenseIssueBody from 'ee/vue_shared/license_compliance/components/license_issue_body.vue'; import LicenseIssueBody from 'ee/vue_shared/license_compliance/components/license_issue_body.vue';
import SecurityIssueBody from 'ee/vue_shared/security_reports/components/security_issue_body.vue'; import SecurityIssueBody from 'ee/vue_shared/security_reports/components/security_issue_body.vue';
...@@ -12,7 +11,6 @@ import { ...@@ -12,7 +11,6 @@ import {
export const components = { export const components = {
...componentsCE, ...componentsCE,
PerformanceIssueBody, PerformanceIssueBody,
CodequalityIssueBody,
LicenseIssueBody, LicenseIssueBody,
SecurityIssueBody, SecurityIssueBody,
MetricsReportsIssueBody, MetricsReportsIssueBody,
...@@ -22,7 +20,6 @@ export const components = { ...@@ -22,7 +20,6 @@ export const components = {
export const componentNames = { export const componentNames = {
...componentNamesCE, ...componentNamesCE,
PerformanceIssueBody: PerformanceIssueBody.name, PerformanceIssueBody: PerformanceIssueBody.name,
CodequalityIssueBody: CodequalityIssueBody.name,
LicenseIssueBody: LicenseIssueBody.name, LicenseIssueBody: LicenseIssueBody.name,
SecurityIssueBody: SecurityIssueBody.name, SecurityIssueBody: SecurityIssueBody.name,
MetricsReportsIssueBody: MetricsReportsIssueBody.name, MetricsReportsIssueBody: MetricsReportsIssueBody.name,
......
...@@ -8,7 +8,7 @@ import MrWidgetLicenses from 'ee/vue_shared/license_compliance/mr_widget_license ...@@ -8,7 +8,7 @@ import MrWidgetLicenses from 'ee/vue_shared/license_compliance/mr_widget_license
import ReportSection from '~/reports/components/report_section.vue'; import ReportSection from '~/reports/components/report_section.vue';
import BlockingMergeRequestsReport from './components/blocking_merge_requests/blocking_merge_requests_report.vue'; import BlockingMergeRequestsReport from './components/blocking_merge_requests/blocking_merge_requests_report.vue';
import { n__, s__, __, sprintf } from '~/locale'; import { s__, __, sprintf } from '~/locale';
import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import MrWidgetApprovals from './components/approvals/approvals.vue'; import MrWidgetApprovals from './components/approvals/approvals.vue';
import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue'; import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue';
...@@ -33,10 +33,8 @@ export default { ...@@ -33,10 +33,8 @@ export default {
componentNames, componentNames,
data() { data() {
return { return {
isLoadingCodequality: false,
isLoadingBrowserPerformance: false, isLoadingBrowserPerformance: false,
isLoadingLoadPerformance: false, isLoadingLoadPerformance: false,
loadingCodequalityFailed: false,
loadingBrowserPerformanceFailed: false, loadingBrowserPerformanceFailed: false,
loadingLoadPerformanceFailed: false, loadingLoadPerformanceFailed: false,
loadingLicenseReportFailed: false, loadingLicenseReportFailed: false,
...@@ -46,22 +44,9 @@ export default { ...@@ -46,22 +44,9 @@ export default {
shouldRenderApprovals() { shouldRenderApprovals() {
return this.mr.hasApprovalsAvailable && this.mr.state !== 'nothingToMerge'; return this.mr.hasApprovalsAvailable && this.mr.state !== 'nothingToMerge';
}, },
shouldRenderCodeQuality() {
const { codeclimate } = this.mr || {};
return codeclimate && codeclimate.head_path;
},
shouldRenderLicenseReport() { shouldRenderLicenseReport() {
return this.mr.enabledReports?.licenseScanning; return this.mr.enabledReports?.licenseScanning;
}, },
hasCodequalityIssues() {
return (
this.mr.codeclimateMetrics &&
((this.mr.codeclimateMetrics.newIssues &&
this.mr.codeclimateMetrics.newIssues.length > 0) ||
(this.mr.codeclimateMetrics.resolvedIssues &&
this.mr.codeclimateMetrics.resolvedIssues.length > 0))
);
},
hasBrowserPerformanceMetrics() { hasBrowserPerformanceMetrics() {
return ( return (
this.mr.browserPerformanceMetrics?.degraded?.length > 0 || this.mr.browserPerformanceMetrics?.degraded?.length > 0 ||
...@@ -112,47 +97,6 @@ export default { ...@@ -112,47 +97,6 @@ export default {
this.$options.securityReportTypes.some(reportType => enabledReports[reportType]) this.$options.securityReportTypes.some(reportType => enabledReports[reportType])
); );
}, },
codequalityText() {
const { newIssues, resolvedIssues } = this.mr.codeclimateMetrics;
const text = [];
if (!newIssues.length && !resolvedIssues.length) {
text.push(s__('ciReport|No changes to code quality'));
} else if (newIssues.length || resolvedIssues.length) {
text.push(s__('ciReport|Code quality'));
if (resolvedIssues.length) {
text.push(n__(' improved on %d point', ' improved on %d points', resolvedIssues.length));
}
if (newIssues.length > 0 && resolvedIssues.length > 0) {
text.push(__(' and'));
}
if (newIssues.length) {
text.push(n__(' degraded on %d point', ' degraded on %d points', newIssues.length));
}
}
return text.join('');
},
codequalityPopover() {
const { codeclimate } = this.mr || {};
if (codeclimate && !codeclimate.base_path) {
return {
title: s__('ciReport|Base pipeline codequality artifact not found'),
content: sprintf(
s__('ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}'),
{
linkStartTag: `<a href="${this.mr.codequalityHelpPath}" target="_blank" rel="noopener noreferrer">`,
linkEndTag: '<i class="fa fa-external-link" aria-hidden="true"></i></a>',
},
false,
),
};
}
return {};
},
browserPerformanceText() { browserPerformanceText() {
const { improved, degraded, same } = this.mr.browserPerformanceMetrics; const { improved, degraded, same } = this.mr.browserPerformanceMetrics;
...@@ -204,10 +148,6 @@ export default { ...@@ -204,10 +148,6 @@ export default {
return [...text, ...reportNumbers.join(', ')].join(''); return [...text, ...reportNumbers.join(', ')].join('');
}, },
codequalityStatus() {
return this.checkReportStatus(this.isLoadingCodequality, this.loadingCodequalityFailed);
},
browserPerformanceStatus() { browserPerformanceStatus() {
return this.checkReportStatus( return this.checkReportStatus(
this.isLoadingBrowserPerformance, this.isLoadingBrowserPerformance,
...@@ -236,11 +176,6 @@ export default { ...@@ -236,11 +176,6 @@ export default {
}, },
}, },
watch: { watch: {
shouldRenderCodeQuality(newVal) {
if (newVal) {
this.fetchCodeQuality();
}
},
hasBrowserPerformancePaths(newVal) { hasBrowserPerformancePaths(newVal) {
if (newVal) { if (newVal) {
this.fetchBrowserPerformance(); this.fetchBrowserPerformance();
...@@ -264,37 +199,6 @@ export default { ...@@ -264,37 +199,6 @@ export default {
apiUnapprovePath: store.apiUnapprovePath, apiUnapprovePath: store.apiUnapprovePath,
}; };
}, },
fetchCodeQuality() {
const { codeclimate } = this.mr || {};
if (!codeclimate.base_path) {
this.isLoadingCodequality = false;
this.loadingCodequalityFailed = true;
return;
}
this.isLoadingCodequality = true;
Promise.all([
this.service.fetchReport(codeclimate.head_path),
this.service.fetchReport(codeclimate.base_path),
])
.then(values =>
this.mr.compareCodeclimateMetrics(
values[0],
values[1],
this.mr.headBlobPath,
this.mr.baseBlobPath,
),
)
.then(() => {
this.isLoadingCodequality = false;
})
.catch(() => {
this.isLoadingCodequality = false;
this.loadingCodequalityFailed = true;
});
},
fetchBrowserPerformance() { fetchBrowserPerformance() {
const { head_path, base_path } = this.mr.browserPerformance; const { head_path, base_path } = this.mr.browserPerformance;
...@@ -367,18 +271,13 @@ export default { ...@@ -367,18 +271,13 @@ export default {
/> />
<div class="mr-section-container mr-widget-workflow"> <div class="mr-section-container mr-widget-workflow">
<blocking-merge-requests-report :mr="mr" /> <blocking-merge-requests-report :mr="mr" />
<report-section <grouped-codequality-reports-app
v-if="shouldRenderCodeQuality" v-if="shouldRenderCodeQuality"
:status="codequalityStatus" :base-path="mr.codeclimate.base_path"
:loading-text="translateText('codeclimate').loading" :head-path="mr.codeclimate.head_path"
:error-text="translateText('codeclimate').error" :head-blob-path="mr.headBlobPath"
:success-text="codequalityText" :base-blob-path="mr.baseBlobPath"
:unresolved-issues="mr.codeclimateMetrics.newIssues" :codequality-help-path="mr.codequalityHelpPath"
:resolved-issues="mr.codeclimateMetrics.resolvedIssues"
:has-issues="hasCodequalityIssues"
:component="$options.componentNames.CodequalityIssueBody"
:popover-options="codequalityPopover"
class="js-codequality-widget mr-widget-border-top mr-report"
/> />
<report-section <report-section
v-if="shouldRenderBrowserPerformance" v-if="shouldRenderBrowserPerformance"
......
import CEMergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store'; import CEMergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { mapApprovalsResponse, mapApprovalRulesResponse } from '../mappers'; import { mapApprovalsResponse, mapApprovalRulesResponse } from '../mappers';
import CodeQualityComparisonWorker from '../workers/code_quality_comparison_worker';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export default class MergeRequestStore extends CEMergeRequestStore { export default class MergeRequestStore extends CEMergeRequestStore {
constructor(data) { constructor(data) {
super(data); super(data);
const blobPath = data.blob_path || {};
this.headBlobPath = blobPath.head_path || '';
this.baseBlobPath = blobPath.base_path || '';
this.sastHelp = data.sast_help_path; this.sastHelp = data.sast_help_path;
this.containerScanningHelp = data.container_scanning_help_path; this.containerScanningHelp = data.container_scanning_help_path;
this.dastHelp = data.dast_help_path; this.dastHelp = data.dast_help_path;
...@@ -20,7 +16,6 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -20,7 +16,6 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.canReadVulnerabilityFeedback = data.can_read_vulnerability_feedback; this.canReadVulnerabilityFeedback = data.can_read_vulnerability_feedback;
this.vulnerabilityFeedbackHelpPath = data.vulnerability_feedback_help_path; this.vulnerabilityFeedbackHelpPath = data.vulnerability_feedback_help_path;
this.approvalsHelpPath = data.approvals_help_path; this.approvalsHelpPath = data.approvals_help_path;
this.codequalityHelpPath = data.codequality_help_path;
this.securityReportsPipelineId = data.pipeline_id; this.securityReportsPipelineId = data.pipeline_id;
this.securityReportsPipelineIid = data.pipeline_iid; this.securityReportsPipelineIid = data.pipeline_iid;
this.createVulnerabilityFeedbackIssuePath = data.create_vulnerability_feedback_issue_path; this.createVulnerabilityFeedbackIssuePath = data.create_vulnerability_feedback_issue_path;
...@@ -31,7 +26,6 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -31,7 +26,6 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.visualReviewAppAvailable = Boolean(data.visual_review_app_available); this.visualReviewAppAvailable = Boolean(data.visual_review_app_available);
this.appUrl = gon && gon.gitlab_url; this.appUrl = gon && gon.gitlab_url;
this.initCodeclimate(data);
this.initBrowserPerformanceReport(data); this.initBrowserPerformanceReport(data);
this.initLoadPerformanceReport(data); this.initLoadPerformanceReport(data);
this.licenseScanning = data.license_scanning; this.licenseScanning = data.license_scanning;
...@@ -81,14 +75,6 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -81,14 +75,6 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.approvalRules = mapApprovalRulesResponse(data.rules, this.approvals); this.approvalRules = mapApprovalRulesResponse(data.rules, this.approvals);
} }
initCodeclimate(data) {
this.codeclimate = data.codeclimate;
this.codeclimateMetrics = {
newIssues: [],
resolvedIssues: [],
};
}
initBrowserPerformanceReport(data) { initBrowserPerformanceReport(data) {
this.browserPerformance = data.browser_performance; this.browserPerformance = data.browser_performance;
this.browserPerformanceMetrics = { this.browserPerformanceMetrics = {
...@@ -107,32 +93,6 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -107,32 +93,6 @@ export default class MergeRequestStore extends CEMergeRequestStore {
}; };
} }
static doCodeClimateComparison(headIssues, baseIssues) {
// Do these comparisons in worker threads to avoid blocking the main thread
return new Promise((resolve, reject) => {
const worker = new CodeQualityComparisonWorker();
worker.addEventListener('message', ({ data }) =>
data.newIssues && data.resolvedIssues ? resolve(data) : reject(data),
);
worker.postMessage({
headIssues,
baseIssues,
});
});
}
compareCodeclimateMetrics(headIssues, baseIssues, headBlobPath, baseBlobPath) {
const parsedHeadIssues = MergeRequestStore.parseCodeclimateMetrics(headIssues, headBlobPath);
const parsedBaseIssues = MergeRequestStore.parseCodeclimateMetrics(baseIssues, baseBlobPath);
return MergeRequestStore.doCodeClimateComparison(parsedHeadIssues, parsedBaseIssues).then(
response => {
this.codeclimateMetrics.newIssues = response.newIssues;
this.codeclimateMetrics.resolvedIssues = response.resolvedIssues;
},
);
}
compareBrowserPerformanceMetrics(headMetrics, baseMetrics) { compareBrowserPerformanceMetrics(headMetrics, baseMetrics) {
const headMetricsIndexed = MergeRequestStore.normalizeBrowserPerformanceMetrics(headMetrics); const headMetricsIndexed = MergeRequestStore.normalizeBrowserPerformanceMetrics(headMetrics);
const baseMetricsIndexed = MergeRequestStore.normalizeBrowserPerformanceMetrics(baseMetrics); const baseMetricsIndexed = MergeRequestStore.normalizeBrowserPerformanceMetrics(baseMetrics);
...@@ -254,38 +214,4 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -254,38 +214,4 @@ export default class MergeRequestStore extends CEMergeRequestStore {
return indexedMetrics; return indexedMetrics;
} }
static parseCodeclimateMetrics(issues = [], path = '') {
return issues.map(issue => {
const parsedIssue = {
...issue,
name: issue.description,
};
if (issue.location) {
let parseCodeQualityUrl;
if (issue.location.path) {
parseCodeQualityUrl = `${path}/${issue.location.path}`;
parsedIssue.path = issue.location.path;
if (issue.location.lines && issue.location.lines.begin) {
parsedIssue.line = issue.location.lines.begin;
parseCodeQualityUrl += `#L${issue.location.lines.begin}`;
} else if (
issue.location.positions &&
issue.location.positions.begin &&
issue.location.positions.begin.line
) {
parsedIssue.line = issue.location.positions.begin.line;
parseCodeQualityUrl += `#L${issue.location.positions.begin.line}`;
}
parsedIssue.urlPath = parseCodeQualityUrl;
}
}
return parsedIssue;
});
}
} }
import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key';
const keyToFilterBy = 'fingerprint';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('message', e => {
const { data } = e;
if (data === undefined) {
return null;
}
const { headIssues, baseIssues } = data;
if (!headIssues || !baseIssues) {
// eslint-disable-next-line no-restricted-globals
return self.postMessage({});
}
// eslint-disable-next-line no-restricted-globals
self.postMessage({
newIssues: filterByKey(headIssues, baseIssues, keyToFilterBy),
resolvedIssues: filterByKey(baseIssues, headIssues, keyToFilterBy),
});
// eslint-disable-next-line no-restricted-globals
return self.close();
});
/**
* Compares two arrays by the given key and returns the difference
*
* @param {Array} firstArray
* @param {Array} secondArray
* @param {String} key
* @returns {Array}
*/
const filterByKey = (firstArray = [], secondArray = [], key = '') =>
firstArray.filter(item => !secondArray.find(el => el[key] === item[key]));
export default filterByKey;
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
window.gl.mrWidgetData.secret_scanning_help_path = '#{help_page_path('user/application_security/sast/index', anchor: 'secret-detection')}'; window.gl.mrWidgetData.secret_scanning_help_path = '#{help_page_path('user/application_security/sast/index', anchor: 'secret-detection')}';
window.gl.mrWidgetData.vulnerability_feedback_help_path = '#{help_page_path("user/application_security/index")}'; window.gl.mrWidgetData.vulnerability_feedback_help_path = '#{help_page_path("user/application_security/index")}';
window.gl.mrWidgetData.approvals_help_path = '#{help_page_path("user/project/merge_requests/merge_request_approvals")}'; window.gl.mrWidgetData.approvals_help_path = '#{help_page_path("user/project/merge_requests/merge_request_approvals")}';
window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}';
window.gl.mrWidgetData.visual_review_app_available = '#{@project.feature_available?(:visual_review_app)}' === 'true'; window.gl.mrWidgetData.visual_review_app_available = '#{@project.feature_available?(:visual_review_app)}' === 'true';
window.gl.mrWidgetData.license_scanning_comparison_path = '#{license_scanning_reports_project_merge_request_path(@project, @merge_request) if @project.feature_available?(:license_scanning)}' window.gl.mrWidgetData.license_scanning_comparison_path = '#{license_scanning_reports_project_merge_request_path(@project, @merge_request) if @project.feature_available?(:license_scanning)}'
window.gl.mrWidgetData.container_scanning_comparison_path = '#{container_scanning_reports_project_merge_request_path(@project, @merge_request) if @project.feature_available?(:container_scanning)}' window.gl.mrWidgetData.container_scanning_comparison_path = '#{container_scanning_reports_project_merge_request_path(@project, @merge_request) if @project.feature_available?(:container_scanning)}'
......
import Vue from 'vue'; import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import mrWidgetOptions from 'ee/vue_merge_request_widget/mr_widget_options.vue'; import mrWidgetOptions from 'ee/vue_merge_request_widget/mr_widget_options.vue';
import MRWidgetStore from 'ee/vue_merge_request_widget/stores/mr_widget_store';
import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key';
import mountComponent from 'helpers/vue_mount_component_helper'; import mountComponent from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
import mockData, { import mockData, {
baseIssues,
headIssues,
baseBrowserPerformance, baseBrowserPerformance,
headBrowserPerformance, headBrowserPerformance,
baseLoadPerformance, baseLoadPerformance,
headLoadPerformance, headLoadPerformance,
parsedBaseIssues,
parsedHeadIssues,
} from './mock_data'; } from './mock_data';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants'; import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
...@@ -303,204 +297,6 @@ describe('ee merge request widget options', () => { ...@@ -303,204 +297,6 @@ describe('ee merge request widget options', () => {
}); });
}); });
describe('code quality', () => {
beforeEach(() => {
gl.mrWidgetData = {
...mockData,
codeclimate: {},
};
});
describe('when it is loading', () => {
it('should render loading indicator', done => {
mock.onGet('head.json').reply(200, headIssues);
mock.onGet('base.json').reply(200, baseIssues);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
vm.mr.codeclimate = {
head_path: 'head.json',
base_path: 'base.json',
};
vm.$nextTick(() => {
expect(trimText(vm.$el.querySelector('.js-codequality-widget').textContent)).toContain(
'Loading codeclimate report',
);
done();
});
});
});
describe('with successful request', () => {
beforeEach(() => {
mock.onGet('head.json').reply(200, headIssues);
mock.onGet('base.json').reply(200, baseIssues);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
gl.mrWidgetData.codeclimate = {
head_path: 'head.json',
base_path: 'base.json',
};
vm.mr.codeclimate = gl.mrWidgetData.codeclimate;
// mock worker response
jest.spyOn(MRWidgetStore, 'doCodeClimateComparison').mockResolvedValue({
newIssues: filterByKey(parsedHeadIssues, parsedBaseIssues, 'fingerprint'),
resolvedIssues: filterByKey(parsedBaseIssues, parsedHeadIssues, 'fingerprint'),
});
});
it('should render provided data', done => {
setImmediate(() => {
expect(
trimText(vm.$el.querySelector('.js-codequality-widget .js-code-text').textContent),
).toEqual('Code quality improved on 1 point and degraded on 1 point');
done();
});
});
describe('text connector', () => {
it('should only render information about fixed issues', done => {
setImmediate(() => {
vm.mr.codeclimateMetrics.newIssues = [];
Vue.nextTick(() => {
expect(
trimText(vm.$el.querySelector('.js-codequality-widget .js-code-text').textContent),
).toEqual('Code quality improved on 1 point');
done();
});
});
});
it('should only render information about added issues', done => {
setImmediate(() => {
vm.mr.codeclimateMetrics.resolvedIssues = [];
Vue.nextTick(() => {
expect(
trimText(vm.$el.querySelector('.js-codequality-widget .js-code-text').textContent),
).toEqual('Code quality degraded on 1 point');
done();
});
});
});
});
});
describe('with empty successful request', () => {
beforeEach(() => {
mock.onGet('head.json').reply(200, []);
mock.onGet('base.json').reply(200, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
gl.mrWidgetData.codeclimate = {
head_path: 'head.json',
base_path: 'base.json',
};
vm.mr.codeclimate = gl.mrWidgetData.codeclimate;
// mock worker response
jest.spyOn(MRWidgetStore, 'doCodeClimateComparison').mockResolvedValue({
newIssues: filterByKey([], [], 'fingerprint'),
resolvedIssues: filterByKey([], [], 'fingerprint'),
});
});
afterEach(() => {
mock.restore();
});
it('should render provided data', done => {
setImmediate(() => {
expect(
trimText(vm.$el.querySelector('.js-codequality-widget .js-code-text').textContent),
).toEqual('No changes to code quality');
done();
});
});
});
describe('with a head_path but no base_path', () => {
beforeEach(() => {
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
gl.mrWidgetData.codeclimate = {
head_path: 'head.json',
base_path: null,
};
vm.mr.codeclimate = gl.mrWidgetData.codeclimate;
});
it('should render error indicator', done => {
setImmediate(() => {
expect(
trimText(vm.$el.querySelector('.js-codequality-widget .js-code-text').textContent),
).toContain('Failed to load codeclimate report');
done();
});
});
it('should render a help icon with more information', done => {
setImmediate(() => {
expect(vm.$el.querySelector('.js-codequality-widget .btn-help')).not.toBeNull();
expect(vm.codequalityPopover.title).toBe('Base pipeline codequality artifact not found');
done();
});
});
});
describe('with codeclimate comparison worker rejection', () => {
beforeEach(() => {
mock.onGet('head.json').reply(200, headIssues);
mock.onGet('base.json').reply(200, baseIssues);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
gl.mrWidgetData.codeclimate = {
head_path: 'head.json',
base_path: 'base.json',
};
vm.mr.codeclimate = gl.mrWidgetData.codeclimate;
// mock worker rejection
jest.spyOn(MRWidgetStore, 'doCodeClimateComparison').mockRejectedValue();
});
it('should render error indicator', done => {
setImmediate(() => {
expect(
trimText(vm.$el.querySelector('.js-codequality-widget .js-code-text').textContent),
).toEqual('Failed to load codeclimate report');
done();
});
});
});
describe('with failed request', () => {
beforeEach(() => {
mock.onGet('head.json').reply(500, []);
mock.onGet('base.json').reply(500, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
gl.mrWidgetData.codeclimate = {
head_path: 'head.json',
base_path: 'base.json',
};
vm.mr.codeclimate = gl.mrWidgetData.codeclimate;
});
it('should render error indicator', done => {
setImmediate(() => {
expect(
trimText(vm.$el.querySelector('.js-codequality-widget .js-code-text').textContent),
).toContain('Failed to load codeclimate report');
done();
});
});
});
});
describe('browser_performance', () => { describe('browser_performance', () => {
beforeEach(() => { beforeEach(() => {
gl.mrWidgetData = { gl.mrWidgetData = {
......
...@@ -2,14 +2,6 @@ import mockData, { mockStore } from 'jest/vue_mr_widget/mock_data'; ...@@ -2,14 +2,6 @@ import mockData, { mockStore } from 'jest/vue_mr_widget/mock_data';
export default { export default {
...mockData, ...mockData,
codeclimate: {
head_path: 'head.json',
base_path: 'base.json',
},
blob_path: {
base_path: 'blob_path',
head_path: 'blob_path',
},
vulnerability_feedback_help_path: '/help/user/application_security/index', vulnerability_feedback_help_path: '/help/user/application_security/index',
enabled_reports: { enabled_reports: {
sast: false, sast: false,
...@@ -21,84 +13,6 @@ export default { ...@@ -21,84 +13,6 @@ export default {
}, },
}; };
// Codeclimate
export const headIssues = [
{
check_name: 'Rubocop/Lint/UselessAssignment',
description: 'Insecure Dependency',
location: {
path: 'lib/six.rb',
lines: {
begin: 6,
end: 7,
},
},
fingerprint: 'e879dd9bbc0953cad5037cde7ff0f627',
},
{
categories: ['Security'],
check_name: 'Insecure Dependency',
description: 'Insecure Dependency',
location: {
path: 'Gemfile.lock',
lines: {
begin: 22,
end: 22,
},
},
fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
},
];
// Codeclimate
export const parsedHeadIssues = [
{
...headIssues[1],
name: 'Insecure Dependency',
path: 'lib/six.rb',
urlPath: 'headPath/lib/six.rb#L6',
line: 6,
},
];
export const baseIssues = [
{
categories: ['Security'],
check_name: 'Insecure Dependency',
description: 'Insecure Dependency',
location: {
path: 'Gemfile.lock',
lines: {
begin: 22,
end: 22,
},
},
fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
},
{
categories: ['Security'],
check_name: 'Insecure Dependency',
description: 'Insecure Dependency',
location: {
path: 'Gemfile.lock',
lines: {
begin: 21,
end: 21,
},
},
fingerprint: 'ca2354534dee94ae60ba2f54e3857c50e5',
},
];
export const parsedBaseIssues = [
{
...baseIssues[1],
name: 'Insecure Dependency',
path: 'Gemfile.lock',
line: 21,
urlPath: 'basePath/Gemfile.lock#L21',
},
];
// Browser Performance Testing // Browser Performance Testing
export const headBrowserPerformance = [ export const headBrowserPerformance = [
{ {
......
import MergeRequestStore from 'ee/vue_merge_request_widget/stores/mr_widget_store'; import MergeRequestStore from 'ee/vue_merge_request_widget/stores/mr_widget_store';
import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key'; import mockData from 'ee_jest/vue_mr_widget/mock_data';
import mockData, {
headIssues,
baseIssues,
parsedBaseIssues,
parsedHeadIssues,
} from 'ee_jest/vue_mr_widget/mock_data';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
describe('MergeRequestStore', () => { describe('MergeRequestStore', () => {
...@@ -15,38 +9,6 @@ describe('MergeRequestStore', () => { ...@@ -15,38 +9,6 @@ describe('MergeRequestStore', () => {
store = new MergeRequestStore(mockData); store = new MergeRequestStore(mockData);
}); });
describe('compareCodeclimateMetrics', () => {
beforeEach(() => {
// mock worker response
jest.spyOn(MergeRequestStore, 'doCodeClimateComparison').mockImplementation(() =>
Promise.resolve({
newIssues: filterByKey(parsedHeadIssues, parsedBaseIssues, 'fingerprint'),
resolvedIssues: filterByKey(parsedBaseIssues, parsedHeadIssues, 'fingerprint'),
}),
);
return store.compareCodeclimateMetrics(headIssues, baseIssues, 'headPath', 'basePath');
});
it('should return the new issues', () => {
expect(store.codeclimateMetrics.newIssues[0]).toEqual(parsedHeadIssues[0]);
});
it('should return the resolved issues', () => {
expect(store.codeclimateMetrics.resolvedIssues[0]).toEqual(parsedBaseIssues[0]);
});
});
describe('parseCodeclimateMetrics', () => {
it('should parse the received issues', () => {
const codequality = MergeRequestStore.parseCodeclimateMetrics(baseIssues, 'path')[0];
expect(codequality.name).toEqual(baseIssues[0].check_name);
expect(codequality.path).toEqual(baseIssues[0].location.path);
expect(codequality.line).toEqual(baseIssues[0].location.lines.begin);
});
});
describe('isNothingToMergeState', () => { describe('isNothingToMergeState', () => {
it('returns true when nothingToMerge', () => { it('returns true when nothingToMerge', () => {
store.state = stateKey.nothingToMerge; store.state = stateKey.nothingToMerge;
......
...@@ -6,7 +6,6 @@ import { ...@@ -6,7 +6,6 @@ import {
groupedReportText, groupedReportText,
} from 'ee/vue_shared/security_reports/store/utils'; } from 'ee/vue_shared/security_reports/store/utils';
import convertReportType from 'ee/vue_shared/security_reports/store/utils/convert_report_type'; import convertReportType from 'ee/vue_shared/security_reports/store/utils/convert_report_type';
import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key';
import getFileLocation from 'ee/vue_shared/security_reports/store/utils/get_file_location'; import getFileLocation from 'ee/vue_shared/security_reports/store/utils/get_file_location';
import { import {
CRITICAL, CRITICAL,
...@@ -63,15 +62,6 @@ describe('security reports utils', () => { ...@@ -63,15 +62,6 @@ describe('security reports utils', () => {
); );
}); });
describe('filterByKey', () => {
it('filters the array with the provided key', () => {
const array1 = [{ id: '1234' }, { id: 'abg543' }, { id: '214swfA' }];
const array2 = [{ id: '1234' }, { id: 'abg543' }, { id: '453OJKs' }];
expect(filterByKey(array1, array2, 'id')).toEqual([{ id: '214swfA' }]);
});
});
describe('getFileLocation', () => { describe('getFileLocation', () => {
const hostname = 'https://hostna.me'; const hostname = 'https://hostna.me';
const path = '/deeply/nested/route'; const path = '/deeply/nested/route';
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import component from 'ee/vue_merge_request_widget/components/codequality_issue_body.vue'; import component from '~/reports/codequality_report/components/codequality_issue_body.vue';
import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants'; import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
describe('code quality issue body issue body', () => { describe('code quality issue body issue body', () => {
......
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
import store from '~/reports/codequality_report/store';
import { mockParsedHeadIssues, mockParsedBaseIssues } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Grouped code quality reports app', () => {
const Component = localVue.extend(GroupedCodequalityReportsApp);
let wrapper;
let mockStore;
const mountComponent = (props = {}) => {
wrapper = mount(Component, {
store: mockStore,
localVue,
propsData: {
basePath: 'base.json',
headPath: 'head.json',
baseBlobPath: 'base/blob/path/',
headBlobPath: 'head/blob/path/',
codequalityHelpPath: 'codequality_help.html',
...props,
},
methods: {
fetchReports: () => {},
},
});
};
const findWidget = () => wrapper.find('.js-codequality-widget');
const findIssueBody = () => wrapper.find(CodequalityIssueBody);
beforeEach(() => {
mockStore = store();
mountComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('when it is loading reports', () => {
beforeEach(() => {
mockStore.state.isLoading = true;
});
it('should render loading text', () => {
expect(findWidget().text()).toEqual('Loading codeclimate report');
});
});
describe('when base and head reports are loaded and compared', () => {
describe('with no issues', () => {
beforeEach(() => {
mockStore.state.newIssues = [];
mockStore.state.resolvedIssues = [];
});
it('renders no changes text', () => {
expect(findWidget().text()).toEqual('No changes to code quality');
});
});
describe('with issues', () => {
describe('with new issues', () => {
beforeEach(() => {
mockStore.state.newIssues = [mockParsedHeadIssues[0]];
mockStore.state.resolvedIssues = [];
});
it('renders summary text', () => {
expect(findWidget().text()).toContain('Code quality degraded on 1 point');
});
it('renders custom codequality issue body', () => {
expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
});
});
describe('with resolved issues', () => {
beforeEach(() => {
mockStore.state.newIssues = [];
mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
});
it('renders summary text', () => {
expect(findWidget().text()).toContain('Code quality improved on 1 point');
});
it('renders custom codequality issue body', () => {
expect(findIssueBody().props('issue')).toEqual(mockParsedBaseIssues[0]);
});
});
describe('with new and resolved issues', () => {
beforeEach(() => {
mockStore.state.newIssues = [mockParsedHeadIssues[0]];
mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
});
it('renders summary text', () => {
expect(findWidget().text()).toContain(
'Code quality improved on 1 point and degraded on 1 point',
);
});
it('renders custom codequality issue body', () => {
expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
});
});
});
});
describe('when there is a head report but no base report', () => {
beforeEach(() => {
mockStore.state.basePath = null;
mockStore.state.hasError = true;
});
it('renders error text', () => {
expect(findWidget().text()).toEqual('Failed to load codeclimate report');
});
it('renders a help icon with more information', () => {
expect(findWidget().html()).toContain('ic-question');
});
});
describe('on error', () => {
beforeEach(() => {
mockStore.state.hasError = true;
});
it('renders error text', () => {
expect(findWidget().text()).toContain('Failed to load codeclimate report');
});
it('does not render a help icon', () => {
expect(findWidget().html()).not.toContain('ic-question');
});
});
});
...@@ -211,6 +211,15 @@ export default { ...@@ -211,6 +211,15 @@ export default {
can_revert_on_current_merge_request: true, can_revert_on_current_merge_request: true,
can_cherry_pick_on_current_merge_request: true, can_cherry_pick_on_current_merge_request: true,
}, },
codeclimate: {
head_path: 'head.json',
base_path: 'base.json',
},
blob_path: {
base_path: 'blob_path',
head_path: 'blob_path',
},
codequality_help_path: 'code_quality.html',
target_branch_path: '/root/acets-app/branches/master', target_branch_path: '/root/acets-app/branches/master',
source_branch_path: '/root/acets-app/branches/daaaa', source_branch_path: '/root/acets-app/branches/daaaa',
conflict_resolution_ui_path: '/root/acets-app/-/merge_requests/22/conflicts', conflict_resolution_ui_path: '/root/acets-app/-/merge_requests/22/conflicts',
......
...@@ -609,6 +609,12 @@ describe('mrWidgetOptions', () => { ...@@ -609,6 +609,12 @@ describe('mrWidgetOptions', () => {
}); });
}); });
describe('code quality widget', () => {
it('renders the component', () => {
expect(vm.$el.querySelector('.js-codequality-widget')).toExist();
});
});
describe('pipeline for target branch after merge', () => { describe('pipeline for target branch after merge', () => {
describe('with information for target branch pipeline', () => { describe('with information for target branch pipeline', () => {
beforeEach(done => { beforeEach(done => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment