Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-24 18:09:14 +00:00
parent 94c1ea6190
commit 72875e4a37
48 changed files with 1041 additions and 355 deletions

View file

@ -194,12 +194,17 @@ Dangerfile @gl-quality/eng-prod
# Secure & Threat Management ownership delineation
# https://about.gitlab.com/handbook/engineering/development/threat-management/delineate-secure-threat-management.html#technical-boundaries
[Secure]
[Threat Insights]
/ee/app/finders/security/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/security/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/vulnerability.rb @gitlab-org/secure/threat-insights-backend-team
/ee/app/policies/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/policies/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team
/ee/lib/api/vulnerabilit*.rb @gitlab-org/secure/threat-insights-backend-team
/ee/spec/policies/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
/ee/spec/policies/vulnerabilities/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team
[Secure]
/ee/lib/gitlab/ci/parsers/license_compliance/ @gitlab-org/secure/composition-analysis-be
/ee/lib/gitlab/ci/parsers/security/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/dynamic-analysis-be @gitlab-org/secure/static-analysis-be @gitlab-org/secure/fuzzing-be
/ee/lib/gitlab/ci/reports/coverage_fuzzing/ @gitlab-org/secure/fuzzing-be

View file

@ -1,20 +1,48 @@
<script>
import $ from 'jquery';
import { escape } from 'lodash';
import { GlButton, GlModalDirective } from '@gitlab/ui';
import { GlButton, GlModalDirective, GlSkeletonLoader } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import StatusIcon from '../mr_widget_status_icon.vue';
import userPermissionsQuery from '../../queries/permissions.query.graphql';
import conflictsStateQuery from '../../queries/states/conflicts.query.graphql';
export default {
name: 'MRWidgetConflicts',
components: {
GlSkeletonLoader,
StatusIcon,
GlButton,
},
directives: {
GlModalDirective,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
apollo: {
userPermissions: {
query: userPermissionsQuery,
skip() {
return !this.glFeatures.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
update: data => data.project.mergeRequest.userPermissions,
},
stateData: {
query: conflictsStateQuery,
skip() {
return !this.glFeatures.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
update: data => data.project.mergeRequest,
},
},
props: {
/* TODO: This is providing all store and service down when it
only needs a few props */
@ -24,21 +52,72 @@ export default {
default: () => ({}),
},
},
data() {
return {
userPermissions: {},
stateData: {},
};
},
computed: {
isLoading() {
return (
this.glFeatures.mergeRequestWidgetGraphql &&
this.$apollo.queries.userPermissions.loading &&
this.$apollo.queries.stateData.loading
);
},
canPushToSourceBranch() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.userPermissions.pushToSourceBranch;
}
return this.mr.canPushToSourceBranch;
},
canMerge() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.userPermissions.canMerge;
}
return this.mr.canMerge;
},
shouldBeRebased() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.stateData.shouldBeRebased;
}
return this.mr.shouldBeRebased;
},
sourceBranchProtected() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.stateData.sourceBranchProtected;
}
return this.mr.sourceBranchProtected;
},
popoverTitle() {
return s__(
'mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected.',
);
},
showResolveButton() {
return this.mr.conflictResolutionPath && this.mr.canPushToSourceBranch;
return this.mr.conflictResolutionPath && this.canPushToSourceBranch;
},
showPopover() {
return this.showResolveButton && this.mr.sourceBranchProtected;
return this.showResolveButton && this.sourceBranchProtected;
},
},
mounted() {
if (this.showPopover) {
watch: {
showPopover: {
handler(newVal) {
if (newVal) {
this.$nextTick(this.initPopover);
}
},
immediate: true,
},
},
methods: {
initPopover() {
const $el = $(this.$refs.popover);
$el
@ -68,7 +147,7 @@ export default {
.on('show.bs.popover', () => {
window.addEventListener('scroll', togglePopover.bind($el, false), { once: true });
});
}
},
},
};
</script>
@ -76,34 +155,41 @@ export default {
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
<span v-if="mr.shouldBeRebased" class="bold">
<div v-if="isLoading" class="gl-ml-4 gl-w-full mr-conflict-loader">
<gl-skeleton-loader :width="334" :height="30">
<rect x="0" y="7" width="150" height="16" rx="4" />
<rect x="158" y="7" width="84" height="16" rx="4" />
<rect x="250" y="7" width="84" height="16" rx="4" />
</gl-skeleton-loader>
</div>
<div v-else class="media-body space-children">
<span v-if="shouldBeRebased" class="bold">
{{
s__(`mrWidget|Fast-forward merge is not possible.
To merge this request, first rebase locally.`)
To merge this request, first rebase locally.`)
}}
</span>
<template v-else>
<span class="bold">
{{ s__('mrWidget|There are merge conflicts') }}<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge">
{{ s__('mrWidget|There are merge conflicts') }}<span v-if="!canMerge">.</span>
<span v-if="!canMerge">
{{
s__(`mrWidget|Resolve these conflicts or ask someone
with write access to this repository to merge it locally`)
with write access to this repository to merge it locally`)
}}
</span>
</span>
<span v-if="showResolveButton" ref="popover">
<gl-button
:href="mr.conflictResolutionPath"
:disabled="mr.sourceBranchProtected"
:href="!sourceBranchProtected && mr.conflictResolutionPath"
:disabled="sourceBranchProtected"
class="js-resolve-conflicts-button"
>
{{ s__('mrWidget|Resolve conflicts') }}
</gl-button>
</span>
<gl-button
v-if="mr.canMerge"
v-if="canMerge"
v-gl-modal-directive="'modal-merge-info'"
class="js-merge-locally-button"
>

View file

@ -0,0 +1,10 @@
query userPermissionsQuery($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
userPermissions {
canMerge
pushToSourceBranch
}
}
}
}

View file

@ -0,0 +1,8 @@
query workInProgressQuery($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
shouldBeRebased
sourceBranchProtected
}
}
}

View file

@ -468,7 +468,6 @@ $gl-line-height-20: 20px;
$gl-line-height-24: 24px;
$gl-line-height-14: 14px;
$issue-box-upcoming-bg: #8f8f8f;
$pages-group-name-color: #4c4e54;
/*

View file

@ -693,10 +693,6 @@
.issuable-list {
li {
.issue-box {
display: flex;
}
.issuable-info-container {
flex: 1;
display: flex;

View file

@ -1039,3 +1039,11 @@ $mr-widget-min-height: 69px;
.diff-file-row.is-active {
background-color: $gray-50;
}
.mr-conflict-loader {
max-width: 334px;
> svg {
vertical-align: middle;
}
}

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class WhatsNewController < ApplicationController
include Gitlab::WhatsNew
skip_before_action :authenticate_user!
before_action :check_feature_flag, :check_valid_page_param, :set_pagination_headers
@ -12,7 +10,7 @@ class WhatsNewController < ApplicationController
def index
respond_to do |format|
format.js do
render json: whats_new_release_items(page: current_page)
render json: most_recent_items
end
end
end
@ -27,18 +25,19 @@ class WhatsNewController < ApplicationController
render_404 if current_page < 1
end
def set_pagination_headers
response.set_header('X-Next-Page', next_page)
end
def current_page
params[:page]&.to_i || 1
end
def next_page
next_page = current_page + 1
next_index = next_page - 1
def most_recent
@most_recent ||= ReleaseHighlight.paginated(page: current_page)
end
next_page if whats_new_file_paths[next_index]
def most_recent_items
most_recent[:items].map {|item| Gitlab::WhatsNew::ItemPresenter.present(item) }
end
def set_pagination_headers
response.set_header('X-Next-Page', most_recent[:next_page])
end
end

View file

@ -49,6 +49,8 @@ module Types
description: 'ID of the merge request target project'
field :source_branch, GraphQL::STRING_TYPE, null: false,
description: 'Source branch of the merge request'
field :source_branch_protected, GraphQL::BOOLEAN_TYPE, null: false, calls_gitaly: true,
description: 'Indicates if the source branch is protected'
field :target_branch, GraphQL::STRING_TYPE, null: false,
description: 'Target branch of the merge request'
field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false,
@ -194,6 +196,10 @@ module Types
def commit_count
object&.metrics&.commits_count
end
def source_branch_protected
object.source_project.present? && ProtectedBranch.protected?(object.source_project, object.source_branch)
end
end
end
Types::MergeRequestType.prepend_if_ee('::EE::Types::MergeRequestType')

View file

@ -1,25 +1,15 @@
# frozen_string_literal: true
module WhatsNewHelper
include Gitlab::WhatsNew
def whats_new_most_recent_release_items_count
Gitlab::ProcessMemoryCache.cache_backend.fetch('whats_new:release_items_count', expires_in: CACHE_DURATION) do
whats_new_release_items&.count
end
ReleaseHighlight.most_recent_item_count
end
def whats_new_storage_key
return unless whats_new_most_recent_version
most_recent_version = ReleaseHighlight.most_recent_version
['display-whats-new-notification', whats_new_most_recent_version].join('-')
end
return unless most_recent_version
private
def whats_new_most_recent_version
Gitlab::ProcessMemoryCache.cache_backend.fetch('whats_new:release_version', expires_in: CACHE_DURATION) do
whats_new_release_items&.first&.[]('release')
end
['display-whats-new-notification', most_recent_version].join('-')
end
end

View file

@ -0,0 +1,67 @@
# frozen_string_literal: true
class ReleaseHighlight
CACHE_DURATION = 1.hour
FILES_PATH = Rails.root.join('data', 'whats_new', '*.yml')
def self.paginated(page: 1)
Rails.cache.fetch(cache_key(page), expires_in: CACHE_DURATION) do
items = self.load_items(page: page)
next if items.nil?
{
items: items,
next_page: next_page(current_page: page)
}
end
end
def self.load_items(page:)
index = page - 1
file_path = file_paths[index]
return if file_path.nil?
file = File.read(file_path)
items = YAML.safe_load(file, permitted_classes: [Date])
platform = Gitlab.com? ? 'gitlab-com' : 'self-managed'
items&.select {|item| item[platform] }
rescue Psych::Exception => e
Gitlab::ErrorTracking.track_exception(e, file_path: file_path)
nil
end
def self.file_paths
@file_paths ||= Rails.cache.fetch('release_highlight:file_paths', expires_in: CACHE_DURATION) do
Dir.glob(FILES_PATH).sort.reverse
end
end
def self.cache_key(page)
filename = /\d*\_\d*\_\d*/.match(self.file_paths&.first)
"release_highlight:items:file-#{filename}:page-#{page}"
end
def self.next_page(current_page: 1)
next_page = current_page + 1
next_index = next_page - 1
next_page if self.file_paths[next_index]
end
def self.most_recent_version
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:release_version', expires_in: CACHE_DURATION) do
self.paginated&.[](:items)&.first&.[]('release')
end
end
def self.most_recent_item_count
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:recent_item_count', expires_in: CACHE_DURATION) do
self.paginated&.[](:items)&.count
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Gitlab
module WhatsNew
class ItemPresenter
DICTIONARY = {
free: 'Free',
starter: 'Bronze',
premium: 'Silver',
ultimate: 'Gold'
}.freeze
def self.present(item)
if Gitlab.com?
item['packages'] = item['packages'].map { |p| DICTIONARY[p.downcase.to_sym] }
end
item
end
end
end
end

View file

@ -0,0 +1,5 @@
---
title: Remove unused .issue-box CSS
merge_request: 48002
author: Takuya Noguchi
type: other

View file

@ -0,0 +1,5 @@
---
title: Add packages_size to ProjectStatistics API entity
merge_request: 47156
author: Roger Meier
type: added

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AddIndexOnPackageSizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_project_statistics_on_packages_size_and_project_id'
disable_ddl_transaction!
def up
add_concurrent_index :project_statistics, [:packages_size, :project_id],
name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :project_statistics, INDEX_NAME
end
end

View file

@ -0,0 +1 @@
008f3a69d23abbd513336c5a48b2448e470a9413920beeb6a1684d0c6840d6a4

View file

@ -21779,6 +21779,8 @@ CREATE UNIQUE INDEX index_project_settings_on_push_rule_id ON project_settings U
CREATE INDEX index_project_statistics_on_namespace_id ON project_statistics USING btree (namespace_id);
CREATE INDEX index_project_statistics_on_packages_size_and_project_id ON project_statistics USING btree (packages_size, project_id);
CREATE UNIQUE INDEX index_project_statistics_on_project_id ON project_statistics USING btree (project_id);
CREATE INDEX index_project_statistics_on_repository_size_and_project_id ON project_statistics USING btree (repository_size, project_id);

View file

@ -133,12 +133,6 @@ recorded:
- A user's personal access token was successfully created or revoked ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6)
- A failed attempt to create or revoke a user's personal access token ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6)
It's possible to filter particular actions by choosing an audit data type from
the filter dropdown box. You can further filter by specific group, project, or user
(for authentication events).
![audit log](img/audit_log.png)
Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events).
### Missing events
@ -180,6 +174,19 @@ the steps bellow.
Feature.enable(:repository_push_audit_event)
```
## Search
The search filters you can see depends on which audit level you are at.
| Filter | Available options |
| ------ | ----------------- |
| Scope (Project level) | A specific user who performed the action. |
| Scope (Group level) | A specific user (in a group) who performed the action. |
| Scope (Instance level) | A specific group, project, or user that the action was scoped to. |
| Date range | Either via the date range buttons or pickers (maximum range of 31 days). Default is from the first day of the month to today's date. |
![audit log](img/audit_log_v13_6.png)
## Export to CSV **(PREMIUM ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1449) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
@ -193,23 +200,18 @@ This feature might not be available to you. Check the **version history** note a
If available, you can enable it with a [feature flag](#enable-or-disable-audit-log-export-to-csv).
Export to CSV allows customers to export the current filter view of your audit log as a
CSV file,
which stores tabular data in plain text. The data provides a comprehensive view with respect to
CSV file, which stores tabular data in plain text. The data provides a comprehensive view with respect to
audit events.
To export the Audit Log to CSV, navigate to
**{monitor}** **Admin Area > Monitoring > Audit Log**
1. Click in the field **Search**.
1. In the dropdown menu that appears, select the event type that you want to filter by.
1. Select the preferred date range.
1. Select the available search [filters](#search).
1. Click **Export as CSV**.
![Export Audit Log](img/export_audit_log_v13_4.png)
### Sort
Exported events are always sorted by `ID` in ascending order.
Exported events are always sorted by `created_at` in ascending order.
### Format
@ -222,8 +224,8 @@ The first row contains the headers, which are listed in the following table alon
| Author ID | ID of the author |
| Author Name | Full name of the author |
| Entity ID | ID of the scope |
| Entity Type | Type of the entity (`Project`/`Group`/`User`) |
| Entity Path | Path of the entity |
| Entity Type | Type of the scope (`Project`/`Group`/`User`) |
| Entity Path | Path of the scope |
| Target ID | ID of the target |
| Target Type | Type of the target |
| Target Details | Details of the target |
@ -233,7 +235,7 @@ The first row contains the headers, which are listed in the following table alon
### Limitation
The Audit Log CSV file size is limited to a maximum of `100,000` events.
The Audit Log CSV file is limited to a maximum of `100,000` events.
The remaining records are truncated when this limit is reached.
### Enable or disable Audit Log Export to CSV

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -25,8 +25,9 @@ you want using steps 1 and 2 from the GitLab downloads page.
## Optional: Enable extra Sidekiq processes
sidekiq_cluster['enable'] = true
sidekiq_cluster['enable'] = true
"elastic_indexer"
sidekiq['queue_groups'] = [
"elastic_indexer",
"*"
]
```

View file

@ -12780,6 +12780,11 @@ type MergeRequest implements CurrentUserTodos & Noteable {
"""
sourceBranchExists: Boolean!
"""
Indicates if the source branch is protected
"""
sourceBranchProtected: Boolean!
"""
Source project of the merge request
"""

View file

@ -35042,6 +35042,24 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sourceBranchProtected",
"description": "Indicates if the source branch is protected",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sourceProject",
"description": "Source project of the merge request",

View file

@ -1950,6 +1950,7 @@ Autogenerated return type of MarkAsSpamSnippet.
| `shouldRemoveSourceBranch` | Boolean | Indicates if the source branch of the merge request will be deleted after merge |
| `sourceBranch` | String! | Source branch of the merge request |
| `sourceBranchExists` | Boolean! | Indicates if the source branch of the merge request exists |
| `sourceBranchProtected` | Boolean! | Indicates if the source branch is protected |
| `sourceProject` | Project | Source project of the merge request |
| `sourceProjectId` | Int | ID of the merge request source project |
| `state` | MergeRequestState! | State of the merge request |

View file

@ -49,7 +49,7 @@ GET /projects
| `last_activity_before` | datetime | **{dotted-circle}** No | Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
| `membership` | boolean | **{dotted-circle}** No | Limit by projects that the current user is a member of. |
| `min_access_level` | integer | **{dotted-circle}** No | Limit by current user minimal [access level](members.md#valid-access-levels). |
| `order_by` | string | **{dotted-circle}** No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. `repository_size`, `storage_size`, or `wiki_size` fields are only allowed for admins. Default is `created_at`. |
| `order_by` | string | **{dotted-circle}** No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. `repository_size`, `storage_size`, `packages_size` or `wiki_size` fields are only allowed for admins. Default is `created_at`. |
| `owned` | boolean | **{dotted-circle}** No | Limit by projects explicitly owned by the current user. |
| `repository_checksum_failed` **(PREMIUM)** | boolean | **{dotted-circle}** No | Limit projects where the repository checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2). |
| `repository_storage` | string | **{dotted-circle}** No | Limit results to projects stored on `repository_storage`. _(admins only)_ |

View file

@ -224,7 +224,7 @@ PUT /runners/:id
| `run_untagged`| boolean | no | Flag indicating the runner can execute untagged jobs |
| `locked` | boolean | no | Flag indicating the runner is locked |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner will handle the job |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
@ -559,7 +559,7 @@ POST /runners
| `run_untagged` | boolean | no | Whether the runner should handle untagged jobs |
| `tag_list` | string array | no | List of runner's tags |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner will handle the job |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job |
```shell
curl --request POST "https://gitlab.example.com/api/v4/runners" --form "token=<registration_token>" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"

View file

@ -589,6 +589,147 @@ The configuration is picked up by the `dind` service.
sub_path = "daemon.json"
```
## Authenticating with registry in Docker-in-Docker
When you use Docker-in-Docker, the [normal authentication
methods](using_docker_images.html#define-an-image-from-a-private-container-registry)
won't work because a fresh Docker daemon is started with the service.
### Option 1: Run `docker login`
In [`before_script`](../yaml/README.md#before_script) run `docker
login`:
```yaml
image: docker:19.03.13
variables:
DOCKER_TLS_CERTDIR: "/certs"
services:
- docker:19.03.13-dind
build:
stage: build
before_script:
- echo "$DOCKER_REGISTRY_PASS" | docker login $DOCKER_REGISTRY --username $DOCKER_REGISTRY_USER --password-stdin
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
```
To log in to Docker Hub, leave `$DOCKER_REGISTRY`
empty or remove it.
### Option 2: Mount `~/.docker/config.json` on each job
If you are an administrator for GitLab Runner, you can mount a file
with the authentication configuration to `~/.docker/config.json`.
Then every job that the runner picks up will be authenticated already. If you
are using the official `docker:19.03.13` image, the home directory is
under `/root`.
If you mount the config file, any `docker` command
that modifies the `~/.docker/config.json` (for example, `docker login`)
fails, because the file is mounted as read-only. Do not change it from
read-only, because other problems will occur.
Here is an example of `/opt/.docker/config.json` that follows the
[`DOCKER_AUTH_CONFIG`](using_docker_images.md#determining-your-docker_auth_config-data)
documentation:
```json
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
}
}
}
```
#### Docker
Update the [volume
mounts](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#volumes-in-the-runnersdocker-section)
to include the file.
```toml
[[runners]]
...
executor = "docker"
[runners.docker]
...
privileged = true
volumes = ["/opt/.docker/config.json:/root/.docker/config.json:ro"]
```
#### Kubernetes
Create a [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/) with the content
of this file. You can do this with a command like:
```shell
kubectl create configmap docker-client-config --namespace gitlab-runner --from-file /opt/.docker/config.json
```
Update the [volume
mounts](https://docs.gitlab.com/runner/executors/kubernetes.html#using-volumes)
to include the file.
```toml
[[runners]]
...
executor = "kubernetes"
[runners.kubernetes]
image = "alpine:3.12"
privileged = true
[[runners.kubernetes.volumes.config_map]]
name = "docker-client-config"
mount_path = "/root/.docker/config.json"
# If you are running GitLab Runner 13.5
# or lower you can remove this
sub_path = "config.json"
```
### Option 3: Use `DOCKER_ATUH_CONFIG`
If you already have
[`DOCKER_AUTH_CONFIG`](using_docker_images.md#determining-your-docker_auth_config-data)
defined, you can use the variable and save it in
`~/.docker/config.json`.
There are multiple ways to define this. For example:
- Inside
[`pre_build_script`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section)
inside of the runner config file.
- Inside [`before_script`](../yaml/README.md#before_script).
- Inside of [`script`](../yaml/README.md#script).
Below is an example of
[`before_script`](../yaml/README.md#before_script). The same commands
apply for any solution you implement.
```yaml
image: docker:19.03.13
variables:
DOCKER_TLS_CERTDIR: "/certs"
services:
- docker:19.03.13-dind
build:
stage: build
before_script:
- mkdir -p $HOME/.docker
- echo $DOCKER_AUTH_CONFIG > $HOME/.docker/config.json
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
```
## Making Docker-in-Docker builds faster with Docker layer caching
When using Docker-in-Docker, Docker downloads all layers of your image every

View file

@ -0,0 +1,53 @@
---
stage: Manage
group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# GitLab Group Migration
[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2771) in GitLab 13.7.
CAUTION: **Caution:**
This feature is [under construction](https://gitlab.com/groups/gitlab-org/-/epics/2771) and its API/Architecture might change in the future.
GitLab Group Migration is the evolution of Project and Group Import functionality. The
goal is to have an easier way to the user migrate a whole Group, including
Projects, from one GitLab instance to another.
## Design decisions
### Overview
The following architectural diagram illustrates how the Group Migration
works with a set of [ETL](#etl) Pipelines leveraging from the current [GitLab APIs](#api).
![Simplified Component Overview](img/bulk_imports_overview_v13_7.png)
### [ETL](https://www.ibm.com/cloud/learn/etl)
<!-- Direct quote from the IBM URL link -->
> ETL, for extract, transform and load, is a data integration process that
> combines data from multiple data sources into a single, consistent data store
> that is loaded into a data warehouse or other target system.
Using ETL architecture makes the code more explicit and easier to follow, test and extend. The
idea is to have one ETL pipeline for each relation to be imported.
### API
The current [Project](../user/project/settings/import_export.md) and [Group](../user/group/settings/import_export.md) Import are file based, so they require an export
step to generate the file to be imported.
GitLab Group migration leverages on [GitLab API](../api/README.md) to speed the migration.
And, because we're on the road to [GraphQL](../api/README.md#road-to-graphql),
GitLab Group Migration will be contributing towards to expand the GraphQL API coverage, which benefits both GitLab
and its users.
### Namespace
The migration process starts with the creation of a [`BulkImport`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/bulk_import.rb)
record to keep track of the migration. From there all the code related to the
GitLab Group Migration can be found under the new `BulkImports` namespace in all the application layers.

View file

@ -32,7 +32,8 @@ should be leveraged:
requests, you can use the following workflow:
1. [Create a new feature flag](development.md#create-a-new-feature-flag)
which is **off** by default, in the first merge request.
which is **off** by default, in the first merge request which uses the flag.
Flags [should not be added separately](development.md#risk-of-a-broken-master-main-branch).
1. Submit incremental changes via one or more merge requests, ensuring that any
new code added can only be reached if the feature flag is **on**.
You can keep the feature flag enabled on your local GDK during development.

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -161,23 +161,9 @@ gitops:
```
GitLab [versions 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/259669) also
supports manifest projects containing multiple directories (or subdirectories)
of YAML files. To use multiple YAML files, specify a `paths` attribute:
```yaml
gitops:
manifest_projects:
- id: "path-to/your-manifest-project-number1"
paths:
# Read all .yaml files from team1/app1 directory.
# See https://github.com/bmatcuk/doublestar#about and
# https://pkg.go.dev/github.com/bmatcuk/doublestar/v2#Match for globbing rules.
- glob: '/team1/app1/*.yaml'
# Read all .yaml files from team2/apps and all subdirectories
- glob: '/team2/apps/**/*.yaml'
# If 'paths' is not specified or is an empty list, the configuration below is used
- glob: '/**/*.{yaml,yml,json}'
```
supports manifest projects containing
multiple directories (or subdirectories) of YAML files. For more information see our
documentation on the [Kubernetes Agent configuration respository](repository.md).
### Create an Agent record in GitLab

View file

@ -0,0 +1,93 @@
---
stage: Configure
group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Kubernetes Agent configuration repository **(PREMIUM ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259669) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.7.
> - It's disabled on GitLab.com. Rolling this feature out to GitLab.com is [planned](https://gitlab.com/groups/gitlab-org/-/epics/3834).
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
The [GitLab Kubernetes Agent integration](index.md) supports hosting your configuration for
multiple GitLab Kubernetes Agents in a single repository. These agents can be running
in the same cluster or in multiple clusters, and potentially with more than one Agent per cluster.
The Agent bootstraps with the GitLab installation URL and an authentication token,
and you provide the rest of the configuration in your repository, following
Infrastructure as Code (IaaC) best practices.
A minimal repository layout looks like this, with `my_agent_1` as the name
of your Agent:
```plaintext
|- .gitlab
|- agents
|- my_agent_1
|- config.yaml
```
## Synchronize manifest projects
Your `config.yaml` file contains a `gitops` section, which contains a `manifest_projects`
section. Each `id` in the `manifest_projects` section is the path to a Git repository
with Kubernetes resource definitions in YAML or JSON format. The Agent monitors
each project you declare, and when the project changes, GitLab deploys the changes
using the Agent.
To use multiple YAML files, specify a `paths` attribute in the `gitops` section.
By default, the Agent monitors all types of resources. You can exclude some types of resources
from monitoring. This enables you to reduce the permissions needed by the GitOps feature,
through `resource_exclusions`.
To enable a specific named resource, first use `resource_inclusions` to enable desired resources.
The following file excerpt includes specific `api_groups` and `kinds`. The `resource_exclusions`
which follow excludes all other `api_groups` and `kinds`:
```yaml
gitops:
# Manifest projects are watched by the agent. Whenever a project changes,
# GitLab deploys the changes using the agent.
manifest_projects:
# No authentication mechanisms are currently supported.
# The `id` is a path to a Git repository with Kubernetes resource definitions
# in YAML or JSON format.
- id: gitlab-org/cluster-integration/gitlab-agent
# Holds the only API groups and kinds of resources that gitops will monitor.
# Inclusion rules are evaluated first, then exclusion rules.
# If there is still no match, resource is monitored.
resource_inclusions:
- api_groups:
- apps
kinds:
- '*'
- api_groups:
- ''
kinds:
- 'ConfigMap'
# Holds the API groups and kinds of resources to exclude from gitops watch.
# Inclusion rules are evaluated first, then exclusion rules.
# If there is still no match, resource is monitored.
resource_exclusions:
- api_groups:
- '*'
kinds:
- '*'
# Namespace to use if not set explicitly in object manifest.
default_namespace: my-ns
# Paths inside of the repository to scan for manifest files.
# Directories with names starting with a dot are ignored.
paths:
# Read all .yaml files from team1/app1 directory.
# See https://github.com/bmatcuk/doublestar#about and
# https://pkg.go.dev/github.com/bmatcuk/doublestar/v2#Match for globbing rules.
- glob: '/team1/app1/*.yaml'
# Read all .yaml files from team2/apps and all subdirectories
- glob: '/team2/apps/**/*.yaml'
# If 'paths' is not specified or is an empty list, the configuration below is used
- glob: '/**/*.{yaml,yml,json}'
```

View file

@ -707,23 +707,23 @@ You can create a new package each time the `master` branch is updated.
1. Make sure your `pom.xml` file includes the following.
You can either let Maven use the CI environment variables, as shown in this example,
or you can hard code your project's ID.
or you can hard code your server's hostname and project's ID.
```xml
<repositories>
<repository>
<id>gitlab-maven</id>
<url>https://gitlab.example.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
<url>${env.CI_SERVER_URL}/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
<url>https://gitlab.example.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
<url>${env.CI_SERVER_URL}/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
<url>https://gitlab.example.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
<url>${env.CI_SERVER_URL}/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</snapshotRepository>
</distributionManagement>
```

View file

@ -10,6 +10,7 @@ module API
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
expose :snippets_size
expose :packages_size
end
end
end

View file

@ -6,7 +6,7 @@ module API
extend ActiveSupport::Concern
extend Grape::API::Helpers
STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size].freeze
STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size packages_size].freeze
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'

View file

@ -1,40 +0,0 @@
# frozen_string_literal: true
module Gitlab
module WhatsNew
CACHE_DURATION = 1.hour
WHATS_NEW_FILES_PATH = Rails.root.join('data', 'whats_new', '*.yml')
private
def whats_new_release_items(page: 1)
Rails.cache.fetch(whats_new_items_cache_key(page), expires_in: CACHE_DURATION) do
index = page - 1
file_path = whats_new_file_paths[index]
next if file_path.nil?
file = File.read(file_path)
items = YAML.safe_load(file, permitted_classes: [Date])
items if items.is_a?(Array)
end
rescue => e
Gitlab::ErrorTracking.track_exception(e, page: page)
nil
end
def whats_new_file_paths
@whats_new_file_paths ||= Rails.cache.fetch('whats_new:file_paths', expires_in: CACHE_DURATION) do
Dir.glob(WHATS_NEW_FILES_PATH).sort.reverse
end
end
def whats_new_items_cache_key(page)
filename = /\d*\_\d*\_\d*/.match(whats_new_file_paths&.first)
"whats_new:release_items:file-#{filename}:page-#{page}"
end
end
end

View file

@ -44,7 +44,7 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page.html.scan(expected).count).to be(4)
end
it 'renders only 2 Mermaid blocks and ', :js, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' } do
it 'renders only 2 Mermaid blocks and ', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' do
description = <<~MERMAID
```mermaid
graph LR
@ -71,7 +71,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
end
it 'correctly sizes mermaid diagram inside <details> block', :js do
it 'correctly sizes mermaid diagram inside <details> block' do
description = <<~MERMAID
<details>
<summary>Click to show diagram</summary>
@ -102,7 +102,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
end
it 'correctly sizes mermaid diagram block', :js do
it 'correctly sizes mermaid diagram block' do
description = <<~MERMAID
```mermaid
graph TD;
@ -121,7 +121,7 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]')
end
it 'display button when diagram exceeds length', :js do
it 'display button when diagram exceeds length', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/287806' do
graph_edges = "A-->B;B-->A;" * 420
description = <<~MERMAID

View file

@ -1,2 +1,5 @@
---
- title: It's gonna be a bright
self-managed: true
gitlab-com: false
packages: ["Premium", "Ultimate"]

View file

@ -1,2 +1,5 @@
---
- title: bright
self-managed: true
gitlab-com: false
packages: ["Premium", "Ultimate"]

View file

@ -1,3 +1,10 @@
---
- title: bright and sunshinin' day
self-managed: true
gitlab-com: false
packages: ["Premium", "Ultimate"]
release: '01.05'
- title: I think I can make it now the pain is gone
self-managed: false
gitlab-com: true
packages: ["Premium", "Ultimate"]

View file

@ -6,6 +6,7 @@ import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_
describe('MRWidgetConflicts', () => {
let vm;
let mergeRequestWidgetGraphql = null;
const path = '/conflicts';
function createComponent(propsData = {}) {
@ -13,7 +14,35 @@ describe('MRWidgetConflicts', () => {
vm = shallowMount(localVue.extend(ConflictsComponent), {
propsData,
provide: {
glFeatures: {
mergeRequestWidgetGraphql,
},
},
mocks: {
$apollo: {
queries: {
userPermissions: { loading: false },
stateData: { loading: false },
},
},
},
});
if (mergeRequestWidgetGraphql) {
vm.setData({
userPermissions: {
canMerge: propsData.mr.canMerge,
pushToSourceBranch: propsData.mr.canPushToSourceBranch,
},
stateData: {
shouldBeRebased: propsData.mr.shouldBeRebased,
sourceBranchProtected: propsData.mr.sourceBranchProtected,
},
});
}
return vm.vm.$nextTick();
}
beforeEach(() => {
@ -21,206 +50,215 @@ describe('MRWidgetConflicts', () => {
});
afterEach(() => {
mergeRequestWidgetGraphql = null;
vm.destroy();
});
// There are two permissions we need to consider:
//
// 1. Is the user allowed to merge to the target branch?
// 2. Is the user allowed to push to the source branch?
//
// This yields 4 possible permutations that we need to test, and
// we test them below. A user who can push to the source
// branch should be allowed to resolve conflicts. This is
// consistent with what the backend does.
describe('when allowed to merge but not allowed to push to source branch', () => {
beforeEach(() => {
createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: false,
conflictResolutionPath: path,
conflictsDocsPath: '',
},
});
});
it('should tell you about conflicts without bothering other people', () => {
expect(vm.text()).toContain('There are merge conflicts');
expect(vm.text()).not.toContain('ask someone with write access');
});
it('should not allow you to resolve the conflicts', () => {
expect(vm.text()).not.toContain('Resolve conflicts');
});
it('should have merge buttons', () => {
const mergeLocallyButton = vm.find('.js-merge-locally-button');
expect(mergeLocallyButton.text()).toContain('Merge locally');
});
});
describe('when not allowed to merge but allowed to push to source branch', () => {
beforeEach(() => {
createComponent({
mr: {
canMerge: false,
canPushToSourceBranch: true,
conflictResolutionPath: path,
conflictsDocsPath: '',
},
});
});
it('should tell you about conflicts', () => {
expect(vm.text()).toContain('There are merge conflicts');
expect(vm.text()).toContain('ask someone with write access');
});
it('should allow you to resolve the conflicts', () => {
const resolveButton = vm.find('.js-resolve-conflicts-button');
expect(resolveButton.text()).toContain('Resolve conflicts');
expect(resolveButton.attributes('href')).toEqual(path);
});
it('should not have merge buttons', () => {
expect(vm.text()).not.toContain('Merge locally');
});
});
describe('when allowed to merge and push to source branch', () => {
beforeEach(() => {
createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: true,
conflictResolutionPath: path,
conflictsDocsPath: '',
},
});
});
it('should tell you about conflicts without bothering other people', () => {
expect(vm.text()).toContain('There are merge conflicts');
expect(vm.text()).not.toContain('ask someone with write access');
});
it('should allow you to resolve the conflicts', () => {
const resolveButton = vm.find('.js-resolve-conflicts-button');
expect(resolveButton.text()).toContain('Resolve conflicts');
expect(resolveButton.attributes('href')).toEqual(path);
});
it('should have merge buttons', () => {
const mergeLocallyButton = vm.find('.js-merge-locally-button');
expect(mergeLocallyButton.text()).toContain('Merge locally');
});
});
describe('when user does not have permission to push to source branch', () => {
it('should show proper message', () => {
createComponent({
mr: {
canMerge: false,
canPushToSourceBranch: false,
conflictsDocsPath: '',
},
[false, true].forEach(featureEnabled => {
describe(`with GraphQL feature flag ${featureEnabled ? 'enabled' : 'disabled'}`, () => {
beforeEach(() => {
mergeRequestWidgetGraphql = featureEnabled;
});
expect(
vm
.text()
.trim()
.replace(/\s\s+/g, ' '),
).toContain('ask someone with write access');
});
// There are two permissions we need to consider:
//
// 1. Is the user allowed to merge to the target branch?
// 2. Is the user allowed to push to the source branch?
//
// This yields 4 possible permutations that we need to test, and
// we test them below. A user who can push to the source
// branch should be allowed to resolve conflicts. This is
// consistent with what the backend does.
describe('when allowed to merge but not allowed to push to source branch', () => {
beforeEach(async () => {
await createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: false,
conflictResolutionPath: path,
conflictsDocsPath: '',
},
});
});
it('should not have action buttons', () => {
createComponent({
mr: {
canMerge: false,
canPushToSourceBranch: false,
conflictsDocsPath: '',
},
it('should tell you about conflicts without bothering other people', () => {
expect(vm.text()).toContain('There are merge conflicts');
expect(vm.text()).not.toContain('ask someone with write access');
});
it('should not allow you to resolve the conflicts', () => {
expect(vm.text()).not.toContain('Resolve conflicts');
});
it('should have merge buttons', () => {
const mergeLocallyButton = vm.find('.js-merge-locally-button');
expect(mergeLocallyButton.text()).toContain('Merge locally');
});
});
expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
expect(vm.find('.js-merge-locally-button').exists()).toBe(false);
});
describe('when not allowed to merge but allowed to push to source branch', () => {
beforeEach(async () => {
await createComponent({
mr: {
canMerge: false,
canPushToSourceBranch: true,
conflictResolutionPath: path,
conflictsDocsPath: '',
},
});
});
it('should not have resolve button when no conflict resolution path', () => {
createComponent({
mr: {
canMerge: true,
conflictResolutionPath: null,
conflictsDocsPath: '',
},
it('should tell you about conflicts', () => {
expect(vm.text()).toContain('There are merge conflicts');
expect(vm.text()).toContain('ask someone with write access');
});
it('should allow you to resolve the conflicts', () => {
const resolveButton = vm.find('.js-resolve-conflicts-button');
expect(resolveButton.text()).toContain('Resolve conflicts');
expect(resolveButton.attributes('href')).toEqual(path);
});
it('should not have merge buttons', () => {
expect(vm.text()).not.toContain('Merge locally');
});
});
expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
});
});
describe('when allowed to merge and push to source branch', () => {
beforeEach(async () => {
await createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: true,
conflictResolutionPath: path,
conflictsDocsPath: '',
},
});
});
describe('when fast-forward or semi-linear merge enabled', () => {
it('should tell you to rebase locally', () => {
createComponent({
mr: {
shouldBeRebased: true,
conflictsDocsPath: '',
},
it('should tell you about conflicts without bothering other people', () => {
expect(vm.text()).toContain('There are merge conflicts');
expect(vm.text()).not.toContain('ask someone with write access');
});
it('should allow you to resolve the conflicts', () => {
const resolveButton = vm.find('.js-resolve-conflicts-button');
expect(resolveButton.text()).toContain('Resolve conflicts');
expect(resolveButton.attributes('href')).toEqual(path);
});
it('should have merge buttons', () => {
const mergeLocallyButton = vm.find('.js-merge-locally-button');
expect(mergeLocallyButton.text()).toContain('Merge locally');
});
});
expect(removeBreakLine(vm.text()).trim()).toContain(
'Fast-forward merge is not possible. To merge this request, first rebase locally.',
);
});
});
describe('when user does not have permission to push to source branch', () => {
it('should show proper message', async () => {
await createComponent({
mr: {
canMerge: false,
canPushToSourceBranch: false,
conflictsDocsPath: '',
},
});
describe('when source branch protected', () => {
beforeEach(() => {
createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: true,
conflictResolutionPath: TEST_HOST,
sourceBranchProtected: true,
conflictsDocsPath: '',
},
expect(
vm
.text()
.trim()
.replace(/\s\s+/g, ' '),
).toContain('ask someone with write access');
});
it('should not have action buttons', async () => {
await createComponent({
mr: {
canMerge: false,
canPushToSourceBranch: false,
conflictsDocsPath: '',
},
});
expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
expect(vm.find('.js-merge-locally-button').exists()).toBe(false);
});
it('should not have resolve button when no conflict resolution path', async () => {
await createComponent({
mr: {
canMerge: true,
conflictResolutionPath: null,
conflictsDocsPath: '',
},
});
expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
});
});
});
it('sets resolve button as disabled', () => {
expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('true');
});
describe('when fast-forward or semi-linear merge enabled', () => {
it('should tell you to rebase locally', async () => {
await createComponent({
mr: {
shouldBeRebased: true,
conflictsDocsPath: '',
},
});
it('renders popover', () => {
expect($.fn.popover).toHaveBeenCalled();
});
});
describe('when source branch not protected', () => {
beforeEach(() => {
createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: true,
conflictResolutionPath: TEST_HOST,
sourceBranchProtected: false,
conflictsDocsPath: '',
},
expect(removeBreakLine(vm.text()).trim()).toContain(
'Fast-forward merge is not possible. To merge this request, first rebase locally.',
);
});
});
});
it('sets resolve button as disabled', () => {
expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
});
describe('when source branch protected', () => {
beforeEach(async () => {
await createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: true,
conflictResolutionPath: TEST_HOST,
sourceBranchProtected: true,
conflictsDocsPath: '',
},
});
});
it('renders popover', () => {
expect($.fn.popover).not.toHaveBeenCalled();
it('sets resolve button as disabled', () => {
expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('true');
});
it('renders popover', () => {
expect($.fn.popover).toHaveBeenCalled();
});
});
describe('when source branch not protected', () => {
beforeEach(async () => {
await createComponent({
mr: {
canMerge: true,
canPushToSourceBranch: true,
conflictResolutionPath: TEST_HOST,
sourceBranchProtected: false,
conflictsDocsPath: '',
},
});
});
it('sets resolve button as disabled', () => {
expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
});
it('renders popover', () => {
expect($.fn.popover).not.toHaveBeenCalled();
});
});
});
});
});

View file

@ -27,7 +27,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees participants subscribed labels discussion_locked time_estimate
total_time_spent reference author merged_at commit_count current_user_todos
conflicts auto_merge_enabled approved_by
conflicts auto_merge_enabled approved_by source_branch_protected
]
if Gitlab.ee?

View file

@ -3,22 +3,22 @@
require 'spec_helper'
RSpec.describe WhatsNewHelper do
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
describe '#whats_new_storage_key' do
subject { helper.whats_new_storage_key }
context 'when version exist' do
let(:release_item) { double(:item) }
before do
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
allow(ReleaseHighlight).to receive(:most_recent_version).and_return(84.0)
end
it { is_expected.to eq('display-whats-new-notification-01.05') }
it { is_expected.to eq('display-whats-new-notification-84.0') }
end
context 'when recent release items do NOT exist' do
context 'when most recent release highlights do NOT exist' do
before do
allow(helper).to receive(:whats_new_release_items).and_return(nil)
allow(ReleaseHighlight).to receive(:most_recent_version).and_return(nil)
end
it { is_expected.to be_nil }
@ -30,31 +30,18 @@ RSpec.describe WhatsNewHelper do
context 'when recent release items exist' do
it 'returns the count from the most recent file' do
expect(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
allow(ReleaseHighlight).to receive(:most_recent_item_count).and_return(1)
expect(subject).to eq(1)
end
end
context 'when recent release items do NOT exist' do
before do
allow(YAML).to receive(:safe_load).and_raise
it 'returns nil' do
allow(ReleaseHighlight).to receive(:most_recent_item_count).and_return(nil)
expect(Gitlab::ErrorTracking).to receive(:track_exception)
end
it 'fails gracefully and logs an error' do
expect(subject).to be_nil
end
end
end
# Testing this important private method here because the request spec required multiple confusing mocks and felt wrong and overcomplicated
describe '#whats_new_items_cache_key' do
it 'returns a key containing the most recent file name and page parameter' do
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
expect(helper.send(:whats_new_items_cache_key, 2)).to eq('whats_new:release_items:file-20201225_01_05:page-2')
end
end
end

View file

@ -0,0 +1,135 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ReleaseHighlight do
describe '#paginated' do
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
let(:cache_mock) { double(:cache_mock) }
let(:dot_com) { false }
before do
allow(Gitlab).to receive(:com?).and_return(dot_com)
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
expect(Rails).to receive(:cache).twice.and_return(cache_mock)
expect(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield
end
after do
ReleaseHighlight.instance_variable_set(:@file_paths, nil)
end
context 'with page param' do
subject { ReleaseHighlight.paginated(page: page) }
before do
allow(cache_mock).to receive(:fetch).and_yield
end
context 'when there is another page of results' do
let(:page) { 2 }
it 'responds with paginated results' do
expect(subject[:items].first['title']).to eq('bright')
expect(subject[:next_page]).to eq(3)
end
end
context 'when there is NOT another page of results' do
let(:page) { 3 }
it 'responds with paginated results and no next_page' do
expect(subject[:items].first['title']).to eq("It's gonna be a bright")
expect(subject[:next_page]).to eq(nil)
end
end
context 'when that specific page does not exist' do
let(:page) { 84 }
it 'returns nil' do
expect(subject).to be_nil
end
end
end
context 'with no page param' do
subject { ReleaseHighlight.paginated }
before do
expect(cache_mock).to receive(:fetch).with('release_highlight:items:file-20201225_01_05:page-1', expires_in: 1.hour).and_yield
end
it 'returns platform specific items and uses a cache key' do
expect(subject[:items].count).to eq(1)
expect(subject[:items].first['title']).to eq("bright and sunshinin' day")
expect(subject[:next_page]).to eq(2)
end
context 'when Gitlab.com' do
let(:dot_com) { true }
it 'responds with a different set of data' do
expect(subject[:items].count).to eq(1)
expect(subject[:items].first['title']).to eq("I think I can make it now the pain is gone")
end
end
context 'when recent release items do NOT exist' do
before do
allow(YAML).to receive(:safe_load).and_raise(Psych::Exception)
expect(Gitlab::ErrorTracking).to receive(:track_exception)
end
it 'fails gracefully and logs an error' do
expect(subject).to be_nil
end
end
end
end
describe '.most_recent_version' do
subject { ReleaseHighlight.most_recent_version }
context 'when version exist' do
let(:release_item) { double(:item) }
before do
allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [release_item] })
allow(release_item).to receive(:[]).with('release').and_return(84.0)
end
it { is_expected.to eq(84.0) }
end
context 'when most recent release highlights do NOT exist' do
before do
allow(ReleaseHighlight).to receive(:paginated).and_return(nil)
end
it { is_expected.to be_nil }
end
end
describe '#most_recent_item_count' do
subject { ReleaseHighlight.most_recent_item_count }
context 'when recent release items exist' do
it 'returns the count from the most recent file' do
allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [double(:item)] })
expect(subject).to eq(1)
end
end
context 'when recent release items do NOT exist' do
it 'returns nil' do
allow(ReleaseHighlight).to receive(:paginated).and_return(nil)
expect(subject).to be_nil
end
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::WhatsNew::ItemPresenter do
let(:present) { Gitlab::WhatsNew::ItemPresenter.present(item) }
let(:item) { { "packages" => %w(Premium Ultimate) } }
let(:gitlab_com) { true }
before do
allow(Gitlab).to receive(:com?).and_return(gitlab_com)
end
describe '.present' do
context 'when on Gitlab.com' do
it 'transforms package names to gitlab.com friendly package names' do
expect(present).to eq({ "packages" => %w(Silver Gold) })
end
end
context 'when not on Gitlab.com' do
let(:gitlab_com) { false }
it 'does not transform package names' do
expect(present).to eq({ "packages" => %w(Premium Ultimate) })
end
end
end
end

View file

@ -254,7 +254,7 @@ RSpec.describe API::Projects do
statistics = json_response.find { |p| p['id'] == project.id }['statistics']
expect(statistics).to be_present
expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size')
expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size', 'packages_size')
end
it "does not include license by default" do
@ -619,7 +619,7 @@ RSpec.describe API::Projects do
end
context 'sorting by project statistics' do
%w(repository_size storage_size wiki_size).each do |order_by|
%w(repository_size storage_size wiki_size packages_size).each do |order_by|
context "sorting by #{order_by}" do
before do
ProjectStatistics.update_all(order_by => 100)

View file

@ -5,28 +5,30 @@ require 'spec_helper'
RSpec.describe WhatsNewController do
describe 'whats_new_path' do
context 'with whats_new_drawer feature enabled' do
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
before do
stub_feature_flags(whats_new_drawer: true)
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
end
context 'with no page param' do
let(:most_recent) { { items: [item], next_page: 2 } }
let(:item) { double(:item) }
it 'responds with paginated data and headers' do
allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(most_recent)
allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
get whats_new_path, xhr: true
expect(response.body).to eq([{ title: "bright and sunshinin' day", release: "01.05" }].to_json)
expect(response.body).to eq(most_recent[:items].to_json)
expect(response.headers['X-Next-Page']).to eq(2)
end
end
context 'with page param' do
it 'responds with paginated data and headers' do
get whats_new_path(page: 2), xhr: true
it 'passes the page parameter' do
expect(ReleaseHighlight).to receive(:paginated).with(page: 2).and_call_original
expect(response.body).to eq([{ title: 'bright' }].to_json)
expect(response.headers['X-Next-Page']).to eq(3)
get whats_new_path(page: 2), xhr: true
end
it 'returns a 404 if page param is negative' do
@ -34,14 +36,6 @@ RSpec.describe WhatsNewController do
expect(response).to have_gitlab_http_status(:not_found)
end
context 'when there are no more paginated results' do
it 'responds with nil X-Next-Page header' do
get whats_new_path(page: 3), xhr: true
expect(response.body).to eq([{ title: "It's gonna be a bright" }].to_json)
expect(response.headers['X-Next-Page']).to be nil
end
end
end
end