diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-21 09:09:01 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-21 09:09:01 +0000 |
commit | a53d2c37c4934f564caa94543dd4cf5af1703e2d (patch) | |
tree | a028dc39771a4612a9845ab700a73af2d6f3f51b | |
parent | 18b8435318887d3fc6e9f9d305967a953cdd7d3f (diff) | |
download | gitlab-ce-a53d2c37c4934f564caa94543dd4cf5af1703e2d.tar.gz |
Add latest changes from gitlab-org/gitlab@master
29 files changed, 505 insertions, 291 deletions
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml index 964aa8e523f..688cfb5033e 100644 --- a/.gitlab/ci/reports.gitlab-ci.yml +++ b/.gitlab/ci/reports.gitlab-ci.yml @@ -168,6 +168,7 @@ dast: # DAST_USERNAME: "root" # DAST_USERNAME_FIELD: "user[login]" # DAST_PASSWORD_FIELD: "user[passowrd]" + DAST_VERSION: 1 allow_failure: true script: - 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"' diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 12d68256598..0fc60528eb6 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -65,11 +65,12 @@ export default { }, showIssue(e) { if (e.target.classList.contains('js-no-trigger')) return; - if (this.showDetail) { - this.showDetail = false; - // If CMD or CTRL is clicked - const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey); + // If CMD or CTRL is clicked + const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey); + + if (this.showDetail || isMultiSelect) { + this.showDetail = false; if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) { eventHub.$emit('clearDetailIssue', isMultiSelect); diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue index 3276d85f1cd..c0dadedbc51 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue @@ -1,6 +1,5 @@ <script> /* eslint-disable vue/require-default-prop */ -import { isEmpty, isString } from 'lodash'; import Identicon from '~/vue_shared/components/identicon.vue'; import highlight from '~/lib/utils/highlight'; import { truncateNamespace } from '~/lib/utils/text_utility'; @@ -38,9 +37,6 @@ export default { }, }, computed: { - hasAvatar() { - return isString(this.avatarUrl) && !isEmpty(this.avatarUrl); - }, truncatedNamespace() { return truncateNamespace(this.namespace); }, @@ -54,8 +50,11 @@ export default { <template> <li class="frequent-items-list-item-container"> <a :href="webUrl" class="clearfix"> - <div class="frequent-items-item-avatar-container avatar-container rect-avatar s32"> - <img v-if="hasAvatar" :src="avatarUrl" class="avatar s32" /> + <div + ref="frequentItemsItemAvatarContainer" + class="frequent-items-item-avatar-container avatar-container rect-avatar s32" + > + <img v-if="avatarUrl" ref="frequentItemsItemAvatar" :src="avatarUrl" class="avatar s32" /> <identicon v-else :entity-id="itemId" @@ -64,16 +63,18 @@ export default { class="rect-avatar" /> </div> - <div class="frequent-items-item-metadata-container"> + <div ref="frequentItemsItemMetadataContainer" class="frequent-items-item-metadata-container"> <div + ref="frequentItemsItemTitle" :title="itemName" - class="frequent-items-item-title js-frequent-items-item-title" + class="frequent-items-item-title" v-html="highlightedItemName" ></div> <div v-if="namespace" + ref="frequentItemsItemNamespace" :title="namespace" - class="frequent-items-item-namespace js-frequent-items-item-namespace" + class="frequent-items-item-namespace" > {{ truncatedNamespace }} </div> diff --git a/app/assets/javascripts/ide/components/nav_dropdown_button.vue b/app/assets/javascripts/ide/components/nav_dropdown_button.vue index 4cd320d5d66..8dc22620eca 100644 --- a/app/assets/javascripts/ide/components/nav_dropdown_button.vue +++ b/app/assets/javascripts/ide/components/nav_dropdown_button.vue @@ -31,8 +31,8 @@ export default { <template> <dropdown-button> - <span class="row"> - <span class="col-auto text-truncate" :class="{ 'col-7': showMergeRequests }"> + <span class="row flex-nowrap"> + <span class="col-auto flex-fill text-truncate"> <icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }} </span> <span v-if="showMergeRequests" class="col-5 pl-0 text-truncate"> diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index fa5649679d7..550ec3cb0d1 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, consistent-return, no-else-return, no-param-reassign */ +/* eslint-disable func-names, consistent-return, no-param-reassign */ import $ from 'jquery'; import _ from 'underscore'; @@ -34,8 +34,6 @@ Sidebar.prototype.addEventListeners = function() { this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); this.sidebar.on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden); - $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); - $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); $document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked); return $(document) @@ -133,36 +131,6 @@ Sidebar.prototype.todoUpdateDone = function(data) { }); }; -Sidebar.prototype.sidebarDropdownLoading = function() { - const $sidebarCollapsedIcon = $(this) - .closest('.block') - .find('.sidebar-collapsed-icon'); - const img = $sidebarCollapsedIcon.find('img'); - const i = $sidebarCollapsedIcon.find('i'); - const $loading = $('<i class="fa fa-spinner fa-spin"></i>'); - if (img.length) { - img.before($loading); - return img.hide(); - } else if (i.length) { - i.before($loading); - return i.hide(); - } -}; - -Sidebar.prototype.sidebarDropdownLoaded = function() { - const $sidebarCollapsedIcon = $(this) - .closest('.block') - .find('.sidebar-collapsed-icon'); - const img = $sidebarCollapsedIcon.find('img'); - $sidebarCollapsedIcon.find('i.fa-spin').remove(); - const i = $sidebarCollapsedIcon.find('i'); - if (img.length) { - return img.show(); - } else { - return i.show(); - } -}; - Sidebar.prototype.sidebarCollapseClicked = function(e) { if ($(e.currentTarget).hasClass('dont-change-state')) { return; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue index 18d4073ecd4..e1a1b1022e3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue @@ -1,4 +1,5 @@ <script> +import { GlLink } from '@gitlab/ui'; import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue'; import ReviewAppLink from '../review_app_link.vue'; @@ -6,6 +7,7 @@ export default { name: 'DeploymentViewButton', components: { FilteredSearchDropdown, + GlLink, ReviewAppLink, VisualReviewAppLink: () => import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'), @@ -67,7 +69,7 @@ export default { </template> <template slot="result" slot-scope="slotProps"> - <a + <gl-link :href="slotProps.result.external_url" target="_blank" rel="noopener noreferrer nofollow" @@ -80,16 +82,15 @@ export default { <p class="text-secondary str-truncated-100 append-bottom-0 d-block"> {{ slotProps.result.external_url }} </p> - </a> + </gl-link> </template> </filtered-search-dropdown> - <template v-else> - <review-app-link - :display="appButtonText" - :link="deploymentExternalUrl" - css-class="js-deploy-url deploy-link btn btn-default btn-sm inline" - /> - </template> + <review-app-link + v-else + :display="appButtonText" + :link="deploymentExternalUrl" + css-class="js-deploy-url deploy-link btn btn-default btn-sm inline" + /> <visual-review-app-link v-if="showVisualReviewApp" :link="deploymentExternalUrl" diff --git a/changelogs/unreleased/13810-cluster-environments-table.yml b/changelogs/unreleased/13810-cluster-environments-table.yml new file mode 100644 index 00000000000..bbcebec9106 --- /dev/null +++ b/changelogs/unreleased/13810-cluster-environments-table.yml @@ -0,0 +1,5 @@ +--- +title: Add responsivity to cluster environments table +merge_request: 25501 +author: +type: fixed diff --git a/changelogs/unreleased/195969-multi-select-on-issue-boards-inconsistent-erratic.yml b/changelogs/unreleased/195969-multi-select-on-issue-boards-inconsistent-erratic.yml new file mode 100644 index 00000000000..614379d7ad8 --- /dev/null +++ b/changelogs/unreleased/195969-multi-select-on-issue-boards-inconsistent-erratic.yml @@ -0,0 +1,5 @@ +--- +title: Improved selection of multiple cards +merge_request: +author: Gwen_ +type: fixed diff --git a/changelogs/unreleased/205435.yml b/changelogs/unreleased/205435.yml new file mode 100644 index 00000000000..c004bdf35c3 --- /dev/null +++ b/changelogs/unreleased/205435.yml @@ -0,0 +1,5 @@ +--- +title: Clean up conditional `col-` classes in `nav_dropdown_button.vue` +merge_request: 25312 +author: +type: other diff --git a/changelogs/unreleased/28560_cleanup_optimistic_locking_db.yml b/changelogs/unreleased/28560_cleanup_optimistic_locking_db.yml new file mode 100644 index 00000000000..30d1b6ce94d --- /dev/null +++ b/changelogs/unreleased/28560_cleanup_optimistic_locking_db.yml @@ -0,0 +1,5 @@ +--- +title: Set all NULL `lock_version` values to 0 for issuables +merge_request: 18418 +author: +type: fixed diff --git a/db/post_migrate/20200128210353_cleanup_optimistic_locking_nulls.rb b/db/post_migrate/20200128210353_cleanup_optimistic_locking_nulls.rb new file mode 100644 index 00000000000..8bc037c7333 --- /dev/null +++ b/db/post_migrate/20200128210353_cleanup_optimistic_locking_nulls.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CleanupOptimisticLockingNulls < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + TABLES = %w(epics merge_requests issues).freeze + BATCH_SIZE = 10_000 + + def declare_class(table) + Class.new(ActiveRecord::Base) do + include EachBatch + + self.table_name = table + self.inheritance_column = :_type_disabled # Disable STI + end + end + + def up + TABLES.each do |table| + add_concurrent_index table.to_sym, :lock_version, where: "lock_version IS NULL" + + queue_background_migration_jobs_by_range_at_intervals( + declare_class(table).where(lock_version: nil), + 'CleanupOptimisticLockingNulls', + 2.minutes, + batch_size: BATCH_SIZE, + other_arguments: [table] + ) + end + end + + def down + TABLES.each do |table| + remove_concurrent_index table.to_sym, :lock_version, where: "lock_version IS NULL" + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 36f15e000f7..0b3eab9797e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1574,6 +1574,7 @@ ActiveRecord::Schema.define(version: 2020_02_20_180944) do t.index ["end_date"], name: "index_epics_on_end_date" t.index ["group_id"], name: "index_epics_on_group_id" t.index ["iid"], name: "index_epics_on_iid" + t.index ["lock_version"], name: "index_epics_on_lock_version", where: "(lock_version IS NULL)" t.index ["parent_id"], name: "index_epics_on_parent_id" t.index ["start_date"], name: "index_epics_on_start_date" t.index ["start_date_sourcing_epic_id"], name: "index_epics_on_start_date_sourcing_epic_id", where: "(start_date_sourcing_epic_id IS NOT NULL)" @@ -2193,6 +2194,7 @@ ActiveRecord::Schema.define(version: 2020_02_20_180944) do t.index ["confidential"], name: "index_issues_on_confidential" t.index ["description"], name: "index_issues_on_description_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["duplicated_to_id"], name: "index_issues_on_duplicated_to_id", where: "(duplicated_to_id IS NOT NULL)" + t.index ["lock_version"], name: "index_issues_on_lock_version", where: "(lock_version IS NULL)" t.index ["milestone_id"], name: "index_issues_on_milestone_id" t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)" t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state" @@ -2611,6 +2613,7 @@ ActiveRecord::Schema.define(version: 2020_02_20_180944) do t.index ["id", "merge_jid"], name: "idx_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND (state_id = 4))" t.index ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))" t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id" + t.index ["lock_version"], name: "index_merge_requests_on_lock_version", where: "(lock_version IS NULL)" t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)" t.index ["milestone_id"], name: "index_merge_requests_on_milestone_id" t.index ["source_branch"], name: "index_merge_requests_on_source_branch" diff --git a/doc/.linting/vale/styles/gitlab/OxfordComma.yml b/doc/.linting/vale/styles/gitlab/OxfordComma.yml index c9f4d2895d1..4b37ba8c2b9 100644 --- a/doc/.linting/vale/styles/gitlab/OxfordComma.yml +++ b/doc/.linting/vale/styles/gitlab/OxfordComma.yml @@ -8,4 +8,4 @@ message: Use a comma before the last "and" or "or" in a list of four or more ite link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#punctuation level: warning raw: - - '(?:[\w-_` ]+,){2,}(?:[\w-_` ]+) (and|or)' + - '(?:[\w-_` ]+,){2,}(?:[\w-_` ]+) (and |or )' diff --git a/doc/.linting/vale/styles/gitlab/SentenceSpacing.yml b/doc/.linting/vale/styles/gitlab/SentenceSpacing.yml index 5efc6ceeef5..0fc8bfedadc 100644 --- a/doc/.linting/vale/styles/gitlab/SentenceSpacing.yml +++ b/doc/.linting/vale/styles/gitlab/SentenceSpacing.yml @@ -1,32 +1,12 @@ --- -# `extends` indicates the Vale extension point being used. -# Full list of styles: https://errata-ai.github.io/vale/styles/ +# Checks the presence of more than one space between sentences or clauses. +# +# For a list of all options, see https://errata-ai.github.io/vale/styles/ extends: existence - -# Existence rules can display the matched strings in the user message. -message: "'%s' should have one space between sentences." - -# Should a result be flagged as a suggestion, warning, or error? -# Results that fall below the MinAlertLevel set in -# https://gitlab.com/gitlab-org/gitlab/blob/master/.vale.ini won't be shown. -level: suggestion - -# Should a match be case-insensitive or case-sensitive? -# Acceptable values are 'true' or 'false' -# This value is irrelevant when testing non-alphabetical characters -#ignorecase: true - -# Should this rule be limited to a specific scope? If yes, uncomment the line. -# Possible scopes: https://errata-ai.github.io/vale/formats/#available-scopes -# scope: heading - -# Should this rule ignore normal word boundaries, such as \b ? -# Acceptable values are 'true' or 'false' -nonword: true - -# What is the source for this rule? +message: "'%s' should have one space between sentences or clauses." link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#punctuation - +level: warning +nonword: true tokens: - - '[a-z][.?!][A-Z]' - - '[.?!] {2,}[A-Z]' + - '[a-z][.?!,][A-Z]' + - '[.?!,] {2,}[\w]' diff --git a/doc/README.md b/doc/README.md index 132351f5353..3efba168f48 100644 --- a/doc/README.md +++ b/doc/README.md @@ -112,7 +112,7 @@ The following documentation relates to the DevOps **Plan** stage: | [Discussions](user/discussions/index.md) | Threads, comments, and resolvable threads in issues, commits, and merge requests. | | [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. | | [Epics](user/group/epics/index.md) **(ULTIMATE)** | Tracking groups of issues that share a theme. | -| [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md),<br/>[issue and merge request templates](user/project/description_templates.md),<br/>and [moving issues](user/project/issues/managing_issues.md#moving-issues) | Project issues, restricting access to issues, create templates for submitting new issues and merge requests, and moving issues between projects. | +| [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md),<br/>[issue and merge request templates](user/project/description_templates.md),<br/>and [moving issues](user/project/issues/managing_issues.md#moving-issues) | Project issues and restricting access to issues as well as creating templates for submitting new issues and merge requests. Also, moving issues between projects. | | [Labels](user/project/labels.md) | Categorize issues or merge requests with descriptive labels. | | [Milestones](user/project/milestones/index.md) | Set milestones for delivery of issues and merge requests, with optional due date. | | [Project Issue Board](user/project/issue_board.md) | Display issues on a Scrum or Kanban board. | diff --git a/doc/administration/git_protocol.md b/doc/administration/git_protocol.md index 2e5c362a3ab..b15c3160459 100644 --- a/doc/administration/git_protocol.md +++ b/doc/administration/git_protocol.md @@ -4,17 +4,9 @@ description: "Set and configure Git protocol v2" # Configuring Git Protocol v2 -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/46555) in GitLab 11.4. -> Temporarily disabled (see [confidential issue](../user/project/issues/confidential_issues.md) -> `https://gitlab.com/gitlab-org/gitlab-foss/issues/55769`) in GitLab 11.5.8, 11.6.6, 11.7.1, and 11.8+. - -NOTE: **Note:** -Git protocol v2 support has been temporarily disabled -because a feature used to hide certain internal references does not function when it -is enabled, and this has a security impact. Once this problem has been resolved, -protocol v2 support will be re-enabled. For more information, see the -[confidential issue](../user/project/issues/confidential_issues.md) -`https://gitlab.com/gitlab-org/gitlab-foss/issues/55769`. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/46555) in GitLab 11.4. +> - [Temporarily disabled](https://gitlab.com/gitlab-org/gitlab-foss/issues/55769) in GitLab 11.5.8, 11.6.6, 11.7.1, and 11.8+. +> - [Re-enabled](https://gitlab.com/gitlab-org/gitlab/issues/27828) in GitLab 12.8. Git protocol v2 improves the v1 wire protocol in several ways and is enabled by default in GitLab for HTTP requests. In order to enable SSH, @@ -110,3 +102,15 @@ debug1: Sending env GIT_PROTOCOL = version=2 For the server side, you can use the [same examples from HTTP](#http-connections), changing the URL to use SSH. + +### Observe Git protocol version of connections + +To observe what Git protocol versions are being used in a +production environment, you can use the following Prometheus query: + +```prometheus +sum(rate(gitaly_git_protocol_requests_total[1m])) by (grpc_method,git_protocol,grpc_service) +``` + +You can view what Git protocol versions are being used on GitLab.com at +<https://dashboards.gitlab.com/d/pqlQq0xik/git-protocol-versions>. diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 441ad2186f6..0c0be1da3c0 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -160,6 +160,11 @@ _The artifacts are stored by default in gitlab-rake gitlab:artifacts:migrate ``` +CAUTION: **CAUTION:** +JUnit test report artifact (`junit.xml.gz`) migration +[is not supported](https://gitlab.com/gitlab-org/gitlab/issues/27698) +by the `gitlab:artifacts:migrate` script. + **In installations from source:** _The artifacts are stored by default in @@ -188,6 +193,11 @@ _The artifacts are stored by default in sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production ``` +CAUTION: **CAUTION:** +JUnit test report artifact (`junit.xml.gz`) migration +[is not supported](https://gitlab.com/gitlab-org/gitlab/issues/27698) +by the `gitlab:artifacts:migrate` script. + ### Migrating from object storage to local storage In order to migrate back to local storage: diff --git a/doc/api/groups.md b/doc/api/groups.md index 946626859f8..14e04b9dd17 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -498,7 +498,7 @@ Parameters: ## Transfer project to group -Transfer a project to the Group namespace. Available only to instance administrators. Transferring projects may fail when tagged packages exist in the project's repository. +Transfer a project to the Group namespace. Available only to instance administrators. Transferring projects may fail when tagged packages exist in the project's repository. ``` POST /groups/:id/projects/:project_id diff --git a/doc/ci/parent_child_pipelines.md b/doc/ci/parent_child_pipelines.md index 387f2e69606..f8d100b83b8 100644 --- a/doc/ci/parent_child_pipelines.md +++ b/doc/ci/parent_child_pipelines.md @@ -80,6 +80,52 @@ microservice_a: strategy: depend ``` +## Merge Request child pipelines + +To trigger a child pipeline as a [Merge Request Pipeline](merge_request_pipelines/index.md) we need to: + +- Set the trigger job to run on merge requests: + +```yaml +# parent .gitlab-ci.yml +microservice_a: + trigger: + include: path/to/microservice_a.yml + rules: + - if: $CI_MERGE_REQUEST_ID +``` + +- Configure the child pipeline by either: + + - Setting all jobs in the child pipeline to evaluate in the context of a merge request: + + ```yaml + # child path/to/microservice_a.yml + workflow: + rules: + - if: $CI_MERGE_REQUEST_ID + + job1: + script: ... + + job2: + script: ... + ``` + + - Alternatively, setting the rule per job. For example, to create only `job1` in + the context of merge request pipelines: + + ```yaml + # child path/to/microservice_a.yml + job1: + script: ... + rules: + - if: $CI_MERGE_REQUEST_ID + + job2: + script: ... + ``` + ## Limitations A parent pipeline can trigger many child pipelines, but a child pipeline cannot trigger diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index 8fc3ea4ee98..861c0e53103 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -176,7 +176,7 @@ using environment variables. | `CI_APPLICATION_TAG` | Docker respository tag for the image to be scanned. | `$CI_COMMIT_SHA` | | `CLAIR_DB_IMAGE` | The Docker image name and tag for the [Postgres server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db). It can be useful to override this value with a specific version, for example, to provide a consistent set of vulnerabilities for integration testing purposes, or to refer to a locally hosted vulnerabilities database for an on-premise air-gapped installation. | `arminc/clair-db:latest` | | `CLAIR_DB_IMAGE_TAG` | (**DEPRECATED - use `CLAIR_DB_IMAGE` instead**) The Docker image tag for the [Postgres server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db). It can be useful to override this value with a specific version, for example, to provide a consistent set of vulnerabilities for integration testing purposes. | `latest` | -| `DOCKERFILE_PATH` | The path to the `Dockerfile` to be used for generating remediations. By default, the scanner will look for a file named `Dockerfile` in the root directory of the project, so this variable should only be configured if your `Dockerfile` is in a non-standard location, such as a subdirectory. See [Solutions for vulnerabilities](#solutions-for-vulnerabilities-auto-remediation) for more details. | `Dockerfile` | +| `DOCKERFILE_PATH` | The path to the `Dockerfile` to be used for generating remediations. By default, the scanner will look for a file named `Dockerfile` in the root directory of the project, so this variable should only be configured if your `Dockerfile` is in a non-standard location, such as a subdirectory. See [Solutions for vulnerabilities](#solutions-for-vulnerabilities-auto-remediation) for more details. | `Dockerfile` | ## Security Dashboard @@ -352,7 +352,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on |------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `version` | Report syntax version used to generate this JSON. | | `vulnerabilities` | Array of vulnerability objects. | -| `vulnerabilities[].category` | Where this vulnerability belongs (SAST, Container Scanning etc.). For Container Scanning, it will always be `container_scanning`. | +| `vulnerabilities[].category` | Where this vulnerability belongs (for example, SAST or Container Scanning). For Container Scanning, it will always be `container_scanning`. | | `vulnerabilities[].message` | A short text that describes the vulnerability, it may include occurrence's specific information. Optional. | | `vulnerabilities[].description` | A long text that describes the vulnerability. Optional. | | `vulnerabilities[].cve` | A fingerprint string value that represents a concrete occurrence of the vulnerability. It's used to determine whether two vulnerability occurrences are same or different. May not be 100% accurate. **This is NOT a [CVE](https://cve.mitre.org/)**. | @@ -388,7 +388,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on ### docker: Error response from daemon: failed to copy xattrs When the GitLab Runner uses the Docker executor and NFS is used -(e.g., `/var/lib/docker` is on an NFS mount), Container Scanning might fail with +(for example, `/var/lib/docker` is on an NFS mount), Container Scanning might fail with an error like the following: ```text diff --git a/doc/user/group/issues_analytics/index.md b/doc/user/group/issues_analytics/index.md index 6c710885b98..4477b9bb1e6 100644 --- a/doc/user/group/issues_analytics/index.md +++ b/doc/user/group/issues_analytics/index.md @@ -4,13 +4,14 @@ type: reference # Issues Analytics **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7478) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.5. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7478) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.5. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/196561) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.9 at the project level. Issues Analytics is a bar graph which illustrates the number of issues created each month. The default timespan is 13 months, which includes the current month, and the 12 months prior. -To access the chart, navigate to a group's sidebar and select **Analytics > Issues Analytics**. +To access the chart, navigate to your group or project sidebar and select **{chart}** **Analytics > Issues Analytics**. Hover over each bar to see the total number of issues. diff --git a/lib/gitlab/background_migration/cleanup_optimistic_locking_nulls.rb b/lib/gitlab/background_migration/cleanup_optimistic_locking_nulls.rb new file mode 100644 index 00000000000..3aa1ebb49f9 --- /dev/null +++ b/lib/gitlab/background_migration/cleanup_optimistic_locking_nulls.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class CleanupOptimisticLockingNulls + QUERY_ITEM_SIZE = 1_000 + + # table - The name of the table the migration is performed for. + # start_id - The ID of the object to start at + # stop_id - The ID of the object to end at + def perform(start_id, stop_id, table) + model = define_model_for(table) + + # After analysis done, a batch size of 1,000 items per query was found to be + # the most optimal. Discussion in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18418#note_282285336 + (start_id..stop_id).each_slice(QUERY_ITEM_SIZE).each do |range| + model + .where(lock_version: nil) + .where(id: range) + .update_all(lock_version: 0) + end + end + + def define_model_for(table) + Class.new(ActiveRecord::Base) do + self.table_name = table + end + end + end + end +end diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 6317e034cfb..95a562ca1f3 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -1042,6 +1042,7 @@ into similar problems in the future (e.g. when new tables are created). # job_class_name - The background migration job class as a string # delay_interval - The duration between each job's scheduled time (must respond to `to_f`) # batch_size - The maximum number of rows per job + # other_arguments - Other arguments to send to the job # # Example: # @@ -1059,7 +1060,7 @@ into similar problems in the future (e.g. when new tables are created). # # do something # end # end - def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE) + def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE, other_arguments: []) raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id') # To not overload the worker too much we enforce a minimum interval both @@ -1074,7 +1075,7 @@ into similar problems in the future (e.g. when new tables are created). # `BackgroundMigrationWorker.bulk_perform_in` schedules all jobs for # the same time, which is not helpful in most cases where we wish to # spread the work over time. - migrate_in(delay_interval * index, job_class_name, [start_id, end_id]) + migrate_in(delay_interval * index, job_class_name, [start_id, end_id] + other_arguments) end end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index bcb05e1c718..4ab6b0ce506 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -3,102 +3,123 @@ require 'spec_helper' describe 'Project navbar' do - it_behaves_like 'verified navigation bar' do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } - let(:structure) do - [ - { - nav_item: _('Project overview'), - nav_sub_items: [ - _('Details'), - _('Activity'), - _('Releases') - ] - }, - { - nav_item: _('Repository'), - nav_sub_items: [ - _('Files'), - _('Commits'), - _('Branches'), - _('Tags'), - _('Contributors'), - _('Graph'), - _('Compare'), - (_('Locked Files') if Gitlab.ee?) - ] - }, - { - nav_item: _('Issues'), - nav_sub_items: [ - _('List'), - _('Boards'), - _('Labels'), - _('Milestones') - ] - }, - { - nav_item: _('Merge Requests'), - nav_sub_items: [] - }, - { - nav_item: _('CI / CD'), - nav_sub_items: [ - _('Pipelines'), - _('Jobs'), - _('Artifacts'), - _('Schedules') - ] - }, - { - nav_item: _('Operations'), - nav_sub_items: [ - _('Metrics'), - _('Environments'), - _('Error Tracking'), - _('Serverless'), - _('Kubernetes') - ] - }, - { - nav_item: _('Analytics'), - nav_sub_items: [ - _('CI / CD Analytics'), - (_('Code Review') if Gitlab.ee?), - _('Repository Analytics'), - _('Value Stream Analytics') - ] - }, - { - nav_item: _('Wiki'), - nav_sub_items: [] - }, - { - nav_item: _('Snippets'), - nav_sub_items: [] - }, - { - nav_item: _('Settings'), - nav_sub_items: [ - _('General'), - _('Members'), - _('Integrations'), - _('Repository'), - _('CI / CD'), - _('Operations'), - (_('Audit Events') if Gitlab.ee?) - ].compact - } + let(:analytics_nav_item) do + { + nav_item: _('Analytics'), + nav_sub_items: [ + _('CI / CD Analytics'), + (_('Code Review') if Gitlab.ee?), + _('Repository Analytics'), + _('Value Stream Analytics') ] - end + } + end - before do - project.add_maintainer(user) - sign_in(user) + let(:structure) do + [ + { + nav_item: _('Project overview'), + nav_sub_items: [ + _('Details'), + _('Activity'), + _('Releases') + ] + }, + { + nav_item: _('Repository'), + nav_sub_items: [ + _('Files'), + _('Commits'), + _('Branches'), + _('Tags'), + _('Contributors'), + _('Graph'), + _('Compare'), + (_('Locked Files') if Gitlab.ee?) + ] + }, + { + nav_item: _('Issues'), + nav_sub_items: [ + _('List'), + _('Boards'), + _('Labels'), + _('Milestones') + ] + }, + { + nav_item: _('Merge Requests'), + nav_sub_items: [] + }, + { + nav_item: _('CI / CD'), + nav_sub_items: [ + _('Pipelines'), + _('Jobs'), + _('Artifacts'), + _('Schedules') + ] + }, + { + nav_item: _('Operations'), + nav_sub_items: [ + _('Metrics'), + _('Environments'), + _('Error Tracking'), + _('Serverless'), + _('Kubernetes') + ] + }, + analytics_nav_item, + { + nav_item: _('Wiki'), + nav_sub_items: [] + }, + { + nav_item: _('Snippets'), + nav_sub_items: [] + }, + { + nav_item: _('Settings'), + nav_sub_items: [ + _('General'), + _('Members'), + _('Integrations'), + _('Repository'), + _('CI / CD'), + _('Operations'), + (_('Audit Events') if Gitlab.ee?) + ].compact + } + ] + end + + before do + project.add_maintainer(user) + sign_in(user) + end + it_behaves_like 'verified navigation bar' do + before do visit project_path(project) end end + + if Gitlab.ee? + context 'when issues analytics is available' do + before do + stub_licensed_features(issues_analytics: true) + + analytics_nav_item[:nav_sub_items] << _('Issues Analytics') + analytics_nav_item[:nav_sub_items].sort! + + visit project_path(project) + end + + it_behaves_like 'verified navigation bar' + end + end end diff --git a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js new file mode 100644 index 00000000000..925699f5623 --- /dev/null +++ b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js @@ -0,0 +1,103 @@ +import { shallowMount } from '@vue/test-utils'; +import { trimText } from 'helpers/text_helper'; +import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; +import mockData from '../mock_data'; // can also use 'mockGroup', but not useful to test here + +const mockProject = mockData(); + +describe('FrequentItemsListItemComponent', () => { + let wrapper; + + const findTitle = () => wrapper.find({ ref: 'frequentItemsItemTitle' }); + const findAvatar = () => wrapper.find({ ref: 'frequentItemsItemAvatar' }); + const findAllTitles = () => wrapper.findAll({ ref: 'frequentItemsItemTitle' }); + const findNamespace = () => wrapper.find({ ref: 'frequentItemsItemNamespace' }); + const findAllAnchors = () => wrapper.findAll('a'); + const findAllNamespace = () => wrapper.findAll({ ref: 'frequentItemsItemNamespace' }); + const findAvatarContainer = () => wrapper.findAll({ ref: 'frequentItemsItemAvatarContainer' }); + const findAllMetadataContainers = () => + wrapper.findAll({ ref: 'frequentItemsItemMetadataContainer' }); + + const createComponent = (props = {}) => { + wrapper = shallowMount(frequentItemsListItemComponent, { + propsData: { + itemId: mockProject.id, + itemName: mockProject.name, + namespace: mockProject.namespace, + webUrl: mockProject.webUrl, + avatarUrl: mockProject.avatarUrl, + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('computed', () => { + describe('highlightedItemName', () => { + it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => { + createComponent({ matcher: 'lab' }); + + expect(findTitle().element.innerHTML).toContain('<b>L</b><b>a</b><b>b</b>'); + }); + + it('should return project name as it is if `matcher` is not available', () => { + createComponent({ matcher: null }); + + expect(trimText(findTitle().text())).toBe(mockProject.name); + }); + }); + + describe('truncatedNamespace', () => { + it('should truncate project name from namespace string', () => { + createComponent({ namespace: 'platform / nokia-3310' }); + + expect(trimText(findNamespace().text())).toBe('platform'); + }); + + it('should truncate namespace string from the middle if it includes more than two groups in path', () => { + createComponent({ + namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310', + }); + + expect(trimText(findNamespace().text())).toBe('platform / ... / Mobile Chipset'); + }); + }); + }); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('should render avatar if avatarUrl is present', () => { + wrapper.setProps({ avatarUrl: 'path/to/avatar.png' }); + + return wrapper.vm.$nextTick(() => { + expect(findAvatar().exists()).toBe(true); + }); + }); + + it('should not render avatar if avatarUrl is not present', () => { + expect(findAvatar().exists()).toBe(false); + }); + + it('renders root element with the right classes', () => { + expect(wrapper.classes('frequent-items-list-item-container')).toBe(true); + }); + + it.each` + name | selector | expected + ${'anchor'} | ${findAllAnchors} | ${1} + ${'avatar container'} | ${findAvatarContainer} | ${1} + ${'metadata container'} | ${findAllMetadataContainers} | ${1} + ${'title'} | ${findAllTitles} | ${1} + ${'namespace'} | ${findAllNamespace} | ${1} + `('should render $expected $name', ({ selector, expected }) => { + expect(selector()).toHaveLength(expected); + }); + }); +}); diff --git a/spec/frontend/frequent_items/mock_data.js b/spec/frontend/frequent_items/mock_data.js new file mode 100644 index 00000000000..81f34053543 --- /dev/null +++ b/spec/frontend/frequent_items/mock_data.js @@ -0,0 +1,9 @@ +import { TEST_HOST } from 'helpers/test_constants'; + +export default () => ({ + id: 1, + name: 'GitLab Community Edition', + namespace: 'gitlab-org / gitlab-ce', + webUrl: `${TEST_HOST}/gitlab-org/gitlab-foss`, + avatarUrl: null, +}); diff --git a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js deleted file mode 100644 index e3f05e89a2d..00000000000 --- a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js +++ /dev/null @@ -1,94 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { trimText } from 'spec/helpers/text_helper'; -import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; -import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here - -const localVue = createLocalVue(); - -describe('FrequentItemsListItemComponent', () => { - let wrapper; - - const createComponent = (props = {}) => { - wrapper = shallowMount(localVue.extend(frequentItemsListItemComponent), { - propsData: { - itemId: mockProject.id, - itemName: mockProject.name, - namespace: mockProject.namespace, - webUrl: mockProject.webUrl, - avatarUrl: mockProject.avatarUrl, - ...props, - }, - localVue, - }); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('computed', () => { - describe('hasAvatar', () => { - it('should return `true` or `false` if whether avatar is present or not', () => { - createComponent({ avatarUrl: 'path/to/avatar.png' }); - - expect(wrapper.vm.hasAvatar).toBe(true); - }); - - it('should return `false` if avatar is not present', () => { - createComponent({ avatarUrl: null }); - - expect(wrapper.vm.hasAvatar).toBe(false); - }); - }); - - describe('highlightedItemName', () => { - it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => { - createComponent({ matcher: 'lab' }); - - expect(wrapper.find('.js-frequent-items-item-title').html()).toContain( - '<b>L</b><b>a</b><b>b</b>', - ); - }); - - it('should return project name as it is if `matcher` is not available', () => { - createComponent({ matcher: null }); - - expect(trimText(wrapper.find('.js-frequent-items-item-title').text())).toBe( - mockProject.name, - ); - }); - }); - - describe('truncatedNamespace', () => { - it('should truncate project name from namespace string', () => { - createComponent({ namespace: 'platform / nokia-3310' }); - - expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe('platform'); - }); - - it('should truncate namespace string from the middle if it includes more than two groups in path', () => { - createComponent({ - namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310', - }); - - expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe( - 'platform / ... / Mobile Chipset', - ); - }); - }); - }); - - describe('template', () => { - it('should render component element', () => { - createComponent(); - - expect(wrapper.classes()).toContain('frequent-items-list-item-container'); - expect(wrapper.findAll('a').length).toBe(1); - expect(wrapper.findAll('.frequent-items-item-avatar-container').length).toBe(1); - expect(wrapper.findAll('.frequent-items-item-metadata-container').length).toBe(1); - expect(wrapper.findAll('.frequent-items-item-title').length).toBe(1); - expect(wrapper.findAll('.frequent-items-item-namespace').length).toBe(1); - }); - }); -}); diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index bb5475130cf..ce6e8c731e2 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -1332,6 +1332,15 @@ describe Gitlab::Database::MigrationHelpers do end end end + + context 'with other_arguments option' do + it 'queues jobs correctly' do + model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_arguments: [1, 2]) + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]]) + expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f) + end + end end context "when the model doesn't have an ID column" do diff --git a/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb b/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb new file mode 100644 index 00000000000..bec8435b2f0 --- /dev/null +++ b/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200128210353_cleanup_optimistic_locking_nulls') + +describe CleanupOptimisticLockingNulls, :migration do + TABLES = %w(epics merge_requests issues).freeze + TABLES.each do |table| + let(table.to_sym) { table(table.to_sym) } + end + let(:tables) { TABLES.map { |t| method(t.to_sym).call } } + + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:users) { table(:users)} + + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + users.create!(id: 123, username: 'author', projects_limit: 1000) + + # Create necessary rows + epics.create!(iid: 123, group_id: 123, author_id: 123, title: 'a', title_html: 'a') + merge_requests.create!(iid: 123, target_project_id: 123, source_project_id: 123, target_branch: 'master', source_branch: 'hmm', title: 'a', title_html: 'a') + issues.create!(iid: 123, project_id: 123, title: 'a', title_html: 'a') + + # Nullify `lock_version` column for all rows + # Needs to be done with a SQL fragment, otherwise Rails will coerce it to 0 + tables.each do |table| + table.update_all('lock_version = NULL') + end + end + + it 'correctly migrates nullified lock_version column', :sidekiq_inline do + tables.each do |table| + expect(table.where(lock_version: nil).count).to eq(1) + end + + tables.each do |table| + expect(table.where(lock_version: 0).count).to eq(0) + end + + migrate! + + tables.each do |table| + expect(table.where(lock_version: nil).count).to eq(0) + end + + tables.each do |table| + expect(table.where(lock_version: 0).count).to eq(1) + end + end +end |