Commit f4a9fc6a authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 3fbdb2ef847b
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
stages: stages:
- sync - sync
......
...@@ -92,13 +92,21 @@ setup-test-env: ...@@ -92,13 +92,21 @@ setup-test-env:
- .use-pg10 - .use-pg10
- .only-master - .only-master
rspec migration pg9:
extends: .rspec-base-pg9
parallel: 4
rspec migration pg9-foss:
extends: .rspec-base-pg9-foss
parallel: 4
rspec unit pg9: rspec unit pg9:
extends: .rspec-base-pg9 extends: .rspec-base-pg9
parallel: 24 parallel: 20
rspec unit pg9-foss: rspec unit pg9-foss:
extends: .rspec-base-pg9-foss extends: .rspec-base-pg9-foss
parallel: 24 parallel: 20
rspec integration pg9: rspec integration pg9:
extends: .rspec-base-pg9 extends: .rspec-base-pg9
...@@ -140,9 +148,13 @@ rspec system pg10: ...@@ -140,9 +148,13 @@ rspec system pg10:
- .only-ee - .only-ee
- .use-pg10-ee - .use-pg10-ee
rspec-ee migration pg9:
extends: .rspec-ee-base-pg9
parallel: 2
rspec-ee unit pg9: rspec-ee unit pg9:
extends: .rspec-ee-base-pg9 extends: .rspec-ee-base-pg9
parallel: 7 parallel: 5
rspec-ee integration pg9: rspec-ee integration pg9:
extends: .rspec-ee-base-pg9 extends: .rspec-ee-base-pg9
...@@ -152,11 +164,17 @@ rspec-ee system pg9: ...@@ -152,11 +164,17 @@ rspec-ee system pg9:
extends: .rspec-ee-base-pg9 extends: .rspec-ee-base-pg9
parallel: 5 parallel: 5
rspec-ee migration pg10:
extends:
- .rspec-ee-base-pg10
- .only-master
parallel: 2
rspec-ee unit pg10: rspec-ee unit pg10:
extends: extends:
- .rspec-ee-base-pg10 - .rspec-ee-base-pg10
- .only-master - .only-master
parallel: 7 parallel: 5
rspec-ee integration pg10: rspec-ee integration pg10:
extends: extends:
......
...@@ -133,7 +133,7 @@ export default { ...@@ -133,7 +133,7 @@ export default {
:keys="keys[currentTab]" :keys="keys[currentTab]"
:store="store" :store="store"
:endpoint="endpoint" :endpoint="endpoint"
class="qa-project-deploy-keys" data-qa-selector="project_deploy_keys"
/> />
</template> </template>
</div> </div>
......
...@@ -31,7 +31,7 @@ export default { ...@@ -31,7 +31,7 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective, GlModal: GlModalDirective,
}, },
mixins: [Tracking.mixin({})], mixins: [Tracking.mixin()],
props: { props: {
repo: { repo: {
type: Object, type: Object,
...@@ -43,7 +43,6 @@ export default { ...@@ -43,7 +43,6 @@ export default {
isOpen: false, isOpen: false,
modalId: `confirm-repo-deletion-modal-${this.repo.id}`, modalId: `confirm-repo-deletion-modal-${this.repo.id}`,
tracking: { tracking: {
category: document.body.dataset.page,
label: 'registry_repository_delete', label: 'registry_repository_delete',
}, },
}; };
...@@ -67,7 +66,7 @@ export default { ...@@ -67,7 +66,7 @@ export default {
} }
}, },
handleDeleteRepository() { handleDeleteRepository() {
this.track('confirm_delete', {}); this.track('confirm_delete');
return this.deleteItem(this.repo) return this.deleteItem(this.repo)
.then(() => { .then(() => {
createFlash(__('This container registry has been scheduled for deletion.'), 'notice'); createFlash(__('This container registry has been scheduled for deletion.'), 'notice');
...@@ -103,7 +102,7 @@ export default { ...@@ -103,7 +102,7 @@ export default {
:aria-label="s__('ContainerRegistry|Remove repository')" :aria-label="s__('ContainerRegistry|Remove repository')"
class="js-remove-repo btn-inverted" class="js-remove-repo btn-inverted"
variant="danger" variant="danger"
@click="track('click_button', {})" @click="track('click_button')"
> >
<icon name="remove" /> <icon name="remove" />
</gl-button> </gl-button>
...@@ -132,7 +131,7 @@ export default { ...@@ -132,7 +131,7 @@ export default {
:modal-id="modalId" :modal-id="modalId"
ok-variant="danger" ok-variant="danger"
@ok="handleDeleteRepository" @ok="handleDeleteRepository"
@cancel="track('cancel_delete', {})" @cancel="track('cancel_delete')"
> >
<template v-slot:modal-title>{{ s__('ContainerRegistry|Remove repository') }}</template> <template v-slot:modal-title>{{ s__('ContainerRegistry|Remove repository') }}</template>
<p <p
......
...@@ -23,7 +23,7 @@ export default { ...@@ -23,7 +23,7 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
mixins: [timeagoMixin], mixins: [timeagoMixin, Tracking.mixin()],
props: { props: {
repo: { repo: {
type: Object, type: Object,
...@@ -71,9 +71,6 @@ export default { ...@@ -71,9 +71,6 @@ export default {
}, },
methods: { methods: {
...mapActions(['fetchList', 'deleteItem', 'multiDeleteItems']), ...mapActions(['fetchList', 'deleteItem', 'multiDeleteItems']),
track(action) {
Tracking.event(document.body.dataset.page, action, this.tracking);
},
setModalDescription(itemIndex = -1) { setModalDescription(itemIndex = -1) {
if (itemIndex === -1) { if (itemIndex === -1) {
this.modalDescription = sprintf( this.modalDescription = sprintf(
......
...@@ -391,6 +391,10 @@ def issuable_templates(issuable) ...@@ -391,6 +391,10 @@ def issuable_templates(issuable)
end end
end end
def issuable_templates_names(issuable)
issuable_templates(issuable).map { |template| template[:name] }
end
def selected_template(issuable) def selected_template(issuable)
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end end
......
...@@ -133,7 +133,7 @@ def self.active_session_entries(session_ids, user_id, redis) ...@@ -133,7 +133,7 @@ def self.active_session_entries(session_ids, user_id, redis)
entry_keys = raw_active_session_entries(redis, session_ids, user_id) entry_keys = raw_active_session_entries(redis, session_ids, user_id)
entry_keys.map do |raw_session| entry_keys.compact.map do |raw_session|
Marshal.load(raw_session) # rubocop:disable Security/MarshalLoad Marshal.load(raw_session) # rubocop:disable Security/MarshalLoad
end end
end end
......
...@@ -543,7 +543,11 @@ class << self ...@@ -543,7 +543,11 @@ class << self
# #
# query - The search query as a String. # query - The search query as a String.
def search(query) def search(query)
fuzzy_search(query, [:path, :name, :description]) if Feature.enabled?(:project_search_by_full_path, default_enabled: true)
joins(:route).fuzzy_search(query, [Route.arel_table[:path], :name, :description])
else
fuzzy_search(query, [:path, :name, :description])
end
end end
def search_by_title(query) def search_by_title(query)
......
---
title: Allow searching of projects by full path
merge_request: 20659
author:
type: added
...@@ -48,35 +48,6 @@ function a(p) { ...@@ -48,35 +48,6 @@ function a(p) {
}; };
``` ```
## Avoid side effects in constructors
Avoid making asynchronous calls, API requests or DOM manipulations in the `constructor`.
Move them into separate functions instead. This will make tests easier to write and
code easier to maintain.
```javascript
// bad
class myClass {
constructor(config) {
this.config = config;
axios.get(this.config.endpoint)
}
}
// good
class myClass {
constructor(config) {
this.config = config;
}
makeRequest() {
axios.get(this.config.endpoint)
}
}
const instance = new myClass();
instance.makeRequest();
```
## Avoid classes to handle DOM events ## Avoid classes to handle DOM events
If the only purpose of the class is to bind a DOM event and handle the callback, prefer If the only purpose of the class is to bind a DOM event and handle the callback, prefer
...@@ -215,7 +186,7 @@ we have a lot of examples of files which wrap their contents in IIFEs, ...@@ -215,7 +186,7 @@ we have a lot of examples of files which wrap their contents in IIFEs,
this is no longer necessary after the transition from Sprockets to webpack. this is no longer necessary after the transition from Sprockets to webpack.
Do not use them anymore and feel free to remove them when refactoring legacy code. Do not use them anymore and feel free to remove them when refactoring legacy code.
## Global namespace and side effects ## Global namespace
Avoid adding to the global namespace. Avoid adding to the global namespace.
...@@ -227,6 +198,10 @@ window.MyClass = class { /* ... */ }; ...@@ -227,6 +198,10 @@ window.MyClass = class { /* ... */ };
export default class MyClass { /* ... */ } export default class MyClass { /* ... */ }
``` ```
## Side effects
### Top-level side effects
Top-level side effects are forbidden in any script which contains `export`: Top-level side effects are forbidden in any script which contains `export`:
```javascript ```javascript
...@@ -238,38 +213,35 @@ document.addEventListener("DOMContentLoaded", function(event) { ...@@ -238,38 +213,35 @@ document.addEventListener("DOMContentLoaded", function(event) {
} }
``` ```
Avoid constructors with side effects whenever possible. ### Avoid side effects in constructors
Side effects make constructors difficult to unit test and violate the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle). Avoid making asynchronous calls, API requests or DOM manipulations in the `constructor`.
Move them into separate functions instead. This will make tests easier to write and
avoids violating the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle).
```javascript ```javascript
// Bad // bad
export class Foo { class myClass {
constructor() { constructor(config) {
this.currentState = state.INITIAL; this.config = config;
document.getElementById('root').addEventListener('click', this.handleCallback) axios.get(this.config.endpoint)
}
handleCallback() {
} }
} }
// Good // good
export class Foo { class myClass {
constructor() { constructor(config) {
this.currentState = state.INITIAL; this.config = config;
}
initListener(element) {
element.addEventListener('click', this.handleCallback)
} }
handleCallback() {
makeRequest() {
axios.get(this.config.endpoint)
} }
} }
const instance = new myClass();
instance.makeRequest();
``` ```
On the other hand, if a class only needs to extend a third-party or add
event listeners in some specific cases, they should be initialized outside
of the constructor.
## Pure Functions and Data Mutation ## Pure Functions and Data Mutation
Strive to write many small pure functions and minimize where mutations occur Strive to write many small pure functions and minimize where mutations occur
......
...@@ -42,9 +42,9 @@ The current stages are: ...@@ -42,9 +42,9 @@ The current stages are:
## Default image ## Default image
The default image is currently The default image is currently
`registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33`. `registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33`.
It includes Ruby 2.6.3, Go 1.11, Git 2.22, Chrome 73, Node 12, Yarn 1.16, It includes Ruby 2.6.3, Go 1.12, Git 2.22, Chrome 73, Node 12, Yarn 1.16,
PostgreSQL 9.6, and Graphics Magick 1.3.33. PostgreSQL 9.6, and Graphics Magick 1.3.33.
The images used in our pipelines are configured in the The images used in our pipelines are configured in the
......
...@@ -57,7 +57,7 @@ you can skip the step 1 below; you only need to enable it per project. ...@@ -57,7 +57,7 @@ you can skip the step 1 below; you only need to enable it per project.
support [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing). support [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing).
1. Navigate to your project's **Settings** and scroll down to the **Service Desk** 1. Navigate to your project's **Settings** and scroll down to the **Service Desk**
section. section.
1. If you have the correct access and an Premium license, 1. If you have the correct access and a Premium license,
you will see an option to set up Service Desk: you will see an option to set up Service Desk:
![Activate Service Desk option](img/service_desk_disabled.png) ![Activate Service Desk option](img/service_desk_disabled.png)
...@@ -79,6 +79,9 @@ you can skip the step 1 below; you only need to enable it per project. ...@@ -79,6 +79,9 @@ you can skip the step 1 below; you only need to enable it per project.
However the older format is still supported, allowing existing aliases However the older format is still supported, allowing existing aliases
or contacts to continue working._ or contacts to continue working._
1. If you have [templates](description_templates.md) in your repository, then you can optionally
select one of these templates from the dropdown to append it to all Service Desk issues.
1. Service Desk is now enabled for this project! You should be able to access it from your project's navigation **Issue submenu**: 1. Service Desk is now enabled for this project! You should be able to access it from your project's navigation **Issue submenu**:
![Service Desk Navigation Item](img/service_desk_nav_item.png) ![Service Desk Navigation Item](img/service_desk_nav_item.png)
......
...@@ -4,21 +4,7 @@ module API ...@@ -4,21 +4,7 @@ module API
module Helpers module Helpers
module Pagination module Pagination
def paginate(relation) def paginate(relation)
return Gitlab::Pagination::OffsetPagination.new(self).paginate(relation) unless keyset_pagination_enabled? ::Gitlab::Pagination::OffsetPagination.new(self).paginate(relation)
request_context = Gitlab::Pagination::Keyset::RequestContext.new(self)
unless Gitlab::Pagination::Keyset.available?(request_context, relation)
return error!('Keyset pagination is not yet available for this type of request', 501)
end
Gitlab::Pagination::Keyset.paginate(request_context, relation)
end
private
def keyset_pagination_enabled?
params[:pagination] == 'keyset' && Feature.enabled?(:api_keyset_pagination)
end end
end end
end end
......
...@@ -82,6 +82,7 @@ def load_projects ...@@ -82,6 +82,7 @@ def load_projects
def present_projects(projects, options = {}) def present_projects(projects, options = {})
projects = reorder_projects(projects) projects = reorder_projects(projects)
projects = apply_filters(projects) projects = apply_filters(projects)
projects = paginate(projects)
projects, options = with_custom_attributes(projects, options) projects, options = with_custom_attributes(projects, options)
options = options.reverse_merge( options = options.reverse_merge(
...@@ -92,10 +93,7 @@ def present_projects(projects, options = {}) ...@@ -92,10 +93,7 @@ def present_projects(projects, options = {})
) )
options[:with] = Entities::BasicProjectDetails if params[:simple] options[:with] = Entities::BasicProjectDetails if params[:simple]
projects = options[:with].prepare_relation(projects, options) present options[:with].prepare_relation(projects, options), options
projects = paginate(projects)
present projects, options
end end
def translate_params_for_compatibility(params) def translate_params_for_compatibility(params)
......
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
def self.paginate(request_context, relation)
Gitlab::Pagination::Keyset::Pager.new(request_context).paginate(relation)
end
def self.available?(request_context, relation)
order_by = request_context.page.order_by
# This is only available for Project and order-by id (asc/desc)
return false unless relation.klass == Project
return false unless order_by.size == 1 && order_by[:id]
true
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
# A Page models the pagination information for a particular page of the collection
class Page
# Default and maximum size of records for a page
DEFAULT_PAGE_SIZE = 20
attr_accessor :lower_bounds, :end_reached
attr_reader :order_by
def initialize(order_by: {}, lower_bounds: nil, per_page: DEFAULT_PAGE_SIZE, end_reached: false)
@order_by = order_by.symbolize_keys
@lower_bounds = lower_bounds&.symbolize_keys
@per_page = per_page
@end_reached = end_reached
end
# Number of records to return per page
def per_page
return DEFAULT_PAGE_SIZE if @per_page <= 0
[@per_page, DEFAULT_PAGE_SIZE].min
end
# Determine whether this page indicates the end of the collection
def end_reached?
@end_reached
end
# Construct a Page for the next page
# Uses identical order_by/per_page information for the next page
def next(lower_bounds, end_reached)
dup.tap do |next_page|
next_page.lower_bounds = lower_bounds&.symbolize_keys
next_page.end_reached = end_reached
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
class Pager
attr_reader :request
def initialize(request)
@request = request
end
def paginate(relation)
# Validate assumption: The last two columns must match the page order_by
validate_order!(relation)
# This performs the database query and retrieves records
# We retrieve one record more to check if we have data beyond this page
all_records = relation.limit(page.per_page + 1).to_a # rubocop: disable CodeReuse/ActiveRecord
records_for_page = all_records.first(page.per_page)
# If we retrieved more records than belong on this page,
# we know there's a next page
there_is_more = all_records.size > records_for_page.size
apply_headers(records_for_page.last, there_is_more)
records_for_page
end
private
def apply_headers(last_record_in_page, there_is_more)
end_reached = last_record_in_page.nil? || !there_is_more
lower_bounds = last_record_in_page&.slice(page.order_by.keys)
next_page = page.next(lower_bounds, end_reached