Commit eabf8fd7 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 20d564f1
......@@ -84,7 +84,7 @@ gem 'net-ldap'
# API
gem 'grape', '~> 1.1.0'
gem 'grape-entity', '~> 0.7.1'
gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
# GraphQL API
gem 'graphql', '~> 1.9.11'
......
......@@ -28,8 +28,8 @@ export default function setupVueRepositoryList() {
},
});
router.afterEach(({ params: { pathMatch } }) => {
setTitle(pathMatch, ref, fullName);
router.afterEach(({ params: { path } }) => {
setTitle(path, ref, fullName);
});
const breadcrumbEl = document.getElementById('js-repo-breadcrumb');
......@@ -48,9 +48,9 @@ export default function setupVueRepositoryList() {
newDirPath,
} = breadcrumbEl.dataset;
router.afterEach(({ params: { pathMatch = '/' } }) => {
updateFormAction('.js-upload-blob-form', uploadPath, pathMatch);
updateFormAction('.js-create-dir-form', newDirPath, pathMatch);
router.afterEach(({ params: { path = '/' } }) => {
updateFormAction('.js-upload-blob-form', uploadPath, path);
updateFormAction('.js-create-dir-form', newDirPath, path);
});
// eslint-disable-next-line no-new
......@@ -61,7 +61,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(Breadcrumbs, {
props: {
currentPath: this.$route.params.pathMatch,
currentPath: this.$route.params.path,
canCollaborate: parseBoolean(canCollaborate),
canEditTree: parseBoolean(canEditTree),
newBranchPath,
......@@ -84,7 +84,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(LastCommit, {
props: {
currentPath: this.$route.params.pathMatch,
currentPath: this.$route.params.path,
},
});
},
......@@ -100,7 +100,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(TreeActionLink, {
props: {
path: historyLink + (this.$route.params.pathMatch || '/'),
path: `${historyLink}/${this.$route.params.path || ''}`,
text: __('History'),
},
});
......@@ -117,7 +117,7 @@ export default function setupVueRepositoryList() {
render(h) {
return h(TreeActionLink, {
props: {
path: webIDEUrl(`/${projectPath}/edit/${ref}/-${this.$route.params.pathMatch || '/'}`),
path: webIDEUrl(`/${projectPath}/edit/${ref}/-/${this.$route.params.path || ''}`),
text: __('Web IDE'),
cssClass: 'qa-web-ide-button',
},
......@@ -134,7 +134,7 @@ export default function setupVueRepositoryList() {
el: directoryDownloadLinks,
router,
render(h) {
const currentPath = this.$route.params.pathMatch || '/';
const currentPath = this.$route.params.path || '/';
if (currentPath !== '/') {
return h(DirectoryDownloadLinks, {
......
......@@ -13,10 +13,10 @@ export default {
return { projectPath: '', loadingPath: null };
},
beforeRouteUpdate(to, from, next) {
this.preload(to.params.pathMatch, next);
this.preload(to.params.path, next);
},
methods: {
preload(path, next) {
preload(path = '/', next) {
this.loadingPath = path.replace(/^\//, '');
return this.$apollo
......
......@@ -12,11 +12,11 @@ export default function createRouter(base, baseRef) {
base: joinPaths(gon.relative_url_root || '', base),
routes: [
{
path: `/-/tree/${escape(baseRef)}(/.*)?`,
path: `(/-)?/tree/${escape(baseRef)}/:path*`,
name: 'treePath',
component: TreePage,
props: route => ({
path: route.params.pathMatch && (route.params.pathMatch.replace(/^\//, '') || '/'),
path: route.params.path?.replace(/^\//, '') || '/',
}),
},
{
......
export const GRADIENT_COLORS = ['#000', '#a7a7a7'];
export const GRADIENT_OPACITY = ['0', '0.4'];
export const INVERSE_GRADIENT_COLORS = ['#a7a7a7', '#000'];
export const INVERSE_GRADIENT_OPACITY = ['0.4', '0'];
......@@ -5,13 +5,22 @@ class SentryIssue < ApplicationRecord
validates :issue, uniqueness: true, presence: true
validates :sentry_issue_identifier, presence: true
validate :ensure_sentry_issue_identifier_is_unique_per_project
after_create_commit :enqueue_sentry_sync_job
def self.for_project_and_identifier(project, identifier)
joins(:issue)
.where(issues: { project_id: project.id })
.find_by_sentry_issue_identifier(identifier)
.where(sentry_issue_identifier: identifier)
.order('issues.created_at').last
end
def ensure_sentry_issue_identifier_is_unique_per_project
if issue && self.class.for_project_and_identifier(issue.project, sentry_issue_identifier).present?
# Custom message because field is hidden
errors.add(:_, _('is already associated to a GitLab Issue. New issue will not be associated.'))
end
end
def enqueue_sentry_sync_job
......
---
title: Move contribution analytics chart to echarts
merge_request: 24272
author:
type: other
---
title: 'Geo: Add tables to prepare to replicate package files'
merge_request: 23447
author:
type: added
---
title: Fail upstream bridge on downstream pipeline creation failure.
merge_request: 24092
author:
type: fixed
---
title: Separate merge request entities into own class files
merge_request: 24373
author: Rajendra Kadam
type: added
......@@ -55,8 +55,6 @@ module Gitlab
memo << ee_path.to_s
end
ee_paths << "ee/app/replicators"
# Eager load should load CE first
config.eager_load_paths.push(*ee_paths)
config.helpers_paths.push "#{config.root}/ee/app/helpers"
......
......@@ -19,7 +19,6 @@ ActiveSupport::Inflector.inflections do |inflect|
group_view
job_artifact_registry
lfs_object_registry
package_file_registry
project_auto_devops
project_registry
project_statistics
......
# frozen_string_literal: true
class CreateGeoEvents < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
create_table :geo_events do |t|
t.string :replicable_name, limit: 255, null: false
t.string :event_name, limit: 255, null: false
t.jsonb :payload, default: {}, null: false
t.datetime_with_timezone :created_at, null: false
end
end
end
# frozen_string_literal: true
class AddGeoEventIdToGeoEventLog < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :geo_event_log, :geo_event_id, :integer
end
end
# frozen_string_literal: true
class AddGeoEventIdIndexToGeoEventLog < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :geo_event_log, :geo_event_id,
where: "(geo_event_id IS NOT NULL)",
using: :btree,
name: 'index_geo_event_log_on_geo_event_id'
end
def down
remove_concurrent_index :geo_event_log, :geo_event_id, name: 'index_geo_event_log_on_geo_event_id'
end
end
# frozen_string_literal: true
class AddGeoEventsForeignKey < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :geo_event_log, :geo_events,
column: :geo_event_id,
name: 'fk_geo_event_log_on_geo_event_id',
on_delete: :cascade
end
def down
remove_foreign_key_without_error :geo_event_log, column: :geo_event_id, name: 'fk_geo_event_log_on_geo_event_id'
end
end
......@@ -1665,10 +1665,8 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
t.bigint "reset_checksum_event_id"
t.bigint "cache_invalidation_event_id"
t.bigint "container_repository_updated_event_id"
t.integer "geo_event_id"
t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)"
t.index ["container_repository_updated_event_id"], name: "index_geo_event_log_on_container_repository_updated_event_id"
t.index ["geo_event_id"], name: "index_geo_event_log_on_geo_event_id", where: "(geo_event_id IS NOT NULL)"
t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)"
t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)"
t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)"
......@@ -1682,13 +1680,6 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
t.index ["upload_deleted_event_id"], name: "index_geo_event_log_on_upload_deleted_event_id", where: "(upload_deleted_event_id IS NOT NULL)"
end
create_table "geo_events", force: :cascade do |t|
t.string "replicable_name", limit: 255, null: false
t.string "event_name", limit: 255, null: false
t.jsonb "payload", default: {}, null: false
t.datetime_with_timezone "created_at", null: false
end
create_table "geo_hashed_storage_attachments_events", force: :cascade do |t|
t.integer "project_id", null: false
t.text "old_attachments_path", null: false
......@@ -4649,7 +4640,6 @@ ActiveRecord::Schema.define(version: 2020_02_04_131054) do
add_foreign_key "geo_container_repository_updated_events", "container_repositories", name: "fk_212c89c706", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_cache_invalidation_events", column: "cache_invalidation_event_id", name: "fk_42c3b54bed", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_container_repository_updated_events", column: "container_repository_updated_event_id", name: "fk_6ada82d42a", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_events", name: "fk_geo_event_log_on_geo_event_id", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_hashed_storage_migrated_events", column: "hashed_storage_migrated_event_id", name: "fk_27548c6db3", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_job_artifact_deleted_events", column: "job_artifact_deleted_event_id", name: "fk_176d3fbb5d", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_lfs_object_deleted_events", column: "lfs_object_deleted_event_id", name: "fk_d5af95fcd9", on_delete: :cascade
......
......@@ -17,7 +17,18 @@ All administrators at the time of creation of the project and group will be adde
as maintainers of the group and project, and as an admin, you'll be able to add new
members to the group in order to give them maintainer access to the project.
This project will be used for self-monitoring your GitLab instance.
This project will be used for self monitoring your GitLab instance.
## Activating or deactivating the self monitoring project
1. Navigate to **Admin Area > Settings > Metrics and profiling** and expand the **Self monitoring** section.
1. Toggle on or off the **Create Project** button to create or remove the "GitLab self monitoring" project.
1. Click **Save changes** for the changes to take effect.
If you activated the monitoring project, it should now be visible in **Projects > Your projects**.
CAUTION: **Warning:**
If you deactivate the self monitoring project, it will be permanently deleted.
## Connection to Prometheus
......
......@@ -56,6 +56,7 @@ The following API resources are available in the project context:
| [Project milestones](milestones.md) | `/projects/:id/milestones` |
| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
| [Project templates](project_templates.md) | `/projects/:id/templates` |
| [Protected_environments](protected_environments.md) | `/projects/:id/protected_environments` |
| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
| [Releases](releases/index.md) | `/projects/:id/releases` |
......
# Protected environments API **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/30595) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.8.
## Valid access levels
The access levels are defined in the `ProtectedEnvironment::DeployAccessLevel::ALLOWED_ACCESS_LEVELS` method.
Currently, these levels are recognized:
```
30 => Developer access
40 => Maintainer access
60 => Admin access
```
## List protected environments
Gets a list of protected environments from a project:
```bash
GET /projects/:id/protected_environments
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_environments/'
```
Example response:
```json
[
{
"name":"production",
"deploy_access_levels":[
{
"access_level":40,
"access_level_description":"Maintainers",
"user_id":null,
"group_id":null
}
]
}
]
```
## Get a single protected environment
Gets a single protected environment:
```bash
GET /projects/:id/protected_environments/:name
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the protected environment |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_environments/production'
```
Example response:
```json
{
"name":"production",
"deploy_access_levels":[
{
"access_level":40,
"access_level_description":"Maintainers",
"user_id":null,
"group_id":null
}
]
}
```
## Protect repository environments
Protects a single environment:
```bash
POST /projects/:id/protected_environments
```
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_environments?name=staging&deploy_access_levels%5B%5D%5Buser_id%5D=1'
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `name` | string | yes | The name of the environment. |
| `deploy_access_levels` | array | yes | Array of access levels allowed to deploy, with each described by a hash. |
Elements in the `deploy_access_levels` array should take the
form `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}`.
Each user must have access to the project and each group must [have this project shared](../user/project/members/share_project_with_groups.md).
Example response:
```json
{
"name":"staging",
"deploy_access_levels":[
{
"access_level":null,
"access_level_description":"Administrator",
"user_id":1,
"group_id":null
}
]
}
```
## Unprotect environment
Unprotects the given protected environment:
```bash
DELETE /projects/:id/protected_environments/:name
```
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_environments/staging'
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `name` | string | yes | The name of the protected environment. |
......@@ -85,7 +85,7 @@ Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
template: Container-Scanning.gitlab-ci.yml
- template: Container-Scanning.gitlab-ci.yml
```
The included template will:
......
......@@ -71,7 +71,7 @@ Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
template: DAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
variables:
DAST_WEBSITE: https://example.com
......@@ -111,7 +111,7 @@ It's also possible to authenticate the user before performing the DAST checks:
```yaml
include:
template: DAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
variables:
DAST_WEBSITE: https://example.com
......@@ -135,7 +135,7 @@ includes both passive and active scanning against the same target website:
```yaml
include:
template: DAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
variables:
DAST_FULL_SCAN_ENABLED: "true"
......@@ -151,7 +151,7 @@ Domain validation is not required by default. It can be required by setting the
```yaml
include:
template: DAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
variables:
DAST_FULL_SCAN_ENABLED: "true"
......@@ -260,7 +260,7 @@ For example:
```yaml
include:
template: DAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
variables:
DAST_WEBSITE: https://example.com
......@@ -278,7 +278,7 @@ template inclusion and specify any additional keys under it. For example:
```yaml
include:
template: DAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
dast:
stage: dast # IMPORTANT: don't forget to add this
......@@ -447,7 +447,7 @@ for DAST by overwriting the `script` key in the DAST template:
```yaml
include:
template: DAST.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
dast:
script:
......
......@@ -79,7 +79,7 @@ Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
template: Dependency-Scanning.gitlab-ci.yml
- template: Dependency-Scanning.gitlab-ci.yml
```
The included template will create a `dependency_scanning` job in your CI/CD
......@@ -99,7 +99,7 @@ For example:
```yaml
include:
template: Dependency-Scanning.gitlab-ci.yml
- template: Dependency-Scanning.gitlab-ci.yml
variables:
DS_PYTHON_VERSION: 2
......@@ -116,7 +116,7 @@ after the template inclusion and specify any additional keys under it. For examp
```yaml
include:
template: Dependency-Scanning.gitlab-ci.yml
- template: Dependency-Scanning.gitlab-ci.yml
dependency_scanning:
variables:
......@@ -187,7 +187,7 @@ This does not require running the executor in privileged mode. For example:
```yaml
include:
template: Dependency-Scanning.gitlab-ci.yml
- template: Dependency-Scanning.gitlab-ci.yml
variables:
DS_DISABLE_DIND: "true"
......
......@@ -88,7 +88,7 @@ Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
template: License-Scanning.gitlab-ci.yml
- template: License-Scanning.gitlab-ci.yml
```
The included template will create a `license_scanning` job in your CI/CD pipeline
......@@ -141,7 +141,7 @@ For example:
```yaml
include:
template: License-Scanning.gitlab-ci.yml
- template: License-Scanning.gitlab-ci.yml
variables:
LICENSE_MANAGEMENT_SETUP_CMD: sh my-custom-install-script.sh
......@@ -158,7 +158,7 @@ after the template inclusion and specify any additional keys under it. For examp
```yaml
include:
template: License-Scanning.gitlab-ci.yml
- template: License-Scanning.gitlab-ci.yml
license_scanning:
variables:
......@@ -173,7 +173,7 @@ Feel free to use it for the customization of Maven execution. For example:
```yaml
include:
template: License-Scanning.gitlab-ci.yml
- template: License-Scanning.gitlab-ci.yml
license_scanning:
variables:
......@@ -201,7 +201,7 @@ by setting the `LM_PYTHON_VERSION` environment variable to `2`.
```yaml
include:
template: License-Scanning.gitlab-ci.yml
- template: License-Scanning.gitlab-ci.yml
license_scanning:
variables:
......@@ -223,7 +223,7 @@ For example, the following `.gitlab-ci.yml`:
```yaml
include:
template: License-Management.gitlab-ci.yml
- template: License-Management.gitlab-ci.yml
license_management:
artifacts:
......@@ -235,7 +235,7 @@ Should be changed to:
```yaml
include:
template: License-Scanning.gitlab-ci.yml
- template: License-Scanning.gitlab-ci.yml
license_scanning:
artifacts:
......
......@@ -49,7 +49,7 @@ In `.gitlab-ci.yml` define:
```yaml
include:
template: SAST.gitlab-ci.yml
- template: SAST.gitlab-ci.yml
variables:
SAST_ANALYZER_IMAGE_PREFIX: my-docker-registry/gl-images
......@@ -66,7 +66,7 @@ In `.gitlab-ci.yml` define:
```yaml
include:
template: SAST.gitlab-ci.yml
- template: SAST.gitlab-ci.yml
variables:
SAST_DEFAULT_ANALYZERS: "bandit,flawfinder"
......@@ -82,7 +82,7 @@ default analyzers. In `.gitlab-ci.yml` define:
```yaml
include:
template: SAST.gitlab-ci.yml
- template: SAST.gitlab-ci.yml
variables:
SAST_DEFAULT_ANALYZERS: ""
......@@ -98,7 +98,7 @@ In `.gitlab-ci.yml` define:
```yaml
include:
template: SAST.gitlab-ci.yml
- template: SAST.gitlab-ci.yml
variables:
SAST_ANALYZER_IMAGES: "my-docker-registry/analyzers/csharp,amy-docker-registry/analyzers/perl"
......
......@@ -225,7 +225,7 @@ stages:
- test
include:
template: SAST.gitlab-ci.yml
- template: SAST.gitlab-ci.yml
variables:
SAST_DISABLE_DIND: "true"
......
......@@ -129,178 +129,6 @@ module API
end
end
class PipelineBasic < Grape::Entity
expose :id, :sha, :ref, :status
expose :created_at, :updated_at
expose :web_url do |pipeline, _options|
Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
end
end
class MergeRequestSimple < IssuableEntity
expose :title
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
end
class MergeRequestBasic < IssuableEntity
expose :merged_by, using: Entities::UserBasic do |merge_request, _options|
merge_request.metrics&.merged_by
end
expose :merged_at do |merge_request, _options|
merge_request.metrics&.merged_at
end
expose :closed_by, using: Entities::UserBasic do |merge_request, _options|
merge_request.metrics&.latest_closed_by
end
expose :closed_at do |merge_request, _options|
merge_request.metrics&.latest_closed_at
end
expose :title_html, if: -> (_, options) { options[:render_html] } do |entity|
MarkupHelper.markdown_field(entity, :title)
end
expose :description_html, if: -> (_, options) { options[:render_html] } do |entity|
MarkupHelper.markdown_field(entity, :description)
end
expose :target_branch, :source_branch
expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) }
expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) }
expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) }
expose :assignee, using: ::API::Entities::UserBasic do |merge_request|
merge_request.assignee
end
expose :author, :assignees, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :labels do |merge_request, options|
if options[:with_labels_details]
::API::Entities::LabelBasic.represent(merge_request.labels.sort_by(&:title))
else
merge_request.labels.map(&:title).sort
end
end
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_pipeline_succeeds
# Ideally we should deprecate `MergeRequest#merge_status` exposure and
# use `MergeRequest#mergeable?` instead (boolean).
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/42344 for more
# information.
expose :merge_status do |merge_request|
merge_request.check_mergeability(async: true)
merge_request.merge_status
end
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
expose :squash_commit_sha
expose :discussion_locked
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
expose :allow_collaboration, if: -> (merge_request, _) { merge_request.for_fork? }
# Deprecated
expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
# reference is deprecated in favour of references
# Introduced [Gitlab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354)
expose :reference do |merge_request, options|
merge_request.to_reference(options[:project])
end
expose :references, with: IssuableReferences do |merge_request|
merge_request
end
expose :web_url do |merge_request|
Gitlab::UrlBuilder.build(merge_request)
end
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request|
merge_request
end
expose :squash
expose :task_completion_status
expose :cannot_be_merged?, as: :has_conflicts
expose :mergeable_discussions_state?, as: :blocking_discussions_resolved
end
class MergeRequest < MergeRequestBasic
expose :subscribed, if: -> (_, options) { options.fetch(:include_subscribed, true) } do |merge_request, options|
merge_request.subscribed?(options[:current_user], options[:project])
end
expose :changes_count do |merge_request, _options|
merge_request.merge_request_diff.real_size
end
expose :latest_build_started_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.latest_build_started_at
end
expose :latest_build_finished_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.latest_build_finished_at
end
expose :first_deployed_to_production_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.first_deployed_to_production_at
end
expose :pipeline, using: Entities::PipelineBasic, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.pipeline
end
expose :head_pipeline, using: 'API::Entities::Pipeline', if: -> (_, options) do
Ability.allowed?(options[:current_user], :read_pipeline, options[:project])
end
expose :diff_refs, using: Entities::DiffRefs
# Allow the status of a rebase to be determined
expose :merge_error
expose :rebase_in_progress?, as: :rebase_in_progress, if: -> (_, options) { options[:include_rebase_in_progress] }
expose :diverged_commits_count, as: :diverged_commits_count, if: -> (_, options) { options[:include_diverged_commits_count] }
def build_available?(options)
options[:project]&.feature_available?(:builds, options[:current_user])
end
expose :user do
expose :can_merge do |merge_request, options|
merge_request.can_be_merged_by?(options[:current_user])
end
end
end
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::Diff do |compare, _|
compare.raw_diffs(limits: false).to_a
end
end
class MergeRequestDiff < Grape::Entity
expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha,
:created_at, :merge_request_id, :state, :real_size
end
class MergeRequestDiffFull < MergeRequestDiff
expose :commits, using: Entities::Commit
expose :diffs, using: Entities::Diff do |compare, _|
compare.raw_diffs(limits: false).to_a
end
end
class SSHKey < Grape::Entity
expose :id, :title, :key, :created_at
end
......
# frozen_string_literal: true
module API
module Entities
class MergeRequest < MergeRequestBasic
expose :subscribed, if: -> (_, options) { options.fetch(:include_subscribed, true) } do |merge_request, options|
merge_request.subscribed?(options[:current_user], options[:project])
end
expose :changes_count do |merge_request, _options|
merge_request.merge_request_diff.real_size
end
expose :latest_build_started_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.latest_build_started_at
end
expose :latest_build_finished_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.latest_build_finished_at
end
expose :first_deployed_to_production_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.first_deployed_to_production_at
end
expose :pipeline, using: Entities::PipelineBasic, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
merge_request.metrics&.pipeline
end
expose :head_pipeline, using: 'API::Entities::Pipeline', if: -> (_, options) do
Ability.allowed?(options[:current_user], :read_pipeline, options[:project])
end
expose :diff_refs, using: Entities::DiffRefs
# Allow the status of a rebase to be determined
expose :merge_error
expose :rebase_in_progress?, as: :rebase_in_progress, if: -> (_, options) { options[:include_rebase_in_progress] }
expose :diverged_commits_count, as: :diverged_commits_count, if: -> (_, options) { options[:include_diverged_commits_count] }
def build_available?(options)
options[:project]&.feature_available?(:builds, options[:current_user])
end
expose :user do
expose :can_merge do |merge_request, options|
merge_request.can_be_merged_by?(options[:current_user])
end
end
end
end
end
# frozen_string_literal: true
module API
module Entities
class MergeRequestBasic < IssuableEntity
expose :merged_by, using: Entities::UserBasic do |merge_request, _options|
merge_request.metrics&.merged_by
end
expose :merged_at do |merge_request, _options|
merge_request.metrics&.merged_at
end
expose :closed_by, using: Entities::UserBasic do |merge_request, _options|
merge_request.metrics&.latest_closed_by
end
expose :closed_at do |merge_request, _options|
merge_request.metrics&.latest_closed_at
end
expose :title_html, if: -> (_, options) { options[:render_html] } do |entity|
MarkupHelper.markdown_field(entity, :title)
end
expose :description_html, if: -> (_, options) { options[:render_html] } do |entity|
MarkupHelper.markdown_field(entity, :description)
end
expose :target_branch, :source_branch
expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) }
expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) }
expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) }
expose :assignee, using: ::API::Entities::UserBasic do |merge_request|
merge_request.assignee
end
expose :author, :assignees, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :labels do |merge_request, options|
if options[:with_labels_details]
::API::Entities::LabelBasic.represent(merge_request.labels.sort_by(&:title))
else
merge_request.labels.map(&:title).sort
end
end
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_pipeline_succeeds
# Ideally we should deprecate `MergeRequest#merge_status` exposure and
# use `MergeRequest#mergeable?` instead (boolean).
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/42344 for more
# information.
expose :merge_status do |merge_request|
merge_request.check_mergeability(async: true)
merge_request.merge_status
end
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
expose :squash_commit_sha
expose :discussion_locked
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
expose :allow_collaboration, if: -> (merge_request, _) { merge_request.for_fork? }
# Deprecated
expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
# reference is deprecated in favour of references
# Introduced [Gitlab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354)
expose :reference do |merge_request, options|
merge_request.to_reference(options[:project])
end
expose :references, with: IssuableReferences do |merge_request|
merge_request
end
expose :web_url do |merge_request|
Gitlab::UrlBuilder.build(merge_request)
end
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request|
merge_request
end
expose :squash
expose :task_completion_status
expose :cannot_be_merged?, as: :has_conflicts
expose :mergeable_discussions_state?, as: :blocking_discussions_resolved
end
end
end
# frozen_string_literal: true
module API
module Entities
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::Diff do |compare, _|
compare.raw_diffs(limits: false).to_a
end
end
end
end
# frozen_string_literal: true
module API
module Entities
class MergeRequestDiff < Grape::Entity
expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha,
:created_at, :merge_request_id, :state, :real_size
end
end
end
# frozen_string_literal: true
module API
module Entities
class MergeRequestDiffFull < MergeRequestDiff
expose :commits, using: Entities::Commit
expose :diffs, using: Entities::Diff do |compare, _|
compare.raw_diffs(limits: false).to_a
end
end
end
end
# frozen_string_literal: true
module API
module Entities
class MergeRequestSimple < IssuableEntity
expose :title
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
end
end
end
# frozen_string_literal: true
module API
module Entities
class PipelineBasic < Grape::Entity
expose :id, :sha, :ref, :status
expose :created_at, :updated_at
expose :web_url do |pipeline, _options|
Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
end
end
end
end
......@@ -27,7 +27,6 @@ module Quality
policies
presenters
rack_servers
replicators
routing
rubocop
serializers
......
......@@ -22692,6 +22692,9 @@ msgstr ""
msgid "is"
msgstr ""
msgid "is already associated to a GitLab Issue. New issue will not be associated."
msgstr ""
msgid "is an invalid IP address range"
msgstr ""
......
# frozen_string_literal: true
module QA
context 'Plan', :orchestrated, :smtp do
context 'Plan', :orchestrated, :smtp, :reliable do
describe 'Email Notification' do
let(:user) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
......
# frozen_string_literal: true
module QA
context 'Plan' do
context 'Plan', :reliable do
describe 'check xss occurence in @mentions in issues', :requires_admin do
it 'user mentions a user in comment' do
QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token
......
# frozen_string_literal: true
module QA
context 'Plan' do
context 'Plan', :reliable do
describe 'Close issue' do
let(:issue) do
Resource::Issue.fabricate_via_api!
......
# frozen_string_literal: true
module QA
context 'Plan' do
context 'Plan', :reliable do
describe 'collapse comments in issue discussions' do
let(:my_first_reply) { 'My first reply' }
......
# frozen_string_literal: true
module QA
context 'Plan' do
context 'Plan', :reliable do
describe 'Issue comments' do
before do
Flow::Login.sign_in
......
# frozen_string_literal: true
module QA
context 'Plan', :smoke do
context 'Plan', :smoke, :reliable do
describe 'Issue creation' do
before do
Flow::Login.sign_in
......
# frozen_string_literal: true
module QA
context 'Plan' do
context 'Plan', :reliable do
describe 'filter issue comments activities' do
before do
Flow::Login.sign_in
......
# frozen_string_literal: true
module QA
context 'Plan' do
context 'Plan', :reliable do
describe 'issue suggestions' do
let(:issue_title) { 'Issue Lists are awesome' }
......
# frozen_string_literal: true
module QA
context 'Plan', :smoke do
context 'Plan', :smoke, :reliable do
describe 'mention' do
before do
Flow::Login.sign_in
......
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'spec/test_constants';
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import eventHub from '~/environments/event_hub';
import EnvironmentActions from '~/environments/components/environment_actions.vue';
import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon } from '@gitlab/ui';
describe('EnvironmentActions Component', () => {
const Component = Vue.extend(EnvironmentActions);
let vm;
beforeEach(() => {
vm = shallowMount(EnvironmentActions, { propsData: { actions: [] } });
});
afterEach(() => {
vm.$destroy();
vm.destroy();
});
it('should render a dropdown button with 2 icons', () => {
expect(vm.find('.dropdown-new').findAll(Icon).length).toBe(2);
});
it('should render a dropdown button with aria-label description', () => {
expect(vm.find('.dropdown-new').attributes('aria-label')).toEqual('Deploy to...');
});
describe('is loading', () => {
beforeEach(() => {
vm.setData({ isLoading: true });
});
it('should render a dropdown button with a loading icon', () => {
expect(vm.findAll(GlLoadingIcon).length).toBe(1);
});
});
describe('manual actions', () => {
......@@ -30,32 +52,19 @@ describe('EnvironmentActions Component', () => {
];
beforeEach(() => {
vm = mountComponent(Component, { actions });
});
it('should render a dropdown button with icon and title attribute', () => {
expect(vm.$el.querySelector('.fa-caret-down')).toBeDefined();
expect(vm.$el.querySelector('.dropdown-new').getAttribute('data-original-title')).toEqual(
'Deploy to...',
);
expect(vm.$el.querySelector('.dropdown-new').getAttribute('aria-label')).toEqual(
'Deploy to...',
);
vm.setProps({ actions });
});
it('should render a dropdown with the provided list of actions', () => {
expect(vm.$el.querySelectorAll('.dropdown-menu li').length).toEqual(actions.length);
expect(vm.findAll('.dropdown-menu li').length).toEqual(actions.length);
});
it("should render a disabled action when it's not playable", () => {
expect(
vm.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
).toEqual('disabled');
expect(vm.find('.dropdown-menu li:last-child button').attributes('disabled')).toEqual(
'disabled',
);
expect(
vm.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'),
).toEqual(true);
expect(vm.find('.dropdown-menu li:last-child button').classes('disabled')).toBe(true);
});
});
......@@ -73,45 +82,43 @@ describe('EnvironmentActions Component', () => {
scheduledAt: '2018-10-05T08:23:00Z',
};
const findDropdownItem = action => {
const buttons = vm.$el.querySelectorAll('.dropdown-menu li button');
return Array.prototype.find.call(buttons, element =>
element.innerText.trim().startsWith(action.name),
);
const buttons = vm.findAll('.dropdown-menu li button');
return buttons.filter(button => button.text().startsWith(action.name)).at(0);
};
beforeEach(() => {
spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime());
vm = mountComponent(Component, { actions: [scheduledJobAction, expiredJobAction] });
jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
vm.setProps({ actions: [scheduledJobAction, expiredJobAction] });
});
it('emits postAction event after confirming', () => {
const emitSpy = jasmine.createSpy('emit');
const emitSpy = jest.fn();
eventHub.$on('postAction', emitSpy);
spyOn(window, 'confirm').and.callFake(() => true);
jest.spyOn(window, 'confirm').mockImplementation(() => true);
findDropdownItem(scheduledJobAction).click();
findDropdownItem(scheduledJobAction).trigger('click');
expect(window.confirm).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith({ endpoint: scheduledJobAction.playPath });
});
it('does not emit postAction event if confirmation is cancelled', () => {
const emitSpy = jasmine.createSpy('emit');
const emitSpy = jest.fn();
eventHub.$on('postAction', emitSpy);
spyOn(window, 'confirm').and.callFake(() => false);
jest.spyOn(window, 'confirm').mockImplementation(() => false);
findDropdownItem(scheduledJobAction).click();
findDropdownItem(scheduledJobAction).trigger('click');
expect(window.confirm).toHaveBeenCalled();
expect(emitSpy).not.toHaveBeenCalled();
});
it('displays the remaining time in the dropdown', () => {
expect(findDropdownItem(scheduledJobAction)).toContainText('24:00:00');
expect(findDropdownItem(scheduledJobAction).text()).toContain('24:00:00');
});
it('displays 00:00:00 for expired jobs in the dropdown', () => {
expect(findDropdownItem(expiredJobAction)).toContainText('00:00:00');
expect(findDropdownItem(expiredJobAction).text()).toContain('00:00:00');
});
});
});
......@@ -6,6 +6,7 @@ describe('Repository router spec', () => {
it.each`
path | component | componentName
${'/'} | ${IndexPage} | ${'IndexPage'}
${'/tree/master'} | ${TreePage} | ${'TreePage'}
${'/-/tree/master'} | ${TreePage} | ${'TreePage'}
${'/-/tree/master/app/assets'} | ${TreePage} | ${'TreePage'}
${'/-/tree/123/app/assets'} | ${null} | ${'null'}
......
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import BarChart from '~/vue_shared/components/bar_chart.vue';
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
function generateRandomData(dataNumber) {
const randomGraphData = [];
for (let i = 1; i <= dataNumber; i += 1) {
randomGraphData.push({
name: `random ${i}`,
value: parseInt(getRandomArbitrary(1, 8), 10),
});
}
return randomGraphData;
}
describe('Bar chart component', () => {
let barChart;
const graphData = generateRandomData(10);
beforeEach(() => {
const BarChartComponent = Vue.extend(BarChart);
barChart = mountComponent(BarChartComponent, {
graphData,
yAxisLabel: 'data',
});
});
afterEach(() => {
barChart.$destroy();
});
it('calculates the padding for even distribution across bars', () => {
barChart.vbWidth = 1000;
const result = barChart.calculatePadding(30);
// since padding can't be higher than 1 and lower than 0
// for more info: https://github.com/d3/d3-scale#band-scales
expect(result).not.toBeLessThan(0);
expect(result).not.toBeGreaterThan(1);
});
it('formats the tooltip title', () => {
const tooltipTitle = barChart.setTooltipTitle(barChart.graphData[0]);
expect(tooltipTitle).toContain('random 1:');
});
it('has a translates the bar graphs on across the X axis', () => {
barChart.panX = 100;
expect(barChart.barTranslationTransform).toEqual('translate(100, 0)');
});
it('translates the scroll indicator to the far right side', () => {
barChart.vbWidth = 500;
expect(barChart.scrollIndicatorTransform).toEqual('translate(420, 0)');
});
it('translates the x-axis to the bottom of the viewbox and pan coordinates', () => {
barChart.panX = 100;
barChart.vbHeight = 250;
expect(barChart.xAxisLocation).toEqual('translate(100, 250)');
});
it('rotates the x axis labels a total of 90 degress (CCW)', () => {
const xAxisLabel = barChart.$el.querySelector('.x-axis').querySelectorAll('text')[0];
expect(xAxisLabel.getAttribute('transform')).toEqual('rotate(-90)');
});
});
......@@ -21,7 +21,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
.to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
.to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
end
end
......@@ -82,7 +82,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
.to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
.to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
end
end
......
......@@ -13,6 +13,20 @@ describe SentryIssue do
it { is_expected.to validate_presence_of(:issue) }
it { is_expected.to validate_uniqueness_of(:issue) }
it { is_expected.to validate_presence_of(:sentry_issue_identifier) }
it 'allows duplicated sentry_issue_identifier' do
duplicate_sentry_issue = build(:sentry_issue, sentry_issue_identifier: sentry_issue.sentry_issue_identifier)
expect(duplicate_sentry_issue).to be_valid
end
it 'validates uniqueness of sentry_issue_identifier per project' do
second_issue = create(:issue, project: sentry_issue.issue.project)
duplicate_sentry_issue = build(:sentry_issue, issue: second_issue, sentry_issue_identifier: sentry_issue.sentry_issue_identifier)
expect(duplicate_sentry_issue).to be_invalid
expect(duplicate_sentry_issue.errors.full_messages.first).to include('is already associated')
end
end
describe 'callbacks' do
......@@ -28,13 +42,16 @@ describe SentryIssue do
end
describe '.for_project_and_identifier' do
let!(:sentry_issue) { create(:sentry_issue) }
let(:project) { sentry_issue.issue.project }
let(:identifier) { sentry_issue.sentry_issue_identifier }
let!(:second_sentry_issue) { create(:sentry_issue) }
it 'finds the most recent per project and sentry_issue_identifier' do
sentry_issue = create(:sentry_issue)
create(:sentry_issue)
project = sentry_issue.issue.project
sentry_issue_3 = build(:sentry_issue, issue: create(:issue, project: project), sentry_issue_identifier: sentry_issue.sentry_issue_identifier)
sentry_issue_3.save(validate: false)
subject { described_class.for_project_and_identifier(project, identifier) }
result = described_class.for_project_and_identifier(project, sentry_issue.sentry_issue_identifier)
it { is_expected.to eq(sentry_issue) }
expect(result).to eq(sentry_issue_3)
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