Commit a53d2c37 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 18b84353
......@@ -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)}"'
......
......@@ -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);
......
<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>
......
......@@ -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">
......
/* 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;
......
<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"
......
---
title: Add responsivity to cluster environments table
merge_request: 25501
author:
type: fixed
---
title: Improved selection of multiple cards
merge_request:
author: Gwen_
type: fixed
---
title: Clean up conditional `col-` classes in `nav_dropdown_button.vue`
merge_request: 25312
author:
type: other
---
title: Set all NULL `lock_version` values to 0 for issuables
merge_request: 18418
author:
type: fixed
# 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
......@@ -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"
......
......@@ -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 )'
---
# `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]'
......@@ -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. |
......
......@@ -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>.
......@@ -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:
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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.
......
# 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
......@@ -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
......
......@@ -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
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { trimText } from 'spec/helpers/text_helper';
import { shallowMount } from '@vue/test-utils';
import { trimText } from '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
import mockData from '../mock_data'; // can also use 'mockGroup', but not useful to test here
const localVue = createLocalVue();
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(localVue.extend(frequentItemsListItemComponent), {
wrapper = shallowMount(frequentItemsListItemComponent, {
propsData: {
itemId: mockProject.id,
itemName: mockProject.name,
......@@ -18,7 +28,6 @@ describe('FrequentItemsListItemComponent', () => {
avatarUrl: mockProject.avatarUrl,
...props,
},
localVue,
});
};
......@@ -28,35 +37,17 @@ describe('FrequentItemsListItemComponent', () => {
});
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>',
);
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(wrapper.find('.js-frequent-items-item-title').text())).toBe(
mockProject.name,
);
expect(trimText(findTitle().text())).toBe(mockProject.name);
});
});
......@@ -64,7 +55,7 @@ describe('FrequentItemsListItemComponent', () => {
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');
expect(trimText(findNamespace().text())).toBe('platform');
});
it('should truncate namespace string from the middle if it includes more than two groups in path', () => {
......@@ -72,23 +63,41 @@ describe('FrequentItemsListItemComponent', () => {
namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310',
});
expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe(
'platform / ... / Mobile Chipset',
);
expect(trimText(findNamespace().text())).toBe('platform / ... / Mobile Chipset');
});
});
});
describe('template', () => {
it('should render component element', () => {
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);
});
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);
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);
});
});
});
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,
});
......@@ -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
......
# 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
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