Commit d363014c authored by Dennis Tang's avatar Dennis Tang

Merge remote-tracking branch 'origin/master' into 43446-new-cluster-page-tabs

# Conflicts:
#	app/controllers/projects/clusters/gcp_controller.rb
#	app/views/projects/clusters/gcp/_form.html.haml
#	app/views/projects/clusters/gcp/login.html.haml
parents b4308842 5b9edea9
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"airbnb-base",
"plugin:vue/recommended"
],
"globals": {
"__webpack_public_path__": true,
"gl": false,
"gon": false,
"localStorage": false
},
"parserOptions": {
"parser": "babel-eslint"
},
"plugins": [
"filenames",
"import",
"html",
"promise"
],
"settings": {
"html/html-extensions": [".html", ".html.raw"],
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
}
}
},
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+$"],
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
"no-mixed-operators": 0,
"space-before-function-paren": 0,
"curly": 0,
"arrow-parens": 0,
"vue/html-self-closing": [
"error",
{
"html": {
"void": "always",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}
]
}
}
---
env:
browser: true
es6: true
extends:
- airbnb-base
- plugin:vue/recommended
globals:
__webpack_public_path__: true
gl: false
gon: false
localStorage: false
parserOptions:
parser: babel-eslint
plugins:
- filenames
- import
- html
- promise
settings:
html/html-extensions:
- ".html"
- ".html.raw"
import/resolver:
webpack:
config: "./config/webpack.config.js"
rules:
filenames/match-regex:
- error
- "^[a-z0-9_]+$"
import/no-commonjs: error
no-multiple-empty-lines:
- error
- max: 1
promise/catch-or-return: error
no-underscore-dangle:
- error
- allow:
- __
- _links
no-mixed-operators: off
vue/html-self-closing:
- error
- html:
void: always
normal: never
component: always
svg: always
math: always
## Conflicting rules with prettier:
space-before-function-paren: off
curly: off
arrow-parens: off
function-paren-newline: off
object-curly-newline: off
padded-blocks: off
# Disabled for now, to make the eslint 3 -> eslint 4 update smoother
## Indent rule. We are using the old for now: https://eslint.org/docs/user-guide/migrating-to-4.0.0#indent-rewrite
indent: off
indent-legacy:
- error
- 2
- SwitchCase: 1
VariableDeclarator: 1
outerIIFEBody: 1
FunctionDeclaration:
parameters: 1
body: 1
FunctionExpression:
parameters: 1
body: 1
## Destructuring: https://eslint.org/docs/rules/prefer-destructuring
prefer-destructuring: off
## no-restricted-globals: https://eslint.org/docs/rules/no-restricted-globals
no-restricted-globals: off
## no-multi-assign: https://eslint.org/docs/rules/no-multi-assign
no-multi-assign: off
...@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git ...@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git
- gitlab-org - gitlab-org
.default-cache: &default-cache .default-cache: &default-cache
key: "ruby-2.3.7-with-yarn" key: "ruby-2.3.7-debian-stretch-with-yarn"
paths: paths:
- vendor/ruby - vendor/ruby
- .yarn-cache/ - .yarn-cache/
...@@ -550,7 +550,7 @@ static-analysis: ...@@ -550,7 +550,7 @@ static-analysis:
script: script:
- scripts/static-analysis - scripts/static-analysis
cache: cache:
key: "ruby-2.3.7-with-yarn-and-rubocop" key: "ruby-2.3.7-debian-stretch-with-yarn-and-rubocop"
paths: paths:
- vendor/ruby - vendor/ruby
- .yarn-cache/ - .yarn-cache/
...@@ -591,7 +591,7 @@ ee_compat_check: ...@@ -591,7 +591,7 @@ ee_compat_check:
except: except:
- master - master
- tags - tags
- /^[\d-]+-stable(-ee)?/ - /[\d-]+-stable(-ee)?/
- /^security-/ - /^security-/
- branches@gitlab-org/gitlab-ee - branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee - branches@gitlab/gitlab-ee
......
...@@ -2,6 +2,29 @@ ...@@ -2,6 +2,29 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.8.3 (2018-05-30)
### Fixed (4 changes)
- Replace Gitlab::REVISION with Gitlab.revision and handle installations without a .git directory. !19125
- Fix encoding of branch names on compare and new merge request page. !19143
- Fix remote mirror database inconsistencies when upgrading from EE to CE. !19196
- Fix local storage not being cleared after creating a new issue.
### Performance (1 change)
- Memoize Gitlab::Database.version.
## 10.8.2 (2018-05-28)
### Security (3 changes)
- Prevent user passwords from being changed without providing the previous password.
- Fix API to remove deploy key from project instead of deleting it entirely.
- Fixed bug that allowed importing arbitrary project attributes.
## 10.8.1 (2018-05-23) ## 10.8.1 (2018-05-23)
### Fixed (9 changes) ### Fixed (9 changes)
...@@ -193,6 +216,15 @@ entry. ...@@ -193,6 +216,15 @@ entry.
- Gitaly handles repository forks by default. - Gitaly handles repository forks by default.
## 10.7.5 (2018-05-28)
### Security (3 changes)
- Prevent user passwords from being changed without providing the previous password.
- Fix API to remove deploy key from project instead of deleting it entirely.
- Fixed bug that allowed importing arbitrary project attributes.
## 10.7.4 (2018-05-21) ## 10.7.4 (2018-05-21)
### Fixed (1 change) ### Fixed (1 change)
...@@ -457,6 +489,16 @@ entry. ...@@ -457,6 +489,16 @@ entry.
- Upgrade Gitaly to upgrade its charlock_holmes. - Upgrade Gitaly to upgrade its charlock_holmes.
## 10.6.6 (2018-05-28)
### Security (4 changes)
- Do not allow non-members to create MRs via forked projects when MRs are private.
- Prevent user passwords from being changed without providing the previous password.
- Fix API to remove deploy key from project instead of deleting it entirely.
- Fixed bug that allowed importing arbitrary project attributes.
## 10.6.5 (2018-04-24) ## 10.6.5 (2018-04-24)
### Security (1 change) ### Security (1 change)
......
...@@ -181,7 +181,7 @@ Team labels specify what team is responsible for this issue. ...@@ -181,7 +181,7 @@ Team labels specify what team is responsible for this issue.
Assigning a team label makes sure issues get the attention of the appropriate Assigning a team label makes sure issues get the attention of the appropriate
people. people.
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Quality, The current team labels are ~Distribution, ~"CI/CD", ~Discussion, ~Documentation, ~Quality,
~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX". ~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the The descriptions on the [labels page][labels-page] explain what falls under the
......
...@@ -28,7 +28,7 @@ gem 'mysql2', '~> 0.4.10', group: :mysql ...@@ -28,7 +28,7 @@ gem 'mysql2', '~> 0.4.10', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.27' gem 'rugged', '~> 0.27'
gem 'grape-route-helpers', '~> 2.1.0' gem 'grape-path-helpers', '~> 1.0'
gem 'faraday', '~> 0.12' gem 'faraday', '~> 0.12'
...@@ -133,7 +133,7 @@ gem 'gitlab-markup', '~> 1.6.2' ...@@ -133,7 +133,7 @@ gem 'gitlab-markup', '~> 1.6.2'
gem 'redcarpet', '~> 3.4' gem 'redcarpet', '~> 3.4'
gem 'commonmarker', '~> 0.17' gem 'commonmarker', '~> 0.17'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 4.2' gem 'rdoc', '~> 6.0'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
...@@ -162,7 +162,7 @@ gem 'acts-as-taggable-on', '~> 5.0' ...@@ -162,7 +162,7 @@ gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs # Background jobs
gem 'sidekiq', '~> 5.1' gem 'sidekiq', '~> 5.1'
gem 'sidekiq-cron', '~> 0.6.0' gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.5.2' gem 'redis-namespace', '~> 1.6.0'
gem 'sidekiq-limit_fetch', '~> 3.4', require: false gem 'sidekiq-limit_fetch', '~> 3.4', require: false
# Cron Parser # Cron Parser
...@@ -219,7 +219,7 @@ gem 'asana', '~> 0.6.0' ...@@ -219,7 +219,7 @@ gem 'asana', '~> 0.6.0'
gem 'ruby-fogbugz', '~> 0.2.1' gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration # Kubernetes integration
gem 'kubeclient', '~> 3.0' gem 'kubeclient', '~> 3.1.0'
# Sanitize user input # Sanitize user input
gem 'sanitize', '~> 2.0' gem 'sanitize', '~> 2.0'
...@@ -320,7 +320,7 @@ group :development, :test do ...@@ -320,7 +320,7 @@ group :development, :test do
gem 'pry-byebug', '~> 3.4.1', platform: :mri gem 'pry-byebug', '~> 3.4.1', platform: :mri
gem 'pry-rails', '~> 0.3.4' gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false gem 'awesome_print', require: false
gem 'fuubar', '~> 2.2.0' gem 'fuubar', '~> 2.2.0'
gem 'database_cleaner', '~> 1.5.0' gem 'database_cleaner', '~> 1.5.0'
...@@ -412,7 +412,7 @@ group :ed25519 do ...@@ -412,7 +412,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.99.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.100.0', require: 'gitaly'
gem 'grpc', '~> 1.11.0' gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
......
...@@ -69,7 +69,7 @@ GEM ...@@ -69,7 +69,7 @@ GEM
attr_encrypted (3.1.0) attr_encrypted (3.1.0)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.0) attr_required (1.0.0)
awesome_print (1.2.0) awesome_print (1.8.0)
axiom-types (0.1.1) axiom-types (0.1.1)
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
...@@ -168,7 +168,7 @@ GEM ...@@ -168,7 +168,7 @@ GEM
diff-lcs (1.3) diff-lcs (1.3)
diffy (3.1.0) diffy (3.1.0)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20170404) domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2) doorkeeper (4.3.2)
railties (>= 4.2) railties (>= 4.2)
...@@ -281,7 +281,7 @@ GEM ...@@ -281,7 +281,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (0.99.0) gitaly-proto (0.100.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -348,7 +348,7 @@ GEM ...@@ -348,7 +348,7 @@ GEM
signet (~> 0.7) signet (~> 0.7)
gpgme (2.0.13) gpgme (2.0.13)
mini_portile2 (~> 2.1) mini_portile2 (~> 2.1)
grape (1.0.2) grape (1.0.3)
activesupport activesupport
builder builder
mustermann-grape (~> 1.0.0) mustermann-grape (~> 1.0.0)
...@@ -358,10 +358,10 @@ GEM ...@@ -358,10 +358,10 @@ GEM
grape-entity (0.7.1) grape-entity (0.7.1)
activesupport (>= 4.0) activesupport (>= 4.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grape-route-helpers (2.1.0) grape-path-helpers (1.0.1)
activesupport activesupport (~> 4)
grape (>= 0.16.0) grape (~> 1.0)
rake rake (~> 12)
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.11.0) grpc (1.11.0)
...@@ -446,9 +446,9 @@ GEM ...@@ -446,9 +446,9 @@ GEM
knapsack (1.16.0) knapsack (1.16.0)
rake rake
timecop (>= 0.1.0) timecop (>= 0.1.0)
kubeclient (3.0.0) kubeclient (3.1.0)
http (~> 2.2.2) http (~> 2.2.2)
recursive-open-struct (~> 1.0.4) recursive-open-struct (~> 1.0, >= 1.0.4)
rest-client (~> 2.0) rest-client (~> 2.0)
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
...@@ -694,12 +694,11 @@ GEM ...@@ -694,12 +694,11 @@ GEM
ffi ffi
rbnacl-libsodium (1.0.11) rbnacl-libsodium (1.0.11)
rbnacl (>= 3.0.1) rbnacl (>= 3.0.1)
rdoc (4.2.2) rdoc (6.0.4)
json (~> 1.4)
re2 (1.1.1) re2 (1.1.1)
recaptcha (3.0.0) recaptcha (3.0.0)
json json
recursive-open-struct (1.0.5) recursive-open-struct (1.1.0)
redcarpet (3.4.0) redcarpet (3.4.0)
redis (3.3.5) redis (3.3.5)
redis-actionpack (5.0.2) redis-actionpack (5.0.2)
...@@ -709,8 +708,8 @@ GEM ...@@ -709,8 +708,8 @@ GEM
redis-activesupport (5.0.4) redis-activesupport (5.0.4)
activesupport (>= 3, < 6) activesupport (>= 3, < 6)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-namespace (1.5.2) redis-namespace (1.6.0)
redis (~> 3.0, >= 3.0.4) redis (>= 3.0.4)
redis-rack (2.0.4) redis-rack (2.0.4)
rack (>= 1.5, < 3) rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
...@@ -801,7 +800,7 @@ GEM ...@@ -801,7 +800,7 @@ GEM
rubyzip (1.2.1) rubyzip (1.2.1)
rufus-scheduler (3.4.0) rufus-scheduler (3.4.0)
et-orbi (~> 1.0) et-orbi (~> 1.0)
rugged (0.27.0) rugged (0.27.1)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -918,7 +917,7 @@ GEM ...@@ -918,7 +917,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.5) unf_ext (0.0.7.5)
unicode-display_width (1.3.0) unicode-display_width (1.3.2)
unicorn (5.1.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
...@@ -978,7 +977,7 @@ DEPENDENCIES ...@@ -978,7 +977,7 @@ DEPENDENCIES
asciidoctor-plantuml (= 0.0.8) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4) asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0) attr_encrypted (~> 3.1.0)
awesome_print (~> 1.2.0) awesome_print
babosa (~> 1.0.2) babosa (~> 1.0.2)
base32 (~> 0.3.0) base32 (~> 0.3.0)
batch-loader (~> 1.2.1) batch-loader (~> 1.2.1)
...@@ -1036,7 +1035,7 @@ DEPENDENCIES ...@@ -1036,7 +1035,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.99.0) gitaly-proto (~> 0.100.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
...@@ -1050,7 +1049,7 @@ DEPENDENCIES ...@@ -1050,7 +1049,7 @@ DEPENDENCIES
gpgme gpgme
grape (~> 1.0) grape (~> 1.0)
grape-entity (~> 0.7.1) grape-entity (~> 0.7.1)
grape-route-helpers (~> 2.1.0) grape-path-helpers (~> 1.0)
grape_logging (~> 1.7) grape_logging (~> 1.7)
grpc (~> 1.11.0) grpc (~> 1.11.0)
haml_lint (~> 0.26.0) haml_lint (~> 0.26.0)
...@@ -1068,7 +1067,7 @@ DEPENDENCIES ...@@ -1068,7 +1067,7 @@ DEPENDENCIES
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.16) knapsack (~> 1.16)
kubeclient (~> 3.0) kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 3.1) license_finder (~> 3.1)
licensee (~> 8.9) licensee (~> 8.9)
...@@ -1124,12 +1123,12 @@ DEPENDENCIES ...@@ -1124,12 +1123,12 @@ DEPENDENCIES
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
rbnacl (~> 4.0) rbnacl (~> 4.0)
rbnacl-libsodium rbnacl-libsodium
rdoc (~> 4.2) rdoc (~> 6.0)
re2 (~> 1.1.1) re2 (~> 1.1.1)
recaptcha (~> 3.0) recaptcha (~> 3.0)
redcarpet (~> 3.4) redcarpet (~> 3.4)
redis (~> 3.2) redis (~> 3.2)
redis-namespace (~> 1.5.2) redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2) redis-rails (~> 5.0.2)
request_store (~> 1.3) request_store (~> 1.3)
responders (~> 2.0) responders (~> 2.0)
......
...@@ -160,7 +160,7 @@ export default { ...@@ -160,7 +160,7 @@ export default {
@input="debouncedPreview" @input="debouncedPreview"
/> />
<span <span
class="help-block" class="form-text text-muted"
v-html="helpText" v-html="helpText"
></span> ></span>
</div> </div>
...@@ -176,7 +176,7 @@ export default { ...@@ -176,7 +176,7 @@ export default {
@input="debouncedPreview" @input="debouncedPreview"
/> />
<span <span
class="help-block" class="form-text text-muted"
v-html="helpText" v-html="helpText"
></span> ></span>
</div> </div>
......
...@@ -41,10 +41,10 @@ gl.issueBoards.ModalEmptyState = Vue.extend({ ...@@ -41,10 +41,10 @@ gl.issueBoards.ModalEmptyState = Vue.extend({
template: ` template: `
<section class="empty-state"> <section class="empty-state">
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-6 order-sm-last"> <div class="col-12 col-md-6 order-md-last">
<aside class="svg-content"><img :src="emptyStateSvg"/></aside> <aside class="svg-content"><img :src="emptyStateSvg"/></aside>
</div> </div>
<div class="col-xs-12 col-sm-6 order-sm-first"> <div class="col-12 col-md-6 order-md-first">
<div class="text-content"> <div class="text-content">
<h4>{{ contents.title }}</h4> <h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p> <p v-html="contents.content"></p>
......
/* global ListIssue */
import Vue from 'vue'; import Vue from 'vue';
import bp from '../../../breakpoints'; import bp from '../../../breakpoints';
import ModalStore from '../../stores/modal_store'; import ModalStore from '../../stores/modal_store';
...@@ -56,8 +54,11 @@ gl.issueBoards.ModalList = Vue.extend({ ...@@ -56,8 +54,11 @@ gl.issueBoards.ModalList = Vue.extend({
scrollHandler() { scrollHandler() {
const currentPage = Math.floor(this.issues.length / this.perPage); const currentPage = Math.floor(this.issues.length / this.perPage);
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage if (
&& currentPage === this.page) { this.scrollTop() > this.scrollHeight() - 100 &&
!this.loadingNewPage &&
currentPage === this.page
) {
this.loadingNewPage = true; this.loadingNewPage = true;
this.page += 1; this.page += 1;
} }
......
<script> <script>
/* global ListIssue */ import $ from 'jquery';
import _ from 'underscore';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import Api from '../../api';
import $ from 'jquery'; export default {
import _ from 'underscore'; name: 'BoardProjectSelect',
import eventHub from '../eventhub'; components: {
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; loadingIcon,
import Api from '../../api'; },
props: {
export default { groupId: {
name: 'BoardProjectSelect', type: Number,
components: { required: true,
loadingIcon, default: 0,
},
props: {
groupId: {
type: Number,
required: true,
default: 0,
},
}, },
data() { },
return { data() {
loading: true, return {
selectedProject: {}, loading: true,
}; selectedProject: {},
};
},
computed: {
selectedProjectName() {
return this.selectedProject.name || 'Select a project';
}, },
computed: { },
selectedProjectName() { mounted() {
return this.selectedProject.name || 'Select a project'; $(this.$refs.projectsDropdown).glDropdown({
filterable: true,
filterRemote: true,
search: {
fields: ['name_with_namespace'],
}, },
}, clicked: ({ $el, e }) => {
mounted() { e.preventDefault();
$(this.$refs.projectsDropdown).glDropdown({ this.selectedProject = {
filterable: true, id: $el.data('project-id'),
filterRemote: true, name: $el.data('project-name'),
search: { };
fields: ['name_with_namespace'], eventHub.$emit('setSelectedProject', this.selectedProject);
}, },
clicked: ({ $el, e }) => { selectable: true,
e.preventDefault(); data: (term, callback) => {
this.selectedProject = { this.loading = true;
id: $el.data('project-id'), return Api.groupProjects(this.groupId, term, projects => {
name: $el.data('project-name'), this.loading = false;
}; callback(projects);
eventHub.$emit('setSelectedProject', this.selectedProject); });
}, },
selectable: true, renderRow(project) {
data: (term, callback) => { return `
this.loading = true;
return Api.groupProjects(this.groupId, term, (projects) => {
this.loading = false;
callback(projects);
});
},
renderRow(project) {
return `
<li> <li>
<a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}"> <a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}">
${_.escape(project.name)} ${_.escape(project.name)}
</a> </a>
</li> </li>
`; `;
}, },
text: project => project.name, text: project => project.name,
}); });
}, },
}; };
</script> </script>
<template> <template>
......
...@@ -43,7 +43,7 @@ export default { ...@@ -43,7 +43,7 @@ export default {
return `${this.changedIcon}-solid`; return `${this.changedIcon}-solid`;
}, },
changedIconClass() { changedIconClass() {
return `multi-${this.changedIcon} pull-left`; return `multi-${this.changedIcon} float-left`;
}, },
tooltipTitle() { tooltipTitle() {
if (!this.showTooltip) return undefined; if (!this.showTooltip) return undefined;
......
...@@ -144,14 +144,14 @@ export default { ...@@ -144,14 +144,14 @@ export default {
<loading-button <loading-button
:loading="submitCommitLoading" :loading="submitCommitLoading"
:disabled="commitButtonDisabled" :disabled="commitButtonDisabled"
container-class="btn btn-success btn-sm pull-left" container-class="btn btn-success btn-sm float-left"
:label="__('Commit')" :label="__('Commit')"
@click="commitChanges" @click="commitChanges"
/> />
<button <button
v-if="!discardDraftButtonDisabled" v-if="!discardDraftButtonDisabled"
type="button" type="button"
class="btn btn-default btn-sm pull-right" class="btn btn-default btn-sm float-right"
@click="discardDraft" @click="discardDraft"
> >
{{ __('Discard draft') }} {{ __('Discard draft') }}
...@@ -159,7 +159,7 @@ export default { ...@@ -159,7 +159,7 @@ export default {
<button <button
v-else v-else
type="button" type="button"
class="btn btn-default btn-sm pull-right" class="btn btn-default btn-sm float-right"
@click="toggleIsSmall" @click="toggleIsSmall"
> >
{{ __('Collapse') }} {{ __('Collapse') }}
......
...@@ -120,7 +120,7 @@ export default { ...@@ -120,7 +120,7 @@ export default {
</ul> </ul>
<p <p
v-else v-else
class="multi-file-commit-list help-block" class="multi-file-commit-list form-text text-muted"
> >
{{ __('No changes') }} {{ __('No changes') }}
</p> </p>
......
...@@ -80,7 +80,7 @@ export default { ...@@ -80,7 +80,7 @@ export default {
{{ __('Commit Message') }} {{ __('Commit Message') }}
<span <span
v-popover="$options.popoverOptions" v-popover="$options.popoverOptions"
class="help-block prepend-left-10" class="form-text text-muted prepend-left-10"
> >
<icon <icon
name="question" name="question"
......
...@@ -72,21 +72,19 @@ export default { ...@@ -72,21 +72,19 @@ export default {
<form <form
slot="body" slot="body"
@submit.prevent="createEntryInStore" @submit.prevent="createEntryInStore"
class="form-group row append-bottom-0" class="form-group row"
> >
<fieldset class="form-group append-bottom-0"> <label class="label-light col-form-label col-sm-3">
<label class="label-light col-form-label col-sm-3 ide-new-modal-label"> {{ __('Name') }}
{{ __('Name') }} </label>
</label> <div class="col-sm-9">
<div class="col-sm-9"> <input
<input type="text"
type="text" class="form-control"
class="form-control" v-model="entryName"
v-model="entryName" ref="fieldName"
ref="fieldName" />
/> </div>
</div>
</fieldset>
</form> </form>
</deprecated-modal> </deprecated-modal>
</template> </template>
...@@ -169,7 +169,7 @@ export default { ...@@ -169,7 +169,7 @@ export default {
:show-tooltip="true" :show-tooltip="true"
:show-staged-icon="true" :show-staged-icon="true"
:force-modified-icon="true" :force-modified-icon="true"
class="pull-right" class="float-right"
/> />
</span> </span>
<new-dropdown <new-dropdown
......
/* global monaco */
import Disposable from './disposable'; import Disposable from './disposable';
import eventHub from '../../eventhub'; import eventHub from '../../eventhub';
......
...@@ -84,11 +84,11 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive ...@@ -84,11 +84,11 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive
}); });
}; };
export const setFileMrChange = ({ state, commit }, { file, mrChange }) => { export const setFileMrChange = ({ commit }, { file, mrChange }) => {
commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange }); commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange });
}; };
export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => { export const getRawFileData = ({ state, commit }, { path, baseSha }) => {
const file = state.entries[path]; const file = state.entries[path];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
service service
...@@ -156,7 +156,7 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn ...@@ -156,7 +156,7 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn
} }
}; };
export const setFileViewMode = ({ state, commit }, { file, viewMode }) => { export const setFileViewMode = ({ commit }, { file, viewMode }) => {
commit(types.SET_FILE_VIEWMODE, { file, viewMode }); commit(types.SET_FILE_VIEWMODE, { file, viewMode });
}; };
......
...@@ -3,7 +3,7 @@ import service from '../../services'; ...@@ -3,7 +3,7 @@ import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
export const getMergeRequestData = ( export const getMergeRequestData = (
{ commit, state, dispatch }, { commit, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
...@@ -32,7 +32,7 @@ export const getMergeRequestData = ( ...@@ -32,7 +32,7 @@ export const getMergeRequestData = (
}); });
export const getMergeRequestChanges = ( export const getMergeRequestChanges = (
{ commit, state, dispatch }, { commit, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
...@@ -58,7 +58,7 @@ export const getMergeRequestChanges = ( ...@@ -58,7 +58,7 @@ export const getMergeRequestChanges = (
}); });
export const getMergeRequestVersions = ( export const getMergeRequestVersions = (
{ commit, state, dispatch }, { commit, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
......
...@@ -7,10 +7,7 @@ import Poll from '../../../lib/utils/poll'; ...@@ -7,10 +7,7 @@ import Poll from '../../../lib/utils/poll';
let eTagPoll; let eTagPoll;
export const getProjectData = ( export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) =>
{ commit, state, dispatch },
{ namespace, projectId, force = false } = {},
) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.projects[`${namespace}/${projectId}`] || force) { if (!state.projects[`${namespace}/${projectId}`] || force) {
commit(types.TOGGLE_LOADING, { entry: state }); commit(types.TOGGLE_LOADING, { entry: state });
...@@ -40,10 +37,7 @@ export const getProjectData = ( ...@@ -40,10 +37,7 @@ export const getProjectData = (
} }
}); });
export const getBranchData = ( export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) =>
{ commit, state, dispatch },
{ projectId, branchId, force = false } = {},
) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if ( if (
typeof state.projects[`${projectId}`] === 'undefined' || typeof state.projects[`${projectId}`] === 'undefined' ||
...@@ -78,7 +72,7 @@ export const getBranchData = ( ...@@ -78,7 +72,7 @@ export const getBranchData = (
} }
}); });
export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId, branchId } = {}) => export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {}) =>
service service
.getBranchData(projectId, branchId) .getBranchData(projectId, branchId)
.then(({ data }) => { .then(({ data }) => {
...@@ -92,7 +86,7 @@ export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId, ...@@ -92,7 +86,7 @@ export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId,
flash(__('Error loading last commit.'), 'alert', document, null, false, true); flash(__('Error loading last commit.'), 'alert', document, null, false, true);
}); });
export const pollSuccessCallBack = ({ commit, state, dispatch }, { data }) => { export const pollSuccessCallBack = ({ commit, state }, { data }) => {
if (data.pipelines && data.pipelines.length) { if (data.pipelines && data.pipelines.length) {
const lastCommitHash = const lastCommitHash =
state.projects[state.currentProjectId].branches[state.currentBranchId].commit.id; state.projects[state.currentProjectId].branches[state.currentBranchId].commit.id;
......
...@@ -5,7 +5,7 @@ import * as types from '../mutation_types'; ...@@ -5,7 +5,7 @@ import * as types from '../mutation_types';
import { findEntry } from '../utils'; import { findEntry } from '../utils';
import FilesDecoratorWorker from '../workers/files_decorator_worker'; import FilesDecoratorWorker from '../workers/files_decorator_worker';
export const toggleTreeOpen = ({ commit, dispatch }, path) => { export const toggleTreeOpen = ({ commit }, path) => {
commit(types.TOGGLE_TREE_OPEN, path); commit(types.TOGGLE_TREE_OPEN, path);
}; };
...@@ -23,7 +23,7 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => { ...@@ -23,7 +23,7 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
} }
}; };
export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => { export const getLastCommitData = ({ state, commit, dispatch }, tree = state) => {
if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return; if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
service service
...@@ -49,7 +49,7 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s ...@@ -49,7 +49,7 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s
.catch(() => flash('Error fetching log data.', 'alert', document, null, false, true)); .catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
}; };
export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) => export const getFiles = ({ state, commit }, { projectId, branchId } = {}) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.trees[`${projectId}/${branchId}`]) { if (!state.trees[`${projectId}/${branchId}`]) {
const selectedProject = state.projects[projectId]; const selectedProject = state.projects[projectId];
......
...@@ -31,9 +31,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => { ...@@ -31,9 +31,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => {
const currentProject = rootState.projects[rootState.currentProjectId]; const currentProject = rootState.projects[rootState.currentProjectId];
const commitStats = data.stats const commitStats = data.stats
? sprintf(__('with %{additions} additions, %{deletions} deletions.'), { ? sprintf(__('with %{additions} additions, %{deletions} deletions.'), {
additions: data.stats.additions, // eslint-disable-line indent additions: data.stats.additions, // eslint-disable-line indent-legacy
deletions: data.stats.deletions, // eslint-disable-line indent deletions: data.stats.deletions, // eslint-disable-line indent-legacy
}) // eslint-disable-line indent }) // eslint-disable-line indent-legacy
: ''; : '';
const commitMsg = sprintf( const commitMsg = sprintf(
__('Your changes have been committed. Commit %{commitId} %{commitStats}'), __('Your changes have been committed. Commit %{commitId} %{commitStats}'),
...@@ -74,10 +74,7 @@ export const checkCommitStatus = ({ rootState }) => ...@@ -74,10 +74,7 @@ export const checkCommitStatus = ({ rootState }) =>
), ),
); );
export const updateFilesAfterCommit = ( export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }) => {
{ commit, dispatch, state, rootState, rootGetters },
{ data },
) => {
const selectedProject = rootState.projects[rootState.currentProjectId]; const selectedProject = rootState.projects[rootState.currentProjectId];
const lastCommit = { const lastCommit = {
commit_path: `${selectedProject.web_url}/commit/${data.id}`, commit_path: `${selectedProject.web_url}/commit/${data.id}`,
......
...@@ -30,7 +30,7 @@ export default class IssuableForm { ...@@ -30,7 +30,7 @@ export default class IssuableForm {
} }
this.initAutosave(); this.initAutosave();
this.form.on('submit:success', this.handleSubmit); this.form.on('submit', this.handleSubmit);
this.form.on('click', '.btn-cancel', this.resetAutosave); this.form.on('click', '.btn-cancel', this.resetAutosave);
this.initWip(); this.initWip();
......
...@@ -84,7 +84,7 @@ export default class Job { ...@@ -84,7 +84,7 @@ export default class Job {
If the browser does not support position sticky, it returns the position as static. If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not If the browser does support sticky, then we allow the browser to handle it, if not
then we use a polyfill then we use a polyfill
**/ */
if (this.$topBar.css('position') !== 'static') return; if (this.$topBar.css('position') !== 'static') return;
StickyFill.add(this.$topBar); StickyFill.add(this.$topBar);
......
...@@ -48,11 +48,10 @@ export default { ...@@ -48,11 +48,10 @@ export default {
return `${this.job.runner.description} (#${this.job.runner.id})`; return `${this.job.runner.description} (#${this.job.runner.id})`;
}, },
retryButtonClass() { retryButtonClass() {
let className = 'js-retry-button pull-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; let className =
'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
className += className +=
this.job.status && this.job.recoverable this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
? ' btn-primary'
: ' btn-inverted-secondary';
return className; return className;
}, },
hasTimeout() { hasTimeout() {
...@@ -104,8 +103,7 @@ export default { ...@@ -104,8 +103,7 @@ export default {
<button <button
type="button" type="button"
:aria-label="__('Toggle Sidebar')" :aria-label="__('Toggle Sidebar')"
class="btn btn-blank gutter-toggle pull-right class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle"
d-block d-sm-block d-md-none js-sidebar-build-toggle"
> >
<i <i
aria-hidden="true" aria-hidden="true"
......
/* global Build */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../flash'; import Flash from '../flash';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
...@@ -50,7 +48,8 @@ export default class JobMediator { ...@@ -50,7 +48,8 @@ export default class JobMediator {
} }
getJob() { getJob() {
return this.service.getJob() return this.service
.getJob()
.then(response => this.successCallback(response)) .then(response => this.successCallback(response))
.catch(() => this.errorCallback()); .catch(() => this.errorCallback());
} }
......
...@@ -9,7 +9,7 @@ delete window.translations; ...@@ -9,7 +9,7 @@ delete window.translations;
Translates `text` Translates `text`
@param text The text to be translated @param text The text to be translated
@returns {String} The translated text @returns {String} The translated text
**/ */
const gettext = locale.gettext.bind(locale); const gettext = locale.gettext.bind(locale);
/** /**
...@@ -21,7 +21,7 @@ const gettext = locale.gettext.bind(locale); ...@@ -21,7 +21,7 @@ const gettext = locale.gettext.bind(locale);
@param pluralText Plural text to translate (eg. '%d days') @param pluralText Plural text to translate (eg. '%d days')
@param count Number to decide which translation to use (eg. 2) @param count Number to decide which translation to use (eg. 2)
@returns {String} Translated text with the number replaced (eg. '2 days') @returns {String} Translated text with the number replaced (eg. '2 days')
**/ */
const ngettext = (text, pluralText, count) => { const ngettext = (text, pluralText, count) => {
const translated = locale.ngettext(text, pluralText, count).replace(/%d/g, count).split('|'); const translated = locale.ngettext(text, pluralText, count).replace(/%d/g, count).split('|');
...@@ -38,7 +38,7 @@ const ngettext = (text, pluralText, count) => { ...@@ -38,7 +38,7 @@ const ngettext = (text, pluralText, count) => {
(eg. 'Context') (eg. 'Context')
@param key Is the dynamic variable you want to be translated @param key Is the dynamic variable you want to be translated
@returns {String} Translated context based text @returns {String} Translated context based text
**/ */
const pgettext = (keyOrContext, key) => { const pgettext = (keyOrContext, key) => {
const normalizedKey = key ? `${keyOrContext}|${key}` : keyOrContext; const normalizedKey = key ? `${keyOrContext}|${key}` : keyOrContext;
const translated = gettext(normalizedKey).split('|'); const translated = gettext(normalizedKey).split('|');
......
...@@ -10,7 +10,7 @@ import _ from 'underscore'; ...@@ -10,7 +10,7 @@ import _ from 'underscore';
@see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf @see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf
@see https://gitlab.com/gitlab-org/gitlab-ce/issues/37992 @see https://gitlab.com/gitlab-org/gitlab-ce/issues/37992
**/ */
export default (input, parameters, escapeParameters = true) => { export default (input, parameters, escapeParameters = true) => {
let output = input; let output = input;
......
...@@ -362,7 +362,7 @@ export default class MergeRequestTabs { ...@@ -362,7 +362,7 @@ export default class MergeRequestTabs {
// //
// status - Boolean, true to show, false to hide // status - Boolean, true to show, false to hide
toggleLoading(status) { toggleLoading(status) {
$('.mr-loading-status .loading').toggleClass('hidden', status); $('.mr-loading-status .loading').toggleClass('hidden', !status);
} }
diffViewType() { diffViewType() {
...@@ -427,7 +427,7 @@ export default class MergeRequestTabs { ...@@ -427,7 +427,7 @@ export default class MergeRequestTabs {
If the browser does not support position sticky, it returns the position as static. If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not If the browser does support sticky, then we allow the browser to handle it, if not
then we default back to Bootstraps affix then we default back to Bootstraps affix
**/ */
if ($tabs.css('position') !== 'static') return; if ($tabs.css('position') !== 'static') return;
const $diffTabs = $('#diff-notes-app'); const $diffTabs = $('#diff-notes-app');
......
...@@ -12,20 +12,13 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; ...@@ -12,20 +12,13 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
let eTagPoll; let eTagPoll;
export const setNotesData = ({ commit }, data) => export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
commit(types.SET_NOTES_DATA, data); export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
export const setNoteableData = ({ commit }, data) => export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
commit(types.SET_NOTEABLE_DATA, data); export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
export const setUserData = ({ commit }, data) => export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data);
commit(types.SET_USER_DATA, data); export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
export const setLastFetchedAt = ({ commit }, data) => export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) =>
commit(types.SET_INITIAL_NOTES, data);
export const setTargetNoteHash = ({ commit }, data) =>
commit(types.SET_TARGET_NOTE_HASH, data);
export const toggleDiscussion = ({ commit }, data) =>
commit(types.TOGGLE_DISCUSSION, data);
export const fetchNotes = ({ commit }, path) => export const fetchNotes = ({ commit }, path) =>
service service
...@@ -69,20 +62,14 @@ export const createNewNote = ({ commit }, { endpoint, data }) => ...@@ -69,20 +62,14 @@ export const createNewNote = ({ commit }, { endpoint, data }) =>
return res; return res;
}); });
export const removePlaceholderNotes = ({ commit }) => export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES);
commit(types.REMOVE_PLACEHOLDER_NOTES);
export const toggleResolveNote = ( export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) =>
{ commit },
{ endpoint, isResolved, discussion },
) =>
service service
.toggleResolveNote(endpoint, isResolved) .toggleResolveNote(endpoint, isResolved)
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
const mutationType = discussion const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
? types.UPDATE_DISCUSSION
: types.UPDATE_NOTE;
commit(mutationType, res); commit(mutationType, res);
}); });
...@@ -114,7 +101,7 @@ export const reopenIssue = ({ commit, dispatch, state }) => { ...@@ -114,7 +101,7 @@ export const reopenIssue = ({ commit, dispatch, state }) => {
export const toggleStateButtonLoading = ({ commit }, value) => export const toggleStateButtonLoading = ({ commit }, value) =>
commit(types.TOGGLE_STATE_BUTTON_LOADING, value); commit(types.TOGGLE_STATE_BUTTON_LOADING, value);
export const emitStateChangedEvent = ({ commit, getters }, data) => { export const emitStateChangedEvent = ({ getters }, data) => {
const event = new CustomEvent('issuable_vue_app:change', { const event = new CustomEvent('issuable_vue_app:change', {
detail: { detail: {
data, data,
...@@ -179,10 +166,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -179,10 +166,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
loadAwardsHandler() loadAwardsHandler()
.then(awardsHandler => { .then(awardsHandler => {
awardsHandler.addAwardToEmojiBar( awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award);
votesBlock,
commandsChanges.emoji_award,
);
awardsHandler.scrollToAwards(); awardsHandler.scrollToAwards();
}) })
.catch(() => { .catch(() => {
...@@ -194,10 +178,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -194,10 +178,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
}); });
} }
if ( if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) {
commandsChanges.spend_time != null ||
commandsChanges.time_estimate != null
) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res); sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
} }
} }
...@@ -218,14 +199,8 @@ const pollSuccessCallBack = (resp, commit, state, getters) => { ...@@ -218,14 +199,8 @@ const pollSuccessCallBack = (resp, commit, state, getters) => {
resp.notes.forEach(note => { resp.notes.forEach(note => {
if (notesById[note.id]) { if (notesById[note.id]) {
commit(types.UPDATE_NOTE, note); commit(types.UPDATE_NOTE, note);
} else if ( } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
note.type === constants.DISCUSSION_NOTE || const discussion = utils.findNoteObjectById(state.notes, note.discussion_id);
note.type === constants.DIFF_NOTE
) {
const discussion = utils.findNoteObjectById(
state.notes,
note.discussion_id,
);
if (discussion) { if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
...@@ -249,11 +224,8 @@ export const poll = ({ commit, state, getters }) => { ...@@ -249,11 +224,8 @@ export const poll = ({ commit, state, getters }) => {
method: 'poll', method: 'poll',
data: state, data: state,
successCallback: resp => successCallback: resp =>
resp resp.json().then(data => pollSuccessCallBack(data, commit, state, getters)),
.json() errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
.then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () =>
Flash('Something went wrong while fetching latest comments.'),
}); });
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
...@@ -292,14 +264,11 @@ export const fetchData = ({ commit, state, getters }) => { ...@@ -292,14 +264,11 @@ export const fetchData = ({ commit, state, getters }) => {
.catch(() => Flash('Something went wrong while fetching latest comments.')); .catch(() => Flash('Something went wrong while fetching latest comments.'));
}; };
export const toggleAward = ( export const toggleAward = ({ commit, getters }, { awardName, noteId }) => {
{ commit, state, getters, dispatch },
{ awardName, noteId },
) => {
commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] }); commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] });
}; };
export const toggleAwardRequest = ({ commit, getters, dispatch }, data) => { export const toggleAwardRequest = ({ dispatch }, data) => {
const { endpoint, awardName } = data; const { endpoint, awardName } = data;
return service return service
......
import groupAvatar from '~/group_avatar'; import groupAvatar from '~/group_avatar';
import TransferDropdown from '~/groups/transfer_dropdown'; import TransferDropdown from '~/groups/transfer_dropdown';
import initConfirmDangerModal from '~/confirm_danger_modal'; import initConfirmDangerModal from '~/confirm_danger_modal';
import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
groupAvatar(); groupAvatar();
new TransferDropdown(); // eslint-disable-line no-new new TransferDropdown(); // eslint-disable-line no-new
initConfirmDangerModal(); initConfirmDangerModal();
}); });
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
});
...@@ -213,7 +213,7 @@ ...@@ -213,7 +213,7 @@
</i> </i>
</div> </div>
</div> </div>
<span class="help-block">{{ visibilityLevelDescription }}</span> <span class="form-text text-muted">{{ visibilityLevelDescription }}</span>
<label <label
v-if="visibilityLevel !== visibilityOptions.PRIVATE" v-if="visibilityLevel !== visibilityOptions.PRIVATE"
class="request-access" class="request-access"
......
import bp from '../../../breakpoints'; import bp from '../../../breakpoints';
import { slugify } from '../../../lib/utils/text_utility'; import { slugify } from '../../../lib/utils/text_utility';
import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils';
import { mergeUrlParams, redirectTo } from '../../../lib/utils/url_utility';
export default class Wikis { export default class Wikis {
constructor() { constructor() {
...@@ -28,7 +30,12 @@ export default class Wikis { ...@@ -28,7 +30,12 @@ export default class Wikis {
if (slug.length > 0) { if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path'); const wikisPath = slugInput.getAttribute('data-wikis-path');
window.location.href = `${wikisPath}/${slug}`;
// If the wiki is empty, we need to merge the current URL params to keep the "create" view.
const params = parseQueryStringIntoObject(window.location.search.substr(1));
const url = mergeUrlParams(params, `${wikisPath}/${slug}`);
redirectTo(url);
e.preventDefault(); e.preventDefault();
} }
} }
......
...@@ -5,7 +5,7 @@ import $ from 'jquery'; ...@@ -5,7 +5,7 @@ import $ from 'jquery';
* *
* Toggling this checkbox adds/removes a `remember_me` parameter to the * Toggling this checkbox adds/removes a `remember_me` parameter to the
* login buttons' href, which is passed on to the omniauth callback. * login buttons' href, which is passed on to the omniauth callback.
**/ */
export default class OAuthRememberMe { export default class OAuthRememberMe {
constructor(opts = {}) { constructor(opts = {}) {
......
...@@ -77,10 +77,9 @@ export default class UserTabs { ...@@ -77,10 +77,9 @@ export default class UserTabs {
this.action = action || this.defaultAction; this.action = action || this.defaultAction;
this.$parentEl = $(parentEl) || $(document); this.$parentEl = $(parentEl) || $(document);
this.windowLocation = window.location; this.windowLocation = window.location;
this.$parentEl.find('.nav-links a') this.$parentEl.find('.nav-links a').each((i, navLink) => {
.each((i, navLink) => { this.loaded[$(navLink).attr('data-action')] = false;
this.loaded[$(navLink).attr('data-action')] = false; });
});
this.actions = Object.keys(this.loaded); this.actions = Object.keys(this.loaded);
this.bindEvents(); this.bindEvents();
...@@ -116,8 +115,7 @@ export default class UserTabs { ...@@ -116,8 +115,7 @@ export default class UserTabs {
} }
activateTab(action) { activateTab(action) {
return this.$parentEl.find(`.nav-links .js-${action}-tab a`) return this.$parentEl.find(`.nav-links .js-${action}-tab a`).tab('show');
.tab('show');
} }
setTab(action, endpoint) { setTab(action, endpoint) {
...@@ -137,7 +135,8 @@ export default class UserTabs { ...@@ -137,7 +135,8 @@ export default class UserTabs {
loadTab(action, endpoint) { loadTab(action, endpoint) {
this.toggleLoading(true); this.toggleLoading(true);
return axios.get(endpoint) return axios
.get(endpoint)
.then(({ data }) => { .then(({ data }) => {
const tabSelector = `div#${action}`; const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html); this.$parentEl.find(tabSelector).html(data.html);
...@@ -161,10 +160,11 @@ export default class UserTabs { ...@@ -161,10 +160,11 @@ export default class UserTabs {
const utcOffset = $calendarWrap.data('utcOffset'); const utcOffset = $calendarWrap.data('utcOffset');
let utcFormatted = 'UTC'; let utcFormatted = 'UTC';
if (utcOffset !== 0) { if (utcOffset !== 0) {
utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`; utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${utcOffset / 3600}`;
} }
axios.get(calendarPath) axios
.get(calendarPath)
.then(({ data }) => { .then(({ data }) => {
$calendarWrap.html(CALENDAR_TEMPLATE); $calendarWrap.html(CALENDAR_TEMPLATE);
$calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
...@@ -180,17 +180,20 @@ export default class UserTabs { ...@@ -180,17 +180,20 @@ export default class UserTabs {
} }
toggleLoading(status) { toggleLoading(status) {
return this.$parentEl.find('.loading-status .loading') return this.$parentEl.find('.loading-status .loading').toggleClass('hidden', !status);
.toggleClass('hidden', status);
} }
setCurrentAction(source) { setCurrentAction(source) {
let newState = source; let newState = source;
newState = newState.replace(/\/+$/, ''); newState = newState.replace(/\/+$/, '');
newState += this.windowLocation.search + this.windowLocation.hash; newState += this.windowLocation.search + this.windowLocation.hash;
history.replaceState({ history.replaceState(
url: newState, {
}, document.title, newState); url: newState,
},
document.title,
newState,
);
return newState; return newState;
} }
......
...@@ -99,7 +99,7 @@ Please update your Git repository remotes as soon as possible.`), ...@@ -99,7 +99,7 @@ Please update your Git repository remotes as soon as possible.`),
:disabled="isRequestPending" :disabled="isRequestPending"
/> />
</div> </div>
<p class="help-block"> <p class="form-text text-muted">
{{ path }} {{ path }}
</p> </p>
</div> </div>
......
...@@ -8,14 +8,24 @@ export default { ...@@ -8,14 +8,24 @@ export default {
name: 'GkeMachineTypeDropdown', name: 'GkeMachineTypeDropdown',
mixins: [gkeDropdownMixin], mixins: [gkeDropdownMixin],
computed: { computed: {
...mapState(['projectHasBillingEnabled', 'selectedZone', 'selectedMachineType']), ...mapState([
'isValidatingProjectBilling',
'projectHasBillingEnabled',
'selectedZone',
'selectedMachineType',
]),
...mapState({ items: 'machineTypes' }), ...mapState({ items: 'machineTypes' }),
...mapGetters(['hasZone', 'hasMachineType']), ...mapGetters(['hasZone', 'hasMachineType']),
allDropdownsSelected() { allDropdownsSelected() {
return this.projectHasBillingEnabled && this.hasZone && this.hasMachineType; return this.projectHasBillingEnabled && this.hasZone && this.hasMachineType;
}, },
isDisabled() { isDisabled() {
return !this.projectHasBillingEnabled || !this.selectedZone; return (
this.isLoading ||
this.isValidatingProjectBilling ||
!this.projectHasBillingEnabled ||
!this.hasZone
);
}, },
toggleText() { toggleText() {
if (this.isLoading) { if (this.isLoading) {
...@@ -45,11 +55,15 @@ export default { ...@@ -45,11 +55,15 @@ export default {
}, },
watch: { watch: {
selectedZone() { selectedZone() {
this.isLoading = true; this.hasErrors = false;
if (this.hasZone) {
this.isLoading = true;
this.fetchMachineTypes() this.fetchMachineTypes()
.then(this.fetchSuccessHandler) .then(this.fetchSuccessHandler)
.catch(this.fetchFailureHandler); .catch(this.fetchFailureHandler);
}
}, },
selectedMachineType() { selectedMachineType() {
this.enableSubmit(); this.enableSubmit();
...@@ -118,7 +132,7 @@ export default { ...@@ -118,7 +132,7 @@ export default {
</div> </div>
</div> </div>
<span <span
class="help-block" class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }" :class="{ 'gl-field-error': hasErrors }"
v-if="hasErrors" v-if="hasErrors"
> >
......
...@@ -14,20 +14,17 @@ export default { ...@@ -14,20 +14,17 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
isValidatingProjectBilling: false,
};
},
computed: { computed: {
...mapState(['selectedProject', 'projectHasBillingEnabled']), ...mapState(['selectedProject', 'isValidatingProjectBilling', 'projectHasBillingEnabled']),
...mapState({ items: 'projects' }), ...mapState({ items: 'projects' }),
...mapGetters(['hasProject']), ...mapGetters(['hasProject']),
hasOneProject() { hasOneProject() {
return this.items && this.items.length === 1; return this.items && this.items.length === 1;
}, },
isDisabled() { isDisabled() {
return this.items && this.items.length < 2; return (
this.isLoading || this.isValidatingProjectBilling || (this.items && this.items.length < 2)
);
}, },
toggleText() { toggleText() {
if (this.isValidatingProjectBilling) { if (this.isValidatingProjectBilling) {
...@@ -103,17 +100,12 @@ export default { ...@@ -103,17 +100,12 @@ export default {
}, },
watch: { watch: {
selectedProject() { selectedProject() {
this.isLoading = true; this.setIsValidatingProjectBilling(true);
this.isValidatingProjectBilling = true;
this.validateProjectBilling() this.validateProjectBilling()
.then(this.validateProjectBillingSuccessHandler) .then(this.validateProjectBillingSuccessHandler)
.catch(this.validateProjectBillingFailureHandler); .catch(this.validateProjectBillingFailureHandler);
}, },
projectHasBillingEnabled(billingEnabled) {
this.hasErrors = !billingEnabled;
this.isValidatingProjectBilling = false;
},
}, },
created() { created() {
this.isLoading = true; this.isLoading = true;
...@@ -123,7 +115,7 @@ export default { ...@@ -123,7 +115,7 @@ export default {
.catch(this.fetchFailureHandler); .catch(this.fetchFailureHandler);
}, },
methods: { methods: {
...mapActions(['fetchProjects', 'validateProjectBilling']), ...mapActions(['fetchProjects', 'setIsValidatingProjectBilling', 'validateProjectBilling']),
...mapActions({ setItem: 'setProject' }), ...mapActions({ setItem: 'setProject' }),
fetchSuccessHandler() { fetchSuccessHandler() {
if (this.defaultValue) { if (this.defaultValue) {
...@@ -140,10 +132,9 @@ export default { ...@@ -140,10 +132,9 @@ export default {
this.hasErrors = false; this.hasErrors = false;
}, },
validateProjectBillingSuccessHandler() { validateProjectBillingSuccessHandler() {
this.isLoading = false; this.hasErrors = !this.projectHasBillingEnabled;
}, },
validateProjectBillingFailureHandler(resp) { validateProjectBillingFailureHandler(resp) {
this.isLoading = false;
this.hasErrors = true; this.hasErrors = true;
this.gapiError = resp.result ? resp.result.error.message : resp; this.gapiError = resp.result ? resp.result.error.message : resp;
...@@ -202,7 +193,7 @@ export default { ...@@ -202,7 +193,7 @@ export default {
</div> </div>
</div> </div>
<span <span
class="help-block" class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }" :class="{ 'gl-field-error': hasErrors }"
v-html="helpText" v-html="helpText"
></span> ></span>
......
...@@ -8,10 +8,16 @@ export default { ...@@ -8,10 +8,16 @@ export default {
name: 'GkeZoneDropdown', name: 'GkeZoneDropdown',
mixins: [gkeDropdownMixin], mixins: [gkeDropdownMixin],
computed: { computed: {
...mapState(['selectedProject', 'selectedZone', 'projects', 'projectHasBillingEnabled']), ...mapState([
'selectedProject',
'selectedZone',
'projects',
'isValidatingProjectBilling',
'projectHasBillingEnabled',
]),
...mapState({ items: 'zones' }), ...mapState({ items: 'zones' }),
isDisabled() { isDisabled() {
return !this.projectHasBillingEnabled; return this.isLoading || this.isValidatingProjectBilling || !this.projectHasBillingEnabled;
}, },
toggleText() { toggleText() {
if (this.isLoading) { if (this.isLoading) {
...@@ -34,13 +40,16 @@ export default { ...@@ -34,13 +40,16 @@ export default {
}, },
}, },
watch: { watch: {
projectHasBillingEnabled(billingEnabled) { isValidatingProjectBilling(isValidating) {
if (!billingEnabled) return false; this.hasErrors = false;
this.isLoading = true;
return this.fetchZones() if (!isValidating && this.projectHasBillingEnabled) {
.then(this.fetchSuccessHandler) this.isLoading = true;
.catch(this.fetchFailureHandler);
this.fetchZones()
.then(this.fetchSuccessHandler)
.catch(this.fetchFailureHandler);
}
}, },
}, },
methods: { methods: {
...@@ -97,7 +106,7 @@ export default { ...@@ -97,7 +106,7 @@ export default {
</div> </div>
</div> </div>
<span <span
class="help-block" class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }" :class="{ 'gl-field-error': hasErrors }"
v-if="hasErrors" v-if="hasErrors"
> >
......
...@@ -31,6 +31,10 @@ export const setMachineType = ({ commit }, selectedMachineType) => { ...@@ -31,6 +31,10 @@ export const setMachineType = ({ commit }, selectedMachineType) => {
commit(types.SET_MACHINE_TYPE, selectedMachineType); commit(types.SET_MACHINE_TYPE, selectedMachineType);
}; };
export const setIsValidatingProjectBilling = ({ commit }, isValidatingProjectBilling) => {
commit(types.SET_IS_VALIDATING_PROJECT_BILLING, isValidatingProjectBilling);
};
export const fetchProjects = ({ commit }) => export const fetchProjects = ({ commit }) =>
gapiResourceListRequest({ gapiResourceListRequest({
resource: gapi.client.cloudresourcemanager.projects, resource: gapi.client.cloudresourcemanager.projects,
...@@ -40,20 +44,25 @@ export const fetchProjects = ({ commit }) => ...@@ -40,20 +44,25 @@ export const fetchProjects = ({ commit }) =>
payloadKey: 'projects', payloadKey: 'projects',
}); });
export const validateProjectBilling = ({ commit, state }) => export const validateProjectBilling = ({ dispatch, commit, state }) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const request = gapi.client.cloudbilling.projects.getBillingInfo({ const request = gapi.client.cloudbilling.projects.getBillingInfo({
name: `projects/${state.selectedProject.projectId}`, name: `projects/${state.selectedProject.projectId}`,
}); });
commit(types.SET_ZONE, '');
commit(types.SET_MACHINE_TYPE, '');
return request.then( return request.then(
resp => { resp => {
const { billingEnabled } = resp.result; const { billingEnabled } = resp.result;
commit(types.SET_PROJECT_BILLING_STATUS, !!billingEnabled); commit(types.SET_PROJECT_BILLING_STATUS, !!billingEnabled);
dispatch('setIsValidatingProjectBilling', false);
resolve(); resolve();
}, },
resp => { resp => {
dispatch('setIsValidatingProjectBilling', false);
reject(resp); reject(resp);
}, },
); );
......
export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECT = 'SET_PROJECT';
export const SET_PROJECT_BILLING_STATUS = 'SET_PROJECT_BILLING_STATUS'; export const SET_PROJECT_BILLING_STATUS = 'SET_PROJECT_BILLING_STATUS';
export const SET_IS_VALIDATING_PROJECT_BILLING = 'SET_IS_VALIDATING_PROJECT_BILLING';
export const SET_ZONE = 'SET_ZONE'; export const SET_ZONE = 'SET_ZONE';
export const SET_MACHINE_TYPE = 'SET_MACHINE_TYPE'; export const SET_MACHINE_TYPE = 'SET_MACHINE_TYPE';
export const SET_PROJECTS = 'SET_PROJECTS'; export const SET_PROJECTS = 'SET_PROJECTS';
......
...@@ -4,6 +4,9 @@ export default { ...@@ -4,6 +4,9 @@ export default {
[types.SET_PROJECT](state, selectedProject) { [types.SET_PROJECT](state, selectedProject) {
Object.assign(state, { selectedProject }); Object.assign(state, { selectedProject });
}, },
[types.SET_IS_VALIDATING_PROJECT_BILLING](state, isValidatingProjectBilling) {
Object.assign(state, { isValidatingProjectBilling });
},
[types.SET_PROJECT_BILLING_STATUS](state, projectHasBillingEnabled) { [types.SET_PROJECT_BILLING_STATUS](state, projectHasBillingEnabled) {
Object.assign(state, { projectHasBillingEnabled }); Object.assign(state, { projectHasBillingEnabled });
}, },
......
...@@ -5,6 +5,7 @@ export default () => ({ ...@@ -5,6 +5,7 @@ export default () => ({
}, },
selectedZone: '', selectedZone: '',
selectedMachineType: '', selectedMachineType: '',
isValidatingProjectBilling: null,
projectHasBillingEnabled: null, projectHasBillingEnabled: null,
projects: [], projects: [],
zones: [], zones: [],
......
...@@ -37,7 +37,7 @@ const IGNORE_URLS = [ ...@@ -37,7 +37,7 @@ const IGNORE_URLS = [
/extensions\//i, /extensions\//i,
/^chrome:\/\//i, /^chrome:\/\//i,
// Other plugins // Other plugins
/127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
/webappstoolbarba\.texthelp\.com\//i, /webappstoolbarba\.texthelp\.com\//i,
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i, /metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
]; ];
......
...@@ -7,9 +7,10 @@ Vue.use(VueResource); ...@@ -7,9 +7,10 @@ Vue.use(VueResource);
export const fetchRepos = ({ commit, state }) => { export const fetchRepos = ({ commit, state }) => {
commit(types.TOGGLE_MAIN_LOADING); commit(types.TOGGLE_MAIN_LOADING);
return Vue.http.get(state.endpoint) return Vue.http
.get(state.endpoint)
.then(res => res.json()) .then(res => res.json())
.then((response) => { .then(response => {
commit(types.TOGGLE_MAIN_LOADING); commit(types.TOGGLE_MAIN_LOADING);
commit(types.SET_REPOS_LIST, response); commit(types.SET_REPOS_LIST, response);
}); });
...@@ -18,19 +19,20 @@ export const fetchRepos = ({ commit, state }) => { ...@@ -18,19 +19,20 @@ export const fetchRepos = ({ commit, state }) => {
export const fetchList = ({ commit }, { repo, page }) => { export const fetchList = ({ commit }, { repo, page }) => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
return Vue.http.get(repo.tagsPath, { params: { page } }) return Vue.http.get(repo.tagsPath, { params: { page } }).then(response => {
.then((response) => { const headers = response.headers;
const headers = response.headers;
return response.json().then((resp) => { return response.json().then(resp => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
commit(types.SET_REGISTRY_LIST, { repo, resp, headers }); commit(types.SET_REGISTRY_LIST, { repo, resp, headers });
});
}); });
});
}; };
// eslint-disable-next-line no-unused-vars
export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath); export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath);
// eslint-disable-next-line no-unused-vars
export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath); export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath);
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
......
/*
The squash-before-merge button is EE only, but it's located right in the middle
of the readyToMerge state component template.
If we didn't declare this component in CE, we'd need to maintain a separate copy
of the readyToMergeState template in EE, which is pretty big and likely to change.
Instead, in CE, we declare the component, but it's hidden and is configured to do nothing.
In EE, the configuration extends this object to add a functioning squash-before-merge
button.
*/
<script> <script>
export default {}; import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
components: {
Icon,
},
directives: {
tooltip,
},
props: {
mr: {
type: Object,
required: true,
},
isMergeButtonDisabled: {
type: Boolean,
required: true,
},
},
data() {
return {
squashBeforeMerge: this.mr.squash,
};
},
methods: {
updateSquashModel() {
eventHub.$emit('MRWidgetUpdateSquash', this.squashBeforeMerge);
},
},
};
</script> </script>
<template>
<div class="accept-control inline">
<label class="merge-param-checkbox">
<input
type="checkbox"
name="squash"
class="qa-squash-checkbox"
:disabled="isMergeButtonDisabled"
v-model="squashBeforeMerge"
@change="updateSquashModel"
/>
{{ __('Squash commits') }}
</label>
<a
:href="mr.squashBeforeMergeHelpPath"
data-title="About this feature"
data-placement="bottom"
target="_blank"
rel="noopener noreferrer nofollow"
data-container="body"
v-tooltip
>
<icon
name="question-o"
/>
</a>
</div>
</template>
...@@ -6,11 +6,13 @@ import MergeRequest from '../../../merge_request'; ...@@ -6,11 +6,13 @@ import MergeRequest from '../../../merge_request';
import Flash from '../../../flash'; import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue'; import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import SquashBeforeMerge from './mr_widget_squash_before_merge.vue';
export default { export default {
name: 'ReadyToMerge', name: 'ReadyToMerge',
components: { components: {
statusIcon, statusIcon,
'squash-before-merge': SquashBeforeMerge,
}, },
props: { props: {
mr: { type: Object, required: true }, mr: { type: Object, required: true },
...@@ -101,6 +103,12 @@ export default { ...@@ -101,6 +103,12 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1; return enableSquashBeforeMerge && commitsCount > 1;
}, },
}, },
created() {
eventHub.$on('MRWidgetUpdateSquash', this.handleUpdateSquash);
},
beforeDestroy() {
eventHub.$off('MRWidgetUpdateSquash', this.handleUpdateSquash);
},
methods: { methods: {
shouldShowMergeControls() { shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText; return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
...@@ -128,13 +136,9 @@ export default { ...@@ -128,13 +136,9 @@ export default {
commit_message: this.commitMessage, commit_message: this.commitMessage,
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds, merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
should_remove_source_branch: this.removeSourceBranch === true, should_remove_source_branch: this.removeSourceBranch === true,
squash: this.mr.squash,
}; };
// Only truthy in EE extension of this component
if (this.setAdditionalParams) {
this.setAdditionalParams(options);
}
this.isMakingRequest = true; this.isMakingRequest = true;
this.service.merge(options) this.service.merge(options)
.then(res => res.data) .then(res => res.data)
...@@ -154,6 +158,9 @@ export default { ...@@ -154,6 +158,9 @@ export default {
new Flash('Something went wrong. Please try again.'); // eslint-disable-line new Flash('Something went wrong. Please try again.'); // eslint-disable-line
}); });
}, },
handleUpdateSquash(val) {
this.mr.squash = val;
},
initiateMergePolling() { initiateMergePolling() {
simplePoll((continuePolling, stopPolling) => { simplePoll((continuePolling, stopPolling) => {
this.handleMergePolling(continuePolling, stopPolling); this.handleMergePolling(continuePolling, stopPolling);
......
...@@ -15,6 +15,11 @@ export default class MergeRequestStore { ...@@ -15,6 +15,11 @@ export default class MergeRequestStore {
const currentUser = data.current_user; const currentUser = data.current_user;
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null; const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
this.squash = data.squash;
this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath ||
data.squash_before_merge_help_path;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
this.title = data.title; this.title = data.title;
this.targetBranch = data.target_branch; this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch; this.sourceBranch = data.source_branch;
......
...@@ -13,7 +13,7 @@ export default (Vue) => { ...@@ -13,7 +13,7 @@ export default (Vue) => {
@param text The text to be translated @param text The text to be translated
@returns {String} The translated text @returns {String} The translated text
**/ */
__, __,
/** /**
Translate the text with a number Translate the text with a number
...@@ -24,7 +24,7 @@ export default (Vue) => { ...@@ -24,7 +24,7 @@ export default (Vue) => {
@param pluralText Plural text to translate (eg. '%d days') @param pluralText Plural text to translate (eg. '%d days')
@param count Number to decide which translation to use (eg. 2) @param count Number to decide which translation to use (eg. 2)
@returns {String} Translated text with the number replaced (eg. '2 days') @returns {String} Translated text with the number replaced (eg. '2 days')
**/ */
n__, n__,
/** /**
Translate context based text Translate context based text
...@@ -36,7 +36,7 @@ export default (Vue) => { ...@@ -36,7 +36,7 @@ export default (Vue) => {
(eg. 'Context') (eg. 'Context')
@param key Is the dynamic variable you want to be translated @param key Is the dynamic variable you want to be translated
@returns {String} Translated context based text @returns {String} Translated context based text
**/ */
s__, s__,
sprintf, sprintf,
}, },
......
...@@ -131,6 +131,10 @@ table { ...@@ -131,6 +131,10 @@ table {
} }
.card { .card {
.card-title {
margin-bottom: 0;
}
&.card-without-border { &.card-without-border {
@extend .border-0; @extend .border-0;
} }
...@@ -147,3 +151,7 @@ table { ...@@ -147,3 +151,7 @@ table {
.nav-tabs .nav-link { .nav-tabs .nav-link {
border: 0; border: 0;
} }
pre code {
white-space: pre-wrap;
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Well styled list * Well styled list
* *
*/ */
.card-body-list { .hover-list {
position: relative; position: relative;
margin: 0; margin: 0;
padding: 0; padding: 0;
......
...@@ -74,12 +74,6 @@ body.modal-open { ...@@ -74,12 +74,6 @@ body.modal-open {
} }
} }
@include media-breakpoint-up(lg) {
.modal-full {
width: 98%;
}
}
.modal { .modal {
background-color: $black-transparent; background-color: $black-transparent;
z-index: 2100; z-index: 2100;
......
@mixin flat-connector-before($length: 44px) {
&::before {
content: '';
position: absolute;
top: 48%;
left: -$length;
border-top: 2px solid $border-color;
width: $length;
height: 1px;
}
}
@mixin build-content($border-radius: 30px) {
display: inline-block;
padding: 8px 10px 9px;
width: 100%;
border: 1px solid $border-color;
border-radius: $border-radius;
background-color: $white-light;
&:hover {
background-color: $stage-hover-bg;
border: 1px solid $dropdown-toggle-active-border-color;
color: $gl-text-color;
}
}
.pipelines { .pipelines {
.stage { .stage {
max-width: 90px; max-width: 90px;
...@@ -357,14 +384,8 @@ ...@@ -357,14 +384,8 @@
&:not(:first-child) { &:not(:first-child) {
margin-left: 44px; margin-left: 44px;
.left-connector::before { .left-connector {
content: ''; @include flat-connector-before;
position: absolute;
top: 48%;
left: -44px;
border-top: 2px solid $border-color;
width: 44px;
height: 1px;
} }
} }
} }
...@@ -479,12 +500,7 @@ ...@@ -479,12 +500,7 @@
} }
.build-content { .build-content {
display: inline-block; @include build-content();
padding: 8px 10px 9px;
width: 100%;
border: 1px solid $border-color;
border-radius: 30px;
background-color: $white-light;
} }
a.build-content:hover, a.build-content:hover,
...@@ -622,8 +638,7 @@ ...@@ -622,8 +638,7 @@
} }
} }
// Dropdown button in mini pipeline graph @mixin mini-pipeline-item() {
button.mini-pipeline-graph-dropdown-toggle {
border-radius: 100px; border-radius: 100px;
background-color: $white-light; background-color: $white-light;
border-width: 1px; border-width: 1px;
...@@ -636,30 +651,6 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -636,30 +651,6 @@ button.mini-pipeline-graph-dropdown-toggle {
position: relative; position: relative;
vertical-align: middle; vertical-align: middle;
> .fa.fa-caret-down {
position: absolute;
left: 20px;
top: 5px;
display: inline-block;
visibility: hidden;
opacity: 0;
color: inherit;
font-size: 12px;
transition: visibility 0.1s, opacity 0.1s linear;
}
&:active,
&:focus,
&:hover {
outline: none;
width: 35px;
.fa.fa-caret-down {
visibility: visible;
opacity: 1;
}
}
// Dropdown button animation in mini pipeline graph // Dropdown button animation in mini pipeline graph
&.ci-status-icon-success { &.ci-status-icon-success {
@include mini-pipeline-graph-color($green-100, $green-500, $green-600); @include mini-pipeline-graph-color($green-100, $green-500, $green-600);
...@@ -691,6 +682,35 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -691,6 +682,35 @@ button.mini-pipeline-graph-dropdown-toggle {
} }
} }
// Dropdown button in mini pipeline graph
button.mini-pipeline-graph-dropdown-toggle {
@include mini-pipeline-item();
> .fa.fa-caret-down {
position: absolute;
left: 20px;
top: 5px;
display: inline-block;
visibility: hidden;
opacity: 0;
color: inherit;
font-size: 12px;
transition: visibility 0.1s, opacity 0.1s linear;
}
&:active,
&:focus,
&:hover {
outline: none;
width: 35px;
.fa.fa-caret-down {
visibility: visible;
opacity: 1;
}
}
}
/** /**
Action icons inside dropdowns: Action icons inside dropdowns:
- mini graph in pipelines table - mini graph in pipelines table
...@@ -744,7 +764,7 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -744,7 +764,7 @@ button.mini-pipeline-graph-dropdown-toggle {
} }
} }
// SVGs in the commit widget and mr widget // SVGs in the commit widget and mr widget
a.ci-action-icon-container.ci-action-icon-wrapper svg { a.ci-action-icon-container.ci-action-icon-wrapper svg {
top: 2px; top: 2px;
} }
......
...@@ -405,7 +405,7 @@ table.u2f-registrations { ...@@ -405,7 +405,7 @@ table.u2f-registrations {
margin-right: $gl-padding / 4; margin-right: $gl-padding / 4;
} }
.label-verification-status { .badge-verification-status {
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
......
...@@ -546,7 +546,7 @@ ...@@ -546,7 +546,7 @@
margin-right: 0; margin-right: 0;
} }
&.help-block { &.form-text.text-muted {
margin-left: 0; margin-left: 0;
right: 0; right: 0;
} }
...@@ -952,7 +952,7 @@ ...@@ -952,7 +952,7 @@
height: 30px; height: 30px;
} }
.help-block { .form-text.text-muted {
margin-top: 2px; margin-top: 2px;
color: $blue-500; color: $blue-500;
cursor: pointer; cursor: pointer;
...@@ -1088,10 +1088,6 @@ ...@@ -1088,10 +1088,6 @@
font-size: 12px; font-size: 12px;
} }
.ide-new-modal-label {
line-height: 34px;
}
.multi-file-commit-panel-success-message { .multi-file-commit-panel-success-message {
position: absolute; position: absolute;
top: 61px; top: 61px;
......
class Admin::DashboardController < Admin::ApplicationController class Admin::DashboardController < Admin::ApplicationController
include CountHelper include CountHelper
COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest,
Note, Snippet, Key, Milestone].freeze
def index def index
@counts = Gitlab::Database::Count.approximate_counts(COUNTED_ITEMS)
@projects = Project.order_id_desc.without_deleted.with_route.limit(10) @projects = Project.order_id_desc.without_deleted.with_route.limit(10)
@users = User.order_id_desc.limit(10) @users = User.order_id_desc.limit(10)
@groups = Group.order_id_desc.with_route.limit(10) @groups = Group.order_id_desc.with_route.limit(10)
......
module Groups
class SharedProjectsController < Groups::ApplicationController
respond_to :json
before_action :group
skip_cross_project_access_check :index
def index
shared_projects = GroupProjectsFinder.new(
group: group,
current_user: current_user,
params: finder_params,
options: { only_shared: true }
).execute
serializer = GroupChildSerializer.new(current_user: current_user)
.with_pagination(request, response)
render json: serializer.represent(shared_projects)
end
private
def finder_params
@finder_params ||= begin
# Make the `search` param consistent for the frontend,
# which will be using `filter`.
params[:search] ||= params[:filter] if params[:filter]
params.permit(:sort, :search)
end
end
end
end
...@@ -93,8 +93,6 @@ class ProfilesController < Profiles::ApplicationController ...@@ -93,8 +93,6 @@ class ProfilesController < Profiles::ApplicationController
:linkedin, :linkedin,
:location, :location,
:name, :name,
:password,
:password_confirmation,
:public_email, :public_email,
:skype, :skype,
:twitter, :twitter,
......
...@@ -9,6 +9,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -9,6 +9,7 @@ class Projects::ClustersController < Projects::ApplicationController
before_action :authorize_update_cluster!, only: [:update] before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy] before_action :authorize_admin_cluster!, only: [:destroy]
before_action :update_applications_status, only: [:status] before_action :update_applications_status, only: [:status]
helper_method :token_in_session
STATUS_POLLING_INTERVAL = 10_000 STATUS_POLLING_INTERVAL = 10_000
...@@ -190,8 +191,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -190,8 +191,7 @@ class Projects::ClustersController < Projects::ApplicationController
end end
def token_in_session def token_in_session
@token_in_session ||= session[GoogleApi::CloudPlatform::Client.session_key_for_token]
session[GoogleApi::CloudPlatform::Client.session_key_for_token]
end end
def expires_at_in_session def expires_at_in_session
......
...@@ -7,6 +7,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -7,6 +7,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize] before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics] before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
def index def index
@environments = project.environments @environments = project.environments
...@@ -148,6 +149,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -148,6 +149,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController
Gitlab::Workhorse.verify_api_request!(request.headers) Gitlab::Workhorse.verify_api_request!(request.headers)
end end
def expire_etag_cache
return if request.format.json?
# this forces to reload json content
Gitlab::EtagCaching::Store.new.tap do |store|
store.touch(project_environments_path(project, format: :json))
end
end
def environment_params def environment_params
params.require(:environment).permit(:name, :external_url) params.require(:environment).permit(:name, :external_url)
end end
......
...@@ -24,6 +24,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -24,6 +24,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:source_branch, :source_branch,
:source_project_id, :source_project_id,
:state_event, :state_event,
:squash,
:target_branch, :target_branch,
:target_project_id, :target_project_id,
:task_num, :task_num,
......
...@@ -253,7 +253,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -253,7 +253,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end end
def merge_params_attributes def merge_params_attributes
[:should_remove_source_branch, :commit_message] [:should_remove_source_branch, :commit_message, :squash]
end end
def merge_when_pipeline_succeeds_active? def merge_when_pipeline_succeeds_active?
...@@ -282,7 +282,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -282,7 +282,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha
@merge_request.update(merge_error: nil) @merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
if params[:merge_when_pipeline_succeeds].present? if params[:merge_when_pipeline_succeeds].present?
return :failed unless @merge_request.actual_head_pipeline return :failed unless @merge_request.actual_head_pipeline
......
...@@ -14,6 +14,8 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -14,6 +14,8 @@ class Projects::WikisController < Projects::ApplicationController
def show def show
@page = @project_wiki.find_page(params[:id], params[:version_id]) @page = @project_wiki.find_page(params[:id], params[:version_id])
view_param = @project_wiki.empty? ? params[:view] : 'create'
if @page if @page
render 'show' render 'show'
elsif file = @project_wiki.find_file(params[:id], params[:version_id]) elsif file = @project_wiki.find_file(params[:id], params[:version_id])
...@@ -26,12 +28,12 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -26,12 +28,12 @@ class Projects::WikisController < Projects::ApplicationController
disposition: 'inline', disposition: 'inline',
filename: file.name filename: file.name
) )
else elsif can?(current_user, :create_wiki, @project) && view_param == 'create'
return render('empty') unless can?(current_user, :create_wiki, @project)
@page = build_page(title: params[:id]) @page = build_page(title: params[:id])
render 'edit' render 'edit'
else
render 'empty'
end end
end end
......
...@@ -39,25 +39,15 @@ class GroupProjectsFinder < ProjectsFinder ...@@ -39,25 +39,15 @@ class GroupProjectsFinder < ProjectsFinder
end end
def collection_with_user def collection_with_user
if group.users.include?(current_user) if only_shared?
if only_shared? [shared_projects.public_or_visible_to_user(current_user)]
[shared_projects] elsif only_owned?
elsif only_owned? [owned_projects.public_or_visible_to_user(current_user)]
[owned_projects]
else
[shared_projects, owned_projects]
end
else else
if only_shared? [
[shared_projects.public_or_visible_to_user(current_user)] owned_projects.public_or_visible_to_user(current_user),
elsif only_owned? shared_projects.public_or_visible_to_user(current_user)
[owned_projects.public_or_visible_to_user(current_user)] ]
else
[
owned_projects.public_or_visible_to_user(current_user),
shared_projects.public_or_visible_to_user(current_user)
]
end
end end
end end
......
...@@ -204,7 +204,7 @@ module ApplicationSettingsHelper ...@@ -204,7 +204,7 @@ module ApplicationSettingsHelper
:pages_domain_verification_enabled, :pages_domain_verification_enabled,
:password_authentication_enabled_for_web, :password_authentication_enabled_for_web,
:password_authentication_enabled_for_git, :password_authentication_enabled_for_git,
:performance_bar_allowed_group_id, :performance_bar_allowed_group_path,
:performance_bar_enabled, :performance_bar_enabled,
:plantuml_enabled, :plantuml_enabled,
:plantuml_url, :plantuml_url,
......
module CountHelper module CountHelper
def approximate_count_with_delimiters(model) def approximate_count_with_delimiters(count_data, model)
number_with_delimiter(Gitlab::Database::Count.approximate_count(model)) count = count_data[model]
raise "Missing model #{model} from count data" unless count
number_with_delimiter(count)
end end
end end
...@@ -97,8 +97,9 @@ module MergeRequestsHelper ...@@ -97,8 +97,9 @@ module MergeRequestsHelper
{ {
merge_when_pipeline_succeeds: true, merge_when_pipeline_succeeds: true,
should_remove_source_branch: true, should_remove_source_branch: true,
sha: merge_request.diff_head_sha sha: merge_request.diff_head_sha,
}.merge(merge_params_ee(merge_request)) squash: merge_request.squash
}
end end
def tab_link_for(merge_request, tab, options = {}, &block) def tab_link_for(merge_request, tab, options = {}, &block)
...@@ -149,8 +150,4 @@ module MergeRequestsHelper ...@@ -149,8 +150,4 @@ module MergeRequestsHelper
current_user.fork_of(project) current_user.fork_of(project)
end end
end end
def merge_params_ee(merge_request)
{}
end
end end
...@@ -11,6 +11,7 @@ module NavHelper ...@@ -11,6 +11,7 @@ module NavHelper
class_name = page_gutter_class class_name = page_gutter_class
class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
class_name -= ['right-sidebar-expanded'] if defined?(@right_sidebar) && !@right_sidebar
class_name class_name
end end
......
...@@ -257,6 +257,9 @@ module ProjectsHelper ...@@ -257,6 +257,9 @@ module ProjectsHelper
if project.builds_enabled? && can?(current_user, :read_pipeline, project) if project.builds_enabled? && can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines nav_tabs << :pipelines
end
if can?(current_user, :read_environment, project) || can?(current_user, :read_cluster, project)
nav_tabs << :operations nav_tabs << :operations
end end
......
...@@ -230,6 +230,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -230,6 +230,7 @@ class ApplicationSetting < ActiveRecord::Base
after_commit do after_commit do
reset_memoized_terms reset_memoized_terms
end end
after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') }
def self.defaults def self.defaults
{ {
...@@ -386,31 +387,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -386,31 +387,6 @@ class ApplicationSetting < ActiveRecord::Base
super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) }) super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end end
def performance_bar_allowed_group_id=(group_full_path)
group_full_path = nil if group_full_path.blank?
if group_full_path.nil?
if group_full_path != performance_bar_allowed_group_id
super(group_full_path)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
return
end
group = Group.find_by_full_path(group_full_path)
if group
if group.id != performance_bar_allowed_group_id
super(group.id)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
else
super(nil)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
end
def performance_bar_allowed_group def performance_bar_allowed_group
Group.find_by_id(performance_bar_allowed_group_id) Group.find_by_id(performance_bar_allowed_group_id)
end end
...@@ -420,15 +396,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -420,15 +396,6 @@ class ApplicationSetting < ActiveRecord::Base
performance_bar_allowed_group_id.present? performance_bar_allowed_group_id.present?
end end
# - If `enable` is true, we early return since the actual attribute that holds
# the enabling/disabling is `performance_bar_allowed_group_id`
# - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil`
def performance_bar_enabled=(enable)
return if Gitlab::Utils.to_boolean(enable)
self.performance_bar_allowed_group_id = nil
end
# Choose one of the available repository storage options. Currently all have # Choose one of the available repository storage options. Currently all have
# equal weighting. # equal weighting.
def pick_repository_storage def pick_repository_storage
...@@ -506,4 +473,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -506,4 +473,8 @@ class ApplicationSetting < ActiveRecord::Base
errors.add(:terms, "You need to set terms to be enforced") unless terms.present? errors.add(:terms, "You need to set terms to be enforced") unless terms.present?
end end
def expire_performance_bar_allowed_user_ids_cache
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
end end
# Provides a way to work around Rails issue where dependent objects are all
# loaded into memory before destroyed: https://github.com/rails/rails/issues/22510.
#
# This concern allows an ActiveRecord module to destroy all its dependent
# associations in batches. The idea is borrowed from https://github.com/thisismydesign/batch_dependent_associations.
#
# The differences here with that gem:
#
# 1. We allow excluding certain associations.
# 2. We don't need to support delete_all since we can use the EachBatch concern.
module BatchDestroyDependentAssociations
extend ActiveSupport::Concern
DEPENDENT_ASSOCIATIONS_BATCH_SIZE = 1000
def dependent_associations_to_destroy
self.class.reflect_on_all_associations(:has_many).select { |assoc| assoc.options[:dependent] == :destroy }
end
def destroy_dependent_associations_in_batches(exclude: [])
dependent_associations_to_destroy.each do |association|
next if exclude.include?(association.name)
# rubocop:disable GitlabSecurity/PublicSend
public_send(association.name).find_each(batch_size: DEPENDENT_ASSOCIATIONS_BATCH_SIZE, &:destroy)
end
end
end
module DiffFile
extend ActiveSupport::Concern
def to_hash
keys = Gitlab::Git::Diff::SERIALIZE_KEYS - [:diff]
as_json(only: keys).merge(diff: diff).with_indifferent_access
end
end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# A note of this type can be resolvable. # A note of this type can be resolvable.
class DiffNote < Note class DiffNote < Note
include NoteOnDiff include NoteOnDiff
include Gitlab::Utils::StrongMemoize
NOTEABLE_TYPES = %w(MergeRequest Commit).freeze NOTEABLE_TYPES = %w(MergeRequest Commit).freeze
...@@ -12,7 +13,6 @@ class DiffNote < Note ...@@ -12,7 +13,6 @@ class DiffNote < Note
validates :original_position, presence: true validates :original_position, presence: true
validates :position, presence: true validates :position, presence: true
validates :diff_line, presence: true, if: :on_text?
validates :line_code, presence: true, line_code: true, if: :on_text? validates :line_code, presence: true, line_code: true, if: :on_text?
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES } validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
validate :positions_complete validate :positions_complete
...@@ -23,6 +23,7 @@ class DiffNote < Note ...@@ -23,6 +23,7 @@ class DiffNote < Note
before_validation :update_position, on: :create, if: :on_text? before_validation :update_position, on: :create, if: :on_text?
before_validation :set_line_code, if: :on_text? before_validation :set_line_code, if: :on_text?
after_save :keep_around_commits after_save :keep_around_commits
after_commit :create_diff_file, on: :create
def discussion_class(*) def discussion_class(*)
DiffDiscussion DiffDiscussion
...@@ -53,21 +54,25 @@ class DiffNote < Note ...@@ -53,21 +54,25 @@ class DiffNote < Note
position.position_type == "image" position.position_type == "image"
end end
def create_diff_file
return unless should_create_diff_file?
diff_file = fetch_diff_file
diff_line = diff_file.line_for_position(self.original_position)
creation_params = diff_file.diff.to_hash
.except(:too_large)
.merge(diff: diff_file.diff_hunk(diff_line))
create_note_diff_file(creation_params)
end
def diff_file def diff_file
@diff_file ||= strong_memoize(:diff_file) do
begin enqueue_diff_file_creation_job if should_create_diff_file?
if created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're fetch_diff_file
# presenting a "current version" of the MR discussion diff. end
# So no need to make an extra Gitaly diff request for it.
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first
else
original_position.diff_file(self.project.repository)
end
end
end end
def diff_line def diff_line
...@@ -98,6 +103,38 @@ class DiffNote < Note ...@@ -98,6 +103,38 @@ class DiffNote < Note
private private
def enqueue_diff_file_creation_job
# Avoid enqueuing multiple file creation jobs at once for a note (i.e.
# parallel calls to `DiffNote#diff_file`).
lease = Gitlab::ExclusiveLease.new("note_diff_file_creation:#{id}", timeout: 1.hour.to_i)
return unless lease.try_obtain
CreateNoteDiffFileWorker.perform_async(id)
end
def should_create_diff_file?
on_text? && note_diff_file.nil? && self == discussion.first_note
end
def fetch_diff_file
if note_diff_file
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
Gitlab::Diff::File.new(diff,
repository: project.repository,
diff_refs: original_position.diff_refs)
elsif created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
# presenting a "current version" of the MR discussion diff.
# So no need to make an extra Gitaly diff request for it.
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first
else
original_position.diff_file(self.project.repository)
end
end
def supported? def supported?
for_commit? || self.noteable.has_complete_diff_refs? for_commit? || self.noteable.has_complete_diff_refs?
end end
......
...@@ -40,6 +40,7 @@ class Event < ActiveRecord::Base ...@@ -40,6 +40,7 @@ class Event < ActiveRecord::Base
).freeze ).freeze
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
REPOSITORY_UPDATED_AT_INTERVAL = 5.minutes
delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true
delegate :title, to: :issue, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true
...@@ -391,6 +392,7 @@ class Event < ActiveRecord::Base ...@@ -391,6 +392,7 @@ class Event < ActiveRecord::Base
def set_last_repository_updated_at def set_last_repository_updated_at
Project.unscoped.where(id: project_id) Project.unscoped.where(id: project_id)
.where("last_repository_updated_at < ? OR last_repository_updated_at IS NULL", REPOSITORY_UPDATED_AT_INTERVAL.ago)
.update_all(last_repository_updated_at: created_at) .update_all(last_repository_updated_at: created_at)
end end
......
...@@ -24,12 +24,9 @@ class InternalId < ActiveRecord::Base ...@@ -24,12 +24,9 @@ class InternalId < ActiveRecord::Base
# #
# The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL). # The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL).
# As such, the increment is atomic and safe to be called concurrently. # As such, the increment is atomic and safe to be called concurrently.
# def increment_and_save!
# If a `maximum_iid` is passed in, this overrides the incremented value if it's
# greater than that. This can be used to correct the increment value if necessary.
def increment_and_save!(maximum_iid)
lock! lock!
self.last_value = [(last_value || 0) + 1, (maximum_iid || 0) + 1].max self.last_value = (last_value || 0) + 1
save! save!
last_value last_value
end end
...@@ -93,16 +90,7 @@ class InternalId < ActiveRecord::Base ...@@ -93,16 +90,7 @@ class InternalId < ActiveRecord::Base
# and increment its last value # and increment its last value
# #
# Note this will acquire a ROW SHARE lock on the InternalId record # Note this will acquire a ROW SHARE lock on the InternalId record
(lookup || create_record).increment_and_save!
# Note we always calculate the maximum iid present here and
# pass it in to correct the InternalId entry if it's last_value is off.
#
# This can happen in a transition phase where both `AtomicInternalId` and
# `NonatomicInternalId` code runs (e.g. during a deploy).
#
# This is subject to be cleaned up with the 10.8 release:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/45389.
(lookup || create_record).increment_and_save!(maximum_iid)
end end
end end
...@@ -128,15 +116,11 @@ class InternalId < ActiveRecord::Base ...@@ -128,15 +116,11 @@ class InternalId < ActiveRecord::Base
InternalId.create!( InternalId.create!(
**scope, **scope,
usage: usage_value, usage: usage_value,
last_value: maximum_iid last_value: init.call(subject) || 0
) )
end end
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
lookup lookup
end end
def maximum_iid
@maximum_iid ||= init.call(subject) || 0
end
end end
end end
...@@ -1140,4 +1140,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -1140,4 +1140,11 @@ class MergeRequest < ActiveRecord::Base
maintainer_push_possible? && maintainer_push_possible? &&
Ability.allowed?(user, :push_code, source_project) Ability.allowed?(user, :push_code, source_project)
end end
def squash_in_progress?
# The source project can be deleted
return false unless source_project
source_project.repository.squash_in_progress?(id)
end
end end
class MergeRequestDiffFile < ActiveRecord::Base class MergeRequestDiffFile < ActiveRecord::Base
include Gitlab::EncodingHelper include Gitlab::EncodingHelper
include DiffFile
belongs_to :merge_request_diff belongs_to :merge_request_diff
...@@ -12,10 +13,4 @@ class MergeRequestDiffFile < ActiveRecord::Base ...@@ -12,10 +13,4 @@ class MergeRequestDiffFile < ActiveRecord::Base
def diff def diff
binary? ? super.unpack('m0').first : super binary? ? super.unpack('m0').first : super
end end
def to_hash
keys = Gitlab::Git::Diff::SERIALIZE_KEYS - [:diff]
as_json(only: keys).merge(diff: diff).with_indifferent_access
end
end end
...@@ -63,6 +63,7 @@ class Note < ActiveRecord::Base ...@@ -63,6 +63,7 @@ class Note < ActiveRecord::Base
has_many :todos has_many :todos
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :system_note_metadata has_one :system_note_metadata
has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id
delegate :gfm_reference, :local_reference, to: :noteable delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
...@@ -100,7 +101,8 @@ class Note < ActiveRecord::Base ...@@ -100,7 +101,8 @@ class Note < ActiveRecord::Base
scope :inc_author_project, -> { includes(:project, :author) } scope :inc_author_project, -> { includes(:project, :author) }
scope :inc_author, -> { includes(:author) } scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do scope :inc_relations_for_view, -> do
includes(:project, :author, :updated_by, :resolved_by, :award_emoji, :system_note_metadata) includes(:project, :author, :updated_by, :resolved_by, :award_emoji,
:system_note_metadata, :note_diff_file)
end end
scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) } scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
......
class NoteDiffFile < ActiveRecord::Base
include DiffFile
belongs_to :diff_note, inverse_of: :note_diff_file
validates :diff_note, presence: true
end
...@@ -24,6 +24,7 @@ class Project < ActiveRecord::Base ...@@ -24,6 +24,7 @@ class Project < ActiveRecord::Base
include ChronicDurationAttribute include ChronicDurationAttribute
include FastDestroyAll::Helpers include FastDestroyAll::Helpers
include WithUploads include WithUploads
include BatchDestroyDependentAssociations
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
......
...@@ -957,6 +957,22 @@ class Repository ...@@ -957,6 +957,22 @@ class Repository
remote_branch: merge_request.target_branch) remote_branch: merge_request.target_branch)
end end
def blob_data_at(sha, path)
blob = blob_at(sha, path)
return unless blob
blob.load_all_data!
blob.data
end
def squash(user, merge_request)
raw.squash(user, merge_request.id, branch: merge_request.target_branch,
start_sha: merge_request.diff_start_sha,
end_sha: merge_request.diff_head_sha,
author: merge_request.author,
message: merge_request.title)
end
private private
# TODO Generice finder, later split this on finders by Ref or Oid # TODO Generice finder, later split this on finders by Ref or Oid
...@@ -971,14 +987,6 @@ class Repository ...@@ -971,14 +987,6 @@ class Repository
::Commit.new(commit, @project) if commit ::Commit.new(commit, @project) if commit
end end
def blob_data_at(sha, path)
blob = blob_at(sha, path)
return unless blob
blob.load_all_data!
blob.data
end
def cache def cache
@cache ||= Gitlab::RepositoryCache.new(self) @cache ||= Gitlab::RepositoryCache.new(self)
end end
......
...@@ -206,10 +206,11 @@ class Service < ActiveRecord::Base ...@@ -206,10 +206,11 @@ class Service < ActiveRecord::Base
args.each do |arg| args.each do |arg|
class_eval %{ class_eval %{
def #{arg}? def #{arg}?
# '!!' is used because nil or empty string is converted to nil
if Gitlab.rails5? if Gitlab.rails5?
!ActiveModel::Type::Boolean::FALSE_VALUES.include?(#{arg}) !!ActiveRecord::Type::Boolean.new.cast(#{arg})
else else
ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg}) !!ActiveRecord::Type::Boolean.new.type_cast_from_database(#{arg})
end end
end end
} }
......
...@@ -31,7 +31,7 @@ class GroupChildEntity < Grape::Entity ...@@ -31,7 +31,7 @@ class GroupChildEntity < Grape::Entity
end end
# Project only attributes # Project only attributes
expose :star_count, expose :star_count, :archived,
if: lambda { |_instance, _options| project? } if: lambda { |_instance, _options| project? }
# Group only attributes # Group only attributes
......
...@@ -10,6 +10,7 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -10,6 +10,7 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_when_pipeline_succeeds expose :merge_when_pipeline_succeeds
expose :source_branch expose :source_branch
expose :source_project_id expose :source_project_id
expose :squash
expose :target_branch expose :target_branch
expose :target_project_id expose :target_project_id
expose :allow_maintainer_to_push expose :allow_maintainer_to_push
......
...@@ -3,6 +3,10 @@ module ApplicationSettings ...@@ -3,6 +3,10 @@ module ApplicationSettings
def execute def execute
update_terms(@params.delete(:terms)) update_terms(@params.delete(:terms))
if params.key?(:performance_bar_allowed_group_path)
params[:performance_bar_allowed_group_id] = performance_bar_allowed_group_id
end
@application_setting.update(@params) @application_setting.update(@params)
end end
...@@ -18,5 +22,13 @@ module ApplicationSettings ...@@ -18,5 +22,13 @@ module ApplicationSettings
ApplicationSetting::Term.create(terms: terms) ApplicationSetting::Term.create(terms: terms)
@application_setting.reset_memoized_terms @application_setting.reset_memoized_terms
end end
def performance_bar_allowed_group_id
performance_bar_enabled = !params.key?(:performance_bar_enabled) || params.delete(:performance_bar_enabled)
group_full_path = params.delete(:performance_bar_allowed_group_path)
return nil unless Gitlab::Utils.to_boolean(performance_bar_enabled)
Group.find_by_full_path(group_full_path)&.id if group_full_path.present?
end
end end
end end
...@@ -34,6 +34,19 @@ module MergeRequests ...@@ -34,6 +34,19 @@ module MergeRequests
handle_merge_error(log_message: e.message, save_message_on_model: true) handle_merge_error(log_message: e.message, save_message_on_model: true)
end end
def source
return merge_request.diff_head_sha unless merge_request.squash
squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute(merge_request)
case squash_result[:status]
when :success
squash_result[:squash_sha]
when :error
raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
end
end
private private
def error_check! def error_check!
...@@ -116,9 +129,5 @@ module MergeRequests ...@@ -116,9 +129,5 @@ module MergeRequests
def merge_request_info def merge_request_info
merge_request.to_reference(full: true) merge_request.to_reference(full: true)
end end
def source
@source ||= @merge_request.diff_head_sha
end
end end
end end
module MergeRequests
class SquashService < MergeRequests::WorkingCopyBaseService
def execute(merge_request)
@merge_request = merge_request
@repository = target_project.repository
squash || error('Failed to squash. Should be done manually.')
end
def squash
if merge_request.commits_count < 2
return success(squash_sha: merge_request.diff_head_sha)
end
if merge_request.squash_in_progress?
return error('Squash task canceled: another squash is already in progress.')
end
squash_sha = repository.squash(current_user, merge_request)
success(squash_sha: squash_sha)
rescue => e
log_error("Failed to squash merge request #{merge_request.to_reference(full: true)}:")
log_error(e.message)
false
end
end
end
...@@ -137,7 +137,13 @@ module Projects ...@@ -137,7 +137,13 @@ module Projects
trash_repositories! trash_repositories!
project.team.truncate # Rails attempts to load all related records into memory before
# destroying: https://github.com/rails/rails/issues/22510
# This ensures we delete records in batches.
#
# Exclude container repositories because its before_destroy would be
# called multiple times, and it doesn't destroy any database records.
project.destroy_dependent_associations_in_batches(exclude: [:container_repositories])
project.destroy! project.destroy!
end end
end end
......
...@@ -11,7 +11,8 @@ module ObjectStorage ...@@ -11,7 +11,8 @@ module ObjectStorage
ObjectStorageUnavailable = Class.new(StandardError) ObjectStorageUnavailable = Class.new(StandardError)
DIRECT_UPLOAD_TIMEOUT = 4.hours DIRECT_UPLOAD_TIMEOUT = 4.hours
TMP_UPLOAD_PATH = 'tmp/upload'.freeze DIRECT_UPLOAD_EXPIRE_OFFSET = 15.minutes
TMP_UPLOAD_PATH = 'tmp/uploads'.freeze
module Store module Store
LOCAL = 1 LOCAL = 1
...@@ -174,11 +175,12 @@ module ObjectStorage ...@@ -174,11 +175,12 @@ module ObjectStorage
id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-') id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-')
upload_path = File.join(TMP_UPLOAD_PATH, id) upload_path = File.join(TMP_UPLOAD_PATH, id)
connection = ::Fog::Storage.new(self.object_store_credentials) connection = ::Fog::Storage.new(self.object_store_credentials)
expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT + DIRECT_UPLOAD_EXPIRE_OFFSET
options = { 'Content-Type' => 'application/octet-stream' } options = { 'Content-Type' => 'application/octet-stream' }
{ {
ID: id, ID: id,
Timeout: DIRECT_UPLOAD_TIMEOUT,
GetURL: connection.get_object_url(remote_store_path, upload_path, expire_at), GetURL: connection.get_object_url(remote_store_path, upload_path, expire_at),
DeleteURL: connection.delete_object_url(remote_store_path, upload_path, expire_at), DeleteURL: connection.delete_object_url(remote_store_path, upload_path, expire_at),
StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options) StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options)
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
= f.check_box :performance_bar_enabled = f.check_box :performance_bar_enabled
Enable the Performance Bar Enable the Performance Bar
.form-group.row .form-group.row
= f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'col-form-label col-sm-2' = f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'col-form-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path = f.text_field :performance_bar_allowed_group_path, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success"
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= f.label :mirror_available do = f.label :mirror_available do
= f.check_box :mirror_available = f.check_box :mirror_available
Allow mirrors to be setup for projects Allow mirrors to be setup for projects
%span.help-block %span.form-text.text-muted
If disabled, only admins will be able to setup mirrors in projects. If disabled, only admins will be able to setup mirrors in projects.
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring') = link_to icon('question-circle'), help_page_path('workflow/repository_mirroring')
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
must be used to authenticate. must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
.form-group.row .form-group.row
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'col-form-label col-sm-2'
= hidden_field_tag 'application_setting[enabled_oauth_sign_in_sources][]' = hidden_field_tag 'application_setting[enabled_oauth_sign_in_sources][]'
.col-sm-10 .col-sm-10
.btn-group{ data: { toggle: 'buttons' } } .btn-group{ data: { toggle: 'buttons' } }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= f.label :enforce_terms do = f.label :enforce_terms do
= f.check_box :enforce_terms = f.check_box :enforce_terms
= _("Require all users to accept Terms of Service when they access GitLab.") = _("Require all users to accept Terms of Service when they access GitLab.")
.help-block .form-text.text-muted
= _("When enabled, users cannot use GitLab until the terms have been accepted.") = _("When enabled, users cannot use GitLab until the terms have been accepted.")
.form-group .form-group
.col-sm-12 .col-sm-12
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
= _("Terms of Service Agreement") = _("Terms of Service Agreement")
.col-sm-12 .col-sm-12
= f.text_area :terms, class: 'form-control', rows: 8 = f.text_area :terms, class: 'form-control', rows: 8
.help-block .form-text.text-muted
= _("Markdown enabled") = _("Markdown enabled")
= f.submit _("Save changes"), class: "btn btn-success" = f.submit _("Save changes"), class: "btn btn-success"
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.form-check .form-check
= level = level
%span.form-text.text-muted#restricted-visibility-help %span.form-text.text-muted#restricted-visibility-help
Selected levels cannot be used by non-admin users for projects or snippets. Selected levels cannot be used by non-admin users for groups, projects or snippets.
If the public level is restricted, user profiles are only visible to logged in users. If the public level is restricted, user profiles are only visible to logged in users.
.form-group.row .form-group.row
= f.label :import_sources, class: 'col-form-label col-sm-2' = f.label :import_sources, class: 'col-form-label col-sm-2'
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
= link_to admin_projects_path do = link_to admin_projects_path do
%h3.text-center %h3.text-center
Projects: Projects:
= approximate_count_with_delimiters(Project) = approximate_count_with_delimiters(@counts, Project)
%hr %hr
= link_to('New project', new_project_path, class: "btn btn-new") = link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4 .col-sm-4
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= link_to admin_users_path do = link_to admin_users_path do
%h3.text-center %h3.text-center
Users: Users:
= approximate_count_with_delimiters(User) = approximate_count_with_delimiters(@counts, User)
= render_if_exists 'users_statistics' = render_if_exists 'users_statistics'
%hr %hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new" = link_to 'New user', new_admin_user_path, class: "btn btn-new"
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
= link_to admin_groups_path do = link_to admin_groups_path do
%h3.text-center %h3.text-center
Groups: Groups:
= approximate_count_with_delimiters(Group) = approximate_count_with_delimiters(@counts, Group)
%hr %hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new" = link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row .row
...@@ -42,31 +42,31 @@ ...@@ -42,31 +42,31 @@
%p %p
Forks Forks
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(ForkedProjectLink) = approximate_count_with_delimiters(@counts, ForkedProjectLink)
%p %p
Issues Issues
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Issue) = approximate_count_with_delimiters(@counts, Issue)
%p %p
Merge Requests Merge Requests
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(MergeRequest) = approximate_count_with_delimiters(@counts, MergeRequest)
%p %p
Notes Notes
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Note) = approximate_count_with_delimiters(@counts, Note)
%p %p
Snippets Snippets
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Snippet) = approximate_count_with_delimiters(@counts, Snippet)
%p %p
SSH Keys SSH Keys
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Key) = approximate_count_with_delimiters(@counts, Key)
%p %p
Milestones Milestones
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Milestone) = approximate_count_with_delimiters(@counts, Milestone)
%p %p
Active Users Active Users
%span.light.float-right %span.light.float-right
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.card .card
.card-header .card-header
Group info: Group info:
%ul.well-list %ul.content-list
%li %li
.avatar-container.s60 .avatar-container.s60
= group_icon(@group, class: "avatar s60") = group_icon(@group, class: "avatar s60")
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
Projects Projects
%span.badge.badge-pill %span.badge.badge-pill
#{@group.projects.count} #{@group.projects.count}
%ul.well-list %ul.content-list
- @projects.each do |project| - @projects.each do |project|
%li %li
%strong %strong
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
Projects shared with #{@group.name} Projects shared with #{@group.name}
%span.badge.badge-pill %span.badge.badge-pill
#{@group.shared_projects.count} #{@group.shared_projects.count}
%ul.well-list %ul.content-list
- @group.shared_projects.sort_by(&:name).each do |project| - @group.shared_projects.sort_by(&:name).each do |project|
%li %li
%strong %strong
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
%span.badge.badge-pill= @group.members.size %span.badge.badge-pill= @group.members.size
.float-right .float-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-sm" = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-sm"
%ul.well-list.group-users-list.content-list.members-list %ul.content-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.card-footer .card-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab' = paginate @members, param_name: 'members_page', theme: 'gitlab'
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
.card .card
.card-header .card-header
Project info: Project info:
%ul.well-list %ul.content-list
%li %li
%span.light Name: %span.light Name:
%strong %strong
...@@ -166,7 +166,7 @@ ...@@ -166,7 +166,7 @@
.float-right .float-right
= link_to admin_group_path(@group), class: 'btn btn-sm' do = link_to admin_group_path(@group), class: 'btn btn-sm' do
= icon('pencil-square-o', text: 'Manage access') = icon('pencil-square-o', text: 'Manage access')
%ul.well-list.content-list.members-list %ul.content-list.members-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.card-footer .card-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
...@@ -180,7 +180,7 @@ ...@@ -180,7 +180,7 @@
%span.badge.badge-pill= @project.users.size %span.badge.badge-pill= @project.users.size
.float-right .float-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm" = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm"
%ul.well-list.project_members.content-list.members-list %ul.content-list.project_members.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.card-footer .card-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
.card .card
.card-header .card-header
Profile Profile
%ul.well-list %ul.content-list
%li %li
%span.light Member since %span.light Member since
%strong= user.created_at.to_s(:medium) %strong= user.created_at.to_s(:medium)
......
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.
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