Commit c02d344c authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into ide-staged-changes

parents 49f198e6 32d2206b
......@@ -78,6 +78,19 @@ stages:
- mysql:latest
- redis:alpine
.rails5-variables: &rails5-variables
script:
- export RAILS5=${RAILS5}
- export BUNDLE_GEMFILE=${BUNDLE_GEMFILE}
.rails5: &rails5
allow_failure: true
only:
- /rails5/
variables:
BUNDLE_GEMFILE: "Gemfile.rails5"
RAILS5: "true"
# Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes.
# https://docs.gitlab.com/ce/development/writing_documentation.html#testing
......@@ -118,6 +131,7 @@ stages:
<<: *dedicated-runner
<<: *except-docs-and-qa
<<: *pull-cache
<<: *rails5-variables
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
......@@ -148,14 +162,23 @@ stages:
<<: *rspec-metadata
<<: *use-pg
.rspec-metadata-pg-rails5: &rspec-metadata-pg-rails5
<<: *rspec-metadata-pg
<<: *rails5
.rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata
<<: *use-mysql
.rspec-metadata-mysql-rails5: &rspec-metadata-mysql-rails5
<<: *rspec-metadata-mysql
<<: *rails5
.spinach-metadata: &spinach-metadata
<<: *dedicated-runner
<<: *except-docs-and-qa
<<: *pull-cache
<<: *rails5-variables
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
......@@ -179,10 +202,18 @@ stages:
<<: *spinach-metadata
<<: *use-pg
.spinach-metadata-pg-rails5: &spinach-metadata-pg-rails5
<<: *spinach-metadata-pg
<<: *rails5
.spinach-metadata-mysql: &spinach-metadata-mysql
<<: *spinach-metadata
<<: *use-mysql
.spinach-metadata-mysql-rails5: &spinach-metadata-mysql-rails5
<<: *spinach-metadata-mysql
<<: *rails5
.only-canonical-masters: &only-canonical-masters
only:
- master@gitlab-org/gitlab-ce
......@@ -266,12 +297,13 @@ package-and-qa:
when: manual
variables:
GIT_STRATEGY: none
retry: 0
before_script:
# We need to download the script rather than clone the repo since the
# package-and-qa job will not be able to run when the branch gets
# deleted (when merging the MR).
- apk add --update openssl
- wget https://gitlab.com/gitlab-org/gitlab-ce/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus
- wget https://gitlab.com/$CI_PROJECT_PATH/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus
- chmod 755 trigger-build-omnibus
script:
- ./trigger-build-omnibus
......@@ -467,6 +499,70 @@ spinach-pg 1 2: *spinach-metadata-pg
spinach-mysql 0 2: *spinach-metadata-mysql
spinach-mysql 1 2: *spinach-metadata-mysql
rspec-pg-rails5 0 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 1 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 2 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 3 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 4 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 5 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 6 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 7 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 8 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 9 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 10 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 11 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 12 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 13 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 14 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 15 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 16 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 17 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 18 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 19 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 20 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 21 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 22 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 23 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 24 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 25 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 26 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 27 28: *rspec-metadata-pg-rails5
rspec-mysql-rails5 0 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 1 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 2 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 3 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 4 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 5 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 6 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 7 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 8 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 9 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 10 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 11 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 12 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 13 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 14 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 15 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 16 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 17 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 18 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 19 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 20 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 21 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 22 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 23 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 24 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 25 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 26 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 27 28: *rspec-metadata-mysql-rails5
spinach-pg-rails5 0 2: *spinach-metadata-pg-rails5
spinach-pg-rails5 1 2: *spinach-metadata-pg-rails5
spinach-mysql-rails5 0 2: *spinach-metadata-mysql-rails5
spinach-mysql-rails5 1 2: *spinach-metadata-mysql-rails5
static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job
dependencies:
......@@ -617,21 +713,26 @@ karma:
codequality:
<<: *dedicated-no-docs-no-db-pull-cache-job
image: docker:latest
image: docker:stable
allow_failure: true
# gitlab-org runners set `privileged: false` but we need to have it set to true
# since we're using Docker in Docker
tags: []
before_script: []
services:
- docker:dind
variables:
SETUP_DB: "false"
DOCKER_DRIVER: overlay2
CODECLIMATE_FORMAT: json
cache: {}
dependencies: []
script:
- apk update && apk add jq
- ./scripts/codequality analyze -f json > raw_codeclimate.json || true
# The following line keeps only the fields used in the MR widget, reducing the JSON artifact size
- jq -c 'map({check_name,description,fingerprint,location})' raw_codeclimate.json > codeclimate.json
# Get the custom rubocop codeclimate image (https://gitlab.com/gitlab-org/codeclimate-rubocop/wikis/home)
- docker pull dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1
- docker tag dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1 codeclimate/codeclimate-rubocop:gitlab-codeclimate-rubocop-0-52-1
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts:
paths: [codeclimate.json]
expire_in: 1 week
......
......@@ -33,13 +33,16 @@ When removing columns, tables, indexes or other structures:
## General Checklist
- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html)
- [ ] API support added
- [ ] Tests added for this feature/bug
- Review
- [ ] Has been reviewed by Backend
- [ ] Has been reviewed by Database
- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
- [ ] Internationalization required/considered
- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan
- [ ] End-to-end tests pass (`package-qa` manual pipeline job)
......@@ -191,7 +191,6 @@ entry.
- Enable privileged mode for GitLab Runner. !17528
- Expose GITLAB_FEATURES as CI/CD variable (fixes #40994).
- Upgrade GitLab Workhorse to 4.0.0.
- Allow CI/CD Jobs being grouped on version strings.
- Add discussions API for Issues and Snippets.
- Add one group board to Libre.
- Add support for filtering by source and target branch to merge requests API.
......
......@@ -384,7 +384,7 @@ group :test do
gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2'
gem 'test_after_commit', '~> 1.1'
gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0.
gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.0.5'
gem 'test-prof', '~> 0.2.5'
......
......@@ -60,7 +60,7 @@ GEM
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
asciidoctor (1.5.6.1)
asciidoctor-plantuml (0.0.7)
asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5)
asset_sync (2.2.0)
activemodel (>= 4.1.0)
......@@ -97,7 +97,7 @@ GEM
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
bootstrap_form (2.7.0)
brakeman (3.6.2)
brakeman (4.2.1)
browser (2.5.3)
builder (3.2.3)
bullet (5.5.1)
......@@ -144,6 +144,7 @@ GEM
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.3)
creole (0.5.0)
css_parser (1.6.0)
addressable
......@@ -244,10 +245,11 @@ GEM
builder
excon (~> 0.58)
formatador (~> 0.2)
fog-google (0.6.0)
fog-google (1.3.3)
fog-core
fog-json
fog-xml
google-api-client (~> 0.19.1)
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
......@@ -289,7 +291,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.88.0)
gitaly-proto (0.91.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
......@@ -398,7 +400,7 @@ GEM
hipchat (1.5.4)
httparty
mimemagic
html-pipeline (2.6.0)
html-pipeline (2.7.1)
activesupport (>= 2)
nokogiri (>= 1.4)
html2text (0.2.1)
......@@ -484,7 +486,8 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.0.3)
loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.0)
mini_mime (>= 0.1.1)
......@@ -527,8 +530,8 @@ GEM
omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.2)
omniauth-oauth2 (~> 1.1)
omniauth-auth0 (2.0.0)
omniauth-oauth2 (~> 1.4)
omniauth-authentiq (0.3.1)
omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.9)
......@@ -551,6 +554,9 @@ GEM
jwt (>= 1.5)
omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5)
omniauth-jwt (0.0.2)
jwt
omniauth (~> 1.1)
omniauth-kerberos (0.3.0)
omniauth-multipassword
timfel-krb5-auth (~> 0.8)
......@@ -569,9 +575,9 @@ GEM
ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
json (~> 1.3)
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
omniauth_crowd (2.2.3)
activesupport
nokogiri (>= 1.4.4)
......@@ -808,7 +814,7 @@ GEM
rubyzip (1.2.1)
rufus-scheduler (3.4.2)
et-orbi (~> 1.0)
rugged (0.26.0)
rugged (0.27.0)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
......@@ -907,8 +913,6 @@ GEM
sysexits (1.2.0)
temple (0.7.7)
test-prof (0.2.5)
test_after_commit (1.1.0)
activerecord (>= 3.2)
text (1.3.1)
thin (1.7.2)
daemons (~> 1.0, >= 1.0.9)
......@@ -995,8 +999,8 @@ DEPENDENCIES
akismet (~> 2.0)
allocations (~> 1.0)
asana (~> 0.6.0)
asciidoctor (~> 1.5.2)
asciidoctor-plantuml (= 0.0.7)
asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0)
......@@ -1009,7 +1013,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0)
brakeman (~> 4.2)
browser (~> 2.2)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
......@@ -1044,9 +1048,9 @@ DEPENDENCIES
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0)
fog-aws (~> 2.0)
fog-aws (~> 2.0.1)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-google (~> 1.3.3)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
......@@ -1058,7 +1062,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.88.0)
gitaly-proto (~> 0.91.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
......@@ -1080,7 +1084,7 @@ DEPENDENCIES
hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
html-pipeline (~> 2.6.0)
html-pipeline (~> 2.7.1)
html2text
httparty (~> 0.13.3)
influxdb (~> 0.2)
......@@ -1095,7 +1099,7 @@ DEPENDENCIES
license_finder (~> 3.1)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.0.3)
loofah (~> 2.2)
mail_room (~> 0.9.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
......@@ -1107,19 +1111,20 @@ DEPENDENCIES
oauth2 (~> 1.4)
octokit (~> 4.8)
omniauth (~> 1.8)
omniauth-auth0 (~> 1.4.1)
omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.1)
omniauth-azure-oauth2 (~> 0.0.9)
omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.2)
omniauth-google-oauth2 (~> 0.5.3)
omniauth-jwt (~> 0.0.2)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
......@@ -1169,7 +1174,7 @@ DEPENDENCIES
ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
rugged (~> 0.26.0)
rugged (~> 0.27)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
......@@ -1197,7 +1202,6 @@ DEPENDENCIES
state_machines-activerecord (~> 0.5.1)
sys-filesystem (~> 1.1.6)
test-prof (~> 0.2.5)
test_after_commit (~> 1.1)
thin (~> 1.7.0)
timecop (~> 0.8.0)
toml-rb (~> 1.0.0)
......
......@@ -4,7 +4,7 @@ import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import { __ } from './locale';
import { isInIssuePage, isInMRPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils';
import { isInIssuePage, isInMRPage, isInEpicPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
......@@ -300,7 +300,7 @@ class AwardsHandler {
}
isInVueNoteablePage() {
return isInIssuePage() || this.isVueMRDiscussions();
return isInIssuePage() || isInEpicPage() || this.isVueMRDiscussions();
}
getVotesBlock() {
......
......@@ -19,7 +19,7 @@ export default class BoardService {
}
static generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
}
all() {
......
......@@ -26,8 +26,8 @@ export default class FilteredSearchDropdownManager {
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.groupsOnly = isGroup;
this.groupAncestor = isGroupAncestor;
this.isGroupDecendent = isGroupDecendent;
this.includeAncestorGroups = isGroupAncestor;
this.includeDescendantGroups = isGroupDecendent;
this.setupMapping();
......@@ -108,7 +108,19 @@ export default class FilteredSearchDropdownManager {
}
getLabelsEndpoint() {
const endpoint = `${this.baseEndpoint}/labels.json`;
let endpoint = `${this.baseEndpoint}/labels.json?`;
if (this.groupsOnly) {
endpoint = `${endpoint}only_group_labels=true&`;
}
if (this.includeAncestorGroups) {
endpoint = `${endpoint}include_ancestor_groups=true&`;
}
if (this.includeDescendantGroups) {
endpoint = `${endpoint}include_descendant_groups=true`;
}
return endpoint;
}
......
......@@ -21,7 +21,7 @@ export default class FilteredSearchManager {
constructor({
page,
isGroup = false,
isGroupAncestor = false,
isGroupAncestor = true,
isGroupDecendent = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
......@@ -86,6 +86,7 @@ export default class FilteredSearchManager {
page: this.page,
isGroup: this.isGroup,
isGroupAncestor: this.isGroupAncestor,
isGroupDecendent: this.isGroupDecendent,
filteredSearchTokenKeys: this.filteredSearchTokenKeys,
});
......
......@@ -3,7 +3,6 @@ import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import StageButton from './stage_button.vue';
import UnstageButton from './unstage_button.vue';
import router from '../../ide_router';
export default {
components: {
......@@ -26,17 +25,17 @@ export default {
return this.file.tempFile ? 'file-addition' : 'file-modified';
},
iconClass() {
return `multi-file-${
this.file.tempFile ? 'addition' : 'modified'
} append-right-8`;
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
},
},
methods: {
...mapActions(['updateViewer', 'stageChange', 'unstageChange']),
...mapActions(['discardFileChanges', 'updateViewer', 'openPendingTab']),
openFileInEditor() {
this.updateViewer('diff');
router.push(`/project${this.file.url}`);
return this.openPendingTab(this.file).then(changeViewer => {
if (changeViewer) {
this.updateViewer('diff');
}
});
},
fileAction() {
if (this.file.staged) {
......
......@@ -60,6 +60,7 @@ export default {
v-if="activeFile"
>
<repo-tabs
:active-file="activeFile"
:files="openFiles"
:viewer="viewer"
:has-changes="hasChanges"
......
......@@ -21,7 +21,8 @@ export default {
},
watch: {
file(oldVal, newVal) {
if (newVal.path !== this.file.path) {
// Compare key to allow for files opened in review mode to be cached differently
if (newVal.key !== this.file.key) {
this.initMonaco();
}
},
......@@ -70,7 +71,7 @@ export default {
})
.then(() => {
const viewerPromise = this.delayViewerUpdated
? this.updateViewer('editor')
? this.updateViewer(this.file.pending ? 'diff' : 'editor')
: Promise.resolve();
return viewerPromise;
......
......@@ -62,11 +62,7 @@ export default {
this.toggleTreeOpen(this.file.path);
}
const delayPromise = this.file.changed
? Promise.resolve()
: this.updateDelayViewerUpdated(true);
return delayPromise.then(() => {
return this.updateDelayViewerUpdated(true).then(() => {
router.push(`/project${this.file.url}`);
});
},
......
<script>
import { mapActions } from 'vuex';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import icon from '~/vue_shared/components/icon.vue';
import fileStatusIcon from './repo_file_status_icon.vue';
import changedFileIcon from './changed_file_icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import FileStatusIcon from './repo_file_status_icon.vue';
import ChangedFileIcon from './changed_file_icon.vue';
export default {
components: {
fileStatusIcon,
fileIcon,
icon,
changedFileIcon,
FileStatusIcon,
FileIcon,
Icon,
ChangedFileIcon,
},
props: {
tab: {
......@@ -32,7 +32,7 @@ export default {
return `Close ${this.tab.name}`;
},
showChangedIcon() {
return this.fileHasChanged ? !this.tabMouseOver : false;
return this.tab.changed ? !this.tabMouseOver : false;
},
fileHasChanged() {
return this.tab.changed || this.tab.tempFile || this.tab.staged;
......@@ -40,9 +40,15 @@ export default {
},
methods: {
...mapActions(['closeFile']),
...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
clickFile(tab) {
this.$router.push(`/project${tab.url}`);
this.updateDelayViewerUpdated(true);
if (tab.pending) {
this.openPendingTab(tab);
} else {
this.$router.push(`/project${tab.url}`);
}
},
mouseOverTab() {
if (this.fileHasChanged) {
......@@ -67,7 +73,7 @@ export default {
<button
type="button"
class="multi-file-tab-close"
@click.stop.prevent="closeFile(tab.path)"
@click.stop.prevent="closeFile(tab)"
:aria-label="closeLabel"
>
<icon
......@@ -83,7 +89,9 @@ export default {
<div
class="multi-file-tab"
:class="{active : tab.active }"
:class="{
active: tab.active
}"
:title="tab.url"
>
<file-icon
......
......@@ -2,6 +2,7 @@
import { mapActions } from 'vuex';
import RepoTab from './repo_tab.vue';
import EditorMode from './editor_mode_dropdown.vue';
import router from '../ide_router';
export default {
components: {
......@@ -9,6 +10,10 @@ export default {
EditorMode,
},
props: {
activeFile: {
type: Object,
required: true,
},
files: {
type: Array,
required: true,
......@@ -38,7 +43,18 @@ export default {
this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
},
methods: {
...mapActions(['updateViewer']),
...mapActions(['updateViewer', 'removePendingTab']),
openFileViewer(viewer) {
this.updateViewer(viewer);
if (this.activeFile.pending) {
return this.removePendingTab(this.activeFile).then(() => {
router.push(`/project${this.activeFile.url}`);
});
}
return null;
},
},
};
</script>
......@@ -60,7 +76,7 @@ export default {
:show-shadow="showShadow"
:has-changes="hasChanges"
:merge-request-id="mergeRequestId"
@click="updateViewer"
@click="openFileViewer"
/>
</div>
</template>
......@@ -77,7 +77,11 @@ router.beforeEach((to, from, next) => {
if (to.params[0]) {
const path =
to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0];
const treeEntry = store.state.entries[path];
const treeEntryKey = Object.keys(store.state.entries).find(
key => key === path && !store.state.entries[key].pending,
);
const treeEntry = store.state.entries[treeEntryKey];
if (treeEntry) {
store.dispatch('handleTreeEntryAction', treeEntry);
}
......
......@@ -13,12 +13,12 @@ export default class Model {
(this.originalModel = this.monaco.editor.createModel(
this.file.raw,
undefined,
new this.monaco.Uri(null, null, `original/${this.file.path}`),
new this.monaco.Uri(null, null, `original/${this.file.key}`),
)),
(this.model = this.monaco.editor.createModel(
this.content,
undefined,
new this.monaco.Uri(null, null, this.file.path),
new this.monaco.Uri(null, null, this.file.key),
)),
);
if (this.file.mrChange) {
......@@ -36,7 +36,7 @@ export default class Model {
this.updateContent = this.updateContent.bind(this);
this.dispose = this.dispose.bind(this);
eventHub.$on(`editor.update.model.dispose.${this.file.path}`, this.dispose);
eventHub.$on(`editor.update.model.dispose.${this.file.key}`, this.dispose);
eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent);
}
......@@ -53,7 +53,7 @@ export default class Model {
}
get path() {
return this.file.path;
return this.file.key;
}
getModel() {
......@@ -91,7 +91,7 @@ export default class Model {
this.disposable.dispose();
this.events.clear();
eventHub.$off(`editor.update.model.dispose.${this.file.path}`, this.dispose);
eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose);
eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent);
}
}
......@@ -9,17 +9,17 @@ export default class ModelManager {
this.models = new Map();
}
hasCachedModel(path) {
return this.models.has(path);
hasCachedModel(key) {
return this.models.has(key);
}
getModel(path) {
return this.models.get(path);
getModel(key) {
return this.models.get(key);
}
addModel(file) {
if (this.hasCachedModel(file.path)) {
return this.getModel(file.path);
if (this.hasCachedModel(file.key)) {
return this.getModel(file.key);
}
const model = new Model(this.monaco, file);
......@@ -27,7 +27,7 @@ export default class ModelManager {
this.disposable.add(model);
eventHub.$on(
`editor.update.model.dispose.${file.path}`,
`editor.update.model.dispose.${file.key}`,
this.removeCachedModel.bind(this, file),
);
......@@ -35,12 +35,9 @@ export default class ModelManager {
}
removeCachedModel(file) {
this.models.delete(file.path);
this.models.delete(file.key);
eventHub.$off(
`editor.update.model.dispose.${file.path}`,
this.removeCachedModel,
);
eventHub.$off(`editor.update.model.dispose.${file.key}`, this.removeCachedModel);
}
dispose() {
......
......@@ -22,7 +22,7 @@ export const discardAllChanges = ({ state, commit, dispatch }) => {
};
export const closeAllFiles = ({ state, dispatch }) => {
state.openFiles.forEach(file => dispatch('closeFile', file.path));
state.openFiles.forEach(file => dispatch('closeFile', file));
};
export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
......
......@@ -6,24 +6,34 @@ import * as types from '../mutation_types';
import router from '../../ide_router';
import { setPageTitle } from '../utils';
export const closeFile = ({ commit, state, getters, dispatch }, path) => {
const indexOfClosedFile = state.openFiles.findIndex(f => f.path === path);
const file = state.entries[path];
export const closeFile = ({ commit, state, dispatch }, file) => {
const path = file.path;
const indexOfClosedFile = state.openFiles.findIndex(f => f.key === file.key);
const fileWasActive = file.active;
commit(types.TOGGLE_FILE_OPEN, path);
commit(types.SET_FILE_ACTIVE, { path, active: false });
if (file.pending) {
commit(types.REMOVE_PENDING_TAB, file);
} else {
commit(types.TOGGLE_FILE_OPEN, path);
commit(types.SET_FILE_ACTIVE, { path, active: false });
}
if (state.openFiles.length > 0 && fileWasActive) {
const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
const nextFileToOpen = state.entries[state.openFiles[nextIndexToOpen].path];
router.push(`/project${nextFileToOpen.url}`);
const nextFileToOpen = state.openFiles[nextIndexToOpen];
if (nextFileToOpen.pending) {
dispatch('updateViewer', 'diff');
dispatch('openPendingTab', nextFileToOpen);
} else {
dispatch('updateDelayViewerUpdated', true);
router.push(`/project${nextFileToOpen.url}`);
}
} else if (!state.openFiles.length) {
router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
}
eventHub.$emit(`editor.update.model.dispose.${file.path}`);
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
};
export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
......@@ -162,3 +172,23 @@ export const stageChange = ({ commit }, path) => {
export const unstageChange = ({ commit }, path) => {
commit(types.UNSTAGE_CHANGE, path);
};
export const openPendingTab = ({ commit, getters, dispatch, state }, file) => {
if (getters.activeFile && getters.activeFile.path === file.path && state.viewer === 'diff') {
return false;
}
commit(types.ADD_PENDING_TAB, { file });
dispatch('scrollToTab');
router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
return true;
};
export const removePendingTab = ({ commit }, file) => {
commit(types.REMOVE_PENDING_TAB, file);
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
};
......@@ -55,3 +55,5 @@ export const STAGE_CHANGE = 'STAGE_CHANGE';
export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE';
export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
......@@ -5,6 +5,14 @@ export default {
Object.assign(state.entries[path], {
active,
});
if (active && !state.entries[path].pending) {
Object.assign(state, {
openFiles: state.openFiles.map(f =>
Object.assign(f, { active: f.pending ? false : f.active }),
),
});
}
},
[types.TOGGLE_FILE_OPEN](state, path) {
Object.assign(state.entries[path], {
......@@ -12,10 +20,14 @@ export default {
});
if (state.entries[path].opened) {
state.openFiles.push(state.entries[path]);
Object.assign(state, {
openFiles: state.openFiles.filter(f => f.path !== path).concat(state.entries[path]),
});
} else {
const file = state.entries[path];
Object.assign(state, {
openFiles: state.openFiles.filter(f => f.path !== path),
openFiles: state.openFiles.filter(f => f.key !== file.key),
});
}
},
......@@ -141,4 +153,37 @@ export default {
changed,
});
},
[types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) {
const pendingTab = state.openFiles.find(f => f.path === file.path && f.pending);
let openFiles = state.openFiles.map(f =>
Object.assign(f, { active: f.path === file.path, opened: false }),
);
if (!pendingTab) {
const openFile = openFiles.find(f => f.path === file.path);
openFiles = openFiles.concat(openFile ? null : file).reduce((acc, f) => {
if (!f) return acc;
if (f.path === file.path) {
return acc.concat({
...f,
active: true,
pending: true,
opened: true,
key: `${keyPrefix}-${f.key}`,
});
}
return acc.concat(f);
}, []);
}
Object.assign(state, { openFiles });
},
[types.REMOVE_PENDING_TAB](state, file) {
Object.assign(state, {
openFiles: state.openFiles.filter(f => f.key !== file.key),
});
},
};
export const dataStructure = () => ({
id: '',
// Key will contain a mixture of ID and path
// it can also contain a prefix `pending-` for files opened in review mode
key: '',
type: '',
projectId: '',
......
......@@ -45,7 +45,7 @@
return `#${this.job.runner.id}`;
},
hasTimeout() {
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== '';
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
},
timeout() {
if (this.job.metadata == null) {
......
......@@ -33,6 +33,7 @@ export const checkPageAndAction = (page, action) => {
export const isInIssuePage = () => checkPageAndAction('issues', 'show');
export const isInMRPage = () => checkPageAndAction('merge_requests', 'show');
export const isInEpicPage = () => checkPageAndAction('epics', 'show');
export const isInNoteablePage = () => isInIssuePage() || isInMRPage();
export const hasVueMRDiscussionsCookie = () => Cookies.get('vue_mr_discussions');
......
......@@ -99,6 +99,10 @@ export default {
'js-note-target-reopen': !this.isOpen,
};
},
supportQuickActions() {
// Disable quick actions support for Epics
return this.noteableType !== constants.EPIC_NOTEABLE_TYPE;
},
markdownDocsPath() {
return this.getNotesData.markdownDocsPath;
},
......@@ -355,7 +359,7 @@ Please check your network connection and try again.`;
name="note[note]"
class="note-textarea js-vue-comment-form
js-gfm-input js-autosize markdown-area js-vue-textarea"
data-supports-quick-actions="true"
:data-supports-quick-actions="supportQuickActions"
aria-label="Description"
v-model="note"
ref="textarea"
......
......@@ -50,7 +50,11 @@ export default {
...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']),
noteableType() {
// FIXME -- @fatihacet Get this from JSON data.
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE } = constants;
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants;
if (this.noteableData.noteableType === EPIC_NOTEABLE_TYPE) {
return EPIC_NOTEABLE_TYPE;
}
return this.noteableData.merge_params
? MERGE_REQUEST_NOTEABLE_TYPE
......
......@@ -10,6 +10,7 @@ export const CLOSED = 'closed';
export const EMOJI_THUMBSUP = 'thumbsup';
export const EMOJI_THUMBSDOWN = 'thumbsdown';
export const ISSUE_NOTEABLE_TYPE = 'issue';
export const EPIC_NOTEABLE_TYPE = 'epic';
export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request';
export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
export const RESOLVE_NOTE_METHOD_NAME = 'post';
......@@ -12,8 +12,11 @@ document.addEventListener(
data() {
const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData);
const noteableData = JSON.parse(notesDataset.noteableData);
let currentUserData = {};
noteableData.noteableType = notesDataset.noteableType;
if (parsedUserData) {
currentUserData = {
id: parsedUserData.id,
......@@ -25,7 +28,7 @@ document.addEventListener(
}
return {
noteableData: JSON.parse(notesDataset.noteableData),
noteableData,
currentUserData,
notesData: JSON.parse(notesDataset.notesData),
};
......
......@@ -14,6 +14,8 @@ export default {
return constants.MERGE_REQUEST_NOTEABLE_TYPE;
case 'Issue':
return constants.ISSUE_NOTEABLE_TYPE;
case 'Epic':
return constants.EPIC_NOTEABLE_TYPE;
default:
return '';
}
......
......@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
});
projectSelect();
});
......@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
isGroupDecendent: true,
});
projectSelect();
});
......@@ -706,8 +706,8 @@ button.mini-pipeline-graph-dropdown-toggle {
// dropdown content for big and mini pipeline
.big-pipeline-graph-dropdown-menu,
.mini-pipeline-graph-dropdown-menu {
width: 195px;
max-width: 195px;
width: 240px;
max-width: 240px;
.scrollable-menu {
padding: 0;
......@@ -750,7 +750,7 @@ button.mini-pipeline-graph-dropdown-toggle {
height: #{$ci-action-icon-size - 6};
left: -3px;
position: relative;
top: -2px;
top: -1px;
&.icon-action-stop,
&.icon-action-cancel {
......@@ -931,13 +931,11 @@ button.mini-pipeline-graph-dropdown-toggle {
*/
&.dropdown-menu {
transform: translate(-80%, 0);
min-width: 150px;
@media(min-width: $screen-md-min) {
transform: translate(-50%, 0);
right: auto;
left: 50%;
min-width: 240px;
}
}
}
......
......@@ -5,7 +5,7 @@ class Admin::GroupsController < Admin::ApplicationController
def index
@groups = Group.with_statistics.with_route
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.sort_by_attribute(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page])
end
......
......@@ -4,7 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
def index
@users = User.order_name_asc.filter(params[:filter])
@users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present?
@users = @users.sort(@sort = params[:sort])
@users = @users.sort_by_attribute(@sort = params[:sort])
@users = @users.page(params[:page])
end
......
......@@ -229,10 +229,6 @@ class ApplicationController < ActionController::Base
@event_filter ||= EventFilter.new(filters)
end
def gitlab_ldap_access(&block)
Gitlab::Auth::LDAP::Access.open { |access| yield(access) }
end
# JSON for infinite scroll via Pager object
def pager_json(partial, count, locals = {})
html = render_to_string(
......
......@@ -14,7 +14,7 @@ module GroupTree
end
@groups = @groups.with_selects_for_list(archived: params[:archived])
.sort(@sort = params[:sort])
.sort_by_attribute(@sort = params[:sort])
.page(params[:page])
respond_to do |format|
......
......@@ -88,11 +88,15 @@ module IssuableActions
discussions = Discussion.build_collection(notes, issuable)
render json: DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user).represent(discussions, context: self)
render json: discussion_serializer.represent(discussions, context: self)
end
private
def discussion_serializer
DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user, note_entity: ProjectNoteEntity)
end
def recaptcha_check_if_spammable(should_redirect = true, &block)
return yield unless issuable.is_a? Spammable
......
......@@ -212,7 +212,7 @@ module NotesActions
end
def note_serializer
NoteSerializer.new(project: project, noteable: noteable, current_user: current_user)
ProjectNoteSerializer.new(project: project, noteable: noteable, current_user: current_user)
end
def note_project
......
......@@ -17,7 +17,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = GroupMembersFinder.new(@group).execute
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort)
@members = @members.sort_by_attribute(@sort)
@members = @members.page(params[:page]).per(50)
@members = present_members(@members.includes(:user))
......
......@@ -51,7 +51,7 @@ class ProfilesController < Profiles::ApplicationController
end
def update_username
result = Users::UpdateService.new(current_user, user: @user, username: user_params[:username]).execute
result = Users::UpdateService.new(current_user, user: @user, username: username_param).execute
options = if result[:status] == :success
{ notice: "Username successfully changed" }
......@@ -72,6 +72,10 @@ class ProfilesController < Profiles::ApplicationController
return render_404 unless @user.can_change_username?
end
def username_param
@username_param ||= user_params.require(:username)
end
def user_params
@user_params ||= params.require(:user).permit(
:avatar,
......
......@@ -22,9 +22,13 @@ class Projects::BranchesController < Projects::ApplicationController
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
# n+1: https://gitlab.com/gitlab-org/gitaly/issues/992
Gitlab::GitalyClient.allow_n_plus_1_calls do
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end
end
render
......
......@@ -43,7 +43,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
def render_json_with_discussions_serializer
render json:
DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user)
DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user, note_entity: ProjectNoteEntity)
.represent(discussion, context: self)
end
......
......@@ -150,7 +150,8 @@ class Projects::LabelsController < Projects::ApplicationController
end
def find_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
@available_labels ||=
LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups]).execute
end
def authorize_admin_labels!
......
......@@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def existing_oids
@existing_oids ||= begin
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end
end
......
......@@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def index
@sort = params[:sort] || 'due_date_asc'
@milestones = milestones.sort(@sort)
@milestones = milestones.sort_by_attribute(@sort)
respond_to do |format|
format.html do
......
......@@ -21,7 +21,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end
@project_members = present_members(@project_members.sort(@sort).page(params[:page]))
@project_members = present_members(@project_members.sort_by_attribute(@sort).page(params[:page]))
@requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
@project_member = @project.project_members.new
end
......
......@@ -16,6 +16,10 @@ module Projects
@protected_tags = @project.protected_tags.order(:name).page(params[:page])
@protected_branch = @project.protected_branches.new
@protected_tag = @project.protected_tags.new
@protected_branches_count = @protected_branches.reduce(0) { |sum, branch| sum + branch.matching(@project.repository.branches).size }
@protected_tags_count = @protected_tags.reduce(0) { |sum, tag| sum + tag.matching(@project.repository.tags).size }
load_gon_index
end
......
......@@ -62,6 +62,6 @@ class Admin::ProjectsFinder
def sort(items)
sort = params.fetch(:sort) { 'latest_activity_desc' }
items.sort(sort)
items.sort_by_attribute(sort)
end
end
......@@ -337,7 +337,7 @@ class IssuableFinder
def sort(items)
# Ensure we always have an explicit sort order (instead of inheriting
# multiple orders when combining ActiveRecord::Relation objects).
params[:sort] ? items.sort(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
end
def by_assignee(items)
......
......@@ -28,9 +28,10 @@ class LabelsFinder < UnionFinder
if project
if project.group.present?
labels_table = Label.arel_table
group_ids = group_ids_for(project.group)
label_ids << Label.where(
labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].eq(project.group.id)).or(
labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].in(group_ids)).or(
labels_table[:type].eq('ProjectLabel').and(labels_table[:project_id].eq(project.id))
)
)
......@@ -38,11 +39,14 @@ class LabelsFinder < UnionFinder
label_ids << project.labels
end
end
elsif only_group_labels?
label_ids << Label.where(group_id: group_ids)
else
if group?
group = Group.find(params[:group_id])
label_ids << Label.where(group_id: group_ids_for(group))
end
label_ids << Label.where(group_id: projects.group_ids)
label_ids << Label.where(project_id: projects.select(:id))
label_ids << Label.where(project_id: projects.select(:id)) unless only_group_labels?
end
label_ids
......@@ -59,22 +63,33 @@ class LabelsFinder < UnionFinder
items.where(title: title)
end
def group_ids
# Gets redacted array of group ids
# which can include the ancestors and descendants of the requested group.
def group_ids_for(group)
strong_memoize(:group_ids) do
groups_user_can_read_labels(groups_to_include).map(&:id)
groups = groups_to_include(group)
groups_user_can_read_labels(groups).map(&:id)
end
end
def groups_to_include
group = Group.find(params[:group_id])
def groups_to_include(group)
groups = [group]
groups += group.ancestors if params[:include_ancestor_groups].present?
groups += group.descendants if params[:include_descendant_groups].present?
groups += group.ancestors if include_ancestor_groups?
groups += group.descendants if include_descendant_groups?
groups
end
def include_ancestor_groups?
params[:include_ancestor_groups]
end
def include_descendant_groups?
params[:include_descendant_groups]
end
def group?
params[:group_id].present?
end
......
......@@ -124,7 +124,7 @@ class ProjectsFinder < UnionFinder
end
def sort(items)
params[:sort].present? ? items.sort(params[:sort]) : items.order_id_desc
params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
end
def by_archived(projects)
......
......@@ -119,7 +119,7 @@ class TodosFinder
end
def sort(items)
params[:sort] ? items.sort(params[:sort]) : items.order_id_desc
params[:sort] ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
end
def by_action(items)
......
......@@ -53,10 +53,12 @@ module BoardsHelper
end
def board_list_data
include_descendant_groups = @group&.present?
{
toggle: "dropdown",
list_labels_path: labels_filter_path(true),
labels: labels_filter_path(true),
list_labels_path: labels_filter_path(true, include_ancestor_groups: true),
labels: labels_filter_path(true, include_descendant_groups: include_descendant_groups),
labels_endpoint: @labels_endpoint,
namespace_path: @namespace_path,
project_path: @project&.path,
......
......@@ -129,13 +129,17 @@ module LabelsHelper
end
end
def labels_filter_path(only_group_labels = false)
def labels_filter_path(only_group_labels = false, include_ancestor_groups: true, include_descendant_groups: false)
project = @target_project || @project
options = {}
options[:include_ancestor_groups] = include_ancestor_groups if include_ancestor_groups
options[:include_descendant_groups] = include_descendant_groups if include_descendant_groups
if project
project_labels_path(project, :json)
project_labels_path(project, :json, options)
elsif @group
options = { only_group_labels: only_group_labels } if only_group_labels
options[:only_group_labels] = only_group_labels if only_group_labels
group_labels_path(@group, :json, options)
else
dashboard_labels_path(:json)
......
......@@ -151,16 +151,17 @@ module NotesHelper
}
end
def notes_data(issuable)
discussions_path =
if issuable.is_a?(Issue)
discussions_project_issue_path(@project, issuable, format: :json)
else
discussions_project_merge_request_path(@project, issuable, format: :json)
end
def discussions_path(issuable)
if issuable.is_a?(Issue)
discussions_project_issue_path(@project, issuable, format: :json)
else
discussions_project_merge_request_path(@project, issuable, format: :json)
end
end
def notes_data(issuable)
{
discussionsPath: discussions_path,
discussionsPath: discussions_path(issuable),
registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'),
markdownDocsPath: help_page_path('user/markdown'),
......@@ -170,7 +171,6 @@ module NotesHelper
notesPath: notes_url,
totalNotes: issuable.discussions.length,
lastFetchedAt: Time.now.to_i
}.to_json
end
......
......@@ -123,7 +123,7 @@ module TreeHelper
# returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(root_path, tree)
return tree.flat_path.sub(%r{\A#{root_path}/}, '') if tree.flat_path.present?
return tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '') if tree.flat_path.present?
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir?
......
......@@ -15,6 +15,7 @@ module Emails
setup_merge_request_mail(merge_request_id, recipient_id)
@new_commits = new_commits
@existing_commits = existing_commits
@updated_by_user = User.find(updated_by_user_id)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
......
......@@ -90,6 +90,7 @@ module Ci
before_save :ensure_token
before_destroy { unscoped_project }
before_create :ensure_metadata
after_create unless: :importing? do |build|
run_after_commit { BuildHooksWorker.perform_async(build.id) }
end
......
......@@ -32,7 +32,8 @@ class Commit
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field)
context = { pipeline: :single_line, project: self.project }
pipeline = field == :description ? :commit_description : :single_line
context = { pipeline: pipeline, project: self.project }
context[:author] = self.author if self.author
context
......
......@@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base
end
def group_name
name.to_s.gsub(%r{\d+[\.\s:/\\]+\d+\s*}, '').strip
name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
end
def failed_but_allowed?
......
......@@ -137,7 +137,7 @@ module Issuable
fuzzy_search(query, [:title, :description])
end
def sort(method, excluded_labels: [])
def sort_by_attribute(method, excluded_labels: [])
sorted =
case method.to_s
when 'downvotes_desc' then order_downvotes_desc
......
......@@ -45,11 +45,11 @@ module Milestoneish
end
def sorted_issues(user)
issues_visible_to_user(user).preload_associations.sort('label_priority')
issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority')
end
def sorted_merge_requests
merge_requests.sort('label_priority')
merge_requests.sort_by_attribute('label_priority')
end
def upcoming?
......
......@@ -53,7 +53,7 @@ class Group < Namespace
Gitlab::Database.postgresql?
end
def sort(method)
def sort_by_attribute(method)
if method == 'storage_size_desc'
# storage_size is a virtual column so we need to
# pass a string to avoid AR adding the table name
......
......@@ -116,7 +116,7 @@ class Issue < ActiveRecord::Base
'project_id'
end
def self.sort(method, excluded_labels: [])
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc
......
......@@ -96,7 +96,7 @@ class Member < ActiveRecord::Base
joins(:user).merge(User.search(query))
end
def sort(method)
def sort_by_attribute(method)
case method.to_s
when 'access_level_asc' then reorder(access_level: :asc)
when 'access_level_desc' then reorder(access_level: :desc)
......
......@@ -138,7 +138,7 @@ class Milestone < ActiveRecord::Base
User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).uniq
end
def self.sort(method)
def self.sort_by_attribute(method)
case method.to_s
when 'due_date_asc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
......
......@@ -379,12 +379,15 @@ class Note < ActiveRecord::Base
def expire_etag_cache
return unless noteable&.discussions_rendered_on_frontend?
key = Gitlab::Routing.url_helpers.project_noteable_notes_path(
Gitlab::EtagCaching::Store.new.touch(etag_key)
end
def etag_key
Gitlab::Routing.url_helpers.project_noteable_notes_path(
project,
target_type: noteable_type.underscore,
target_id: noteable_id
)
Gitlab::EtagCaching::Store.new.touch(key)
end
def touch(*args)
......
......@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base
Gitlab::VisibilityLevel.options
end
def sort(method)
def sort_by_attribute(method)
case method.to_s
when 'storage_size_desc'
# storage_size is a joined column so we need to
......@@ -566,9 +566,7 @@ class Project < ActiveRecord::Base
def add_import_job
job_id =
if forked?
RepositoryForkWorker.perform_async(id,
forked_from_project.repository_storage_path,
forked_from_project.disk_path)
RepositoryForkWorker.perform_async(id)
elsif gitlab_project_import?
# Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-ce/issues/26189 is solved.
RepositoryImportWorker.set(retry: false).perform_async(self.id)
......@@ -1068,6 +1066,16 @@ class Project < ActiveRecord::Base
end
end
# This will return all `lfs_objects` that are accessible to the project.
# So this might be `self.lfs_objects` if the project is not part of a fork
# network, or it is the base of the fork network.
#
# TODO: refactor this to get the correct lfs objects when implementing
# https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
def all_lfs_objects
lfs_storage_project.lfs_objects
end
def personal?
!group
end
......
......@@ -50,7 +50,7 @@ class Todo < ActiveRecord::Base
# Priority sorting isn't displayed in the dropdown, because we don't show
# milestones, but still show something if the user has a URL with that
# selected.
def sort(method)
def sort_by_attribute(method)
sorted =
case method.to_s
when 'priority', 'label_priority' then order_by_labels_priority
......
......@@ -256,7 +256,7 @@ class User < ActiveRecord::Base
end
end
def sort(method)
def sort_by_attribute(method)
order_method = method || 'id_desc'
case order_method.to_s
......
class BuildMetadataEntity < Grape::Entity
expose :timeout_human_readable do |metadata|
metadata.timeout_human_readable unless metadata.timeout.nil?
end
expose :timeout_human_readable
expose :timeout_source do |metadata|
metadata.present.timeout_source
end
......
......@@ -4,7 +4,9 @@ class DiscussionEntity < Grape::Entity
expose :id, :reply_id
expose :expanded?, as: :expanded
expose :notes, using: NoteEntity
expose :notes do |discussion, opts|
request.note_entity.represent(discussion.notes, opts)
end
expose :individual_note?, as: :individual_note
expose :resolvable?, as: :resolvable
......@@ -12,7 +14,7 @@ class DiscussionEntity < Grape::Entity
expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion|
resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id)
end
expose :resolve_with_issue_path do |discussion|
expose :resolve_with_issue_path, if: -> (d, _) { d.resolvable? } do |discussion|
new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id)
end
......
......@@ -5,10 +5,6 @@ class NoteEntity < API::Entities::Note
expose :author, using: NoteUserEntity
expose :human_access do |note|
note.project.team.human_max_access(note.author_id)
end
unexpose :note, as: :body
expose :note
......@@ -37,36 +33,10 @@ class NoteEntity < API::Entities::Note
expose :emoji_awardable?, as: :emoji_awardable
expose :award_emoji, if: -> (note, _) { note.emoji_awardable? }, using: AwardEmojiEntity
expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note|
if note.for_personal_snippet?
toggle_award_emoji_snippet_note_path(note.noteable, note)
else
toggle_award_emoji_project_note_path(note.project, note.id)
end
end
expose :report_abuse_path do |note|
new_abuse_report_path(user_id: note.author.id, ref_url: Gitlab::UrlBuilder.build(note))
end
expose :path do |note|
if note.for_personal_snippet?
snippet_note_path(note.noteable, note)
else
project_note_path(note.project, note)
end
end
expose :resolve_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
resolve_project_merge_request_discussion_path(note.project, note.noteable, note.discussion_id)
end
expose :resolve_with_issue_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
new_project_issue_path(note.project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id)
end
expose :attachment, using: NoteAttachmentEntity, if: -> (note, _) { note.attachment? }
expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note|
delete_attachment_project_note_path(note.project, note)
end
end
class NoteSerializer < BaseSerializer
entity NoteEntity
end
class ProjectNoteEntity < NoteEntity
expose :human_access do |note|
note.project.team.human_max_access(note.author_id)
end
expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note|
toggle_award_emoji_project_note_path(note.project, note.id)
end
expose :path do |note|
project_note_path(note.project, note)
end
expose :resolve_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
resolve_project_merge_request_discussion_path(note.project, note.noteable, note.discussion_id)
end
expose :resolve_with_issue_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
new_project_issue_path(note.project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id)
end
expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note|
delete_attachment_project_note_path(note.project, note)
end
end
class ProjectNoteSerializer < BaseSerializer
entity ProjectNoteEntity
end
......@@ -42,7 +42,10 @@ module Boards
)
end
attrs[:move_between_ids] = move_between_ids if move_between_ids
if move_between_ids
attrs[:move_between_ids] = move_between_ids
attrs[:board_group_id] = board.group&.id
end
attrs
end
......
......@@ -12,11 +12,15 @@ module Boards
private
def available_labels_for(board)
options = { include_ancestor_groups: true }
if board.group_board?
parent.labels
options.merge!(group_id: parent.id, only_group_labels: true)
else
LabelsFinder.new(current_user, project_id: parent.id).execute
options[:project_id] = parent.id
end
LabelsFinder.new(current_user, options).execute
end
def next_position(board)
......
......@@ -106,7 +106,7 @@ class IssuableBaseService < BaseService
end
def available_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: true).execute
end
def handle_quick_actions_on_create(issuable)
......
......@@ -55,9 +55,10 @@ module Issues
return unless params[:move_between_ids]
after_id, before_id = params.delete(:move_between_ids)
board_group_id = params.delete(:board_group_id)
issue_before = get_issue_if_allowed(issue.project, before_id) if before_id
issue_after = get_issue_if_allowed(issue.project, after_id) if after_id
issue_before = get_issue_if_allowed(before_id, board_group_id)
issue_after = get_issue_if_allowed(after_id, board_group_id)
issue.move_between(issue_before, issue_after)
end
......@@ -84,8 +85,16 @@ module Issues
private
def get_issue_if_allowed(project, id)
issue = project.issues.find(id)
def get_issue_if_allowed(id, board_group_id = nil)
return unless id
issue =
if board_group_id
IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id)
else
project.issues.find(id)
end
issue if can?(current_user, :update_issue, issue)
end
......
......@@ -21,7 +21,8 @@ module Projects
end
def labels(target = nil)
labels = LabelsFinder.new(current_user, project_id: project.id).execute.select([:color, :title])
labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true)
.execute.select([:color, :title])
return labels unless target&.respond_to?(:labels)
......
......@@ -28,7 +28,7 @@ module Projects
end
def save_services
[version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
[version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver].all?(&:save)
end
def version_saver
......@@ -55,6 +55,10 @@ module Projects
Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared)
end
def lfs_saver
Gitlab::ImportExport::LfsSaver.new(project: project, shared: @shared)
end
def cleanup_and_notify_error
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
......
......@@ -61,7 +61,7 @@ module Projects
project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: refmap)
else
gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, project.import_url)
gitlab_shell.import_repository(project.repository_storage, project.disk_path, project.import_url)
end
rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e
# Expire cache to prevent scenarios such as:
......
......@@ -31,15 +31,17 @@ module Projects
# Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public')
raise FailedToExtractError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise InvaildStateError, 'pages are outdated' unless latest?
deploy_page!(archive_public_path)
success
end
rescue InvaildStateError, FailedToExtractError => e
register_failure
rescue InvaildStateError => e
error(e.message)
rescue => e
error(e.message, false)
raise e
end
private
......@@ -50,12 +52,13 @@ module Projects
super
end
def error(message, http_status = nil)
def error(message, allow_delete_artifact = true)
register_failure
log_error("Projects::UpdatePagesService: #{message}")
@status.allow_failure = !latest?
@status.description = message
@status.drop(:script_failure)
delete_artifact!
delete_artifact! if allow_delete_artifact
super
end
......@@ -76,7 +79,7 @@ module Projects
elsif artifacts.ends_with?('.zip')
extract_zip_archive!(temp_path)
else
raise FailedToExtractError, 'unsupported artifacts format'
raise InvaildStateError, 'unsupported artifacts format'
end
end
......@@ -91,13 +94,13 @@ module Projects
end
def extract_zip_archive!(temp_path)
raise FailedToExtractError, 'missing artifacts metadata' unless build.artifacts_metadata?
raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata?
# Calculate page size after extract
public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
if public_entry.total_size > max_size
raise FailedToExtractError, "artifacts for pages are too large: #{public_entry.total_size}"
raise InvaildStateError, "artifacts for pages are too large: #{public_entry.total_size}"
end
# Requires UnZip at least 6.00 Info-ZIP.
......
......@@ -200,7 +200,7 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
available_labels = LabelsFinder.new(current_user, project_id: project.id).execute
available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
available_labels.any?
......@@ -562,7 +562,7 @@ module QuickActions
def find_labels(labels_param)
extract_references(labels_param, :label) |
LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute
LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split, include_ancestor_groups: true).execute
end
def find_label_references(labels_param)
......@@ -593,6 +593,7 @@ module QuickActions
def extract_references(arg, type)
ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze(arg, author: current_user)
ext.references(type)
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :admin_notification_email, class: 'form-control'
.help-block
Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :email_author_in_body do
= f.check_box :email_author_in_body
Include author name in notification email body
.help-block
Some email servers do not support overriding the email sender name.
Enable this option to include the name of the author of the issue,
merge request or comment in the email body instead.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :html_emails_enabled do
= f.check_box :html_emails_enabled
Enable HTML emails
.help-block
By default GitLab sends emails in HTML and plain text formats so mail
clients can choose what format to use. Disable this option if you only
want to send emails in plain text format.
= f.submit 'Save changes', class: "btn btn-success"
This diff is collapsed.
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :gitaly_timeout_default, class: 'form-control'
.help-block
Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced
for git fetch/push operations or Sidekiq jobs.
.form-group
= f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :gitaly_timeout_fast, class: 'form-control'
.help-block
Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast.
If they exceed this threshold, there may be a problem with a storage shard and 'failing fast'
can help maintain the stability of the GitLab instance.
.form-group
= f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :gitaly_timeout_medium, class: 'form-control'
.help-block
Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :throttle_unauthenticated_enabled do
= f.check_box :throttle_unauthenticated_enabled
Enable unauthenticated request rate limit
%span.help-block
Helps reduce request volume (e.g. from crawlers or abusive bots)
.form-group
= f.label :throttle_unauthenticated_requests_per_period, 'Max requests per period per IP', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_unauthenticated_requests_per_period, class: 'form-control'
.form-group
= f.label :throttle_unauthenticated_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :throttle_authenticated_api_enabled do
= f.check_box :throttle_authenticated_api_enabled
Enable authenticated API request rate limit
%span.help-block
Helps reduce request volume (e.g. from crawlers or abusive bots)
.form-group
= f.label :throttle_authenticated_api_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_api_requests_per_period, class: 'form-control'
.form-group
= f.label :throttle_authenticated_api_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :throttle_authenticated_web_enabled do
= f.check_box :throttle_authenticated_web_enabled
Enable authenticated web request rate limit
%span.help-block
Helps reduce request volume (e.g. from crawlers or abusive bots)
.form-group
= f.label :throttle_authenticated_web_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_web_requests_per_period, class: 'form-control'
.form-group
= f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :koding_enabled do
= f.check_box :koding_enabled
Enable Koding
.help-block
Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
.form-group
= f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
.help-block
Koding has integration enabled out of the box for the
%strong gitlab
team, and you need to provide that team's URL here. Learn more in the
= succeed "." do
= link_to "Koding administration documentation", help_page_path("administration/integration/koding")
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :sentry_enabled do
= f.check_box :sentry_enabled
Enable Sentry
.help-block
%p This setting requires a restart to take effect.
Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here:
%a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com
.form-group
= f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :sentry_dsn, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :clientside_sentry_enabled do
= f.check_box :clientside_sentry_enabled
Enable Clientside Sentry
.help-block
Sentry can also be used for reporting and logging clientside exceptions.
%a{ href: 'https://sentry.io/for/javascript/', target: '_blank', rel: 'noopener noreferrer' } https://sentry.io/for/javascript/
.form-group
= f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :clientside_sentry_dsn, class: 'form-control'
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :allow_local_requests_from_hooks_and_services do
= f.check_box :allow_local_requests_from_hooks_and_services
Allow requests to the local network from hooks and services
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :authorized_keys_enabled do
= f.check_box :authorized_keys_enabled
Write to "authorized_keys" file
.help-block
By default, we write to the "authorized_keys" file to support Git
over SSH without additional configuration. GitLab can be optimized
to authenticate SSH keys via the database file. Only uncheck this
if you have configured your OpenSSH server to use the
AuthorizedKeysCommand. Click on the help icon for more details.
= link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :plantuml_enabled do
= f.check_box :plantuml_enabled
Enable PlantUML
.form-group
= f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
.help-block
Allow rendering of
= link_to "PlantUML", "http://plantuml.com"
diagrams in Asciidoc documents using an external PlantUML service.
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :polling_interval_multiplier, class: 'form-control'
.help-block
Change this value to influence how frequently the GitLab UI polls for updates.
If you set the value to 2 all polling intervals are multiplied
by 2, which means that polling happens half as frequently.
The multiplier can also have a decimal value.
The default value (1) is a reasonable choice for the majority of GitLab
installations. Set to 0 to completely disable polling.
= link_to icon('question-circle'), help_page_path('administration/polling')
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :container_registry_token_expire_delay, class: 'form-control'
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.sub-section
%h4 Repository checks
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :repository_checks_enabled do
= f.check_box :repository_checks_enabled
Enable Repository Checks
.help-block
GitLab will periodically run
%a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
in all project and wiki repositories to look for silent disk corruption issues.
.form-group
.col-sm-offset-2.col-sm-10
= link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
.help-block
If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
.sub-section
%h4 Housekeeping
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :housekeeping_enabled do
= f.check_box :housekeeping_enabled
Enable automatic repository housekeeping (git repack, git gc)
.help-block
If you keep automatic housekeeping disabled for a long time Git
repository access on your GitLab server will become slower and your
repositories will use more disk space. We recommend to always leave
this enabled.
.checkbox
= f.label :housekeeping_bitmaps_enabled do
= f.check_box :housekeeping_bitmaps_enabled
Enable Git pack file bitmap creation
.help-block
Creating pack file bitmaps makes housekeeping take a little longer but
bitmaps should accelerate 'git clone' performance.
.form-group
= f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :housekeeping_incremental_repack_period, class: 'form-control'
.help-block
Number of Git pushes after which an incremental 'git repack' is run.
.form-group
= f.label :housekeeping_full_repack_period, 'Full repack period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :housekeeping_full_repack_period, class: 'form-control'
.help-block
Number of Git pushes after which a full 'git repack' is run.
.form-group
= f.label :housekeeping_gc_period, 'Git GC period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :housekeeping_gc_period, class: 'form-control'
.help-block
Number of Git pushes after which 'git gc' is run.
= f.submit 'Save changes', class: "btn btn-success"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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