summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo.yml1
-rw-r--r--CHANGELOG.md16
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile11
-rw-r--r--Gemfile.lock30
-rw-r--r--app/assets/javascripts/boards/models/milestone.js11
-rw-r--r--app/assets/javascripts/diffs/components/app.vue7
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue2
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue4
-rw-r--r--app/assets/javascripts/diffs/constants.js2
-rw-r--r--app/assets/javascripts/diffs/index.js2
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_form.js2
-rw-r--r--app/assets/javascripts/event_tracking/notes.js1
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue8
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue6
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue2
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js8
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/mr_popover/index.js6
-rw-r--r--app/assets/javascripts/notes.js8
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue6
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue2
-rw-r--r--app/assets/javascripts/notes/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js17
-rw-r--r--app/assets/stylesheets/framework/common.scss5
-rw-r--r--app/assets/stylesheets/framework/forms.scss4
-rw-r--r--app/assets/stylesheets/framework/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/builds.scss47
-rw-r--r--app/assets/stylesheets/pages/issuable.scss11
-rw-r--r--app/assets/stylesheets/pages/notes.scss8
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss7
-rw-r--r--app/assets/stylesheets/pages/reports.scss5
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-rw-r--r--app/models/application_setting.rb4
-rw-r--r--app/models/concerns/group_descendant.rb4
-rw-r--r--app/models/concerns/token_authenticatable_strategies/base.rb2
-rw-r--r--app/models/concerns/token_authenticatable_strategies/encrypted.rb6
-rw-r--r--app/models/milestone.rb10
-rw-r--r--app/models/project.rb33
-rw-r--r--app/models/project_group_link.rb4
-rw-r--r--app/models/project_services/asana_service.rb8
-rw-r--r--app/models/project_services/bamboo_service.rb12
-rw-r--r--app/models/project_services/discord_service.rb4
-rw-r--r--app/models/project_services/emails_on_push_service.rb14
-rw-r--r--app/models/project_services/external_wiki_service.rb6
-rw-r--r--app/models/project_services/flowdock_service.rb4
-rw-r--r--app/models/project_services/hipchat_service.rb311
-rw-r--r--app/models/project_services/jira_service.rb20
-rw-r--r--app/models/project_services/pipelines_email_service.rb6
-rw-r--r--app/models/project_services/pivotaltracker_service.rb8
-rw-r--r--app/models/project_services/pushover_service.rb26
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/u2f_registration.rb2
-rw-r--r--app/models/upload.rb6
-rw-r--r--app/models/user.rb22
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/services/clusters/applications/install_service.rb9
-rw-r--r--app/services/clusters/applications/patch_service.rb10
-rw-r--r--app/services/clusters/applications/upgrade_service.rb36
-rw-r--r--app/services/groups/create_service.rb6
-rw-r--r--app/services/users/activity_service.rb2
-rw-r--r--app/views/discussions/_diff_discussion.html.haml2
-rw-r--r--app/views/discussions/_parallel_diff_discussion.html.haml8
-rw-r--r--app/views/projects/_classification_policy_settings.html.haml6
-rw-r--r--app/views/projects/_export.html.haml67
-rw-r--r--app/views/projects/deployments/_actions.haml2
-rw-r--r--app/views/projects/diffs/_text_file.html.haml2
-rw-r--r--app/views/projects/edit.html.haml329
-rw-r--r--app/views/projects/environments/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml3
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml6
-rw-r--r--app/views/projects/settings/_general.html.haml42
-rw-r--r--app/views/shared/_confirm_modal.html.haml8
-rw-r--r--changelogs/unreleased/59921-pipeline-schedule.yml5
-rw-r--r--changelogs/unreleased/60224-btn-env.yml5
-rw-r--r--changelogs/unreleased/60241-merge-request-popover-doesn-t-go-away-on-mouse-leave.yml5
-rw-r--r--changelogs/unreleased/60261-save-btn-env.yml5
-rw-r--r--changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml5
-rw-r--r--changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml5
-rw-r--r--changelogs/unreleased/fix-api-group-visibility.yml5
-rw-r--r--changelogs/unreleased/fix-pull-request-importer.yml5
-rw-r--r--changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml5
-rw-r--r--changelogs/unreleased/sh-disable-diff-instrumentation.yml5
-rw-r--r--config/initializers/hipchat_client_patch.rb15
-rw-r--r--db/migrate/20190107151029_remove_hipchat_services.rb16
-rw-r--r--doc/administration/high_availability/nfs.md11
-rw-r--r--doc/administration/pages/index.md4
-rw-r--r--doc/api/services.md39
-rw-r--r--doc/integration/README.md4
-rw-r--r--doc/project_services/hipchat.md1
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/user/index.md4
-rw-r--r--doc/user/project/integrations/hipchat.md53
-rw-r--r--doc/user/project/integrations/project_services.md1
-rw-r--r--lib/api/helpers/services_helpers.rb40
-rw-r--r--lib/api/import_github.rb4
-rw-r--r--lib/gitlab/visibility_level.rb2
-rw-r--r--locale/gitlab.pot442
-rw-r--r--qa/Dockerfile2
-rw-r--r--qa/Gemfile.lock2
-rw-r--r--qa/qa/page/project/settings/advanced.rb20
-rw-r--r--qa/qa/page/project/settings/common.rb8
-rw-r--r--qa/qa/page/project/settings/main.rb18
-rw-r--r--qa/spec/specs/runner_spec.rb4
-rw-r--r--spec/factories/services.rb6
-rw-r--r--spec/features/admin/admin_hooks_spec.rb10
-rw-r--r--spec/features/projects/commit/comments/user_adds_comment_spec.rb4
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb3
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb5
-rw-r--r--spec/features/projects/services/user_activates_hipchat_spec.rb40
-rw-r--r--spec/features/projects/services/user_views_services_spec.rb3
-rw-r--r--spec/features/projects/settings/user_renames_a_project_spec.rb37
-rw-r--r--spec/features/projects_spec.rb15
-rw-r--r--spec/features/u2f_spec.rb20
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js2
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js20
-rw-r--r--spec/frontend/mr_popover/index_spec.js30
-rw-r--r--spec/javascripts/diffs/components/app_spec.js8
-rw-r--r--spec/javascripts/notes_spec.js2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/project.json22
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb8
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb410
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/wiki_page_spec.rb8
-rw-r--r--spec/requests/api/pipelines_spec.rb6
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb6
-rw-r--r--spec/services/clusters/applications/patch_service_spec.rb6
-rw-r--r--spec/services/clusters/applications/upgrade_service_spec.rb6
-rw-r--r--spec/services/groups/create_service_spec.rb11
-rw-r--r--spec/services/users/activity_service_spec.rb2
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb4
-rw-r--r--vendor/licenses.csv1
135 files changed, 2110 insertions, 656 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 97e39ce99cb..77ad4753c84 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -268,6 +268,7 @@ Rails/Presence:
- 'app/models/clusters/platforms/kubernetes.rb'
- 'app/models/concerns/mentionable.rb'
- 'app/models/concerns/token_authenticatable.rb'
+ - 'app/models/project_services/hipchat_service.rb'
- 'app/models/project_services/irker_service.rb'
- 'app/models/project_services/jira_service.rb'
- 'app/models/project_services/kubernetes_service.rb'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e91b9f29ff..41506746c98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.9.8 (2019-04-11)
+
+### Deprecated (1 change)
+
+- Allow to use untrusted Regexp via feature flag. !26905
+
+### Performance (2 changes)
+
+- Improve performance of PR import. !27121
+- Disable method instrumentation for diffs. !27235
+
+### Other (1 change)
+
+- Restore HipChat project service. !27172
+
+
## 11.9.7 (2019-04-09)
- No changes.
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index f9c71a52e2f..acd405b1d62 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.5.1
+8.6.0
diff --git a/Gemfile b/Gemfile
index 6052018754a..5f345f4329a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -201,6 +201,9 @@ gem 'connection_pool', '~> 2.0'
# Discord integration
gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
+# HipChat integration
+gem 'hipchat', '~> 1.5.0'
+
# JIRA integration
gem 'jira-ruby', '~> 1.4'
@@ -342,14 +345,14 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.10'
- gem 'capybara', '~> 2.16.1'
- gem 'capybara-screenshot', '~> 1.0.18'
- gem 'selenium-webdriver', '~> 3.12'
+ gem 'capybara', '~> 2.18.0'
+ gem 'capybara-screenshot', '~> 1.0.22'
+ gem 'selenium-webdriver', '~> 3.141'
gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4'
- gem 'gitlab-styles', '~> 2.4', require: false
+ gem 'gitlab-styles', '~> 2.5', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines
gem 'rubocop', '~> 0.54.0'
gem 'rubocop-rspec', '~> 1.22.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index b522aa85b39..c1d1a50799f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -100,13 +100,13 @@ GEM
bundler (~> 1.2)
thor (~> 0.18)
byebug (9.1.0)
- capybara (2.16.1)
+ capybara (2.18.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
- xpath (~> 2.0)
+ xpath (>= 2.0, < 4.0)
capybara-screenshot (1.0.22)
capybara (>= 1.0, < 4)
launchy
@@ -289,7 +289,7 @@ GEM
gitlab-markup (1.7.0)
gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
- gitlab-styles (2.5.1)
+ gitlab-styles (2.5.2)
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19)
@@ -366,6 +366,9 @@ GEM
hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
+ hipchat (1.5.2)
+ httparty
+ mimemagic
html-pipeline (2.8.4)
activesupport (>= 2)
nokogiri (>= 1.4)
@@ -485,7 +488,7 @@ GEM
net-ssh (5.0.1)
netrc (0.11.0)
nio4r (2.3.1)
- nokogiri (1.10.1)
+ nokogiri (1.10.2)
mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
@@ -703,7 +706,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.6.0)
redis (>= 2.2, < 5)
- regexp_parser (1.3.0)
+ regexp_parser (1.4.0)
regexp_property_values (0.3.4)
representable (3.0.4)
declarative (< 0.1.0)
@@ -812,9 +815,9 @@ GEM
seed-fu (2.3.7)
activerecord (>= 3.1)
activesupport (>= 3.1)
- selenium-webdriver (3.12.0)
+ selenium-webdriver (3.141.0)
childprocess (~> 0.5)
- rubyzip (~> 1.2)
+ rubyzip (~> 1.2, >= 1.2.2)
sentry-raven (2.9.0)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
@@ -940,8 +943,8 @@ GEM
rinku
with_env (1.1.0)
xml-simple (1.1.5)
- xpath (2.1.0)
- nokogiri (~> 1.3)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
PLATFORMS
ruby
@@ -971,8 +974,8 @@ DEPENDENCIES
browser (~> 2.5)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
- capybara (~> 2.16.1)
- capybara-screenshot (~> 1.0.18)
+ capybara (~> 2.18.0)
+ capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3)
charlock_holmes (~> 0.7.5)
chronic (~> 0.10.2)
@@ -1022,7 +1025,7 @@ DEPENDENCIES
gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.7.0)
gitlab-sidekiq-fetcher (~> 0.4.0)
- gitlab-styles (~> 2.4)
+ gitlab-styles (~> 2.5)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.23)
@@ -1040,6 +1043,7 @@ DEPENDENCIES
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
health_check (~> 2.6.0)
+ hipchat (~> 1.5.0)
html-pipeline (~> 2.8)
html2text
httparty (~> 0.16.4)
@@ -1142,7 +1146,7 @@ DEPENDENCIES
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
- selenium-webdriver (~> 3.12)
+ selenium-webdriver (~> 3.141)
sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
diff --git a/app/assets/javascripts/boards/models/milestone.js b/app/assets/javascripts/boards/models/milestone.js
index 17d15278a74..6f81d6bc6f8 100644
--- a/app/assets/javascripts/boards/models/milestone.js
+++ b/app/assets/javascripts/boards/models/milestone.js
@@ -1,7 +1,16 @@
-class ListMilestone {
+import { isEE } from '~/lib/utils/common_utils';
+
+export default class ListMilestone {
constructor(obj) {
this.id = obj.id;
this.title = obj.title;
+
+ if (isEE) {
+ this.path = obj.path;
+ this.state = obj.state;
+ this.webUrl = obj.web_url || obj.webUrl;
+ this.description = obj.description;
+ }
}
}
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 5e74998579b..0ed4dcdcd81 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -64,6 +64,11 @@ export default {
required: false,
default: '',
},
+ isFluidLayout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
const treeWidth =
@@ -116,7 +121,7 @@ export default {
return this.treeWidth <= TREE_HIDE_STATS_WIDTH;
},
isLimitedContainer() {
- return !this.showTreeList && !this.isParallelView;
+ return !this.showTreeList && !this.isParallelView && !this.isFluidLayout;
},
},
watch: {
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index 69146f1f6fd..1faa0493e79 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -41,7 +41,7 @@ export default {
<template>
<tr v-if="shouldRender" :class="className" class="notes_holder">
- <td class="notes_content" colspan="3">
+ <td class="notes-content" colspan="3">
<div class="content">
<diff-discussions
v-if="line.discussions.length"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index 370cb6e339a..d2e54edca85 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -87,7 +87,7 @@ export default {
<template>
<tr v-if="shouldRender" :class="className" class="notes_holder">
- <td class="notes_content parallel old" colspan="2">
+ <td class="notes-content parallel old" colspan="2">
<div v-if="shouldRenderDiscussionsOnLeft" class="content">
<diff-discussions
v-if="line.left.discussions.length"
@@ -105,7 +105,7 @@ export default {
line-position="left"
/>
</td>
- <td class="notes_content parallel new" colspan="2">
+ <td class="notes-content parallel new" colspan="2">
<div v-if="shouldRenderDiscussionsOnRight" class="content">
<diff-discussions
v-if="line.right.discussions.length"
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 5dabe224baa..4feb73cfef2 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -49,4 +49,4 @@ export const TYPE_KEY = 'type';
export const LEFT_LINE_KEY = 'left';
export const CENTERED_LIMITED_CONTAINER_CLASSES =
- 'container-limited limit-container-width mx-auto px-3';
+ 'container-limited limit-container-width mx-lg-auto px-3';
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index 63954d9d412..1d897bca1dd 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -71,6 +71,7 @@ export default function initDiffsApp(store) {
helpPagePath: dataset.helpPagePath,
currentUser: JSON.parse(dataset.currentUserData) || {},
changesEmptyStateIllustration: dataset.changesEmptyStateIllustration,
+ isFluidLayout: parseBoolean(dataset.isFluidLayout),
};
},
computed: {
@@ -97,6 +98,7 @@ export default function initDiffsApp(store) {
helpPagePath: this.helpPagePath,
shouldShow: this.activeTab === 'diffs',
changesEmptyStateIllustration: this.changesEmptyStateIllustration,
+ isFluidLayout: this.isFluidLayout,
},
});
},
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_form.js b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
index 00e41dd0301..765969daa32 100644
--- a/app/assets/javascripts/dirty_submit/dirty_submit_form.js
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import $ from 'jquery';
class DirtySubmitForm {
constructor(form) {
@@ -26,6 +27,7 @@ class DirtySubmitForm {
);
this.form.addEventListener('input', throttledUpdateDirtyInput);
this.form.addEventListener('change', throttledUpdateDirtyInput);
+ $(this.form).on('change.select2', throttledUpdateDirtyInput);
this.form.addEventListener('submit', event => this.formSubmit(event));
}
diff --git a/app/assets/javascripts/event_tracking/notes.js b/app/assets/javascripts/event_tracking/notes.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/app/assets/javascripts/event_tracking/notes.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 0670e2b06b9..7594edfac27 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -275,7 +275,7 @@ export default {
<!-- job log -->
<div
v-if="hasTrace"
- class="build-trace-container"
+ class="build-trace-container position-relative"
:class="{ 'prepend-top-default': !job.archived }"
>
<log-top-bar
diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue
index 845699a90b5..a55dffbe488 100644
--- a/app/assets/javascripts/jobs/components/job_container_item.vue
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -43,7 +43,7 @@ export default {
<template>
<div
- class="build-job"
+ class="build-job position-relative"
:class="{
retried: job.retried,
active: isActive,
@@ -56,7 +56,11 @@ export default {
data-boundary="viewport"
class="js-job-link"
>
- <icon v-if="isActive" name="arrow-right" class="js-arrow-right icon-arrow-right" />
+ <icon
+ v-if="isActive"
+ name="arrow-right"
+ class="js-arrow-right icon-arrow-right position-absolute d-block"
+ />
<ci-icon :status="job.status" />
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 52e14f954ee..607b2bd1c74 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -75,7 +75,11 @@ export default {
<template v-if="isTraceSizeVisible">
{{ jobLogSize }}
- <gl-link v-if="rawPath" :href="rawPath" class="js-raw-link raw-link">
+ <gl-link
+ v-if="rawPath"
+ :href="rawPath"
+ class="js-raw-link text-plain text-underline prepend-left-5"
+ >
{{ s__('Job|Complete Raw') }}
</gl-link>
</template>
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index 997737b3e23..922f64d93fe 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -52,7 +52,7 @@ export default {
</p>
<template v-if="hasVariables">
- <p class="trigger-variables-btn-container">
+ <p class="trigger-variables-btn-container d-flex">
<span class="font-weight-bold">{{ __('Trigger variables:') }}</span>
<gl-button
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 1b7f8732c65..cc1d85fd97d 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -44,18 +44,18 @@ export const pluralize = (str, count) => str + (count > 1 || count === 0 ? 's' :
export const dasherize = str => str.replace(/[_\s]+/g, '-');
/**
- * Removes accents and converts to lower case
+ * Replaces whitespaces with hyphens and converts to lower case
* @param {String} str
* @returns {String}
*/
-export const slugify = str => str.trim().toLowerCase();
+export const slugifyWithHyphens = str => str.toLowerCase().replace(/\s+/g, '-');
/**
- * Replaces whitespaces with hyphens and converts to lower case
+ * Replaces whitespaces with underscore and converts to lower case
* @param {String} str
* @returns {String}
*/
-export const slugifyWithHyphens = str => str.toLowerCase().replace(/\s+/g, '-');
+export const slugifyWithUnderscore = str => str.toLowerCase().replace(/\s+/g, '_');
/**
* Truncates given text
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 2f15da42271..509f19e6f00 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -398,7 +398,7 @@ export default class MergeRequestTabs {
const hash = getLocationHash();
const anchor = hash && $container.find(`.note[id="${hash}"]`);
if (anchor && anchor.length > 0) {
- const notesContent = anchor.closest('.notes_content');
+ const notesContent = anchor.closest('.notes-content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
Notes.instance.toggleDiffNote({
target: anchor,
diff --git a/app/assets/javascripts/mr_popover/index.js b/app/assets/javascripts/mr_popover/index.js
index cc686b401d2..9a97e98f9db 100644
--- a/app/assets/javascripts/mr_popover/index.js
+++ b/app/assets/javascripts/mr_popover/index.js
@@ -54,9 +54,13 @@ export default elements => {
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
+ const listenerAddedAttr = 'data-mr-listener-added';
mrLinks.forEach(el => {
- el.addEventListener('mouseenter', handleMRPopoverMount(apolloProvider));
+ if (!el.getAttribute(listenerAddedAttr)) {
+ el.addEventListener('mouseenter', handleMRPopoverMount(apolloProvider));
+ el.setAttribute(listenerAddedAttr, true);
+ }
});
}
};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 94d2e2b53e9..36725e22365 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -506,7 +506,7 @@ export default class Notes {
var contentContainerClass =
'.' +
$notes
- .closest('.notes_content')
+ .closest('.notes-content')
.attr('class')
.split(' ')
.join('.');
@@ -1069,14 +1069,14 @@ export default class Notes {
addForm = false;
let lineTypeSelector = '';
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_content" colspan="3"><div class="content"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes-content" colspan="3"><div class="content"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes-content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes-content parallel new"><div class="content"></div></td></tr>';
}
- const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
+ const notesContentSelector = `.notes-content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
if (hasNotes && showReplyInput) {
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index b30d7fa9b73..caf22f71bf9 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -11,6 +11,7 @@ import {
capitalizeFirstCharacter,
convertToCamelCase,
splitCamelCase,
+ slugifyWithUnderscore,
} from '../../lib/utils/text_utility';
import * as constants from '../constants';
import eventHub from '../event_hub';
@@ -129,6 +130,9 @@ export default {
? 'merge request'
: 'issue';
},
+ trackingLabel() {
+ return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
+ },
},
watch: {
note(newNote) {
@@ -370,6 +374,8 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-success js-comment-button js-comment-submit-button
qa-comment-button"
type="submit"
+ :data-track-label="trackingLabel"
+ data-track-event="click_button"
@click.prevent="handleSave()"
>
{{ __(commentButtonTitle) }}
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index ab758a9e922..b95835ed10a 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -105,7 +105,7 @@ export default {
</td>
</tr>
<tr class="notes_holder">
- <td class="notes_content" colspan="3"><slot></slot></td>
+ <td class="notes-content" colspan="3"><slot></slot></td>
</tr>
</table>
</div>
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 30372103590..57dd1c5cab2 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,4 +1,6 @@
import Vue from 'vue';
+import { isEE } from '~/lib/utils/common_utils';
+import initNoteStats from 'ee_else_ce/event_tracking/notes';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
import createStore from './stores';
@@ -38,6 +40,11 @@ document.addEventListener('DOMContentLoaded', () => {
notesData: JSON.parse(notesDataset.notesData),
};
},
+ mounted() {
+ if (isEE) {
+ initNoteStats();
+ }
+ },
render(createElement) {
return createElement('notes-app', {
props: {
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 278c35d3846..92ed6a652d7 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -3,17 +3,24 @@ import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initAvatarPicker from '~/avatar_picker';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
document.addEventListener('DOMContentLoaded', () => {
- initProjectLoadingSpinner();
- setupProjectEdit();
- // Initialize expandable settings panels
- initSettingsPanels();
initAvatarPicker();
- initProjectPermissionsSettings();
initConfirmDangerModal();
+ initSettingsPanels();
mountBadgeSettings(PROJECT_BADGE);
+
+ initProjectLoadingSpinner();
+ initProjectPermissionsSettings();
+ setupProjectEdit();
+
+ dirtySubmitFactory(
+ document.querySelectorAll(
+ '.js-general-settings-form, .js-mr-settings-form, .js-mr-approvals-form',
+ ),
+ );
});
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index c3031c3375e..be0073559b6 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -188,11 +188,6 @@ li.note {
background-color: inherit;
}
-.show-suppressed-diff,
-.show-all-commits {
- cursor: pointer;
-}
-
.error-message {
padding: 10px;
background: $red-400;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 1c23c14c2de..be544c0a814 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -157,6 +157,10 @@ label {
padding-left: 10px;
padding-right: 10px;
appearance: none;
+ /* stylelint-disable property-no-vendor-prefix */
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ /* stylelint-enable property-no-vendor-prefix */
&::-ms-expand {
display: none;
diff --git a/app/assets/stylesheets/framework/notes.scss b/app/assets/stylesheets/framework/notes.scss
index d349e3fad9c..85ddf11d6fe 100644
--- a/app/assets/stylesheets/framework/notes.scss
+++ b/app/assets/stylesheets/framework/notes.scss
@@ -4,7 +4,7 @@
}
// Diff is side by side
- .notes_content.parallel & {
+ .notes-content.parallel & {
// We hide at double what we normally hide at because
// there are two columns of notes
@media (#{$condition}-width: (2 * $breakpoint-width)) {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 916f6cd3137..6fc742871e7 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -46,10 +46,6 @@
}
.build-page {
- .build-trace-container {
- position: relative;
- }
-
.build-trace {
@include build-trace();
}
@@ -104,18 +100,6 @@
top: 0;
}
- .truncated-info {
- .truncated-info-size {
- margin: 0 5px;
- }
-
- .raw-link {
- color: $gl-text-color;
- margin-left: 5px;
- text-decoration: underline;
- }
- }
-
.controllers {
@include build-controllers(15px, center, false, 0, inline, 0);
}
@@ -142,12 +126,6 @@
}
}
-.with-performance-bar .build-page {
- .top-bar.affix {
- top: $header-height + $performance-bar-height;
- }
-}
-
.build-header {
.ci-header-container,
.header-action-buttons {
@@ -233,7 +211,6 @@
}
.trigger-variables-btn-container {
- @extend .d-flex;
justify-content: space-between;
align-items: center;
@@ -277,12 +254,6 @@
.retry-link {
display: block;
- .btn {
- i {
- margin-left: 5px;
- }
- }
-
.btn-inverted-secondary {
color: $blue-500;
@@ -329,16 +300,12 @@
}
}
- .build-job {
- position: relative;
-
- .icon-arrow-right {
- position: absolute;
- left: 15px;
- top: 20px;
- display: block;
- }
+ .icon-arrow-right {
+ left: 15px;
+ top: 20px;
+ }
+ .build-job {
&.active {
font-weight: $gl-font-weight-bold;
}
@@ -350,10 +317,6 @@
&:hover {
background-color: $gray-darker;
}
-
- .icon-retry {
- margin-left: 3px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9be3f8138a0..fbd291f095a 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,8 +1,3 @@
-// Limit MR description for side-by-side diff view
-.fixed-width-container {
- @include fixed-width-container;
-}
-
.issuable-warning-icon {
background-color: $orange-100;
border-radius: $border-radius-default;
@@ -27,7 +22,7 @@
.files-changed-inner,
.limited-header-width,
.limited-width-notes {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
.issuable-details {
@@ -35,13 +30,13 @@
.mr-source-target,
.mr-state-widget,
.merge-manually {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
}
.merge-request-details {
.emoji-list-container {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index fd07415a52f..09f75cd827f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -68,7 +68,7 @@ $note-form-margin-left: 72px;
}
}
- .notes_content {
+ .notes-content {
border: 0;
border-top: 1px solid $border-color;
}
@@ -453,7 +453,7 @@ $note-form-margin-left: 72px;
// Merge request notes in diffs
// Diff is inline
- .notes_content .note-header .note-headline-light {
+ .notes-content .note-header .note-headline-light {
display: inline-block;
position: relative;
}
@@ -465,7 +465,7 @@ $note-form-margin-left: 72px;
border: 1px solid $border-color;
border-left: 0;
- &.notes_content {
+ &.notes-content {
border-width: 1px 0;
padding: 0;
vertical-align: top;
@@ -512,7 +512,7 @@ $note-form-margin-left: 72px;
}
.commit-diff {
- .notes_content {
+ .notes-content {
background-color: $white-light;
}
}
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index 617b3db2fae..85c4902eee2 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -1,9 +1,4 @@
-.js-pipeline-schedule-form {
- .dropdown-select,
- .dropdown-menu-toggle {
- width: 100% !important;
- }
-
+.pipeline-schedule-form {
.gl-field-error {
margin: 10px 0 0;
}
diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss
index f7619ccbd20..94da72622af 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -52,11 +52,6 @@
.report-block-list-icon .loading-container {
position: relative;
left: -2px;
- // needed to make the next element align with the
- // elements below that have a svg with 16px width
- .fa-spinner {
- width: 16px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 7b0538dca20..0a9c56f5625 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -39,7 +39,7 @@
.settings-header {
position: relative;
- padding: 20px 110px 10px 0;
+ padding: 20px 110px 0 0;
h4 {
margin-top: 0;
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index eb6ddaac871..2f9b4c4eaa2 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -198,7 +198,7 @@ class ApplicationSetting < ApplicationRecord
validates_each :restricted_visibility_levels do |record, attr, value|
value&.each do |level|
unless Gitlab::VisibilityLevel.options.value?(level)
- record.errors.add(attr, "'#{level}' is not a valid visibility level")
+ record.errors.add(attr, _("'%{level}' is not a valid visibility level") % { level: level })
end
end
end
@@ -206,7 +206,7 @@ class ApplicationSetting < ApplicationRecord
validates_each :import_sources do |record, attr, value|
value&.each do |source|
unless Gitlab::ImportSources.options.value?(source)
- record.errors.add(attr, "'#{source}' is not a import source")
+ record.errors.add(attr, _("'%{source}' is not a import source") % { source: source })
end
end
end
diff --git a/app/models/concerns/group_descendant.rb b/app/models/concerns/group_descendant.rb
index 05cd4265133..cfffd845e43 100644
--- a/app/models/concerns/group_descendant.rb
+++ b/app/models/concerns/group_descendant.rb
@@ -22,7 +22,7 @@ module GroupDescendant
return [] if descendants.empty?
unless descendants.all? { |hierarchy| hierarchy.is_a?(GroupDescendant) }
- raise ArgumentError.new('element is not a hierarchy')
+ raise ArgumentError.new(_('element is not a hierarchy'))
end
all_hierarchies = descendants.map do |descendant|
@@ -56,7 +56,7 @@ module GroupDescendant
end
if parent.nil? && hierarchy_top.present?
- raise ArgumentError.new('specified top is not part of the tree')
+ raise ArgumentError.new(_('specified top is not part of the tree'))
end
if parent && parent != hierarchy_top
diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb
index df14e6e4754..aafd0b538a3 100644
--- a/app/models/concerns/token_authenticatable_strategies/base.rb
+++ b/app/models/concerns/token_authenticatable_strategies/base.rb
@@ -41,7 +41,7 @@ module TokenAuthenticatableStrategies
def self.fabricate(model, field, options)
if options[:digest] && options[:encrypted]
- raise ArgumentError, 'Incompatible options set!'
+ raise ArgumentError, _('Incompatible options set!')
end
if options[:digest]
diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb
index 2c7fa2c5b3c..4728cb658dc 100644
--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb
+++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb
@@ -13,7 +13,7 @@ module TokenAuthenticatableStrategies
elsif migrating?
find_by_plaintext_token(token, unscoped)
else
- raise ArgumentError, "Unknown encryption strategy: #{encrypted_strategy}!"
+ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy }
end
end
@@ -32,7 +32,7 @@ module TokenAuthenticatableStrategies
return super if instance.has_attribute?(encrypted_field)
if required?
- raise ArgumentError, 'Using required encryption strategy when encrypted field is missing!'
+ raise ArgumentError, _('Using required encryption strategy when encrypted field is missing!')
else
insecure_strategy.ensure_token(instance)
end
@@ -74,7 +74,7 @@ module TokenAuthenticatableStrategies
value = value.call if value.is_a?(Proc)
unless value.in?([:required, :optional, :migrating])
- raise ArgumentError, 'encrypted: needs to be a :required, :optional or :migrating!'
+ raise ArgumentError, _('encrypted: needs to be a :required, :optional or :migrating!')
end
value
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index b4aad9e512e..787600569fa 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -291,22 +291,22 @@ class Milestone < ApplicationRecord
end
title_exists = relation.find_by_title(title)
- errors.add(:title, "already being used for another group or project milestone.") if title_exists
+ errors.add(:title, _("already being used for another group or project milestone.")) if title_exists
end
# Milestone should be either a project milestone or a group milestone
def milestone_type_check
if group_id && project_id
field = project_id_changed? ? :project_id : :group_id
- errors.add(field, "milestone should belong either to a project or a group.")
+ errors.add(field, _("milestone should belong either to a project or a group."))
end
end
def milestone_format_reference(format = :iid)
- raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
+ raise ArgumentError, _('Unknown format') unless [:iid, :name].include?(format)
if group_milestone? && format == :iid
- raise ArgumentError, 'Cannot refer to a group milestone by an internal id!'
+ raise ArgumentError, _('Cannot refer to a group milestone by an internal id!')
end
if format == :name && !name.include?('"')
@@ -322,7 +322,7 @@ class Milestone < ApplicationRecord
def start_date_should_be_less_than_due_date
if due_date <= start_date
- errors.add(:due_date, "must be greater than start date")
+ errors.add(:due_date, _("must be greater than start date"))
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 89ad90a964e..2fb6f5cb6a7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -146,6 +146,7 @@ class Project < ApplicationRecord
has_one :pipelines_email_service
has_one :irker_service
has_one :pivotaltracker_service
+ has_one :hipchat_service
has_one :flowdock_service
has_one :assembla_service
has_one :asana_service
@@ -312,7 +313,7 @@ class Project < ApplicationRecord
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path,
format: { without: %r{(\.{2}|\A/)},
- message: 'cannot include leading slash or directory traversal.' },
+ message: _('cannot include leading slash or directory traversal.') },
length: { maximum: 255 },
allow_blank: true
validates :name,
@@ -419,13 +420,13 @@ class Project < ApplicationRecord
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
- default: 3600, error_message: 'Maximum job timeout has a value which could not be accepted'
+ default: 3600, error_message: _('Maximum job timeout has a value which could not be accepted')
validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 10.minutes,
less_than: 1.month,
only_integer: true,
- message: 'needs to be beetween 10 minutes and 1 month' }
+ message: _('needs to be beetween 10 minutes and 1 month') }
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map, AttachmentUploader
@@ -849,7 +850,7 @@ class Project < ApplicationRecord
def mark_stuck_remote_mirrors_as_failed!
remote_mirrors.stuck.update_all(
update_status: :failed,
- last_error: 'The remote mirror took to long to complete.',
+ last_error: _('The remote mirror took to long to complete.'),
last_update_at: Time.now
)
end
@@ -886,14 +887,14 @@ class Project < ApplicationRecord
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase
- self.errors.add(:visibility_level, "#{level_name} is not allowed in a #{group_level_name} group.")
+ self.errors.add(:visibility_level, _("%{level_name} is not allowed in a %{group_level_name} group.") % { level_name: level_name, group_level_name: group_level_name })
end
def visibility_level_allowed_as_fork
return if visibility_level_allowed_as_fork?
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
- self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.")
+ self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name })
end
def check_wiki_path_conflict
@@ -902,7 +903,7 @@ class Project < ApplicationRecord
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
if Project.where(namespace_id: namespace_id, path: path_to_check).exists?
- errors.add(:name, 'has already been taken')
+ errors.add(:name, _('has already been taken'))
end
end
@@ -922,7 +923,7 @@ class Project < ApplicationRecord
return unless pages_https_only?
unless pages_domains.all?(&:https?)
- errors.add(:pages_https_only, "cannot be enabled unless all domains have TLS certificates")
+ errors.add(:pages_https_only, _("cannot be enabled unless all domains have TLS certificates"))
end
end
@@ -1202,7 +1203,7 @@ class Project < ApplicationRecord
def valid_repo?
repository.exists?
rescue
- errors.add(:path, 'Invalid repository path')
+ errors.add(:path, _('Invalid repository path'))
false
end
@@ -1293,7 +1294,7 @@ class Project < ApplicationRecord
# Check if repository with same path already exists on disk we can
# skip this for the hashed storage because the path does not change
if legacy_storage? && repository_with_same_path_already_exists?
- errors.add(:base, 'There is already a repository with that name on disk')
+ errors.add(:base, _('There is already a repository with that name on disk'))
return false
end
@@ -1315,7 +1316,7 @@ class Project < ApplicationRecord
repository.after_create
true
else
- errors.add(:base, 'Failed to create repository via gitlab-shell')
+ errors.add(:base, _('Failed to create repository via gitlab-shell'))
false
end
end
@@ -1391,7 +1392,7 @@ class Project < ApplicationRecord
ProjectCacheWorker.perform_async(self.id, [], [:commit_count])
reload_default_branch
else
- errors.add(:base, "Could not change HEAD: branch '#{branch}' does not exist")
+ errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch })
false
end
end
@@ -1443,7 +1444,7 @@ class Project < ApplicationRecord
ProjectWiki.new(self, self.owner).wiki
true
rescue ProjectWiki::CouldNotCreateWikiError
- errors.add(:base, 'Failed create wiki')
+ errors.add(:base, _('Failed create wiki'))
false
end
@@ -1932,7 +1933,7 @@ class Project < ApplicationRecord
#
# @param [Symbol] feature that needs to be rolled out for the project (:repository, :attachments)
def hashed_storage?(feature)
- raise ArgumentError, "Invalid feature" unless HASHED_STORAGE_FEATURES.include?(feature)
+ raise ArgumentError, _("Invalid feature") unless HASHED_STORAGE_FEATURES.include?(feature)
self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
end
@@ -2164,7 +2165,7 @@ class Project < ApplicationRecord
return if skip_disk_validation
if repository_storage.blank? || repository_with_same_path_already_exists?
- errors.add(:base, 'There is already a repository with that name on disk')
+ errors.add(:base, _('There is already a repository with that name on disk'))
throw :abort
end
end
@@ -2210,7 +2211,7 @@ class Project < ApplicationRecord
errors.delete(error)
end
- errors.add(:base, "The project is still being deleted. Please try again later.")
+ errors.add(:base, _("The project is still being deleted. Please try again later."))
end
def pending_delete_twin
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index 58b555c3581..feaf172d48d 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -14,7 +14,7 @@ class ProjectGroupLink < ApplicationRecord
validates :project_id, presence: true
validates :group, presence: true
- validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" }
+ validates :group_id, uniqueness: { scope: [:project_id], message: _("already shared with this group") }
validates :group_access, presence: true
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
validate :different_group
@@ -44,7 +44,7 @@ class ProjectGroupLink < ApplicationRecord
group_ids = project_group.ancestors.map(&:id).push(project_group.id)
if group_ids.include?(self.group.id)
- errors.add(:base, "Project cannot be shared with the group it is in or one of its ancestors.")
+ errors.add(:base, _("Project cannot be shared with the group it is in or one of its ancestors."))
end
end
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index cc5f1207653..3e28dc23782 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -11,7 +11,7 @@ class AsanaService < Service
end
def description
- 'Asana - Teamwork without email'
+ s_('AsanaService|Asana - Teamwork without email')
end
def help
@@ -36,13 +36,13 @@ http://app.asana.com/-/account_api'
{
type: 'text',
name: 'api_key',
- placeholder: 'User Personal Access Token. User must have access to task, all comments will be attributed to this user.',
+ placeholder: s_('AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user.'),
required: true
},
{
type: 'text',
name: 'restrict_to_branch',
- placeholder: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
+ placeholder: s_('AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.')
}
]
end
@@ -73,7 +73,7 @@ http://app.asana.com/-/account_api'
project_name = project.full_name
data[:commits].each do |commit|
- push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):"
+ push_msg = s_("AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):") % { user: user, branch: branch, project_name: project_name, commit_url: commit[:url] }
check_commit(commit[:message], push_msg)
end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 71f5607dbdb..dfeb21680a9 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -31,15 +31,15 @@ class BambooService < CiService
end
def title
- 'Atlassian Bamboo CI'
+ s_('BambooService|Atlassian Bamboo CI')
end
def description
- 'A continuous integration and build server'
+ s_('BambooService|A continuous integration and build server')
end
def help
- 'You must set up automatic revision labeling and a repository trigger in Bamboo.'
+ s_('BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo.')
end
def self.to_param
@@ -49,11 +49,11 @@ class BambooService < CiService
def fields
[
{ type: 'text', name: 'bamboo_url',
- placeholder: 'Bamboo root URL like https://bamboo.example.com', required: true },
+ placeholder: s_('BambooService|Bamboo root URL like https://bamboo.example.com'), required: true },
{ type: 'text', name: 'build_key',
- placeholder: 'Bamboo build plan key like KEY', required: true },
+ placeholder: s_('BambooService|Bamboo build plan key like KEY'), required: true },
{ type: 'text', name: 'username',
- placeholder: 'A user with API access, if applicable' },
+ placeholder: s_('BambooService|A user with API access, if applicable') },
{ type: 'password', name: 'password' }
]
end
diff --git a/app/models/project_services/discord_service.rb b/app/models/project_services/discord_service.rb
index 21afd14dbff..405676792de 100644
--- a/app/models/project_services/discord_service.rb
+++ b/app/models/project_services/discord_service.rb
@@ -4,11 +4,11 @@ require "discordrb/webhooks"
class DiscordService < ChatNotificationService
def title
- "Discord Notifications"
+ s_("DiscordService|Discord Notifications")
end
def description
- "Receive event notifications in Discord"
+ s_("DiscordService|Receive event notifications in Discord")
end
def self.to_param
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index fb73d430fb1..45de64a9990 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -7,11 +7,11 @@ class EmailsOnPushService < Service
validates :recipients, presence: true, if: :valid_recipients?
def title
- 'Emails on push'
+ s_('EmailsOnPushService|Emails on push')
end
def description
- 'Email the commits and diff of each push to a list of recipients.'
+ s_('EmailsOnPushService|Email the commits and diff of each push to a list of recipients.')
end
def self.to_param
@@ -45,11 +45,11 @@ class EmailsOnPushService < Service
def fields
domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ")
[
- { type: 'checkbox', name: 'send_from_committer_email', title: "Send from committer",
- help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." },
- { type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs",
- help: "Don't include possibly sensitive code diffs in notification body." },
- { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }
+ { type: 'checkbox', name: 'send_from_committer_email', title: s_("EmailsOnPushService|Send from committer"),
+ help: s_("EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains}).") % { domains: domains } },
+ { type: 'checkbox', name: 'disable_diffs', title: s_("EmailsOnPushService|Disable code diffs"),
+ help: s_("EmailsOnPushService|Don't include possibly sensitive code diffs in notification body.") },
+ { type: 'textarea', name: 'recipients', placeholder: s_('EmailsOnPushService|Emails separated by whitespace') }
]
end
end
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index d2835c6ac82..593ce69b0fd 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -6,11 +6,11 @@ class ExternalWikiService < Service
validates :external_wiki_url, presence: true, public_url: true, if: :activated?
def title
- 'External Wiki'
+ s_('ExternalWikiService|External Wiki')
end
def description
- 'Replaces the link to the internal wiki with a link to an external wiki.'
+ s_('ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki.')
end
def self.to_param
@@ -19,7 +19,7 @@ class ExternalWikiService < Service
def fields
[
- { type: 'text', name: 'external_wiki_url', placeholder: 'The URL of the external Wiki', required: true }
+ { type: 'text', name: 'external_wiki_url', placeholder: s_('ExternalWikiService|The URL of the external Wiki'), required: true }
]
end
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 76624263aab..094488cb431 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -9,7 +9,7 @@ class FlowdockService < Service
end
def description
- 'Flowdock is a collaboration web app for technical teams.'
+ s_('FlowdockService|Flowdock is a collaboration web app for technical teams.')
end
def self.to_param
@@ -18,7 +18,7 @@ class FlowdockService < Service
def fields
[
- { type: 'text', name: 'token', placeholder: 'Flowdock Git source token', required: true }
+ { type: 'text', name: 'token', placeholder: s_('FlowdockService|Flowdock Git source token'), required: true }
]
end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
new file mode 100644
index 00000000000..a69b7b4c4b6
--- /dev/null
+++ b/app/models/project_services/hipchat_service.rb
@@ -0,0 +1,311 @@
+# frozen_string_literal: true
+
+class HipchatService < Service
+ include ActionView::Helpers::SanitizeHelper
+
+ MAX_COMMITS = 3
+ HIPCHAT_ALLOWED_TAGS = %w[
+ a b i strong em br img pre code
+ table th tr td caption colgroup col thead tbody tfoot
+ ul ol li dl dt dd
+ ].freeze
+
+ prop_accessor :token, :room, :server, :color, :api_version
+ boolean_accessor :notify_only_broken_pipelines, :notify
+ validates :token, presence: true, if: :activated?
+
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ self.notify_only_broken_pipelines = true
+ end
+ end
+
+ def title
+ 'HipChat'
+ end
+
+ def description
+ 'Private group chat and IM'
+ end
+
+ def self.to_param
+ 'hipchat'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: 'Room token', required: true },
+ { type: 'text', name: 'room', placeholder: 'Room name or ID' },
+ { type: 'checkbox', name: 'notify' },
+ { type: 'select', name: 'color', choices: %w(yellow red green purple gray random) },
+ { type: 'text', name: 'api_version',
+ placeholder: 'Leave blank for default (v2)' },
+ { type: 'text', name: 'server',
+ placeholder: 'Leave blank for default. https://hipchat.example.com' },
+ { type: 'checkbox', name: 'notify_only_broken_pipelines' }
+ ]
+ end
+
+ def self.supported_events
+ %w(push issue confidential_issue merge_request note confidential_note tag_push pipeline)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ message = create_message(data)
+ return unless message.present?
+
+ gate[room].send('GitLab', message, message_options(data)) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def test(data)
+ begin
+ result = execute(data)
+ rescue StandardError => error
+ return { success: false, result: error }
+ end
+
+ { success: true, result: result }
+ end
+
+ private
+
+ def gate
+ options = { api_version: api_version.present? ? api_version : 'v2' }
+ options[:server_url] = server unless server.blank?
+ @gate ||= HipChat::Client.new(token, options)
+ end
+
+ def message_options(data = nil)
+ { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
+ end
+
+ def create_message(data)
+ object_kind = data[:object_kind]
+
+ case object_kind
+ when "push", "tag_push"
+ create_push_message(data)
+ when "issue"
+ create_issue_message(data) unless update?(data)
+ when "merge_request"
+ create_merge_request_message(data) unless update?(data)
+ when "note"
+ create_note_message(data)
+ when "pipeline"
+ create_pipeline_message(data) if should_pipeline_be_notified?(data)
+ end
+ end
+
+ def render_line(text)
+ markdown(text.lines.first.chomp, pipeline: :single_line) if text
+ end
+
+ def create_push_message(push)
+ ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch'
+ ref = Gitlab::Git.ref_name(push[:ref])
+
+ before = push[:before]
+ after = push[:after]
+
+ message = []
+ message << "#{push[:user_name]} "
+
+ if Gitlab::Git.blank_ref?(before)
+ message << "pushed new #{ref_type} <a href=\""\
+ "#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
+ " to #{project_link}\n"
+ elsif Gitlab::Git.blank_ref?(after)
+ message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n"
+ else
+ message << "pushed to #{ref_type} <a href=\""\
+ "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
+ message << "of <a href=\"#{project.web_url}\">#{project.full_name.gsub!(/\s/, '')}</a> "
+ message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
+
+ push[:commits].take(MAX_COMMITS).each do |commit|
+ message << "<br /> - #{render_line(commit[:message])} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
+ end
+
+ if push[:commits].count > MAX_COMMITS
+ message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
+ end
+ end
+
+ message.join
+ end
+
+ def markdown(text, options = {})
+ return "" unless text
+
+ context = {
+ project: project,
+ pipeline: :email
+ }
+
+ Banzai.render(text, context)
+
+ context.merge!(options)
+
+ html = Banzai.render_and_post_process(text, context)
+ sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt])
+
+ sanitized_html.truncate(200, separator: ' ', omission: '...')
+ end
+
+ def create_issue_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ title = render_line(obj_attr[:title])
+ state = obj_attr[:state]
+ issue_iid = obj_attr[:iid]
+ issue_url = obj_attr[:url]
+ description = obj_attr[:description]
+
+ issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
+
+ message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
+ message << "<pre>#{markdown(description)}</pre>"
+
+ message.join
+ end
+
+ def create_merge_request_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ merge_request_id = obj_attr[:iid]
+ state = obj_attr[:state]
+ description = obj_attr[:description]
+ title = render_line(obj_attr[:title])
+
+ merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
+ merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
+ message = ["#{user_name} #{state} #{merge_request_link} in " \
+ "#{project_link}: <b>#{title}</b>"]
+
+ message << "<pre>#{markdown(description)}</pre>"
+ message.join
+ end
+
+ def format_title(title)
+ "<b>#{render_line(title)}</b>"
+ end
+
+ def create_note_message(data)
+ data = HashWithIndifferentAccess.new(data)
+ user_name = data[:user][:name]
+
+ obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
+ note = obj_attr[:note]
+ note_url = obj_attr[:url]
+ noteable_type = obj_attr[:noteable_type]
+ commit_id = nil
+
+ case noteable_type
+ when "Commit"
+ commit_attr = HashWithIndifferentAccess.new(data[:commit])
+ commit_id = commit_attr[:id]
+ subject_desc = commit_id
+ subject_desc = Commit.truncate_sha(subject_desc)
+ subject_type = "commit"
+ title = format_title(commit_attr[:message])
+ when "Issue"
+ subj_attr = HashWithIndifferentAccess.new(data[:issue])
+ subject_id = subj_attr[:iid]
+ subject_desc = "##{subject_id}"
+ subject_type = "issue"
+ title = format_title(subj_attr[:title])
+ when "MergeRequest"
+ subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
+ subject_id = subj_attr[:iid]
+ subject_desc = "!#{subject_id}"
+ subject_type = "merge request"
+ title = format_title(subj_attr[:title])
+ when "Snippet"
+ subj_attr = HashWithIndifferentAccess.new(data[:snippet])
+ subject_id = subj_attr[:id]
+ subject_desc = "##{subject_id}"
+ subject_type = "snippet"
+ title = format_title(subj_attr[:title])
+ end
+
+ subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
+ message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
+ message << title
+
+ message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
+ message.join
+ end
+
+ def create_pipeline_message(data)
+ pipeline_attributes = data[:object_attributes]
+ pipeline_id = pipeline_attributes[:id]
+ ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+ ref = pipeline_attributes[:ref]
+ user_name = (data[:user] && data[:user][:name]) || 'API'
+ status = pipeline_attributes[:status]
+ duration = pipeline_attributes[:duration]
+
+ branch_link = "<a href=\"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"
+ pipeline_url = "<a href=\"#{project_url}/pipelines/#{pipeline_id}\">##{pipeline_id}</a>"
+
+ "#{project_link}: Pipeline #{pipeline_url} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
+ end
+
+ def message_color(data)
+ pipeline_status_color(data) || color || 'yellow'
+ end
+
+ def pipeline_status_color(data)
+ return unless data && data[:object_kind] == 'pipeline'
+
+ case data[:object_attributes][:status]
+ when 'success'
+ 'green'
+ else
+ 'red'
+ end
+ end
+
+ def project_name
+ project.full_name.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+
+ def project_link
+ "<a href=\"#{project_url}\">#{project_name}</a>"
+ end
+
+ def update?(data)
+ data[:object_attributes][:action] == 'update'
+ end
+
+ def humanized_status(status)
+ case status
+ when 'success'
+ 'passed'
+ else
+ status
+ end
+ end
+
+ def should_pipeline_be_notified?(data)
+ case data[:object_attributes][:status]
+ when 'success'
+ !notify_only_broken_pipelines?
+ when 'failed'
+ true
+ else
+ false
+ end
+ end
+end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 81302c516c2..ebf28dc842c 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -11,7 +11,7 @@ class JiraService < IssueTrackerService
validates :password, presence: true, if: :activated?
validates :jira_issue_transition_id,
- format: { with: Gitlab::Regex.jira_transition_id_regex, message: "transition ids can have only numbers which can be split with , or ;" },
+ format: { with: Gitlab::Regex.jira_transition_id_regex, message: s_("JiraService|transition ids can have only numbers which can be split with , or ;") },
allow_blank: true
# JIRA cloud version is deprecating authentication via username and password.
@@ -86,7 +86,7 @@ class JiraService < IssueTrackerService
if self.properties && self.properties['description'].present?
self.properties['description']
else
- 'Jira issue tracker'
+ s_('JiraService|Jira issue tracker')
end
end
@@ -96,11 +96,11 @@ class JiraService < IssueTrackerService
def fields
[
- { type: 'text', name: 'url', title: 'Web URL', placeholder: 'https://jira.example.com', required: true },
- { type: 'text', name: 'api_url', title: 'JIRA API URL', placeholder: 'If different from Web URL' },
- { type: 'text', name: 'username', title: 'Username or Email', placeholder: 'Use a username for server version and an email for cloud version', required: true },
- { type: 'password', name: 'password', title: 'Password or API token', placeholder: 'Use a password for server version and an API token for cloud version', required: true },
- { type: 'text', name: 'jira_issue_transition_id', title: 'Transition ID(s)', placeholder: 'Use , or ; to separate multiple transition IDs' }
+ { type: 'text', name: 'url', title: s_('JiraService|Web URL'), placeholder: 'https://jira.example.com', required: true },
+ { type: 'text', name: 'api_url', title: s_('JiraService|JIRA API URL'), placeholder: s_('JiraService|If different from Web URL') },
+ { type: 'text', name: 'username', title: s_('JiraService|Username or Email'), placeholder: s_('JiraService|Use a username for server version and an email for cloud version'), required: true },
+ { type: 'password', name: 'password', title: s_('JiraService|Password or API token'), placeholder: s_('JiraService|Use a password for server version and an API token for cloud version'), required: true },
+ { type: 'text', name: 'jira_issue_transition_id', title: s_('JiraService|Transition ID(s)'), placeholder: s_('JiraService|Use , or ; to separate multiple transition IDs') }
]
end
@@ -139,7 +139,7 @@ class JiraService < IssueTrackerService
def create_cross_reference_note(mentioned, noteable, author)
unless can_cross_reference?(noteable)
- return "Events for #{noteable.model_name.plural.humanize(capitalize: false)} are disabled."
+ return s_("JiraService|Events for %{noteable_model_name} are disabled.") % { noteable_model_name: noteable.model_name.plural.humanize(capitalize: false) }
end
jira_issue = jira_request { client.Issue.find(mentioned.id) }
@@ -338,9 +338,9 @@ class JiraService < IssueTrackerService
def self.event_description(event)
case event
when "merge_request", "merge_request_events"
- "JIRA comments will be created when an issue gets referenced in a merge request."
+ s_("JiraService|JIRA comments will be created when an issue gets referenced in a merge request.")
when "commit", "commit_events"
- "JIRA comments will be created when an issue gets referenced in a commit."
+ s_("JiraService|JIRA comments will be created when an issue gets referenced in a commit.")
end
end
end
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index d60a6a7efa3..7ba69370f14 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -10,11 +10,11 @@ class PipelinesEmailService < Service
end
def title
- 'Pipelines emails'
+ _('Pipelines emails')
end
def description
- 'Email the pipelines status to a list of recipients.'
+ _('Email the pipelines status to a list of recipients.')
end
def self.to_param
@@ -51,7 +51,7 @@ class PipelinesEmailService < Service
[
{ type: 'textarea',
name: 'recipients',
- placeholder: 'Emails separated by comma',
+ placeholder: _('Emails separated by comma'),
required: true },
{ type: 'checkbox',
name: 'notify_only_broken_pipelines' }
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 617e502b639..c15993bdc06 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -11,7 +11,7 @@ class PivotaltrackerService < Service
end
def description
- 'Project Management Software (Source Commits Endpoint)'
+ s_('PivotalTrackerService|Project Management Software (Source Commits Endpoint)')
end
def self.to_param
@@ -23,14 +23,14 @@ class PivotaltrackerService < Service
{
type: 'text',
name: 'token',
- placeholder: 'Pivotal Tracker API token.',
+ placeholder: s_('PivotalTrackerService|Pivotal Tracker API token.'),
required: true
},
{
type: 'text',
name: 'restrict_to_branch',
- placeholder: 'Comma-separated list of branches which will be ' \
- 'automatically inspected. Leave blank to include all branches.'
+ placeholder: s_('PivotalTrackerService|Comma-separated list of branches which will be ' \
+ 'automatically inspected. Leave blank to include all branches.')
}
]
end
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 4e48c348b45..0d35bab7f80 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -11,7 +11,7 @@ class PushoverService < Service
end
def description
- 'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
+ s_('PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.')
end
def self.to_param
@@ -20,15 +20,15 @@ class PushoverService < Service
def fields
[
- { type: 'text', name: 'api_key', placeholder: 'Your application key', required: true },
- { type: 'text', name: 'user_key', placeholder: 'Your user key', required: true },
- { type: 'text', name: 'device', placeholder: 'Leave blank for all active devices' },
+ { type: 'text', name: 'api_key', placeholder: s_('PushoverService|Your application key'), required: true },
+ { type: 'text', name: 'user_key', placeholder: s_('PushoverService|Your user key'), required: true },
+ { type: 'text', name: 'device', placeholder: s_('PushoverService|Leave blank for all active devices') },
{ type: 'select', name: 'priority', required: true, choices:
[
- ['Lowest Priority', -2],
- ['Low Priority', -1],
- ['Normal Priority', 0],
- ['High Priority', 1]
+ [s_('PushoverService|Lowest Priority'), -2],
+ [s_('PushoverService|Low Priority'), -1],
+ [s_('PushoverService|Normal Priority'), 0],
+ [s_('PushoverService|High Priority'), 1]
],
default_choice: 0 },
{ type: 'select', name: 'sound', choices:
@@ -73,15 +73,15 @@ class PushoverService < Service
message =
if Gitlab::Git.blank_ref?(before)
- "#{data[:user_name]} pushed new branch \"#{ref}\"."
+ s_("PushoverService|%{user_name} pushed new branch \"%{ref}\".") % { user_name: data[:user_name], ref: ref }
elsif Gitlab::Git.blank_ref?(after)
- "#{data[:user_name]} deleted branch \"#{ref}\"."
+ s_("PushoverService|%{user_name} deleted branch \"%{ref}\".") % { user_name: data[:user_name], ref: ref }
else
- "#{data[:user_name]} push to branch \"#{ref}\"."
+ s_("PushoverService|%{user_name} push to branch \"%{ref}\".") % { user_name: data[:user_name], ref: ref }
end
if data[:total_commits_count] > 0
- message = [message, "Total commits count: #{data[:total_commits_count]}"].join("\n")
+ message = [message, s_("PushoverService|Total commits count: %{total_commits_count}") % { total_commits_count: data[:total_commits_count] }].join("\n")
end
pushover_data = {
@@ -92,7 +92,7 @@ class PushoverService < Service
title: "#{project.full_name}",
message: message,
url: data[:project][:web_url],
- url_title: "See project #{project.full_name}"
+ url_title: s_("PushoverService|See project %{project_full_name}") % { project_full_name: project.full_name }
}
# Sound parameter MUST NOT be sent to API if not selected
diff --git a/app/models/service.rb b/app/models/service.rb
index c6d5eb353dc..de549becf71 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -255,6 +255,7 @@ class Service < ApplicationRecord
external_wiki
flowdock
hangouts_chat
+ hipchat
irker
jira
kubernetes
diff --git a/app/models/u2f_registration.rb b/app/models/u2f_registration.rb
index b4645462314..81415eb383b 100644
--- a/app/models/u2f_registration.rb
+++ b/app/models/u2f_registration.rb
@@ -19,7 +19,7 @@ class U2fRegistration < ApplicationRecord
user: user,
name: params[:name])
rescue JSON::ParserError, NoMethodError, ArgumentError
- registration.errors.add(:base, 'Your U2F device did not send a valid JSON response.')
+ registration.errors.add(:base, _('Your U2F device did not send a valid JSON response.'))
rescue U2F::Error => e
registration.errors.add(:base, e.message)
end
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 9bffdcdb2e7..ca74f16b3b8 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -45,7 +45,7 @@ class Upload < ApplicationRecord
end
def absolute_path
- raise ObjectStorage::RemoteStoreError, "Remote object has no absolute path." unless local?
+ raise ObjectStorage::RemoteStoreError, _("Remote object has no absolute path.") unless local?
return path unless relative_path?
uploader_class.absolute_path(self)
@@ -71,10 +71,10 @@ class Upload < ApplicationRecord
# Help sysadmins find missing upload files
if persisted? && !exist
if Gitlab::Sentry.enabled?
- Raven.capture_message("Upload file does not exist", extra: self.attributes)
+ Raven.capture_message(_("Upload file does not exist"), extra: self.attributes)
end
- Gitlab::Metrics.counter(:upload_file_does_not_exist_total, 'The number of times an upload record could not find its file').increment
+ Gitlab::Metrics.counter(:upload_file_does_not_exist_total, _('The number of times an upload record could not find its file')).increment
end
exist
diff --git a/app/models/user.rb b/app/models/user.rb
index d3524bfd6ae..551eb58a4de 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -517,7 +517,7 @@ class User < ApplicationRecord
def ghost
email = 'ghost%s@example.com'
unique_internal(where(ghost: true), 'ghost', email) do |u|
- u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
+ u.bio = _('This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.')
u.name = 'Ghost User'
end
end
@@ -537,20 +537,16 @@ class User < ApplicationRecord
username
end
- def self.internal_attributes
- [:ghost]
- end
-
def internal?
- self.class.internal_attributes.any? { |a| self[a] }
+ ghost?
end
def self.internal
- where(Hash[internal_attributes.zip([true] * internal_attributes.size)])
+ where(ghost: true)
end
def self.non_internal
- where(internal_attributes.map { |attr| "#{attr} IS NOT TRUE" }.join(" AND "))
+ where('ghost IS NOT TRUE')
end
#
@@ -626,32 +622,32 @@ class User < ApplicationRecord
def namespace_move_dir_allowed
if namespace&.any_project_has_container_registry_tags?
- errors.add(:username, 'cannot be changed if a personal project has container registry tags.')
+ errors.add(:username, _('cannot be changed if a personal project has container registry tags.'))
end
end
def unique_email
if !emails.exists?(email: email) && Email.exists?(email: email)
- errors.add(:email, 'has already been taken')
+ errors.add(:email, _('has already been taken'))
end
end
def owns_notification_email
return if temp_oauth_email?
- errors.add(:notification_email, "is not an email you own") unless all_emails.include?(notification_email)
+ errors.add(:notification_email, _("is not an email you own")) unless all_emails.include?(notification_email)
end
def owns_public_email
return if public_email.blank?
- errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
+ errors.add(:public_email, _("is not an email you own")) unless all_emails.include?(public_email)
end
def owns_commit_email
return if read_attribute(:commit_email).blank?
- errors.add(:commit_email, "is not an email you own") unless verified_emails.include?(commit_email)
+ errors.add(:commit_email, _("is not an email you own")) unless verified_emails.include?(commit_email)
end
# Define commit_email-related attribute methods explicitly instead of relying
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 909da4316d0..cd4c7895587 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -31,7 +31,9 @@ class WikiPage
pages.each_with_object([]) do |page, grouped_pages|
next grouped_pages << page unless page.directory.present?
- directory = grouped_pages.find { |dir| dir.slug == page.directory }
+ directory = grouped_pages.find do |obj|
+ obj.is_a?(WikiDirectory) && obj.slug == page.directory
+ end
next directory.pages << page if directory
diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb
index 1f62b3eb4de..b37ac510ff4 100644
--- a/app/services/clusters/applications/install_service.rb
+++ b/app/services/clusters/applications/install_service.rb
@@ -7,6 +7,13 @@ module Clusters
return unless app.scheduled?
app.make_installing!
+
+ install
+ end
+
+ private
+
+ def install
log_event(:begin_install)
helm_api.install(install_command)
@@ -18,7 +25,7 @@ module Clusters
app.make_errored!("Kubernetes error: #{e.error_code}")
rescue StandardError => e
log_error(e)
- app.make_errored!("Can't start installation process.")
+ app.make_errored!('Failed to install.')
end
end
end
diff --git a/app/services/clusters/applications/patch_service.rb b/app/services/clusters/applications/patch_service.rb
index c3d317e226b..977a5e91041 100644
--- a/app/services/clusters/applications/patch_service.rb
+++ b/app/services/clusters/applications/patch_service.rb
@@ -8,6 +8,12 @@ module Clusters
app.make_updating!
+ patch
+ end
+
+ private
+
+ def patch
log_event(:begin_patch)
helm_api.update(update_command)
@@ -16,10 +22,10 @@ module Clusters
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
rescue Kubeclient::HttpError => e
log_error(e)
- app.make_update_errored!("Kubernetes error: #{e.error_code}")
+ app.make_errored!("Kubernetes error: #{e.error_code}")
rescue StandardError => e
log_error(e)
- app.make_update_errored!("Can't start update process.")
+ app.make_errored!('Failed to update.')
end
end
end
diff --git a/app/services/clusters/applications/upgrade_service.rb b/app/services/clusters/applications/upgrade_service.rb
index c34391bc8ad..813a9c4d071 100644
--- a/app/services/clusters/applications/upgrade_service.rb
+++ b/app/services/clusters/applications/upgrade_service.rb
@@ -6,24 +6,28 @@ module Clusters
def execute
return unless app.scheduled?
- begin
- app.make_updating!
+ app.make_updating!
- log_event(:begin_upgrade)
- # install_command works with upgrades too
- # as it basically does `helm upgrade --install`
- helm_api.update(install_command)
+ upgrade
+ end
+
+ private
+
+ def upgrade
+ # install_command works with upgrades too
+ # as it basically does `helm upgrade --install`
+ log_event(:begin_upgrade)
+ helm_api.update(install_command)
- log_event(:schedule_wait_for_upgrade)
- ClusterWaitForAppInstallationWorker.perform_in(
- ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
- rescue Kubeclient::HttpError => e
- log_error(e)
- app.make_update_errored!("Kubernetes error: #{e.error_code}")
- rescue StandardError => e
- log_error(e)
- app.make_update_errored!("Can't start upgrade process.")
- end
+ log_event(:schedule_wait_for_upgrade)
+ ClusterWaitForAppInstallationWorker.perform_in(
+ ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+ app.make_errored!("Kubernetes error: #{e.error_code}")
+ rescue StandardError => e
+ log_error(e)
+ app.make_errored!('Failed to upgrade.')
end
end
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 74aad3b1c94..8f1f25a7307 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -62,12 +62,16 @@ module Groups
end
def can_use_visibility_level?
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
deny_visibility_level(@group)
return false
end
true
end
+
+ def visibility_level
+ params[:visibility].present? ? Gitlab::VisibilityLevel.level_value(params[:visibility]) : params[:visibility_level]
+ end
end
end
diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb
index e50840a9158..33444c2a7dc 100644
--- a/app/services/users/activity_service.rb
+++ b/app/services/users/activity_service.rb
@@ -30,7 +30,7 @@ module Users
return if @user.last_activity_on == today
- lease = Gitlab::ExclusiveLease.new("acitvity_service:#{@user.id}",
+ lease = Gitlab::ExclusiveLease.new("activity_service:#{@user.id}",
timeout: LEASE_TIMEOUT)
return unless lease.try_obtain
diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml
index 6b8dd156874..5a47040874f 100644
--- a/app/views/discussions/_diff_discussion.html.haml
+++ b/app/views/discussions/_diff_discussion.html.haml
@@ -4,6 +4,6 @@
-# Text diff discussions
- expanded = local_assigns.fetch(:expanded, true)
%tr.notes_holder{ class: ('hide' unless expanded) }
- %td.notes_content{ colspan: 3 }
+ %td.notes-content{ colspan: 3 }
.content{ class: ('hide' unless expanded) }
= render partial: "discussions/notes", collection: discussions, as: :discussion, locals: { disable_collapse_class: true }
diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml
index 2e621c4082d..03b428714b9 100644
--- a/app/views/discussions/_parallel_diff_discussion.html.haml
+++ b/app/views/discussions/_parallel_diff_discussion.html.haml
@@ -1,17 +1,17 @@
- expanded = [*discussions_left, *discussions_right].any?(&:expanded?)
%tr.notes_holder{ class: ('hide' unless expanded) }
- if discussions_left
- %td.notes_content.parallel.old{ colspan: 2 }
+ %td.notes-content.parallel.old{ colspan: 2 }
.content{ class: ('hide' unless discussions_left.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old', locals: { disable_collapse_class: true }
- else
- %td.notes_content.parallel.old{ colspan: 2 }
+ %td.notes-content.parallel.old{ colspan: 2 }
.content
- if discussions_right
- %td.notes_content.parallel.new{ colspan: 2 }
+ %td.notes-content.parallel.new{ colspan: 2 }
.content{ class: ('hide' unless discussions_right.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new', locals: { disable_collapse_class: true }
- else
- %td.notes_content.parallel.new{ colspan: 2 }
+ %td.notes-content.parallel.new{ colspan: 2 }
.content
diff --git a/app/views/projects/_classification_policy_settings.html.haml b/app/views/projects/_classification_policy_settings.html.haml
index 57c7a718d53..5a766ab024f 100644
--- a/app/views/projects/_classification_policy_settings.html.haml
+++ b/app/views/projects/_classification_policy_settings.html.haml
@@ -1,8 +1,6 @@
- if ::Gitlab::ExternalAuthorization.enabled?
- .form-group
- = f.label :external_authorization_classification_label, class: 'label-bold' do
- = s_('ExternalAuthorizationService|Classification Label')
- %span.light (optional)
+ .form-group.col-md-9
+ = f.label :external_authorization_classification_label, _('Classification Label (optional)'), class: 'label-bold'
= f.text_field :external_authorization_classification_label, class: "form-control"
%span.form-text.text-muted
= external_classification_label_help_message
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index 409b6dba9ca..1056977886a 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -1,42 +1,33 @@
- return unless Gitlab::CurrentSettings.project_export_enabled?
- project = local_assigns.fetch(:project)
-- expanded = Rails.env.test?
-%section.settings.no-animate#js-export-project{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Export project
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
- .settings-content
- .bs-callout.bs-callout-info
- %p.append-bottom-0
- %p
- The following items will be exported:
- %ul
- %li Project and wiki repositories
- %li Project uploads
- %li Project configuration, including services
- %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
- %li LFS objects
- %p
- The following items will NOT be exported:
- %ul
- %li Job traces and artifacts
- %li Container registry images
- %li CI variables
- %li Webhooks
- %li Any encrypted tokens
- %p
- Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- - if project.export_status == :finished
- = link_to 'Download export', download_export_project_path(project),
- rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
- = link_to 'Generate new export', generate_new_export_project_path(project),
- method: :post, class: "btn btn-default"
- - else
- = link_to 'Export project', export_project_path(project),
- method: :post, class: "btn btn-default"
+.sub-section
+ %h4= _('Export project')
+ %p= _('Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.')
+
+ .bs-callout.bs-callout-info
+ %p.append-bottom-0
+ %p= _('The following items will be exported:')
+ %ul
+ %li= _('Project and wiki repositories')
+ %li= _('Project uploads')
+ %li= _('Project configuration, including services')
+ %li= _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities')
+ %li= _('LFS objects')
+ %p= _('The following items will NOT be exported:')
+ %ul
+ %li= _('Job traces and artifacts')
+ %li= _('Container registry images')
+ %li= _('CI variables')
+ %li= _('Webhooks')
+ %li= _('Any encrypted tokens')
+ %p= _('Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.')
+ - if project.export_status == :finished
+ = link_to _('Download export'), download_export_project_path(project),
+ rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
+ = link_to _('Generate new export'), generate_new_export_project_path(project),
+ method: :post, class: "btn btn-default"
+ - else
+ = link_to _('Export project'), export_project_path(project),
+ method: :post, class: "btn btn-default"
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index e45c5be76de..c84c376d57b 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -10,5 +10,5 @@
- actions.each do |action|
- next unless can?(current_user, :update_build, action)
%li
- = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow', class: 'btn' do
+ = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
%span= action.name
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 56427a74d56..018c5b38536 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -1,7 +1,7 @@
- too_big = diff_file.diff_lines.count > Commit::DIFF_SAFE_LINES
- if too_big
.suppressed-container
- %a.show-suppressed-diff.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
+ %a.show-suppressed-diff.cursor-pointer.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
%table.text-file.diff-wrap-lines.code.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
= render partial: "projects/diffs/line",
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index abf2fb7dc57..1a3e4a5d608 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -3,210 +3,155 @@
- @content_class = "limit-container-width" unless fluid_layout
- expanded = Rails.env.test?
-.project-edit-container
- %section.settings.general-settings.no-animate#js-general-project-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, tags, avatar')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Update your project name, tags, description and avatar.')
-
- .settings-content
- .project-edit-errors
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-general-project-settings' }
- %fieldset
- .row
- .form-group.col-md-9
- = f.label :name, class: 'label-bold', for: 'project_name_edit' do
- Project name
- = f.text_field :name, class: "form-control", id: "project_name_edit"
-
- .form-group.col-md-3
- = f.label :id, class: 'label-bold' do
- Project ID
- = f.text_field :id, class: 'form-control', readonly: true
-
- .form-group
- = f.label :description, class: 'label-bold' do
- Project description
- %span.light (optional)
- = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
-
- = render 'projects/classification_policy_settings', f: f
-
- = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
-
- .form-group
- = f.label :tag_list, "Topics", class: 'label-bold'
- = f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
- %p.form-text.text-muted Separate topics with commas.
-
- .form-group.prepend-top-default.append-bottom-20
- .avatar-container.s90
- = project_icon(@project, alt: _('Project avatar'), class: 'avatar project-avatar s90')
- = f.label :avatar, _('Project avatar'), class: 'label-bold d-block'
- = render 'shared/choose_avatar_button', f: f
- - if @project.avatar?
- %hr
- = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
-
- = f.submit 'Save changes', class: "btn btn-success js-btn-success-general-project-settings"
-
- %section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions.')
-
- .settings-content
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
- -# haml-lint:disable InlineJavaScript
- %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
- .js-project-permissions-form
- = f.submit 'Save changes', class: "btn btn-success"
-
- = render_if_exists 'projects/issues_settings'
-
- %section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Choose your merge method, set up a default merge request description template.')
-
- .settings-content
- = render_if_exists 'shared/promotions/promote_mr_features'
-
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
- = render 'projects/merge_request_settings', form: f
- = f.submit 'Save changes', class: "btn btn-success qa-save-merge-request-changes"
-
- = render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
-
-
- %section.settings.no-animate{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
- = s_('ProjectSettings|Badges')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- = s_('ProjectSettings|Customize your project badges.')
- = link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
- .settings-content
- = render 'shared/badges/badge_settings'
-
- = render_if_exists 'projects/service_desk_settings'
- = render 'export', project: @project
-
- %section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Housekeeping, export, path, transfer, remove, archive.')
-
- .settings-content
+%section.settings.general-settings.no-animate.expanded#js-general-settings
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= _('Collapse')
+ %p= _('Update your project name, topics, description and avatar.')
+ .settings-content= render 'projects/settings/general'
+
+%section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions.')
+
+ .settings-content
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
+ %template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
+ .js-project-permissions-form
+ = f.submit _('Save changes'), class: "btn btn-success"
+
+%section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Choose your merge method, set up a default merge request description template.')
+
+ .settings-content
+ = render_if_exists 'shared/promotions/promote_mr_features'
+
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form js-mr-settings-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
+ = render 'projects/merge_request_settings', form: f
+ = f.submit _('Save changes'), class: "btn btn-success qa-save-merge-request-changes"
+
+= render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
+
+
+%section.settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
+ = s_('ProjectSettings|Badges')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = s_('ProjectSettings|Customize your project badges.')
+ = link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
+ .settings-content
+ = render 'shared/badges/badge_settings'
+
+= render_if_exists 'projects/settings/default_issue_template'
+
+= render_if_exists 'projects/service_desk_settings'
+
+%section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Housekeeping, export, path, transfer, remove, archive.')
+
+ .settings-content
+ .sub-section
+ %h4= _('Housekeeping')
+ %p= _('Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.')
+ = link_to _('Run housekeeping'), housekeeping_project_path(@project),
+ method: :post, class: "btn btn-default"
+
+ = render 'export', project: @project
+
+ - if can? current_user, :archive_project, @project
.sub-section
- %h4 Housekeeping
- %p
- Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.
- = link_to 'Run housekeeping', housekeeping_project_path(@project),
- method: :post, class: "btn btn-default"
- - if can? current_user, :archive_project, @project
- .sub-section
- %h4.warning-title
- - if @project.archived?
- Unarchive project
- - else
- Archive project
+ %h4.warning-title
- if @project.archived?
- %p
- Unarchiving the project will restore people's ability to make changes to it.
- The repository can be committed to, and issues, comments and other entities can be created.
- %strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive project', unarchive_project_path(@project),
- data: { confirm: "Are you sure that you want to unarchive this project?" },
- method: :post, class: "btn btn-success"
+ = _('Unarchive project')
- else
- %p
- Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
- %strong The repository cannot be committed to, and no issues, comments or other entities can be created.
- = link_to 'Archive project', archive_project_path(@project),
- data: { confirm: "Are you sure that you want to archive this project?" },
- method: :post, class: "btn btn-warning"
- .sub-section.rename-repository
- %h4.warning-title
- Rename repository
- = render 'projects/errors'
- = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
- .form-group.project_name_holder
- = f.label :name, class: 'label-bold' do
- Project name
- .form-group
- = f.text_field :name, class: "form-control"
+ = _('Archive project')
+ - if @project.archived?
+ %p= _("Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>").html_safe
+ = link_to _('Unarchive project'), unarchive_project_path(@project),
+ data: { confirm: _("Are you sure that you want to unarchive this project?") },
+ method: :post, class: "btn btn-success"
+ - else
+ %p= _("Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>").html_safe
+ = link_to _('Archive project'), archive_project_path(@project),
+ data: { confirm: _("Are you sure that you want to archive this project?") },
+ method: :post, class: "btn btn-warning"
+ .sub-section.rename-repository
+ %h4.warning-title= _('Change path')
+ = render 'projects/errors'
+ = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
+ .form-group
+ = f.label :path, _('Path'), class: 'label-bold'
+ .form-group
+ .input-group
+ .input-group-prepend
+ .input-group-text
+ #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
+ = f.text_field :path, class: 'form-control qa-project-path-field h-auto'
+ %ul
+ %li= _("Be careful. Renaming a project's repository can have unintended side effects.")
+ %li= _('You will need to update your local repositories to point to the new location.')
+ - if @project.deployment_platform.present?
+ %li= _('Your deployment services will be broken, you will need to manually fix the services after renaming.')
+ = f.submit _('Change path'), class: "btn btn-warning qa-change-path-button"
+
+ - if can?(current_user, :change_namespace, @project)
+ .sub-section
+ %h4.danger-title= _('Transfer project')
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
.form-group
- = f.label :path, class: 'label-bold' do
- %span Path
+ = label_tag :new_namespace_id, nil, class: 'label-bold' do
+ %span= _('Select a new namespace')
.form-group
- .input-group
- .input-group-prepend
- .input-group-text
- #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
- = f.text_field :path, class: 'form-control'
+ = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
%ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
- %li You will need to update your local repositories to point to the new location.
- - if @project.deployment_platform.present?
- %li Your deployment services will be broken, you will need to manually fix the services after renaming.
- = f.submit 'Rename project', class: "btn btn-warning"
- - if can?(current_user, :change_namespace, @project)
- .sub-section
- %h4.danger-title
- Transfer project
- = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
- .form-group
- = label_tag :new_namespace_id, nil, class: 'label-bold' do
- %span Select a new namespace
- .form-group
- = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
- %ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %li You can only transfer the project to namespaces you manage.
- %li You will need to update your local repositories to point to the new location.
- %li Project visibility level will be changed to match namespace rules when transferring to a group.
- = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- - if @project.forked? && can?(current_user, :remove_fork_project, @project)
- .sub-section
- %h4.danger-title
- Remove fork relationship
+ %li= _("Be careful. Changing the project's namespace can have unintended side effects.")
+ %li= _('You can only transfer the project to namespaces you manage.')
+ %li= _('You will need to update your local repositories to point to the new location.')
+ %li= _('Project visibility level will be changed to match namespace rules when transferring to a group.')
+ = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+
+ - if @project.forked? && can?(current_user, :remove_fork_project, @project)
+ .sub-section
+ %h4.danger-title= _('Remove fork relationship')
+ %p
+ = _('This will remove the fork relationship to source project')
+ = succeed "." do
+ - if @project.fork_source
+ = link_to(fork_source_name(@project), project_path(@project.fork_source))
+ - else
+ = fork_source_name(@project)
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
%p
- This will remove the fork relationship to source project
- = succeed "." do
- - if @project.fork_source
- = link_to(fork_source_name(@project), project_path(@project.fork_source))
- - else
- = fork_source_name(@project)
- = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
- %p
- %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
- = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
- - if can?(current_user, :remove_project, @project)
- .sub-section
- %h4.danger-title
- Remove project
+ %strong= _('Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.')
+ = button_to _('Remove fork relationship'), '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+
+ - if can?(current_user, :remove_project, @project)
+ .sub-section
+ %h4.danger-title= _('Remove project')
+ %p= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.')
+ = form_tag(project_path(@project), method: :delete) do
%p
- Removing the project will delete its repository and all related resources including issues, merge requests etc.
- = form_tag(project_path(@project), method: :delete) do
- %p
- %strong Removed projects cannot be restored!
- = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ %strong= _('Removed projects cannot be restored!')
+ = button_to _('Remove project'), '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
.save-project-loader.hide
.center
%h2
%i.fa.fa-spinner.fa-spin
- Saving project.
- %p Please wait a moment, this page will automatically refresh when ready.
+ = _('Saving project.')
+ %p= _('Please wait a moment, this page will automatically refresh when ready.')
= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index cbd5c54cecc..1fbe34cfff3 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -17,5 +17,5 @@
= f.url_field :external_url, class: 'form-control'
.form-actions
- = f.submit _('Save'), class: 'btn btn-save'
+ = f.submit _('Save'), class: 'btn btn-success'
= link_to _('Cancel'), project_environments_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 5111c9fab8d..79c586eef73 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -82,7 +82,8 @@
help_page_path: suggest_changes_help_path,
current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json,
project_path: project_path(@merge_request.project),
- changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg') } }
+ changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
+ is_fluid_layout: fluid_layout.to_s } }
.mr-loading-status
= spinner
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 1121cf06b5c..396e5da87bc 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "js-pipeline-schedule-form" } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "js-pipeline-schedule-form pipeline-schedule-form" } do |f|
= form_errors(@schedule)
.form-group.row
.col-md-9
@@ -11,12 +11,12 @@
.form-group.row
.col-md-9
= f.label :cron_timezone, _('Cron Timezone'), class: 'label-bold'
- = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
+ = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown w-100', dropdown_class: 'w-100', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
= f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true
.form-group.row
.col-md-9
= f.label :ref, _('Target Branch'), class: 'label-bold'
- = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
+ = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown w-100', dropdown_class: 'git-revision-dropdown w-100', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group.row.js-ci-variable-list-section
.col-md-9
diff --git a/app/views/projects/settings/_general.html.haml b/app/views/projects/settings/_general.html.haml
new file mode 100644
index 00000000000..380430ff52b
--- /dev/null
+++ b/app/views/projects/settings/_general.html.haml
@@ -0,0 +1,42 @@
+= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project js-general-settings-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-general-settings' }
+ = form_errors(@project)
+
+ %fieldset
+ .row
+ .form-group.col-md-5
+ = f.label :name, class: 'label-bold', for: 'project_name_edit' do
+ = _('Project name')
+ = f.text_field :name, class: 'form-control qa-project-name-field', id: "project_name_edit"
+
+ .form-group.col-md-7
+ = f.label :id, class: 'label-bold' do
+ = _('Project ID')
+ = f.text_field :id, class: 'form-control w-auto', readonly: true
+
+ .row
+ .form-group.col-md-9
+ = f.label :tag_list, _('Topics'), class: 'label-bold'
+ = f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
+ %p.form-text.text-muted= _('Separate topics with commas.')
+
+ .row
+ .form-group.col-md-9
+ = f.label :description, _('Project description (optional)'), class: 'label-bold'
+ = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
+
+ .row= render_if_exists 'projects/classification_policy_settings', f: f
+
+ .row= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
+
+ .form-group.prepend-top-default.append-bottom-20
+ .avatar-container.s90
+ = project_icon(@project, alt: _('Project avatar'), class: 'avatar project-avatar s90')
+ = f.label :avatar, _('Project avatar'), class: 'label-bold d-block'
+ = render 'shared/choose_avatar_button', f: f
+ - if @project.avatar?
+ %hr
+ = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
+
+
+ = f.submit _('Save changes'), class: "btn btn-success mt-4 qa-save-naming-topics-avatar-button"
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 1dcf4369253..3967c8148d2 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -2,8 +2,7 @@
.modal-dialog
.modal-content
.modal-header
- %h3.page-title
- Confirmation required
+ %h3.page-title= _('Confirmation required')
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
@@ -11,8 +10,7 @@
%p.text-danger.js-confirm-text
%p
- This action can lead to data loss.
- To prevent accidental actions we ask you to confirm your intention.
+ %span.js-warning-text= _('This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.')
%br
Please type
%code.js-confirm-danger-match= phrase
@@ -21,4 +19,4 @@
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
.form-actions
- = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
+ = submit_tag _('Confirm'), class: "btn btn-danger js-confirm-danger-submit"
diff --git a/changelogs/unreleased/59921-pipeline-schedule.yml b/changelogs/unreleased/59921-pipeline-schedule.yml
new file mode 100644
index 00000000000..4227a047913
--- /dev/null
+++ b/changelogs/unreleased/59921-pipeline-schedule.yml
@@ -0,0 +1,5 @@
+---
+title: Replaces CSS with BS4 utility class for pipeline schedules
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/60224-btn-env.yml b/changelogs/unreleased/60224-btn-env.yml
new file mode 100644
index 00000000000..5053ddb31fa
--- /dev/null
+++ b/changelogs/unreleased/60224-btn-env.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes actions dropdowns in environments page
+merge_request: 27160
+author:
+type: fixed
diff --git a/changelogs/unreleased/60241-merge-request-popover-doesn-t-go-away-on-mouse-leave.yml b/changelogs/unreleased/60241-merge-request-popover-doesn-t-go-away-on-mouse-leave.yml
new file mode 100644
index 00000000000..ce942777dca
--- /dev/null
+++ b/changelogs/unreleased/60241-merge-request-popover-doesn-t-go-away-on-mouse-leave.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug where MR popover doesn't go away on mouse leave
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/60261-save-btn-env.yml b/changelogs/unreleased/60261-save-btn-env.yml
new file mode 100644
index 00000000000..b0936198d2e
--- /dev/null
+++ b/changelogs/unreleased/60261-save-btn-env.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes create button background for Environments form
+merge_request: 27161
+author:
+type: fixed
diff --git a/changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml b/changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml
new file mode 100644
index 00000000000..ec5e9e4703b
--- /dev/null
+++ b/changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix long file header names bug in diffs
+merge_request: 27233
+author:
+type: fixed
diff --git a/changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml b/changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml
deleted file mode 100644
index 731c9c10b00..00000000000
--- a/changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow to use untrusted Regexp via feature flag
-merge_request: 26905
-author:
-type: deprecated
diff --git a/changelogs/unreleased/fix-api-group-visibility.yml b/changelogs/unreleased/fix-api-group-visibility.yml
new file mode 100644
index 00000000000..7fbdcd729c6
--- /dev/null
+++ b/changelogs/unreleased/fix-api-group-visibility.yml
@@ -0,0 +1,5 @@
+---
+title: Fix api group visibility
+merge_request: 26896
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pull-request-importer.yml b/changelogs/unreleased/fix-pull-request-importer.yml
deleted file mode 100644
index 5f642a0710b..00000000000
--- a/changelogs/unreleased/fix-pull-request-importer.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance of PR import
-merge_request: 27121
-author:
-type: performance
diff --git a/changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml b/changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml
new file mode 100644
index 00000000000..e53499e21ba
--- /dev/null
+++ b/changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Workhorse to v8.6.0
+merge_request: 27260
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-disable-diff-instrumentation.yml b/changelogs/unreleased/sh-disable-diff-instrumentation.yml
deleted file mode 100644
index 55f4c2a8510..00000000000
--- a/changelogs/unreleased/sh-disable-diff-instrumentation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable method instrumentation for diffs
-merge_request: 27235
-author:
-type: performance
diff --git a/config/initializers/hipchat_client_patch.rb b/config/initializers/hipchat_client_patch.rb
new file mode 100644
index 00000000000..1879ecb15fb
--- /dev/null
+++ b/config/initializers/hipchat_client_patch.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb.
+module HipChat
+ class Client
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+
+ class Room
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+
+ class User
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+end
diff --git a/db/migrate/20190107151029_remove_hipchat_services.rb b/db/migrate/20190107151029_remove_hipchat_services.rb
deleted file mode 100644
index 4741ec88907..00000000000
--- a/db/migrate/20190107151029_remove_hipchat_services.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-# See http://doc.gitlab.com/ce/development/migration_style_guide.html
-# for more information on how to write migrations for GitLab.
-
-class RemoveHipchatServices < ActiveRecord::Migration[5.0]
- DOWNTIME = false
-
- def up
- execute "DELETE FROM services WHERE type = 'HipchatService'"
- end
-
- def down
- # no-op
- end
-end
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index f406163aea0..72341a5c777 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -145,7 +145,6 @@ mountpoint
└── gitlab-data
├── builds
├── git-data
- ├── home-git
├── shared
└── uploads
```
@@ -158,16 +157,11 @@ configuration to move each data location to a subdirectory:
```ruby
git_data_dirs({"default" => { "path" => "/gitlab-nfs/gitlab-data/git-data"} })
-user['home'] = '/gitlab-nfs/gitlab-data/home'
gitlab_rails['uploads_directory'] = '/gitlab-nfs/gitlab-data/uploads'
gitlab_rails['shared_path'] = '/gitlab-nfs/gitlab-data/shared'
gitlab_ci['builds_directory'] = '/gitlab-nfs/gitlab-data/builds'
```
-To move the `git` home directory, all GitLab services must be stopped. Run
-`gitlab-ctl stop && initctl stop gitlab-runsvdir`. Then continue with the
-reconfigure.
-
Run `sudo gitlab-ctl reconfigure` to start using the central location. Please
be aware that if you had existing data you will need to manually copy/rsync it
to these new locations and then restart GitLab.
@@ -197,14 +191,13 @@ are empty before attempting a restore. Read more about the
## Multiple NFS mounts
-When using default Omnibus configuration you will need to share 5 data locations
+When using default Omnibus configuration you will need to share 4 data locations
between all GitLab cluster nodes. No other locations should be shared. The
-following are the 5 locations need to be shared:
+following are the 4 locations need to be shared:
| Location | Description | Default configuration |
| -------- | ----------- | --------------------- |
| `/var/opt/gitlab/git-data` | Git repository data. This will account for a large portion of your data | `git_data_dirs({"default" => { "path" => "/var/opt/gitlab/git-data"} })`
-| `/var/opt/gitlab/.ssh` | SSH `authorized_keys` file and keys used to import repositories from some other Git services | `user['home'] = '/var/opt/gitlab/'`
| `/var/opt/gitlab/gitlab-rails/uploads` | User uploaded attachments | `gitlab_rails['uploads_directory'] = '/var/opt/gitlab/gitlab-rails/uploads'`
| `/var/opt/gitlab/gitlab-rails/shared` | Build artifacts, GitLab Pages, LFS objects, temp files, etc. If you're using LFS this may also account for a large portion of your data | `gitlab_rails['shared_path'] = '/var/opt/gitlab/gitlab-rails/shared'`
| `/var/opt/gitlab/gitlab-ci/builds` | GitLab CI build traces | `gitlab_ci['builds_directory'] = '/var/opt/gitlab/gitlab-ci/builds'`
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 17d72b96a51..1373bd56fe3 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -174,6 +174,10 @@ behavior:
1. [Reconfigure GitLab][reconfigure].
+NOTE: **Note:**
+`inplace_chroot` option might not work with the other features, such as [Pages Access Control](#access-control).
+The [GitLab Pages README](https://gitlab.com/gitlab-org/gitlab-pages#caveats) has more information about caveats and workarounds.
+
## Advanced configuration
In addition to the wildcard domains, you can also have the option to configure
diff --git a/doc/api/services.md b/doc/api/services.md
index 1f84e2de7de..e8ae7ff78f4 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -449,6 +449,45 @@ Get Hangouts Chat service settings for a project.
GET /projects/:id/services/hangouts-chat
```
+## HipChat
+
+Private group chat and IM
+
+### Create/Edit HipChat service
+
+Set HipChat service for a project.
+
+```
+PUT /projects/:id/services/hipchat
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | true | Room token |
+| `color` | string | false | The room color |
+| `notify` | boolean | false | Enable notifications |
+| `room` | string | false |Room name or ID |
+| `api_version` | string | false | Leave blank for default (v2) |
+| `server` | string | false | Leave blank for default. For example, `https://hipchat.example.com`. |
+
+### Delete HipChat service
+
+Delete HipChat service for a project.
+
+```
+DELETE /projects/:id/services/hipchat
+```
+
+### Get HipChat service settings
+
+Get HipChat service settings for a project.
+
+```
+GET /projects/:id/services/hipchat
+```
+
## Irker (IRC gateway)
Send IRC messages, on update, to a list of recipients through an Irker gateway.
diff --git a/doc/integration/README.md b/doc/integration/README.md
index f5bc0693b84..a539933f223 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -29,8 +29,8 @@ See the documentation below for details on how to configure these services.
## Project services
-Integration with services such as Campfire, Flowdock, Pivotal Tracker, and Slack
-are available in the form of a [Project Service][].
+Integration with services such as Campfire, Flowdock, HipChat,
+Pivotal Tracker, and Slack are available in the form of a [Project Service][].
[Project Service]: ../user/project/integrations/project_services.md
diff --git a/doc/project_services/hipchat.md b/doc/project_services/hipchat.md
new file mode 100644
index 00000000000..4ae9f6c6b2e
--- /dev/null
+++ b/doc/project_services/hipchat.md
@@ -0,0 +1 @@
+This document was moved to [user/project/integrations/hipchat.md](../user/project/integrations/hipchat.md).
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 254e234a22c..0af2f8d2f54 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -41,7 +41,7 @@ Objects (usually binary and large) created by a build process. These can include
### Atlassian
-A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Confluence, Bamboo.
+A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo.
### Audit Log
diff --git a/doc/user/index.md b/doc/user/index.md
index 626246447f3..8164b31c37e 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -65,9 +65,7 @@ With GitLab Enterprise Edition, you can also:
- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html).
-You can also [integrate](project/integrations/project_services.md) GitLab with
-numerous third-party applications, such as Mattermost, Microsoft Teams, Trello,
-Slack, Bamboo CI, JIRA, and a lot more.
+You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
## Projects
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
new file mode 100644
index 00000000000..0fd847d415f
--- /dev/null
+++ b/doc/user/project/integrations/hipchat.md
@@ -0,0 +1,53 @@
+# Atlassian HipChat
+
+GitLab provides a way to send HipChat notifications upon a number of events,
+such as when a user pushes code, creates a branch or tag, adds a comment, and
+creates a merge request.
+
+## Setup
+
+GitLab requires the use of a HipChat v2 API token to work. v1 tokens are
+not supported at this time. Note the differences between v1 and v2 tokens:
+
+HipChat v1 API (legacy) supports "API Auth Tokens" in the Group API menu. A v1
+token is allowed to send messages to *any* room.
+
+HipChat v2 API has tokens that are can be created using the Integrations tab
+in the Group or Room admin page. By design, these are lightweight tokens that
+allow GitLab to send messages only to *one* room.
+
+### Complete these steps in HipChat
+
+1. Go to: <https://admin.hipchat.com/admin>
+1. Click on "Group Admin" -> "Integrations".
+1. Find "Build Your Own!" and click "Create".
+1. Select the desired room, name the integration "GitLab", and click "Create".
+1. In the "Send messages to this room by posting this URL" column, you should
+see a URL in the format:
+
+```
+https://api.hipchat.com/v2/room/<room>/notification?auth_token=<token>
+```
+
+HipChat is now ready to accept messages from GitLab. Next, set up the HipChat
+service in GitLab.
+
+### Complete these steps in GitLab
+
+1. Navigate to the project you want to configure for notifications.
+1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
+1. Click "HipChat".
+1. Select the "Active" checkbox.
+1. Insert the `token` field from the URL into the `Token` field on the Web page.
+1. Insert the `room` field from the URL into the `Room` field on the Web page.
+1. Save or optionally click "Test Settings".
+
+## Troubleshooting
+
+If you do not see notifications, make sure you are using a HipChat v2 API
+token, not a v1 token.
+
+Note that the v2 token is tied to a specific room. If you want to be able to
+specify arbitrary rooms, you can create an API token for a specific user in
+HipChat under "Account settings" and "API access". Use the `XXX` value under
+`auth_token=XXX`.
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index e2f23827360..42c7824a125 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -36,6 +36,7 @@ Click on the service links to see further configuration instructions and details
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
| Flowdock | Flowdock is a collaboration web app for technical teams |
| [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat |
+| [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
| [JIRA](jira.md) | JIRA issue tracker |
| JetBrains TeamCity CI | A continuous integration and build server |
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index 8582c45798f..953be7f3798 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
# frozen_string_literal: true
module API
@@ -386,6 +387,44 @@ module API
},
chat_notification_events
].flatten,
+ 'hipchat' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The room token'
+ },
+ {
+ required: false,
+ name: :room,
+ type: String,
+ desc: 'The room name or ID'
+ },
+ {
+ required: false,
+ name: :color,
+ type: String,
+ desc: 'The room color'
+ },
+ {
+ required: false,
+ name: :notify,
+ type: Boolean,
+ desc: 'Enable notifications'
+ },
+ {
+ required: false,
+ name: :api_version,
+ type: String,
+ desc: 'Leave blank for default (v2)'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'Leave blank for default. https://hipchat.example.com'
+ }
+ ],
'irker' => [
{
required: true,
@@ -690,6 +729,7 @@ module API
::ExternalWikiService,
::FlowdockService,
::HangoutsChatService,
+ ::HipchatService,
::IrkerService,
::JiraService,
::KubernetesService,
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index bb4e536cf57..e7504051808 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -20,6 +20,10 @@ module API
def provider
:github
end
+
+ def provider_unauthorized
+ error!("Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account.", 401)
+ end
end
desc 'Import a GitHub project' do
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index a3c7de87765..8f9d5cf1e63 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -75,8 +75,8 @@ module Gitlab
user.admin? || allowed_level?(level.to_i)
end
+ # Level should be a numeric value, e.g. `20`
# Return true if the specified level is allowed for the current user.
- # Level should be a numeric value, e.g. `20`.
def allowed_level?(level)
valid_level?(level) && non_restricted_level?(level)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d562e76da42..4f401acccf9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -132,6 +132,12 @@ msgstr ""
msgid "%{label_for_message} unavailable"
msgstr ""
+msgid "%{level_name} is not allowed in a %{group_level_name} group."
+msgstr ""
+
+msgid "%{level_name} is not allowed since the fork source project has lower visibility."
+msgstr ""
+
msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
@@ -203,6 +209,12 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "'%{level}' is not a valid visibility level"
+msgstr ""
+
+msgid "'%{source}' is not a import source"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -867,6 +879,9 @@ msgstr ""
msgid "Any"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
@@ -912,15 +927,27 @@ msgstr ""
msgid "Archive jobs"
msgstr ""
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -963,6 +990,18 @@ msgstr ""
msgid "Artifacts"
msgstr ""
+msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
+msgstr ""
+
+msgid "AsanaService|Asana - Teamwork without email"
+msgstr ""
+
+msgid "AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user."
+msgstr ""
+
msgid "Ask your group maintainer to set up a group Runner."
msgstr ""
@@ -1206,6 +1245,30 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
+msgid "BambooService|A continuous integration and build server"
+msgstr ""
+
+msgid "BambooService|A user with API access, if applicable"
+msgstr ""
+
+msgid "BambooService|Atlassian Bamboo CI"
+msgstr ""
+
+msgid "BambooService|Bamboo build plan key like KEY"
+msgstr ""
+
+msgid "BambooService|Bamboo root URL like https://bamboo.example.com"
+msgstr ""
+
+msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1413,6 +1476,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI/CD"
msgstr ""
@@ -1491,6 +1557,9 @@ msgstr ""
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot refer to a group milestone by an internal id!"
+msgstr ""
+
msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
msgstr ""
@@ -1503,6 +1572,9 @@ msgstr ""
msgid "Certificate (PEM)"
msgstr ""
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1707,6 +1779,9 @@ msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -2408,6 +2483,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2417,6 +2498,9 @@ msgstr ""
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2561,6 +2645,9 @@ msgstr ""
msgid "Could not authorize chat nickname. Try again!"
msgstr ""
+msgid "Could not change HEAD: branch '%{branch}' does not exist"
+msgstr ""
+
msgid "Could not connect to FogBugz, check your URL"
msgstr ""
@@ -3070,6 +3157,12 @@ msgstr ""
msgid "Discard draft"
msgstr ""
+msgid "DiscordService|Discord Notifications"
+msgstr ""
+
+msgid "DiscordService|Receive event notifications in Discord"
+msgstr ""
+
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr ""
@@ -3109,6 +3202,9 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3193,6 +3289,9 @@ msgstr ""
msgid "Email patch"
msgstr ""
+msgid "Email the pipelines status to a list of recipients."
+msgstr ""
+
msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
msgstr ""
@@ -3220,6 +3319,30 @@ msgstr ""
msgid "Emails"
msgstr ""
+msgid "Emails separated by comma"
+msgstr ""
+
+msgid "EmailsOnPushService|Disable code diffs"
+msgstr ""
+
+msgid "EmailsOnPushService|Don't include possibly sensitive code diffs in notification body."
+msgstr ""
+
+msgid "EmailsOnPushService|Email the commits and diff of each push to a list of recipients."
+msgstr ""
+
+msgid "EmailsOnPushService|Emails on push"
+msgstr ""
+
+msgid "EmailsOnPushService|Emails separated by whitespace"
+msgstr ""
+
+msgid "EmailsOnPushService|Send from committer"
+msgstr ""
+
+msgid "EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains})."
+msgstr ""
+
msgid "Embed"
msgstr ""
@@ -3679,6 +3802,12 @@ msgstr ""
msgid "Explore public groups"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the \"New Project\" page."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3697,15 +3826,21 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
msgstr ""
+msgid "ExternalWikiService|External Wiki"
+msgstr ""
+
+msgid "ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki."
+msgstr ""
+
+msgid "ExternalWikiService|The URL of the external Wiki"
+msgstr ""
+
msgid "Facebook"
msgstr ""
@@ -3715,12 +3850,18 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed create wiki"
+msgstr ""
+
msgid "Failed to change the owner"
msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create repository via gitlab-shell"
+msgstr ""
+
msgid "Failed to create resources"
msgstr ""
@@ -3876,6 +4017,12 @@ msgstr ""
msgid "FirstPushedBy|pushed by"
msgstr ""
+msgid "FlowdockService|Flowdock Git source token"
+msgstr ""
+
+msgid "FlowdockService|Flowdock is a collaboration web app for technical teams."
+msgstr ""
+
msgid "FogBugz Email"
msgstr ""
@@ -3981,6 +4128,9 @@ msgstr ""
msgid "Generate a default set of labels"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4346,6 +4496,9 @@ msgstr ""
msgid "Hook was successfully updated."
msgstr ""
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
@@ -4556,6 +4709,9 @@ msgstr ""
msgid "Incompatible Project"
msgstr ""
+msgid "Incompatible options set!"
+msgstr ""
+
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
@@ -4631,6 +4787,9 @@ msgstr ""
msgid "Invalid Login or password"
msgstr ""
+msgid "Invalid feature"
+msgstr ""
+
msgid "Invalid file."
msgstr ""
@@ -4640,6 +4799,9 @@ msgstr ""
msgid "Invalid pin code"
msgstr ""
+msgid "Invalid repository path"
+msgstr ""
+
msgid "Invalid two-factor code."
msgstr ""
@@ -4682,6 +4844,9 @@ msgstr ""
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr ""
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
msgid "Issues, merge requests, pushes, and comments."
msgstr ""
@@ -4697,6 +4862,48 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "JiraService|Events for %{noteable_model_name} are disabled."
+msgstr ""
+
+msgid "JiraService|If different from Web URL"
+msgstr ""
+
+msgid "JiraService|JIRA API URL"
+msgstr ""
+
+msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgstr ""
+
+msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgstr ""
+
+msgid "JiraService|Jira issue tracker"
+msgstr ""
+
+msgid "JiraService|Password or API token"
+msgstr ""
+
+msgid "JiraService|Transition ID(s)"
+msgstr ""
+
+msgid "JiraService|Use , or ; to separate multiple transition IDs"
+msgstr ""
+
+msgid "JiraService|Use a password for server version and an API token for cloud version"
+msgstr ""
+
+msgid "JiraService|Use a username for server version and an email for cloud version"
+msgstr ""
+
+msgid "JiraService|Username or Email"
+msgstr ""
+
+msgid "JiraService|Web URL"
+msgstr ""
+
+msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
+msgstr ""
+
msgid "Job"
msgstr ""
@@ -4721,6 +4928,9 @@ msgstr ""
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -4829,6 +5039,9 @@ msgstr ""
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5127,6 +5340,9 @@ msgstr ""
msgid "Maximum job timeout"
msgstr ""
+msgid "Maximum job timeout has a value which could not be accepted"
+msgstr ""
+
msgid "Maximum push size (MB)"
msgstr ""
@@ -5427,7 +5643,7 @@ msgstr ""
msgid "Name:"
msgstr ""
-msgid "Naming, tags, avatar"
+msgid "Naming, topics, avatar"
msgstr ""
msgid "Naming, visibility"
@@ -5795,6 +6011,12 @@ msgstr ""
msgid "OfSearchInADropdown|Filter"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -5947,6 +6169,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6064,6 +6289,9 @@ msgstr ""
msgid "Pipelines charts"
msgstr ""
+msgid "Pipelines emails"
+msgstr ""
+
msgid "Pipelines for last month"
msgstr ""
@@ -6187,6 +6415,15 @@ msgstr ""
msgid "Pipeline|with stages"
msgstr ""
+msgid "PivotalTrackerService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "PivotalTrackerService|Pivotal Tracker API token."
+msgstr ""
+
+msgid "PivotalTrackerService|Project Management Software (Source Commits Endpoint)"
+msgstr ""
+
msgid "Plain diff"
msgstr ""
@@ -6244,6 +6481,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we import the repository for you. Refresh at will."
msgstr ""
@@ -6580,15 +6820,30 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
+msgid "Project cannot be shared with the group it is in or one of its ancestors."
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
msgid "Project details"
msgstr ""
@@ -6619,6 +6874,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -6838,6 +7099,45 @@ msgstr ""
msgid "Push to create a project"
msgstr ""
+msgid "PushoverService|%{user_name} deleted branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} push to branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|High Priority"
+msgstr ""
+
+msgid "PushoverService|Leave blank for all active devices"
+msgstr ""
+
+msgid "PushoverService|Low Priority"
+msgstr ""
+
+msgid "PushoverService|Lowest Priority"
+msgstr ""
+
+msgid "PushoverService|Normal Priority"
+msgstr ""
+
+msgid "PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop."
+msgstr ""
+
+msgid "PushoverService|See project %{project_full_name}"
+msgstr ""
+
+msgid "PushoverService|Total commits count: %{total_commits_count}"
+msgstr ""
+
+msgid "PushoverService|Your application key"
+msgstr ""
+
+msgid "PushoverService|Your user key"
+msgstr ""
+
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
@@ -6924,6 +7224,9 @@ msgstr ""
msgid "Remind later"
msgstr ""
+msgid "Remote object has no absolute path."
+msgstr ""
+
msgid "Remove"
msgstr ""
@@ -6933,6 +7236,9 @@ msgstr ""
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -6951,9 +7257,15 @@ msgstr ""
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7142,6 +7454,9 @@ msgstr ""
msgid "Revoked personal access token %{personal_access_token_name}!"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run untagged jobs"
msgstr ""
@@ -7196,6 +7511,9 @@ msgstr ""
msgid "Running…"
msgstr ""
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SSH Keys"
msgstr ""
@@ -7235,6 +7553,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -7358,6 +7679,9 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
msgid "Select a timezone"
msgstr ""
@@ -7412,6 +7736,9 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
@@ -8253,6 +8580,12 @@ msgstr ""
msgid "The file has been successfully deleted."
msgstr ""
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
@@ -8307,6 +8640,9 @@ msgstr ""
msgid "The name %{entryName} is already taken in this directory."
msgstr ""
+msgid "The number of times an upload record could not find its file"
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -8334,12 +8670,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project is still being deleted. Please try again later."
+msgstr ""
+
msgid "The project was successfully forked."
msgstr ""
msgid "The project was successfully imported."
msgstr ""
+msgid "The remote mirror took to long to complete."
+msgstr ""
+
msgid "The remote repository is being updated..."
msgstr ""
@@ -8439,6 +8781,9 @@ msgstr ""
msgid "There are no unstaged changes"
msgstr ""
+msgid "There is already a repository with that name on disk"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
@@ -8484,6 +8829,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -8532,6 +8880,9 @@ msgstr ""
msgid "This group does not provide any group Runners yet."
msgstr ""
+msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
+msgstr ""
+
msgid "This is a confidential issue."
msgstr ""
@@ -8676,6 +9027,9 @@ msgstr ""
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -8977,6 +9331,9 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
msgid "Total Time"
msgstr ""
@@ -8989,6 +9346,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -9049,6 +9409,12 @@ msgstr ""
msgid "Unable to schedule a pipeline to run immediately"
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -9058,6 +9424,12 @@ msgstr ""
msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr ""
+msgid "Unknown encryption strategy: %{encrypted_strategy}!"
+msgstr ""
+
+msgid "Unknown format"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -9130,7 +9502,7 @@ msgstr ""
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
-msgid "Update your project name, tags, description and avatar."
+msgid "Update your project name, topics, description and avatar."
msgstr ""
msgid "Updating"
@@ -9154,6 +9526,9 @@ msgstr ""
msgid "Upload file"
msgstr ""
+msgid "Upload file does not exist"
+msgstr ""
+
msgid "Upload object map"
msgstr ""
@@ -9319,6 +9694,9 @@ msgstr ""
msgid "Users were successfully added."
msgstr ""
+msgid "Using required encryption strategy when encrypted field is missing!"
+msgstr ""
+
msgid "Validate"
msgstr ""
@@ -9454,6 +9832,9 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
msgid "Webhooks Help"
msgstr ""
@@ -9735,6 +10116,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -9825,6 +10209,9 @@ msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -9891,6 +10278,9 @@ msgstr ""
msgid "Your Todos"
msgstr ""
+msgid "Your U2F device did not send a valid JSON response."
+msgstr ""
+
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
@@ -9927,6 +10317,9 @@ msgstr ""
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -9969,6 +10362,12 @@ msgstr ""
msgid "allowed to fail"
msgstr ""
+msgid "already being used for another group or project milestone."
+msgstr ""
+
+msgid "already shared with this group"
+msgstr ""
+
msgid "among other things"
msgstr ""
@@ -9981,6 +10380,15 @@ msgstr ""
msgid "branch name"
msgstr ""
+msgid "cannot be changed if a personal project has container registry tags."
+msgstr ""
+
+msgid "cannot be enabled unless all domains have TLS certificates"
+msgstr ""
+
+msgid "cannot include leading slash or directory traversal."
+msgstr ""
+
msgid "commented on %{link_to_project}"
msgstr ""
@@ -10024,9 +10432,15 @@ msgstr[1] ""
msgid "done"
msgstr ""
+msgid "element is not a hierarchy"
+msgstr ""
+
msgid "enabled"
msgstr ""
+msgid "encrypted: needs to be a :required, :optional or :migrating!"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -10060,6 +10474,9 @@ msgstr ""
msgid "group"
msgstr ""
+msgid "has already been taken"
+msgstr ""
+
msgid "here"
msgstr ""
@@ -10087,6 +10504,9 @@ msgstr ""
msgid "is not a valid X509 certificate."
msgstr ""
+msgid "is not an email you own"
+msgstr ""
+
msgid "issue boards"
msgstr ""
@@ -10119,6 +10539,9 @@ msgid_plural "merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "milestone should belong either to a project or a group."
+msgstr ""
+
msgid "missing"
msgstr ""
@@ -10326,9 +10749,15 @@ msgstr ""
msgid "mrWidget|to be merged automatically when the pipeline succeeds"
msgstr ""
+msgid "must be greater than start date"
+msgstr ""
+
msgid "n/a"
msgstr ""
+msgid "needs to be beetween 10 minutes and 1 month"
+msgstr ""
+
msgid "new merge request"
msgstr ""
@@ -10416,6 +10845,9 @@ msgstr ""
msgid "source diff"
msgstr ""
+msgid "specified top is not part of the tree"
+msgstr ""
+
msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr ""
diff --git a/qa/Dockerfile b/qa/Dockerfile
index ca7f9accb70..ae3ec2acfdf 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -30,7 +30,7 @@ RUN wget -q https://download.docker.com/linux/static/stable/x86_64/docker-17.09.
#
RUN curl -sS -L https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
-RUN apt-get update -q && apt-get install -y google-chrome-stable && apt-get clean
+RUN apt-get update -q && apt-get install -y --allow-unauthenticated google-chrome-stable && apt-get clean
##
# Install chromedriver to make it work with Selenium
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index c9b0db6a272..9d3d42fb6ae 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -49,7 +49,7 @@ GEM
mini_portile2 (2.4.0)
minitest (5.11.1)
netrc (0.11.0)
- nokogiri (1.10.1)
+ nokogiri (1.10.2)
mini_portile2 (~> 2.4.0)
pry (0.11.3)
coderay (~> 1.1.0)
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index 578f097e2dc..6dffbac5694 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -4,27 +4,21 @@ module QA
module Settings
class Advanced < Page::Base
view 'app/views/projects/edit.html.haml' do
- element :project_path_field, 'text_field :path' # rubocop:disable QA/ElementWithPattern
- element :project_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
- element :rename_project_button, "submit 'Rename project'" # rubocop:disable QA/ElementWithPattern
+ element :project_path_field
+ element :change_path_button
end
- def rename_to(path)
- fill_project_name(path)
+ def update_project_path_to(path)
fill_project_path(path)
- rename_project!
+ click_change_path_button
end
def fill_project_path(path)
- fill_in :project_path, with: path
+ fill_element :project_path_field, path
end
- def fill_project_name(name)
- fill_in :project_name, with: name
- end
-
- def rename_project!
- click_on 'Rename project'
+ def click_change_path_button
+ click_element :change_path_button
end
end
end
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index f3b217677f2..233e681e0df 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -4,14 +4,6 @@ module QA
module Settings
module Common
include QA::Page::Settings::Common
-
- def self.included(base)
- base.class_eval do
- view 'app/views/projects/edit.html.haml' do
- element :advanced_settings_expand, "= expanded ? 'Collapse' : 'Expand'" # rubocop:disable QA/ElementWithPattern
- end
- end
- end
end
end
end
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index d8cf1d49dd2..cf464e25ca5 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -9,6 +9,24 @@ module QA
element :advanced_settings
end
+ view 'app/views/projects/settings/_general.html.haml' do
+ element :project_name_field
+ element :save_naming_topics_avatar_button
+ end
+
+ def rename_project_to(name)
+ fill_project_name(name)
+ click_save_changes
+ end
+
+ def fill_project_name(name)
+ fill_element :project_name_field, name
+ end
+
+ def click_save_changes
+ click_element :save_naming_topics_avatar_button
+ end
+
def expand_advanced_settings(&block)
expand_section(:advanced_settings) do
Advanced.perform(&block)
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index 741821ddf8c..5c86c102105 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -69,7 +69,7 @@ describe QA::Specs::Runner do
subject { described_class.new }
- it 'it includes default args and excludes the skip_signup_disabled tag' do
+ it 'includes default args and excludes the skip_signup_disabled tag' do
expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
@@ -83,7 +83,7 @@ describe QA::Specs::Runner do
subject { described_class.new }
- it 'it includes default args and excludes the requires_git_protocol_v2 tag' do
+ it 'includes default args and excludes the requires_git_protocol_v2 tag' do
expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~requires_git_protocol_v2', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 70c34f8640b..0d8c26a2ee9 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -62,4 +62,10 @@ FactoryBot.define do
project_key: 'jira-key'
)
end
+
+ factory :hipchat_service do
+ project
+ type 'HipchatService'
+ token 'test_token'
+ end
end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 25ed3bdc88e..ce780789f5a 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -73,22 +73,26 @@ describe 'Admin::Hooks' do
end
describe 'Remove existing hook', :js do
+ let(:hook_url) { generate(:url) }
+
before do
- create(:system_hook)
+ create(:system_hook, url: hook_url)
end
context 'removes existing hook' do
it 'from hooks list page' do
visit admin_hooks_path
- expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1)
+ accept_confirm { click_link 'Remove' }
+ expect(page).not_to have_content(hook_url)
end
it 'from hook edit page' do
visit admin_hooks_path
click_link 'Edit'
- expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1)
+ accept_confirm { click_link 'Remove' }
+ expect(page).not_to have_content(hook_url)
end
end
end
diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
index 29442a58ea4..586e2e33112 100644
--- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb
+++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
@@ -138,7 +138,7 @@ describe "User adds a comment on a commit", :js do
click_button("Comment")
end
- page.within(".diff-file:nth-of-type(1) .notes_content.parallel.old") do
+ page.within(".diff-file:nth-of-type(1) .notes-content.parallel.old") do
expect(page).to have_content(old_comment)
end
@@ -152,7 +152,7 @@ describe "User adds a comment on a commit", :js do
wait_for_requests
- expect(all(".diff-file:nth-of-type(1) .notes_content.parallel.new")[1].text).to have_content(new_comment)
+ expect(all(".diff-file:nth-of-type(1) .notes-content.parallel.new")[1].text).to have_content(new_comment)
end
end
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 50ba67f0ffc..f26941ab567 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -62,8 +62,9 @@ describe 'Projects > Members > User requests access', :js do
accept_confirm { click_link 'Withdraw Access Request' }
- expect(project.requesters.exists?(user_id: user)).to be_falsey
expect(page).to have_content 'Your access request to the project has been withdrawn.'
+ expect(page).not_to have_content 'Withdraw Access Request'
+ expect(page).to have_content 'Request Access'
end
def open_project_settings_menu
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
index 65b597da269..1a13fe03a67 100644
--- a/spec/features/projects/services/disable_triggers_spec.rb
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -14,11 +14,10 @@ describe 'Disable individual triggers' do
end
context 'service has multiple supported events' do
- let(:service_name) { 'JIRA' }
+ let(:service_name) { 'HipChat' }
it 'shows trigger checkboxes' do
- event_count = JiraService.supported_events.count
- expect(event_count).to be > 1
+ event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
expect(page).to have_css(checkbox_selector, count: event_count)
diff --git a/spec/features/projects/services/user_activates_hipchat_spec.rb b/spec/features/projects/services/user_activates_hipchat_spec.rb
new file mode 100644
index 00000000000..d6b69a5bd68
--- /dev/null
+++ b/spec/features/projects/services/user_activates_hipchat_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User activates HipChat' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit(project_settings_integrations_path(project))
+
+ click_link('HipChat')
+ end
+
+ context 'with standart settings' do
+ it 'activates service' do
+ check('Active')
+ fill_in('Room', with: 'gitlab')
+ fill_in('Token', with: 'verySecret')
+ click_button('Save')
+
+ expect(page).to have_content('HipChat activated.')
+ end
+ end
+
+ context 'with custom settings' do
+ it 'activates service' do
+ check('Active')
+ fill_in('Room', with: 'gitlab_custom')
+ fill_in('Token', with: 'secretCustom')
+ fill_in('Server', with: 'https://chat.example.com')
+ click_button('Save')
+
+ expect(page).to have_content('HipChat activated.')
+ end
+ end
+end
diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb
index b0a838a7d2b..e9c8cf0fe34 100644
--- a/spec/features/projects/services/user_views_services_spec.rb
+++ b/spec/features/projects/services/user_views_services_spec.rb
@@ -14,6 +14,7 @@ describe 'User views services' do
it 'shows the list of available services' do
expect(page).to have_content('Project services')
expect(page).to have_content('Campfire')
+ expect(page).to have_content('HipChat')
expect(page).to have_content('Assembla')
expect(page).to have_content('Pushover')
expect(page).to have_content('Atlassian Bamboo')
@@ -21,7 +22,5 @@ describe 'User views services' do
expect(page).to have_content('Asana')
expect(page).to have_content('Irker (IRC gateway)')
expect(page).to have_content('Packagist')
- expect(page).to have_content('Mattermost')
- expect(page).to have_content('Slack')
end
end
diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb
index 64c9af4b706..d3979b79910 100644
--- a/spec/features/projects/settings/user_renames_a_project_spec.rb
+++ b/spec/features/projects/settings/user_renames_a_project_spec.rb
@@ -9,24 +9,33 @@ describe 'Projects > Settings > User renames a project' do
visit edit_project_path(project)
end
- def rename_project(project, name: nil, path: nil)
- fill_in('project_name', with: name) if name
- fill_in('Path', with: path) if path
- click_button('Rename project')
+ def change_path(project, path)
+ within('.advanced-settings') do
+ fill_in('Path', with: path)
+ click_button('Change path')
+ end
+ project.reload
wait_for_edit_project_page_reload
+ end
+
+ def change_name(project, name)
+ within('.general-settings') do
+ fill_in('Project name', with: name)
+ click_button('Save changes')
+ end
project.reload
+ wait_for_edit_project_page_reload
end
def wait_for_edit_project_page_reload
- expect(find('.project-edit-container')).to have_content('Rename repository')
+ expect(find('.advanced-settings')).to have_content('Change path')
end
context 'with invalid characters' do
- it 'shows errors for invalid project path/name' do
- rename_project(project, name: 'foo&bar', path: 'foo&bar')
- expect(page).to have_field 'Project name', with: 'foo&bar'
+ it 'shows errors for invalid project path' do
+ change_path(project, 'foo&bar')
+
expect(page).to have_field 'Path', with: 'foo&bar'
- expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
end
end
@@ -42,13 +51,13 @@ describe 'Projects > Settings > User renames a project' do
context 'when changing project name' do
it 'renames the repository' do
- rename_project(project, name: 'bar')
+ change_name(project, 'bar')
expect(find('.breadcrumbs')).to have_content(project.name)
end
context 'with emojis' do
it 'shows error for invalid project name' do
- rename_project(project, name: '🚀 foo bar ☁️')
+ change_name(project, '🚀 foo bar ☁️')
expect(page).to have_field 'Project name', with: '🚀 foo bar ☁️'
expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'."
end
@@ -67,7 +76,7 @@ describe 'Projects > Settings > User renames a project' do
end
it 'the project is accessible via the new path' do
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit new_path
@@ -77,7 +86,7 @@ describe 'Projects > Settings > User renames a project' do
it 'the project is accessible via a redirect from the old path' do
old_path = project_path(project)
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit old_path
@@ -88,7 +97,7 @@ describe 'Projects > Settings > User renames a project' do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
old_path = project_path(project)
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index dbf0d427976..ff4e6197746 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -373,6 +373,21 @@ describe 'Project' do
end
end
+ describe 'edit' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:path) { edit_project_path(project) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ visit path
+ end
+
+ it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' },
+ { form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
+ end
+
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index ae9b65d1a39..ea02f36d9d0 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -246,26 +246,6 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
end
end
end
-
- describe "when two-factor authentication is disabled" do
- let(:user) { create(:user) }
-
- before do
- user = gitlab_sign_in(:user)
- user.update_attribute(:otp_required_for_login, true)
- visit profile_account_path
- manage_two_factor_authentication
- expect(page).to have_content("Your U2F device needs to be set up.")
- register_u2f_device
- end
-
- it "deletes u2f registrations" do
- visit profile_two_factor_auth_path
- expect do
- accept_confirm { click_on "Disable" }
- end.to change { U2fRegistration.count }.by(-1)
- end
- end
end
describe 'fallback code authentication' do
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index eea7bd87257..33a35069004 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -12,7 +12,7 @@ import { setTestTimeout } from 'helpers/timeout';
import $ from 'jquery';
describe('Clusters', () => {
- setTestTimeout(500);
+ setTestTimeout(1000);
let cluster;
let mock;
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 3f331055a32..0878c1de095 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -23,14 +23,6 @@ describe('text_utility', () => {
});
});
- describe('capitalizeFirstCharacter', () => {
- it('returns string with first letter capitalized', () => {
- expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
- expect(textUtils.highCountTrim(105)).toBe('99+');
- expect(textUtils.highCountTrim(100)).toBe('99+');
- });
- });
-
describe('humanize', () => {
it('should remove underscores and uppercase the first letter', () => {
expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
@@ -57,9 +49,9 @@ describe('text_utility', () => {
});
});
- describe('slugify', () => {
- it('should remove accents and convert to lower case', () => {
- expect(textUtils.slugify('João')).toEqual('joão');
+ describe('capitalizeFirstCharacter', () => {
+ it('returns string with first letter capitalized', () => {
+ expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
});
});
@@ -152,6 +144,12 @@ describe('text_utility', () => {
});
});
+ describe('slugifyWithUnderscore', () => {
+ it('should replaces whitespaces with underscore and convert to lower case', () => {
+ expect(textUtils.slugifyWithUnderscore('My Input String')).toEqual('my_input_string');
+ });
+ });
+
describe('truncateNamespace', () => {
it(`should return the root namespace if the namespace only includes one level`, () => {
expect(textUtils.truncateNamespace('a / b')).toBe('a');
diff --git a/spec/frontend/mr_popover/index_spec.js b/spec/frontend/mr_popover/index_spec.js
new file mode 100644
index 00000000000..8c33e52a04b
--- /dev/null
+++ b/spec/frontend/mr_popover/index_spec.js
@@ -0,0 +1,30 @@
+import * as createDefaultClient from '~/lib/graphql';
+import { setHTMLFixture } from '../helpers/fixtures';
+import initMRPopovers from '~/mr_popover/index';
+
+createDefaultClient.default = jest.fn();
+
+describe('initMRPopovers', () => {
+ let mr1;
+ let mr2;
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ <div id="one" class="gfm-merge_request">MR1</div>
+ <div id="two" class="gfm-merge_request">MR2</div>
+ `);
+
+ mr1 = document.querySelector('#one');
+ mr2 = document.querySelector('#two');
+
+ mr1.addEventListener = jest.fn();
+ mr2.addEventListener = jest.fn();
+ });
+
+ it('does not add the same event listener twice', () => {
+ initMRPopovers([mr1, mr1, mr2]);
+
+ expect(mr1.addEventListener).toHaveBeenCalledTimes(1);
+ expect(mr2.addEventListener).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index 3ce69bc3c20..1aabf3c2132 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -75,6 +75,14 @@ describe('diffs/components/app', () => {
expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false);
});
+ it('does not add container-limiting classes when isFluidLayout', () => {
+ createComponent({ isFluidLayout: true }, ({ state }) => {
+ state.diffs.isParallelView = false;
+ });
+
+ expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false);
+ });
+
it('displays loading icon on loading', () => {
createComponent({}, ({ state }) => {
state.diffs.isLoading = true;
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 3d2c617e479..394e3343be6 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -670,7 +670,7 @@ describe('Notes', function() {
done();
})
.catch(done.fail);
- });
+ }, 2000);
});
describe('postComment with Slash commands', () => {
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index ed557ffd4e3..54369ff75f4 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -223,6 +223,7 @@ project:
- packagist_service
- pivotaltracker_service
- prometheus_service
+- hipchat_service
- flowdock_service
- assembla_service
- asana_service
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 773651dd226..4a7accc4c52 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6795,6 +6795,28 @@
"wiki_page_events": true
},
{
+ "id": 93,
+ "title": "HipChat",
+ "project_id": 5,
+ "created_at": "2016-06-14T15:01:51.219Z",
+ "updated_at": "2016-06-14T15:01:51.219Z",
+ "active": false,
+ "properties": {
+ "notify_only_broken_pipelines": true
+ },
+ "template": false,
+ "push_events": true,
+ "issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": true,
+ "note_events": true,
+ "pipeline_events": true,
+ "type": "HipchatService",
+ "category": "common",
+ "default": false,
+ "wiki_page_events": true
+ },
+ {
"id": 91,
"title": "Flowdock",
"project_id": 5,
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 2c1146ceff5..0a170a157fe 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -85,4 +85,12 @@ describe Gitlab::VisibilityLevel do
.to eq(described_class::PRIVATE)
end
end
+
+ describe '.valid_level?' do
+ it 'returns true when visibility is valid' do
+ expect(described_class.valid_level?(described_class::PRIVATE)).to be_truthy
+ expect(described_class.valid_level?(described_class::INTERNAL)).to be_truthy
+ expect(described_class.valid_level?(described_class::PUBLIC)).to be_truthy
+ end
+ end
end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
new file mode 100644
index 00000000000..fd9e33c1781
--- /dev/null
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -0,0 +1,410 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HipchatService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
+ describe "Execute" do
+ let(:hipchat) { described_class.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
+ let(:project_name) { project.full_name.gsub(/\s/, '') }
+ let(:token) { 'verySecret' }
+ let(:server_url) { 'https://hipchat.example.com'}
+ let(:push_sample_data) do
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+ end
+
+ before do
+ allow(hipchat).to receive_messages(
+ project_id: project.id,
+ project: project,
+ room: 123456,
+ server: server_url,
+ token: token
+ )
+ WebMock.stub_request(:post, api_url)
+ end
+
+ it 'tests and return errors' do
+ allow(hipchat).to receive(:execute).and_raise(StandardError, 'no such room')
+ result = hipchat.test(push_sample_data)
+
+ expect(result[:success]).to be_falsey
+ expect(result[:result].to_s).to eq('no such room')
+ end
+
+ it 'uses v1 if version is provided' do
+ allow(hipchat).to receive(:api_version).and_return('v1')
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v1',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+
+ it 'uses v2 as the version when nothing is provided' do
+ allow(hipchat).to receive(:api_version).and_return('')
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v2',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+
+ context 'push events' do
+ it "calls Hipchat API for push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ push_sample_data[:object_attributes]
+ branch = push_sample_data[:ref].gsub('refs/heads/', '')
+ expect(message).to include("#{user.name} pushed to branch " \
+ "<a href=\"#{project.web_url}/commits/#{branch}\">#{branch}</a> of " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>")
+ end
+ end
+
+ context 'tag_push events' do
+ let(:push_sample_data) do
+ Gitlab::DataBuilder::Push.build(
+ project,
+ user,
+ Gitlab::Git::BLANK_SHA,
+ '1' * 40,
+ 'refs/tags/test',
+ [])
+ end
+
+ it "calls Hipchat API for tag push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a tag push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ push_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} pushed new tag " \
+ "<a href=\"#{project.web_url}/commits/test\">test</a> to " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>\n")
+ end
+ end
+
+ context 'issue events' do
+ let(:issue) { create(:issue, title: 'Awesome issue', description: '**please** fix') }
+ let(:issue_service) { Issues::CreateService.new(project, user) }
+ let(:issues_sample_data) { issue_service.hook_data(issue, 'open') }
+
+ it "calls Hipchat API for issue events" do
+ hipchat.execute(issues_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates an issue message" do
+ message = hipchat.send(:create_issue_message, issues_sample_data)
+
+ obj_attr = issues_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "<a href=\"#{obj_attr[:url]}\">issue ##{obj_attr["iid"]}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>Awesome issue</b>" \
+ "<pre><strong>please</strong> fix</pre>")
+ end
+ end
+
+ context 'merge request events' do
+ let(:merge_request) { create(:merge_request, description: '**please** fix', title: 'Awesome merge request', target_project: project, source_project: project) }
+ let(:merge_service) { MergeRequests::CreateService.new(project, user) }
+ let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') }
+
+ it "calls Hipchat API for merge requests events" do
+ hipchat.execute(merge_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a merge request message" do
+ message = hipchat.send(:create_merge_request_message,
+ merge_sample_data)
+
+ obj_attr = merge_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{obj_attr["iid"]}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>Awesome merge request</b>" \
+ "<pre><strong>please</strong> fix</pre>")
+ end
+ end
+
+ context "Note events" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, creator: user) }
+
+ context 'when commit comment event triggered' do
+ let(:commit_note) do
+ create(:note_on_commit, author: user, project: project,
+ commit_id: project.repository.commit.id,
+ note: 'a comment on a commit')
+ end
+
+ it "calls Hipchat API for commit comment events" do
+ data = Gitlab::DataBuilder::Note.build(commit_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ commit_id = Commit.truncate_sha(data[:commit][:id])
+ title = hipchat.send(:format_title, data[:commit][:message])
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">commit #{commit_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "#{title}" \
+ "<pre>a comment on a commit</pre>")
+ end
+ end
+
+ context 'when merge request comment event triggered' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ let(:merge_request_note) do
+ create(:note_on_merge_request, noteable: merge_request,
+ project: project,
+ note: "merge request **note**")
+ end
+
+ it "calls Hipchat API for merge request comment events" do
+ data = Gitlab::DataBuilder::Note.build(merge_request_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ merge_id = data[:merge_request]['iid']
+ title = data[:merge_request]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{merge_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>merge request <strong>note</strong></pre>")
+ end
+ end
+
+ context 'when issue comment event triggered' do
+ let(:issue) { create(:issue, project: project) }
+ let(:issue_note) do
+ create(:note_on_issue, noteable: issue, project: project,
+ note: "issue **note**")
+ end
+
+ it "calls Hipchat API for issue comment events" do
+ data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ hipchat.execute(data)
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ issue_id = data[:issue]['iid']
+ title = data[:issue]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">issue ##{issue_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>issue <strong>note</strong></pre>")
+ end
+
+ context 'with confidential issue' do
+ before do
+ issue.update!(confidential: true)
+ end
+
+ it 'calls Hipchat API with issue comment' do
+ data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ hipchat.execute(data)
+
+ message = hipchat.send(:create_message, data)
+
+ expect(message).to include("<pre>issue <strong>note</strong></pre>")
+ end
+ end
+ end
+
+ context 'when snippet comment event triggered' do
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:snippet_note) do
+ create(:note_on_project_snippet, noteable: snippet,
+ project: project,
+ note: "snippet note")
+ end
+
+ it "calls Hipchat API for snippet comment events" do
+ data = Gitlab::DataBuilder::Note.build(snippet_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ snippet_id = data[:snippet]['id']
+ title = data[:snippet]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">snippet ##{snippet_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>snippet note</pre>")
+ end
+ end
+ end
+
+ context 'pipeline events' do
+ let(:pipeline) { create(:ci_empty_pipeline, user: create(:user)) }
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+
+ context 'for failed' do
+ before do
+ pipeline.drop
+ end
+
+ it "calls Hipchat API" do
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a build message" do
+ message = hipchat.__send__(:create_pipeline_message, data)
+
+ project_url = project.web_url
+ project_name = project.full_name.gsub(/\s/, '')
+ pipeline_attributes = data[:object_attributes]
+ ref = pipeline_attributes[:ref]
+ ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+ duration = pipeline_attributes[:duration]
+ user_name = data[:user][:name]
+
+ expect(message).to eq("<a href=\"#{project_url}\">#{project_name}</a>: " \
+ "Pipeline <a href=\"#{project_url}/pipelines/#{pipeline.id}\">##{pipeline.id}</a> " \
+ "of <a href=\"#{project_url}/commits/#{ref}\">#{ref}</a> #{ref_type} " \
+ "by #{user_name} failed in #{duration} second(s)")
+ end
+ end
+
+ context 'for succeeded' do
+ before do
+ pipeline.succeed
+ end
+
+ it "calls Hipchat API" do
+ hipchat.notify_only_broken_pipelines = false
+ hipchat.execute(data)
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "notifies only broken" do
+ hipchat.notify_only_broken_pipelines = true
+ hipchat.execute(data)
+ expect(WebMock).not_to have_requested(:post, api_url).once
+ end
+ end
+ end
+
+ context "#message_options" do
+ it "is set to the defaults" do
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' })
+ end
+
+ it "sets notify to true" do
+ allow(hipchat).to receive(:notify).and_return('1')
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' })
+ end
+
+ it "sets the color" do
+ allow(hipchat).to receive(:color).and_return('red')
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' })
+ end
+
+ context 'with a successful build' do
+ it 'uses the green color' do
+ data = { object_kind: 'pipeline',
+ object_attributes: { status: 'success' } }
+
+ expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'green' })
+ end
+ end
+
+ context 'with a failed build' do
+ it 'uses the red color' do
+ data = { object_kind: 'pipeline',
+ object_attributes: { status: 'failed' } }
+
+ expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'red' })
+ end
+ end
+ end
+ end
+
+ context 'with UrlBlocker' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:hipchat) { create(:hipchat_service, project: project, properties: { room: 'test' }) }
+ let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+
+ describe '#execute' do
+ before do
+ hipchat.server = 'http://localhost:9123'
+ end
+
+ it 'raises UrlBlocker for localhost' do
+ expect(Gitlab::UrlBlocker).to receive(:validate!).and_call_original
+ expect { hipchat.execute(push_sample_data) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 7f8d2ff91fd..9f6a0b53281 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -44,6 +44,7 @@ describe Project do
it { is_expected.to have_one(:pipelines_email_service) }
it { is_expected.to have_one(:irker_service) }
it { is_expected.to have_one(:pivotaltracker_service) }
+ it { is_expected.to have_one(:hipchat_service) }
it { is_expected.to have_one(:flowdock_service) }
it { is_expected.to have_one(:assembla_service) }
it { is_expected.to have_one(:slack_slash_commands_service) }
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index cacdb0e0595..d5c85c11195 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -22,6 +22,7 @@ describe WikiPage do
create_page('dir_1/dir_1_1/page_3', 'content')
create_page('page_1', 'content')
create_page('dir_1/page_2', 'content')
+ create_page('dir_2', 'page with dir name')
create_page('dir_2/page_5', 'content')
create_page('page_6', 'content')
create_page('dir_2/page_4', 'content')
@@ -29,6 +30,7 @@ describe WikiPage do
let(:page_1) { wiki.find_page('page_1') }
let(:page_6) { wiki.find_page('page_6') }
+ let(:page_dir_2) { wiki.find_page('dir_2') }
let(:dir_1) do
WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
@@ -44,7 +46,7 @@ describe WikiPage do
context 'sort by title' do
let(:grouped_entries) { described_class.group_by_directory(wiki.pages) }
- let(:expected_grouped_entries) { [dir_1_1, dir_1, dir_2, page_1, page_6] }
+ let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] }
it 'returns an array with pages and directories' do
grouped_entries.each_with_index do |page_or_dir, i|
@@ -59,7 +61,7 @@ describe WikiPage do
context 'sort by created_at' do
let(:grouped_entries) { described_class.group_by_directory(wiki.pages(sort: 'created_at')) }
- let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, dir_2, page_6] }
+ let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] }
it 'returns an array with pages and directories' do
grouped_entries.each_with_index do |page_or_dir, i|
@@ -73,7 +75,7 @@ describe WikiPage do
end
it 'returns an array with retained order with directories at the top' do
- expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6']
+ expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6']
grouped_entries = described_class.group_by_directory(wiki.pages)
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 0d46463312b..26158231444 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Pipelines do
@@ -493,7 +495,7 @@ describe API::Pipelines do
context 'pipeline created is not created by the developer user' do
let(:api_user) { create(:user) }
- it 'should not return pipeline variables' do
+ it 'does not return pipeline variables' do
subject
expect(response).to have_gitlab_http_status(403)
@@ -502,7 +504,7 @@ describe API::Pipelines do
end
context 'user is not a project member' do
- it 'should not return pipeline variables' do
+ it 'does not return pipeline variables' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member)
expect(response).to have_gitlab_http_status(404)
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 494c5892210..9e1ae5e8742 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -60,7 +60,7 @@ describe Clusters::Applications::InstallService do
let(:error) { StandardError.new('something bad happened') }
before do
- expect(application).to receive(:make_installing!).once.and_raise(error)
+ expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
include_examples 'logs kubernetes errors' do
@@ -70,12 +70,10 @@ describe Clusters::Applications::InstallService do
end
it 'make the application errored' do
- expect(helm_client).not_to receive(:install)
-
service.execute
expect(application).to be_errored
- expect(application.status_reason).to eq("Can't start installation process.")
+ expect(application.status_reason).to eq('Failed to install.')
end
end
end
diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb
index 10b1379a127..3ebe0540837 100644
--- a/spec/services/clusters/applications/patch_service_spec.rb
+++ b/spec/services/clusters/applications/patch_service_spec.rb
@@ -66,16 +66,14 @@ describe Clusters::Applications::PatchService do
end
before do
- expect(application).to receive(:make_updating!).once.and_raise(error)
+ expect(helm_client).to receive(:update).with(update_command).and_raise(error)
end
it 'make the application errored' do
- expect(helm_client).not_to receive(:update)
-
service.execute
expect(application).to be_update_errored
- expect(application.status_reason).to eq("Can't start update process.")
+ expect(application.status_reason).to eq('Failed to update.')
end
end
end
diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb
index dd2e6e94e4f..a80b1d9127c 100644
--- a/spec/services/clusters/applications/upgrade_service_spec.rb
+++ b/spec/services/clusters/applications/upgrade_service_spec.rb
@@ -60,7 +60,7 @@ describe Clusters::Applications::UpgradeService do
let(:error) { StandardError.new('something bad happened') }
before do
- expect(application).to receive(:make_updating!).once.and_raise(error)
+ expect(helm_client).to receive(:update).with(install_command).and_raise(error)
end
include_examples 'logs kubernetes errors' do
@@ -70,12 +70,10 @@ describe Clusters::Applications::UpgradeService do
end
it 'make the application errored' do
- expect(helm_client).not_to receive(:update)
-
service.execute
expect(application).to be_update_errored
- expect(application.status_reason).to eq("Can't start upgrade process.")
+ expect(application.status_reason).to eq('Failed to upgrade.')
end
end
end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index 54836c9adbf..c5ff6cdbacd 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -90,6 +90,17 @@ describe Groups::CreateService, '#execute' do
end
end
+ describe "when visibility level is passed as a string" do
+ let(:service) { described_class.new(user, group_params) }
+ let(:group_params) { { path: 'group_path', visibility: 'public' } }
+
+ it "assigns the correct visibility level" do
+ group = service.execute
+
+ expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
describe 'creating a mattermost team' do
let!(:params) { group_params.merge(create_chat_team: "true") }
let!(:service) { described_class.new(user, params) }
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
index 48fc247f699..d8d2be87fd3 100644
--- a/spec/services/users/activity_service_spec.rb
+++ b/spec/services/users/activity_service_spec.rb
@@ -78,7 +78,7 @@ describe Users::ActivityService do
let(:last_activity_on) { nil }
it 'does not update last_activity_on' do
- stub_exclusive_lease_taken("acitvity_service:#{user.id}", timeout: 1.minute.to_i)
+ stub_exclusive_lease_taken("activity_service:#{user.id}", timeout: 1.minute.to_i)
expect { subject.execute }.not_to change(user, :last_activity_on)
end
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
index 52a2ee49495..4e45e2921e7 100644
--- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -1,18 +1,17 @@
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
- def expect_disabled_state(form, submit, is_disabled = true)
+ def expect_disabled_state(form, submit_selector, is_disabled = true)
disabled_selector = is_disabled == true ? '[disabled]' : ':not([disabled])'
- form.find(".js-dirty-submit#{disabled_selector}", match: :first)
-
- expect(submit.disabled?).to be is_disabled
+ form.find("#{submit_selector}#{disabled_selector}")
end
selectors.each do |selector|
it "disables #{selector[:form]} submit until there are changes on #{selector[:input]}", :js do
form = find(selector[:form])
- submit = form.first('.js-dirty-submit')
+ submit_selector = selector[:submit] || 'input[type="submit"]'
+ submit = form.first(submit_selector)
input = form.first(selector[:input])
is_radio = input[:type] == 'radio'
is_checkbox = input[:type] == 'checkbox'
@@ -22,15 +21,14 @@ shared_examples 'dirty submit form' do |selector_args|
original_checkable = input if is_checkbox
expect(submit.disabled?).to be true
- expect(input.checked?).to be false
is_checkable ? input.click : input.set("#{original_value} changes")
- expect_disabled_state(form, submit, false)
+ expect_disabled_state(form, submit_selector, false)
is_checkable ? original_checkable.click : input.set(original_value)
- expect_disabled_state(form, submit)
+ expect_disabled_state(form, submit_selector)
end
end
end
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index 75ad948e42c..d87e5fcaa88 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -19,7 +19,7 @@ RSpec.shared_examples 'Maintainer manages access requests' do
expect_visible_access_request(entity, user)
- accept_confirm { click_on 'Grant access' }
+ click_on 'Grant access'
expect_no_visible_access_request(entity, user)
@@ -40,13 +40,11 @@ RSpec.shared_examples 'Maintainer manages access requests' do
end
def expect_visible_access_request(entity, user)
- expect(entity.requesters.exists?(user_id: user)).to be_truthy
expect(page).to have_content "Users requesting access to #{entity.name} 1"
expect(page).to have_content user.name
end
def expect_no_visible_access_request(entity, user)
- expect(entity.requesters.exists?(user_id: user)).to be_falsy
expect(page).not_to have_content "Users requesting access to #{entity.name}"
end
end
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index de6e32cb998..0c52cb5a947 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -520,6 +520,7 @@ hashie-forbidden_attributes,0.1.1,MIT
he,1.1.1,MIT
health_check,2.6.0,MIT
highlight.js,9.13.1,New BSD
+hipchat,1.5.2,MIT
hmac-drbg,1.0.1,MIT
hoopy,0.1.4,MIT
html-pipeline,2.8.4,MIT