summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc2
-rw-r--r--CHANGELOG.md19
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--app/assets/javascripts/awards_handler.js4
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js1
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js4
-rw-r--r--app/assets/javascripts/commits.js72
-rw-r--r--app/assets/javascripts/dispatcher.js165
-rw-r--r--app/assets/javascripts/ide/monaco_loader.js5
-rw-r--r--app/assets/javascripts/importer_status.js49
-rw-r--r--app/assets/javascripts/integrations/index.js7
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue26
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/index.js43
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/index.js4
-rw-r--r--app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue (renamed from app/assets/javascripts/pages/admin/users/shared/components/delete_user_modal.vue)0
-rw-r--r--app/assets/javascripts/pages/admin/users/index.js (renamed from app/assets/javascripts/pages/admin/users/shared/index.js)4
-rw-r--r--app/assets/javascripts/pages/dashboard/issues/index.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/merge_requests/index.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/show/index.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/index.js2
-rw-r--r--app/assets/javascripts/pages/explore/groups/index.js4
-rw-r--r--app/assets/javascripts/pages/explore/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/milestones/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/milestones/new/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/milestones/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/branches/new/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/commits/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/compare/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/environments/metrics/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/new/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/creations/index.js (renamed from app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js)2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js (renamed from app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js)2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue (renamed from app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue (renamed from app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue)2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js (renamed from app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js (renamed from app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg (renamed from app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js (renamed from app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js)10
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/charts/index.js56
-rw-r--r--app/assets/javascripts/pages/projects/services/edit/index.js13
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js (renamed from app/assets/javascripts/users/activity_calendar.js)0
-rw-r--r--app/assets/javascripts/pages/users/index.js (renamed from app/assets/javascripts/users/index.js)2
-rw-r--r--app/assets/javascripts/pages/users/show/index.js3
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js (renamed from app/assets/javascripts/users/user_tabs.js)10
-rw-r--r--app/assets/javascripts/pipelines/pipelines_charts.js38
-rw-r--r--app/assets/javascripts/pipelines/pipelines_times.js27
-rw-r--r--app/assets/javascripts/prometheus_metrics/index.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue106
-rw-r--r--app/assets/stylesheets/framework/lists.scss2
-rw-r--r--app/helpers/webpack_helper.rb15
-rw-r--r--app/models/ci/build.rb37
-rw-r--r--app/models/identity.rb9
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/repository.rb18
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/issues/move_service.rb25
-rw-r--r--app/validators/variable_duplicates_validator.rb21
-rw-r--r--app/views/admin/jobs/index.html.haml7
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml4
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml4
-rw-r--r--app/views/projects/pipelines/charts.html.haml3
-rw-r--r--app/views/projects/pipelines/charts/_pipeline_times.haml3
-rw-r--r--app/views/projects/pipelines/charts/_pipelines.haml3
-rw-r--r--app/views/projects/services/_form.html.haml3
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml3
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/shared/groups/_dropdown.html.haml6
-rw-r--r--app/views/users/show.html.haml3
-rw-r--r--changelogs/unreleased/17500-mr-multiple-issues-oxford-comma.yml5
-rw-r--r--changelogs/unreleased/34130-null-pipes.yml5
-rw-r--r--changelogs/unreleased/35530-teleporting-emoji.yml5
-rw-r--r--changelogs/unreleased/39607-fix-avatar--vertical-align.yml5
-rw-r--r--changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml4
-rw-r--r--changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml5
-rw-r--r--changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml5
-rw-r--r--changelogs/unreleased/42591-update-nokogiri.yml5
-rw-r--r--changelogs/unreleased/42641-monaco-service-workers-do-not-work-with-cdn-enabled.yml5
-rw-r--r--changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml5
-rw-r--r--changelogs/unreleased/42929-hide-new-variable-values.yml5
-rw-r--r--changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml5
-rw-r--r--changelogs/unreleased/asciidoc_inter_document_cross_references.yml5
-rw-r--r--changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml5
-rw-r--r--changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml5
-rw-r--r--changelogs/unreleased/dm-dont-cache-nil-root-ref.yml5
-rw-r--r--changelogs/unreleased/dm-escape-commit-message.yml5
-rw-r--r--changelogs/unreleased/dm-user-namespace-route-path-validation.yml5
-rw-r--r--changelogs/unreleased/expired-ci-artifacts.yml5
-rw-r--r--changelogs/unreleased/fix-validation-of-environment-scope-for-variables.yml5
-rw-r--r--changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml5
-rw-r--r--changelogs/unreleased/fl-refresh-btn.yml5
-rw-r--r--changelogs/unreleased/jej-fix-slow-lfs-object-check.yml5
-rw-r--r--changelogs/unreleased/mk-fix-no-untracked-upload-files-error.yml5
-rw-r--r--changelogs/unreleased/remove_ldap_person_validation.yml5
-rw-r--r--changelogs/unreleased/sh-fix-jira-trailing-slash.yml5
-rw-r--r--changelogs/unreleased/winh-new-branch-dropdown-style.yml5
-rw-r--r--changelogs/unreleased/winh-new-modal-component.yml5
-rw-r--r--config/application.rb2
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/rack_attack_global.rb5
-rw-r--r--config/webpack.config.js9
-rw-r--r--db/fixtures/development/01_admin.rb1
-rw-r--r--doc/administration/integration/plantuml.md14
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md2
-rw-r--r--doc/administration/pages/index.md15
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/project_import_export.md74
-rw-r--r--doc/api/projects.md4
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/development/fe_guide/components.md61
-rw-r--r--doc/development/fe_guide/dropdowns.md33
-rw-r--r--doc/development/fe_guide/img/gl-modal.pngbin0 -> 25893 bytes
-rw-r--r--doc/development/fe_guide/index.md9
-rw-r--r--doc/development/profiling.md11
-rw-r--r--doc/install/google_cloud_platform/img/boot_disk.pngbin0 -> 124175 bytes
-rw-r--r--doc/install/google_cloud_platform/img/change_admin_passwd_email.pngbin7193 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/chrome_not_secure_page.pngbin21705 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/first_signin.pngbin0 -> 69516 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.pngbin23486 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_gitlab_overview.pngbin42028 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_landing.pngbin59912 -> 15025 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.pngbin42090 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gcp_search_for_gitlab.pngbin7648 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gitlab_deployed_page.pngbin35573 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gitlab_first_sign_in.pngbin20054 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/gitlab_launch_button.pngbin5198 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/launch_vm.pngbin0 -> 47201 bytes
-rw-r--r--doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.pngbin50014 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/ssh_terminal.pngbin0 -> 94161 bytes
-rw-r--r--doc/install/google_cloud_platform/img/ssh_via_button.pngbin3062 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/img/vm_created.pngbin0 -> 13809 bytes
-rw-r--r--doc/install/google_cloud_platform/img/vm_details.pngbin0 -> 76519 bytes
-rw-r--r--doc/install/google_cloud_platform/index.md86
-rw-r--r--doc/topics/autodevops/index.md4
-rw-r--r--doc/user/project/clusters/index.md117
-rw-r--r--doc/user/project/repository/index.md2
-rw-r--r--doc/user/project/settings/import_export.md1
-rw-r--r--features/group/members.feature12
-rw-r--r--features/group/milestones.feature48
-rw-r--r--features/profile/profile.feature85
-rw-r--r--features/steps/group/members.rb15
-rw-r--r--features/steps/group/milestones.rb135
-rw-r--r--features/steps/profile/profile.rb226
-rw-r--r--features/support/env.rb7
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/api/project_import.rb69
-rw-r--r--lib/banzai/filter/html_entity_filter.rb2
-rw-r--r--lib/gitlab/asciidoc.rb3
-rw-r--r--lib/gitlab/checks/change_access.rb7
-rw-r--r--lib/gitlab/git_access.rb9
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/ldap/config.rb2
-rw-r--r--lib/gitlab/o_auth/user.rb8
-rw-r--r--lib/gitlab/profiler.rb1
-rw-r--r--lib/tasks/lint.rake49
-rw-r--r--qa/qa/page/main/login.rb4
-rw-r--r--qa/qa/page/project/show.rb4
-rw-r--r--qa/qa/runtime/env.rb8
-rw-r--r--qa/qa/runtime/user.rb8
-rwxr-xr-xscripts/static-analysis11
-rw-r--r--spec/features/groups/members/search_members_spec.rb29
-rw-r--r--spec/features/groups/milestone_spec.rb151
-rw-r--r--spec/features/profiles/password_spec.rb76
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb58
-rw-r--r--spec/features/profiles/user_manages_applications_spec.rb39
-rw-r--r--spec/features/profiles/user_visits_profile_authentication_log_spec.rb25
-rw-r--r--spec/features/profiles/user_visits_profile_spec.rb52
-rw-r--r--spec/fixtures/api/schemas/variable.json3
-rw-r--r--spec/helpers/events_helper_spec.rb4
-rw-r--r--spec/javascripts/awards_handler_spec.js13
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js46
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js41
-rw-r--r--spec/javascripts/commits_spec.js20
-rw-r--r--spec/javascripts/importer_status_spec.js74
-rw-r--r--spec/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js (renamed from spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js)2
-rw-r--r--spec/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js (renamed from spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js)2
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js9
-rw-r--r--spec/javascripts/vue_shared/components/gl_modal_spec.js192
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb9
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb8
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb14
-rw-r--r--spec/lib/gitlab/git_access_spec.rb19
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb8
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb4
-rw-r--r--spec/lib/gitlab/profiler_spec.rb9
-rw-r--r--spec/models/identity_spec.rb33
-rw-r--r--spec/models/repository_spec.rb12
-rw-r--r--spec/models/user_spec.rb8
-rw-r--r--spec/requests/api/issues_spec.rb2
-rw-r--r--spec/requests/api/project_import_spec.rb102
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/requests/api/v3/issues_spec.rb2
-rw-r--r--spec/requests/api/v3/projects_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb10
-rw-r--r--spec/support/factory_bot.rb (renamed from spec/support/factory_girl.rb)0
-rw-r--r--spec/support/features/variable_list_shared_examples.rb2
-rw-r--r--spec/support/fixture_helpers.rb8
-rw-r--r--spec/validators/variable_duplicates_validator_spec.rb67
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml10
217 files changed, 2058 insertions, 1380 deletions
diff --git a/.eslintrc b/.eslintrc
index ad5eaebccae..8f9cdfb14ac 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -36,7 +36,7 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
- "no-underscore-dangle": ["error", { "allow": ["__"]}],
+ "no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
"vue/html-self-closing": ["error", {
"html": {
"void": "always",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ad603fdc75..c3bb93fbe3e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.4.4 (2018-02-16)
+
+### Security (1 change)
+
+- Update nokogiri to 1.8.2. !16807
+
+### Fixed (9 changes)
+
+- Fix 500 error when loading a merge request with an invalid comment. !16795
+- Cleanup new branch/merge request form in issues. !16854
+- Fix GitLab import leaving group_id on ProjectLabel. !16877
+- Fix forking projects when no restricted visibility levels are defined applicationwide. !16881
+- Resolve PrepareUntrackedUploads PostgreSQL syntax error. !17019
+- Fixed error 500 when removing an identity with synced attributes and visiting the profile page. !17054
+- Validate user namespace before saving so that errors persist on model.
+- LDAP Person no longer throws exception on invalid entry.
+- Fix JIRA not working when a trailing slash is included.
+
+
## 10.4.3 (2018-02-05)
### Security (4 changes)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ed56da0353d..dfe4bf65f9f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -174,7 +174,7 @@ Assigning a team label makes sure issues get the attention of the appropriate
people.
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
-~Geo, ~Gitaly, ~Platform, ~Monitoring, ~Release, and ~"UX".
+~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the
responsibility of each team.
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 87109a802e5..3283ce5ec36 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -50,10 +50,8 @@ class AwardsHandler {
this.registerEventListener('on', $('html'), 'click', (e) => {
const $target = $(e.target);
- if (!$target.closest('.emoji-menu-content').length) {
- $('.js-awards-block.current').removeClass('current');
- }
if (!$target.closest('.emoji-menu').length) {
+ $('.js-awards-block.current').removeClass('current');
if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active');
this.hideMenuElement($('.emoji-menu'));
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index 76f93e5c6bd..b33adff609f 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -75,6 +75,7 @@ export default class AjaxVariableList {
if (res.status === statusCodes.OK && res.data) {
this.updateRowsWithPersistedVariables(res.data.variables);
+ this.variableList.hideValues();
} else if (res.status === statusCodes.BAD_REQUEST) {
// Validation failed
this.errorBox.innerHTML = generateErrorBoxContent(res.data);
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 3467e88119b..745f3404295 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -178,6 +178,10 @@ export default class VariableList {
this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled);
}
+ hideValues() {
+ this.secretValues.updateDom(false);
+ }
+
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 4b2f75fffde..2be63bd8c76 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,52 +1,36 @@
-/* eslint-disable func-names, wrap-iife, consistent-return,
- no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars,
- prefer-template, object-shorthand, prefer-arrow-callback */
-
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
import axios from './lib/utils/axios_utils';
-export default (function () {
- const CommitsList = {};
-
- CommitsList.timer = null;
+export default class CommitsList {
+ constructor(limit = 0) {
+ this.timer = null;
- CommitsList.init = function (limit) {
this.$contentList = $('.content_list');
- $('body').on('click', '.day-commits-table li.commit', function (e) {
- if (e.target.nodeName !== 'A') {
- location.href = $(this).attr('url');
- e.stopPropagation();
- return false;
- }
- });
-
- Pager.init(parseInt(limit, 10), false, false, this.processCommits);
+ Pager.init(parseInt(limit, 10), false, false, this.processCommits.bind(this));
this.content = $('#commits-list');
this.searchField = $('#commits-search');
this.lastSearch = this.searchField.val();
- return this.initSearch();
- };
+ this.initSearch();
+ }
- CommitsList.initSearch = function () {
+ initSearch() {
this.timer = null;
- return this.searchField.keyup((function (_this) {
- return function () {
- clearTimeout(_this.timer);
- return _this.timer = setTimeout(_this.filterResults, 500);
- };
- })(this));
- };
+ this.searchField.on('keyup', () => {
+ clearTimeout(this.timer);
+ this.timer = setTimeout(this.filterResults.bind(this), 500);
+ });
+ }
- CommitsList.filterResults = function () {
+ filterResults() {
const form = $('.commits-search-form');
- const search = CommitsList.searchField.val();
- if (search === CommitsList.lastSearch) return Promise.resolve();
- const commitsUrl = form.attr('action') + '?' + form.serialize();
- CommitsList.content.fadeTo('fast', 0.5);
+ const search = this.searchField.val();
+ if (search === this.lastSearch) return Promise.resolve();
+ const commitsUrl = `${form.attr('action')}?${form.serialize()}`;
+ this.content.fadeTo('fast', 0.5);
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
[obj.name]: obj.value,
}), {});
@@ -55,9 +39,9 @@ export default (function () {
params,
})
.then(({ data }) => {
- CommitsList.lastSearch = search;
- CommitsList.content.html(data.html);
- CommitsList.content.fadeTo('fast', 1.0);
+ this.lastSearch = search;
+ this.content.html(data.html);
+ this.content.fadeTo('fast', 1.0);
// Change url so if user reload a page - search results are saved
history.replaceState({
@@ -65,16 +49,16 @@ export default (function () {
}, document.title, commitsUrl);
})
.catch(() => {
- CommitsList.content.fadeTo('fast', 1.0);
- CommitsList.lastSearch = null;
+ this.content.fadeTo('fast', 1.0);
+ this.lastSearch = null;
});
- };
+ }
// Prepare loaded data.
- CommitsList.processCommits = (data) => {
+ processCommits(data) {
let processedData = data;
const $processedData = $(processedData);
- const $commitsHeadersLast = CommitsList.$contentList.find('li.js-commit-header').last();
+ const $commitsHeadersLast = this.$contentList.find('li.js-commit-header').last();
const lastShownDay = $commitsHeadersLast.data('day');
const $loadedCommitsHeadersFirst = $processedData.filter('li.js-commit-header').first();
const loadedShownDayFirst = $loadedCommitsHeadersFirst.data('day');
@@ -97,7 +81,5 @@ export default (function () {
localTimeAgo($processedData.find('.js-timeago'));
return processedData;
- };
-
- return CommitsList;
-})();
+ }
+}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index f8082c74943..7e750d15d3d 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -43,169 +43,14 @@ var Dispatcher;
});
switch (page) {
- case 'projects:environments:metrics':
- import('./pages/projects/environments/metrics')
- .then(callDefault)
- .catch(fail);
- break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
case 'projects:issues:show':
- shortcut_handler = true;
- break;
- case 'projects:milestones:index':
- import('./pages/projects/milestones/index')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:milestones:show':
- import('./pages/projects/milestones/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:milestones:show':
- import('./pages/groups/milestones/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:milestones:show':
- import('./pages/dashboard/milestones/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:issues':
- import('./pages/dashboard/issues')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:merge_requests':
- import('./pages/dashboard/merge_requests')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:issues':
- import('./pages/groups/issues')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:merge_requests':
- import('./pages/groups/merge_requests')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:todos:index':
- import('./pages/dashboard/todos/index')
- .then(callDefault)
- .catch(fail);
- break;
- case 'admin:jobs:index':
- import('./pages/admin/jobs/index')
- .then(callDefault)
- .catch(fail);
- break;
- case 'admin:projects:index':
- import('./pages/admin/projects/index/index')
- .then(callDefault)
- .catch(fail);
- break;
- case 'admin:users:index':
- import('./pages/admin/users/shared')
- .then(callDefault)
- .catch(fail);
- break;
- case 'admin:users:show':
- import('./pages/admin/users/shared')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:projects:index':
- case 'dashboard:projects:starred':
- import('./pages/dashboard/projects')
- .then(callDefault)
- .catch(fail);
- break;
- case 'explore:projects:index':
- case 'explore:projects:trending':
- case 'explore:projects:starred':
- import('./pages/explore/projects')
- .then(callDefault)
- .catch(fail);
- break;
- case 'explore:groups:index':
- import('./pages/explore/groups')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:milestones:new':
- case 'projects:milestones:create':
- import('./pages/projects/milestones/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:milestones:edit':
- case 'projects:milestones:update':
- import('./pages/projects/milestones/edit')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:milestones:new':
- case 'groups:milestones:create':
- import('./pages/groups/milestones/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:milestones:edit':
- case 'groups:milestones:update':
- import('./pages/groups/milestones/edit')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:compare:show':
- import('./pages/projects/compare/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:branches:new':
- import('./pages/projects/branches/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:branches:create':
- import('./pages/projects/branches/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:branches:index':
- import('./pages/projects/branches/index')
- .then(callDefault)
- .catch(fail);
- break;
case 'projects:issues:new':
- import('./pages/projects/issues/new')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:issues:edit':
- import('./pages/projects/issues/edit')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:merge_requests:creations:new':
- import('./pages/projects/merge_requests/creations/new')
- .then(callDefault)
- .catch(fail);
case 'projects:merge_requests:creations:diffs':
- import('./pages/projects/merge_requests/creations/diffs')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:merge_requests:edit':
- import('./pages/projects/merge_requests/edit')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'projects:tags:new':
@@ -224,6 +69,11 @@ var Dispatcher;
.then(callDefault)
.catch(fail);
break;
+ case 'projects:services:edit':
+ import('./pages/projects/services/edit')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:snippets:edit':
case 'projects:snippets:update':
import('./pages/projects/snippets/edit')
@@ -462,11 +312,6 @@ var Dispatcher;
.then(callDefault)
.catch(fail);
break;
- case 'users:show':
- import('./pages/users/show')
- .then(callDefault)
- .catch(fail);
- break;
case 'admin:conversational_development_index:show':
import('./pages/admin/conversational_development_index/show')
.then(callDefault)
diff --git a/app/assets/javascripts/ide/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js
index af83a1ec0b4..142a220097b 100644
--- a/app/assets/javascripts/ide/monaco_loader.js
+++ b/app/assets/javascripts/ide/monaco_loader.js
@@ -6,6 +6,11 @@ monacoContext.require.config({
},
});
+// ignore CDN config and use local assets path for service worker which cannot be cross-domain
+const relativeRootPath = (gon && gon.relative_url_root) || '';
+const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`;
+window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` };
+
// eslint-disable-next-line no-underscore-dangle
window.__monaco_context__ = monacoContext;
export default monacoContext.require;
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 134a503864e..35094f8e73b 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -59,29 +59,36 @@ class ImporterStatus {
.catch(() => flash(__('An error occurred while importing project')));
}
- setAutoUpdate() {
- return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => {
- const jobItem = $(`#project_${job.id}`);
- const statusField = jobItem.find('.job-status');
+ autoUpdate() {
+ return axios.get(this.jobsUrl)
+ .then(({ data = [] }) => {
+ data.forEach((job) => {
+ const jobItem = $(`#project_${job.id}`);
+ const statusField = jobItem.find('.job-status');
+
+ const spinner = '<i class="fa fa-spinner fa-spin"></i>';
- const spinner = '<i class="fa fa-spinner fa-spin"></i>';
+ switch (job.import_status) {
+ case 'finished':
+ jobItem.removeClass('active').addClass('success');
+ statusField.html('<span><i class="fa fa-check"></i> done</span>');
+ break;
+ case 'scheduled':
+ statusField.html(`${spinner} scheduled`);
+ break;
+ case 'started':
+ statusField.html(`${spinner} started`);
+ break;
+ default:
+ statusField.html(job.import_status);
+ break;
+ }
+ });
+ });
+ }
- switch (job.import_status) {
- case 'finished':
- jobItem.removeClass('active').addClass('success');
- statusField.html('<span><i class="fa fa-check"></i> done</span>');
- break;
- case 'scheduled':
- statusField.html(`${spinner} scheduled`);
- break;
- case 'started':
- statusField.html(`${spinner} started`);
- break;
- default:
- statusField.html(job.import_status);
- break;
- }
- })), 4000);
+ setAutoUpdate() {
+ setInterval(this.autoUpdate.bind(this), 4000);
}
}
diff --git a/app/assets/javascripts/integrations/index.js b/app/assets/javascripts/integrations/index.js
deleted file mode 100644
index 10fe6bac0e8..00000000000
--- a/app/assets/javascripts/integrations/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-disable no-new */
-import IntegrationSettingsForm from './integration_settings_form';
-
-$(() => {
- const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
- integrationSettingsForm.init();
-});
diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
index 555725cbe12..ba1d8e4d8db 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
+++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
@@ -1,13 +1,13 @@
<script>
import axios from '~/lib/utils/axios_utils';
- import Flash from '~/flash';
- import modal from '~/vue_shared/components/modal.vue';
- import { s__ } from '~/locale';
+ import createFlash from '~/flash';
+ import GlModal from '~/vue_shared/components/gl_modal.vue';
import { redirectTo } from '~/lib/utils/url_utility';
+ import { s__ } from '~/locale';
export default {
components: {
- modal,
+ GlModal,
},
props: {
url: {
@@ -17,7 +17,7 @@
},
computed: {
text() {
- return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.');
+ return s__('AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.');
},
},
methods: {
@@ -28,7 +28,7 @@
redirectTo(response.request.responseURL);
})
.catch((error) => {
- Flash(s__('AdminArea|Stopping jobs failed'));
+ createFlash(s__('AdminArea|Stopping jobs failed'));
throw error;
});
},
@@ -37,11 +37,13 @@
</script>
<template>
- <modal
+ <gl-modal
id="stop-jobs-modal"
- :title="s__('AdminArea|Stop all jobs?')"
- :text="text"
- kind="danger"
- :primary-button-label="s__('AdminArea|Stop jobs')"
- @submit="onSubmit" />
+ :header-title-text="s__('AdminArea|Stop all jobs?')"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('AdminArea|Stop jobs')"
+ @submit="onSubmit"
+ >
+ {{ text }}
+ </gl-modal>
</template>
diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js
index 0e004bd9174..5a4f8c6e745 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/index.js
+++ b/app/assets/javascripts/pages/admin/jobs/index/index.js
@@ -1,29 +1,28 @@
import Vue from 'vue';
-
import Translate from '~/vue_shared/translate';
-
import stopJobsModal from './components/stop_jobs_modal.vue';
Vue.use(Translate);
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
const stopJobsButton = document.getElementById('stop-jobs-button');
-
- // eslint-disable-next-line no-new
- new Vue({
- el: '#stop-jobs-modal',
- components: {
- stopJobsModal,
- },
- mounted() {
- stopJobsButton.classList.remove('disabled');
- },
- render(createElement) {
- return createElement('stop-jobs-modal', {
- props: {
- url: stopJobsButton.dataset.url,
- },
- });
- },
- });
-};
+ if (stopJobsButton) {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#stop-jobs-modal',
+ components: {
+ stopJobsModal,
+ },
+ mounted() {
+ stopJobsButton.classList.remove('disabled');
+ },
+ render(createElement) {
+ return createElement('stop-jobs-modal', {
+ props: {
+ url: stopJobsButton.dataset.url,
+ },
+ });
+ },
+ });
+ }
+});
diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js
index a87b27090a8..3c597a1093e 100644
--- a/app/assets/javascripts/pages/admin/projects/index/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index/index.js
@@ -5,7 +5,7 @@ import csrf from '~/lib/utils/csrf';
import deleteProjectModal from './components/delete_project_modal.vue';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
Vue.use(Translate);
const deleteProjectModalEl = document.getElementById('delete-project-modal');
@@ -34,4 +34,4 @@ export default () => {
deleteModal.projectName = buttonProps.projectName;
}
});
-};
+});
diff --git a/app/assets/javascripts/pages/admin/users/shared/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
index 7b5e333011e..7b5e333011e 100644
--- a/app/assets/javascripts/pages/admin/users/shared/components/delete_user_modal.vue
+++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
diff --git a/app/assets/javascripts/pages/admin/users/shared/index.js b/app/assets/javascripts/pages/admin/users/index.js
index d2a0f82fa2b..4f5d6b55031 100644
--- a/app/assets/javascripts/pages/admin/users/shared/index.js
+++ b/app/assets/javascripts/pages/admin/users/index.js
@@ -5,7 +5,7 @@ import csrf from '~/lib/utils/csrf';
import deleteUserModal from './components/delete_user_modal.vue';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
Vue.use(Translate);
const deleteUserModalEl = document.getElementById('delete-user-modal');
@@ -40,4 +40,4 @@ export default () => {
deleteModal.username = buttonProps.username;
}
});
-};
+});
diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js
index b7353669e65..c4901dd1cb6 100644
--- a/app/assets/javascripts/pages/dashboard/issues/index.js
+++ b/app/assets/javascripts/pages/dashboard/issues/index.js
@@ -1,7 +1,7 @@
import projectSelect from '~/project_select';
import initLegacyFilters from '~/init_legacy_filters';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
projectSelect();
initLegacyFilters();
-};
+});
diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
index b7353669e65..c4901dd1cb6 100644
--- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js
+++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
@@ -1,7 +1,7 @@
import projectSelect from '~/project_select';
import initLegacyFilters from '~/init_legacy_filters';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
projectSelect();
initLegacyFilters();
-};
+});
diff --git a/app/assets/javascripts/pages/dashboard/milestones/show/index.js b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
index 2e7a08a369c..06195d73c0a 100644
--- a/app/assets/javascripts/pages/dashboard/milestones/show/index.js
+++ b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
@@ -1,7 +1,7 @@
import Milestone from '~/milestone';
import Sidebar from '~/right_sidebar';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new
-};
+});
diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js
index c88cbf1a6ba..0c585e162cb 100644
--- a/app/assets/javascripts/pages/dashboard/projects/index.js
+++ b/app/assets/javascripts/pages/dashboard/projects/index.js
@@ -1,3 +1,3 @@
import ProjectsList from '~/projects_list';
-export default () => new ProjectsList();
+document.addEventListener('DOMContentLoaded', () => new ProjectsList());
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/index.js b/app/assets/javascripts/pages/dashboard/todos/index/index.js
index 77c23685943..9d2c2f2994f 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/index.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/index.js
@@ -1,3 +1,3 @@
import Todos from './todos';
-export default () => new Todos();
+document.addEventListener('DOMContentLoaded', () => new Todos());
diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js
index e59c38b8bc4..3c7edbdd7c7 100644
--- a/app/assets/javascripts/pages/explore/groups/index.js
+++ b/app/assets/javascripts/pages/explore/groups/index.js
@@ -2,7 +2,7 @@ import GroupsList from '~/groups_list';
import Landing from '~/landing';
import initGroupsList from '../../../groups';
-export default function () {
+document.addEventListener('DOMContentLoaded', () => {
new GroupsList(); // eslint-disable-line no-new
initGroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
@@ -13,4 +13,4 @@ export default function () {
'explore_groups_landing_dismissed',
);
exploreGroupsLanding.toggle();
-}
+});
diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js
index c88cbf1a6ba..0c585e162cb 100644
--- a/app/assets/javascripts/pages/explore/projects/index.js
+++ b/app/assets/javascripts/pages/explore/projects/index.js
@@ -1,3 +1,3 @@
import ProjectsList from '~/projects_list';
-export default () => new ProjectsList();
+document.addEventListener('DOMContentLoaded', () => new ProjectsList());
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index fbdfabd1e95..d149b307e7f 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -2,9 +2,9 @@ import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
});
projectSelect();
-};
+});
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index f6d284bf9ef..a5cc1f34b63 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -2,9 +2,9 @@ import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
});
projectSelect();
-};
+});
diff --git a/app/assets/javascripts/pages/groups/milestones/edit/index.js b/app/assets/javascripts/pages/groups/milestones/edit/index.js
index 5c99c90e24d..ddd10fe5062 100644
--- a/app/assets/javascripts/pages/groups/milestones/edit/index.js
+++ b/app/assets/javascripts/pages/groups/milestones/edit/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm(false);
+document.addEventListener('DOMContentLoaded', () => initForm(false));
diff --git a/app/assets/javascripts/pages/groups/milestones/new/index.js b/app/assets/javascripts/pages/groups/milestones/new/index.js
index 5c99c90e24d..ddd10fe5062 100644
--- a/app/assets/javascripts/pages/groups/milestones/new/index.js
+++ b/app/assets/javascripts/pages/groups/milestones/new/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm(false);
+document.addEventListener('DOMContentLoaded', () => initForm(false));
diff --git a/app/assets/javascripts/pages/groups/milestones/show/index.js b/app/assets/javascripts/pages/groups/milestones/show/index.js
index c9a18353f2e..88f40b5278e 100644
--- a/app/assets/javascripts/pages/groups/milestones/show/index.js
+++ b/app/assets/javascripts/pages/groups/milestones/show/index.js
@@ -1,3 +1,3 @@
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
-export default initMilestonesShow;
+document.addEventListener('DOMContentLoaded', initMilestonesShow);
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index cee0f19bf2a..8fa266a37ce 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -1,7 +1,7 @@
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
import DeleteModal from '~/branches/branches_delete_modal';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
AjaxLoadingSpinner.init();
new DeleteModal(); // eslint-disable-line no-new
-};
+});
diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js
index ae5e033e97e..d32d5c6cb29 100644
--- a/app/assets/javascripts/pages/projects/branches/new/index.js
+++ b/app/assets/javascripts/pages/projects/branches/new/index.js
@@ -1,3 +1,5 @@
import NewBranchForm from '~/new_branch_form';
-export default () => new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
+document.addEventListener('DOMContentLoaded', () => (
+ new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML))
+));
diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js
index 90b5882a24f..6110fda17de 100644
--- a/app/assets/javascripts/pages/projects/commits/show/index.js
+++ b/app/assets/javascripts/pages/projects/commits/show/index.js
@@ -3,7 +3,7 @@ import GpgBadges from '~/gpg_badges';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default () => {
- CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
+ new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
GpgBadges.fetch();
};
diff --git a/app/assets/javascripts/pages/projects/compare/show/index.js b/app/assets/javascripts/pages/projects/compare/show/index.js
index 6b8d4503568..2b4fd3c47c0 100644
--- a/app/assets/javascripts/pages/projects/compare/show/index.js
+++ b/app/assets/javascripts/pages/projects/compare/show/index.js
@@ -1,8 +1,8 @@
import Diff from '~/diff';
import initChangesDropdown from '~/init_changes_dropdown';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Diff(); // eslint-disable-line no-new
const paddingTop = 16;
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
-};
+});
diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js
index f4760cb2720..0b644780ad4 100644
--- a/app/assets/javascripts/pages/projects/environments/metrics/index.js
+++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js
@@ -1,3 +1,3 @@
import monitoringBundle from '~/monitoring/monitoring_bundle';
-export default monitoringBundle;
+document.addEventListener('DOMContentLoaded', monitoringBundle);
diff --git a/app/assets/javascripts/pages/projects/issues/edit/index.js b/app/assets/javascripts/pages/projects/issues/edit/index.js
index 7f27f379d8c..ffc84dc106b 100644
--- a/app/assets/javascripts/pages/projects/issues/edit/index.js
+++ b/app/assets/javascripts/pages/projects/issues/edit/index.js
@@ -1,5 +1,3 @@
import initForm from '../form';
-export default () => {
- initForm();
-};
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/issues/new/index.js b/app/assets/javascripts/pages/projects/issues/new/index.js
index 7f27f379d8c..ffc84dc106b 100644
--- a/app/assets/javascripts/pages/projects/issues/new/index.js
+++ b/app/assets/javascripts/pages/projects/issues/new/index.js
@@ -1,5 +1,3 @@
import initForm from '../form';
-export default () => {
- initForm();
-};
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/index.js
index 734d01ae6f2..febfecebbd2 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/creations/index.js
@@ -1,3 +1,3 @@
import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
-export default initMergeRequest;
+document.addEventListener('DOMContentLoaded', initMergeRequest);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
index ccd0b54c5ed..1d5aec4001d 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
@@ -1,7 +1,7 @@
import Compare from '~/compare';
import MergeRequest from '~/merge_request';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
if (mrNewCompareNode) {
new Compare({ // eslint-disable-line no-new
@@ -15,4 +15,4 @@ export default () => {
action: mrNewSubmitNode.dataset.mrSubmitAction,
});
}
-};
+});
diff --git a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
index 734d01ae6f2..febfecebbd2 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
@@ -1,3 +1,3 @@
import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
-export default initMergeRequest;
+document.addEventListener('DOMContentLoaded', initMergeRequest);
diff --git a/app/assets/javascripts/pages/projects/milestones/edit/index.js b/app/assets/javascripts/pages/projects/milestones/edit/index.js
index 10e3979a36e..9a4ebf9890d 100644
--- a/app/assets/javascripts/pages/projects/milestones/edit/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/edit/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm();
+document.addEventListener('DOMContentLoaded', () => initForm());
diff --git a/app/assets/javascripts/pages/projects/milestones/index/index.js b/app/assets/javascripts/pages/projects/milestones/index/index.js
index 8fb4d83d8a3..38789365a67 100644
--- a/app/assets/javascripts/pages/projects/milestones/index/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/index/index.js
@@ -1,3 +1,3 @@
import milestones from '~/pages/milestones/shared';
-export default milestones;
+document.addEventListener('DOMContentLoaded', milestones);
diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js
index 10e3979a36e..9a4ebf9890d 100644
--- a/app/assets/javascripts/pages/projects/milestones/new/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/new/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm();
+document.addEventListener('DOMContentLoaded', () => initForm());
diff --git a/app/assets/javascripts/pages/projects/milestones/show/index.js b/app/assets/javascripts/pages/projects/milestones/show/index.js
index 35b5c9c2ced..84a52421598 100644
--- a/app/assets/javascripts/pages/projects/milestones/show/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/show/index.js
@@ -1,7 +1,7 @@
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
import milestones from '~/pages/milestones/shared';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initMilestonesShow();
milestones();
-};
+});
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
index a6c945e22b0..544360dcd51 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import PipelineSchedulesCallout from './components/pipeline_schedules_callout.vue';
+import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipeline-schedules-callout',
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index 2d18fa2044b..2d18fa2044b 100644
--- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
index aa04a0ac47a..77508e62cef 100644
--- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
@@ -1,7 +1,7 @@
<script>
import Vue from 'vue';
import Cookies from 'js-cookie';
- import Translate from '../../vue_shared/translate';
+ import Translate from '../../../../../vue_shared/translate';
import illustrationSvg from '../icons/intro_illustration.svg';
Vue.use(Translate);
diff --git a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
index 0c3926d76b5..0c3926d76b5 100644
--- a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
diff --git a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
index 95ed9c7dc21..95ed9c7dc21 100644
--- a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
diff --git a/app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg
index 26d1ff97b3e..26d1ff97b3e 100644
--- a/app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index 0b1a81bae13..cfd30d6053f 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
-import Translate from '../vue_shared/translate';
-import GlFieldErrors from '../gl_field_errors';
+import Translate from '../../../../vue_shared/translate';
+import GlFieldErrors from '../../../../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
-import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list';
+import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list';
Vue.use(Translate);
@@ -27,7 +27,7 @@ function initIntervalPatternInput() {
});
}
-document.addEventListener('DOMContentLoaded', () => {
+export default () => {
/* Most of the form is written in haml, but for fields with more complex behaviors,
* you should mount individual Vue components here. If at some point components need
* to share state, it may make sense to refactor the whole form to Vue */
@@ -46,4 +46,4 @@ document.addEventListener('DOMContentLoaded', () => {
container: $('.js-ci-variable-list-section'),
formField: 'schedule',
});
-});
+};
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
new file mode 100644
index 00000000000..c1dafda0e24
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
@@ -0,0 +1,56 @@
+import Chart from 'vendor/Chart';
+
+const options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false,
+};
+
+const buildChart = (chartScope) => {
+ const data = {
+ labels: chartScope.labels,
+ datasets: [{
+ fillColor: '#707070',
+ strokeColor: '#707070',
+ pointColor: '#707070',
+ pointStrokeColor: '#EEE',
+ data: chartScope.totalValues,
+ },
+ {
+ fillColor: '#1aaa55',
+ strokeColor: '#1aaa55',
+ pointColor: '#1aaa55',
+ pointStrokeColor: '#fff',
+ data: chartScope.successValues,
+ },
+ ],
+ };
+ const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
+
+ new Chart(ctx).Line(data, options);
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ const chartTimesData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
+ const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
+ const data = {
+ labels: chartTimesData.labels,
+ datasets: [{
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: chartTimesData.values,
+ }],
+ };
+
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8;
+ }
+
+ new Chart($('#build_timesChart').get(0).getContext('2d')).Bar(data, options);
+
+ chartsData.forEach(scope => buildChart(scope));
+});
diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js
new file mode 100644
index 00000000000..5c73171e62e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/services/edit/index.js
@@ -0,0 +1,13 @@
+import IntegrationSettingsForm from '~/integrations/integration_settings_form';
+import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
+
+export default () => {
+ const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring');
+ const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ integrationSettingsForm.init();
+
+ if (prometheusSettingsWrapper) {
+ const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+ prometheusMetrics.loadActiveMetrics();
+ }
+};
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 57306322aa4..57306322aa4 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/pages/users/index.js
index 9fd8452a2b6..899dcd42e37 100644
--- a/app/assets/javascripts/users/index.js
+++ b/app/assets/javascripts/pages/users/index.js
@@ -1,3 +1,4 @@
+import UserCallout from '~/user_callout';
import Cookies from 'js-cookie';
import UserTabs from './user_tabs';
@@ -22,4 +23,5 @@ document.addEventListener('DOMContentLoaded', () => {
const page = $('body').attr('data-page');
const action = page.split(':')[1];
initUserProfile(action);
+ new UserCallout(); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js
deleted file mode 100644
index f18f98b4e9a..00000000000
--- a/app/assets/javascripts/pages/users/show/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import UserCallout from '~/user_callout';
-
-export default () => new UserCallout();
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index e13b9839a20..c1217623467 100644
--- a/app/assets/javascripts/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -1,9 +1,9 @@
-import axios from '../lib/utils/axios_utils';
-import Activities from '../activities';
+import axios from '~/lib/utils/axios_utils';
+import Activities from '~/activities';
+import { localTimeAgo } from '~/lib/utils/datetime_utility';
+import { __ } from '~/locale';
+import flash from '~/flash';
import ActivityCalendar from './activity_calendar';
-import { localTimeAgo } from '../lib/utils/datetime_utility';
-import { __ } from '../locale';
-import flash from '../flash';
/**
* UserTabs
diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js
deleted file mode 100644
index 821aa7e229f..00000000000
--- a/app/assets/javascripts/pipelines/pipelines_charts.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import Chart from 'vendor/Chart';
-
-document.addEventListener('DOMContentLoaded', () => {
- const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
- const buildChart = (chartScope) => {
- const data = {
- labels: chartScope.labels,
- datasets: [{
- fillColor: '#707070',
- strokeColor: '#707070',
- pointColor: '#707070',
- pointStrokeColor: '#EEE',
- data: chartScope.totalValues,
- },
- {
- fillColor: '#1aaa55',
- strokeColor: '#1aaa55',
- pointColor: '#1aaa55',
- pointStrokeColor: '#fff',
- data: chartScope.successValues,
- },
- ],
- };
- const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
- const options = {
- scaleOverlay: true,
- responsive: true,
- maintainAspectRatio: false,
- };
- if (window.innerWidth < 768) {
- // Scale fonts if window width lower than 768px (iPad portrait)
- options.scaleFontSize = 8;
- }
- new Chart(ctx).Line(data, options);
- };
-
- chartData.forEach(scope => buildChart(scope));
-});
diff --git a/app/assets/javascripts/pipelines/pipelines_times.js b/app/assets/javascripts/pipelines/pipelines_times.js
deleted file mode 100644
index b5e7a0e53d9..00000000000
--- a/app/assets/javascripts/pipelines/pipelines_times.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import Chart from 'vendor/Chart';
-
-document.addEventListener('DOMContentLoaded', () => {
- const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
- const data = {
- labels: chartData.labels,
- datasets: [{
- fillColor: 'rgba(220,220,220,0.5)',
- strokeColor: 'rgba(220,220,220,1)',
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data: chartData.values,
- }],
- };
- const ctx = $('#build_timesChart').get(0).getContext('2d');
- const options = {
- scaleOverlay: true,
- responsive: true,
- maintainAspectRatio: false,
- };
- if (window.innerWidth < 768) {
- // Scale fonts if window width lower than 768px (iPad portrait)
- options.scaleFontSize = 8;
- }
- new Chart(ctx).Bar(data, options);
-});
diff --git a/app/assets/javascripts/prometheus_metrics/index.js b/app/assets/javascripts/prometheus_metrics/index.js
deleted file mode 100644
index a0c43c5abe1..00000000000
--- a/app/assets/javascripts/prometheus_metrics/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import PrometheusMetrics from './prometheus_metrics';
-
-$(() => {
- const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
- prometheusMetrics.loadActiveMetrics();
-});
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index 40c3cb500bb..ebaf2b972eb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -44,7 +44,10 @@
type="button"
class="btn btn-xs btn-default"
>
- <loading-icon v-if="isRefreshing" />
+ <loading-icon
+ v-if="isRefreshing"
+ :inline="true"
+ />
{{ s__("mrWidget|Refresh") }}
</button>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index edf67fcd0a7..d8f0442ef9d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -152,6 +152,7 @@ export default {
},
handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return;
+ if (!data.pipeline) return;
const label = data.pipeline.details.status.label;
const title = `Pipeline ${label}`;
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
new file mode 100644
index 00000000000..67c9181c7b1
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -0,0 +1,106 @@
+<script>
+ const buttonVariants = [
+ 'danger',
+ 'primary',
+ 'success',
+ 'warning',
+ ];
+
+ export default {
+ name: 'GlModal',
+
+ props: {
+ id: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ headerTitleText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ footerPrimaryButtonVariant: {
+ type: String,
+ required: false,
+ default: 'primary',
+ validator: value => buttonVariants.indexOf(value) !== -1,
+ },
+ footerPrimaryButtonText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ methods: {
+ emitCancel(event) {
+ this.$emit('cancel', event);
+ },
+ emitSubmit(event) {
+ this.$emit('submit', event);
+ },
+ },
+ };
+</script>
+
+<template>
+ <div
+ :id="id"
+ class="modal fade"
+ tabindex="-1"
+ role="dialog"
+ >
+ <div
+ class="modal-dialog"
+ role="document"
+ >
+ <div class="modal-content">
+ <div class="modal-header">
+ <slot name="header">
+ <button
+ type="button"
+ class="close"
+ data-dismiss="modal"
+ :aria-label="s__('Modal|Close')"
+ @click="emitCancel($event)"
+ >
+ <span aria-hidden="true">&times;</span>
+ </button>
+ <h4 class="modal-title">
+ <slot name="title">
+ {{ headerTitleText }}
+ </slot>
+ </h4>
+ </slot>
+ </div>
+
+ <div class="modal-body">
+ <slot></slot>
+ </div>
+
+ <div class="modal-footer">
+ <slot name="footer">
+ <button
+ type="button"
+ class="btn"
+ data-dismiss="modal"
+ @click="emitCancel($event)"
+ >
+ {{ s__('Modal|Cancel') }}
+ </button>
+ <button
+ type="button"
+ class="btn"
+ :class="`btn-${footerPrimaryButtonVariant}`"
+ data-dismiss="modal"
+ @click="emitSubmit($event)"
+ >
+ {{ footerPrimaryButtonText }}
+ </button>
+ </slot>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index dcd98cb522f..7e829826eba 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -255,8 +255,6 @@ ul.controls {
}
.author_link {
- display: inline-block;
-
.avatar-inline {
margin-left: 0;
margin-right: 0;
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 9d071f2d59a..8bcced70d63 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -7,17 +7,24 @@ module WebpackHelper
def webpack_controller_bundle_tags
bundles = []
- segments = [*controller.controller_path.split('/'), controller.action_name].compact
- until segments.empty?
+ action = case controller.action_name
+ when 'create' then 'new'
+ when 'update' then 'edit'
+ else controller.action_name
+ end
+
+ route = [*controller.controller_path.split('/'), action].compact
+
+ until route.empty?
begin
- asset_paths = gitlab_webpack_asset_paths("pages.#{segments.join('.')}", extension: 'js')
+ asset_paths = gitlab_webpack_asset_paths("pages.#{route.join('.')}", extension: 'js')
bundles.unshift(*asset_paths)
rescue Webpack::Rails::Manifest::EntryPointMissingError
# no bundle exists for this path
end
- segments.pop
+ route.pop
end
javascript_include_tag(*bundles)
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 490edf4ac57..ee987949080 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -41,41 +41,12 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
-
- # This convoluted mess is because we need to handle two cases of
- # artifact files during the migration. And a simple OR clause
- # makes it impossible to optimize.
-
- # Instead we want to use UNION ALL and do two carefully
- # constructed disjoint queries. But Rails cannot handle UNION or
- # UNION ALL queries so we do the query in a subquery and wrap it
- # in an otherwise redundant WHERE IN query (IN is fine for
- # non-null columns).
-
- # This should all be ripped out when the migration is finished and
- # replaced with just the new storage to avoid the extra work.
-
scope :with_artifacts, ->() do
- old = Ci::Build.select(:id).where(%q[artifacts_file <> ''])
- new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)],
- Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id'))
- where('ci_builds.id IN (? UNION ALL ?)', old, new)
+ where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
+ '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id'))
end
-
- scope :with_artifacts_not_expired, ->() do
- old = Ci::Build.select(:id).where(%q[artifacts_file <> '' AND (artifacts_expire_at IS NULL OR artifacts_expire_at > ?)], Time.now)
- new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)],
- Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id AND (expire_at IS NULL OR expire_at > ?)', Time.now))
- where('ci_builds.id IN (? UNION ALL ?)', old, new)
- end
-
- scope :with_expired_artifacts, ->() do
- old = Ci::Build.select(:id).where(%q[artifacts_file <> '' AND artifacts_expire_at < ?], Time.now)
- new = Ci::Build.select(:id).where(%q[(artifacts_file IS NULL OR artifacts_file = '') AND EXISTS (?)],
- Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id AND expire_at < ?', Time.now))
- where('ci_builds.id IN (? UNION ALL ?)', old, new)
- end
-
+ scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
+ scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
scope :ref_protected, -> { where(protected: true) }
diff --git a/app/models/identity.rb b/app/models/identity.rb
index b3fa7d8176a..2b433e9b988 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -9,6 +9,7 @@ class Identity < ActiveRecord::Base
validates :user_id, uniqueness: { scope: :provider }
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
+ after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
@@ -34,4 +35,12 @@ class Identity < ActiveRecord::Base
self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid)
end
+
+ def user_synced_attributes_metadata_from_provider?
+ user.user_synced_attributes_metadata&.provider == provider
+ end
+
+ def clear_user_synced_attributes
+ user.user_synced_attributes_metadata&.destroy
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 3893b1818f3..2ba6a863500 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -261,7 +261,7 @@ class Project < ActiveRecord::Base
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
- validates :variables, variable_duplicates: true
+ validates :variables, variable_duplicates: { scope: :environment_scope }
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 1cf55fd4332..299a3f32a85 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -492,12 +492,8 @@ class Repository
end
def root_ref
- if raw_repository
- raw_repository.root_ref
- else
- # When the repo does not exist we raise this error so no data is cached.
- raise Gitlab::Git::Repository::NoRepository
- end
+ # When the repo does not exist, or there is no root ref, we raise this error so no data is cached.
+ raw_repository&.root_ref or raise Gitlab::Git::Repository::NoRepository # rubocop:disable Style/AndOr
end
cache_method :root_ref
@@ -593,7 +589,15 @@ class Repository
def license_key
return unless exists?
- Licensee.license(path).try(:key)
+ # The licensee gem creates a Rugged object from the path:
+ # https://github.com/benbalter/licensee/blob/v8.7.0/lib/licensee/projects/git_project.rb
+ begin
+ Licensee.license(path).try(:key)
+ # Normally we would rescue Rugged::Error, but that is banned by lint-rugged
+ # and we need to migrate this endpoint to Gitaly:
+ # https://gitlab.com/gitlab-org/gitaly/issues/1026
+ rescue
+ end
end
cache_method :license_key
diff --git a/app/models/user.rb b/app/models/user.rb
index 4097fe2b5dc..5e84d2da805 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -249,7 +249,7 @@ class User < ActiveRecord::Base
def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
- where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
+ where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
else
find_by(conditions)
end
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 2f511ab44b7..299b9c6215f 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -19,19 +19,10 @@ module Issues
# on rewriting notes (unfolding references)
#
ActiveRecord::Base.transaction do
- # New issue tasks
- #
@new_issue = create_new_issue
- rewrite_notes
- rewrite_issue_award_emoji
- add_note_moved_from
-
- # Old issue tasks
- #
- add_note_moved_to
- close_issue
- mark_as_moved
+ update_new_issue
+ update_old_issue
end
notify_participants
@@ -41,6 +32,18 @@ module Issues
private
+ def update_new_issue
+ rewrite_notes
+ rewrite_issue_award_emoji
+ add_note_moved_from
+ end
+
+ def update_old_issue
+ add_note_moved_to
+ close_issue
+ mark_as_moved
+ end
+
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids,
milestone_id: cloneable_milestone_id,
diff --git a/app/validators/variable_duplicates_validator.rb b/app/validators/variable_duplicates_validator.rb
index 8a9d8892e9b..4bfa3c45303 100644
--- a/app/validators/variable_duplicates_validator.rb
+++ b/app/validators/variable_duplicates_validator.rb
@@ -1,13 +1,28 @@
# VariableDuplicatesValidator
#
-# This validtor is designed for especially the following condition
+# This validator is designed for especially the following condition
# - Use `accepts_nested_attributes_for :xxx` in a parent model
# - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model
class VariableDuplicatesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- duplicates = value.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
+ if options[:scope]
+ scoped = value.group_by do |variable|
+ Array(options[:scope]).map { |attr| variable.send(attr) } # rubocop:disable GitlabSecurity/PublicSend
+ end
+ scoped.each_value { |scope| validate_duplicates(record, attribute, scope) }
+ else
+ validate_duplicates(record, attribute, value)
+ end
+ end
+
+ private
+
+ def validate_duplicates(record, attribute, values)
+ duplicates = values.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
if duplicates.any?
- record.errors.add(attribute, "Duplicate variables: #{duplicates.join(", ")}")
+ error_message = "have duplicate values (#{duplicates.join(", ")})"
+ error_message += " for #{values.first.send(options[:scope])} scope" if options[:scope] # rubocop:disable GitlabSecurity/PublicSend
+ record.errors.add(attribute, error_message)
end
end
end
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index a01676d82a8..4e3e2f7a475 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -7,10 +7,9 @@
- build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
= render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
- .nav-controls
- - if @all_builds.running_or_pending.any?
- #stop-jobs-modal
-
+ - if @all_builds.running_or_pending.any?
+ #stop-jobs-modal
+ .nav-controls
%button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal',
target: '#stop-jobs-modal',
url: cancel_all_admin_jobs_path } }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index e16d132f869..0931ceb1512 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -58,7 +58,7 @@
- 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-danger btn-inverted"
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit 'Save changes', class: "btn btn-success js-btn-save-general-project-settings"
%section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index ff440e99042..160e325996a 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -1,7 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'common_vue'
- = webpack_bundle_tag 'schedule_form'
-
= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f|
= form_errors(@schedule)
.form-group
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index 4fbdd1dd5e4..bcb6dddba1a 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -1,9 +1,5 @@
- breadcrumb_title _("Schedules")
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'common_vue'
- = webpack_bundle_tag 'schedules_index'
-
- @no_container = true
- page_title _("Pipeline Schedules")
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index ba55bc23add..a86cb14960a 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,9 +1,6 @@
- @no_container = true
- breadcrumb_title "CI / CD Charts"
- page_title _("Charts"), _("Pipelines")
-- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('common_d3')
- = page_specific_javascript_bundle_tag('graphs')
%div{ class: container_class }
.sub-header-block
diff --git a/app/views/projects/pipelines/charts/_pipeline_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml
index a5dbd1b1532..510697c2ae9 100644
--- a/app/views/projects/pipelines/charts/_pipeline_times.haml
+++ b/app/views/projects/pipelines/charts/_pipeline_times.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('pipelines_times')
-
%div
%p.light
= _("Commit duration in minutes for last 30 commits")
diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml
index 41dc2f6cf9d..2f4b6def155 100644
--- a/app/views/projects/pipelines/charts/_pipelines.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('pipelines_charts')
-
%h4= _("Pipelines charts")
%p
&nbsp;
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 0808b28a9df..17e804d682b 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('integrations')
-
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index b0cb5ce5e8f..5f38ecd6820 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('prometheus_metrics')
-
.row.prepend-top-default.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
.col-lg-3
%h4.prepend-top-0
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 2c05ebe52ae..1a353953838 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
- $('.edit-project .btn-save').enable();
+ $('.edit-project .js-btn-save-general-project-settings').enable();
diff --git a/app/views/shared/groups/_dropdown.html.haml b/app/views/shared/groups/_dropdown.html.haml
index 1a259b679c7..8607be9cd06 100644
--- a/app/views/shared/groups/_dropdown.html.haml
+++ b/app/views/shared/groups/_dropdown.html.haml
@@ -23,11 +23,11 @@
- if show_archive_options
%li.divider
%li.js-filter-archived-projects
- = link_to group_children_path(@group, archived: nil), class: ("is-active" unless params[:archived].present?) do
+ = link_to filter_groups_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
Hide archived projects
%li.js-filter-archived-projects
- = link_to group_children_path(@group, archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
+ = link_to filter_groups_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
Show archived projects
%li.js-filter-archived-projects
- = link_to group_children_path(@group, archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
+ = link_to filter_groups_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
Show archived projects only
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index c9a77d668a2..a396d1007a7 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -4,9 +4,6 @@
- page_description @user.bio
- header_title @user.name, user_path(@user)
- @no_container = true
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'common_d3'
- = webpack_bundle_tag 'users'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
diff --git a/changelogs/unreleased/17500-mr-multiple-issues-oxford-comma.yml b/changelogs/unreleased/17500-mr-multiple-issues-oxford-comma.yml
new file mode 100644
index 00000000000..a94e6153a05
--- /dev/null
+++ b/changelogs/unreleased/17500-mr-multiple-issues-oxford-comma.yml
@@ -0,0 +1,5 @@
+---
+title: Update issue closing pattern to allow variations in punctuation
+merge_request: 17198
+author: Vicky Chijwani
+type: changed
diff --git a/changelogs/unreleased/34130-null-pipes.yml b/changelogs/unreleased/34130-null-pipes.yml
new file mode 100644
index 00000000000..a56e5cf8db2
--- /dev/null
+++ b/changelogs/unreleased/34130-null-pipes.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent MR Widget error when no CI configured
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/35530-teleporting-emoji.yml b/changelogs/unreleased/35530-teleporting-emoji.yml
new file mode 100644
index 00000000000..a60a42b9e48
--- /dev/null
+++ b/changelogs/unreleased/35530-teleporting-emoji.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Teleporting Emoji
+merge_request: 16963
+author: Jared Deckard <jared.deckard@gmail.com>
+type: fixed
diff --git a/changelogs/unreleased/39607-fix-avatar--vertical-align.yml b/changelogs/unreleased/39607-fix-avatar--vertical-align.yml
new file mode 100644
index 00000000000..4d9fee12f04
--- /dev/null
+++ b/changelogs/unreleased/39607-fix-avatar--vertical-align.yml
@@ -0,0 +1,5 @@
+---
+title: "Fix user avatar's vertical align on the issues and merge requests pages"
+merge_request: 17072
+author: Laszlo Karpati
+type: fixed
diff --git a/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml b/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml
new file mode 100644
index 00000000000..543fd7c5e8d
--- /dev/null
+++ b/changelogs/unreleased/40623-fix-404-when-listing-archived-projects-in-a-group-where-all-projects-have-been-archived.yml
@@ -0,0 +1,4 @@
+title: Fix 404 when listing archived projects in a group where all projects have been archived
+merge_request: 17077
+author: Ashley Dumaine
+type: fixed
diff --git a/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml b/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml
new file mode 100644
index 00000000000..29ab7cc7cab
--- /dev/null
+++ b/changelogs/unreleased/41899-api-endpoint-for-importing-a-project-export.yml
@@ -0,0 +1,5 @@
+---
+title: API endpoint for importing a project export
+merge_request: 17025
+author:
+type: added
diff --git a/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml b/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml
deleted file mode 100644
index 64340ab08cd..00000000000
--- a/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 500 error when loading a merge request with an invalid comment
-merge_request: 16795
-author:
-type: fixed
diff --git a/changelogs/unreleased/42591-update-nokogiri.yml b/changelogs/unreleased/42591-update-nokogiri.yml
deleted file mode 100644
index 5f9587d2d92..00000000000
--- a/changelogs/unreleased/42591-update-nokogiri.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update nokogiri to 1.8.2
-merge_request: 16807
-author:
-type: security
diff --git a/changelogs/unreleased/42641-monaco-service-workers-do-not-work-with-cdn-enabled.yml b/changelogs/unreleased/42641-monaco-service-workers-do-not-work-with-cdn-enabled.yml
new file mode 100644
index 00000000000..955a5a27e21
--- /dev/null
+++ b/changelogs/unreleased/42641-monaco-service-workers-do-not-work-with-cdn-enabled.yml
@@ -0,0 +1,5 @@
+---
+title: Fix monaco editor features which were incompatable with GitLab CDN settings
+merge_request: 17021
+author:
+type: fixed
diff --git a/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml b/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml
deleted file mode 100644
index c46cc86c99b..00000000000
--- a/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix GitLab import leaving group_id on ProjectLabel
-merge_request: 16877
-author:
-type: fixed
diff --git a/changelogs/unreleased/42929-hide-new-variable-values.yml b/changelogs/unreleased/42929-hide-new-variable-values.yml
new file mode 100644
index 00000000000..68decd25b5a
--- /dev/null
+++ b/changelogs/unreleased/42929-hide-new-variable-values.yml
@@ -0,0 +1,5 @@
+---
+title: Hide CI secret variable values after saving
+merge_request: 17044
+author:
+type: changed
diff --git a/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml b/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml
new file mode 100644
index 00000000000..b527000332e
--- /dev/null
+++ b/changelogs/unreleased/43201-rename-repository-submit-button-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Allows project rename after validation error
+merge_request: 17150
+author:
+type: fixed
diff --git a/changelogs/unreleased/asciidoc_inter_document_cross_references.yml b/changelogs/unreleased/asciidoc_inter_document_cross_references.yml
new file mode 100644
index 00000000000..34b26753312
--- /dev/null
+++ b/changelogs/unreleased/asciidoc_inter_document_cross_references.yml
@@ -0,0 +1,5 @@
+---
+title: Asciidoc now support inter-document cross references between files in repository
+merge_request: 17125
+author: Turo Soisenniemi
+type: changed
diff --git a/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml b/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml
deleted file mode 100644
index 378f0ef7ce9..00000000000
--- a/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix forking projects when no restricted visibility levels are defined applicationwide
-merge_request: 16881
-author:
-type: fixed
diff --git a/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml b/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml
new file mode 100644
index 00000000000..a51781396ee
--- /dev/null
+++ b/changelogs/unreleased/change-strip-whitespace-from-username-input-42637.yml
@@ -0,0 +1,5 @@
+---
+title: Remove whitespace from the username/email sign in form field
+merge_request: 17020
+author: Peter lauck
+type: changed
diff --git a/changelogs/unreleased/dm-dont-cache-nil-root-ref.yml b/changelogs/unreleased/dm-dont-cache-nil-root-ref.yml
new file mode 100644
index 00000000000..4dab7d0ffca
--- /dev/null
+++ b/changelogs/unreleased/dm-dont-cache-nil-root-ref.yml
@@ -0,0 +1,5 @@
+---
+title: Don't cache a nil repository root ref to prevent caching issues
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-escape-commit-message.yml b/changelogs/unreleased/dm-escape-commit-message.yml
new file mode 100644
index 00000000000..89af2da3484
--- /dev/null
+++ b/changelogs/unreleased/dm-escape-commit-message.yml
@@ -0,0 +1,5 @@
+---
+title: Escape HTML entities in commit messages
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-user-namespace-route-path-validation.yml b/changelogs/unreleased/dm-user-namespace-route-path-validation.yml
deleted file mode 100644
index 36615e5b976..00000000000
--- a/changelogs/unreleased/dm-user-namespace-route-path-validation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate user namespace before saving so that errors persist on model
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/expired-ci-artifacts.yml b/changelogs/unreleased/expired-ci-artifacts.yml
deleted file mode 100644
index 2fcbdb02f84..00000000000
--- a/changelogs/unreleased/expired-ci-artifacts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change SQL for expired artifacts to use new ci_job_artifacts.expire_at
-merge_request: 16578
-author:
-type: performance
diff --git a/changelogs/unreleased/fix-validation-of-environment-scope-for-variables.yml b/changelogs/unreleased/fix-validation-of-environment-scope-for-variables.yml
new file mode 100644
index 00000000000..5424c15a8ae
--- /dev/null
+++ b/changelogs/unreleased/fix-validation-of-environment-scope-for-variables.yml
@@ -0,0 +1,5 @@
+---
+title: Fix validation of environment scope of variables
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml b/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml
new file mode 100644
index 00000000000..cef339ef787
--- /dev/null
+++ b/changelogs/unreleased/fj-42910-unauthenticated-limit-via-ssh.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed bug with unauthenticated requests through git ssh
+merge_request: 17149
+author:
+type: fixed
diff --git a/changelogs/unreleased/fl-refresh-btn.yml b/changelogs/unreleased/fl-refresh-btn.yml
new file mode 100644
index 00000000000..640fdda9ce7
--- /dev/null
+++ b/changelogs/unreleased/fl-refresh-btn.yml
@@ -0,0 +1,5 @@
+---
+title: Show loading button inline in refresh button in MR widget
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/jej-fix-slow-lfs-object-check.yml b/changelogs/unreleased/jej-fix-slow-lfs-object-check.yml
new file mode 100644
index 00000000000..09112fba85e
--- /dev/null
+++ b/changelogs/unreleased/jej-fix-slow-lfs-object-check.yml
@@ -0,0 +1,5 @@
+---
+title: Only check LFS integrity for first ref in a push to avoid timeout
+merge_request: 17098
+author:
+type: performance
diff --git a/changelogs/unreleased/mk-fix-no-untracked-upload-files-error.yml b/changelogs/unreleased/mk-fix-no-untracked-upload-files-error.yml
deleted file mode 100644
index fddfba94192..00000000000
--- a/changelogs/unreleased/mk-fix-no-untracked-upload-files-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve PrepareUntrackedUploads PostgreSQL syntax error
-merge_request: 17019
-author:
-type: fixed
diff --git a/changelogs/unreleased/remove_ldap_person_validation.yml b/changelogs/unreleased/remove_ldap_person_validation.yml
deleted file mode 100644
index da7f0a52886..00000000000
--- a/changelogs/unreleased/remove_ldap_person_validation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: LDAP Person no longer throws exception on invalid entry
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-jira-trailing-slash.yml b/changelogs/unreleased/sh-fix-jira-trailing-slash.yml
deleted file mode 100644
index 786f6cd3727..00000000000
--- a/changelogs/unreleased/sh-fix-jira-trailing-slash.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix JIRA not working when a trailing slash is included
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/winh-new-branch-dropdown-style.yml b/changelogs/unreleased/winh-new-branch-dropdown-style.yml
deleted file mode 100644
index 007e9e9f453..00000000000
--- a/changelogs/unreleased/winh-new-branch-dropdown-style.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cleanup new branch/merge request form in issues
-merge_request: 16854
-author:
-type: fixed
diff --git a/changelogs/unreleased/winh-new-modal-component.yml b/changelogs/unreleased/winh-new-modal-component.yml
new file mode 100644
index 00000000000..bcc0d489c88
--- /dev/null
+++ b/changelogs/unreleased/winh-new-modal-component.yml
@@ -0,0 +1,5 @@
+---
+title: Add new modal Vue component
+merge_request: 17108
+author:
+type: changed
diff --git a/config/application.rb b/config/application.rb
index c914e34b9c3..918bd4d57cf 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -69,6 +69,7 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key)
+ # - Secret variable values (:value)
config.filter_parameters += [/token$/, /password/, /secret/]
config.filter_parameters += %i(
certificate
@@ -80,6 +81,7 @@ module Gitlab
sentry_dsn
trace
variables
+ value
)
# Enable escaping HTML in JSON.
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 28e05bfc18d..17a8801f7bc 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -262,7 +262,7 @@ Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?: *,? +and +| *, *)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
diff --git a/config/initializers/rack_attack_global.rb b/config/initializers/rack_attack_global.rb
index 9453df2ec5a..a90516eee7d 100644
--- a/config/initializers/rack_attack_global.rb
+++ b/config/initializers/rack_attack_global.rb
@@ -26,6 +26,7 @@ class Rack::Attack
throttle('throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req|
Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
req.unauthenticated? &&
+ !req.api_internal_request? &&
req.ip
end
@@ -54,6 +55,10 @@ class Rack::Attack
path.start_with?('/api')
end
+ def api_internal_request?
+ path =~ %r{^/api/v\d+/internal/}
+ end
+
def web_request?
!api_request?
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index a4e6c64fce5..5ec33533b4f 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -68,7 +68,6 @@ var config = {
help: './help/help.js',
how_to_merge: './how_to_merge.js',
issue_show: './issue_show/index.js',
- integrations: './integrations',
job_details: './jobs/job_details_bundle.js',
locale: './locale/index.js',
main: './main.js',
@@ -79,19 +78,14 @@ var config = {
notes: './notes/index.js',
pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/pipelines_bundle.js',
- pipelines_charts: './pipelines/pipelines_charts.js',
pipelines_details: './pipelines/pipeline_details_bundle.js',
- pipelines_times: './pipelines/pipelines_times.js',
profile: './profile/profile_bundle.js',
project_import_gl: './projects/project_import_gitlab_project.js',
- prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
registry_list: './registry/index.js',
ide: './ide/index.js',
sidebar: './sidebar/sidebar_bundle.js',
- schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
- schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js',
snippet: './snippet/snippet_bundle.js',
sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js',
@@ -102,7 +96,6 @@ var config = {
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
two_factor_auth: './two_factor_auth.js',
- users: './users/index.js',
webpack_runtime: './webpack.js',
},
@@ -158,7 +151,7 @@ var config = {
include: /node_modules\/katex\/dist/,
use: [
{ loader: 'style-loader' },
- {
+ {
loader: 'css-loader',
options: {
name: '[name].[hash].[ext]'
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index 6f241f6fa4a..dfb50c195c1 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -9,7 +9,6 @@ Gitlab::Seeder.quiet do
s.username = 'root'
s.password = '5iveL!fe'
s.admin = true
- s.projects_limit = 100
s.confirmed_at = DateTime.now
end
end
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index 65f59b72690..d978d1dca53 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -9,7 +9,19 @@ created in snippets, wikis, and repos.
## PlantUML Server
Before you can enable PlantUML in GitLab; you need to set up your own PlantUML
-server that will generate the diagrams. Installing and configuring your
+server that will generate the diagrams.
+
+### Docker
+
+With Docker, you can just run a container like this:
+
+`docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat`
+
+The **PlantUML URL** will be the hostname of the server running the container.
+
+### Debian/Ubuntu
+
+Installing and configuring your
own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat.
First you need to create a `plantuml.war` file from the source code:
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index 9d1589d84aa..a795d5116ea 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -56,7 +56,7 @@ new one, and attempting to pull a repo.
> **Warning:** Do not disable writes until SSH is confirmed to be working
perfectly, because the file will quickly become out-of-date.
-In the case of lookup failures (which are not uncommon), the `authorized_keys`
+In the case of lookup failures (which are common), the `authorized_keys`
file will still be scanned. So git SSH performance will still be slow for many
users as long as a large file exists.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 7d47aaac299..edb3e4c961e 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -61,6 +61,21 @@ Before proceeding with the Pages configuration, you will need to:
NOTE: **Note:**
If your GitLab instance and the Pages daemon are deployed in a private network or behind a firewall, your GitLab Pages websites will only be accessible to devices/users that have access to the private network.
+### Add the domain to the Public Suffix List
+
+The [Public Suffix List](https://publicsuffix.org) is used by browsers to
+decide how to treat subdomains. If your GitLab instance allows members of the
+public to create GitLab Pages sites, it also allows those users to create
+subdomains on the pages domain (`example.io`). Adding the domain to the Public
+Suffix List prevents browsers from accepting
+[supercookies](https://en.wikipedia.org/wiki/HTTP_cookie#Supercookie),
+among other things.
+
+Follow [these instructions](https://publicsuffix.org/submit/) to submit your
+GitLab Pages subdomain. For instance, if your domain is `example.io`, you should
+request that `*.example.io` is added to the Public Suffix List. GitLab.com
+added `*.gitlab.io` [in 2016](https://gitlab.com/gitlab-com/infrastructure/issues/230).
+
### DNS configuration
GitLab Pages expect to run on their own virtual host. In your DNS server/provider
diff --git a/doc/api/README.md b/doc/api/README.md
index 88710eae4fe..b193ef4ab7f 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -43,6 +43,7 @@ following locations:
- [Pipeline Schedules](pipeline_schedules.md)
- [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md)
+- [Project import/export](project_import_export.md)
- [Project Members](members.md)
- [Project Snippets](project_snippets.md)
- [Protected Branches](protected_branches.md)
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
new file mode 100644
index 00000000000..e442442c750
--- /dev/null
+++ b/doc/api/project_import_export.md
@@ -0,0 +1,74 @@
+# Project import API
+
+[Introduced][ce-41899] in GitLab 10.6
+
+[See also the project import/export documentation](../user/project/settings/import_export.md)
+
+## Import a file
+
+```http
+POST /projects/import
+```
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ---------------------------------------- |
+| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
+| `file` | string | yes | The file to be uploaded |
+| `path` | string | yes | Name and path for new project |
+
+To upload a file from your filesystem, use the `--form` argument. This causes
+cURL to post data using the header `Content-Type: multipart/form-data`.
+The `file=` parameter must point to a file on your filesystem and be preceded
+by `@`. For example:
+
+```console
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
+```
+
+```json
+{
+ "id": 1,
+ "description": null,
+ "name": "api-project",
+ "name_with_namespace": "Administrator / api-project",
+ "path": "api-project",
+ "path_with_namespace": "root/api-project",
+ "created_at": "2018-02-13T09:05:58.023Z",
+ "import_status": "scheduled"
+}
+```
+
+## Import status
+
+Get the status of an import.
+
+```http
+GET /projects/:id/import
+```
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ---------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```console
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/import
+```
+
+Status can be one of `none`, `scheduled`, `failed`, `started`, or `finished`.
+
+If the status is `failed`, it will include the import error message under `import_error`.
+
+```json
+{
+ "id": 1,
+ "description": "Itaque perspiciatis minima aspernatur corporis consequatur.",
+ "name": "Gitlab Test",
+ "name_with_namespace": "Gitlab Org / Gitlab Test",
+ "path": "gitlab-test",
+ "path_with_namespace": "gitlab-org/gitlab-test",
+ "created_at": "2017-08-29T04:36:44.383Z",
+ "import_status": "started"
+}
+```
+
+[ce-41899]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41899
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 05d2f2af00b..9e649efea9c 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1330,6 +1330,10 @@ POST /projects/:id/housekeeping
Read more in the [Branches](branches.md) documentation.
+## Project Import/Export
+
+Read more in the [Project import/export](project_import_export.md) documentation.
+
## Project members
Read more in the [Project members](members.md) documentation.
diff --git a/doc/ci/README.md b/doc/ci/README.md
index eabeb4510db..532ae52a184 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -70,6 +70,8 @@ learn how to leverage its potential even more.
- [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger pipelines through the GitLab API](triggers/README.md)
- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md)
+- [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or
+ more Kubernetes clusters to your project
## GitLab CI/CD for Docker
diff --git a/doc/development/fe_guide/components.md b/doc/development/fe_guide/components.md
new file mode 100644
index 00000000000..66a8abe42f7
--- /dev/null
+++ b/doc/development/fe_guide/components.md
@@ -0,0 +1,61 @@
+# Components
+
+## Contents
+* [Dropdowns](#dropdowns)
+* [Modals](#modals)
+
+## Dropdowns
+
+See also the [corresponding UX guide](../ux_guide/components.md#dropdowns).
+
+### How to style a bootstrap dropdown
+1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
+1. Add a specific class to the top level `.dropdown` element
+
+
+ ```Haml
+ .dropdown.my-dropdown
+ %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
+ %span.dropdown-toggle-text
+ Toggle Dropdown
+ = icon('chevron-down')
+
+ %ul.dropdown-menu
+ %li
+ %a
+ item!
+ ```
+
+ Or use the helpers
+ ```Haml
+ .dropdown.my-dropdown
+ = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
+ = dropdown_content
+ %li
+ %a
+ item!
+ ```
+
+[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
+
+## Modals
+
+See also the [corresponding UX guide](../ux_guide/components.md#modals).
+
+We have a reusable Vue component for modals: [vue_shared/components/gl-modal.vue](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/vue_shared/components/gl-modal.vue)
+
+Here is an example of how to use it:
+
+```html
+ <gl-modal
+ id="dogs-out-modal"
+ :header-title-text="s__('ModalExample|Let the dogs out?')"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('ModalExample|Let them out')"
+ @submit="letOut(theDogs)"
+ >
+ {{ s__('ModalExample|You’re about to let the dogs out.') }}
+ </gl-modal>
+```
+
+![example modal](img/gl-modal.png)
diff --git a/doc/development/fe_guide/dropdowns.md b/doc/development/fe_guide/dropdowns.md
index 6314f8f38d2..e9d6244355c 100644
--- a/doc/development/fe_guide/dropdowns.md
+++ b/doc/development/fe_guide/dropdowns.md
@@ -1,32 +1 @@
-# Dropdowns
-
-
-## How to style a bootstrap dropdown
-1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
-1. Add a specific class to the top level `.dropdown` element
-
-
- ```Haml
- .dropdown.my-dropdown
- %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
- %span.dropdown-toggle-text
- Toggle Dropdown
- = icon('chevron-down')
-
- %ul.dropdown-menu
- %li
- %a
- item!
- ```
-
- Or use the helpers
- ```Haml
- .dropdown.my-dropdown
- = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
- = dropdown_content
- %li
- %a
- item!
- ```
-
-[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
+This page has moved [here](components.md#dropdowns).
diff --git a/doc/development/fe_guide/img/gl-modal.png b/doc/development/fe_guide/img/gl-modal.png
new file mode 100644
index 00000000000..47302e857bc
--- /dev/null
+++ b/doc/development/fe_guide/img/gl-modal.png
Binary files differ
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 72cb557d054..12dfc10812b 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -21,6 +21,8 @@ Working with our frontend assets requires Node (v4.3 or greater) and Yarn
[jQuery][jquery] is used throughout the application's JavaScript, with
[Vue.js][vue] for particularly advanced, dynamic elements.
+We also use [Axios][axios] to handle all of our network requests.
+
### Browser Support
For our currently-supported browsers, see our [requirements][requirements].
@@ -77,8 +79,10 @@ Axios specific practices and gotchas.
## [Icons](icons.md)
How we use SVG for our Icons.
-## [Dropdowns](dropdowns.md)
-How we use dropdowns.
+## [Components](components.md)
+
+How we use UI components.
+
---
## Style Guides
@@ -122,6 +126,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
[webpack]: https://webpack.js.org/
[jquery]: https://jquery.com/
[vue]: http://vuejs.org/
+[axios]: https://github.com/axios/axios
[airbnb-js-style-guide]: https://github.com/airbnb/javascript
[scss-lint]: https://github.com/brigade/scss-lint
[install]: ../../install/installation.md#4-node
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index 97c997e0568..11878b4009b 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -27,6 +27,17 @@ Gitlab::Profiler.profile('/my-user')
# Returns a RubyProf::Profile where 100 seconds is spent in UsersController#show
```
+For routes that require authorization you will need to provide a user to
+`Gitlab::Profiler`. You can do this like so:
+
+```ruby
+Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first)
+```
+
+The user you provide will need to have a [personal access
+token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) in
+the GitLab instance.
+
Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send
ActiveRecord and ActionController log output to that logger. Further options are
documented with the method source.
diff --git a/doc/install/google_cloud_platform/img/boot_disk.png b/doc/install/google_cloud_platform/img/boot_disk.png
new file mode 100644
index 00000000000..37b2d9eaae7
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/boot_disk.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/change_admin_passwd_email.png b/doc/install/google_cloud_platform/img/change_admin_passwd_email.png
deleted file mode 100644
index 1ffe14f60ff..00000000000
--- a/doc/install/google_cloud_platform/img/change_admin_passwd_email.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/chrome_not_secure_page.png b/doc/install/google_cloud_platform/img/chrome_not_secure_page.png
deleted file mode 100644
index e732066908f..00000000000
--- a/doc/install/google_cloud_platform/img/chrome_not_secure_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/first_signin.png b/doc/install/google_cloud_platform/img/first_signin.png
new file mode 100644
index 00000000000..6eb3392d674
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/first_signin.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png b/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png
deleted file mode 100644
index 2a1859da6e3..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png b/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png
deleted file mode 100644
index 1c4c870dbc9..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_landing.png b/doc/install/google_cloud_platform/img/gcp_landing.png
index 6398d247ba0..d6390c4dd4f 100644
--- a/doc/install/google_cloud_platform/img/gcp_landing.png
+++ b/doc/install/google_cloud_platform/img/gcp_landing.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png b/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png
deleted file mode 100644
index f492888ea4a..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png b/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png
deleted file mode 100644
index b38af3966e2..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gitlab_deployed_page.png b/doc/install/google_cloud_platform/img/gitlab_deployed_page.png
deleted file mode 100644
index fef9ae45f32..00000000000
--- a/doc/install/google_cloud_platform/img/gitlab_deployed_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png b/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png
deleted file mode 100644
index 381c0fe48a5..00000000000
--- a/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/gitlab_launch_button.png b/doc/install/google_cloud_platform/img/gitlab_launch_button.png
deleted file mode 100644
index 50f66f66118..00000000000
--- a/doc/install/google_cloud_platform/img/gitlab_launch_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/launch_vm.png b/doc/install/google_cloud_platform/img/launch_vm.png
new file mode 100644
index 00000000000..3fd13f232bb
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/launch_vm.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png b/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png
deleted file mode 100644
index 00060841619..00000000000
--- a/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/ssh_terminal.png b/doc/install/google_cloud_platform/img/ssh_terminal.png
new file mode 100644
index 00000000000..6a1a418d8e9
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/ssh_terminal.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/ssh_via_button.png b/doc/install/google_cloud_platform/img/ssh_via_button.png
deleted file mode 100644
index 26106f159ad..00000000000
--- a/doc/install/google_cloud_platform/img/ssh_via_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/vm_created.png b/doc/install/google_cloud_platform/img/vm_created.png
new file mode 100644
index 00000000000..fb467f40838
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/vm_created.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/img/vm_details.png b/doc/install/google_cloud_platform/img/vm_details.png
new file mode 100644
index 00000000000..2d230416a4b
--- /dev/null
+++ b/doc/install/google_cloud_platform/img/vm_details.png
Binary files differ
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index c6b767fff02..3389f0260f9 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -2,12 +2,7 @@
![GCP landing page](img/gcp_landing.png)
-The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through
-the [Google Cloud Launcher][launcher] program.
-
-GitLab's official Google Launcher apps:
-1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public)
-2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public)
+Gettung started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy.
## Prerequisites
@@ -17,84 +12,52 @@ There are only two prerequisites in order to install GitLab on GCP:
1. You need to sign up for the GCP program. If this is your first time, Google
gives you [$300 credit for free][freetrial] to consume over a 60-day period.
-Once you have performed those two steps, you can visit the
-[GCP launcher console][console] which has a list of all the things you can
-deploy on GCP.
-
-![GCP launcher console](img/gcp_launcher_console_home_page.png)
-
-The next step is to find and install GitLab.
+Once you have performed those two steps, you can [create a VM](#creating-the-vm).
-## Configuring and deploying the VM
+## Creating the VM
To deploy GitLab on GCP you need to follow five simple steps:
-1. Go to https://cloud.google.com/launcher and login with your Google credentials
-1. Search for GitLab from GitLab Inc. (not the same as Bitnami) and click on
- the tile.
+1. Go to https://console.cloud.google.com/compute/instances and login with your Google credentials.
- ![Search for GitLab](img/gcp_search_for_gitlab.png)
+1. Click on **Create**
-1. In the next page, you can see an overview of the GitLab VM as well as some
- estimated costs. Click the **Launch on Compute Engine** button to choose the
- hardware and network settings.
+ ![Search for GitLab](img/launch_vm.png)
- ![Launch on Compute Engine](img/gcp_gitlab_overview.png)
+1. On the next page, you can select the type of VM as well as the
+ estimated costs. Provide the name of the instance, desired datacenter, and machine type. Note that GitLab recommends at least 2 vCPU's and 4GB of RAM.
-1. In the settings page you can choose things like the datacenter where your GitLab
- server will be hosted, the number of CPUs and amount of RAM, the disk size
- and type, etc. Read GitLab's [requirements documentation][req] for more
- details on what to choose depending on your needs.
+ ![Launch on Compute Engine](img/vm_details.png)
- ![Deploy settings](img/new_gitlab_deployment_settings.png)
+1. Click **Change** under Boot disk to select the size, type, and desired operating system. GitLab supports a [variety of linux operating systems][req], including Ubuntu and Debian. Click **Select** when finished.
-1. As a last step, hit **Deploy** when ready. The process will finish in a few
- seconds.
+ ![Deploy in progress](img/boot_disk.png)
- ![Deploy in progress](img/gcp_gitlab_being_deployed.png)
+1. As a last step allow HTTP and HTTPS traffic, then click **Create**. The process will finish in a few seconds.
+## Installing GitLab
-## Visiting GitLab for the first time
+After a few seconds, the instance will be created and available to log in. The next step is to install GitLab onto the instance.
-After a few seconds, GitLab will be successfully deployed and you should be
-able to see the IP address that Google assigned to the VM, as well as the
-credentials to the GitLab admin account.
+![Deploy settings](img/vm_created.png)
-![Deploy settings](img/gitlab_deployed_page.png)
+1. Make a note of the IP address of the instance, as you will need that in a later step.
+1. Click on the SSH button to connect to the instance.
+1. A new window will appear, with you logged into the instance.
-1. Click on the IP under **Site address** to visit GitLab.
-1. Accept the self-signed certificate that Google automatically deployed in
- order to securely reach GitLab's login page.
-1. Use the username and password that are present in the Google console page
- to login into GitLab and click **Sign in**.
+ ![GitLab first sign in](img/ssh_terminal.png)
- ![GitLab first sign in](img/gitlab_first_sign_in.png)
+1. Next, follow the instructions for installing GitLab for the operating system you choose, at https://about.gitlab.com/installation/. You can use the IP address from the step above, as the hostname.
-Congratulations! GitLab is now installed and you can access it via your browser,
-but we're not done yet. There are some steps you need to take in order to have
-a fully functional GitLab installation.
+1. Congratulations! GitLab is now installed and you can access it via your browser. To finish installation, open the URL in your browser and provide the initial administrator password. The username for this account is `root`.
+
+ ![GitLab first sign in](img/first_signin.png)
## Next steps
These are the most important next steps to take after you installed GitLab for
the first time.
-### Changing the admin password and email
-
-Google assigned a random password for the GitLab admin account and you should
-change it ASAP:
-
-1. Visit the GitLab admin page through the link in the Google console under
- **Admin URL**.
-1. Find the Administrator user under the **Users** page and hit **Edit**.
-1. Change the email address to a real one and enter a new password.
-
- ![Change GitLab admin password](img/change_admin_passwd_email.png)
-
-1. Hit **Save changes** for the changes to take effect.
-1. After changing the password, you will be signed out from GitLab. Use the
- new credentials to login again.
-
### Assigning a static IP
By default, Google assigns an ephemeral IP to your instance. It is strongly
@@ -112,7 +75,7 @@ here's how you configure GitLab to be aware of the change:
1. SSH into the VM. You can easily use the **SSH** button in the Google console
and a new window will pop up.
- ![SSH button](img/ssh_via_button.png)
+ ![SSH button](img/vm_created.png)
In the future you might want to set up [connecting with an SSH key][ssh]
instead.
@@ -161,7 +124,6 @@ Kerberos, etc. Here are some documents you might be interested in reading:
- [GitLab Pages configuration](https://docs.gitlab.com/ce/administration/pages/index.html)
- [GitLab Container Registry configuration](https://docs.gitlab.com/ce/administration/container_registry.html)
-[console]: https://console.cloud.google.com/launcher "GCP launcher console"
[freetrial]: https://console.cloud.google.com/freetrial "GCP free trial"
[ip]: https://cloud.google.com/compute/docs/configure-instance-ip-addresses#promote_ephemeral_ip "Configuring an Instance's IP Addresses"
[gcp]: https://cloud.google.com/ "Google Cloud Platform"
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 01bd925bd6f..5f5ba2b69bc 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -95,7 +95,9 @@ Auto Deploy, and Auto Monitoring will be silently skipped.
The Auto DevOps base domain is required if you want to make use of [Auto
Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It is defined
-under the project's CI/CD settings while [enabling Auto DevOps](#enabling-auto-devops).
+either under the project's CI/CD settings while
+[enabling Auto DevOps](#enabling-auto-devops) or in instance-wide settings in
+the CI/CD section.
It can also be set at the project or group level as a variable, `AUTO_DEVOPS_DOMAIN`.
A wildcard DNS A record matching the base domain is required, for example,
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 50a8e0d5ec5..bbe25c2d911 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -5,20 +5,23 @@
Connect your project to Google Kubernetes Engine (GKE) or an existing Kubernetes
cluster in a few steps.
-With a cluster associated to your project, you can use Review Apps, deploy your
-applications, run your pipelines, and much more, in an easy way.
+## Overview
+
+With a Kubernetes cluster associated to your project, you can use
+[Review Apps](../../../ci/review_apps/index.md), deploy your applications, run
+your pipelines, and much more, in an easy way.
There are two options when adding a new cluster to your project; either associate
your account with Google Kubernetes Engine (GKE) so that you can [create new
clusters](#adding-and-creating-a-new-gke-cluster-via-gitlab) from within GitLab,
or provide the credentials to an [existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster).
-## Prerequisites
+## Adding and creating a new GKE cluster via GitLab
-In order to be able to manage your Kubernetes cluster through GitLab, the
-following prerequisites must be met.
+NOTE: **Note:**
+You need Master [permissions] and above to access the Kubernetes page.
-**For a cluster hosted on GKE:**
+Before proceeding, make sure the following requirements are met:
- The [Google authentication integration](../../../integration/google.md) must
be enabled in GitLab at the instance level. If that's not the case, ask your
@@ -28,30 +31,16 @@ following prerequisites must be met.
account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
must be set up and that you have to have permissions to access it.
- You must have Master [permissions] in order to be able to access the
- **Cluster** page.
+ **Kubernetes** page.
- You must have [Cloud Billing API](https://cloud.google.com/billing/) enabled
- You must have [Resource Manager
API](https://cloud.google.com/resource-manager/)
-**For an existing Kubernetes cluster:**
-
-- Since the cluster is already created, there are no prerequisites.
-
----
-
-If all of the above requirements are met, you can proceed to add a new Kubernetes
-cluster.
-
-## Adding and creating a new GKE cluster via GitLab
-
-NOTE: **Note:**
-You need Master [permissions] and above to access the Clusters page.
-
-Before proceeding, make sure all [prerequisites](#prerequisites) are met.
-To add a new cluster hosted on GKE to your project:
+If all of the above requirements are met, you can proceed to create and add a
+new Kubernetes cluster that will be hosted on GKE to your project:
-1. Navigate to your project's **CI/CD > Clusters** page.
-1. Click on **Add cluster**.
+1. Navigate to your project's **CI/CD > Kubernetes** page.
+1. Click on **Add Kubernetes cluster**.
1. Click on **Create with GKE**.
1. Connect your Google account if you haven't done already by clicking the
**Sign in with Google** button.
@@ -66,7 +55,7 @@ To add a new cluster hosted on GKE to your project:
- **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
of the Virtual Machine instance that the cluster will be based on.
- **Environment scope** - The [associated environment](#setting-the-environment-scope) to this cluster.
-1. Finally, click the **Create cluster** button.
+1. Finally, click the **Create Kubernetes cluster** button.
After a few moments, your cluster should be created. If something goes wrong,
you will be notified.
@@ -77,14 +66,14 @@ enable the Cluster integration.
## Adding an existing Kubernetes cluster
NOTE: **Note:**
-You need Master [permissions] and above to access the Clusters page.
+You need Master [permissions] and above to access the Kubernetes page.
To add an existing Kubernetes cluster to your project:
-1. Navigate to your project's **CI/CD > Clusters** page.
-1. Click on **Add cluster**.
-1. Click on **Add an existing cluster** and fill in the details:
- - **Cluster name** (required) - The name you wish to give the cluster.
+1. Navigate to your project's **CI/CD > Kubernetes** page.
+1. Click on **Add Kuberntes cluster**.
+1. Click on **Add an existing Kubernetes cluster** and fill in the details:
+ - **Kubernetes cluster name** (required) - The name you wish to give the cluster.
- **Environment scope** (required)- The
[associated environment](#setting-the-environment-scope) to this cluster.
- **API URL** (required) -
@@ -112,15 +101,13 @@ To add an existing Kubernetes cluster to your project:
- If you or someone created a secret specifically for the project, usually
with limited permissions, the secret's namespace and project namespace may
be the same.
-1. Finally, click the **Create cluster** button.
-
-The Kubernetes service takes the following parameters:
+1. Finally, click the **Create Kuberntes cluster** button.
After a few moments, your cluster should be created. If something goes wrong,
you will be notified.
You can now proceed to install some pre-defined applications and then
-enable the Cluster integration.
+enable the Kubernetes cluster integration.
## Installing applications
@@ -139,7 +126,7 @@ added directly to your configured cluster. Those applications are needed for
NOTE: **Note:**
You need a load balancer installed in your cluster in order to obtain the
external IP address with the following procedure. It can be deployed using the
-**Ingress** application described in the previous section.
+[**Ingress** application](#installing-appplications).
In order to publish your web application, you first need to find the external IP
address associated to your load balancer.
@@ -153,7 +140,8 @@ the `gcloud` command in a local terminal or using the **Cloud Shell**.
If the cluster is not on GKE, follow the specific instructions for your
Kubernetes provider to configure `kubectl` with the right credentials.
-If you installed the Ingress using the **Applications** section, run the following command:
+If you installed the Ingress [via the **Applications**](#installing-applications),
+run the following command:
```bash
kubectl get svc --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
@@ -171,9 +159,14 @@ your deployed applications.
## Setting the environment scope
-When adding more than one clusters, you need to differentiate them with an
-environment scope. The environment scope associates clusters and
-[environments](../../../ci/environments.md) in an 1:1 relationship similar to how the
+NOTE: **Note:**
+This is only available for [GitLab Premium][ee] where you can add more than
+one Kubernetes cluster.
+
+When adding more than one Kubernetes clusters to your project, you need to
+differentiate them with an environment scope. The environment scope associates
+clusters and [environments](../../../ci/environments.md) in an 1:1 relationship
+similar to how the
[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-secret-variables)
work.
@@ -183,7 +176,7 @@ cluster in a project, and a validation error will occur if otherwise.
---
-For example, let's say the following clusters exist in a project:
+For example, let's say the following Kubernetes clusters exist in a project:
| Cluster | Environment scope |
| ---------- | ------------------- |
@@ -231,8 +224,7 @@ With GitLab Premium, you can associate more than one Kubernetes clusters to your
project. That way you can have different clusters for different environments,
like dev, staging, production, etc.
-To add another cluster, follow the same steps as described in [adding a
-Kubernetes cluster](#adding-a-kubernetes-cluster) and make sure to
+Simply add another cluster, like you did the first time, and make sure to
[set an environment scope](#setting-the-environment-scope) that will
differentiate the new cluster with the rest.
@@ -240,45 +232,42 @@ differentiate the new cluster with the rest.
The Kubernetes cluster integration exposes the following
[deployment variables](../../../ci/variables/README.md#deployment-variables) in the
-GitLab CI/CD build environment:
-
-- `KUBE_URL` - Equal to the API URL.
-- `KUBE_TOKEN` - The Kubernetes token.
-- `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified.
- The default value is `<project_name>-<project_id>`. You can overwrite it to
- use different one if needed, otherwise the `KUBE_NAMESPACE` variable will
- receive the default value.
-- `KUBE_CA_PEM_FILE` - Only present if a custom CA bundle was specified. Path
- to a file containing PEM data.
-- `KUBE_CA_PEM` (deprecated) - Only if a custom CA bundle was specified. Raw PEM data.
-- `KUBECONFIG` - Path to a file containing `kubeconfig` for this deployment.
- CA bundle would be embedded if specified.
-
-## Enabling or disabling the Cluster integration
+GitLab CI/CD build environment.
+
+| Variable | Description |
+| -------- | ----------- |
+| `KUBE_URL` | Equal to the API URL. |
+| `KUBE_TOKEN` | The Kubernetes token. |
+| `KUBE_NAMESPACE` | The Kubernetes namespace is auto-generated if not specified. The default value is `<project_name>-<project_id>`. You can overwrite it to use different one if needed, otherwise the `KUBE_NAMESPACE` variable will receive the default value. |
+| `KUBE_CA_PEM_FILE` | Only present if a custom CA bundle was specified. Path to a file containing PEM data. |
+| `KUBE_CA_PEM` | (**deprecated**) Only if a custom CA bundle was specified. Raw PEM data. |
+| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. |
+
+## Enabling or disabling the Kubernetes cluster integration
After you have successfully added your cluster information, you can enable the
-Cluster integration:
+Kubernetes cluster integration:
1. Click the "Enabled/Disabled" switch
1. Hit **Save** for the changes to take effect
You can now start using your Kubernetes cluster for your deployments.
-To disable the Cluster integration, follow the same procedure.
+To disable the Kubernetes cluster integration, follow the same procedure.
-## Removing the Cluster integration
+## Removing the Kubernetes cluster integration
NOTE: **Note:**
-You need Master [permissions] and above to remove a cluster integration.
+You need Master [permissions] and above to remove a Kubernetes cluster integration.
NOTE: **Note:**
When you remove a cluster, you only remove its relation to GitLab, not the
cluster itself. To remove the cluster, you can do so by visiting the GKE
dashboard or using `kubectl`.
-To remove the Cluster integration from your project, simply click on the
+To remove the Kubernetes cluster integration from your project, simply click on the
**Remove integration** button. You will then be able to follow the procedure
-and [add a cluster](#adding-a-cluster) again.
+and add a Kubernetes cluster again.
## What you can get with the Kubernetes integration
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index ce081cedd71..da3c30a8eaf 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -18,7 +18,7 @@ documentation.
> **Important:**
For security reasons, when using the command line, we strongly recommend
-you to [connect with GitLab via SSH](../../../ssh/README.md).
+that you [connect with GitLab via SSH](../../../ssh/README.md).
## Files
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index b8f865679a2..dedf102fc37 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -22,6 +22,7 @@
> in the import side is required to map the users, based on email or username.
> Otherwise, a supplementary comment is left to mention the original author and
> the MRs, notes or issues will be owned by the importer.
+> - Control project Import/Export with the [API](../../../api/project_import_export.md).
Existing projects running on any GitLab instance or GitLab.com can be exported
with all their related data and be moved into a new GitLab instance.
diff --git a/features/group/members.feature b/features/group/members.feature
deleted file mode 100644
index 49a44f57cbb..00000000000
--- a/features/group/members.feature
+++ /dev/null
@@ -1,12 +0,0 @@
-Feature: Group Members
- Background:
- Given I sign in as "John Doe"
- And "John Doe" is owner of group "Owned"
- And "John Doe" is guest of group "Guest"
-
- Scenario: Search member by name
- Given "Mary Jane" is guest of group "Guest"
- And I visit group "Guest" members page
- When I search for 'Mary' member
- Then I should see user "Mary Jane" in team list
- Then I should not see user "John Doe" in team list
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
deleted file mode 100644
index 2211acfee20..00000000000
--- a/features/group/milestones.feature
+++ /dev/null
@@ -1,48 +0,0 @@
-Feature: Group Milestones
- Background:
- Given I sign in as "John Doe"
- And "John Doe" is owner of group "Owned"
-
- Scenario: I should see group "Owned" milestone index page with no milestones
- When I visit group "Owned" page
- And I click on group milestones
- Then I should see group milestones index page has no milestones
-
- Scenario: I should see group "Owned" milestone index page with milestones
- Given Group has projects with milestones
- When I visit group "Owned" page
- And I click on group milestones
- Then I should see group milestones index page with milestones
-
- Scenario: I should see group "Owned" milestone show page
- Given Group has projects with milestones
- When I visit group "Owned" page
- And I click on group milestones
- And I click on one group milestone
- Then I should see group milestone with descriptions and expiry date
- And I should see group milestone with all issues and MRs assigned to that milestone
-
- Scenario: Create group milestones
- Given I visit group "Owned" milestones page
- And I click new milestone button
- And I fill milestone name
- When I press create mileston button
- Then group milestone should be created
-
- Scenario: I should see Issues listed with labels
- Given Group has projects with milestones
- When I visit group "Owned" page
- And I click on group milestones
- And I click on one group milestone
- Then I should see the "bug" label
- And I should see the "feature" label
- And I should see the project name in the Issue row
-
- @javascript
- Scenario: I should see the Labels tab
- Given Group has projects with milestones
- When I visit group "Owned" page
- And I click on group milestones
- And I click on one group milestone
- And I click on the "Labels" tab
- Then I should see the list of labels
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
deleted file mode 100644
index 3263d3e212b..00000000000
--- a/features/profile/profile.feature
+++ /dev/null
@@ -1,85 +0,0 @@
-@profile
-Feature: Profile
- Background:
- Given I sign in as a user
-
- Scenario: I look at my profile
- Given I visit profile page
- Then I should see my profile info
-
- @javascript
- Scenario: I can see groups I belong to
- Given I have group with projects
- When I visit profile page
- And I click on my profile picture
- Then I should see my user page
- And I should see groups I belong to
-
- Scenario: I edit profile
- Given I visit profile page
- Then I change my profile info
- And I should see new profile info
-
- Scenario: I change my password without old one
- Given I visit profile password page
- When I try change my password w/o old one
- Then I should see a missing password error message
- And I should be redirected to password page
-
- Scenario: I change my password
- Given I visit profile password page
- Then I change my password
- And I should be redirected to sign in page
-
- Scenario: I edit my avatar
- Given I visit profile page
- Then I change my avatar
- And I should see new avatar
- And I should see the "Remove avatar" button
- And I should see the gravatar host link
-
- Scenario: I remove my avatar
- Given I visit profile page
- And I have an avatar
- When I remove my avatar
- Then I should see my gravatar
- And I should not see the "Remove avatar" button
- And I should see the gravatar host link
-
- Scenario: My password is expired
- Given my password is expired
- And I am not an ldap user
- Given I visit profile password page
- Then I redirected to expired password page
- And I submit new password
- And I redirected to sign in page
-
- Scenario: I unsuccessfully change my password
- Given I visit profile password page
- When I unsuccessfully change my password
- Then I should see a password error message
-
- Scenario: I visit history tab
- Given I logout
- And I sign in via the UI
- And I have activity
- When I visit Authentication log page
- Then I should see my activity
-
- Scenario: I visit my user page
- When I visit profile page
- And I click on my profile picture
- Then I should see my user page
-
- Scenario: I can manage application
- Given I visit profile applications page
- Then I should see application form
- Then I fill application form out and submit
- And I see application
- Then I click edit
- And I see edit application form
- Then I change name of application and submit
- And I see that application was changed
- Then I visit profile applications page
- And I click to remove application
- Then I see that application is removed
diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb
index 0ab1012660c..97bcca7730b 100644
--- a/features/steps/group/members.rb
+++ b/features/steps/group/members.rb
@@ -9,14 +9,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
expect(group_members_list).to have_content("John Doe")
end
- step 'I should not see user "John Doe" in team list' do
- expect(group_members_list).not_to have_content("John Doe")
- end
-
- step 'I should see user "Mary Jane" in team list' do
- expect(group_members_list).to have_content("Mary Jane")
- end
-
step 'I should not see user "Mary Jane" in team list' do
expect(group_members_list).not_to have_content("Mary Jane")
end
@@ -41,13 +33,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
# poltergeist always confirms popups.
end
- step 'I search for \'Mary\' member' do
- page.within '.member-search-form' do
- fill_in 'search', with: 'Mary'
- find('.member-search-btn').click
- end
- end
-
step 'I change the "Mary Jane" role to "Developer"' do
member = mary_jane_member
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
deleted file mode 100644
index 818bbb50d0e..00000000000
--- a/features/steps/group/milestones.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
- include WaitForRequests
- include SharedAuthentication
- include SharedPaths
- include SharedGroup
- include SharedUser
-
- step 'I click on group milestones' do
- visit group_milestones_path('owned')
- end
-
- step 'I should see group milestones index page has no milestones' do
- expect(page).to have_content('No milestones to show')
- end
-
- step 'Group has projects with milestones' do
- group_milestone
- end
-
- step 'I should see group milestones index page with milestones' do
- expect(page).to have_content('Version 7.2')
- expect(page).to have_content('GL-113')
- expect(page).to have_link('3 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
- expect(page).to have_link('0 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
- end
-
- step 'I click on one group milestone' do
- milestones = Milestone.where(title: 'GL-113')
- @global_milestone = GlobalMilestone.new('GL-113', milestones)
-
- click_link 'GL-113'
- end
-
- step 'I should see group milestone with descriptions and expiry date' do
- expect(page).to have_content('expires on Aug 20, 2114')
- end
-
- step 'I should see group milestone with all issues and MRs assigned to that milestone' do
- expect(page).to have_content('Milestone GL-113')
- expect(page).to have_content('Issues 3 Open: 3 Closed: 0')
- issue = Milestone.find_by(name: 'GL-113').issues.first
- expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
- end
-
- step 'I fill milestone name' do
- fill_in 'milestone_title', with: 'v2.9.0'
- end
-
- step 'I click new milestone button' do
- page.within('.nav-controls') do
- click_link "New milestone"
- end
- end
-
- step 'I press create mileston button' do
- click_button "Create milestone"
- end
-
- step 'group milestone should be created' do
- group = Group.find_by(name: 'Owned')
- expect(page).to have_content group.milestones.find_by_title('v2.9.0').title
- end
-
- step 'I should see the "bug" label' do
- page.within('#tab-issues') do
- expect(page).to have_content 'bug'
- end
- end
-
- step 'I should see the "feature" label' do
- page.within('#tab-issues') do
- expect(page).to have_content 'bug'
- end
- end
-
- step 'I should see the project name in the Issue row' do
- page.within('#tab-issues') do
- @global_milestone.projects.each do |project|
- expect(page).to have_content project.name
- end
- end
- end
-
- step 'I click on the "Labels" tab' do
- page.within('.content .nav-links') do
- page.find(:xpath, "//a[@href='#tab-labels']").click
- end
- end
-
- step 'I should see the list of labels' do
- wait_for_requests
-
- page.within('#tab-labels') do
- expect(page).to have_content 'bug'
- expect(page).to have_content 'feature'
- end
- end
-
- private
-
- def group_milestone
- group = owned_group
-
- %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
- project = create(:project, path: path, group: group)
- milestone = create :milestone, title: "Version 7.2", project: project
-
- create(:label, project: project, title: 'bug')
- create(:label, project: project, title: 'feature')
-
- create :issue,
- project: project,
- assignees: [current_user],
- author: current_user,
- milestone: milestone
-
- milestone = create :milestone,
- title: "GL-113",
- project: project,
- due_date: '2114-08-20',
- description: 'Lorem Ipsum is simply dummy text'
-
- issue = create :issue,
- project: project,
- assignees: [current_user],
- author: current_user,
- milestone: milestone
-
- issue.labels << project.labels.find_by(title: 'bug')
- issue.labels << project.labels.find_by(title: 'feature')
- end
-
- current_user.refresh_authorized_projects
- end
-end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
deleted file mode 100644
index d3b88ae8d2a..00000000000
--- a/features/steps/profile/profile.rb
+++ /dev/null
@@ -1,226 +0,0 @@
-class Spinach::Features::Profile < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
-
- step 'I should see my profile info' do
- expect(page).to have_content "This information will appear on your profile"
- end
-
- step 'I change my profile info' do
- fill_in 'user_skype', with: 'testskype'
- fill_in 'user_linkedin', with: 'testlinkedin'
- fill_in 'user_twitter', with: 'testtwitter'
- fill_in 'user_website_url', with: 'testurl'
- fill_in 'user_location', with: 'Ukraine'
- fill_in 'user_bio', with: 'I <3 GitLab'
- fill_in 'user_organization', with: 'GitLab'
- click_button 'Update profile settings'
- @user.reload
- end
-
- step 'I should see new profile info' do
- expect(@user.skype).to eq 'testskype'
- expect(@user.linkedin).to eq 'testlinkedin'
- expect(@user.twitter).to eq 'testtwitter'
- expect(@user.website_url).to eq 'testurl'
- expect(@user.bio).to eq 'I <3 GitLab'
- expect(@user.organization).to eq 'GitLab'
- expect(find('#user_location').value).to eq 'Ukraine'
- end
-
- step 'I change my avatar' do
- attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
- click_button "Update profile settings"
- @user.reload
- end
-
- step 'I should see new avatar' do
- expect(@user.avatar).to be_instance_of AvatarUploader
- expect(@user.avatar.url).to eq "/uploads/-/system/user/avatar/#{@user.id}/banana_sample.gif"
- end
-
- step 'I should see the "Remove avatar" button' do
- expect(page).to have_link("Remove avatar")
- end
-
- step 'I have an avatar' do
- attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
- click_button "Update profile settings"
- @user.reload
- end
-
- step 'I remove my avatar' do
- click_link "Remove avatar"
- @user.reload
- end
-
- step 'I should see my gravatar' do
- expect(@user.avatar?).to eq false
- end
-
- step 'I should not see the "Remove avatar" button' do
- expect(page).not_to have_link("Remove avatar")
- end
-
- step 'I should see the gravatar host link' do
- expect(page).to have_link("gravatar.com")
- end
-
- step 'I try change my password w/o old one' do
- page.within '.update-password' do
- fill_in "user_password", with: "22233344"
- fill_in "user_password_confirmation", with: "22233344"
- click_button "Save password"
- end
- end
-
- step 'I change my password' do
- page.within '.update-password' do
- fill_in "user_current_password", with: "12345678"
- fill_in "user_password", with: "22233344"
- fill_in "user_password_confirmation", with: "22233344"
- click_button "Save password"
- end
- end
-
- step 'I unsuccessfully change my password' do
- page.within '.update-password' do
- fill_in "user_current_password", with: "12345678"
- fill_in "user_password", with: "password"
- fill_in "user_password_confirmation", with: "confirmation"
- click_button "Save password"
- end
- end
-
- step "I should see a missing password error message" do
- page.within ".flash-container" do
- expect(page).to have_content "You must provide a valid current password"
- end
- end
-
- step "I should see a password error message" do
- page.within '.alert-danger' do
- expect(page).to have_content "Password confirmation doesn't match"
- end
- end
-
- step 'I have activity' do
- create(:closed_issue_event, author: current_user)
- end
-
- step 'I should see my activity' do
- expect(page).to have_content "Signed in with standard authentication"
- end
-
- step 'my password is expired' do
- current_user.update_attributes(password_expires_at: Time.now - 1.hour)
- end
-
- step "I am not an ldap user" do
- current_user.identities.delete
- expect(current_user.ldap_user?).to eq false
- end
-
- step 'I redirected to expired password page' do
- expect(current_path).to eq new_profile_password_path
- end
-
- step 'I submit new password' do
- fill_in :user_current_password, with: '12345678'
- fill_in :user_password, with: '12345678'
- fill_in :user_password_confirmation, with: '12345678'
- click_button "Set new password"
- end
-
- step 'I redirected to sign in page' do
- expect(current_path).to eq new_user_session_path
- end
-
- step 'I should be redirected to password page' do
- expect(current_path).to eq edit_profile_password_path
- end
-
- step 'I should be redirected to account page' do
- expect(current_path).to eq profile_account_path
- end
-
- step 'I click on my profile picture' do
- find(:css, '.header-user-dropdown-toggle').click
-
- page.within ".header-user" do
- click_link "Profile"
- end
- end
-
- step 'I should see my user page' do
- page.within ".cover-block" do
- expect(page).to have_content current_user.name
- expect(page).to have_content current_user.username
- end
- end
-
- step 'I have group with projects' do
- @group = create(:group)
- @group.add_owner(current_user)
- @project = create(:project, :repository, namespace: @group)
- @event = create(:closed_issue_event, project: @project)
-
- @project.add_master(current_user)
- end
-
- step 'I should see groups I belong to' do
- page.within ".content" do
- click_link "Groups"
- end
-
- page.within "#groups" do
- expect(page).to have_content @group.name
- end
- end
-
- step 'I should see application form' do
- expect(page).to have_content "Add new application"
- end
-
- step 'I fill application form out and submit' do
- fill_in :doorkeeper_application_name, with: 'test'
- fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
- click_on "Save application"
- end
-
- step 'I see application' do
- expect(page).to have_content "Application: test"
- expect(page).to have_content "Application Id"
- expect(page).to have_content "Secret"
- end
-
- step 'I click edit' do
- click_on "Edit"
- end
-
- step 'I see edit application form' do
- expect(page).to have_content "Edit application"
- end
-
- step 'I change name of application and submit' do
- expect(page).to have_content "Edit application"
- fill_in :doorkeeper_application_name, with: 'test_changed'
- click_on "Save application"
- end
-
- step 'I see that application was changed' do
- expect(page).to have_content "test_changed"
- expect(page).to have_content "Application Id"
- expect(page).to have_content "Secret"
- end
-
- step 'I click to remove application' do
- page.within '.oauth-applications' do
- click_on "Destroy"
- end
- end
-
- step "I see that application is removed" do
- expect(page.find(".oauth-applications")).not_to have_content "test_changed"
- end
-end
diff --git a/features/support/env.rb b/features/support/env.rb
index 7f5b4c1c11b..15211995918 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -20,15 +20,16 @@ Dir["#{Rails.root}/features/steps/shared/*.rb"].each { |file| require file }
Spinach.hooks.before_run do
include RSpec::Mocks::ExampleMethods
+ include ActiveJob::TestHelper
+ include FactoryBot::Syntax::Methods
+ include GitlabRoutingHelper
+
RSpec::Mocks.setup
TestEnv.init(mailer: false)
# skip pre-receive hook check so we can use
# web editor and merge
TestEnv.disable_pre_receive
-
- include FactoryBot::Syntax::Methods
- include GitlabRoutingHelper
end
Spinach.hooks.after_scenario do |scenario_data, step_definitions|
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e953f3d2eca..754549f72f0 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -138,6 +138,7 @@ module API
mount ::API::PagesDomains
mount ::API::Pipelines
mount ::API::PipelineSchedules
+ mount ::API::ProjectImport
mount ::API::ProjectHooks
mount ::API::Projects
mount ::API::ProjectMilestones
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 03abc1b95c5..45c737c6c29 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -91,6 +91,13 @@ module API
expose :created_at
end
+ class ProjectImportStatus < ProjectIdentity
+ expose :import_status
+
+ # TODO: Use `expose_nil` once we upgrade the grape-entity gem
+ expose :import_error, if: lambda { |status, _ops| status.import_error }
+ end
+
class BasicProjectDetails < ProjectIdentity
include ::API::ProjectsRelationBuilder
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
new file mode 100644
index 00000000000..a509c1f32c1
--- /dev/null
+++ b/lib/api/project_import.rb
@@ -0,0 +1,69 @@
+module API
+ class ProjectImport < Grape::API
+ include PaginationParams
+
+ helpers do
+ def import_params
+ declared_params(include_missing: false)
+ end
+
+ def file_is_valid?
+ import_params[:file] && import_params[:file]['tempfile'].respond_to?(:read)
+ end
+
+ def validate_file!
+ render_api_error!('The file is invalid', 400) unless file_is_valid?
+ end
+ end
+
+ before do
+ forbidden! unless Gitlab::CurrentSettings.import_sources.include?('gitlab_project')
+ end
+
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+ params do
+ requires :path, type: String, desc: 'The new project path and name'
+ requires :file, type: File, desc: 'The project export file to be imported'
+ optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace."
+ end
+ desc 'Create a new project import' do
+ detail 'This feature was introduced in GitLab 10.6.'
+ success Entities::ProjectImportStatus
+ end
+ post 'import' do
+ validate_file!
+
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437')
+
+ namespace = if import_params[:namespace]
+ find_namespace!(import_params[:namespace])
+ else
+ current_user.namespace
+ end
+
+ project_params = {
+ path: import_params[:path],
+ namespace_id: namespace.id,
+ file: import_params[:file]['tempfile']
+ }
+
+ project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
+
+ render_api_error!(project.errors.full_messages&.first, 400) unless project.saved?
+
+ present project, with: Entities::ProjectImportStatus
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ desc 'Get a project export status' do
+ detail 'This feature was introduced in GitLab 10.6.'
+ success Entities::ProjectImportStatus
+ end
+ get ':id/import' do
+ present user_project, with: Entities::ProjectImportStatus
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb
index f3bd587c28b..e008fd428b0 100644
--- a/lib/banzai/filter/html_entity_filter.rb
+++ b/lib/banzai/filter/html_entity_filter.rb
@@ -5,7 +5,7 @@ module Banzai
# Text filter that escapes these HTML entities: & " < >
class HtmlEntityFilter < HTML::Pipeline::TextFilter
def call
- ERB::Util.html_escape_once(text)
+ ERB::Util.html_escape(text)
end
end
end
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index ee7f4be6b9f..62c41801d75 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -8,7 +8,8 @@ module Gitlab
module Asciidoc
DEFAULT_ADOC_ATTRS = [
'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
- 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font'
+ 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font',
+ 'outfilesuffix=.adoc'
].freeze
# Public: Converts the provided Asciidoc markup into HTML.
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index d75e73dac10..521680b8708 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -16,11 +16,11 @@ module Gitlab
lfs_objects_missing: 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'
}.freeze
- attr_reader :user_access, :project, :skip_authorization, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name
+ attr_reader :user_access, :project, :skip_authorization, :skip_lfs_integrity_check, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name
def initialize(
change, user_access:, project:, skip_authorization: false,
- protocol:
+ skip_lfs_integrity_check: false, protocol:
)
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
@branch_name = Gitlab::Git.branch_name(@ref)
@@ -28,6 +28,7 @@ module Gitlab
@user_access = user_access
@project = project
@skip_authorization = skip_authorization
+ @skip_lfs_integrity_check = skip_lfs_integrity_check
@protocol = protocol
end
@@ -37,7 +38,7 @@ module Gitlab
push_checks
branch_checks
tag_checks
- lfs_objects_exist_check
+ lfs_objects_exist_check unless skip_lfs_integrity_check
commits_check unless skip_commits_check
true
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 8ec3386184a..9ec3858b493 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -238,19 +238,22 @@ module Gitlab
changes_list = Gitlab::ChangesList.new(changes)
# Iterate over all changes to find if user allowed all of them to be applied
- changes_list.each do |change|
+ changes_list.each.with_index do |change, index|
+ first_change = index == 0
+
# If user does not have access to make at least one change, cancel all
# push by allowing the exception to bubble up
- check_single_change_access(change)
+ check_single_change_access(change, skip_lfs_integrity_check: !first_change)
end
end
- def check_single_change_access(change)
+ def check_single_change_access(change, skip_lfs_integrity_check: false)
Checks::ChangeAccess.new(
change,
user_access: user_access,
project: project,
skip_authorization: deploy_key?,
+ skip_lfs_integrity_check: skip_lfs_integrity_check,
protocol: protocol
).exec
end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 1c9477e84b2..84d6e1490c3 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -13,7 +13,7 @@ module Gitlab
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code)
end
- def check_single_change_access(change)
+ def check_single_change_access(change, _options = {})
unless user_access.can_do_action?(:create_wiki)
raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki]
end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
index 47b3fce3e7a..a6bea98d631 100644
--- a/lib/gitlab/ldap/config.rb
+++ b/lib/gitlab/ldap/config.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def self.servers
- Gitlab.config.ldap.servers.values
+ Gitlab.config.ldap['servers']&.values || []
end
def self.available_servers
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index a3e1c66c19f..28ebac1776e 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -198,9 +198,11 @@ module Gitlab
end
def update_profile
+ clear_user_synced_attributes_metadata
+
return unless sync_profile_from_provider? || creating_linked_ldap_user?
- metadata = gl_user.user_synced_attributes_metadata || gl_user.build_user_synced_attributes_metadata
+ metadata = gl_user.build_user_synced_attributes_metadata
if sync_profile_from_provider?
UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
@@ -221,6 +223,10 @@ module Gitlab
end
end
+ def clear_user_synced_attributes_metadata
+ gl_user&.user_synced_attributes_metadata&.destroy
+ end
+
def log
Gitlab::AppLogger
end
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 95d94b3cc68..98a168b43bb 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -45,6 +45,7 @@ module Gitlab
if user
private_token ||= user.personal_access_tokens.active.pluck(:token).first
+ raise 'Your user must have a personal_access_token' unless private_token
end
headers['Private-Token'] = private_token if private_token
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 3ab406eff2c..fe5032cae18 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -16,5 +16,54 @@ unless Rails.env.production?
task :javascript do
Rake::Task['eslint'].invoke
end
+
+ desc "GitLab | lint | Run several lint checks"
+ task :all do
+ status = 0
+
+ %w[
+ config_lint
+ haml_lint
+ scss_lint
+ flay
+ gettext:lint
+ lint:static_verification
+ ].each do |task|
+ pid = Process.fork do
+ rd, wr = IO.pipe
+ stdout = $stdout.dup
+ stderr = $stderr.dup
+ $stdout.reopen(wr)
+ $stderr.reopen(wr)
+
+ begin
+ begin
+ Rake::Task[task].invoke
+ rescue RuntimeError # The haml_lint tasks raise a RuntimeError
+ exit(1)
+ end
+ rescue SystemExit => ex
+ msg = "*** Rake task #{task} failed with the following error(s):"
+ raise ex
+ ensure
+ $stdout.reopen(stdout)
+ $stderr.reopen(stderr)
+ wr.close
+
+ if msg
+ warn "\n#{msg}\n\n"
+ IO.copy_stream(rd, $stderr)
+ else
+ IO.copy_stream(rd, $stdout)
+ end
+ end
+ end
+
+ Process.waitpid(pid)
+ status += $?.exitstatus
+ end
+
+ exit(status)
+ end
end
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index a8a5601dbe6..596205fe540 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -53,8 +53,8 @@ module QA
click_link 'LDAP'
- fill_in :username, with: Runtime::User.name
- fill_in :password, with: Runtime::User.password
+ fill_in :username, with: Runtime::User.ldap_username
+ fill_in :password, with: Runtime::User.ldap_password
click_button 'Sign in'
end
end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index b603557f59c..0c7ad46d36b 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -45,6 +45,10 @@ module QA
end
def new_merge_request
+ wait(reload: true) do
+ has_css?(element_selector_css(:create_merge_request))
+ end
+
click_element :create_merge_request
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 5401372e225..fe432edfa2a 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -35,6 +35,14 @@ module QA
ENV['GITLAB_PASSWORD']
end
+ def ldap_username
+ ENV['GITLAB_LDAP_USERNAME']
+ end
+
+ def ldap_password
+ ENV['GITLAB_LDAP_PASSWORD']
+ end
+
def sandbox_name
ENV['GITLAB_SANDBOX_NAME']
end
diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb
index ecc9ab321ba..c80ee6d4d96 100644
--- a/qa/qa/runtime/user.rb
+++ b/qa/qa/runtime/user.rb
@@ -18,6 +18,14 @@ module QA
def ldap_user?
Runtime::Env.user_type == 'ldap'
end
+
+ def ldap_username
+ Runtime::Env.ldap_username || name
+ end
+
+ def ldap_password
+ Runtime::Env.ldap_password || password
+ end
end
end
end
diff --git a/scripts/static-analysis b/scripts/static-analysis
index bdb88f3cb57..0e67eabfec1 100755
--- a/scripts/static-analysis
+++ b/scripts/static-analysis
@@ -7,7 +7,7 @@ require_relative '../lib/gitlab/popen/runner'
def emit_warnings(static_analysis)
static_analysis.warned_results.each do |result|
puts
- puts "**** #{result.cmd.join(' ')} had the following warnings:"
+ puts "**** #{result.cmd.join(' ')} had the following warning(s):"
puts
puts result.stderr
puts
@@ -17,7 +17,7 @@ end
def emit_errors(static_analysis)
static_analysis.failed_results.each do |result|
puts
- puts "**** #{result.cmd.join(' ')} failed with the following error:"
+ puts "**** #{result.cmd.join(' ')} failed with the following error(s):"
puts
puts result.stdout
puts result.stderr
@@ -26,15 +26,10 @@ def emit_errors(static_analysis)
end
tasks = [
- %w[bundle exec rake config_lint],
- %w[bundle exec rake flay],
- %w[bundle exec rake haml_lint],
- %w[bundle exec rake scss_lint],
+ %w[bin/rake lint:all],
%w[bundle exec license_finder],
%w[yarn run eslint],
%w[bundle exec rubocop --parallel],
- %w[bundle exec rake gettext:lint],
- %w[bundle exec rake lint:static_verification],
%w[scripts/lint-conflicts.sh],
%w[scripts/lint-rugged]
]
diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb
new file mode 100644
index 00000000000..31fbbcf562c
--- /dev/null
+++ b/spec/features/groups/members/search_members_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe 'Search group member' do
+ let(:user) { create :user }
+ let(:member) { create :user }
+
+ let!(:guest_group) do
+ create(:group) do |group|
+ group.add_guest(user)
+ group.add_guest(member)
+ end
+ end
+
+ before do
+ sign_in(user)
+ visit group_group_members_path(guest_group)
+ end
+
+ it 'renders member users' do
+ page.within '.member-search-form' do
+ fill_in 'search', with: member.name
+ find('.member-search-btn').click
+ end
+
+ group_members_list = find(".panel .content-list")
+ expect(group_members_list).to have_content(member.name)
+ expect(group_members_list).not_to have_content(user.name)
+ end
+end
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 1b41b3842c8..20337f1d3b0 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Group milestones', :js do
+feature 'Group milestones' do
let(:group) { create(:group) }
let!(:project) { create(:project_empty_repo, group: group) }
let(:user) { create(:group_member, :master, user: create(:user), group: group ).user }
@@ -13,7 +13,7 @@ feature 'Group milestones', :js do
sign_in(user)
end
- context 'create a milestone' do
+ context 'create a milestone', :js do
before do
visit new_group_milestone_path(group)
end
@@ -61,55 +61,132 @@ feature 'Group milestones', :js do
end
context 'milestones list' do
- let!(:other_project) { create(:project_empty_repo, group: group) }
-
- let!(:active_project_milestone1) { create(:milestone, project: project, state: 'active', title: 'v1.0') }
- let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') }
- let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
- let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
- let!(:active_group_milestone) { create(:milestone, group: group, state: 'active') }
- let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
-
- before do
- visit group_milestones_path(group)
+ context 'when no milestones' do
+ it 'renders no milestones text' do
+ visit group_milestones_path(group)
+ expect(page).to have_content('No milestones to show')
+ end
end
- it 'counts milestones correctly' do
- expect(find('.top-area .active .badge').text).to eq("2")
- expect(find('.top-area .closed .badge').text).to eq("2")
- expect(find('.top-area .all .badge').text).to eq("4")
- end
+ context 'when milestones exists' do
+ let!(:other_project) { create(:project_empty_repo, group: group) }
+
+ let!(:active_project_milestone1) do
+ create(
+ :milestone,
+ project: project,
+ state: 'active',
+ title: 'v1.0',
+ due_date: '2114-08-20',
+ description: 'Lorem Ipsum is simply dummy text'
+ )
+ end
+ let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') }
+ let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
+ let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
+ let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') }
+ let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
+ let!(:issue) do
+ create :issue, project: project, assignees: [user], author: user, milestone: active_project_milestone1
+ end
- it 'lists legacy group milestones and group milestones' do
- legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first
+ before do
+ visit group_milestones_path(group)
+ end
- expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
- expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1)
- end
+ it 'counts milestones correctly' do
+ expect(find('.top-area .active .badge').text).to eq("2")
+ expect(find('.top-area .closed .badge').text).to eq("2")
+ expect(find('.top-area .all .badge').text).to eq("4")
+ end
- it 'updates milestone' do
- page.within(".milestones #milestone_#{active_group_milestone.id}") do
- click_link('Edit')
+ it 'lists legacy group milestones and group milestones' do
+ legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first
+
+ expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
+ expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1)
end
- page.within('.milestone-form') do
- fill_in 'milestone_title', with: 'new title'
- click_button('Update milestone')
+ it 'updates milestone' do
+ page.within(".milestones #milestone_#{active_group_milestone.id}") do
+ click_link('Edit')
+ end
+
+ page.within('.milestone-form') do
+ fill_in 'milestone_title', with: 'new title'
+ click_button('Update milestone')
+ end
+
+ expect(find('#content-body h2')).to have_content('new title')
end
- expect(find('#content-body h2')).to have_content('new title')
- end
+ it 'shows milestone detail and supports its edit' do
+ page.within(".milestones #milestone_#{active_group_milestone.id}") do
+ click_link(active_group_milestone.title)
+ end
+
+ page.within('.detail-page-header') do
+ click_link('Edit')
+ end
- it 'shows milestone detail and supports its edit' do
- page.within(".milestones #milestone_#{active_group_milestone.id}") do
- click_link(active_group_milestone.title)
+ expect(page).to have_selector('.milestone-form')
end
- page.within('.detail-page-header') do
- click_link('Edit')
+ it 'renders milestones' do
+ expect(page).to have_content('v1.0')
+ expect(page).to have_content('GL-113')
+ expect(page).to have_link(
+ '1 Issue',
+ href: issues_group_path(group, milestone_title: 'v1.0')
+ )
+ expect(page).to have_link(
+ '0 Merge Requests',
+ href: merge_requests_group_path(group, milestone_title: 'v1.0')
+ )
end
- expect(page).to have_selector('.milestone-form')
+ it 'renders group milestone details' do
+ click_link 'v1.0'
+
+ expect(page).to have_content('expires on Aug 20, 2114')
+ expect(page).to have_content('v1.0')
+ expect(page).to have_content('Issues 1 Open: 1 Closed: 0')
+ expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
+ end
+
+ describe 'labels' do
+ before do
+ create(:label, project: project, title: 'bug') do |label|
+ issue.labels << label
+ end
+
+ create(:label, project: project, title: 'feature') do |label|
+ issue.labels << label
+ end
+ end
+
+ it 'renders labels' do
+ click_link 'v1.0'
+
+ page.within('#tab-issues') do
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'feature'
+ end
+ end
+
+ it 'renders labels list', :js do
+ click_link 'v1.0'
+
+ page.within('.content .nav-links') do
+ page.find(:xpath, "//a[@href='#tab-labels']").click
+ end
+
+ page.within('#tab-labels') do
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'feature'
+ end
+ end
+ end
end
end
end
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index 4665626f114..1d7700b6767 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -1,6 +1,15 @@
require 'spec_helper'
describe 'Profile > Password' do
+ let(:user) { create(:user) }
+
+ def fill_passwords(password, confirmation)
+ fill_in 'New password', with: password
+ fill_in 'Password confirmation', with: confirmation
+
+ click_button 'Save password'
+ end
+
context 'Password authentication enabled' do
let(:user) { create(:user, password_automatically_set: true) }
@@ -9,13 +18,6 @@ describe 'Profile > Password' do
visit edit_profile_password_path
end
- def fill_passwords(password, confirmation)
- fill_in 'New password', with: password
- fill_in 'Password confirmation', with: confirmation
-
- click_button 'Save password'
- end
-
context 'User with password automatically set' do
describe 'User puts different passwords in the field and in the confirmation' do
it 'shows an error message' do
@@ -73,4 +75,64 @@ describe 'Profile > Password' do
end
end
end
+
+ context 'Change passowrd' do
+ before do
+ sign_in(user)
+ visit(edit_profile_password_path)
+ end
+
+ it 'does not change user passowrd without old one' do
+ page.within '.update-password' do
+ fill_passwords('22233344', '22233344')
+ end
+
+ page.within '.flash-container' do
+ expect(page).to have_content 'You must provide a valid current password'
+ end
+ end
+
+ it 'does not change password with invalid old password' do
+ page.within '.update-password' do
+ fill_in 'user_current_password', with: 'invalid'
+ fill_passwords('password', 'confirmation')
+ end
+
+ page.within '.flash-container' do
+ expect(page).to have_content 'You must provide a valid current password'
+ end
+ end
+
+ it 'changes user password' do
+ page.within '.update-password' do
+ fill_in "user_current_password", with: user.password
+ fill_passwords('22233344', '22233344')
+ end
+
+ expect(current_path).to eq new_user_session_path
+ end
+ end
+
+ context 'when password is expired' do
+ before do
+ sign_in(user)
+
+ user.update_attributes(password_expires_at: 1.hour.ago)
+ user.identities.delete
+ expect(user.ldap_user?).to eq false
+ end
+
+ it 'needs change user password' do
+ visit edit_profile_password_path
+
+ expect(current_path).to eq new_profile_password_path
+
+ fill_in :user_current_password, with: user.password
+ fill_in :user_password, with: '12345678'
+ fill_in :user_password_confirmation, with: '12345678'
+ click_button 'Set new password'
+
+ expect(current_path).to eq new_user_session_path
+ end
+ end
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
new file mode 100644
index 00000000000..0b5eacbe916
--- /dev/null
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe 'User edit profile' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ visit(profile_path)
+ end
+
+ it 'changes user profile' do
+ fill_in 'user_skype', with: 'testskype'
+ fill_in 'user_linkedin', with: 'testlinkedin'
+ fill_in 'user_twitter', with: 'testtwitter'
+ fill_in 'user_website_url', with: 'testurl'
+ fill_in 'user_location', with: 'Ukraine'
+ fill_in 'user_bio', with: 'I <3 GitLab'
+ fill_in 'user_organization', with: 'GitLab'
+ click_button 'Update profile settings'
+
+ expect(user.reload).to have_attributes(
+ skype: 'testskype',
+ linkedin: 'testlinkedin',
+ twitter: 'testtwitter',
+ website_url: 'testurl',
+ bio: 'I <3 GitLab',
+ organization: 'GitLab'
+ )
+
+ expect(find('#user_location').value).to eq 'Ukraine'
+ expect(page).to have_content('Profile was successfully updated')
+ end
+
+ context 'user avatar' do
+ before do
+ attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
+ click_button 'Update profile settings'
+ end
+
+ it 'changes user avatar' do
+ expect(page).to have_link('Remove avatar')
+
+ user.reload
+ expect(user.avatar).to be_instance_of AvatarUploader
+ expect(user.avatar.url).to eq "/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif"
+ end
+
+ it 'removes user avatar' do
+ click_link 'Remove avatar'
+
+ user.reload
+
+ expect(user.avatar?).to eq false
+ expect(page).not_to have_link('Remove avatar')
+ expect(page).to have_link('gravatar.com')
+ end
+ end
+end
diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb
new file mode 100644
index 00000000000..387584fef62
--- /dev/null
+++ b/spec/features/profiles/user_manages_applications_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'User manages applications' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ visit applications_profile_path
+ end
+
+ it 'manages applications' do
+ expect(page).to have_content 'Add new application'
+
+ fill_in :doorkeeper_application_name, with: 'test'
+ fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
+ click_on 'Save application'
+
+ expect(page).to have_content 'Application: test'
+ expect(page).to have_content 'Application Id'
+ expect(page).to have_content 'Secret'
+
+ click_on 'Edit'
+
+ expect(page).to have_content 'Edit application'
+ fill_in :doorkeeper_application_name, with: 'test_changed'
+ click_on 'Save application'
+
+ expect(page).to have_content 'test_changed'
+ expect(page).to have_content 'Application Id'
+ expect(page).to have_content 'Secret'
+
+ visit applications_profile_path
+
+ page.within '.oauth-applications' do
+ click_on 'Destroy'
+ end
+ expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
+ end
+end
diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
index a50ebb29e01..0f419c3c2c0 100644
--- a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
+++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
@@ -3,13 +3,28 @@ require 'spec_helper'
describe 'User visits the authentication log' do
let(:user) { create(:user) }
- before do
- sign_in(user)
+ context 'when user signed in' do
+ before do
+ sign_in(user)
+ end
- visit(audit_log_profile_path)
+ it 'shows correct menu item' do
+ visit(audit_log_profile_path)
+
+ expect(page).to have_active_navigation('Authentication log')
+ end
end
- it 'shows correct menu item' do
- expect(page).to have_active_navigation('Authentication log')
+ context 'when user has activity' do
+ before do
+ create(:closed_issue_event, author: user)
+ gitlab_sign_in(user)
+ end
+
+ it 'shows user activity' do
+ visit(audit_log_profile_path)
+
+ expect(page).to have_content 'Signed in with standard authentication'
+ end
end
end
diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb
index a5d80439143..713112477c8 100644
--- a/spec/features/profiles/user_visits_profile_spec.rb
+++ b/spec/features/profiles/user_visits_profile_spec.rb
@@ -5,20 +5,58 @@ describe 'User visits their profile' do
before do
sign_in(user)
-
- visit(profile_path)
end
it 'shows correct menu item' do
+ visit(profile_path)
+
expect(page).to have_active_navigation('Profile')
end
- describe 'profile settings', :js do
- it 'saves updates' do
- fill_in 'user_bio', with: 'bio'
- click_button 'Update profile settings'
+ it 'shows profile info' do
+ visit(profile_path)
+
+ expect(page).to have_content "This information will appear on your profile"
+ end
+
+ context 'when user has groups' do
+ let(:group) do
+ create :group do |group|
+ group.add_owner(user)
+ end
+ end
+
+ let!(:project) do
+ create(:project, :repository, namespace: group) do |project|
+ create(:closed_issue_event, project: project)
+ project.add_master(user)
+ end
+ end
+
+ def click_on_profile_picture
+ find(:css, '.header-user-dropdown-toggle').click
+
+ page.within ".header-user" do
+ click_link "Profile"
+ end
+ end
+
+ it 'shows user groups', :js do
+ visit(profile_path)
+ click_on_profile_picture
+
+ page.within ".cover-block" do
+ expect(page).to have_content user.name
+ expect(page).to have_content user.username
+ end
+
+ page.within ".content" do
+ click_link "Groups"
+ end
- expect(page).to have_content('Profile was successfully updated')
+ page.within "#groups" do
+ expect(page).to have_content group.name
+ end
end
end
end
diff --git a/spec/fixtures/api/schemas/variable.json b/spec/fixtures/api/schemas/variable.json
index 78977118b0a..6f6b044115b 100644
--- a/spec/fixtures/api/schemas/variable.json
+++ b/spec/fixtures/api/schemas/variable.json
@@ -10,7 +10,8 @@
"id": { "type": "integer" },
"key": { "type": "string" },
"value": { "type": "string" },
- "protected": { "type": "boolean" }
+ "protected": { "type": "boolean" },
+ "environment_scope": { "type": "string", "optional": true }
},
"additionalProperties": false
}
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 8a80b88da5d..fccde8b7eba 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -20,5 +20,9 @@ describe EventsHelper do
it 'handles nil values' do
expect(helper.event_commit_title(nil)).to eq('')
end
+
+ it 'does not escape HTML entities' do
+ expect(helper.event_commit_title("foo & bar")).to eq("foo & bar")
+ end
end
end
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 268b5b83b73..1c1b3f3ced3 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -79,7 +79,7 @@ import '~/lib/utils/common_utils';
return expect($emojiMenu.length).toBe(1);
});
});
- return it('should remove emoji menu when body is clicked', function(done) {
+ it('should remove emoji menu when body is clicked', function(done) {
$('.js-add-award').eq(0).click();
return lazyAssert(done, function() {
var $emojiMenu;
@@ -90,6 +90,17 @@ import '~/lib/utils/common_utils';
return expect($('.js-awards-block.current').length).toBe(0);
});
});
+ it('should not remove emoji menu when search is clicked', function(done) {
+ $('.js-add-award').eq(0).click();
+ return lazyAssert(done, function() {
+ var $emojiMenu;
+ $emojiMenu = $('.emoji-menu');
+ $('.emoji-search').click();
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(true);
+ return expect($('.js-awards-block.current').length).toBe(1);
+ });
+ });
});
describe('::addAwardToEmojiBar', function() {
it('should add emoji to votes block', function() {
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index 5b9cdceee71..ee457a9c48c 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -1,8 +1,10 @@
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import AjaxFormVariableList from '~/ci_variable_list/ajax_variable_list';
const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/variables';
+const HIDE_CLASS = 'hide';
describe('AjaxFormVariableList', () => {
preloadFixtures('projects/ci_cd_settings.html.raw');
@@ -45,16 +47,16 @@ describe('AjaxFormVariableList', () => {
const loadingIcon = saveButton.querySelector('.js-secret-variables-save-loading-icon');
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => {
- expect(loadingIcon.classList.contains('hide')).toEqual(false);
+ expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(false);
return [200, {}];
});
- expect(loadingIcon.classList.contains('hide')).toEqual(true);
+ expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(loadingIcon.classList.contains('hide')).toEqual(true);
+ expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true);
})
.then(done)
.catch(done.fail);
@@ -78,11 +80,11 @@ describe('AjaxFormVariableList', () => {
it('hides any previous error box', (done) => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200);
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
})
.then(done)
.catch(done.fail);
@@ -103,17 +105,39 @@ describe('AjaxFormVariableList', () => {
.catch(done.fail);
});
+ it('hides secret values', (done) => {
+ mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {});
+
+ const row = container.querySelector('.js-row:first-child');
+ const valueInput = row.querySelector('.js-ci-variable-input-value');
+ const valuePlaceholder = row.querySelector('.js-secret-value-placeholder');
+
+ valueInput.value = 'bar';
+ $(valueInput).trigger('input');
+
+ expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(true);
+ expect(valueInput.classList.contains(HIDE_CLASS)).toBe(false);
+
+ ajaxVariableList.onSaveClicked()
+ .then(() => {
+ expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(false);
+ expect(valueInput.classList.contains(HIDE_CLASS)).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
it('shows error box with validation errors', (done) => {
const validationError = 'some validation error';
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(400, [
validationError,
]);
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(errorBox.classList.contains('hide')).toEqual(false);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(false);
expect(errorBox.textContent.trim().replace(/\n+\s+/m, ' ')).toEqual(`Validation failed ${validationError}`);
})
.then(done)
@@ -123,11 +147,11 @@ describe('AjaxFormVariableList', () => {
it('shows flash message when request fails', (done) => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(500);
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked()
.then(() => {
- expect(errorBox.classList.contains('hide')).toEqual(true);
+ expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
})
.then(done)
.catch(done.fail);
@@ -170,9 +194,9 @@ describe('AjaxFormVariableList', () => {
const valueInput = row.querySelector('.js-ci-variable-input-value');
keyInput.value = 'foo';
- keyInput.dispatchEvent(new Event('input'));
+ $(keyInput).trigger('input');
valueInput.value = 'bar';
- valueInput.dispatchEvent(new Event('input'));
+ $(valueInput).trigger('input');
expect(idInput.value).toEqual('');
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 8acb346901f..cac785fd3c6 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -1,6 +1,8 @@
import VariableList from '~/ci_variable_list/ci_variable_list';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
+const HIDE_CLASS = 'hide';
+
describe('VariableList', () => {
preloadFixtures('pipeline_schedules/edit.html.raw');
preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
@@ -92,14 +94,14 @@ describe('VariableList', () => {
const $inputValue = $row.find('.js-ci-variable-input-value');
const $placeholder = $row.find('.js-secret-value-placeholder');
- expect($placeholder.hasClass('hide')).toBe(false);
- expect($inputValue.hasClass('hide')).toBe(true);
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(false);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(true);
// Reveal values
$wrapper.find('.js-secret-value-reveal-button').click();
- expect($placeholder.hasClass('hide')).toBe(true);
- expect($inputValue.hasClass('hide')).toBe(false);
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(true);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(false);
});
});
});
@@ -179,4 +181,35 @@ describe('VariableList', () => {
expect($wrapper.find('.js-ci-variable-input-key:not([disabled])').length).toBe(3);
});
});
+
+ describe('hideValues', () => {
+ beforeEach(() => {
+ loadFixtures('projects/ci_cd_settings.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'variables',
+ });
+ variableList.init();
+ });
+
+ it('should hide value input and show placeholder stars', () => {
+ const $row = $wrapper.find('.js-row');
+ const $inputValue = $row.find('.js-ci-variable-input-value');
+ const $placeholder = $row.find('.js-secret-value-placeholder');
+
+ $row.find('.js-ci-variable-input-value')
+ .val('foo')
+ .trigger('input');
+
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(true);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(false);
+
+ variableList.hideValues();
+
+ expect($placeholder.hasClass(HIDE_CLASS)).toBe(false);
+ expect($inputValue.hasClass(HIDE_CLASS)).toBe(true);
+ });
+ });
});
diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js
index 44ec9e4eabf..1daccc8dd02 100644
--- a/spec/javascripts/commits_spec.js
+++ b/spec/javascripts/commits_spec.js
@@ -4,6 +4,8 @@ import axios from '~/lib/utils/axios_utils';
import CommitsList from '~/commits';
describe('Commits List', () => {
+ let commitsList;
+
beforeEach(() => {
setFixtures(`
<form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master">
@@ -11,6 +13,7 @@ describe('Commits List', () => {
</form>
<ol id="commits-list"></ol>
`);
+ commitsList = new CommitsList(25);
});
it('should be defined', () => {
@@ -19,7 +22,7 @@ describe('Commits List', () => {
describe('processCommits', () => {
it('should join commit headers', () => {
- CommitsList.$contentList = $(`
+ commitsList.$contentList = $(`
<div>
<li class="commit-header" data-day="2016-09-20">
<span class="day">20 Sep, 2016</span>
@@ -39,7 +42,7 @@ describe('Commits List', () => {
// The last commit header should be removed
// since the previous one has the same data-day value.
- expect(CommitsList.processCommits(data).find('li.commit-header').length).toBe(0);
+ expect(commitsList.processCommits(data).find('li.commit-header').length).toBe(0);
});
});
@@ -48,8 +51,7 @@ describe('Commits List', () => {
let mock;
beforeEach(() => {
- CommitsList.init(25);
- CommitsList.searchField.val('');
+ commitsList.searchField.val('');
spyOn(history, 'replaceState').and.stub();
mock = new MockAdapter(axios);
@@ -66,11 +68,11 @@ describe('Commits List', () => {
});
it('should save the last search string', (done) => {
- CommitsList.searchField.val('GitLab');
- CommitsList.filterResults()
+ commitsList.searchField.val('GitLab');
+ commitsList.filterResults()
.then(() => {
expect(ajaxSpy).toHaveBeenCalled();
- expect(CommitsList.lastSearch).toEqual('GitLab');
+ expect(commitsList.lastSearch).toEqual('GitLab');
done();
})
@@ -78,10 +80,10 @@ describe('Commits List', () => {
});
it('should not make ajax call if the input does not change', (done) => {
- CommitsList.filterResults()
+ commitsList.filterResults()
.then(() => {
expect(ajaxSpy).not.toHaveBeenCalled();
- expect(CommitsList.lastSearch).toEqual('');
+ expect(commitsList.lastSearch).toEqual('');
done();
})
diff --git a/spec/javascripts/importer_status_spec.js b/spec/javascripts/importer_status_spec.js
index bb49c576e91..71a2cd51f63 100644
--- a/spec/javascripts/importer_status_spec.js
+++ b/spec/javascripts/importer_status_spec.js
@@ -3,9 +3,18 @@ import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
describe('Importer Status', () => {
+ let instance;
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
describe('addToImport', () => {
- let instance;
- let mock;
const importUrl = '/import_url';
beforeEach(() => {
@@ -21,11 +30,6 @@ describe('Importer Status', () => {
spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {});
spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {});
instance = new ImporterStatus('', importUrl);
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
});
it('sets table row to active after post request', (done) => {
@@ -44,4 +48,60 @@ describe('Importer Status', () => {
.catch(done.fail);
});
});
+
+ describe('autoUpdate', () => {
+ const jobsUrl = '/jobs_url';
+
+ beforeEach(() => {
+ const div = document.createElement('div');
+ div.innerHTML = `
+ <div id="project_1">
+ <div class="job-status">
+ </div>
+ </div>
+ `;
+
+ document.body.appendChild(div);
+
+ spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {});
+ spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {});
+ instance = new ImporterStatus(jobsUrl);
+ });
+
+ function setupMock(importStatus) {
+ mock.onGet(jobsUrl).reply(200, [{
+ id: 1,
+ import_status: importStatus,
+ }]);
+ }
+
+ function expectJobStatus(done, status) {
+ instance.autoUpdate()
+ .then(() => {
+ expect(document.querySelector('#project_1').innerText.trim()).toEqual(status);
+ done();
+ })
+ .catch(done.fail);
+ }
+
+ it('sets the job status to done', (done) => {
+ setupMock('finished');
+ expectJobStatus(done, 'done');
+ });
+
+ it('sets the job status to scheduled', (done) => {
+ setupMock('scheduled');
+ expectJobStatus(done, 'scheduled');
+ });
+
+ it('sets the job status to started', (done) => {
+ setupMock('started');
+ expectJobStatus(done, 'started');
+ });
+
+ it('sets the job status to custom status', (done) => {
+ setupMock('custom status');
+ expectJobStatus(done, 'custom status');
+ });
+ });
});
diff --git a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
index 040d14efed2..4655e29eed0 100644
--- a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js
+++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
-import IntervalPatternInput from '~/pipeline_schedules/components/interval_pattern_input.vue';
+import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
Vue.use(Translate);
diff --git a/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
index ed481cb60a1..f95a7cef18a 100644
--- a/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js
+++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import Cookies from 'js-cookie';
-import PipelineSchedulesCallout from '~/pipeline_schedules/components/pipeline_schedules_callout.vue';
+import PipelineSchedulesCallout from '~/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue';
const PipelineSchedulesCalloutComponent = Vue.extend(PipelineSchedulesCallout);
const cookieKey = 'pipeline_schedules_callout_dismissed';
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index cd00d0a39a3..45035effe81 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -295,6 +295,15 @@ describe('mrWidgetOptions', () => {
expect(notify.notifyMe).not.toHaveBeenCalled();
});
+
+ it('should not notify if no pipeline provided', () => {
+ vm.handleNotification({
+ ...data,
+ pipeline: undefined,
+ });
+
+ expect(notify.notifyMe).not.toHaveBeenCalled();
+ });
});
describe('resumePolling', () => {
diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/gl_modal_spec.js
new file mode 100644
index 00000000000..d6148cb785b
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/gl_modal_spec.js
@@ -0,0 +1,192 @@
+import Vue from 'vue';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const modalComponent = Vue.extend(GlModal);
+
+describe('GlModal', () => {
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('props', () => {
+ describe('with id', () => {
+ const props = {
+ id: 'my-modal',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('assigns the id to the modal', () => {
+ expect(vm.$el.id).toBe(props.id);
+ });
+ });
+
+ describe('without id', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, { });
+ });
+
+ it('does not add an id attribute to the modal', () => {
+ expect(vm.$el.hasAttribute('id')).toBe(false);
+ });
+ });
+
+ describe('with headerTitleText', () => {
+ const props = {
+ headerTitleText: 'my title text',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('sets the modal title', () => {
+ const modalTitle = vm.$el.querySelector('.modal-title');
+ expect(modalTitle.innerHTML.trim()).toBe(props.headerTitleText);
+ });
+ });
+
+ describe('with footerPrimaryButtonVariant', () => {
+ const props = {
+ footerPrimaryButtonVariant: 'danger',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('sets the primary button class', () => {
+ const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type');
+ expect(primaryButton).toHaveClass(`btn-${props.footerPrimaryButtonVariant}`);
+ });
+ });
+
+ describe('with footerPrimaryButtonText', () => {
+ const props = {
+ footerPrimaryButtonText: 'my button text',
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, props);
+ });
+
+ it('sets the primary button text', () => {
+ const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type');
+ expect(primaryButton.innerHTML.trim()).toBe(props.footerPrimaryButtonText);
+ });
+ });
+ });
+
+ it('works with data-toggle="modal"', (done) => {
+ setFixtures(`
+ <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
+ <div id="modal-container"></div>
+ `);
+
+ const modalContainer = document.getElementById('modal-container');
+ const modalButton = document.getElementById('modal-button');
+ vm = mountComponent(modalComponent, {
+ id: 'my-modal',
+ }, modalContainer);
+ $(vm.$el).on('shown.bs.modal', () => done());
+
+ modalButton.click();
+ });
+
+ describe('methods', () => {
+ const dummyEvent = 'not really an event';
+
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, { });
+ spyOn(vm, '$emit');
+ });
+
+ describe('emitCancel', () => {
+ it('emits a cancel event', () => {
+ vm.emitCancel(dummyEvent);
+
+ expect(vm.$emit).toHaveBeenCalledWith('cancel', dummyEvent);
+ });
+ });
+
+ describe('emitSubmit', () => {
+ it('emits a submit event', () => {
+ vm.emitSubmit(dummyEvent);
+
+ expect(vm.$emit).toHaveBeenCalledWith('submit', dummyEvent);
+ });
+ });
+ });
+
+ describe('slots', () => {
+ const slotContent = 'this should go into the slot';
+ const modalWithSlot = (slotName) => {
+ let template;
+ if (slotName) {
+ template = `
+ <gl-modal>
+ <template slot="${slotName}">${slotContent}</template>
+ </gl-modal>
+ `;
+ } else {
+ template = `<gl-modal>${slotContent}</gl-modal>`;
+ }
+
+ return Vue.extend({
+ components: {
+ GlModal,
+ },
+ template,
+ });
+ };
+
+ describe('default slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot());
+ });
+
+ it('sets the modal body', () => {
+ const modalBody = vm.$el.querySelector('.modal-body');
+ expect(modalBody.innerHTML).toBe(slotContent);
+ });
+ });
+
+ describe('header slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot('header'));
+ });
+
+ it('sets the modal header', () => {
+ const modalHeader = vm.$el.querySelector('.modal-header');
+ expect(modalHeader.innerHTML).toBe(slotContent);
+ });
+ });
+
+ describe('title slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot('title'));
+ });
+
+ it('sets the modal title', () => {
+ const modalTitle = vm.$el.querySelector('.modal-title');
+ expect(modalTitle.innerHTML).toBe(slotContent);
+ });
+ });
+
+ describe('footer slot', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalWithSlot('footer'));
+ });
+
+ it('sets the modal footer', () => {
+ const modalFooter = vm.$el.querySelector('.modal-footer');
+ expect(modalFooter.innerHTML).toBe(slotContent);
+ });
+ });
+ });
+});
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
index 91e18d876d5..1d98fc0d5db 100644
--- a/spec/lib/banzai/filter/html_entity_filter_spec.rb
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -3,17 +3,12 @@ require 'spec_helper'
describe Banzai::Filter::HtmlEntityFilter do
include FilterSpecHelper
- let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' }
- let(:escaped) { 'foo &lt;strike attr=&quot;foo&quot;&gt;&amp;&amp;&amp;&lt;/strike&gt;' }
+ let(:unescaped) { 'foo <strike attr="foo">&&amp;&</strike>' }
+ let(:escaped) { 'foo &lt;strike attr=&quot;foo&quot;&gt;&amp;&amp;amp;&amp;&lt;/strike&gt;' }
it 'converts common entities to their HTML-escaped equivalents' do
output = filter(unescaped)
expect(output).to eq(escaped)
end
-
- it 'does not double-escape' do
- escaped = ERB::Util.html_escape("Merge branch 'blabla' into 'master'")
- expect(filter(escaped)).to eq(escaped)
- end
end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index f668f78c2b8..2a0e19ae796 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -95,6 +95,14 @@ module Gitlab
expect(render(input, context)).to include('<p><code data-math-style="inline" class="code math js-render-math">2+2</code> is 4</p>')
end
end
+
+ context 'outfilesuffix' do
+ it 'defaults to adoc' do
+ output = render("Inter-document reference <<README.adoc#>>", context)
+
+ expect(output).to include("a href=\"README.adoc\"")
+ end
+ end
end
def render(*args)
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 28c679af12a..8d4862932b2 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -365,6 +365,20 @@ describe Gitlab::ClosingIssueExtractor do
.to match_array([issue, other_issue, third_issue])
end
+ it 'allows oxford commas (comma before and) when referencing multiple issues' do
+ message = "Closes #{reference}, #{reference2}, and #{reference3}"
+
+ expect(subject.closed_by_message(message))
+ .to match_array([issue, other_issue, third_issue])
+ end
+
+ it 'allows spaces before commas when referencing multiple issues' do
+ message = "Closes #{reference} , #{reference2} , and #{reference3}"
+
+ expect(subject.closed_by_message(message))
+ .to match_array([issue, other_issue, third_issue])
+ end
+
it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 3c3697e7aa9..19d3f55501e 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -18,8 +18,9 @@ describe Gitlab::GitAccess do
redirected_path: redirected_path)
end
- let(:push_access_check) { access.check('git-receive-pack', '_any') }
- let(:pull_access_check) { access.check('git-upload-pack', '_any') }
+ let(:changes) { '_any' }
+ let(:push_access_check) { access.check('git-receive-pack', changes) }
+ let(:pull_access_check) { access.check('git-upload-pack', changes) }
describe '#check with single protocols allowed' do
def disable_protocol(protocol)
@@ -646,6 +647,20 @@ describe Gitlab::GitAccess do
end
end
+ describe 'check LFS integrity' do
+ let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'checks LFS integrity only for first change' do
+ expect_any_instance_of(Gitlab::Checks::LfsIntegrity).to receive(:objects_missing?).exactly(1).times
+
+ push_access_check
+ end
+ end
+
describe '#check_push_access!' do
before do
merge_into_protected_branch
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index ca2213cd112..e10837578a8 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -5,6 +5,14 @@ describe Gitlab::LDAP::Config do
let(:config) { described_class.new('ldapmain') }
+ describe '.servers' do
+ it 'returns empty array if no server information is available' do
+ allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false)
+
+ expect(described_class.servers).to eq []
+ end
+ end
+
describe '#initialize' do
it 'requires a provider' do
expect { described_class.new }.to raise_error ArgumentError
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 03e0a9e2a03..b8455403bdb 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -724,6 +724,10 @@ describe Gitlab::OAuth::User do
it "does not update the user location" do
expect(gl_user.location).not_to eq(info_hash[:address][:country])
end
+
+ it 'does not create associated user synced attributes metadata' do
+ expect(gl_user.user_synced_attributes_metadata).to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 4a43dbb2371..f02b1cf55fb 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -53,6 +53,15 @@ describe Gitlab::Profiler do
described_class.profile('/', user: user)
end
+ context 'when providing a user without a personal access token' do
+ it 'raises an error' do
+ user = double(:user)
+ allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck).and_return([])
+
+ expect { described_class.profile('/', user: user) }.to raise_error('Your user must have a personal_access_token')
+ end
+ end
+
it 'uses the private_token for auth if both it and user are set' do
user = double(:user)
user_token = 'user'
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 7c66c98231b..a5ce245c21d 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -70,5 +70,38 @@ describe Identity do
end
end
end
+
+ context 'after_destroy' do
+ let!(:user) { create(:user) }
+ let(:ldap_identity) { create(:identity, provider: 'ldapmain', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', user: user) }
+ let(:ldap_user_synced_attributes) { { provider: 'ldapmain', name_synced: true, email_synced: true } }
+ let(:other_provider_user_synced_attributes) { { provider: 'other', name_synced: true, email_synced: true } }
+
+ describe 'if user synced attributes metadada provider' do
+ context 'matches the identity provider ' do
+ it 'removes the user synced attributes' do
+ user.create_user_synced_attributes_metadata(ldap_user_synced_attributes)
+
+ expect(user.user_synced_attributes_metadata.provider).to eq 'ldapmain'
+
+ ldap_identity.destroy
+
+ expect(user.reload.user_synced_attributes_metadata).to be_nil
+ end
+ end
+
+ context 'does not matche the identity provider' do
+ it 'does not remove the user synced attributes' do
+ user.create_user_synced_attributes_metadata(other_provider_user_synced_attributes)
+
+ expect(user.user_synced_attributes_metadata.provider).to eq 'other'
+
+ ldap_identity.destroy
+
+ expect(user.reload.user_synced_attributes_metadata.provider).to eq 'other'
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index a6d48e369ac..0bc07dc7a85 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -873,6 +873,18 @@ describe Repository do
expect(repository.license_key).to be_nil
end
+ it 'returns nil when the commit SHA does not exist' do
+ allow(repository.head_commit).to receive(:sha).and_return('1' * 40)
+
+ expect(repository.license_key).to be_nil
+ end
+
+ it 'returns nil when master does not exist' do
+ repository.rm_branch(user, 'master')
+
+ expect(repository.license_key).to be_nil
+ end
+
it 'returns the license key' do
repository.create_file(user, 'LICENSE',
Licensee::License.new('mit').content,
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 76a6aef39cc..1815696a8a0 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -893,6 +893,14 @@ describe User do
end
end
+ describe '.find_for_database_authentication' do
+ it 'strips whitespace from login' do
+ user = create(:user)
+
+ expect(described_class.find_for_database_authentication({ login: " #{user.username} " })).to eq user
+ end
+ end
+
describe '.find_by_any_email' do
it 'finds by primary email' do
user = create(:user, email: 'foo@example.com')
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 13db40d21a5..13bb3b92085 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -1380,7 +1380,7 @@ describe API::Issues, :mailer do
end
describe '/projects/:id/issues/:issue_iid/move' do
- let!(:target_project) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace ) }
+ let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) }
let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) }
it 'moves an issue' do
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
new file mode 100644
index 00000000000..987f6e26971
--- /dev/null
+++ b/spec/requests/api/project_import_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe API::ProjectImport do
+ let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
+ let(:user) { create(:user) }
+ let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
+ let(:namespace) { create(:group) }
+ before do
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+
+ namespace.add_owner(user)
+ end
+
+ after do
+ FileUtils.rm_rf(export_path, secure: true)
+ end
+
+ describe 'POST /projects/import' do
+ it 'schedules an import using a namespace' do
+ stub_import(namespace)
+
+ post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'schedules an import using the namespace path' do
+ stub_import(namespace)
+
+ post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'schedules an import at the user namespace level' do
+ stub_import(user.namespace)
+
+ post api('/projects/import', user), path: 'test-import2', file: fixture_file_upload(file)
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'schedules an import at the user namespace level' do
+ expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect(::Projects::CreateService).not_to receive(:new)
+
+ post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Namespace Not Found')
+ end
+
+ it 'does not schedule an import if the user has no permission to the namespace' do
+ expect_any_instance_of(Project).not_to receive(:import_schedule)
+
+ post(api('/projects/import', create(:user)),
+ path: 'test-import3',
+ file: fixture_file_upload(file),
+ namespace: namespace.full_path)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Namespace Not Found')
+ end
+
+ it 'does not schedule an import if the user uploads no valid file' do
+ expect_any_instance_of(Project).not_to receive(:import_schedule)
+
+ post api('/projects/import', user), path: 'test-import3', file: './random/test'
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('file is invalid')
+ end
+
+ def stub_import(namespace)
+ expect_any_instance_of(Project).to receive(:import_schedule)
+ expect(::Projects::CreateService).to receive(:new).with(user, hash_including(namespace_id: namespace.id)).and_call_original
+ end
+ end
+
+ describe 'GET /projects/:id/import' do
+ it 'returns the import status' do
+ project = create(:project, import_status: 'started')
+ project.add_master(user)
+
+ get api("/projects/#{project.id}/import", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include('import_status' => 'started')
+ end
+
+ it 'returns the import status and the error if failed' do
+ project = create(:project, import_status: 'failed', import_error: 'error')
+ project.add_master(user)
+
+ get api("/projects/#{project.id}/import", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include('import_status' => 'failed',
+ 'import_error' => 'error')
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 00dd8897e6a..cee93f6ed14 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -7,7 +7,7 @@ describe API::Projects do
let(:user3) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, namespace: user.namespace) }
- let(:project2) { create(:project, path: 'project2', namespace: user.namespace) }
+ let(:project2) { create(:project, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
@@ -315,7 +315,7 @@ describe API::Projects do
context 'and with all query parameters' do
let!(:project5) { create(:project, :public, path: 'gitlab5', namespace: create(:namespace)) }
- let!(:project6) { create(:project, :public, path: 'project6', namespace: user.namespace) }
+ let!(:project6) { create(:project, :public, namespace: user.namespace) }
let!(:project7) { create(:project, :public, path: 'gitlab7', namespace: user.namespace) }
let!(:project8) { create(:project, path: 'gitlab8', namespace: user.namespace) }
let!(:project9) { create(:project, :public, path: 'gitlab9') }
diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb
index 0dd6d673625..25b4c8438e4 100644
--- a/spec/requests/api/v3/issues_spec.rb
+++ b/spec/requests/api/v3/issues_spec.rb
@@ -1191,7 +1191,7 @@ describe API::V3::Issues, :mailer do
end
describe '/projects/:id/issues/:issue_id/move' do
- let!(:target_project) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace ) }
+ let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) }
let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) }
it 'moves an issue' do
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index bf36d3e245a..4c25bd935c6 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -6,7 +6,7 @@ describe API::V3::Projects do
let(:user3) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
+ let(:project2) { create(:project, creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 0fec14d0cce..b18e922b063 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -22,6 +22,7 @@ describe 'Rack Attack global throttles' do
let(:url_that_does_not_require_authentication) { '/users/sign_in' }
let(:url_that_requires_authentication) { '/dashboard/snippets' }
+ let(:url_api_internal) { '/api/v4/internal/check' }
let(:api_partial_url) { '/todos' }
around do |example|
@@ -172,6 +173,15 @@ describe 'Rack Attack global throttles' do
get url_that_does_not_require_authentication
expect(response).to have_http_status 200
end
+
+ context 'when the request is to the api internal endpoints' do
+ it 'allows requests over the rate limit' do
+ (1 + requests_per_period).times do
+ get url_api_internal, secret_token: Gitlab::Shell.secret_token
+ expect(response).to have_http_status 200
+ end
+ end
+ end
end
context 'when the throttle is disabled' do
diff --git a/spec/support/factory_girl.rb b/spec/support/factory_bot.rb
index c7890e49c66..c7890e49c66 100644
--- a/spec/support/factory_girl.rb
+++ b/spec/support/factory_bot.rb
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 4315bf5d037..0d8f7a7aae6 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -263,7 +263,7 @@ shared_examples 'variable list' do
# We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section') do
- expect(find('.js-ci-variable-error-box')).to have_content('Validation failed Variables Duplicate variables: samekey')
+ expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/)
end
end
end
diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb
index 128aaaf25fe..8854382dc6b 100644
--- a/spec/support/fixture_helpers.rb
+++ b/spec/support/fixture_helpers.rb
@@ -1,12 +1,12 @@
module FixtureHelpers
- def fixture_file(filename)
+ def fixture_file(filename, dir: '')
return '' if filename.blank?
- File.read(expand_fixture_path(filename))
+ File.read(expand_fixture_path(filename, dir: dir))
end
- def expand_fixture_path(filename)
- File.expand_path(Rails.root.join('spec/fixtures/', filename))
+ def expand_fixture_path(filename, dir: '')
+ File.expand_path(Rails.root.join(dir, 'spec', 'fixtures', filename))
end
end
diff --git a/spec/validators/variable_duplicates_validator_spec.rb b/spec/validators/variable_duplicates_validator_spec.rb
new file mode 100644
index 00000000000..0b71a67f94d
--- /dev/null
+++ b/spec/validators/variable_duplicates_validator_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe VariableDuplicatesValidator do
+ let(:validator) { described_class.new(attributes: [:variables], **options) }
+
+ describe '#validate_each' do
+ let(:project) { build(:project) }
+
+ subject { validator.validate_each(project, :variables, project.variables) }
+
+ context 'with no scope' do
+ let(:options) { {} }
+ let(:variables) { build_list(:ci_variable, 2, project: project) }
+
+ before do
+ project.variables << variables
+ end
+
+ it 'does not have any errors' do
+ subject
+
+ expect(project.errors.empty?).to be true
+ end
+
+ context 'with duplicates' do
+ before do
+ project.variables.build(key: variables.first.key, value: 'dummy_value')
+ end
+
+ it 'has a duplicate key error' do
+ subject
+
+ expect(project.errors).to have_key(:variables)
+ end
+ end
+ end
+
+ context 'with a scope attribute' do
+ let(:options) { { scope: :environment_scope } }
+ let(:first_variable) { build(:ci_variable, key: 'test_key', environment_scope: '*', project: project) }
+ let(:second_variable) { build(:ci_variable, key: 'test_key', environment_scope: 'prod', project: project) }
+
+ before do
+ project.variables << first_variable
+ project.variables << second_variable
+ end
+
+ it 'does not have any errors' do
+ subject
+
+ expect(project.errors.empty?).to be true
+ end
+
+ context 'with duplicates' do
+ before do
+ project.variables.build(key: second_variable.key, value: 'dummy_value', environment_scope: second_variable.environment_scope)
+ end
+
+ it 'has a duplicate key error' do
+ subject
+
+ expect(project.errors).to have_key(:variables)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 094d6791505..0ac8885405e 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -321,7 +321,15 @@ production:
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable"
SAST_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run --volume "$PWD:/code" \
+ # Deprecation notice for CONFIDENCE_LEVEL variable
+ if [ -z "$SAST_CONFIDENCE_LEVEL" -a "$CONFIDENCE_LEVEL" ]; then
+ SAST_CONFIDENCE_LEVEL="$CONFIDENCE_LEVEL"
+ echo "WARNING: CONFIDENCE_LEVEL is deprecated and MUST be replaced with SAST_CONFIDENCE_LEVEL"
+ fi
+
+ docker run --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}" \
+ --env SAST_DISABLE_REMOTE_CHECKS="${SAST_DISABLE_REMOTE_CHECKS:-false}" \
+ --volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
"registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
;;