summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--CHANGELOG.md54
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock4
-rw-r--r--Gemfile.rails5.lock2
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue4
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue4
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js2
-rw-r--r--app/assets/javascripts/commons/gitlab_ui.js24
-rw-r--r--app/assets/javascripts/diffs/components/app.vue10
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue12
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue8
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js2
-rw-r--r--app/assets/javascripts/environments/components/environment_monitoring.vue4
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js7
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js11
-rw-r--r--app/assets/javascripts/group.js12
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue6
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue2
-rw-r--r--app/assets/javascripts/notes.js6
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue6
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue7
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_artifacts.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue12
-rw-r--r--app/assets/javascripts/pipelines/components/time_ago.vue5
-rw-r--r--app/assets/javascripts/search_autocomplete.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue56
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue95
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue44
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue4
-rw-r--r--app/assets/stylesheets/framework/mixins.scss94
-rw-r--r--app/assets/stylesheets/pages/builds.scss78
-rw-r--r--app/controllers/application_controller.rb9
-rw-r--r--app/controllers/concerns/creates_commit.rb2
-rw-r--r--app/controllers/dashboard/milestones_controller.rb7
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb19
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb26
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb4
-rw-r--r--app/finders/personal_access_tokens_finder.rb2
-rw-r--r--app/helpers/compare_helper.rb2
-rw-r--r--app/helpers/merge_requests_helper.rb4
-rw-r--r--app/models/ci/build.rb4
-rw-r--r--app/models/clusters/cluster.rb28
-rw-r--r--app/models/clusters/group.rb10
-rw-r--r--app/models/commit_status.rb3
-rw-r--r--app/models/concerns/deployment_platform.rb1
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/concerns/redactable.rb33
-rw-r--r--app/models/concerns/token_authenticatable.rb55
-rw-r--r--app/models/concerns/token_authenticatable_strategies/base.rb69
-rw-r--r--app/models/concerns/token_authenticatable_strategies/digest.rb50
-rw-r--r--app/models/concerns/token_authenticatable_strategies/insecure.rb23
-rw-r--r--app/models/deployment.rb4
-rw-r--r--app/models/environment_status.rb41
-rw-r--r--app/models/global_milestone.rb44
-rw-r--r--app/models/group.rb5
-rw-r--r--app/models/lfs_object.rb4
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/milestone.rb18
-rw-r--r--app/models/note.rb3
-rw-r--r--app/models/personal_access_token.rb16
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/snippet.rb3
-rw-r--r--app/models/user.rb15
-rw-r--r--app/presenters/commit_status_presenter.rb3
-rw-r--r--app/presenters/merge_request_presenter.rb12
-rw-r--r--app/serializers/environment_status_entity.rb1
-rw-r--r--app/serializers/merge_request_widget_entity.rb1
-rw-r--r--app/services/clusters/create_service.rb4
-rw-r--r--app/services/delete_merged_branches_service.rb4
-rw-r--r--app/services/labels/transfer_service.rb2
-rw-r--r--app/services/merge_requests/get_urls_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb5
-rw-r--r--app/services/merge_requests/refresh_service.rb7
-rw-r--r--app/views/ci/variables/_index.html.haml4
-rw-r--r--app/views/groups/new.html.haml33
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/profiles/preferences/show.html.haml4
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml33
-rw-r--r--app/views/projects/pipelines/show.html.haml10
-rw-r--r--app/views/shared/_group_form.html.haml45
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml7
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--changelogs/unreleased/34758-create-group-clusters.yml5
-rw-r--r--changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml5
-rw-r--r--changelogs/unreleased/50962-create-new-group-rename-form-fields-and-update-ui.yml5
-rw-r--r--changelogs/unreleased/51527-xss-in-mr-source-branch.yml5
-rw-r--r--changelogs/unreleased/52122-fix-broken-whitespace-button.yml5
-rw-r--r--changelogs/unreleased/52383-ui-filter-assignee-none-any.yml5
-rw-r--r--changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml5
-rw-r--r--changelogs/unreleased/53155-structured-logs-params-array.yml5
-rw-r--r--changelogs/unreleased/53227-empty-list.yml6
-rw-r--r--changelogs/unreleased/53270-remove-mousetrap-rails.yml5
-rw-r--r--changelogs/unreleased/53273-update-moment-to-2-22-2.yml5
-rw-r--r--changelogs/unreleased/ac-post-merge-pipeline.yml5
-rw-r--r--changelogs/unreleased/add-failure-reason-for-execution-timeout.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml5
-rw-r--r--changelogs/unreleased/fix-53298.yml5
-rw-r--r--changelogs/unreleased/fl-missing-i18n.yml5
-rw-r--r--changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml5
-rw-r--r--changelogs/unreleased/rails5-deprecated-uniq.yml5
-rw-r--r--changelogs/unreleased/ravlen-rename-secret-variables-in-codebase.yml5
-rw-r--r--changelogs/unreleased/redact-links-dev.yml5
-rw-r--r--changelogs/unreleased/rz_fix_milestone_count.yml5
-rw-r--r--changelogs/unreleased/security-2717-fix-issue-title-xss.yml5
-rw-r--r--changelogs/unreleased/security-51113-hash_personal_access_tokens.yml5
-rw-r--r--changelogs/unreleased/sh-fix-hipchat-ssrf.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-53153.yml5
-rw-r--r--changelogs/unreleased/sh-fix-search-relative-urls.yml5
-rw-r--r--changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml5
-rw-r--r--changelogs/unreleased/tc-index-lfs-objects-file-store.yml5
-rw-r--r--changelogs/unreleased/winh-pipeline-actions-dynamic-timer.yml5
-rw-r--r--config/initializers/hipchat_client_patch.rb14
-rw-r--r--config/locales/en.yml2
-rw-r--r--config/routes.rb1
-rw-r--r--config/routes/project.rb5
-rw-r--r--db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb19
-rw-r--r--db/migrate/20180910153413_add_index_to_token_digest_on_personal_access_tokens.rb17
-rw-r--r--db/migrate/20181005110927_add_index_to_lfs_objects_file_store.rb17
-rw-r--r--db/migrate/20181014203236_create_cluster_groups.rb17
-rw-r--r--db/migrate/20181017001059_add_cluster_type_to_clusters.rb18
-rw-r--r--db/post_migrate/20180913142237_schedule_digest_personal_access_tokens.rb28
-rw-r--r--db/post_migrate/20181014121030_enqueue_redact_links.rb65
-rw-r--r--db/schema.rb18
-rw-r--r--doc/administration/high_availability/nfs.md19
-rw-r--r--doc/administration/high_availability/redis.md8
-rw-r--r--doc/administration/high_availability/redis_source.md4
-rw-r--r--doc/administration/logs.md2
-rw-r--r--doc/api/tags.md2
-rw-r--r--doc/ci/examples/container_scanning.md6
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md6
-rw-r--r--doc/development/testing_guide/review_apps.md6
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/topics/autodevops/index.md13
-rw-r--r--doc/topics/autodevops/quick_start_guide.md3
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/user/project/issue_board.md4
-rw-r--r--doc/user/project/pages/getting_started_part_three.md2
-rw-r--r--doc/user/search/img/issues_filter_none_any.pngbin0 -> 27717 bytes
-rw-r--r--doc/user/search/index.md10
-rw-r--r--lib/api/validations/types/safe_file.rb15
-rw-r--r--lib/api/wikis.rb4
-rw-r--r--lib/gitlab/auth.rb4
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb4
-rw-r--r--lib/gitlab/background_migration/digest_column.rb25
-rw-r--r--lib/gitlab/background_migration/redact_links.rb62
-rw-r--r--lib/gitlab/cache/ci/project_pipeline_status.rb8
-rw-r--r--lib/gitlab/ci/parsers/test/junit.rb8
-rw-r--r--lib/gitlab/ci/status/build/failed.rb3
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml2
-rw-r--r--lib/gitlab/contributions_calendar.rb2
-rw-r--r--lib/gitlab/crypto_helper.rb30
-rw-r--r--lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb10
-rw-r--r--lib/gitlab/url_blocker.rb7
-rw-r--r--lib/tasks/tokens.rake14
-rw-r--r--locale/gitlab.pot66
-rw-r--r--package.json2
-rw-r--r--qa/qa.rb4
-rw-r--r--qa/qa/factory/repository/push.rb2
-rw-r--r--qa/qa/factory/resource/ci_variable.rb (renamed from qa/qa/factory/resource/secret_variable.rb)8
-rw-r--r--qa/qa/page/project/issue/show.rb29
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb4
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb (renamed from qa/qa/page/project/settings/secret_variables.rb)6
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb34
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb32
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb (renamed from qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb)14
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb26
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb9
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb2
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb51
-rw-r--r--spec/controllers/uploads_controller_spec.rb25
-rw-r--r--spec/factories/clusters/clusters.rb15
-rw-r--r--spec/features/admin/admin_groups_spec.rb28
-rw-r--r--spec/features/boards/modal_filter_spec.rb2
-rw-r--r--spec/features/commits_spec.rb60
-rw-r--r--spec/features/dashboard/group_spec.rb8
-rw-r--r--spec/features/dashboard/projects_spec.rb8
-rw-r--r--spec/features/explore/new_menu_spec.rb4
-rw-r--r--spec/features/groups/milestone_spec.rb6
-rw-r--r--spec/features/groups_spec.rb14
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb12
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb15
-rw-r--r--spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb19
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb13
-rw-r--r--spec/features/merge_request/user_sees_wip_help_message_spec.rb4
-rw-r--r--spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb11
-rw-r--r--spec/features/merge_request/user_uses_quick_actions_spec.rb2
-rw-r--r--spec/features/merge_requests/user_squashes_merge_request_spec.rb4
-rw-r--r--spec/features/projects/files/user_creates_directory_spec.rb5
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb4
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb6
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_uploads_files_spec.rb4
-rw-r--r--spec/features/projects/merge_request_button_spec.rb8
-rw-r--r--spec/features/projects_spec.rb16
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json1
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js6
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js126
-rw-r--r--spec/javascripts/diffs/mock_data/merge_request_diffs.js42
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js1
-rw-r--r--spec/javascripts/job_spec.js265
-rw-r--r--spec/javascripts/pipelines/pipelines_actions_spec.js6
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js162
-rw-r--r--spec/javascripts/vue_mr_widget/components/review_app_link_spec.js38
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js188
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js22
-rw-r--r--spec/lib/gitlab/background_migration/digest_column_spec.rb46
-rw-r--r--spec/lib/gitlab/background_migration/redact_links_spec.rb96
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb22
-rw-r--r--spec/migrations/enqueue_redact_links_spec.rb42
-rw-r--r--spec/migrations/schedule_digest_personal_access_tokens_spec.rb46
-rw-r--r--spec/models/ci/build_spec.rb20
-rw-r--r--spec/models/clusters/cluster_spec.rb67
-rw-r--r--spec/models/clusters/group_spec.rb8
-rw-r--r--spec/models/concerns/redactable_spec.rb69
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb272
-rw-r--r--spec/models/environment_status_spec.rb32
-rw-r--r--spec/models/global_milestone_spec.rb35
-rw-r--r--spec/models/group_spec.rb18
-rw-r--r--spec/models/lfs_object_spec.rb19
-rw-r--r--spec/models/merge_request_spec.rb20
-rw-r--r--spec/models/milestone_spec.rb37
-rw-r--r--spec/models/personal_access_token_spec.rb28
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb18
-rw-r--r--spec/models/project_spec.rb12
-rw-r--r--spec/models/user_spec.rb8
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb9
-rw-r--r--spec/requests/api/internal_spec.rb6
-rw-r--r--spec/requests/api/runner_spec.rb9
-rw-r--r--spec/requests/api/wikis_spec.rb10
-rw-r--r--spec/serializers/environment_status_entity_spec.rb3
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb34
-rw-r--r--spec/services/groups/transfer_service_spec.rb2
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb4
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb60
-rw-r--r--spec/support/features/variable_list_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb2
-rw-r--r--vendor/jupyter/values.yaml2
-rw-r--r--yarn.lock15
267 files changed, 3471 insertions, 1135 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c3163b687b4..0d70eae0d1e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -265,7 +265,7 @@ package-and-qa:
SCRIPT_NAME: trigger-build-docs
environment:
name: review-docs/$CI_COMMIT_REF_SLUG
- # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
+ # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fc5b24aa39..4c99f6ed059 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,29 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.4.3 (2018-10-26)
+
+- No changes.
+
+## 11.4.2 (2018-10-25)
+
+### Security (5 changes)
+
+- Escape entity title while autocomplete template rendering to prevent XSS. !2571
+- Persist only SHA digest of PersonalAccessToken#token.
+- Redact personal tokens in unsubscribe links.
+- Block loopback addresses in UrlBlocker.
+- Validate Wiki attachments are valid temporary files.
+
+
+## 11.4.1 (2018-10-23)
+
+### Security (2 changes)
+
+- Fix XSS in merge request source branch name.
+- Prevent SSRF attacks in HipChat integration.
+
+
## 11.4.0 (2018-10-22)
### Security (9 changes)
@@ -227,6 +250,22 @@ entry.
- Check frozen string in style builds. (gfyoung)
+## 11.3.8 (2018-10-27)
+
+- No changes.
+
+## 11.3.7 (2018-10-26)
+
+### Security (6 changes)
+
+- Escape entity title while autocomplete template rendering to prevent XSS. !2557
+- Persist only SHA digest of PersonalAccessToken#token.
+- Fix XSS in merge request source branch name.
+- Redact personal tokens in unsubscribe links.
+- Prevent SSRF attacks in HipChat integration.
+- Validate Wiki attachments are valid temporary files.
+
+
## 11.3.6 (2018-10-17)
- No changes.
@@ -516,6 +555,21 @@ entry.
- Creates Vue component for artifacts block on job page.
+## 11.2.7 (2018-10-27)
+
+- No changes.
+
+## 11.2.6 (2018-10-26)
+
+### Security (5 changes)
+
+- Escape entity title while autocomplete template rendering to prevent XSS. !2558
+- Fix XSS in merge request source branch name.
+- Redact personal tokens in unsubscribe links.
+- Persist only SHA digest of PersonalAccessToken#token.
+- Prevent SSRF attacks in HipChat integration.
+
+
## 11.2.5 (2018-10-05)
### Security (3 changes)
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 6085e946503..f0bb29e7638 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.2.1
+1.3.0
diff --git a/Gemfile b/Gemfile
index d8c996cf6c4..6674edc1d0c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -244,9 +244,6 @@ gem 'rack-attack', '~> 4.4.1'
# Ace editor
gem 'ace-rails-ap', '~> 4.1.0'
-# Keyboard shortcuts
-gem 'mousetrap-rails', '~> 1.4.6'
-
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index e533b564d15..e755b0e0a8d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -463,7 +463,6 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.7.0)
- mousetrap-rails (1.4.6)
msgpack (1.2.4)
multi_json (1.13.1)
multi_xml (0.6.0)
@@ -547,7 +546,7 @@ GEM
orm_adapter (0.5.0)
os (1.0.0)
parallel (1.12.1)
- parser (2.5.1.2)
+ parser (2.5.3.0)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.0.1)
@@ -1046,7 +1045,6 @@ DEPENDENCIES
method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0)
- mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 5.0)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index b24911f3bb2..6ae7444f18f 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -466,7 +466,6 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.7.0)
- mousetrap-rails (1.4.6)
msgpack (1.2.4)
multi_json (1.13.1)
multi_xml (0.6.0)
@@ -1055,7 +1054,6 @@ DEPENDENCIES
method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0)
- mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 5.0)
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 030288a1c9d..ae2d1ee3c6e 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,6 +1,6 @@
<script>
import $ from 'jquery';
-import { Button } from '@gitlab-org/gitlab-ui';
+import { GlButton } from '@gitlab-org/gitlab-ui';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import ListIssue from '../models/issue';
@@ -10,7 +10,7 @@ export default {
name: 'BoardNewIssue',
components: {
ProjectSelect,
- 'gl-button': Button,
+ GlButton,
},
props: {
groupId: {
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 3baac08d411..20665f903d5 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -1,12 +1,12 @@
<script>
-import { Link } from '@gitlab-org/gitlab-ui';
+import { GlLink } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
import ModalStore from '../../stores/modal_store';
import boardsStore from '../../stores/boards_store';
export default {
components: {
- 'gl-link': Link,
+ GlLink,
Icon,
},
data() {
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 1089d0a72d3..c7a917457f3 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -47,7 +47,7 @@ export default class AjaxVariableList {
}
onSaveClicked() {
- const loadingIcon = this.saveButton.querySelector('.js-secret-variables-save-loading-icon');
+ const loadingIcon = this.saveButton.querySelector('.js-ci-variables-save-loading-icon');
loadingIcon.classList.toggle('hide', false);
this.errorBox.classList.toggle('hide', true);
// We use this to prevent a user from changing a key before we have a chance
diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js
index 1411f7ffd5e..f60665577fe 100644
--- a/app/assets/javascripts/commons/gitlab_ui.js
+++ b/app/assets/javascripts/commons/gitlab_ui.js
@@ -1,17 +1,17 @@
import Vue from 'vue';
import {
- Pagination,
- ProgressBar,
- Modal,
- LoadingIcon,
- ModalDirective,
- TooltipDirective,
+ GlPagination,
+ GlProgressBar,
+ GlModal,
+ GlLoadingIcon,
+ GlModalDirective,
+ GlTooltipDirective,
} from '@gitlab-org/gitlab-ui';
-Vue.component('gl-pagination', Pagination);
-Vue.component('gl-progress-bar', ProgressBar);
-Vue.component('gl-ui-modal', Modal);
-Vue.component('gl-loading-icon', LoadingIcon);
+Vue.component('gl-pagination', GlPagination);
+Vue.component('gl-progress-bar', GlProgressBar);
+Vue.component('gl-ui-modal', GlModal);
+Vue.component('gl-loading-icon', GlLoadingIcon);
-Vue.directive('gl-modal', ModalDirective);
-Vue.directive('gl-tooltip', TooltipDirective);
+Vue.directive('gl-modal', GlModalDirective);
+Vue.directive('gl-tooltip', GlTooltipDirective);
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index a8d615dd8f0..59680959bb1 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -153,13 +153,9 @@ export default {
},
setDiscussions() {
if (this.isNotesFetched && !this.assignedDiscussions && !this.isLoading) {
- requestIdleCallback(
- () =>
- this.assignDiscussionsToDiff().then(() => {
- this.assignedDiscussions = true;
- }),
- { timeout: 1000 },
- );
+ this.assignedDiscussions = true;
+
+ requestIdleCallback(() => this.assignDiscussionsToDiff(), { timeout: 1000 });
}
},
adjustView() {
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 9bbf62c0eb6..29b5aff0fb1 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -40,17 +40,14 @@ export default {
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
- isWhitespaceVisible() {
- return !getParameterValues('w')[0];
- },
toggleWhitespaceText() {
- if (this.isWhitespaceVisible) {
+ if (this.isWhitespaceVisible()) {
return __('Hide whitespace changes');
}
return __('Show whitespace changes');
},
toggleWhitespacePath() {
- if (this.isWhitespaceVisible) {
+ if (this.isWhitespaceVisible()) {
return mergeUrlParams({ w: 1 }, window.location.href);
}
@@ -67,6 +64,9 @@ export default {
'expandAllFiles',
'toggleShowTreeList',
]),
+ isWhitespaceVisible() {
+ return getParameterValues('w')[0] !== '1';
+ },
},
};
</script>
@@ -121,7 +121,7 @@ export default {
</a>
<a
:href="toggleWhitespacePath"
- class="btn btn-default"
+ class="btn btn-default qa-toggle-whitespace"
>
{{ toggleWhitespaceText }}
</a>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 34e836a570a..96e7bd63183 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -1,6 +1,6 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
-import { TooltipDirective as Tooltip } from '@gitlab-org/gitlab-ui';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
@@ -10,7 +10,7 @@ const treeListStorageKey = 'mr_diff_tree_list';
export default {
directives: {
- Tooltip,
+ GlTooltip: GlTooltipDirective,
},
components: {
Icon,
@@ -101,7 +101,7 @@ export default {
class="btn-group prepend-left-8 tree-list-view-toggle"
>
<button
- v-tooltip.hover
+ v-gl-tooltip.hover
:aria-label="__('List view')"
:title="__('List view')"
:class="{
@@ -116,7 +116,7 @@ export default {
/>
</button>
<button
- v-tooltip.hover
+ v-gl-tooltip.hover
:aria-label="__('Tree view')"
:title="__('Tree view')"
:class="{
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 5a8aebd2086..38a65f111a2 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -133,7 +133,7 @@ export default {
},
right: {
...line.right,
- discussions: right ? line.right.discussions.concat(discussion) : [],
+ discussions: right && !left ? line.right.discussions.concat(discussion) : [],
},
};
}
diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue
index a0797b594cb..26bec125445 100644
--- a/app/assets/javascripts/environments/components/environment_monitoring.vue
+++ b/app/assets/javascripts/environments/components/environment_monitoring.vue
@@ -2,14 +2,14 @@
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
-import { Button } from '@gitlab-org/gitlab-ui';
+import { GlButton } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
Icon,
- 'gl-button': Button,
+ GlButton,
},
directives: {
tooltip,
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
index b70125c80ca..e22f542b7bf 100644
--- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -58,11 +58,16 @@ export const alternativeTokenKeys = [
export const conditions = [
{
- url: 'assignee_id=0',
+ url: 'assignee_id=None',
tokenKey: 'assignee',
value: 'none',
},
{
+ url: 'assignee_id=Any',
+ tokenKey: 'assignee',
+ value: 'any',
+ },
+ {
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 95636a9ccdd..7dd0efd622d 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -201,7 +201,7 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.template;
+ tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
}
return tmpl;
},
@@ -267,7 +267,7 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.template;
+ tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
}
return tmpl;
},
@@ -370,7 +370,7 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.template;
+ tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
}
return tmpl;
},
@@ -557,8 +557,9 @@ GfmAutoComplete.Labels = {
};
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
- // eslint-disable-next-line no-template-curly-in-string
- template: '<li><small>${id}</small> ${title}</li>',
+ templateFunction(id, title) {
+ return `<li><small>${id}</small> ${_.escape(title)}</li>`;
+ },
};
// Milestones
GfmAutoComplete.Milestones = {
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
index 4365305c168..903c838e266 100644
--- a/app/assets/javascripts/group.js
+++ b/app/assets/javascripts/group.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { slugifyWithHyphens } from './lib/utils/text_utility';
export default class Group {
constructor() {
@@ -7,17 +8,18 @@ export default class Group {
this.updateHandler = this.update.bind(this);
this.resetHandler = this.reset.bind(this);
if (this.groupName.val() === '') {
- this.groupPath.on('keyup', this.updateHandler);
- this.groupName.on('keydown', this.resetHandler);
+ this.groupName.on('keyup', this.updateHandler);
+ this.groupPath.on('keydown', this.resetHandler);
}
}
update() {
- this.groupName.val(this.groupPath.val());
+ const slug = slugifyWithHyphens(this.groupName.val());
+ this.groupPath.val(slug);
}
reset() {
- this.groupPath.off('keyup', this.updateHandler);
- this.groupName.off('keydown', this.resetHandler);
+ this.groupName.off('keyup', this.updateHandler);
+ this.groupPath.off('keydown', this.resetHandler);
}
}
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index dc84ee12f1e..d4c430cd2f3 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -1,6 +1,6 @@
<script>
import { mapState, mapGetters } from 'vuex';
-import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
+import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui';
import IdeTree from './ide_tree.vue';
import ResizablePanel from './resizable_panel.vue';
import ActivityBar from './activity_bar.vue';
@@ -13,7 +13,7 @@ import { activityBarViews } from '../constants';
export default {
components: {
- SkeletonLoading,
+ GlSkeletonLoading,
ResizablePanel,
ActivityBar,
CommitSection,
@@ -50,7 +50,7 @@ export default {
:key="n"
class="multi-file-loading-container"
>
- <skeleton-loading />
+ <gl-skeleton-loading />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index e88f01fb4f4..d2ff55a4ee3 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -1,7 +1,7 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
-import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
+import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui';
import FileRow from '~/vue_shared/components/file_row.vue';
import NavDropdown from './nav_dropdown.vue';
import FileRowExtra from './file_row_extra.vue';
@@ -9,7 +9,7 @@ import FileRowExtra from './file_row_extra.vue';
export default {
components: {
Icon,
- SkeletonLoading,
+ GlSkeletonLoading,
NavDropdown,
FileRow,
},
@@ -51,7 +51,7 @@ export default {
:key="n"
class="multi-file-loading-container"
>
- <skeleton-loading />
+ <gl-skeleton-loading />
</div>
</template>
<template v-else>
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 0a2681b7a1e..b670b0355b7 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -25,7 +25,7 @@ export default {
...mapState('pipelines', ['isLoadingPipeline', 'latestPipeline', 'stages', 'isLoadingJobs']),
ciLintText() {
return sprintf(
- __('You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}'),
+ __('You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}.'),
{
linkStart: `<a href="${_.escape(this.currentProject.web_url)}/-/ci/lint">`,
linkEnd: '</a>',
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 1369b5820d5..90fe339e3de 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -16,7 +16,7 @@ import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import Vue from 'vue';
import syntaxHighlight from '~/syntax_highlight';
-import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
+import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui';
import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
@@ -1293,10 +1293,10 @@ export default class Notes {
new Vue({
el,
components: {
- SkeletonLoading,
+ GlSkeletonLoading,
},
render(createElement) {
- return createElement('skeleton-loading');
+ return createElement('gl-skeleton-loading');
},
});
}
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index d9e99603238..eaa0cded224 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -3,13 +3,13 @@ import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
-import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
+import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
export default {
components: {
DiffFileHeader,
- SkeletonLoading,
+ GlSkeletonLoading,
},
props: {
discussion: {
@@ -143,7 +143,7 @@ export default {
class="line_content js-success-lazy-load"
>
<span></span>
- <skeleton-loading />
+ <gl-skeleton-loading />
<span></span>
</td>
</tr>
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 27972682ca1..6e8f43048d1 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -22,9 +22,7 @@ export default {
return { currentValue: this.defaultValue };
},
computed: {
- ...mapGetters([
- 'getNotesDataByProp',
- ]),
+ ...mapGetters(['getNotesDataByProp']),
currentFilter() {
if (!this.currentValue) return this.filters[0];
return this.filters.find(filter => filter.value === this.currentValue);
@@ -51,7 +49,7 @@ export default {
<button
id="discussion-filter-dropdown"
ref="dropdownToggle"
- class="btn btn-default"
+ class="btn btn-default qa-discussion-filter"
data-toggle="dropdown"
aria-expanded="false"
>
@@ -69,6 +67,7 @@ export default {
>
<button
:class="{ 'is-active': filter.value === currentValue }"
+ class="qa-filter-options"
type="button"
@click="selectFilter(filter.value)"
>
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index d3b2656743d..ae0a8c74964 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -9,7 +9,7 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line no-new
new AjaxVariableList({
container: variableListEl,
- saveButton: variableListEl.querySelector('.js-secret-variables-save-button'),
+ saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
});
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 8f5ac3d8082..15c6fb550c1 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line no-new
new AjaxVariableList({
container: variableListEl,
- saveButton: variableListEl.querySelector('.js-secret-variables-save-button'),
+ saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
});
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 16e69759091..a7507fb3b6f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -1,16 +1,17 @@
<script>
import { s__, sprintf } from '~/locale';
-import { formatTime } from '~/lib/utils/datetime_utility';
import eventHub from '../event_hub';
-import icon from '../../vue_shared/components/icon.vue';
+import Icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
+import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
export default {
directives: {
tooltip,
},
components: {
- icon,
+ Icon,
+ GlCountdown,
},
props: {
actions: {
@@ -51,11 +52,6 @@ export default {
return !action.playable;
},
-
- remainingTime(action) {
- const remainingMilliseconds = new Date(action.scheduled_at).getTime() - Date.now();
- return formatTime(Math.max(0, remainingMilliseconds));
- },
},
};
</script>
@@ -100,7 +96,7 @@ export default {
class="pull-right"
>
<icon name="clock" />
- {{ remainingTime(action) }}
+ <gl-countdown :end-date-string="action.scheduled_at" />
</span>
</button>
</li>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
index d40de95e051..e0f0434e03d 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
@@ -1,13 +1,13 @@
<script>
import tooltip from '../../vue_shared/directives/tooltip';
-import icon from '../../vue_shared/components/icon.vue';
+import Icon from '../../vue_shared/components/icon.vue';
export default {
directives: {
tooltip,
},
components: {
- icon,
+ Icon,
},
props: {
artifacts: {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index cb14d4400d1..3339b5c13ed 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -88,25 +88,25 @@ export default {
class="table-section section-10 js-pipeline-status pipeline-status"
role="rowheader"
>
- Status
+ {{ s__('Pipeline|Status') }}
</div>
<div
class="table-section section-15 js-pipeline-info pipeline-info"
role="rowheader"
>
- Pipeline
+ {{ s__('Pipeline|Pipeline') }}
</div>
<div
class="table-section section-20 js-pipeline-commit pipeline-commit"
role="rowheader"
>
- Commit
+ {{ s__('Pipeline|Commit') }}
</div>
<div
class="table-section section-20 js-pipeline-stages pipeline-stages"
role="rowheader"
>
- Stages
+ {{ s__('Pipeline|Stages') }}
</div>
</div>
<pipelines-table-row-component
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 4f89ee66023..026d533d10f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -261,7 +261,7 @@ export default {
class="table-mobile-header"
role="rowheader"
>
- Status
+ {{ s__('Pipeline|Status') }}
</div>
<div class="table-mobile-content">
<ci-badge
@@ -279,8 +279,9 @@ export default {
<div class="table-section section-20">
<div
class="table-mobile-header"
- role="rowheader">
- Commit
+ role="rowheader"
+ >
+ {{ s__('Pipeline|Commit') }}
</div>
<div class="table-mobile-content">
<commit-component
@@ -298,8 +299,9 @@ export default {
<div class="table-section section-wrap section-20 stage-cell">
<div
class="table-mobile-header"
- role="rowheader">
- Stages
+ role="rowheader"
+ >
+ {{ s__('Pipeline|Stages') }}
</div>
<div class="table-mobile-content">
<template v-if="pipeline.details.stages.length > 0">
diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/time_ago.vue
index cd43d78de40..bed690200b8 100644
--- a/app/assets/javascripts/pipelines/components/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/time_ago.vue
@@ -60,7 +60,7 @@ export default {
class="table-mobile-header"
role="rowheader"
>
- Duration
+ {{ s__('Pipeline|Duration') }}
</div>
<div class="table-mobile-content">
<p
@@ -87,7 +87,8 @@ export default {
v-tooltip
:title="tooltipTitle(finishedTime)"
data-placement="top"
- data-container="body">
+ data-container="body"
+ >
{{ timeFormated(finishedTime) }}
</time>
</p>
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 7bde4860973..ad4f5320ff8 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -226,7 +226,7 @@ export class SearchAutocomplete {
icon,
text: term,
template: s__('SearchAutocomplete|in all GitLab'),
- url: `/search?search=${term}`,
+ url: `${gon.relative_url_root}/search?search=${term}`,
});
if (template) {
@@ -234,7 +234,7 @@ export class SearchAutocomplete {
icon,
text: term,
template,
- url: `/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`,
+ url: `${gon.relative_url_root}/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`,
});
}
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 6c87287a4c4..57c52a2016a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -2,6 +2,7 @@
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
+import { __ } from '~/locale';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import tooltip from '../../vue_shared/directives/tooltip';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
@@ -9,6 +10,7 @@ import { visitUrl } from '../../lib/utils/url_utility';
import createFlash from '../../flash';
import MemoryUsage from './memory_usage.vue';
import StatusIcon from './mr_widget_status_icon.vue';
+import ReviewAppLink from './review_app_link.vue';
import MRWidgetService from '../services/mr_widget_service';
export default {
@@ -20,6 +22,7 @@ export default {
Icon,
TooltipOnTruncate,
FilteredSearchDropdown,
+ ReviewAppLink,
},
directives: {
tooltip,
@@ -31,6 +34,11 @@ export default {
required: true,
},
},
+ deployedTextMap: {
+ running: __('Deploying to'),
+ success: __('Deployed to'),
+ failed: __('Failed to deploy to'),
+ },
data() {
const features = window.gon.features || {};
return {
@@ -54,10 +62,19 @@ export default {
hasMetrics() {
return !!this.deployment.metrics_url;
},
+ deployedText() {
+ return this.$options.deployedTextMap[this.deployment.status];
+ },
+ shouldRenderDropdown() {
+ return (
+ this.enableCiEnvironmentsStatusChanges &&
+ (this.deployment.changes && this.deployment.changes.length > 0)
+ );
+ },
},
methods: {
stopEnvironment() {
- const msg = 'Are you sure you want to stop this environment?';
+ const msg = __('Are you sure you want to stop this environment?');
const isConfirmed = confirm(msg); // eslint-disable-line
if (isConfirmed) {
@@ -87,10 +104,10 @@ export default {
<div class="ci-widget media">
<div class="media-body">
<div class="deploy-body">
- <div class="deployment-info">
+ <div class="js-deployment-info deployment-info">
<template v-if="hasDeploymentMeta">
<span>
- Deployed to
+ {{ deployedText }}
</span>
<tooltip-on-truncate
:title="deployment.name"
@@ -124,7 +141,7 @@ export default {
<div>
<template v-if="hasExternalUrls">
<filtered-search-dropdown
- v-if="enableCiEnvironmentsStatusChanges"
+ v-if="shouldRenderDropdown"
class="js-mr-wigdet-deployment-dropdown inline"
:items="deployment.changes"
:main-action-link="deployment.external_url"
@@ -134,18 +151,10 @@ export default {
slot="mainAction"
slot-scope="slotProps"
>
- <a
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="deploy-link js-deploy-url inline"
- :class="slotProps.className"
- >
- <span>
- {{ __('View app') }}
- <icon name="external-link" />
- </span>
- </a>
+ <review-app-link
+ :link="deployment.external_url"
+ :css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
+ />
</template>
<template
@@ -168,18 +177,11 @@ export default {
</a>
</template>
</filtered-search-dropdown>
- <a
+ <review-app-link
v-else
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
- >
- <span>
- {{ __('View app') }}
- <icon name="external-link" />
- </span>
- </a>
+ :link="deployment.external_url"
+ css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inlin"
+ />
</template>
<loading-button
v-if="deployment.stop_url"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue b/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue
new file mode 100644
index 00000000000..b007d4f4dcb
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue
@@ -0,0 +1,30 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ link: {
+ type: String,
+ required: true,
+ },
+ cssClass: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <a
+ :href="link"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ :class="cssClass"
+ >
+ {{ __('View app') }}
+ <icon name="external-link" />
+ </a>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 5d9f7cebcf2..a5c69d2bc7a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,4 +1,6 @@
<script>
+import _ from 'underscore';
+import { __ } from '~/locale';
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import createFlash from '../flash';
@@ -80,6 +82,7 @@ export default {
const service = this.createService(store);
return {
mr: store,
+ state: store.state,
service,
};
},
@@ -103,6 +106,17 @@ export default {
(!this.mr.isNothingToMergeState && !this.mr.isMergedState)
);
},
+ shouldRenderMergedPipeline() {
+ return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline);
+ },
+ },
+ watch: {
+ state(newVal, oldVal) {
+ if (newVal !== oldVal && this.shouldRenderMergedPipeline) {
+ // init polling
+ this.initPostMergeDeploymentsPolling();
+ }
+ }
},
created() {
this.initPolling();
@@ -112,11 +126,19 @@ export default {
mounted() {
this.setFaviconHelper();
this.initDeploymentsPolling();
+
+ if (this.shouldRenderMergedPipeline) {
+ this.initPostMergeDeploymentsPolling();
+ }
},
beforeDestroy() {
eventHub.$off('mr.discussion.updated', this.checkStatus);
this.pollingInterval.destroy();
this.deploymentsInterval.destroy();
+
+ if (this.postMergeDeploymentsInterval) {
+ this.postMergeDeploymentsInterval.destroy();
+ }
},
methods: {
createService(store) {
@@ -146,7 +168,13 @@ export default {
cb.call(null, data);
}
})
- .catch(() => createFlash('Something went wrong. Please try again.'));
+ .catch(() => createFlash(__('Something went wrong. Please try again.')));
+ },
+ setFaviconHelper() {
+ if (this.mr.ciStatusFaviconPath) {
+ return setFaviconOverlay(this.mr.ciStatusFaviconPath);
+ }
+ return Promise.resolve();
},
initPolling() {
this.pollingInterval = new SmartInterval({
@@ -158,8 +186,14 @@ export default {
});
},
initDeploymentsPolling() {
- this.deploymentsInterval = new SmartInterval({
- callback: this.fetchDeployments,
+ this.deploymentsInterval = this.deploymentsPoll(this.fetchPreMergeDeployments);
+ },
+ initPostMergeDeploymentsPolling() {
+ this.postMergeDeploymentsInterval = this.deploymentsPoll(this.fetchPostMergeDeployments);
+ },
+ deploymentsPoll(callback) {
+ return new SmartInterval({
+ callback,
startingInterval: 30000,
maxInterval: 120000,
hiddenInterval: 240000,
@@ -167,26 +201,29 @@ export default {
immediateExecution: true,
});
},
- setFaviconHelper() {
- if (this.mr.ciStatusFaviconPath) {
- return setFaviconOverlay(this.mr.ciStatusFaviconPath);
- }
- return Promise.resolve();
+ fetchDeployments(target) {
+ return this.service.fetchDeployments(target);
},
- fetchDeployments() {
- return this.service
- .fetchDeployments()
- .then(res => res.data)
- .then(data => {
+ fetchPreMergeDeployments() {
+ return this.fetchDeployments()
+ .then(({ data }) => {
if (data.length) {
this.mr.deployments = data;
}
})
- .catch(() => {
- createFlash(
- 'Something went wrong while fetching the environments for this merge request. Please try again.',
- );
- });
+ .catch(() => this.throwDeploymentsError());
+ },
+ fetchPostMergeDeployments(){
+ return this.fetchDeployments('merge_commit')
+ .then(({ data }) => {
+ if (data.length) {
+ this.mr.postMergeDeployments = data;
+ }
+ })
+ .catch(() => this.throwDeploymentsError());
+ },
+ throwDeploymentsError() {
+ createFlash(__('Something went wrong while fetching the environments for this merge request. Please try again.'));
},
fetchActionsContent() {
this.service
@@ -199,7 +236,7 @@ export default {
Project.initRefSwitcher();
}
})
- .catch(() => createFlash('Something went wrong. Please try again.'));
+ .catch(() => createFlash(__('Something went wrong. Please try again.')));
},
handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return;
@@ -267,7 +304,8 @@ export default {
/>
<deployment
v-for="deployment in mr.deployments"
- :key="deployment.id"
+ :key="`pre-merge-deploy-${deployment.id}`"
+ class="js-pre-merge-deploy"
:deployment="deployment"
/>
<div class="mr-section-container">
@@ -308,5 +346,22 @@ export default {
<mr-widget-merge-help />
</div>
</div>
+
+ <template v-if="shouldRenderMergedPipeline">
+ <mr-widget-pipeline
+ class="js-post-merge-pipeline prepend-top-default"
+ :pipeline="mr.mergePipeline"
+ :ci-status="mr.ciStatus"
+ :has-ci="mr.hasCI"
+ :source-branch="mr.targetBranch"
+ :source-branch-link="mr.targetBranch"
+ />
+ <deployment
+ v-for="postMergeDeployment in mr.postMergeDeployments"
+ :key="`post-merge-deploy-${postMergeDeployment.id}`"
+ :deployment="postMergeDeployment"
+ class="js-post-deployment"
+ />
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
index fecbfec2214..bf5b85b2ae6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
+++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
@@ -21,8 +21,12 @@ export default class MRWidgetService {
return axios.delete(this.endpoints.sourceBranchPath);
}
- fetchDeployments() {
- return axios.get(this.endpoints.ciEnvironmentsStatusPath);
+ fetchDeployments(targetParam) {
+ return axios.get(this.endpoints.ciEnvironmentsStatusPath, {
+ params: {
+ environment_target: targetParam
+ }
+ });
}
poll() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index e6655914700..a0c008e7314 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -32,7 +32,9 @@ export default class MergeRequestStore {
this.commitsCount = data.commits_count;
this.divergedCommitsCount = data.diverged_commits_count;
this.pipeline = data.pipeline || {};
+ this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || [];
+ this.postMergeDeployments = this.postMergeDeployments || [];
this.initRebase(data);
if (data.issues_links) {
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
index a07d63a495d..c78b96695cf 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
@@ -1,11 +1,11 @@
<script>
-import { Link } from '@gitlab-org/gitlab-ui';
+import { GlLink } from '@gitlab-org/gitlab-ui';
import Icon from '../../icon.vue';
import { numberToHumanSize } from '../../../../lib/utils/number_utils';
export default {
components: {
- 'gl-link': Link,
+ GlLink,
Icon,
},
props: {
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
index 807e049caf6..419987d2c50 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -2,14 +2,14 @@
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import $ from 'jquery';
-import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
+import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui';
const { CancelToken } = axios;
let axiosSource;
export default {
components: {
- SkeletonLoading,
+ GlSkeletonLoading,
},
props: {
content: {
@@ -81,7 +81,7 @@ export default {
<div
ref="markdown-preview"
class="md md-previewer">
- <skeleton-loading v-if="isLoading" />
+ <gl-skeleton-loading v-if="isLoading" />
<div
v-else
v-html="previewContent">
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index feb7b8f227e..b0a93794013 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -1,9 +1,9 @@
<script>
-import { Link } from '@gitlab-org/gitlab-ui';
+import { GlLink } from '@gitlab-org/gitlab-ui';
export default {
components: {
- 'gl-link': Link,
+ GlLink,
},
props: {
markdownDocsPath: {
diff --git a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
index 1d9c9220469..f56414c3c63 100644
--- a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
@@ -1,10 +1,10 @@
<script>
-import { SkeletonLoading } from '@gitlab-org/gitlab-ui';
+import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui';
export default {
name: 'SkeletonNote',
components: {
- SkeletonLoading,
+ GlSkeletonLoading,
},
};
</script>
@@ -17,7 +17,7 @@ export default {
<div class="timeline-content">
<div class="note-header"></div>
<div class="note-body">
- <skeleton-loading />
+ <gl-skeleton-loading />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
index 7f1eb6bcec4..11fac3bb12c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
@@ -1,6 +1,11 @@
<script>
+ import tooltip from '~/vue_shared/directives/tooltip';
+
export default {
name: 'CollapsedCalendarIcon',
+ directives: {
+ tooltip,
+ },
props: {
containerClass: {
type: String,
@@ -17,6 +22,11 @@
required: false,
default: true,
},
+ tooltipText: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
methods: {
click() {
@@ -28,7 +38,13 @@
<template>
<div
+ v-tooltip
:class="containerClass"
+ :title="tooltipText"
+ data-container="body"
+ data-placement="left"
+ data-html="true"
+ data-boundary="viewport"
@click="click"
>
<i
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
index dac438a702d..6e7194ccc9e 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
@@ -1,25 +1,23 @@
<script>
- import { dateInWords } from '../../../lib/utils/datetime_utility';
- import toggleSidebar from './toggle_sidebar.vue';
+ import { __ } from '~/locale';
+ import timeagoMixin from '~/vue_shared/mixins/timeago';
+ import { dateInWords, timeFor } from '~/lib/utils/datetime_utility';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
export default {
name: 'SidebarCollapsedGroupedDatePicker',
components: {
- toggleSidebar,
collapsedCalendarIcon,
},
+ mixins: [
+ timeagoMixin,
+ ],
props: {
collapsed: {
type: Boolean,
required: false,
default: true,
},
- showToggleSidebar: {
- type: Boolean,
- required: false,
- default: false,
- },
minDate: {
type: Date,
required: false,
@@ -51,7 +49,7 @@
},
iconClass() {
const disabledClass = this.disableClickableIcons ? 'disabled' : '';
- return `block sidebar-collapsed-icon calendar-icon ${disabledClass}`;
+ return `sidebar-collapsed-icon calendar-icon ${disabledClass}`;
},
},
methods: {
@@ -63,7 +61,21 @@
const dateWords = dateInWords(date, true);
const parsedDateWords = dateWords ? dateWords.replace(',', '') : dateWords;
- return date ? parsedDateWords : 'None';
+ return date ? parsedDateWords : __('None');
+ },
+ tooltipText(dateType = 'min') {
+ const defaultText = dateType === 'min' ? __('Start date') : __('Due date');
+ const date = this[`${dateType}Date`];
+ const timeAgo = dateType === 'min' ? this.timeFormated(date) : timeFor(date);
+ const dateText = date ? [
+ this.dateText(dateType),
+ `(${timeAgo})`,
+ ].join(' ') : '';
+
+ if (date) {
+ return [defaultText, dateText].join('<br />');
+ }
+ return __('Start and due date');
},
},
};
@@ -71,18 +83,10 @@
<template>
<div class="block sidebar-grouped-item">
- <div
- v-if="showToggleSidebar"
- class="issuable-sidebar-header"
- >
- <toggle-sidebar
- :collapsed="collapsed"
- @toggle="toggleSidebar"
- />
- </div>
<collapsed-calendar-icon
v-if="showMinDateBlock"
:container-class="iconClass"
+ :tooltip-text="tooltipText('min')"
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
@@ -99,7 +103,7 @@
<collapsed-calendar-icon
v-if="maxDate"
:container-class="iconClass"
- :show-icon="!minDate"
+ :tooltip-text="tooltipText('max')"
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
index 14cb44b8619..86c7498a092 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
@@ -17,14 +17,14 @@
*/
-import { Link } from '@gitlab-org/gitlab-ui';
+import { GlLink } from '@gitlab-org/gitlab-ui';
import userAvatarImage from './user_avatar_image.vue';
import tooltip from '../../directives/tooltip';
export default {
name: 'UserAvatarLink',
components: {
- 'gl-link': Link,
+ GlLink,
userAvatarImage,
},
directives: {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 1c84baf68ed..c030d75f5a4 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -250,6 +250,100 @@
max-width: 100%;
}
+/*
+* Mixin that handles the container for the job logs (CI/CD and kubernetes pod logs)
+*/
+@mixin build-trace {
+ background: $black;
+ color: $gray-darkest;
+ white-space: pre;
+ overflow-x: auto;
+ font-size: 12px;
+ border-radius: 0;
+ border: 0;
+ padding: $grid-size;
+
+ .bash {
+ display: block;
+ }
+
+ &.build-trace-rounded {
+ border-radius: $border-radius-base;
+ }
+}
+
+@mixin build-trace-top-bar($height) {
+ height: $height;
+ min-height: $height;
+ background: $gray-light;
+ border: 1px solid $border-color;
+ color: $gl-text-color;
+ position: sticky;
+ position: -webkit-sticky;
+ top: $header-height;
+ padding: $grid-size;
+
+ .with-performance-bar & {
+ top: $header-height + $performance-bar-height;
+ }
+}
+
+/*
+* Mixin that handles the position of the controls placed on the top bar
+*/
+@mixin build-controllers($control-font-size, $flex-direction, $with-grow, $flex-grow-size) {
+ display: flex;
+ font-size: $control-font-size;
+ justify-content: $flex-direction;
+ align-items: center;
+ align-self: baseline;
+ @if $with-grow {
+ flex-grow: $flex-grow-size;
+ }
+
+ svg {
+ width: 15px;
+ height: 15px;
+ display: block;
+ fill: $gl-text-color;
+ }
+
+ .controllers-buttons {
+ color: $gl-text-color;
+ margin: 0 $grid-size;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ .btn-scroll.animate {
+ .first-triangle {
+ animation: blinking-scroll-button 1s ease infinite;
+ animation-delay: 0.3s;
+ }
+
+ .second-triangle {
+ animation: blinking-scroll-button 1s ease infinite;
+ animation-delay: 0.2s;
+ }
+
+ .third-triangle {
+ animation: blinking-scroll-button 1s ease infinite;
+ }
+
+ &:disabled {
+ opacity: 1;
+ }
+ }
+
+ .btn-scroll:disabled,
+ .btn-refresh:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+ }
+}
+
@mixin build-loader-animation {
position: relative;
white-space: initial;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 227f49ec595..31b258e56dd 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -50,35 +50,13 @@
position: relative;
}
- .build-trace {
- background: $black;
- color: $gray-darkest;
- white-space: pre;
- overflow-x: auto;
- font-size: 12px;
- border-radius: 0;
- border: 0;
- padding: $grid-size;
-
- .bash {
- display: block;
- }
- &.build-trace-rounded {
- border-radius: $border-radius-base;
- }
+ .build-trace {
+ @include build-trace();
}
.top-bar {
- height: 35px;
- min-height: 35px;
- background: $gray-light;
- border: 1px solid $border-color;
- color: $gl-text-color;
- position: sticky;
- position: -webkit-sticky;
- top: $header-height;
- padding: $grid-size;
+ @include build-trace-top-bar(35px);
&.affix {
top: $header-height;
@@ -116,49 +94,7 @@
}
.controllers {
- display: flex;
- justify-content: center;
- align-items: center;
-
- svg {
- height: 15px;
- display: block;
- fill: $gl-text-color;
- }
-
- .controllers-buttons {
- color: $gl-text-color;
- margin: 0 $grid-size;
-
- &:last-child {
- margin-right: 0;
- }
- }
-
- .btn-scroll.animate {
- .first-triangle {
- animation: blinking-scroll-button 1s ease infinite;
- animation-delay: 0.3s;
- }
-
- .second-triangle {
- animation: blinking-scroll-button 1s ease infinite;
- animation-delay: 0.2s;
- }
-
- .third-triangle {
- animation: blinking-scroll-button 1s ease infinite;
- }
-
- &:disabled {
- opacity: 1;
- }
- }
-
- .btn-scroll:disabled {
- opacity: 0.35;
- cursor: not-allowed;
- }
+ @include build-controllers(15px, center, false, 0);
}
}
@@ -183,12 +119,8 @@
}
.with-performance-bar .build-page {
- .top-bar {
+ .top-bar.affix {
top: $header-height + $performance-bar-height;
-
- &.affix {
- top: $header-height + $performance-bar-height;
- }
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index eeabcc0c9bb..7f4aa8244ac 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -46,6 +46,8 @@ class ApplicationController < ActionController::Base
:git_import_enabled?, :gitlab_project_import_enabled?,
:manifest_import_enabled?
+ DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store".freeze
+
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
render "errors/encoding", layout: "errors", status: 500
@@ -244,6 +246,13 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
+
+ if current_user
+ # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
+ # concerns due to caching private data.
+ headers['Cache-Control'] = DEFAULT_GITLAB_CACHE_CONTROL
+ headers["Pragma"] = "no-cache" # HTTP 1.0 compatibility
+ end
end
def validate_user_service_ticket!
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index b3777fd2b0f..f644702cbdb 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -86,10 +86,10 @@ module CreatesCommit
def new_merge_request_path
project_new_merge_request_path(
@project_to_commit_into,
+ merge_request_source_branch: @branch_name,
merge_request: {
source_project_id: @project_to_commit_into.id,
target_project_id: @project.id,
- source_branch: @branch_name,
target_branch: @start_branch
}
)
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index 6e17bc212e4..3802aa5f40f 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -4,12 +4,13 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
include MilestoneActions
before_action :projects
+ before_action :groups, only: :index
before_action :milestone, only: [:show, :merge_requests, :participants, :labels]
def index
respond_to do |format|
format.html do
- @milestone_states = GlobalMilestone.states_count(@projects)
+ @milestone_states = Milestone.states_count(@projects.select(:id), @groups.select(:id))
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
@@ -42,4 +43,8 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
@milestone = DashboardMilestone.build(@projects, params[:title])
render_404 unless @milestone
end
+
+ def groups
+ @groups ||= GroupsFinder.new(current_user, state_all: true).execute
+ end
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index a7cee426cf1..b42116b0f36 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -10,7 +10,7 @@ class Groups::MilestonesController < Groups::ApplicationController
def index
respond_to do |format|
format.html do
- @milestone_states = GlobalMilestone.states_count(group_projects, group)
+ @milestone_states = Milestone.states_count(group_projects, [group])
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index 93f3eb2be6d..c1dcc463de7 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -7,7 +7,7 @@ module Groups
before_action :authorize_admin_pipeline!
def show
- define_secret_variables
+ define_ci_variables
end
def reset_registration_token
@@ -19,7 +19,7 @@ module Groups
private
- def define_secret_variables
+ def define_ci_variables
@variable = Ci::GroupVariable.new(group: group)
.present(current_user: current_user)
@variables = group.variables.order_key_asc
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b06a6f3bb0d..308f666394c 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -9,12 +9,25 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuesCalendar
include SpammableActions
- prepend_before_action :authenticate_user!, only: [:new]
+ def self.authenticate_user_only_actions
+ %i[new]
+ end
+
+ def self.issue_except_actions
+ %i[index calendar new create bulk_update]
+ end
+
+ def self.set_issuables_index_only_actions
+ %i[index calendar]
+ end
+
+ prepend_before_action :authenticate_user!, only: authenticate_user_only_actions
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
- before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update]
- before_action :set_issuables_index, only: [:index, :calendar]
+ before_action :issue, except: issue_except_actions
+
+ before_action :set_issuables_index, only: set_issuables_index_only_actions
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 5639402a1e9..bbf662a63c8 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -89,6 +89,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
+ params[:merge_request][:source_branch] ||= params[:merge_request_source_branch].presence
+
@merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 757b03d0b0e..27b83da4f54 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -168,7 +168,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def merge
- return access_denied! unless @merge_request.can_be_merged_by?(current_user)
+ access_check_result = merge_access_check
+
+ return access_check_result if access_check_result
status = merge!
@@ -201,9 +203,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def ci_environments_status
- environments = @merge_request.environments_for(current_user).map do |environment|
- EnvironmentStatus.new(environment, @merge_request)
- end
+ environments = if ci_environments_status_on_merge_result?
+ EnvironmentStatus.after_merge_request(@merge_request, current_user)
+ else
+ EnvironmentStatus.for_merge_request(@merge_request, current_user)
+ end
render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments)
end
@@ -241,6 +245,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
private
+ def ci_environments_status_on_merge_result?
+ params[:environment_target] == 'merge_commit'
+ end
+
def target_branch_missing?
@merge_request.has_no_commits? && !@merge_request.target_branch_exists?
end
@@ -256,6 +264,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
return :failed
end
+ merge_service = ::MergeRequests::MergeService.new(@project, current_user, merge_params)
+
+ unless merge_service.hooks_validation_pass?(@merge_request)
+ return :hook_validation_error
+ end
+
return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha
@merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
@@ -318,6 +332,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
access_denied! unless access_check
end
+ def merge_access_check
+ access_denied! unless @merge_request.can_be_merged_by?(current_user)
+ end
+
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42441
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42438')
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 3a1344651df..75e590f3f33 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -68,7 +68,7 @@ module Projects
def define_variables
define_runners_variables
- define_secret_variables
+ define_ci_variables
define_triggers_variables
define_badges_variables
define_auto_devops_variables
@@ -90,7 +90,7 @@ module Projects
@group_runners = ::Ci::Runner.belonging_to_parent_group_of_project(@project.id)
end
- def define_secret_variables
+ def define_ci_variables
@variable = ::Ci::Variable.new(project: project)
.present(current_user: current_user)
@variables = project.variables.order_key_asc
diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb
index 5beea92689f..81fd3b7a547 100644
--- a/app/finders/personal_access_tokens_finder.rb
+++ b/app/finders/personal_access_tokens_finder.rb
@@ -3,7 +3,7 @@
class PersonalAccessTokensFinder
attr_accessor :params
- delegate :build, :find, :find_by, to: :execute
+ delegate :build, :find, :find_by, :find_by_token, to: :execute
def initialize(params = {})
@params = params
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 9ece8b0bc5b..57e397f6ca0 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -13,8 +13,8 @@ module CompareHelper
def create_mr_path(from = params[:from], to = params[:to], project = @project)
project_new_merge_request_path(
project,
+ merge_request_source_branch: to,
merge_request: {
- source_branch: to,
target_branch: from
}
)
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 23d7aa427bb..8f549bfce73 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -11,10 +11,10 @@ module MergeRequestsHelper
def new_mr_from_push_event(event, target_project)
{
+ merge_request_source_branch: event.branch_name,
merge_request: {
source_project_id: event.project.id,
target_project_id: target_project.id,
- source_branch: event.branch_name,
target_branch: target_project.repository.root_ref
}
}
@@ -51,10 +51,10 @@ module MergeRequestsHelper
def mr_change_branches_path(merge_request)
project_new_merge_request_path(
@project,
+ merge_request_source_branch: merge_request.source_branch,
merge_request: {
source_project_id: merge_request.source_project_id,
target_project_id: merge_request.target_project_id,
- source_branch: merge_request.source_branch,
target_branch: merge_request.target_branch
},
change_branches: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index cdfe8175a42..d73c02ba5d7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -593,11 +593,11 @@ module Ci
def secret_group_variables
return [] unless project.group
- project.group.secret_variables_for(ref, project)
+ project.group.ci_variables_for(ref, project)
end
def secret_project_variables(environment: persisted_environment)
- project.secret_variables_for(ref: ref, environment: environment)
+ project.ci_variables_for(ref: ref, environment: environment)
end
def steps
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 95efecfc41d..222e4217e67 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -20,6 +20,12 @@ module Clusters
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
+ has_many :cluster_groups, class_name: 'Clusters::Group'
+ has_many :groups, through: :cluster_groups, class_name: '::Group'
+
+ has_one :cluster_group, -> { order(id: :desc) }, class_name: 'Clusters::Group'
+ has_one :group, through: :cluster_group, class_name: '::Group'
+
# we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
@@ -38,8 +44,12 @@ module Clusters
accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true
+ validates :cluster_type, presence: true
validate :restrict_modification, on: :update
+ validate :no_groups, unless: :group_type?
+ validate :no_projects, unless: :project_type?
+
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
@@ -50,6 +60,12 @@ module Clusters
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
+ enum cluster_type: {
+ instance_type: 1,
+ group_type: 2,
+ project_type: 3
+ }
+
enum platform_type: {
kubernetes: 1
}
@@ -122,5 +138,17 @@ module Clusters
true
end
+
+ def no_groups
+ if groups.any?
+ errors.add(:cluster, 'cannot have groups assigned')
+ end
+ end
+
+ def no_projects
+ if projects.any?
+ errors.add(:cluster, 'cannot have projects assigned')
+ end
+ end
end
end
diff --git a/app/models/clusters/group.rb b/app/models/clusters/group.rb
new file mode 100644
index 00000000000..2b08a9e47f0
--- /dev/null
+++ b/app/models/clusters/group.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Clusters
+ class Group < ActiveRecord::Base
+ self.table_name = 'cluster_groups'
+
+ belongs_to :cluster, class_name: 'Clusters::Cluster'
+ belongs_to :group, class_name: '::Group'
+ end
+end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 344f091c872..7d36f9982ab 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -50,7 +50,8 @@ class CommitStatus < ActiveRecord::Base
runner_system_failure: 4,
missing_dependency_failure: 5,
runner_unsupported: 6,
- stale_schedule: 7
+ stale_schedule: 7,
+ job_execution_timeout: 8
}
##
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 91052013592..e57a3383544 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -42,6 +42,7 @@ module DeploymentPlatform
{
name: 'kubernetes-template',
projects: [self],
+ cluster_type: :project_type,
provider_type: :user,
platform_type: :kubernetes,
platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 2aa52bbaeea..a808f9ad4ad 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -9,6 +9,7 @@
module Issuable
extend ActiveSupport::Concern
include Gitlab::SQL::Pattern
+ include Redactable
include CacheMarkdownField
include Participable
include Mentionable
@@ -32,6 +33,8 @@ module Issuable
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description, issuable_state_filter_enabled: true
+ redact_field :description
+
belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User"
belongs_to :last_edited_by, class_name: 'User'
diff --git a/app/models/concerns/redactable.rb b/app/models/concerns/redactable.rb
new file mode 100644
index 00000000000..5ad96d6cc46
--- /dev/null
+++ b/app/models/concerns/redactable.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+# This module searches and redacts sensitive information in
+# redactable fields. Currently only unsubscribe link is redacted.
+# Add following lines into your model:
+#
+# include Redactable
+# redact_field :foo
+#
+module Redactable
+ extend ActiveSupport::Concern
+
+ UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe}
+
+ class_methods do
+ def redact_field(field)
+ before_validation do
+ redact_field!(field) if attribute_changed?(field)
+ end
+ end
+ end
+
+ private
+
+ def redact_field!(field)
+ text = public_send(field) # rubocop:disable GitlabSecurity/PublicSend
+ return unless text.present?
+
+ redacted = text.gsub(UNSUBSCRIBE_PATTERN, '/sent_notifications/REDACTED/unsubscribe')
+
+ public_send("#{field}=", redacted) # rubocop:disable GitlabSecurity/PublicSend
+ end
+end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 522b65e4205..66db4bd92de 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -5,57 +5,50 @@ module TokenAuthenticatable
private
- def write_new_token(token_field)
- new_token = generate_available_token(token_field)
- write_attribute(token_field, new_token)
- end
-
- def generate_available_token(token_field)
- loop do
- token = generate_token(token_field)
- break token unless self.class.unscoped.find_by(token_field => token)
- end
- end
-
- def generate_token(token_field)
- Devise.friendly_token
- end
-
class_methods do
- def authentication_token_fields
- @token_fields || []
- end
-
private # rubocop:disable Lint/UselessAccessModifier
- def add_authentication_token_field(token_field)
+ def add_authentication_token_field(token_field, options = {})
@token_fields = [] unless @token_fields
+
+ if @token_fields.include?(token_field)
+ raise ArgumentError.new("#{token_field} already configured via add_authentication_token_field")
+ end
+
@token_fields << token_field
+ attr_accessor :cleartext_tokens
+
+ strategy = if options[:digest]
+ TokenAuthenticatableStrategies::Digest.new(self, token_field, options)
+ else
+ TokenAuthenticatableStrategies::Insecure.new(self, token_field, options)
+ end
+
define_singleton_method("find_by_#{token_field}") do |token|
- find_by(token_field => token) if token
+ strategy.find_token_authenticatable(token)
end
- define_method("ensure_#{token_field}") do
- current_token = read_attribute(token_field)
- current_token.blank? ? write_new_token(token_field) : current_token
+ define_method(token_field) do
+ strategy.get_token(self)
end
define_method("set_#{token_field}") do |token|
- write_attribute(token_field, token) if token
+ strategy.set_token(self, token)
+ end
+
+ define_method("ensure_#{token_field}") do
+ strategy.ensure_token(self)
end
# Returns a token, but only saves when the database is in read & write mode
define_method("ensure_#{token_field}!") do
- send("reset_#{token_field}!") if read_attribute(token_field).blank? # rubocop:disable GitlabSecurity/PublicSend
-
- read_attribute(token_field)
+ strategy.ensure_token!(self)
end
# Resets the token, but only saves when the database is in read & write mode
define_method("reset_#{token_field}!") do
- write_new_token(token_field)
- save! if Gitlab::Database.read_write?
+ strategy.reset_token!(self)
end
end
end
diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb
new file mode 100644
index 00000000000..f0f7107d627
--- /dev/null
+++ b/app/models/concerns/token_authenticatable_strategies/base.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module TokenAuthenticatableStrategies
+ class Base
+ def initialize(klass, token_field, options)
+ @klass = klass
+ @token_field = token_field
+ @options = options
+ end
+
+ def find_token_authenticatable(instance, unscoped = false)
+ raise NotImplementedError
+ end
+
+ def get_token(instance)
+ raise NotImplementedError
+ end
+
+ def set_token(instance)
+ raise NotImplementedError
+ end
+
+ def ensure_token(instance)
+ write_new_token(instance) unless token_set?(instance)
+ end
+
+ # Returns a token, but only saves when the database is in read & write mode
+ def ensure_token!(instance)
+ reset_token!(instance) unless token_set?(instance)
+ get_token(instance)
+ end
+
+ # Resets the token, but only saves when the database is in read & write mode
+ def reset_token!(instance)
+ write_new_token(instance)
+ instance.save! if Gitlab::Database.read_write?
+ end
+
+ protected
+
+ def write_new_token(instance)
+ new_token = generate_available_token
+ set_token(instance, new_token)
+ end
+
+ def generate_available_token
+ loop do
+ token = generate_token
+ break token unless find_token_authenticatable(token, true)
+ end
+ end
+
+ def generate_token
+ @options[:token_generator] ? @options[:token_generator].call : Devise.friendly_token
+ end
+
+ def relation(unscoped)
+ unscoped ? @klass.unscoped : @klass
+ end
+
+ def token_set?(instance)
+ raise NotImplementedError
+ end
+
+ def token_field_name
+ @token_field
+ end
+ end
+end
diff --git a/app/models/concerns/token_authenticatable_strategies/digest.rb b/app/models/concerns/token_authenticatable_strategies/digest.rb
new file mode 100644
index 00000000000..9926662ed66
--- /dev/null
+++ b/app/models/concerns/token_authenticatable_strategies/digest.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module TokenAuthenticatableStrategies
+ class Digest < Base
+ def find_token_authenticatable(token, unscoped = false)
+ return unless token
+
+ token_authenticatable = relation(unscoped).find_by(token_field_name => Gitlab::CryptoHelper.sha256(token))
+
+ if @options[:fallback]
+ token_authenticatable ||= fallback_strategy.find_token_authenticatable(token)
+ end
+
+ token_authenticatable
+ end
+
+ def get_token(instance)
+ token = instance.cleartext_tokens&.[](@token_field)
+ token ||= fallback_strategy.get_token(instance) if @options[:fallback]
+
+ token
+ end
+
+ def set_token(instance, token)
+ return unless token
+
+ instance.cleartext_tokens ||= {}
+ instance.cleartext_tokens[@token_field] = token
+ instance[token_field_name] = Gitlab::CryptoHelper.sha256(token)
+ instance[@token_field] = nil if @options[:fallback]
+ end
+
+ protected
+
+ def fallback_strategy
+ @fallback_strategy ||= TokenAuthenticatableStrategies::Insecure.new(@klass, @token_field, @options)
+ end
+
+ def token_set?(instance)
+ token_digest = instance.read_attribute(token_field_name)
+ token_digest ||= instance.read_attribute(@token_field) if @options[:fallback]
+
+ token_digest.present?
+ end
+
+ def token_field_name
+ "#{@token_field}_digest"
+ end
+ end
+end
diff --git a/app/models/concerns/token_authenticatable_strategies/insecure.rb b/app/models/concerns/token_authenticatable_strategies/insecure.rb
new file mode 100644
index 00000000000..5f915259521
--- /dev/null
+++ b/app/models/concerns/token_authenticatable_strategies/insecure.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module TokenAuthenticatableStrategies
+ class Insecure < Base
+ def find_token_authenticatable(token, unscoped = false)
+ relation(unscoped).find_by(@token_field => token) if token
+ end
+
+ def get_token(instance)
+ instance.read_attribute(@token_field)
+ end
+
+ def set_token(instance, token)
+ instance[@token_field] = token if token
+ end
+
+ protected
+
+ def token_set?(instance)
+ instance.read_attribute(@token_field).present?
+ end
+ end
+end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 62dc0f2cbeb..ee5b96e7454 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -127,6 +127,10 @@ class Deployment < ActiveRecord::Base
metrics&.merge(deployment_time: created_at.to_i) || {}
end
+ def status
+ 'success'
+ end
+
private
def prometheus_adapter
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
index 5ff3acc0e58..a84871f7253 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -3,21 +3,33 @@
class EnvironmentStatus
include Gitlab::Utils::StrongMemoize
- attr_reader :environment, :merge_request
+ attr_reader :environment, :merge_request, :sha
delegate :id, to: :environment
delegate :name, to: :environment
delegate :project, to: :environment
delegate :deployed_at, to: :deployment, allow_nil: true
+ delegate :status, to: :deployment
- def initialize(environment, merge_request)
+ def self.for_merge_request(mr, user)
+ build_environments_status(mr, user, mr.head_pipeline)
+ end
+
+ def self.after_merge_request(mr, user)
+ return [] unless mr.merged?
+
+ build_environments_status(mr, user, mr.merge_pipeline)
+ end
+
+ def initialize(environment, merge_request, sha)
@environment = environment
@merge_request = merge_request
+ @sha = sha
end
def deployment
strong_memoize(:deployment) do
- environment.first_deployment_for(merge_request.diff_head_sha)
+ environment.first_deployment_for(sha)
end
end
@@ -26,10 +38,9 @@ class EnvironmentStatus
end
def changes
- sha = merge_request.diff_head_sha
return [] if project.route_map_for(sha).nil?
- changed_files.map { |file| build_change(file, sha) }.compact
+ changed_files.map { |file| build_change(file) }.compact
end
def changed_files
@@ -41,7 +52,7 @@ class EnvironmentStatus
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
- def build_change(file, sha)
+ def build_change(file)
public_path = project.public_path_for_source_path(file.new_path, sha)
return if public_path.nil?
@@ -53,4 +64,22 @@ class EnvironmentStatus
external_url: environment.external_url_for(file.new_path, sha)
}
end
+
+ def self.build_environments_status(mr, user, pipeline)
+ return [] unless pipeline.present?
+
+ find_environments(user, pipeline).map do |environment|
+ EnvironmentStatus.new(environment, mr, pipeline.sha)
+ end
+ end
+ private_class_method :build_environments_status
+
+ def self.find_environments(user, pipeline)
+ env_ids = Deployment.where(deployable: pipeline.builds).select(:environment_id)
+
+ Environment.available.where(id: env_ids).select do |environment|
+ Ability.allowed?(user, :read_environment, environment)
+ end
+ end
+ private_class_method :find_environments
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index a6cebabe089..085ffd16c6a 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -34,50 +34,6 @@ class GlobalMilestone
new(title, child_milestones)
end
- def self.states_count(projects, group = nil)
- legacy_group_milestones_count = legacy_group_milestone_states_count(projects)
- group_milestones_count = group_milestones_states_count(group)
-
- legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count|
- legacy_group_milestones_count + group_milestones_count
- end
- end
-
- def self.group_milestones_states_count(group)
- return STATE_COUNT_HASH unless group
-
- params = { group_ids: [group.id], state: 'all' }
-
- relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder
- grouped_by_state = relation.reorder(nil).group(:state).count
-
- {
- opened: grouped_by_state['active'] || 0,
- closed: grouped_by_state['closed'] || 0,
- all: grouped_by_state.values.sum
- }
- end
-
- # Counts the legacy group milestones which must be grouped by title
- def self.legacy_group_milestone_states_count(projects)
- return STATE_COUNT_HASH unless projects
-
- params = { project_ids: projects.map(&:id), state: 'all' }
-
- relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder
- project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
-
- opened = count_by_state(project_milestones_by_state_and_title, 'active')
- closed = count_by_state(project_milestones_by_state_and_title, 'closed')
- all = project_milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
-
- {
- opened: opened,
- closed: closed,
- all: all
- }
- end
-
def self.count_by_state(milestones_by_state_and_title, state)
milestones_by_state_and_title.count do |(milestone_state, _), _|
milestone_state == state
diff --git a/app/models/group.rb b/app/models/group.rb
index 612c546ca57..adb9169cfcd 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -41,6 +41,9 @@ class Group < Namespace
has_many :boards
has_many :badges, class_name: 'GroupBadge'
+ has_many :cluster_groups, class_name: 'Clusters::Group'
+ has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster'
+
has_many :todos
accepts_nested_attributes_for :variables, allow_destroy: true
@@ -366,7 +369,7 @@ class Group < Namespace
}
end
- def secret_variables_for(ref, project)
+ def ci_variables_for(ref, project)
list_of_ids = [self] + ancestors
variables = Ci::GroupVariable.where(group: list_of_ids)
variables = variables.unprotected unless project.protected_for?(ref)
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 97bf5d611c2..69c563545bb 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -7,7 +7,7 @@ class LfsObject < ActiveRecord::Base
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :lfs_objects_projects
- scope :with_files_stored_locally, -> { where(file_store: [nil, LfsObjectUploader::Store::LOCAL]) }
+ scope :with_files_stored_locally, -> { where(file_store: LfsObjectUploader::Store::LOCAL) }
validates :oid, presence: true, uniqueness: true
@@ -26,7 +26,7 @@ class LfsObject < ActiveRecord::Base
end
def local_store?
- [nil, LfsObjectUploader::Store::LOCAL].include?(self.file_store)
+ file_store == LfsObjectUploader::Store::LOCAL
end
# rubocop: disable DestroyAll
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 6559f94a696..7eef08aa6a3 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -204,6 +204,12 @@ class MergeRequest < ActiveRecord::Base
head_pipeline&.sha == diff_head_sha ? head_pipeline : nil
end
+ def merge_pipeline
+ return unless merged?
+
+ target_project.pipeline_for(target_branch, merge_commit_sha)
+ end
+
# Pattern used to extract `!123` merge request references from text
#
# This pattern supports cross-project references.
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 892a680f221..3cc8e2c44bb 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -145,7 +145,7 @@ class Milestone < ActiveRecord::Base
end
def participants
- User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).uniq
+ User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).distinct
end
def self.sort_by_attribute(method)
@@ -170,6 +170,22 @@ class Milestone < ActiveRecord::Base
sorted.with_order_id_desc
end
+ def self.states_count(projects, groups = nil)
+ return STATE_COUNT_HASH unless projects || groups
+
+ counts = Milestone
+ .for_projects_and_groups(projects&.map(&:id), groups&.map(&:id))
+ .reorder(nil)
+ .group(:state)
+ .count
+
+ {
+ opened: counts['active'] || 0,
+ closed: counts['closed'] || 0,
+ all: counts.values.sum
+ }
+ end
+
##
# Returns the String necessary to reference this Milestone in Markdown. Group
# milestones only support name references, and do not support cross-project
diff --git a/app/models/note.rb b/app/models/note.rb
index e1bd943e8e4..990689a95f5 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -10,6 +10,7 @@ class Note < ActiveRecord::Base
include Awardable
include Importable
include FasterCacheKeys
+ include Redactable
include CacheMarkdownField
include AfterCommitQueue
include ResolvableNote
@@ -33,6 +34,8 @@ class Note < ActiveRecord::Base
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
+ redact_field :note
+
# Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10392/diffs#note_28719102
alias_attribute :last_edited_at, :updated_at
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 207146479c0..73a58f2420e 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,7 +3,7 @@
class PersonalAccessToken < ActiveRecord::Base
include Expirable
include TokenAuthenticatable
- add_authentication_token_field :token
+ add_authentication_token_field :token, digest: true, fallback: true
REDIS_EXPIRY_TIME = 3.minutes
@@ -33,16 +33,22 @@ class PersonalAccessToken < ActiveRecord::Base
def self.redis_getdel(user_id)
Gitlab::Redis::SharedState.with do |redis|
- token = redis.get(redis_shared_state_key(user_id))
+ encrypted_token = redis.get(redis_shared_state_key(user_id))
redis.del(redis_shared_state_key(user_id))
- token
+ begin
+ Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token)
+ rescue => ex
+ logger.warn "Failed to decrypt PersonalAccessToken value stored in Redis for User ##{user_id}: #{ex.class}"
+ encrypted_token
+ end
end
end
def self.redis_store!(user_id, token)
+ encrypted_token = Gitlab::CryptoHelper.aes256_gcm_encrypt(token)
+
Gitlab::Redis::SharedState.with do |redis|
- redis.set(redis_shared_state_key(user_id), token, ex: REDIS_EXPIRY_TIME)
- token
+ redis.set(redis_shared_state_key(user_id), encrypted_token, ex: REDIS_EXPIRY_TIME)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 382fb4f463a..d593cbb223a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1811,7 +1811,7 @@ class Project < ActiveRecord::Base
.first
end
- def secret_variables_for(ref:, environment: nil)
+ def ci_variables_for(ref:, environment: nil)
# EE would use the environment
if protected_for?(ref)
variables
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index e9533ee7c77..1c5846b4023 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -2,6 +2,7 @@
class Snippet < ActiveRecord::Base
include Gitlab::VisibilityLevel
+ include Redactable
include CacheMarkdownField
include Noteable
include Participable
@@ -18,6 +19,8 @@ class Snippet < ActiveRecord::Base
cache_markdown_field :description
cache_markdown_field :content
+ redact_field :description
+
# Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with snippets.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10392/diffs#note_28719102
alias_attribute :last_edited_at, :updated_at
diff --git a/app/models/user.rb b/app/models/user.rb
index ca7fc3b058f..cc2cd1b7723 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -28,7 +28,7 @@ class User < ActiveRecord::Base
ignore_column :email_provider
ignore_column :authentication_token
- add_authentication_token_field :incoming_email_token
+ add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
add_authentication_token_field :feed_token
default_value_for :admin, false
@@ -463,7 +463,7 @@ class User < ActiveRecord::Base
def find_by_personal_access_token(token_string)
return unless token_string
- PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user # rubocop: disable CodeReuse/Finder
+ PersonalAccessTokensFinder.new(state: 'active').find_by_token(token_string)&.user # rubocop: disable CodeReuse/Finder
end
# Returns a user for the given SSH key.
@@ -1138,7 +1138,7 @@ class User < ActiveRecord::Base
events = Event.select(:project_id)
.contributions.where(author_id: self)
.where("created_at > ?", Time.now - 1.year)
- .uniq
+ .distinct
.reorder(nil)
Project.where(id: events)
@@ -1464,15 +1464,6 @@ class User < ActiveRecord::Base
end
end
- def generate_token(token_field)
- if token_field == :incoming_email_token
- # Needs to be all lowercase and alphanumeric because it's gonna be used in an email address.
- SecureRandom.hex.to_i(16).to_s(36)
- else
- super
- end
- end
-
def self.unique_internal(scope, username, email_pattern, &block)
scope.first || create_unique_internal(scope, username, email_pattern, &block)
end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 29eaad759bb..a866e76df5a 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -9,7 +9,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
runner_system_failure: 'There has been a runner system failure, please try again',
missing_dependency_failure: 'There has been a missing dependency failure',
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
- stale_schedule: 'Delayed job could not be executed by some reason, please try again'
+ stale_schedule: 'Delayed job could not be executed by some reason, please try again',
+ job_execution_timeout: 'The script exceeded the maximum execution time set for the job'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 3f565b826dd..1db6c9eff36 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -108,16 +108,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
namespace = source_project_namespace
branch = source_branch
- if source_branch_exists?
- namespace = link_to(namespace, project_path(source_project))
- branch = link_to(branch, project_tree_path(source_project, source_branch))
- end
+ namespace_link = source_branch_exists? ? link_to(namespace, project_path(source_project)) : ERB::Util.html_escape(namespace)
+ branch_link = source_branch_exists? ? link_to(branch, project_tree_path(source_project, source_branch)) : ERB::Util.html_escape(branch)
- if for_fork?
- namespace + ":" + branch
- else
- branch
- end
+ for_fork? ? "#{namespace_link}:#{branch_link}" : branch_link
end
def closing_issues_links
diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb
index 3dfa4f204c9..f87cc894d2f 100644
--- a/app/serializers/environment_status_entity.rb
+++ b/app/serializers/environment_status_entity.rb
@@ -5,6 +5,7 @@ class EnvironmentStatusEntity < Grape::Entity
expose :id
expose :name
+ expose :status
expose :url do |es|
project_environment_path(es.project, es.environment)
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 9ec24f799ef..f33a1654d5e 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -55,6 +55,7 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_commit_message
expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline
+ expose :merge_pipeline, with: PipelineDetailsEntity, if: ->(mr, _) { mr.merged? && can?(request.current_user, :read_pipeline, mr.target_project)}
# Booleans
expose :merge_ongoing?, as: :merge_ongoing
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index c6e955800af..cd843b8ffa8 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -9,9 +9,9 @@ module Clusters
end
def execute(project:, access_token: nil)
- raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?(project)
+ raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?(project)
- cluster_params = params.merge(user: current_user, projects: [project])
+ cluster_params = params.merge(user: current_user, cluster_type: :project_type, projects: [project])
cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token
end
diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb
index ced87a1c37a..80de897e94b 100644
--- a/app/services/delete_merged_branches_service.rb
+++ b/app/services/delete_merged_branches_service.rb
@@ -24,8 +24,8 @@ class DeleteMergedBranchesService < BaseService
# rubocop: disable CodeReuse/ActiveRecord
def merge_request_branch_names
# reorder(nil) is necessary for SELECT DISTINCT because default scope adds an ORDER BY
- source_names = project.origin_merge_requests.opened.reorder(nil).uniq.pluck(:source_branch)
- target_names = project.merge_requests.opened.reorder(nil).uniq.pluck(:target_branch)
+ source_names = project.origin_merge_requests.opened.reorder(nil).distinct.pluck(:source_branch)
+ target_names = project.merge_requests.opened.reorder(nil).distinct.pluck(:target_branch)
(source_names + target_names).uniq
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
index 52360f775dc..9cbc9fef529 100644
--- a/app/services/labels/transfer_service.rb
+++ b/app/services/labels/transfer_service.rb
@@ -40,7 +40,7 @@ module Labels
group_labels_applied_to_merge_requests
])
.reorder(nil)
- .uniq
+ .distinct
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index 7c88c9abb41..35a22449e34 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -50,8 +50,8 @@ module MergeRequests
end
def url_for_new_merge_request(branch_name)
- merge_request_params = { source_branch: branch_name }
- url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
+ url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, branch_name)
+
{ branch_name: branch_name, url: url, new_merge_request: true }
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index fb44f809c41..70a67baa01c 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -49,6 +49,11 @@ module MergeRequests
end
end
+ # Overridden in EE.
+ def hooks_validation_pass?(_merge_request)
+ true
+ end
+
private
def error_check!
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index b03d14fa3cc..f01872b205e 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -85,7 +85,7 @@ module MergeRequests
.where.not(target_project: @project).to_a
filter_merge_requests(merge_requests).each do |merge_request|
- if merge_request.source_branch == @push.branch_name || @push.force_push?
+ if branch_and_project_match?(merge_request) || @push.force_push?
merge_request.reload_diff(current_user)
else
mr_commit_ids = merge_request.commit_shas
@@ -104,6 +104,11 @@ module MergeRequests
end
# rubocop: enable CodeReuse/ActiveRecord
+ def branch_and_project_match?(merge_request)
+ merge_request.source_project == @project &&
+ merge_request.source_branch == @push.branch_name
+ end
+
def reset_merge_when_pipeline_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
end
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index e402801a776..f34305e94fa 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -9,8 +9,8 @@
= render 'ci/variables/variable_row', form_field: 'variables', variable: variable
= render 'ci/variables/variable_row', form_field: 'variables'
.prepend-top-20
- %button.btn.btn-success.js-secret-variables-save-button{ type: 'button' }
- %span.hide.js-secret-variables-save-loading-icon
+ %button.btn.btn-success.js-ci-variables-save-button{ type: 'button' }
+ %span.hide.js-ci-variables-save-loading-icon
= icon('spinner spin')
= _('Save variables')
%button.btn.btn-info.btn-inverted.prepend-left-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@variables.size == 0}" } }
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 684b51b8552..0904e44a658 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,12 +1,12 @@
- @hide_breadcrumbs = true
- @hide_top_links = true
-- page_title 'New Group'
-- header_title "Groups", dashboard_groups_path
+- page_title _('New Group')
+- header_title _("Groups"), dashboard_groups_path
+.page-title-holder
+ %h1.page-title= _('New group')
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
- %h4.prepend-top-0
- = _('New group')
%p
- group_docs_path = help_page_path('user/group/index')
- group_docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_docs_path }
@@ -15,24 +15,29 @@
- subgroup_docs_path = help_page_path('user/group/subgroups/index')
- subgroup_docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: subgroup_docs_path }
= s_('Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}.').html_safe % { subgroup_docs_link_start: subgroup_docs_link_start, subgroup_docs_link_end: '</a>'.html_safe }
+ %p
+ = _('Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group.')
.col-lg-9
= form_for @group, html: { class: 'group-form gl-show-field-errors' } do |f|
= form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true
- .form-group.row.group-description-holder
- = f.label :avatar, "Group avatar", class: 'col-form-label col-sm-2'
- .col-sm-10
- = render 'shared/choose_group_avatar_button', f: f
-
- = render 'shared/old_visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
+ .row
+ .form-group.group-description-holder.col-sm-12
+ = f.label :avatar, _("Group avatar"), class: 'label-bold'
+ %div
+ = render 'shared/choose_group_avatar_button', f: f
- = render 'create_chat_team', f: f if Gitlab.config.mattermost.enabled
+ .form-group.col-sm-12
+ %label.label-bold
+ = _('Visibility level')
+ %p
+ = _('Who will be able to see this group?')
+ = link_to _('View the documentation'), help_page_path("public_access/public_access"), target: '_blank'
+ = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
- .form-group.row
- .offset-sm-2.col-sm-10
- = render 'shared/group_tips'
+ = render 'create_chat_team', f: f if Gitlab.config.mattermost.enabled
.form-actions
= f.submit 'Create group', class: "btn btn-success"
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index 647948c7dff..a5e6abdba52 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -3,7 +3,7 @@
- expanded = Rails.env.test?
-%section.settings#secret-variables.no-animate{ class: ('expanded' if expanded) }
+%section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
= _('Variables')
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 596fc3985b3..b7d69539eb7 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -5,7 +5,7 @@
- else
- search_path_url = search_path
-%header.navbar.navbar-gitlab.qa-navbar.navbar-expand-sm
+%header.navbar.navbar-gitlab.qa-navbar.navbar-expand-sm.js-navbar
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 156c0d05b02..7c378633667 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -46,7 +46,7 @@
Layout width
= f.select :layout, layout_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose between fixed (max. 1200px) and fluid (100%) application layout.
+ Choose between fixed (max. 1280px) and fluid (100%) application layout.
.form-group
= f.label :dashboard, class: 'label-bold' do
Default dashboard
@@ -56,6 +56,6 @@
Project overview content
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose what content you want to see on a project’s overview page
+ Choose what content you want to see on a project’s overview page.
.form-group
= f.submit 'Save changes', class: 'btn btn-success'
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index c63ff070f70..95bba47802c 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -19,30 +19,23 @@
#js-pipeline-graph-vue
#js-tab-builds.tab-pane
- - if pipeline.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - pipeline.yaml_errors.split(",").each do |error|
- %li= error
- You can also test your .gitlab-ci.yml in the #{link_to "Lint", project_ci_lint_path(@project)}
+ - if pipeline.legacy_stages.present?
+ .table-holder.pipeline-holder
+ %table.table.ci-table.pipeline
+ %thead
+ %tr
+ %th Status
+ %th Job ID
+ %th Name
+ %th
+ %th Coverage
+ %th
+ = render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
- - if pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
+ - elsif pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
- .table-holder.pipeline-holder
- %table.table.ci-table.pipeline
- %thead
- %tr
- %th Status
- %th Job ID
- %th Name
- %th
- %th Coverage
- %th
- = render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
-
- if @pipeline.failed_builds.present?
#js-tab-failures.build-failures.tab-pane.build-page
%table.table.responsive-table.ci-table.responsive-table-sm-rounded
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index ff0ed3ed30d..193d437dad1 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -9,6 +9,14 @@
- if @pipeline.commit.present?
= render "projects/pipelines/info", commit: @pipeline.commit
- = render "projects/pipelines/with_tabs", pipeline: @pipeline
+ - if @pipeline.builds.empty? && @pipeline.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - @pipeline.yaml_errors.split(",").each do |error|
+ %li= error
+ You can test your .gitlab-ci.yml in #{link_to "CI Lint", project_ci_lint_path(@project)}.
+ - else
+ = render "projects/pipelines/with_tabs", pipeline: @pipeline
.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index dbed4b94d61..973c756f496 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -2,10 +2,19 @@
- group_path = root_url
- group_path << parent.full_path + '/' if parent
-.form-group.row
- = f.label :path, class: 'col-form-label col-sm-2' do
- Group path
- .col-sm-10
+.row
+ .form-group.group-name-holder.col-sm-12
+ = f.label :name, class: 'label-bold' do
+ = _("Group name")
+ = f.text_field :name, placeholder: 'My Awesome Group', class: 'form-control input-lg',
+ required: true,
+ title: _('Please fill in a descriptive name for your group.'),
+ autofocus: true
+
+.row
+ .form-group.col-xs-12.col-sm-8
+ = f.label :path, class: 'label-bold' do
+ = _("Group URL")
.input-group.gl-field-error-anchor
.group-root-path.input-group-prepend.has-tooltip{ title: group_path, :'data-placement' => 'bottom' }
.input-group-text
@@ -13,10 +22,10 @@
- if parent
%strong= parent.full_path + '/'
= f.hidden_field :parent_id
- = f.text_field :path, placeholder: 'open-source', class: 'form-control',
+ = f.text_field :path, placeholder: 'my-awesome-group', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
- title: 'Please choose a group path with no special characters.',
+ title: _('Please choose a group URL with no special characters.'),
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
- if @group.persisted?
@@ -25,23 +34,17 @@
= succeed '.' do
= link_to 'Learn more', help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank'
-.form-group.row.group-name-holder
- = f.label :name, class: 'col-form-label col-sm-2' do
- Group name
- .col-sm-10
- = f.text_field :name, class: 'form-control',
- required: true,
- title: 'You can choose a descriptive name different from the path.'
-
- if @group.persisted?
- .form-group.row.group-name-holder
- = f.label :id, class: 'col-form-label col-sm-2' do
- = _("Group ID")
- .col-sm-10
+ .row
+ .form-group.group-name-holder.col-sm-8
+ = f.label :id, class: 'label-bold' do
+ = _("Group ID")
= f.text_field :id, class: 'form-control', readonly: true
-.form-group.row.group-description-holder
- = f.label :description, class: 'col-form-label col-sm-2'
- .col-sm-10
+.row
+ .form-group.group-description-holder.col-sm-8
+ = f.label :description, class: 'label-bold' do
+ = _("Group description")
+ %span (optional)
= f.text_area :description, maxlength: 250,
class: 'form-control js-gfm-input', rows: 4
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index cb45928d9a5..1d876cc4a5d 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -61,7 +61,10 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- = _('No Assignee')
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
%li.divider.droplab-item-ignore
- if current_user
= render 'shared/issuable/user_dropdown_item',
@@ -81,7 +84,7 @@
%li.filter-dropdown-item{ data: { value: 'upcoming' } }
%button.btn.btn-link{ type: 'button' }
= _('Upcoming')
- %li.filter-dropdown-item{ 'data-value' => 'started' }
+ %li.filter-dropdown-item{ data: { value: 'started' } }
%button.btn.btn-link{ type: 'button' }
= _('Started')
%li.divider.droplab-item-ignore
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 10ffe8dd37f..5295e656ab0 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -24,7 +24,7 @@
.block.milestone
.sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
= icon('clock-o', 'aria-hidden': 'true')
- %span.milestone-title
+ %span.milestone-title.collapse-truncated-title
- if issuable.milestone
= issuable.milestone.title
- else
diff --git a/changelogs/unreleased/34758-create-group-clusters.yml b/changelogs/unreleased/34758-create-group-clusters.yml
new file mode 100644
index 00000000000..50efde3cac3
--- /dev/null
+++ b/changelogs/unreleased/34758-create-group-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: Adds model and migrations to enable group level clusters
+merge_request: 22307
+author:
+type: other
diff --git a/changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml b/changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml
new file mode 100644
index 00000000000..97052d01b24
--- /dev/null
+++ b/changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Hide all tables on Pipeline when no Jobs for the Pipeline
+merge_request: 18540
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/50962-create-new-group-rename-form-fields-and-update-ui.yml b/changelogs/unreleased/50962-create-new-group-rename-form-fields-and-update-ui.yml
new file mode 100644
index 00000000000..db374e10c36
--- /dev/null
+++ b/changelogs/unreleased/50962-create-new-group-rename-form-fields-and-update-ui.yml
@@ -0,0 +1,5 @@
+---
+title: 'Create new group: Rename form fields and update UI'
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/51527-xss-in-mr-source-branch.yml b/changelogs/unreleased/51527-xss-in-mr-source-branch.yml
new file mode 100644
index 00000000000..dae277b6413
--- /dev/null
+++ b/changelogs/unreleased/51527-xss-in-mr-source-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Fix XSS in merge request source branch name
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/52122-fix-broken-whitespace-button.yml b/changelogs/unreleased/52122-fix-broken-whitespace-button.yml
new file mode 100644
index 00000000000..3f261eb2ac5
--- /dev/null
+++ b/changelogs/unreleased/52122-fix-broken-whitespace-button.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken "Show whitespace changes" button on MRs.
+merge_request: 22539
+author:
+type: fixed
diff --git a/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml b/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml
new file mode 100644
index 00000000000..adf153f33ce
--- /dev/null
+++ b/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml
@@ -0,0 +1,5 @@
+---
+title: Add None/Any option for assignee_id in search bar
+merge_request: 22599
+author: Heinrich Lee Yu
+type: added
diff --git a/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml b/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml
new file mode 100644
index 00000000000..7586d7995b7
--- /dev/null
+++ b/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Cache pipeline status per SHA.
+merge_request: 22589
+author:
+type: fixed
diff --git a/changelogs/unreleased/53155-structured-logs-params-array.yml b/changelogs/unreleased/53155-structured-logs-params-array.yml
new file mode 100644
index 00000000000..4d4f68a5c84
--- /dev/null
+++ b/changelogs/unreleased/53155-structured-logs-params-array.yml
@@ -0,0 +1,5 @@
+---
+title: Use key-value pair arrays for API query parameter logging instead of hashes
+merge_request: 22623
+author:
+type: other
diff --git a/changelogs/unreleased/53227-empty-list.yml b/changelogs/unreleased/53227-empty-list.yml
new file mode 100644
index 00000000000..8b222145299
--- /dev/null
+++ b/changelogs/unreleased/53227-empty-list.yml
@@ -0,0 +1,6 @@
+---
+title: Only renders dropdown for review app changes when we have a list of files to
+ show. Otherwise will render the regular review app button
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/53270-remove-mousetrap-rails.yml b/changelogs/unreleased/53270-remove-mousetrap-rails.yml
new file mode 100644
index 00000000000..7214c81d73d
--- /dev/null
+++ b/changelogs/unreleased/53270-remove-mousetrap-rails.yml
@@ -0,0 +1,5 @@
+---
+title: Remove mousetrap-rails gem
+merge_request: 22647
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/53273-update-moment-to-2-22-2.yml b/changelogs/unreleased/53273-update-moment-to-2-22-2.yml
new file mode 100644
index 00000000000..a6b40466927
--- /dev/null
+++ b/changelogs/unreleased/53273-update-moment-to-2-22-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update moment to 2.22.2
+merge_request: 22648
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/ac-post-merge-pipeline.yml b/changelogs/unreleased/ac-post-merge-pipeline.yml
new file mode 100644
index 00000000000..08322c9cb8a
--- /dev/null
+++ b/changelogs/unreleased/ac-post-merge-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Show post-merge pipeline in merge request page
+merge_request: 22292
+author:
+type: added
diff --git a/changelogs/unreleased/add-failure-reason-for-execution-timeout.yml b/changelogs/unreleased/add-failure-reason-for-execution-timeout.yml
new file mode 100644
index 00000000000..c8488cbf200
--- /dev/null
+++ b/changelogs/unreleased/add-failure-reason-for-execution-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Add failure reason for execution timeout
+merge_request: 22224
+author:
+type: changed
diff --git a/changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml b/changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml
new file mode 100644
index 00000000000..b89ba754952
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml
@@ -0,0 +1,5 @@
+---
+title: Make new merge request URL more friendly when pushing code
+merge_request: 22526
+author: "@blackst0ne"
+type: changed
diff --git a/changelogs/unreleased/fix-53298.yml b/changelogs/unreleased/fix-53298.yml
new file mode 100644
index 00000000000..f0bf5470dc8
--- /dev/null
+++ b/changelogs/unreleased/fix-53298.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix #53298: JupyterHub restarts should work without errors'
+merge_request: 22671
+author: Amit Rathi
+type: fixed
diff --git a/changelogs/unreleased/fl-missing-i18n.yml b/changelogs/unreleased/fl-missing-i18n.yml
new file mode 100644
index 00000000000..d41a691e636
--- /dev/null
+++ b/changelogs/unreleased/fl-missing-i18n.yml
@@ -0,0 +1,5 @@
+---
+title: Adds missing i18n to pipelines table
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml b/changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml
new file mode 100644
index 00000000000..ca3b99e73ab
--- /dev/null
+++ b/changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Truncate milestone title on collapsed sidebar
+merge_request: 22624
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/rails5-deprecated-uniq.yml b/changelogs/unreleased/rails5-deprecated-uniq.yml
new file mode 100644
index 00000000000..69a169100f0
--- /dev/null
+++ b/changelogs/unreleased/rails5-deprecated-uniq.yml
@@ -0,0 +1,5 @@
+---
+title: Replace deprecated uniq on a Relation with distinct
+merge_request: 22625
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/ravlen-rename-secret-variables-in-codebase.yml b/changelogs/unreleased/ravlen-rename-secret-variables-in-codebase.yml
new file mode 100644
index 00000000000..211d51a3d43
--- /dev/null
+++ b/changelogs/unreleased/ravlen-rename-secret-variables-in-codebase.yml
@@ -0,0 +1,5 @@
+---
+title: "Secret Variables renamed to CI Variables in the codebase, to match UX"
+merge_request: 22414
+author: Marcel Amirault @ravlen
+type: changed \ No newline at end of file
diff --git a/changelogs/unreleased/redact-links-dev.yml b/changelogs/unreleased/redact-links-dev.yml
new file mode 100644
index 00000000000..338e7965465
--- /dev/null
+++ b/changelogs/unreleased/redact-links-dev.yml
@@ -0,0 +1,5 @@
+---
+title: Redact personal tokens in unsubscribe links.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/rz_fix_milestone_count.yml b/changelogs/unreleased/rz_fix_milestone_count.yml
new file mode 100644
index 00000000000..1013b88e0bc
--- /dev/null
+++ b/changelogs/unreleased/rz_fix_milestone_count.yml
@@ -0,0 +1,5 @@
+---
+title: Fixing count on Milestones
+merge_request: 21446
+author:
+type: fixed
diff --git a/changelogs/unreleased/security-2717-fix-issue-title-xss.yml b/changelogs/unreleased/security-2717-fix-issue-title-xss.yml
new file mode 100644
index 00000000000..f2e638e5ab5
--- /dev/null
+++ b/changelogs/unreleased/security-2717-fix-issue-title-xss.yml
@@ -0,0 +1,5 @@
+---
+title: Escape entity title while autocomplete template rendering to prevent XSS
+merge_request: 2556
+author:
+type: security
diff --git a/changelogs/unreleased/security-51113-hash_personal_access_tokens.yml b/changelogs/unreleased/security-51113-hash_personal_access_tokens.yml
new file mode 100644
index 00000000000..4cebe814148
--- /dev/null
+++ b/changelogs/unreleased/security-51113-hash_personal_access_tokens.yml
@@ -0,0 +1,5 @@
+---
+title: Persist only SHA digest of PersonalAccessToken#token
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-fix-hipchat-ssrf.yml b/changelogs/unreleased/sh-fix-hipchat-ssrf.yml
new file mode 100644
index 00000000000..cdc95a34fcf
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-hipchat-ssrf.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent SSRF attacks in HipChat integration
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-fix-issue-53153.yml b/changelogs/unreleased/sh-fix-issue-53153.yml
new file mode 100644
index 00000000000..ee51631f959
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-53153.yml
@@ -0,0 +1,5 @@
+---
+title: Fix extra merge request versions created from forked merge requests
+merge_request: 22611
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-search-relative-urls.yml b/changelogs/unreleased/sh-fix-search-relative-urls.yml
new file mode 100644
index 00000000000..2545e9ca553
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-search-relative-urls.yml
@@ -0,0 +1,5 @@
+---
+title: Fix search "all in GitLab" not working with relative URLs
+merge_request: 22644
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml b/changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml
new file mode 100644
index 00000000000..ac6ab7cc3f4
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml
@@ -0,0 +1,5 @@
+---
+title: Validate Wiki attachments are valid temporary files
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/tc-index-lfs-objects-file-store.yml b/changelogs/unreleased/tc-index-lfs-objects-file-store.yml
new file mode 100644
index 00000000000..90e80cb1ef1
--- /dev/null
+++ b/changelogs/unreleased/tc-index-lfs-objects-file-store.yml
@@ -0,0 +1,5 @@
+---
+title: Enhance performance of counting local LFS objects
+merge_request: 22143
+author:
+type: performance
diff --git a/changelogs/unreleased/winh-pipeline-actions-dynamic-timer.yml b/changelogs/unreleased/winh-pipeline-actions-dynamic-timer.yml
new file mode 100644
index 00000000000..4ea1d3f8256
--- /dev/null
+++ b/changelogs/unreleased/winh-pipeline-actions-dynamic-timer.yml
@@ -0,0 +1,5 @@
+---
+title: Add dynamic timer for delayed jobs in pipelines list
+merge_request: 22621
+author:
+type: changed
diff --git a/config/initializers/hipchat_client_patch.rb b/config/initializers/hipchat_client_patch.rb
new file mode 100644
index 00000000000..aec265312bb
--- /dev/null
+++ b/config/initializers/hipchat_client_patch.rb
@@ -0,0 +1,14 @@
+# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb.
+module HipChat
+ class Client
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+
+ class Room
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+
+ class User
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 795e5d4e6bc..0a43a1d9a6b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -8,6 +8,8 @@ en:
issue_link:
source: Source issue
target: Target issue
+ group:
+ path: Group URL
errors:
messages:
label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
diff --git a/config/routes.rb b/config/routes.rb
index 8723a928cc3..37c7f98ec98 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -80,6 +80,7 @@ Rails.application.routes.draw do
get 'ide' => 'ide#index'
get 'ide/*vueroute' => 'ide#index', format: false
+ draw :operations
draw :instance_statistics
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 85872a4122a..73c46f72168 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -149,9 +149,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
scope path: 'merge_requests', controller: 'merge_requests/creations' do
post '', action: :create, as: nil
- scope path: 'new', as: :new_merge_request do
- get '', action: :new
-
+ scope path: 'new/(:merge_request_source_branch)', as: :new_merge_request do
scope constraints: { format: nil }, action: :new do
get :diffs, defaults: { tab: 'diffs' }
get :pipelines, defaults: { tab: 'pipelines' }
@@ -165,6 +163,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :diff_for_path
get :branch_from
get :branch_to
+ get '', action: :new
end
end
diff --git a/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb b/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
new file mode 100644
index 00000000000..203fcfe8eae
--- /dev/null
+++ b/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddTokenDigestToPersonalAccessTokens < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ change_column :personal_access_tokens, :token, :string, null: true
+
+ add_column :personal_access_tokens, :token_digest, :string
+ end
+
+ def down
+ remove_column :personal_access_tokens, :token_digest
+
+ change_column :personal_access_tokens, :token, :string, null: false
+ end
+end
diff --git a/db/migrate/20180910153413_add_index_to_token_digest_on_personal_access_tokens.rb b/db/migrate/20180910153413_add_index_to_token_digest_on_personal_access_tokens.rb
new file mode 100644
index 00000000000..4300cd13a45
--- /dev/null
+++ b/db/migrate/20180910153413_add_index_to_token_digest_on_personal_access_tokens.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToTokenDigestOnPersonalAccessTokens < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :personal_access_tokens, :token_digest, unique: true
+ end
+
+ def down
+ remove_concurrent_index :personal_access_tokens, :token_digest if index_exists?(:personal_access_tokens, :token_digest)
+ end
+end
diff --git a/db/migrate/20181005110927_add_index_to_lfs_objects_file_store.rb b/db/migrate/20181005110927_add_index_to_lfs_objects_file_store.rb
new file mode 100644
index 00000000000..d09543aa4cc
--- /dev/null
+++ b/db/migrate/20181005110927_add_index_to_lfs_objects_file_store.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToLfsObjectsFileStore < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :lfs_objects, :file_store
+ end
+
+ def down
+ remove_concurrent_index :lfs_objects, :file_store
+ end
+end
diff --git a/db/migrate/20181014203236_create_cluster_groups.rb b/db/migrate/20181014203236_create_cluster_groups.rb
new file mode 100644
index 00000000000..69382d5c851
--- /dev/null
+++ b/db/migrate/20181014203236_create_cluster_groups.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateClusterGroups < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :cluster_groups do |t|
+ t.references :cluster, null: false, foreign_key: { on_delete: :cascade }
+ t.references :group, null: false, index: true
+
+ t.index [:cluster_id, :group_id], unique: true
+ t.foreign_key :namespaces, column: :group_id, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/migrate/20181017001059_add_cluster_type_to_clusters.rb b/db/migrate/20181017001059_add_cluster_type_to_clusters.rb
new file mode 100644
index 00000000000..191e7eb4fb3
--- /dev/null
+++ b/db/migrate/20181017001059_add_cluster_type_to_clusters.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddClusterTypeToClusters < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ PROJECT_CLUSTER_TYPE = 3
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:clusters, :cluster_type, :smallint, default: PROJECT_CLUSTER_TYPE)
+ end
+
+ def down
+ remove_column(:clusters, :cluster_type)
+ end
+end
diff --git a/db/post_migrate/20180913142237_schedule_digest_personal_access_tokens.rb b/db/post_migrate/20180913142237_schedule_digest_personal_access_tokens.rb
new file mode 100644
index 00000000000..36be819b245
--- /dev/null
+++ b/db/post_migrate/20180913142237_schedule_digest_personal_access_tokens.rb
@@ -0,0 +1,28 @@
+class ScheduleDigestPersonalAccessTokens < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ BATCH_SIZE = 10_000
+ MIGRATION = 'DigestColumn'
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ disable_ddl_transaction!
+
+ class PersonalAccessToken < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'personal_access_tokens'
+ end
+
+ def up
+ PersonalAccessToken.where('token is NOT NULL').each_batch(of: BATCH_SIZE) do |batch, index|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+ BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, ['PersonalAccessToken', :token, :token_digest, *range])
+ end
+ end
+
+ def down
+ # raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/db/post_migrate/20181014121030_enqueue_redact_links.rb b/db/post_migrate/20181014121030_enqueue_redact_links.rb
new file mode 100644
index 00000000000..1ee4703c88a
--- /dev/null
+++ b/db/post_migrate/20181014121030_enqueue_redact_links.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+class EnqueueRedactLinks < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 1000
+ DELAY_INTERVAL = 5.minutes.to_i
+ MIGRATION = 'RedactLinks'
+
+ disable_ddl_transaction!
+
+ class Note < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'notes'
+ self.inheritance_column = :_type_disabled
+ end
+
+ class Issue < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'issues'
+ self.inheritance_column = :_type_disabled
+ end
+
+ class MergeRequest < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'merge_requests'
+ self.inheritance_column = :_type_disabled
+ end
+
+ class Snippet < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'snippets'
+ self.inheritance_column = :_type_disabled
+ end
+
+ def up
+ disable_statement_timeout do
+ schedule_migration(Note, 'note')
+ schedule_migration(Issue, 'description')
+ schedule_migration(MergeRequest, 'description')
+ schedule_migration(Snippet, 'description')
+ end
+ end
+
+ def down
+ # nothing to do
+ end
+
+ private
+
+ def schedule_migration(model, field)
+ link_pattern = "%/sent_notifications/" + ("_" * 32) + "/unsubscribe%"
+
+ model.where("#{field} like ?", link_pattern).each_batch(of: BATCH_SIZE) do |batch, index|
+ start_id, stop_id = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, [model.name.demodulize, field, start_id, stop_id])
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7a75aafd7b0..6b0bb33f969 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20181016152238) do
+ActiveRecord::Schema.define(version: 20181017001059) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -599,6 +599,14 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
+ create_table "cluster_groups", force: :cascade do |t|
+ t.integer "cluster_id", null: false
+ t.integer "group_id", null: false
+ end
+
+ add_index "cluster_groups", ["cluster_id", "group_id"], name: "index_cluster_groups_on_cluster_id_and_group_id", unique: true, using: :btree
+ add_index "cluster_groups", ["group_id"], name: "index_cluster_groups_on_group_id", using: :btree
+
create_table "cluster_platforms_kubernetes", force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
@@ -654,6 +662,7 @@ ActiveRecord::Schema.define(version: 20181016152238) do
t.boolean "enabled", default: true
t.string "name", null: false
t.string "environment_scope", default: "*", null: false
+ t.integer "cluster_type", limit: 2, default: 3, null: false
end
add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree
@@ -1158,6 +1167,7 @@ ActiveRecord::Schema.define(version: 20181016152238) do
t.integer "file_store"
end
+ add_index "lfs_objects", ["file_store"], name: "index_lfs_objects_on_file_store", using: :btree
add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
create_table "lfs_objects_projects", force: :cascade do |t|
@@ -1539,7 +1549,7 @@ ActiveRecord::Schema.define(version: 20181016152238) do
create_table "personal_access_tokens", force: :cascade do |t|
t.integer "user_id", null: false
- t.string "token", null: false
+ t.string "token"
t.string "name", null: false
t.boolean "revoked", default: false
t.date "expires_at"
@@ -1547,9 +1557,11 @@ ActiveRecord::Schema.define(version: 20181016152238) do
t.datetime "updated_at", null: false
t.string "scopes", default: "--- []\n", null: false
t.boolean "impersonation", default: false, null: false
+ t.string "token_digest"
end
add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
+ add_index "personal_access_tokens", ["token_digest"], name: "index_personal_access_tokens_on_token_digest", unique: true, using: :btree
add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
create_table "programming_languages", force: :cascade do |t|
@@ -2372,6 +2384,8 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
+ add_foreign_key "cluster_groups", "clusters", on_delete: :cascade
+ add_foreign_key "cluster_groups", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "cluster_platforms_kubernetes", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "projects", on_delete: :cascade
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 96803746637..481eb692674 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -61,7 +61,7 @@ on an Linux NFS server, do the following:
2. Restart the NFS server process. For example, on CentOS run `service nfs restart`.
-## AWS Elastic File System
+## Avoid using AWS's Elastic File System (EFS)
GitLab strongly recommends against using AWS Elastic File System (EFS).
Our support team will not be able to assist on performance issues related to
@@ -78,6 +78,23 @@ stored on a local volume.
For more details on another person's experience with EFS, see
[Amazon's Elastic File System: Burst Credits](https://rawkode.com/2017/04/16/amazons-elastic-file-system-burst-credits/)
+## Avoid using PostgreSQL with NFS
+
+GitLab strongly recommends against running your PostgreSQL database
+across NFS. The GitLab support team will not be able to assist on performance issues related to
+this configuration.
+
+Additionally, this configuration is specifically warned against in the
+[Postgres Documentation](https://www.postgresql.org/docs/current/static/creating-cluster.html#CREATING-CLUSTER-NFS):
+
+>PostgreSQL does nothing special for NFS file systems, meaning it assumes NFS behaves exactly like
+>locally-connected drives. If the client or server NFS implementation does not provide standard file
+>system semantics, this can cause reliability problems. Specifically, delayed (asynchronous) writes
+>to the NFS server can cause data corruption problems.
+
+For supported database architecture, please see our documentation on
+[Configuring a Database for GitLab HA](https://docs.gitlab.com/ee/administration/high_availability/database.html).
+
## NFS Client mount options
Below is an example of an NFS mount point defined in `/etc/fstab` we use on
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index b5d1ff698c6..dcee57def74 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -15,7 +15,7 @@ Omnibus GitLab packages.
> **Notes:**
> - Redis requires authentication for High Availability. See
-> [Redis Security](http://redis.io/topics/security) documentation for more
+> [Redis Security](https://redis.io/topics/security) documentation for more
> information. We recommend using a combination of a Redis password and tight
> firewall rules to secure your Redis service.
> - You are highly encouraged to read the [Redis Sentinel][sentinel] documentation
@@ -82,7 +82,7 @@ When a **Master** fails to respond, it's the application's responsibility
for a new **Master**).
To get a better understanding on how to correctly set up Sentinel, please read
-the [Redis Sentinel documentation](http://redis.io/topics/sentinel) first, as
+the [Redis Sentinel documentation](https://redis.io/topics/sentinel) first, as
failing to configure it correctly can lead to data loss or can bring your
whole cluster down, invalidating the failover effort.
@@ -885,8 +885,8 @@ Read more on High Availability:
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[gh-531]: https://github.com/redis/redis-rb/issues/531
[gh-534]: https://github.com/redis/redis-rb/issues/534
-[redis]: http://redis.io/
-[sentinel]: http://redis.io/topics/sentinel
+[redis]: https://redis.io/
+[sentinel]: https://redis.io/topics/sentinel
[omnifile]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
[source]: ../../install/installation.md
[ce]: https://about.gitlab.com/downloads
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 5823c575251..2101d36d2b6 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -107,7 +107,7 @@ starting with `sentinel` prefix.
Assuming that the Redis Sentinel is installed on the same instance as Redis
master with IP `10.0.0.1` (some settings might overlap with the master):
-1. [Install Redis Sentinel](http://redis.io/topics/sentinel)
+1. [Install Redis Sentinel](https://redis.io/topics/sentinel)
1. Edit `/etc/redis/sentinel.conf`:
```conf
@@ -363,7 +363,7 @@ production:
port: 26379 # point to sentinel, not to redis port
```
-When in doubt, please read [Redis Sentinel documentation](http://redis.io/topics/sentinel).
+When in doubt, please read [Redis Sentinel documentation](https://redis.io/topics/sentinel).
[gh-531]: https://github.com/redis/redis-rb/issues/531
[downloads]: https://about.gitlab.com/downloads
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 0e41a38b7e2..038e043281c 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -84,7 +84,7 @@ Introduced in GitLab 10.0, this file lives in
It helps you see requests made directly to the API. For example:
```json
-{"time":"2017-10-10T12:30:11.579Z","severity":"INFO","duration":16.84,"db":1.57,"view":15.27,"status":200,"method":"POST","path":"/api/v4/internal/allowed","params":{"action":"git-upload-pack","changes":"_any","gl_repository":null,"project":"root/foobar.git","protocol":"ssh","env":"{}","key_id":"[FILTERED]","secret_token":"[FILTERED]"},"host":"127.0.0.1","ip":"127.0.0.1","ua":"Ruby"}
+{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30}
```
This entry above shows an access to an internal endpoint to check whether an
diff --git a/doc/api/tags.md b/doc/api/tags.md
index f2a3f9f28d2..826900ca518 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -134,7 +134,7 @@ Parameters:
"description": "Amazing release. Wow"
},
"name": "v1.0.0",
- "target: "2695effb5807a22ff3d138d593fd856244e155e7",
+ "target": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": null
}
```
diff --git a/doc/ci/examples/container_scanning.md b/doc/ci/examples/container_scanning.md
index 0f79f7d1b17..bc948dc6ea9 100644
--- a/doc/ci/examples/container_scanning.md
+++ b/doc/ci/examples/container_scanning.md
@@ -63,4 +63,10 @@ are still maintained, they have been deprecated with GitLab 11.0 and may be remo
in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
configuration to reflect that change.
+CAUTION: **Caution:**
+Starting with GitLab 11.5, Container Scanning feature is licensed under the name `container_scanning`.
+While the old name `sast_container` is still maintained, it has been deprecated with GitLab 11.5 and
+may be removed in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
+configuration to reflect that change if you are using the `$GITLAB_FEATURES` environment variable.
+
[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index b2c73caae2e..c0346d78141 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -398,10 +398,10 @@ other reasons][ci-reasons] to keep using GitLab CI/CD. The benefits to our teams
- [Using Docker images documentation][using-docker]
- [Example project: Hello GitLab CI/CD on GitLab][hello-gitlab]
-[phoenix-site]: http://phoenixframework.org/ "Phoenix Framework"
+[phoenix-site]: https://phoenixframework.org/ "Phoenix Framework"
[phoenix-learning-guide]: https://hexdocs.pm/phoenix/learning.html "Phoenix Learning Guide"
-[phoenix-install]: http://www.phoenixframework.org/docs/installation "Phoenix Installation"
-[phoenix-mysql]: http://www.phoenixframework.org/docs/using-mysql "Phoenix with MySQL"
+[phoenix-install]: https://hexdocs.pm/phoenix/installation.html "Phoenix Installation"
+[phoenix-mysql]: https://hexdocs.pm/phoenix/ecto.html#using-mysql "Phoenix with MySQL"
[elixir-site]: http://elixir-lang.org/ "Elixir"
[elixir-mix]: http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html "Introduction to mix"
[elixir-docs]: http://elixir-lang.org/getting-started/introduction.html "Elixir Documentation"
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 4292a17bfa5..4f4ff85fe1d 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -37,9 +37,9 @@ Review Apps are automatically deployed by each pipeline, both in
review app manually, and is also started by GitLab once a branch is deleted
- [TBD] Review apps are cleaned up regularly using a pipeline schedule that runs
the [`scripts/review_apps/automated_cleanup.rb`][automated_cleanup.rb] script
-- If you're unable to log in using the `root` username and password the
- deployment may have failed. Stop the review app via the `stop_review`
- manual job and then retry the `review` job to redeploy the review app.
+- If you're unable to log in using the `root` username and password, the
+ deployment may have failed. Stop the Review App via the `stop_review`
+ manual job and then retry the `review` job to redeploy the Review App.
[^1]: We use the `CNG-mirror` project so that the `CNG`, (**C**loud **N**ative **G**itLab), project's registry is
not overloaded with a lot of transient Docker images.
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 9546f43eea8..73301394e9f 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -43,7 +43,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
## Third-party resources
- [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth)
-- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitLab+OAuth+Plugin)
+- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin)
- [Set up Gitlab CE with Active Directory authentication](https://www.caseylabs.com/setup-gitlab-ce-with-active-directory-authentication/)
- [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/)
- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab)
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 4d4832184e2..96e788666a1 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -225,10 +225,11 @@ Auto DevOps at the project level.
### Enabling/disabling Auto DevOps at the project-level
-1. Check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it.
+If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it.
+
1. Go to your project's **Settings > CI/CD > Auto DevOps**.
-1. Check the **Default to Auto DevOps pipeline** checkbox.
-1. Optionally, but recommended, add in the [base domain](#auto-devops-base-domain)
+1. Toggle the **Default to Auto DevOps pipeline** checkbox (checked to enable, unchecked to disable)
+1. When enabling, it's optional but recommended to add in the [base domain](#auto-devops-base-domain)
that will be used by Auto DevOps to [deploy your application](#auto-deploy)
and choose the [deployment strategy](#deployment-strategy).
1. Click **Save changes** for the changes to take effect.
@@ -246,12 +247,6 @@ There is also a feature flag to enable Auto DevOps to a percentage of projects
which can be enabled from the console with
`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`.
-### Disable Auto DevOps at the project level
-
-1. Go to your project's **Settings > CI/CD > Auto DevOps**.
-1. Uncheck the **Default to Auto DevOps pipeline** checkbox.
-1. Click **Save changes** for the changes to take effect.
-
### Deployment strategy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38542) in GitLab 11.0.
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index b12e86cb0a9..6326aadcdf2 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -83,6 +83,9 @@ under which this application will be deployed.
![GitLab GKE cluster details](img/guide_gitlab_gke_details.png)
1. Once ready, click **Create Kubernetes cluster**.
+
+NOTE: **Note:**
+Do not select `f1-micro` from the **Machine type** dropdown. `f1-micro` machines cannot support a full GitLab installation.
After a couple of minutes, the cluster will be created. You can also see its
status on your [GCP dashboard](https://console.cloud.google.com/kubernetes).
diff --git a/doc/university/README.md b/doc/university/README.md
index 5edf92b3b09..f19b1ffd3d9 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -205,7 +205,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
### 4. External Articles
-1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
+1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/)
1. [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/)
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 6ff27e495fb..6e0f71017c6 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -303,7 +303,7 @@ A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) use
### Jenkins
-An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins-ci.org/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html).
+An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins.io/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html).
### Jira
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index f5ea350a58f..9e2434c02ec 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -176,8 +176,8 @@ Clicking on the current board name in the upper left corner will reveal a
menu from where you can create another Issue Board and rename or delete the
existing one.
-Clicking on the main issue board link will take you to the last board
-you visited.
+When you're revisiting an issue board in a project or group with multiple boards,
+GitLab will automatically load the last board you visited.
NOTE: **Note:**
The Multiple Issue Boards feature is available for
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index e1eede8bbed..89b9621b8b9 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -212,7 +212,7 @@ security measure, necessary just for big companies, like banks and shoppings sit
with financial transactions.
Now we have a different picture. [According to Josh Aas](https://letsencrypt.org/2015/10/29/phishing-and-malware.html), Executive Director at [ISRG](https://en.wikipedia.org/wiki/Internet_Security_Research_Group):
-> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](http://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._
+> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](https://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._
Therefore, the reason why certificates are so important is that they encrypt
the connection between the **client** (you, me, your visitors)
diff --git a/doc/user/search/img/issues_filter_none_any.png b/doc/user/search/img/issues_filter_none_any.png
new file mode 100644
index 00000000000..9682fc55315
--- /dev/null
+++ b/doc/user/search/img/issues_filter_none_any.png
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 4f1b96b775c..3f9d07dacaa 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -40,6 +40,16 @@ The same process is valid for merge requests. Navigate to your project's **Merge
and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
milestone, and label.
+### Filtering by **None** / **Any**
+
+Some filter fields like milestone and assignee, allow you to filter by **None** or **Any**.
+
+![filter by none any](img/issues_filter_none_any.png)
+
+Selecting **None** returns results that have an empty value for that field. E.g.: no milestone, no assignee.
+
+Selecting **Any** does the opposite. It returns results that have a non-empty value for that field.
+
### Searching for specific terms
You can filter issues and merge requests by specific terms included in titles or descriptions.
diff --git a/lib/api/validations/types/safe_file.rb b/lib/api/validations/types/safe_file.rb
new file mode 100644
index 00000000000..53b5790bfa2
--- /dev/null
+++ b/lib/api/validations/types/safe_file.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# This module overrides the Grape type validator defined in
+# https://github.com/ruby-grape/grape/blob/master/lib/grape/validations/types/file.rb
+module API
+ module Validations
+ module Types
+ class SafeFile < ::Grape::Validations::Types::File
+ def value_coerced?(value)
+ super && value[:tempfile].is_a?(Tempfile)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 6e1d4eb335f..24746f4efc6 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -6,7 +6,7 @@ module API
def commit_params(attrs)
{
file_name: attrs[:file][:filename],
- file_content: File.read(attrs[:file][:tempfile]),
+ file_content: attrs[:file][:tempfile].read,
branch_name: attrs[:branch]
}
end
@@ -100,7 +100,7 @@ module API
success Entities::WikiAttachment
end
params do
- requires :file, type: File, desc: 'The attachment file to be uploaded'
+ requires :file, type: ::API::Validations::Types::SafeFile, desc: 'The attachment file to be uploaded'
optional :branch, type: String, desc: 'The name of the branch'
end
post ":id/wikis/attachments", requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index d2029a141e7..6eb5f9e2300 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -151,17 +151,15 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def personal_access_token_check(password)
return unless password.present?
- token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
+ token = PersonalAccessTokensFinder.new(state: 'active').find_by_token(password)
if token && valid_scoped_token?(token, available_scopes)
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def valid_oauth_token?(token)
token && token.accessible? && valid_scoped_token?(token, [:api])
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
index 5df6db6f366..c304adc64db 100644
--- a/lib/gitlab/auth/user_auth_finders.rb
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -73,7 +73,6 @@ module Gitlab
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def find_personal_access_token
token =
current_request.params[PRIVATE_TOKEN_PARAM].presence ||
@@ -82,9 +81,8 @@ module Gitlab
return unless token
# Expiration, revocation and scopes are verified in `validate_access_token!`
- PersonalAccessToken.find_by(token: token) || raise(UnauthorizedError)
+ PersonalAccessToken.find_by_token(token) || raise(UnauthorizedError)
end
- # rubocop: enable CodeReuse/ActiveRecord
def find_oauth_access_token
token = Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods)
diff --git a/lib/gitlab/background_migration/digest_column.rb b/lib/gitlab/background_migration/digest_column.rb
new file mode 100644
index 00000000000..22a3bb8f8f3
--- /dev/null
+++ b/lib/gitlab/background_migration/digest_column.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# rubocop:disable Style/Documentation
+module Gitlab
+ module BackgroundMigration
+ class DigestColumn
+ class PersonalAccessToken < ActiveRecord::Base
+ self.table_name = 'personal_access_tokens'
+ end
+
+ def perform(model, attribute_from, attribute_to, start_id, stop_id)
+ model = model.constantize if model.is_a?(String)
+
+ model.transaction do
+ relation = model.where(id: start_id..stop_id).where.not(attribute_from => nil).lock
+
+ relation.each do |instance|
+ instance.update_columns(attribute_to => Gitlab::CryptoHelper.sha256(instance.read_attribute(attribute_from)),
+ attribute_from => nil)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/redact_links.rb b/lib/gitlab/background_migration/redact_links.rb
new file mode 100644
index 00000000000..f5d3bcdd517
--- /dev/null
+++ b/lib/gitlab/background_migration/redact_links.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class RedactLinks
+ module Redactable
+ extend ActiveSupport::Concern
+
+ def redact_field!(field)
+ self[field].gsub!(%r{/sent_notifications/\h{32}/unsubscribe}, '/sent_notifications/REDACTED/unsubscribe')
+
+ if self.changed?
+ self.update_columns(field => self[field],
+ "#{field}_html" => nil)
+ end
+ end
+ end
+
+ class Note < ActiveRecord::Base
+ include EachBatch
+ include Redactable
+
+ self.table_name = 'notes'
+ self.inheritance_column = :_type_disabled
+ end
+
+ class Issue < ActiveRecord::Base
+ include EachBatch
+ include Redactable
+
+ self.table_name = 'issues'
+ self.inheritance_column = :_type_disabled
+ end
+
+ class MergeRequest < ActiveRecord::Base
+ include EachBatch
+ include Redactable
+
+ self.table_name = 'merge_requests'
+ self.inheritance_column = :_type_disabled
+ end
+
+ class Snippet < ActiveRecord::Base
+ include EachBatch
+ include Redactable
+
+ self.table_name = 'snippets'
+ self.inheritance_column = :_type_disabled
+ end
+
+ def perform(model_name, field, start_id, stop_id)
+ link_pattern = "%/sent_notifications/" + ("_" * 32) + "/unsubscribe%"
+ model = "Gitlab::BackgroundMigration::RedactLinks::#{model_name}".constantize
+
+ model.where("#{field} like ?", link_pattern).where(id: start_id..stop_id).each do |resource|
+ resource.redact_field!(field)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index b369b9e7600..dfbb83f7bb9 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -42,7 +42,7 @@ module Gitlab
end
def self.cache_key_for_project(project)
- "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status"
+ "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status/#{project.commit&.sha}"
end
def self.update_for_pipeline(pipeline)
@@ -84,9 +84,7 @@ module Gitlab
def load_from_project
return unless commit
- self.sha = commit.sha
- self.status = commit.status
- self.ref = project.default_branch
+ self.sha, self.status, self.ref = commit.sha, commit.status, project.default_branch
end
# We only cache the status for the HEAD commit of a project
@@ -104,6 +102,8 @@ module Gitlab
def load_from_cache
Gitlab::Redis::Cache.with do |redis|
self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)
+
+ self.status = nil if self.status.empty?
end
end
diff --git a/lib/gitlab/ci/parsers/test/junit.rb b/lib/gitlab/ci/parsers/test/junit.rb
index 5d7d9a751d8..ed5a79d9b9b 100644
--- a/lib/gitlab/ci/parsers/test/junit.rb
+++ b/lib/gitlab/ci/parsers/test/junit.rb
@@ -14,10 +14,10 @@ module Gitlab
test_case = create_test_case(test_case)
test_suite.add_test_case(test_case)
end
- rescue REXML::ParseException => e
- raise JunitParserError, "XML parsing failed: #{e.message}"
- rescue => e
- raise JunitParserError, "JUnit parsing failed: #{e.message}"
+ rescue REXML::ParseException
+ raise JunitParserError, "XML parsing failed"
+ rescue
+ raise JunitParserError, "JUnit parsing failed"
end
private
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index 50b0d044265..4babc23a495 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -11,7 +11,8 @@ module Gitlab
runner_system_failure: 'runner system failure',
missing_dependency_failure: 'missing dependency failure',
runner_unsupported: 'unsupported runner',
- stale_schedule: 'stale schedule'
+ stale_schedule: 'stale schedule',
+ job_execution_timeout: 'job execution timeout'
}.freeze
private_constant :REASONS
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 6fa59e41d20..db48b187e5e 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -210,7 +210,7 @@ container_scanning:
refs:
- branches
variables:
- - $GITLAB_FEATURES =~ /\bsast_container\b/
+ - $GITLAB_FEATURES =~ /\bcontainer_scanning\b/
except:
variables:
- $CONTAINER_SCANNING_DISABLED
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index c819bffdfd6..5ed6427072a 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -73,7 +73,7 @@ module Gitlab
# re-running the contributed projects query in each union is expensive, so
# use IN(project_ids...) instead. It's the intersection of two users so
# the list will be (relatively) short
- @contributed_project_ids ||= projects.uniq.pluck(:id)
+ @contributed_project_ids ||= projects.distinct.pluck(:id)
authed_projects = Project.where(id: @contributed_project_ids)
.with_feature_available_for_user(feature, current_user)
.reorder(nil)
diff --git a/lib/gitlab/crypto_helper.rb b/lib/gitlab/crypto_helper.rb
new file mode 100644
index 00000000000..68d0b5d8f8a
--- /dev/null
+++ b/lib/gitlab/crypto_helper.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CryptoHelper
+ extend self
+
+ AES256_GCM_OPTIONS = {
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ iv: Settings.attr_encrypted_db_key_base_truncated[0..11]
+ }.freeze
+
+ def sha256(value)
+ salt = Settings.attr_encrypted_db_key_base_truncated
+ ::Digest::SHA256.base64digest("#{value}#{salt}")
+ end
+
+ def aes256_gcm_encrypt(value)
+ encrypted_token = Encryptor.encrypt(AES256_GCM_OPTIONS.merge(value: value))
+ Base64.encode64(encrypted_token)
+ end
+
+ def aes256_gcm_decrypt(value)
+ return unless value
+
+ encrypted_token = Base64.decode64(value)
+ Encryptor.decrypt(AES256_GCM_OPTIONS.merge(value: encrypted_token))
+ end
+ end
+end
diff --git a/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb b/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb
index 0014ce2689b..41004408dec 100644
--- a/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb
+++ b/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb
@@ -6,7 +6,7 @@ module Gitlab
def call(severity, datetime, _, data)
time = data.delete :time
- data[:params] = utf8_encode_values(data[:params]) if data.has_key?(:params)
+ data[:params] = process_params(data)
attributes = {
time: datetime.utc.iso8601(3),
@@ -20,6 +20,14 @@ module Gitlab
private
+ def process_params(data)
+ return [] unless data.has_key?(:params)
+
+ data[:params]
+ .each_pair
+ .map { |k, v| { key: k, value: utf8_encode_values(v) } }
+ end
+
def utf8_encode_values(data)
case data
when Hash
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 7735b736689..86efe8ad114 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -32,6 +32,7 @@ module Gitlab
end
validate_localhost!(addrs_info) unless allow_localhost
+ validate_loopback!(addrs_info) unless allow_localhost
validate_local_network!(addrs_info) unless allow_local_network
validate_link_local!(addrs_info) unless allow_local_network
@@ -86,6 +87,12 @@ module Gitlab
raise BlockedUrlError, "Requests to localhost are not allowed"
end
+ def validate_loopback!(addrs_info)
+ return unless addrs_info.any? { |addr| addr.ipv4_loopback? || addr.ipv6_loopback? }
+
+ raise BlockedUrlError, "Requests to loopback addresses are not allowed"
+ end
+
def validate_local_network!(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? }
diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake
index 81829668de8..eec024f9bbb 100644
--- a/lib/tasks/tokens.rake
+++ b/lib/tasks/tokens.rake
@@ -1,4 +1,7 @@
require_relative '../../app/models/concerns/token_authenticatable.rb'
+require_relative '../../app/models/concerns/token_authenticatable_strategies/base.rb'
+require_relative '../../app/models/concerns/token_authenticatable_strategies/insecure.rb'
+require_relative '../../app/models/concerns/token_authenticatable_strategies/digest.rb'
namespace :tokens do
desc "Reset all GitLab incoming email tokens"
@@ -26,13 +29,6 @@ class TmpUser < ActiveRecord::Base
self.table_name = 'users'
- def reset_incoming_email_token!
- write_new_token(:incoming_email_token)
- save!(validate: false)
- end
-
- def reset_feed_token!
- write_new_token(:feed_token)
- save!(validate: false)
- end
+ add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
+ add_authentication_token_field :feed_token
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 26270595c6a..18038d84d45 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -648,6 +648,9 @@ msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
msgid "Are you sure?"
msgstr ""
@@ -2324,6 +2327,12 @@ msgstr ""
msgid "DeployTokens|Your new project deploy token has been created."
msgstr ""
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
msgid "Deprioritize label"
msgstr ""
@@ -2750,6 +2759,9 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to deploy to"
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
@@ -3002,9 +3014,15 @@ msgstr ""
msgid "Group Runners"
msgstr ""
+msgid "Group URL"
+msgstr ""
+
msgid "Group avatar"
msgstr ""
+msgid "Group description"
+msgstr ""
+
msgid "Group description (optional)"
msgstr ""
@@ -4030,9 +4048,6 @@ msgstr ""
msgid "No"
msgstr ""
-msgid "No Assignee"
-msgstr ""
-
msgid "No Label"
msgstr ""
@@ -4479,15 +4494,24 @@ msgstr ""
msgid "Pipelines|This project is not currently set up to run pipelines."
msgstr ""
+msgid "Pipeline|Commit"
+msgstr ""
+
msgid "Pipeline|Create for"
msgstr ""
msgid "Pipeline|Create pipeline"
msgstr ""
+msgid "Pipeline|Duration"
+msgstr ""
+
msgid "Pipeline|Existing branch name or tag"
msgstr ""
+msgid "Pipeline|Pipeline"
+msgstr ""
+
msgid "Pipeline|Run Pipeline"
msgstr ""
@@ -4497,6 +4521,12 @@ msgstr ""
msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
msgstr ""
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
msgid "Pipeline|Stop pipeline"
msgstr ""
@@ -4533,12 +4563,18 @@ msgstr ""
msgid "Please accept the Terms of Service before continuing."
msgstr ""
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
@@ -4890,6 +4926,9 @@ msgstr ""
msgid "Projects shared with %{group_name}"
msgstr ""
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -5607,6 +5646,9 @@ msgstr ""
msgid "Something went wrong while fetching comments. Please try again."
msgstr ""
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
msgid "Something went wrong while fetching the projects."
msgstr ""
@@ -5784,6 +5826,12 @@ msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
msgid "Start the Runner!"
msgstr ""
@@ -6771,6 +6819,9 @@ msgstr ""
msgid "View replaced file @ "
msgstr ""
+msgid "View the documentation"
+msgstr ""
+
msgid "Visibility and access controls"
msgstr ""
@@ -6822,6 +6873,9 @@ msgstr ""
msgid "Who can see this group?"
msgstr ""
+msgid "Who will be able to see this group?"
+msgstr ""
+
msgid "Wiki"
msgstr ""
@@ -6996,9 +7050,6 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
-msgid "You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}"
-msgstr ""
-
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -7020,6 +7071,9 @@ msgstr ""
msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
msgstr ""
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
diff --git a/package.json b/package.json
index 086617dc265..d418147b92b 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@gitlab-org/gitlab-svgs": "^1.33.0",
- "@gitlab-org/gitlab-ui": "^1.8.0",
+ "@gitlab-org/gitlab-ui": "^1.9.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.4",
diff --git a/qa/qa.rb b/qa/qa.rb
index 968687dab67..7be7194f7b9 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -53,7 +53,7 @@ module QA
autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :DeployToken, 'qa/factory/resource/deploy_token'
autoload :Branch, 'qa/factory/resource/branch'
- autoload :SecretVariable, 'qa/factory/resource/secret_variable'
+ autoload :CiVariable, 'qa/factory/resource/ci_variable'
autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster'
@@ -183,7 +183,7 @@ module QA
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
autoload :DeployTokens, 'qa/page/project/settings/deploy_tokens'
autoload :ProtectedBranches, 'qa/page/project/settings/protected_branches'
- autoload :SecretVariables, 'qa/page/project/settings/secret_variables'
+ autoload :CiVariables, 'qa/page/project/settings/ci_variables'
autoload :Runners, 'qa/page/project/settings/runners'
autoload :MergeRequest, 'qa/page/project/settings/merge_request'
autoload :Members, 'qa/page/project/settings/members'
diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb
index 703c78daa99..ffa755b9e88 100644
--- a/qa/qa/factory/repository/push.rb
+++ b/qa/qa/factory/repository/push.rb
@@ -45,7 +45,7 @@ module QA
repository.use_ssh_key(ssh_key)
else
repository.uri = repository_http_uri
- repository.use_default_credentials
+ repository.use_default_credentials unless user
end
username = 'GitLab QA'
diff --git a/qa/qa/factory/resource/secret_variable.rb b/qa/qa/factory/resource/ci_variable.rb
index 24ba3408810..a0aefc61f9f 100644
--- a/qa/qa/factory/resource/secret_variable.rb
+++ b/qa/qa/factory/resource/ci_variable.rb
@@ -1,13 +1,13 @@
module QA
module Factory
module Resource
- class SecretVariable < Factory::Base
+ class CiVariable < Factory::Base
attr_accessor :key, :value
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
- resource.name = 'project-with-secret-variables'
- resource.description = 'project for adding secret variable test'
+ resource.name = 'project-with-ci-variables'
+ resource.description = 'project for adding CI variable test'
end
end
@@ -17,7 +17,7 @@ module QA
Page::Project::Menu.perform(&:click_ci_cd_settings)
Page::Project::Settings::CICD.perform do |setting|
- setting.expand_secret_variables do |page|
+ setting.expand_ci_variables do |page|
page.fill_variable(key, value)
page.save_variables
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 1062f0b2dbb..de18b9cefa6 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -7,35 +7,42 @@ module QA
class Show < Page::Base
include Page::Component::Issuable::Common
- view 'app/views/projects/issues/show.html.haml' do
- element :issue_details, '.issue-details' # rubocop:disable QA/ElementWithPattern
- element :title, '.title' # rubocop:disable QA/ElementWithPattern
- end
-
view 'app/views/shared/notes/_form.html.haml' do
element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern
element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/shared/notes/_comment_button.html.haml' do
- element :comment_button, '%strong Comment' # rubocop:disable QA/ElementWithPattern
+ view 'app/assets/javascripts/notes/components/comment_form.vue' do
+ element :comment_button
+ element :comment_input
end
- def issue_title
- find('.issue-details .title').text
+ view 'app/assets/javascripts/notes/components/discussion_filter.vue' do
+ element :discussion_filter
+ element :filter_options
end
# Adds a comment to an issue
# attachment option should be an absolute path
def comment(text, attachment: nil)
- fill_in(with: text, name: 'note[note]')
+ fill_element :comment_input, text
unless attachment.nil?
QA::Page::Component::Dropzone.new(self, '.new-note')
.attach_file(attachment)
end
- click_on 'Comment'
+ click_element :comment_button
+ end
+
+ def select_comments_only_filter
+ click_element :discussion_filter
+ all_elements(:filter_options).last.click
+ end
+
+ def select_all_activities_filter
+ click_element :discussion_filter
+ all_elements(:filter_options).first.click
end
end
end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index cc5fc370a5a..12c2409a5a7 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -25,9 +25,9 @@ module QA # rubocop:disable Naming/FileName
end
end
- def expand_secret_variables(&block)
+ def expand_ci_variables(&block)
expand_section(:variables_settings) do
- Settings::SecretVariables.perform(&block)
+ Settings::CiVariables.perform(&block)
end
end
diff --git a/qa/qa/page/project/settings/secret_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index 6a87ef472e4..e7a6e4bf628 100644
--- a/qa/qa/page/project/settings/secret_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -2,7 +2,7 @@ module QA
module Page
module Project
module Settings
- class SecretVariables < Page::Base
+ class CiVariables < Page::Base
include Common
view 'app/views/ci/variables/_variable_row.html.haml' do
@@ -12,7 +12,7 @@ module QA
end
view 'app/views/ci/variables/_index.html.haml' do
- element :save_variables, '.js-secret-variables-save-button' # rubocop:disable QA/ElementWithPattern
+ element :save_variables, '.js-ci-variables-save-button' # rubocop:disable QA/ElementWithPattern
element :reveal_values, '.js-secret-value-reveal-button' # rubocop:disable QA/ElementWithPattern
end
@@ -33,7 +33,7 @@ module QA
end
def save_variables
- find('.js-secret-variables-save-button').click
+ find('.js-ci-variables-save-button').click
end
def reveal_variables
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
new file mode 100644
index 00000000000..24877d937d2
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'filter issue comments activities' do
+ let(:issue_title) { 'issue title' }
+
+ it 'user filters comments and activites in an issue' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ Factory::Resource::Issue.fabricate! do |issue|
+ issue.title = issue_title
+ end
+
+ expect(page).to have_content(issue_title)
+
+ Page::Project::Issue::Show.perform do |show_page|
+ show_page.select_comments_only_filter
+ show_page.comment('/confidential')
+ show_page.comment('My own comment')
+
+ expect(show_page).not_to have_content("made the issue confidential")
+ expect(show_page).to have_content("My own comment")
+
+ show_page.select_all_activities_filter
+
+ expect(show_page).to have_content("made the issue confidential")
+ expect(show_page).to have_content("My own comment")
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 46e1005829d..724c48cd125 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -25,6 +25,7 @@ module QA
push.file_content = "Test with unicode characters ❤✓€❄"
end
+ Page::Project::Show.perform(&:wait_for_push)
merge_request.visit!
expect(page).to have_text('to be squashed')
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
new file mode 100644
index 00000000000..8e4210482a2
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Git push over HTTP', :ldap_no_tls do
+ it 'user using a personal access token pushes code to the repository' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ access_token = Factory::Resource::PersonalAccessToken.fabricate!.access_token
+
+ user = Factory::Resource::User.new.tap do |user|
+ user.username = Runtime::User.username
+ user.password = access_token
+ end
+
+ push = Factory::Repository::ProjectPush.fabricate! do |push|
+ push.user = user
+ push.file_name = 'README.md'
+ push.file_content = '# This is a test project'
+ push.commit_message = 'Add README.md'
+ end
+
+ push.project.visit!
+ Page::Project::Show.perform(&:wait_for_push)
+
+ expect(page).to have_content('README.md')
+ expect(page).to have_content('This is a test project')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
index 292f24d9c0d..58b272adcf1 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
@@ -2,24 +2,24 @@
module QA
context 'Verify' do
- describe 'Secret variable support' do
- it 'user adds a secret variable' do
+ describe 'CI variable support' do
+ it 'user adds a CI variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- Factory::Resource::SecretVariable.fabricate! do |resource|
+ Factory::Resource::CiVariable.fabricate! do |resource|
resource.key = 'VARIABLE_KEY'
- resource.value = 'some secret variable'
+ resource.value = 'some CI variable'
end
Page::Project::Settings::CICD.perform do |settings|
- settings.expand_secret_variables do |page|
+ settings.expand_ci_variables do |page|
expect(page).to have_field(with: 'VARIABLE_KEY')
- expect(page).not_to have_field(with: 'some secret variable')
+ expect(page).not_to have_field(with: 'some CI variable')
page.reveal_variables
- expect(page).to have_field(with: 'some secret variable')
+ expect(page).to have_field(with: 'some CI variable')
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index caf014c89ea..604641e54b8 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -55,7 +55,7 @@ module QA
deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
- Factory::Resource::SecretVariable.fabricate! do |resource|
+ Factory::Resource::CiVariable.fabricate! do |resource|
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 40cae0793dd..c2fce1e7df1 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -22,7 +22,7 @@ module QA
# Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test
- Factory::Resource::SecretVariable.fabricate! do |resource|
+ Factory::Resource::CiVariable.fabricate! do |resource|
resource.project = project
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index be3fc832008..4e91068ab88 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -792,4 +792,30 @@ describe ApplicationController do
end
end
end
+
+ context 'control headers' do
+ controller(described_class) do
+ def index
+ render json: :ok
+ end
+ end
+
+ context 'user not logged in' do
+ it 'sets the default headers' do
+ get :index
+
+ expect(response.headers['Cache-Control']).to be_nil
+ end
+ end
+
+ context 'user logged in' do
+ it 'sets the default headers' do
+ sign_in(user)
+
+ get :index
+
+ expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store'
+ end
+ end
+ end
end
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index 56047c0c8d2..278b980b6d8 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -45,6 +45,8 @@ describe Dashboard::MilestonesController do
end
describe "#index" do
+ render_views
+
it 'returns group and project milestones to which the user belongs' do
get :index, format: :json
@@ -53,5 +55,12 @@ describe Dashboard::MilestonesController do
expect(json_response.map { |i| i["first_milestone"]["id"] }).to match_array([group_milestone.id, project_milestone.id])
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
end
+
+ it 'should contain group and project milestones to which the user belongs to' do
+ get :index
+
+ expect(response.body).to include("Open\n<span class=\"badge badge-pill\">3</span>")
+ expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">0</span>")
+ end
end
end
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index d1d08391164..f7a4a4192d6 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -37,7 +37,7 @@ describe Groups::BoardsController do
allow(visited).to receive(:board_id).and_return(12)
allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited)
- list_boards
+ list_boards format: :html
expect(response).to render_template :index
expect(response.content_type).to eq 'text/html'
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 28f7e4634a5..64b589a6d83 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -331,10 +331,10 @@ describe Projects::BlobController do
expect(response).to redirect_to(
project_new_merge_request_path(
forked_project,
+ merge_request_source_branch: "fork-test-1",
merge_request: {
source_project_id: forked_project.id,
target_project_id: project.id,
- source_branch: "fork-test-1",
target_branch: "master"
}
)
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index dcfd6c05200..7b0459e0325 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -749,13 +749,15 @@ describe Projects::MergeRequestsController do
describe 'GET ci_environments_status' do
context 'the environment is from a forked project' do
- let!(:forked) { fork_project(project, user, repository: true) }
- let!(:environment) { create(:environment, project: forked) }
- let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
- let(:admin) { create(:admin) }
+ let(:forked) { fork_project(project, user, repository: true) }
+ let(:sha) { forked.commit.sha }
+ let(:environment) { create(:environment, project: forked) }
+ let(:pipeline) { create(:ci_pipeline, sha: sha, project: forked) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: 'master', deployable: build) }
let(:merge_request) do
- create(:merge_request, source_project: forked, target_project: project)
+ create(:merge_request, source_project: forked, target_project: project, target_branch: 'master', head_pipeline: pipeline)
end
it 'links to the environment on that project' do
@@ -764,6 +766,35 @@ describe Projects::MergeRequestsController do
expect(json_response.first['url']).to match /#{forked.full_path}/
end
+ context "when environment_target is 'merge_commit'" do
+ it 'returns nothing' do
+ get_ci_environments_status(environment_target: 'merge_commit')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_empty
+ end
+
+ context 'when is merged' do
+ let(:source_environment) { create(:environment, project: project) }
+ let(:merge_commit_sha) { project.repository.merge(user, forked.commit.id, merge_request, "merged in test") }
+ let(:post_merge_pipeline) { create(:ci_pipeline, sha: merge_commit_sha, project: project) }
+ let(:post_merge_build) { create(:ci_build, pipeline: post_merge_pipeline) }
+ let!(:source_deployment) { create(:deployment, environment: source_environment, sha: merge_commit_sha, ref: 'master', deployable: post_merge_build) }
+
+ before do
+ merge_request.update!(merge_commit_sha: merge_commit_sha)
+ merge_request.mark_as_merged!
+ end
+
+ it 'returns the enviroment on the source project' do
+ get_ci_environments_status(environment_target: 'merge_commit')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['url']).to match /#{project.full_path}/
+ end
+ end
+ end
+
# we're trying to reduce the overall number of queries for this method.
# set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/52287
it 'keeps queries in check' do
@@ -772,11 +803,15 @@ describe Projects::MergeRequestsController do
expect(control_count).to be <= 137
end
- def get_ci_environments_status
- get :ci_environments_status,
+ def get_ci_environments_status(extra_params = {})
+ params = {
namespace_id: merge_request.project.namespace.to_param,
project_id: merge_request.project,
- id: merge_request.iid, format: 'json'
+ id: merge_request.iid,
+ format: 'json'
+ }
+
+ get :ci_environments_status, params.merge(extra_params)
end
end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index bcf289f36a9..6420b70a54f 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -5,6 +5,17 @@ shared_examples 'content not cached without revalidation' do
end
end
+shared_examples 'content not cached without revalidation and no-store' do
+ it 'ensures content will not be cached without revalidation' do
+ # Fixed in newer versions of ActivePack, it will only output a single `private`.
+ if Gitlab.rails5?
+ expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store')
+ else
+ expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, private, no-store')
+ end
+ end
+end
+
describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
@@ -177,7 +188,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content not cached without revalidation and no-store' do
subject do
get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png'
@@ -239,7 +250,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content not cached without revalidation and no-store' do
subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
@@ -292,7 +303,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content not cached without revalidation and no-store' do
subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
@@ -344,7 +355,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content not cached without revalidation and no-store' do
subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
@@ -388,7 +399,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content not cached without revalidation and no-store' do
subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
@@ -445,7 +456,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content not cached without revalidation and no-store' do
subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
@@ -498,7 +509,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content not cached without revalidation and no-store' do
subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index bbeba8ce8b9..c9f5d0a813e 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -2,13 +2,28 @@ FactoryBot.define do
factory :cluster, class: Clusters::Cluster do
user
name 'test-cluster'
+ cluster_type :project_type
+
+ trait :instance do
+ cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
+ end
trait :project do
+ cluster_type { Clusters::Cluster.cluster_types[:project_type] }
+
before(:create) do |cluster, evaluator|
cluster.projects << create(:project, :repository)
end
end
+ trait :group do
+ cluster_type { Clusters::Cluster.cluster_types[:group_type] }
+
+ before(:create) do |cluster, evalutor|
+ cluster.groups << create(:group)
+ end
+ end
+
trait :provided_by_user do
provider_type :user
platform_type :kubernetes
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 96dfde2e08c..735ca60f7da 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -53,13 +53,33 @@ describe 'Admin Groups' do
expect_selected_visibility(internal)
end
- it 'when entered in group path, it auto filled the group name', :js do
+ it 'when entered in group name, it auto filled the group path', :js do
visit admin_groups_path
click_link "New group"
- group_path = 'gitlab'
+ group_name = 'gitlab'
+ fill_in 'group_name', with: group_name
+ path_field = find('input#group_path')
+ expect(path_field.value).to eq group_name
+ end
+
+ it 'auto populates the group path with the group name', :js do
+ visit admin_groups_path
+ click_link "New group"
+ group_name = 'my gitlab project'
+ fill_in 'group_name', with: group_name
+ path_field = find('input#group_path')
+ expect(path_field.value).to eq 'my-gitlab-project'
+ end
+
+ it 'when entering in group path, group name does not change anymore', :js do
+ visit admin_groups_path
+ click_link "New group"
+ group_path = 'my-gitlab-project'
+ group_name = 'My modified gitlab project'
fill_in 'group_path', with: group_path
- name_field = find('input#group_name')
- expect(name_field.value).to eq group_path
+ fill_in 'group_name', with: group_name
+ path_field = find('input#group_path')
+ expect(path_field.value).to eq 'my-gitlab-project'
end
end
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 615223a2a88..2cdd3f55b50 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -106,7 +106,7 @@ describe 'Issue Boards add issue modal filtering', :js do
it 'filters by unassigned' do
set_filter('assignee')
- click_filter_link('No Assignee')
+ click_filter_link('None')
submit_filter
page.within('.add-issues-modal') do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 8989b2051bb..5c6c1c4fd15 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -114,33 +114,6 @@ describe 'Commits' do
expect(page).to have_content 'canceled'
end
end
-
- describe '.gitlab-ci.yml not found warning' do
- context 'ci builds enabled' do
- it "does not show warning" do
- visit pipeline_path(pipeline)
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
-
- it 'shows warning' do
- stub_ci_pipeline_yaml_file(nil)
- visit pipeline_path(pipeline)
- expect(page).to have_content '.gitlab-ci.yml not found in this commit'
- end
- end
-
- context 'ci builds disabled' do
- before do
- stub_ci_builds_disabled
- stub_ci_pipeline_yaml_file(nil)
- visit pipeline_path(pipeline)
- end
-
- it 'does not show warning' do
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
- end
- end
end
context "when logged as reporter" do
@@ -182,6 +155,39 @@ describe 'Commits' do
end
end
end
+
+ describe '.gitlab-ci.yml not found warning' do
+ before do
+ project.add_reporter(user)
+ end
+
+ context 'ci builds enabled' do
+ it 'does not show warning' do
+ visit pipeline_path(pipeline)
+
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+
+ it 'shows warning' do
+ stub_ci_pipeline_yaml_file(nil)
+
+ visit pipeline_path(pipeline)
+
+ expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+
+ context 'ci builds disabled' do
+ it 'does not show warning' do
+ stub_ci_builds_disabled
+ stub_ci_pipeline_yaml_file(nil)
+
+ visit pipeline_path(pipeline)
+
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+ end
end
context 'viewing commits for a branch' do
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index e57fcde8b2c..259f220c68b 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -14,15 +14,15 @@ RSpec.describe 'Dashboard Group' do
it 'creates new group', :js do
visit dashboard_groups_path
find('.btn-success').click
- new_path = 'Samurai'
+ new_name = 'Samurai'
new_description = 'Tokugawa Shogunate'
- fill_in 'group_path', with: new_path
+ fill_in 'group_name', with: new_name
fill_in 'group_description', with: new_description
click_button 'Create group'
- expect(current_path).to eq group_path(Group.find_by(name: new_path))
- expect(page).to have_content(new_path)
+ expect(current_path).to eq group_path(Group.find_by(name: new_name))
+ expect(page).to have_content(new_name)
expect(page).to have_content(new_description)
end
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 975b7944741..0a24c5e906a 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -147,10 +147,12 @@ describe 'Dashboard Projects' do
end
context 'last push widget', :use_clean_rails_memory_store_caching do
+ let(:ref) { "feature" }
+
before do
event = create(:push_event, project: project, author: user)
- create(:push_event_payload, event: event, ref: 'feature', action: :created)
+ create(:push_event_payload, event: event, ref: ref, action: :created)
Users::LastPushEventService.new(user).cache_last_push_event(event)
@@ -165,9 +167,9 @@ describe 'Dashboard Projects' do
end
expect(page).to have_selector('.merge-request-form')
- expect(current_path).to eq project_new_merge_request_path(project)
+ expect(current_path).to eq project_new_merge_request_path(project, merge_request_source_branch: ref)
expect(find('#merge_request_target_project_id', visible: false).value).to eq project.id.to_s
- expect(find('input#merge_request_source_branch', visible: false).value).to eq 'feature'
+ expect(find('input#merge_request_source_branch', visible: false).value).to eq ref
expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master'
end
end
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
index 11f05b6d220..259f22139ef 100644
--- a/spec/features/explore/new_menu_spec.rb
+++ b/spec/features/explore/new_menu_spec.rb
@@ -29,7 +29,7 @@ describe 'Top Plus Menu', :js do
click_topmenuitem("New group")
- expect(page).to have_content('Group path')
+ expect(page).to have_content('Group URL')
expect(page).to have_content('Group name')
end
@@ -79,7 +79,7 @@ describe 'Top Plus Menu', :js do
click_topmenuitem("New subgroup")
- expect(page).to have_content('Group path')
+ expect(page).to have_content('Group URL')
expect(page).to have_content('Group name')
end
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index e8ca6a6714f..174840794ed 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -95,9 +95,9 @@ describe 'Group milestones' do
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")
+ expect(find('.top-area .active .badge').text).to eq("3")
+ expect(find('.top-area .closed .badge').text).to eq("3")
+ expect(find('.top-area .all .badge').text).to eq("6")
end
it 'lists legacy group milestones and group milestones' do
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 63aa26cf5fd..4d04b8043ec 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -7,7 +7,7 @@ describe 'Group' do
matcher :have_namespace_error_message do
match do |page|
- page.has_content?("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.', '.git' or '.atom'.")
+ page.has_content?("Group URL can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.', '.git' or '.atom'.")
end
end
@@ -18,7 +18,7 @@ describe 'Group' do
describe 'with space in group path' do
it 'renders new group form with validation errors' do
- fill_in 'Group path', with: 'space group'
+ fill_in 'Group URL', with: 'space group'
click_button 'Create group'
expect(current_path).to eq(groups_path)
@@ -28,7 +28,7 @@ describe 'Group' do
describe 'with .atom at end of group path' do
it 'renders new group form with validation errors' do
- fill_in 'Group path', with: 'atom_group.atom'
+ fill_in 'Group URL', with: 'atom_group.atom'
click_button 'Create group'
expect(current_path).to eq(groups_path)
@@ -38,7 +38,7 @@ describe 'Group' do
describe 'with .git at end of group path' do
it 'renders new group form with validation errors' do
- fill_in 'Group path', with: 'git_group.git'
+ fill_in 'Group URL', with: 'git_group.git'
click_button 'Create group'
expect(current_path).to eq(groups_path)
@@ -94,7 +94,8 @@ describe 'Group' do
end
it 'creates a nested group' do
- fill_in 'Group path', with: 'bar'
+ fill_in 'Group name', with: 'bar'
+ fill_in 'Group URL', with: 'bar'
click_button 'Create group'
expect(current_path).to eq(group_path('foo/bar'))
@@ -112,7 +113,8 @@ describe 'Group' do
visit new_group_path(group, parent_id: group.id)
- fill_in 'Group path', with: 'bar'
+ fill_in 'Group name', with: 'bar'
+ fill_in 'Group URL', with: 'bar'
click_button 'Create group'
expect(current_path).to eq(group_path('foo/bar'))
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index d011d2545bb..e910fb54d23 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -156,13 +156,21 @@ describe 'Dropdown assignee', :js do
expect_filtered_search_input_empty
end
- it 'selects `no assignee`' do
- find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
+ it 'selects `None`' do
+ find('#js-dropdown-assignee .filter-dropdown-item', text: 'None').click
expect(page).to have_css(js_dropdown_assignee, visible: false)
expect_tokens([assignee_token('none')])
expect_filtered_search_input_empty
end
+
+ it 'selects `Any`' do
+ find('#js-dropdown-assignee .filter-dropdown-item', text: 'Any').click
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ expect_tokens([assignee_token('any')])
+ expect_filtered_search_input_empty
+ end
end
describe 'selecting from dropdown without Ajax call' do
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 6ac7ccd00f7..1e1dd5691ab 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -118,7 +118,7 @@ describe 'Visual tokens', :js do
describe 'selecting static option from dropdown' do
before do
- find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'No Assignee').click
+ find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click
end
it 'changes value in visual token' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 08bf9bc7243..593dc6b6690 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -35,6 +35,21 @@ describe 'GFM autocomplete', :js do
expect(page).to have_selector('.atwho-container')
end
+ it 'opens autocomplete menu when field starts with text with item escaping HTML characters' do
+ alert_title = 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;'
+ create(:issue, project: project, title: alert_title)
+
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('#')
+ end
+
+ expect(page).to have_selector('.atwho-container')
+
+ page.within '.atwho-container #at-view-issues' do
+ expect(page.all('li').first.text).to include(alert_title)
+ end
+ end
+
it 'doesnt open autocomplete menu character is prefixed with text' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('testing')
diff --git a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
index 0ccab5b2fac..a124c99ecc8 100644
--- a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
+++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
@@ -9,10 +9,10 @@ describe 'create a merge request, allowing commits from members who can merge to
def visit_new_merge_request
visit project_new_merge_request_path(
source_project,
+ merge_request_source_branch: 'fix',
merge_request: {
source_project_id: source_project.id,
target_project_id: target_project.id,
- source_branch: 'fix',
target_branch: 'master'
})
end
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index f744d7941f5..a298ead43db 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -3,15 +3,19 @@ require 'rails_helper'
describe 'Merge request > User sees deployment widget', :js do
describe 'when deployed to an environment' do
let(:user) { create(:user) }
- let(:project) { merge_request.target_project }
- let(:merge_request) { create(:merge_request, :merged) }
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, :merged, source_project: project) }
let(:environment) { create(:environment, project: project) }
let(:role) { :developer }
- let(:sha) { project.commit('master').id }
- let!(:deployment) { create(:deployment, environment: environment, sha: sha) }
+ let(:ref) { merge_request.target_branch }
+ let(:sha) { project.commit(ref).id }
+ let(:pipeline) { create(:ci_pipeline_without_jobs, sha: sha, project: project, ref: ref) }
+ let(:build) { create(:ci_build, :success, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: ref, deployable: build) }
let!(:manual) { }
before do
+ merge_request.update!(merge_commit_sha: sha)
project.add_user(user, role)
sign_in(user)
visit project_merge_request_path(project, merge_request)
@@ -26,15 +30,10 @@ describe 'Merge request > User sees deployment widget', :js do
end
context 'with stop action' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
let(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
- let(:deployment) do
- create(:deployment, environment: environment, ref: merge_request.target_branch,
- sha: sha, deployable: build, on_stop: 'close_app')
- end
before do
+ deployment.update!(on_stop: manual.name)
wait_for_requests
end
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index f15129759de..d2003b61b2a 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -20,10 +20,10 @@ describe 'Merge request > User sees merge widget', :js do
before do
visit project_new_merge_request_path(
project,
+ merge_request_source_branch: 'feature',
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
- source_branch: 'feature',
target_branch: 'master'
})
end
@@ -40,21 +40,26 @@ describe 'Merge request > User sees merge widget', :js do
context 'view merge request' do
let!(:environment) { create(:environment, project: project) }
+ let(:sha) { project.commit(merge_request.source_branch).sha }
+ let(:pipeline) { create(:ci_pipeline_without_jobs, status: 'success', sha: sha, project: project, ref: merge_request.source_branch) }
+ let(:build) { create(:ci_build, :success, pipeline: pipeline) }
let!(:deployment) do
create(:deployment, environment: environment,
- ref: 'feature',
- sha: merge_request.diff_head_sha)
+ ref: merge_request.source_branch,
+ deployable: build,
+ sha: sha)
end
before do
+ merge_request.update!(head_pipeline: pipeline)
visit project_merge_request_path(project, merge_request)
end
it 'shows environments link' do
wait_for_requests
- page.within('.mr-widget-heading') do
+ page.within('.js-pre-merge-deploy') do
expect(page).to have_content("Deployed to #{environment.name}")
expect(find('.js-deploy-url')[:href]).to include(environment.formatted_external_url)
end
diff --git a/spec/features/merge_request/user_sees_wip_help_message_spec.rb b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
index 92cc73ddf1f..6dfc819fe8a 100644
--- a/spec/features/merge_request/user_sees_wip_help_message_spec.rb
+++ b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
@@ -13,10 +13,10 @@ describe 'Merge request > User sees WIP help message' do
it 'shows a specific WIP hint' do
visit project_new_merge_request_path(
project,
+ merge_request_source_branch: 'wip',
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
- source_branch: 'wip',
target_branch: 'master'
})
@@ -32,10 +32,10 @@ describe 'Merge request > User sees WIP help message' do
it 'shows the regular WIP message' do
visit project_new_merge_request_path(
project,
+ merge_request_source_branch: 'fix',
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
- source_branch: 'fix',
target_branch: 'master'
})
diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
index ae41cf90576..147544740dc 100644
--- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
+++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
@@ -109,13 +109,13 @@ describe 'Merge request > User selects branches for new MR', :js do
end
it 'populates source branch button' do
- visit project_new_merge_request_path(project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' })
+ visit project_new_merge_request_path(project, change_branches: true, merge_request_source_branch: 'fix', merge_request: { target_branch: 'master' })
expect(find('.js-source-branch')).to have_content('fix')
end
it 'allows to change the diff view' do
- visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'fix' })
+ visit project_new_merge_request_path(project, merge_request_source_branch: 'fix', merge_request: { target_branch: 'master' })
click_link 'Changes'
@@ -131,7 +131,7 @@ describe 'Merge request > User selects branches for new MR', :js do
end
it 'does not allow non-existing branches' do
- visit project_new_merge_request_path(project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
+ visit project_new_merge_request_path(project, merge_request_source_branch: 'non-exist-source', merge_request: { target_branch: 'non-exist-target' })
expect(page).to have_content('The form contains the following errors')
expect(page).to have_content('Source branch "non-exist-source" does not exist')
@@ -140,7 +140,7 @@ describe 'Merge request > User selects branches for new MR', :js do
context 'when a branch contains commits that both delete and add the same image' do
it 'renders the diff successfully' do
- visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
+ visit project_new_merge_request_path(project, merge_request_source_branch: 'deleted-image-test', merge_request: { target_branch: 'master' })
click_link "Changes"
@@ -165,7 +165,8 @@ describe 'Merge request > User selects branches for new MR', :js do
it 'shows pipelines for a new merge request' do
visit project_new_merge_request_path(
project,
- merge_request: { target_branch: 'master', source_branch: 'fix' })
+ merge_request_source_branch: 'fix',
+ merge_request: { target_branch: 'master' })
page.within('.merge-request') do
click_link 'Pipelines'
diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb
index b81478a481f..6e681185e1f 100644
--- a/spec/features/merge_request/user_uses_quick_actions_spec.rb
+++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb
@@ -144,7 +144,7 @@ describe 'Merge request > User uses quick actions', :js do
describe '/target_branch command in merge request' do
let(:another_project) { create(:project, :public, :repository) }
- let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
+ let(:new_url_opts) { { merge_request_source_branch: 'feature' } }
before do
another_project.add_maintainer(user)
diff --git a/spec/features/merge_requests/user_squashes_merge_request_spec.rb b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
index ec1153b7f7f..8ecdec491b8 100644
--- a/spec/features/merge_requests/user_squashes_merge_request_spec.rb
+++ b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
@@ -65,7 +65,7 @@ describe 'User squashes a merge request', :js do
context 'when squash is enabled on merge request creation' do
before do
- visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: source_branch })
+ visit project_new_merge_request_path(project, merge_request_source_branch: source_branch, merge_request: { target_branch: 'master' })
check 'merge_request[squash]'
click_on 'Submit merge request'
wait_for_requests
@@ -95,7 +95,7 @@ describe 'User squashes a merge request', :js do
context 'when squash is not enabled on merge request creation' do
before do
- visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: source_branch })
+ visit project_new_merge_request_path(project, merge_request_source_branch: source_branch, merge_request: { target_branch: 'master' })
click_on 'Submit merge request'
wait_for_requests
end
diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb
index 847b5f0860f..6f620dff82b 100644
--- a/spec/features/projects/files/user_creates_directory_spec.rb
+++ b/spec/features/projects/files/user_creates_directory_spec.rb
@@ -57,7 +57,7 @@ describe 'Projects > Files > User creates a directory', :js do
expect(page).to have_content('From new-feature into master')
expect(page).to have_content('Add new directory')
- expect(current_path).to eq(project_new_merge_request_path(project))
+ expect(current_path).to eq(project_new_merge_request_path(project, merge_request_source_branch: "new-feature"))
end
end
@@ -80,8 +80,7 @@ describe 'Projects > Files > User creates a directory', :js do
click_button('Create directory')
fork = user.fork_of(project2.reload)
-
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork, merge_request_source_branch: "patch-1"))
end
end
end
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index d4dda43c823..d9df4b50621 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -144,7 +144,7 @@ describe 'Projects > Files > User creates files' do
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Commit changes')
- expect(current_path).to eq(project_new_merge_request_path(project))
+ expect(current_path).to eq(project_new_merge_request_path(project, merge_request_source_branch: "new_branch_name"))
click_link('Changes')
@@ -182,7 +182,7 @@ describe 'Projects > Files > User creates files' do
fork = user.fork_of(project2.reload)
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork, merge_request_source_branch: "patch-1"))
expect(page).to have_content('New commit message')
end
end
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 614b11fa5c8..faf11ee9dd8 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -63,7 +63,7 @@ describe 'Projects > Files > User deletes files', :js do
fork = user.fork_of(project2.reload)
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork, merge_request_source_branch: "patch-1"))
expect(page).to have_content('New commit message')
end
end
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 9eb65ec159c..c6b2aaea906 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -86,7 +86,7 @@ describe 'Projects > Files > User edits files', :js do
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Commit changes')
- expect(current_path).to eq(project_new_merge_request_path(project))
+ expect(current_path).to eq(project_new_merge_request_path(project, merge_request_source_branch: "new_branch_name"))
click_link('Changes')
@@ -155,7 +155,7 @@ describe 'Projects > Files > User edits files', :js do
fork = user.fork_of(project2.reload)
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork, merge_request_source_branch: "patch-1"))
wait_for_requests
@@ -183,7 +183,7 @@ describe 'Projects > Files > User edits files', :js do
fork = user.fork_of(project2)
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork, merge_request_source_branch: "patch-1"))
wait_for_requests
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index e3da28d73c3..09feb315465 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -78,7 +78,7 @@ describe 'Projects > Files > User replaces files', :js do
fork = user.fork_of(project2.reload)
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork, merge_request_source_branch: "undefined"))
click_link('Changes')
diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb
index af3fc528a20..92df8303f46 100644
--- a/spec/features/projects/files/user_uploads_files_spec.rb
+++ b/spec/features/projects/files/user_uploads_files_spec.rb
@@ -36,7 +36,7 @@ describe 'Projects > Files > User uploads files' do
click_button('Upload file')
expect(page).to have_content('New commit message')
- expect(current_path).to eq(project_new_merge_request_path(project))
+ expect(current_path).to eq(project_new_merge_request_path(project, merge_request_source_branch: "new_branch_name"))
click_link('Changes')
find("a[data-action='diffs']", text: 'Changes').click
@@ -92,7 +92,7 @@ describe 'Projects > Files > User uploads files' do
fork = user.fork_of(project2.reload)
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork, merge_request_source_branch: "undefined"))
find("a[data-action='diffs']", text: 'Changes').click
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 69561b4d733..4be31511ceb 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -22,8 +22,8 @@ describe 'Merge Request button' do
it 'shows Create merge request button' do
href = project_new_merge_request_path(project,
- merge_request: { source_branch: 'feature',
- target_branch: 'master' })
+ merge_request_source_branch: 'feature',
+ merge_request: { target_branch: 'master' })
visit url
@@ -77,8 +77,8 @@ describe 'Merge Request button' do
it 'shows Create merge request button' do
href = project_new_merge_request_path(forked_project,
- merge_request: { source_branch: 'feature',
- target_branch: 'master' })
+ merge_request_source_branch: 'feature',
+ merge_request: { target_branch: 'master' })
visit fork_url
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index fb766addb31..0add129dde2 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -322,6 +322,22 @@ describe 'Project' do
end
end
+ context 'content is not cached after signing out', :js do
+ let(:user) { create(:user, project_view: 'activity') }
+ let(:project) { create(:project, :repository) }
+
+ it 'does not load activity', :js do
+ project.add_maintainer(user)
+ sign_in(user)
+ visit project_path(project)
+ sign_out(user)
+
+ page.evaluate_script('window.history.back()')
+
+ expect(page).not_to have_selector('.event-item')
+ end
+ end
+
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index c40977bc4ee..35971d564d5 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -46,6 +46,7 @@
"diff_head_commit_short_id": { "type": ["string", "null"] },
"merge_commit_message": { "type": ["string", "null"] },
"pipeline": { "type": ["object", "null"] },
+ "merge_pipeline": { "type": ["object", "null"] },
"work_in_progress": { "type": "boolean" },
"source_branch_exists": { "type": "boolean" },
"mergeable_discussions_state": { "type": "boolean" },
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 4f8701bae01..1fc0e206d5e 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -24,7 +24,7 @@ describe('AjaxFormVariableList', () => {
mock = new MockAdapter(axios);
const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section');
- saveButton = ajaxVariableListEl.querySelector('.js-secret-variables-save-button');
+ saveButton = ajaxVariableListEl.querySelector('.js-ci-variables-save-button');
errorBox = container.querySelector('.js-ci-variable-error-box');
ajaxVariableList = new AjaxFormVariableList({
container,
@@ -44,7 +44,7 @@ describe('AjaxFormVariableList', () => {
describe('onSaveClicked', () => {
it('shows loading spinner while waiting for the request', done => {
- const loadingIcon = saveButton.querySelector('.js-secret-variables-save-loading-icon');
+ const loadingIcon = saveButton.querySelector('.js-ci-variables-save-loading-icon');
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => {
expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(false);
@@ -172,7 +172,7 @@ describe('AjaxFormVariableList', () => {
container = document.querySelector('.js-ci-variable-list-section');
const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section');
- saveButton = ajaxVariableListEl.querySelector('.js-secret-variables-save-button');
+ saveButton = ajaxVariableListEl.querySelector('.js-ci-variables-save-button');
errorBox = container.querySelector('.js-ci-variable-error-box');
ajaxVariableList = new AjaxFormVariableList({
container,
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 7237274eb43..d9d7f61785f 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -1 +1,125 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import Vue from 'vue';
+import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
+import store from '~/mr_notes/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffsMockData from '../mock_data/merge_request_diffs';
+
+describe('CompareVersions', () => {
+ let vm;
+ const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
+
+ beforeEach(() => {
+ vm = createComponentWithStore(Vue.extend(CompareVersionsComponent), store, {
+ mergeRequestDiffs: diffsMockData,
+ mergeRequestDiff: diffsMockData[0],
+ targetBranch,
+ }).$mount();
+ });
+
+ describe('template', () => {
+ it('should render Tree List toggle button with correct attribute values', () => {
+ const treeListBtn = vm.$el.querySelector('.js-toggle-tree-list');
+
+ expect(treeListBtn).not.toBeNull();
+ expect(treeListBtn.dataset.originalTitle).toBe('Toggle file browser');
+ expect(treeListBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(treeListBtn.querySelector('svg use').getAttribute('xlink:href')).toContain(
+ '#hamburger',
+ );
+ });
+
+ it('should render comparison dropdowns with correct values', () => {
+ const sourceDropdown = vm.$el.querySelector('.mr-version-dropdown');
+ const targetDropdown = vm.$el.querySelector('.mr-version-compare-dropdown');
+
+ expect(sourceDropdown).not.toBeNull();
+ expect(targetDropdown).not.toBeNull();
+ expect(sourceDropdown.querySelector('a span').innerHTML).toContain('latest version');
+ expect(targetDropdown.querySelector('a span').innerHTML).toContain(targetBranch.branchName);
+ });
+
+ it('should not render comparison dropdowns if no mergeRequestDiffs are specified', () => {
+ vm.mergeRequestDiffs = [];
+
+ vm.$nextTick(() => {
+ const sourceDropdown = vm.$el.querySelector('.mr-version-dropdown');
+ const targetDropdown = vm.$el.querySelector('.mr-version-compare-dropdown');
+
+ expect(sourceDropdown).toBeNull();
+ expect(targetDropdown).toBeNull();
+ });
+ });
+
+ it('should render whitespace toggle button with correct attributes', () => {
+ const whitespaceBtn = vm.$el.querySelector('.qa-toggle-whitespace');
+ const href = vm.toggleWhitespacePath;
+
+ expect(whitespaceBtn).not.toBeNull();
+ expect(whitespaceBtn.getAttribute('href')).toEqual(href);
+ expect(whitespaceBtn.innerHTML).toContain('Hide whitespace changes');
+ });
+
+ it('should render view types buttons with correct values', () => {
+ const inlineBtn = vm.$el.querySelector('#inline-diff-btn');
+ const parallelBtn = vm.$el.querySelector('#parallel-diff-btn');
+
+ expect(inlineBtn).not.toBeNull();
+ expect(parallelBtn).not.toBeNull();
+ expect(inlineBtn.dataset.viewType).toEqual('inline');
+ expect(parallelBtn.dataset.viewType).toEqual('parallel');
+ expect(inlineBtn.innerHTML).toContain('Inline');
+ expect(parallelBtn.innerHTML).toContain('Side-by-side');
+ });
+ });
+
+ describe('setInlineDiffViewType', () => {
+ it('should persist the view type in the url', () => {
+ const viewTypeBtn = vm.$el.querySelector('#inline-diff-btn');
+ viewTypeBtn.click();
+
+ expect(window.location.toString()).toContain('?view=inline');
+ });
+ });
+
+ describe('setParallelDiffViewType', () => {
+ it('should persist the view type in the url', () => {
+ const viewTypeBtn = vm.$el.querySelector('#parallel-diff-btn');
+ viewTypeBtn.click();
+
+ expect(window.location.toString()).toContain('?view=parallel');
+ });
+ });
+
+ describe('comparableDiffs', () => {
+ it('should not contain the first item in the mergeRequestDiffs property', () => {
+ const { comparableDiffs } = vm;
+ const comparableDiffsMock = diffsMockData.slice(1);
+
+ expect(comparableDiffs).toEqual(comparableDiffsMock);
+ });
+ });
+
+ describe('isWhitespaceVisible', () => {
+ const originalHref = window.location.href;
+
+ afterEach(() => {
+ window.history.replaceState({}, null, originalHref);
+ });
+
+ it('should return "true" when no "w" flag is present in the URL (default)', () => {
+ expect(vm.isWhitespaceVisible()).toBe(true);
+ });
+
+ it('should return "false" when the flag is set to "1" in the URL', () => {
+ window.history.replaceState({}, null, '?w=1');
+
+ expect(vm.isWhitespaceVisible()).toBe(false);
+ });
+
+ it('should return "true" when the flag is set to "0" in the URL', () => {
+ window.history.replaceState({}, null, '?w=0');
+
+ expect(vm.isWhitespaceVisible()).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/mock_data/merge_request_diffs.js b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
new file mode 100644
index 00000000000..d72ad7818dd
--- /dev/null
+++ b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
@@ -0,0 +1,42 @@
+export default [
+ {
+ versionIndex: 4,
+ createdAt: '2018-10-23T11:49:16.611Z',
+ commitsCount: 4,
+ latest: true,
+ shortCommitSha: 'de7a8f7f',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=de7a8f7f20c3ea2e0bef3ba01cfd41c21f6b4995',
+ },
+ {
+ versionIndex: 3,
+ createdAt: '2018-10-23T11:46:40.617Z',
+ commitsCount: 3,
+ latest: false,
+ shortCommitSha: 'e78fc18f',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=e78fc18fa37acb2185c59ca94d4a964464feb50e',
+ },
+ {
+ versionIndex: 2,
+ createdAt: '2018-10-04T09:57:39.648Z',
+ commitsCount: 2,
+ latest: false,
+ shortCommitSha: '48da7e7e',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=48da7e7e9a99d41c852578bd9cb541ca4d864b3e',
+ },
+ {
+ versionIndex: 1,
+ createdAt: '2018-09-25T20:30:39.493Z',
+ commitsCount: 1,
+ latest: false,
+ shortCommitSha: '47bac2ed',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=47bac2ed972c5bee344c1cea159a22cd7f711dc0',
+ },
+];
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 4b6d3d5bcba..fed04cbaed8 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -221,6 +221,7 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].parallelDiffLines[0].left.discussions.length).toEqual(1);
expect(state.diffFiles[0].parallelDiffLines[0].left.discussions[0].id).toEqual(1);
+ expect(state.diffFiles[0].parallelDiffLines[0].right.discussions).toEqual([]);
expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(1);
expect(state.diffFiles[0].highlightedDiffLines[0].discussions[0].id).toEqual(1);
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
deleted file mode 100644
index bc973407b25..00000000000
--- a/spec/javascripts/job_spec.js
+++ /dev/null
@@ -1,265 +0,0 @@
-// import $ from 'jquery';
-// import MockAdapter from 'axios-mock-adapter';
-// import axios from '~/lib/utils/axios_utils';
-// import { numberToHumanSize } from '~/lib/utils/number_utils';
-// import '~/lib/utils/datetime_utility';
-// import Job from '~/job';
-// import '~/breakpoints';
-// import waitForPromises from 'spec/helpers/wait_for_promises';
-
-// describe('Job', () => {
-// const JOB_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`;
-// let mock;
-// let response;
-// let job;
-
-// preloadFixtures('builds/build-with-artifacts.html.raw');
-
-// beforeEach(() => {
-// loadFixtures('builds/build-with-artifacts.html.raw');
-
-// spyOnDependency(Job, 'visitUrl');
-
-// response = {};
-
-// mock = new MockAdapter(axios);
-
-// mock.onGet(new RegExp(`${JOB_URL}/trace.json?(.*)`)).reply(() => [200, response]);
-// });
-
-// afterEach(() => {
-// mock.restore();
-
-// clearTimeout(job.timeout);
-// });
-
-// describe('class constructor', () => {
-// beforeEach(() => {
-// jasmine.clock().install();
-// });
-
-// afterEach(() => {
-// jasmine.clock().uninstall();
-// });
-
-// describe('running build', () => {
-// it('updates the build trace on an interval', function (done) {
-// response = {
-// html: '<span>Update<span>',
-// status: 'running',
-// state: 'newstate',
-// append: true,
-// complete: false,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-// expect(job.state).toBe('newstate');
-
-// response = {
-// html: '<span>More</span>',
-// status: 'running',
-// state: 'finalstate',
-// append: true,
-// complete: true,
-// };
-// })
-// .then(() => jasmine.clock().tick(4001))
-// .then(waitForPromises)
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
-// expect(job.state).toBe('finalstate');
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('replaces the entire build trace', (done) => {
-// response = {
-// html: '<span>Update<span>',
-// status: 'running',
-// append: false,
-// complete: false,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-
-// response = {
-// html: '<span>Different</span>',
-// status: 'running',
-// append: false,
-// };
-// })
-// .then(() => jasmine.clock().tick(4001))
-// .then(waitForPromises)
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
-// expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-// });
-
-// describe('truncated information', () => {
-// describe('when size is less than total', () => {
-// it('shows information about truncated log', (done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('shows the size in KiB', (done) => {
-// const size = 50;
-
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(
-// document.querySelector('.js-truncated-info-size').textContent.trim(),
-// ).toEqual(`${numberToHumanSize(size)}`);
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('shows incremented size', (done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// complete: false,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(
-// document.querySelector('.js-truncated-info-size').textContent.trim(),
-// ).toEqual(`${numberToHumanSize(50)}`);
-
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: true,
-// size: 10,
-// total: 100,
-// complete: true,
-// };
-// })
-// .then(() => jasmine.clock().tick(4001))
-// .then(waitForPromises)
-// .then(() => {
-// expect(
-// document.querySelector('.js-truncated-info-size').textContent.trim(),
-// ).toEqual(`${numberToHumanSize(60)}`);
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('renders the raw link', () => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// };
-
-// job = new Job();
-
-// expect(
-// document.querySelector('.js-raw-link').textContent.trim(),
-// ).toContain('Complete Raw');
-// });
-// });
-
-// describe('when size is equal than total', () => {
-// it('does not show the trunctated information', (done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 100,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-// });
-// });
-
-// describe('output trace', () => {
-// beforeEach((done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('should render trace controls', () => {
-// const controllers = document.querySelector('.controllers');
-
-// expect(controllers.querySelector('.js-raw-link-controller')).not.toBeNull();
-// expect(controllers.querySelector('.js-scroll-up')).not.toBeNull();
-// expect(controllers.querySelector('.js-scroll-down')).not.toBeNull();
-// });
-
-// it('should render received output', () => {
-// expect(
-// document.querySelector('.js-build-output').innerHTML,
-// ).toEqual('<span>Update</span>');
-// });
-// });
-// });
-
-// });
diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js
index b5c62178642..a7dcd532f4f 100644
--- a/spec/javascripts/pipelines/pipelines_actions_spec.js
+++ b/spec/javascripts/pipelines/pipelines_actions_spec.js
@@ -62,9 +62,13 @@ describe('Pipelines Actions dropdown', () => {
);
};
- beforeEach(() => {
+ beforeEach(done => {
spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime());
vm = mountComponent(Component, { actions: [scheduledJobAction, expiredJobAction] });
+
+ Vue.nextTick()
+ .then(done)
+ .catch(done.fail);
});
it('emits postAction event after confirming', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index ce850bc621e..3d44af11153 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -2,54 +2,48 @@ import Vue from 'vue';
import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import { getTimeago } from '~/lib/utils/datetime_utility';
+import mountComponent from '../../helpers/vue_mount_component_helper';
-const deploymentMockData = {
- id: 15,
- name: 'review/diplo',
- url: '/root/acets-review-apps/environments/15',
- stop_url: '/root/acets-review-apps/environments/15/stop',
- metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
- metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
- external_url: 'http://diplo.',
- external_url_formatted: 'diplo.',
- deployed_at: '2017-03-22T22:44:42.258Z',
- deployed_at_formatted: 'Mar 22, 2017 10:44pm',
- changes: [
- {
- path: 'index.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
- },
- {
- path: 'imgs/gallery.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
- },
- {
- path: 'about/',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
- },
- ],
-};
-const createComponent = () => {
+describe('Deployment component', () => {
const Component = Vue.extend(deploymentComponent);
+ const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/review-apps/environments/15',
+ stop_url: '/root/review-apps/environments/15/stop',
+ metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
+ external_url: 'http://gitlab.com.',
+ external_url_formatted: 'gitlab',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+ };
- return new Component({
- el: document.createElement('div'),
- propsData: { deployment: { ...deploymentMockData } },
- });
-};
-
-describe('Deployment component', () => {
let vm;
- beforeEach(() => {
- vm = createComponent();
- });
-
afterEach(() => {
vm.$destroy();
});
- describe('computed', () => {
+ describe('', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { deployment: { ...deploymentMockData } });
+ });
+
describe('deployTimeago', () => {
it('return formatted date', () => {
const readable = getTimeago().format(deploymentMockData.deployed_at);
@@ -111,9 +105,7 @@ describe('Deployment component', () => {
expect(vm.hasDeploymentMeta).toEqual(false);
});
});
- });
- describe('methods', () => {
describe('stopEnvironment', () => {
const url = '/foo/bar';
const returnPromise = () =>
@@ -152,42 +144,33 @@ describe('Deployment component', () => {
expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
});
});
- });
-
- describe('template', () => {
- let el;
-
- beforeEach(() => {
- vm = createComponent(deploymentMockData);
- el = vm.$el;
- });
it('renders deployment name', () => {
- expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(
+ expect(vm.$el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(
deploymentMockData.url,
);
- expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
+ expect(vm.$el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
});
it('renders external URL', () => {
- expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(
+ expect(vm.$el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(
deploymentMockData.external_url,
);
- expect(el.querySelector('.js-deploy-url').innerText).toContain('View app');
+ expect(vm.$el.querySelector('.js-deploy-url').innerText).toContain('View app');
});
it('renders stop button', () => {
- expect(el.querySelector('.btn')).not.toBeNull();
+ expect(vm.$el.querySelector('.btn')).not.toBeNull();
});
it('renders deployment time', () => {
- expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
+ expect(vm.$el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
});
it('renders metrics component', () => {
- expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-mr-memory-usage')).not.toBeNull();
});
});
@@ -196,8 +179,7 @@ describe('Deployment component', () => {
window.gon = window.gon || {};
window.gon.features = window.gon.features || {};
window.gon.features.ciEnvironmentsStatusChanges = true;
-
- vm = createComponent(deploymentMockData);
+ vm = mountComponent(Component, { deployment: { ...deploymentMockData } });
});
afterEach(() => {
@@ -216,7 +198,7 @@ describe('Deployment component', () => {
window.gon.features = window.gon.features || {};
window.gon.features.ciEnvironmentsStatusChanges = false;
- vm = createComponent(deploymentMockData);
+ vm = mountComponent(Component, { deployment: { ...deploymentMockData } });
});
afterEach(() => {
@@ -228,4 +210,64 @@ describe('Deployment component', () => {
expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
});
});
+
+ describe('without changes', () => {
+ beforeEach(() => {
+ window.gon = window.gon || {};
+ window.gon.features = window.gon.features || {};
+ window.gon.features.ciEnvironmentsStatusChanges = true;
+ delete deploymentMockData.changes;
+
+ vm = mountComponent(Component, { deployment: { ...deploymentMockData } });
+ });
+
+ afterEach(() => {
+ delete window.gon.features.ciEnvironmentsStatusChanges;
+ });
+
+ it('renders the link to the review app without dropdown', () => {
+ expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ });
+ });
+
+ describe('deployment status', () => {
+ describe('running', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'running' }),
+ });
+ });
+
+ it('renders information about running deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deploying to');
+ });
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'success' }),
+ });
+ });
+
+ it('renders information about finished deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deployed to');
+ });
+ });
+
+ describe('failed', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'failed' }),
+ });
+ });
+
+ it('renders information about finished deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
+ 'Failed to deploy to',
+ );
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js b/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js
new file mode 100644
index 00000000000..68a65bd21c6
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import component from '~/vue_merge_request_widget/components/review_app_link.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('review app link', () => {
+ const Component = Vue.extend(component);
+ const props = {
+ link: '/review',
+ cssClass: 'js-link',
+ };
+ let vm;
+ let el;
+
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ el = vm.$el;
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders provided link as href attribute', () => {
+ expect(el.getAttribute('href')).toEqual(props.link);
+ });
+
+ it('renders provided cssClass as class attribute', () => {
+ expect(el.getAttribute('class')).toEqual(props.cssClass);
+ });
+
+ it('renders View app text', () => {
+ expect(el.textContent.trim()).toEqual('View app');
+ });
+
+ it('renders svg icon', () => {
+ expect(el.querySelector('svg')).not.toBeNull();
+ });
+});
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 d1a064b9f4d..27b6c91e154 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -189,7 +189,7 @@ describe('mrWidgetOptions', () => {
it('should fetch deployments', done => {
spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
- vm.fetchDeployments();
+ vm.fetchPreMergeDeployments();
setTimeout(() => {
expect(vm.service.fetchDeployments).toHaveBeenCalled();
@@ -454,6 +454,7 @@ describe('mrWidgetOptions', () => {
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes,
+ status: 'success'
};
beforeEach(done => {
@@ -486,4 +487,189 @@ describe('mrWidgetOptions', () => {
).toEqual(changes.length);
});
});
+
+ describe('pipeline for target branch after merge', () => {
+ describe('with information for target branch pipeline', () => {
+ beforeEach(done => {
+ vm.mr.state = 'merged';
+ vm.mr.mergePipeline = {
+ id: 127,
+ user: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ active: true,
+ coverage: null,
+ source: 'push',
+ created_at: '2018-10-22T11:41:35.186Z',
+ updated_at: '2018-10-22T11:41:35.433Z',
+ path: '/root/ci-web-terminal/pipelines/127',
+ flags: {
+ latest: true,
+ stuck: true,
+ auto_devops: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: true,
+ failure_reason: false,
+ },
+ details: {
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/root/ci-web-terminal/pipelines/127',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ },
+ duration: null,
+ finished_at: null,
+ stages: [
+ {
+ name: 'test',
+ title: 'test: pending',
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/root/ci-web-terminal/pipelines/127#test',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ },
+ path: '/root/ci-web-terminal/pipelines/127#test',
+ dropdown_path: '/root/ci-web-terminal/pipelines/127/stage.json?stage=test',
+ },
+ ],
+ artifacts: [],
+ manual_actions: [],
+ scheduled_actions: [],
+ },
+ ref: {
+ name: 'master',
+ path: '/root/ci-web-terminal/commits/master',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'aa1939133d373c94879becb79d91828a892ee319',
+ short_id: 'aa193913',
+ title: "Merge branch 'master-test' into 'master'",
+ created_at: '2018-10-22T11:41:33.000Z',
+ parent_ids: [
+ '4622f4dd792468993003caf2e3be978798cbe096',
+ '76598df914cdfe87132d0c3c40f80db9fa9396a4',
+ ],
+ message:
+ "Merge branch 'master-test' into 'master'\n\nUpdate .gitlab-ci.yml\n\nSee merge request root/ci-web-terminal!1",
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2018-10-22T11:41:33.000Z',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2018-10-22T11:41:33.000Z',
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ author_gravatar_url: null,
+ commit_url:
+ 'http://localhost:3000/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
+ commit_path: '/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
+ },
+ cancel_path: '/root/ci-web-terminal/pipelines/127/cancel',
+ };
+ vm.$nextTick(done);
+ });
+
+ it('renders pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).not.toBeNull();
+ });
+
+ describe('with post merge deployments', () => {
+ beforeEach(done => {
+ vm.mr.postMergeDeployments = [{
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+ status: 'success'
+ }];
+
+ vm.$nextTick(done);
+ });
+
+ it('renders post deployment information', () => {
+ expect(vm.$el.querySelector('.js-post-deployment')).not.toBeNull();
+ });
+ });
+ });
+
+ describe('without information for target branch pipeline', () => {
+ beforeEach(done => {
+ vm.mr.state = 'merged';
+
+ vm.$nextTick(done);
+ });
+
+ it('does not render pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ });
+ });
+
+ describe('when state is not merged', () => {
+ beforeEach(done => {
+ vm.mr.state = 'archived';
+
+ vm.$nextTick(done);
+ });
+
+ it('does not render pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ });
+
+ it('does not render post deployment information', () => {
+ expect(vm.$el.querySelector('.js-post-deployment')).toBeNull();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
index 026a0c7ea09..3483b7d387d 100644
--- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -11,16 +11,6 @@ describe('collapsedGroupedDatePicker', () => {
});
});
- it('should render toggle sidebar if showToggleSidebar', (done) => {
- expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeDefined();
-
- vm.showToggleSidebar = false;
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeNull();
- done();
- });
- });
-
describe('toggleCollapse events', () => {
beforeEach((done) => {
spyOn(vm, 'toggleSidebar');
@@ -28,12 +18,6 @@ describe('collapsedGroupedDatePicker', () => {
Vue.nextTick(done);
});
- it('should emit when sidebar is toggled', () => {
- vm.$el.querySelector('.gutter-toggle').click();
-
- expect(vm.toggleSidebar).toHaveBeenCalled();
- });
-
it('should emit when collapsed-calendar-icon is clicked', () => {
vm.$el.querySelector('.sidebar-collapsed-icon').click();
@@ -92,5 +76,11 @@ describe('collapsedGroupedDatePicker', () => {
expect(icons.length).toEqual(1);
expect(icons[0].innerText.trim()).toEqual('None');
});
+
+ it('should have tooltip as `Start and due date`', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+
+ expect(icons[0].dataset.originalTitle).toBe('Start and due date');
+ });
});
});
diff --git a/spec/lib/gitlab/background_migration/digest_column_spec.rb b/spec/lib/gitlab/background_migration/digest_column_spec.rb
new file mode 100644
index 00000000000..3e107ac3027
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/digest_column_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::DigestColumn, :migration, schema: 20180913142237 do
+ let(:personal_access_tokens) { table(:personal_access_tokens) }
+ let(:users) { table(:users) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ context 'token is not yet hashed' do
+ before do
+ users.create(id: 1, email: 'user@example.com', projects_limit: 10)
+ personal_access_tokens.create!(id: 1, user_id: 1, name: 'pat-01', token: 'token-01')
+ end
+
+ it 'saves token digest' do
+ expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to(
+ change { PersonalAccessToken.find(1).token_digest }.from(nil).to(Gitlab::CryptoHelper.sha256('token-01')))
+ end
+
+ it 'erases token' do
+ expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to(
+ change { PersonalAccessToken.find(1).token }.from('token-01').to(nil))
+ end
+ end
+
+ context 'token is already hashed' do
+ before do
+ users.create(id: 1, email: 'user@example.com', projects_limit: 10)
+ personal_access_tokens.create!(id: 1, user_id: 1, name: 'pat-01', token_digest: 'token-digest-01')
+ end
+
+ it 'does not change existing token digest' do
+ expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to(
+ change { PersonalAccessToken.find(1).token_digest })
+ end
+
+ it 'leaves token empty' do
+ expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to(
+ change { PersonalAccessToken.find(1).token }.from(nil))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/redact_links_spec.rb b/spec/lib/gitlab/background_migration/redact_links_spec.rb
new file mode 100644
index 00000000000..a40e68069cc
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/redact_links_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::RedactLinks, :migration, schema: 20181014121030 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:notes) { table(:notes) }
+ let(:snippets) { table(:snippets) }
+ let(:users) { table(:users) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') }
+
+ def create_merge_request(id, params)
+ params.merge!(id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}")
+
+ merge_requests.create(params)
+ end
+
+ def create_issue(id, params)
+ params.merge!(id: id, title: "issue#{id}", project_id: project.id)
+
+ issues.create(params)
+ end
+
+ def create_note(id, params)
+ params[:id] = id
+
+ notes.create(params)
+ end
+
+ def create_snippet(id, params)
+ params.merge!(id: id, author_id: user.id)
+
+ snippets.create(params)
+ end
+
+ def create_resource(model, id, params)
+ send("create_#{model.name.underscore}", id, params)
+ end
+
+ shared_examples_for 'redactable resource' do
+ it 'updates only matching texts' do
+ matching_text = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
+ redacted_text = 'some text /sent_notifications/REDACTED/unsubscribe more text'
+ create_resource(model, 1, { field => matching_text })
+ create_resource(model, 2, { field => 'not matching text' })
+ create_resource(model, 3, { field => matching_text })
+ create_resource(model, 4, { field => redacted_text })
+ create_resource(model, 5, { field => matching_text })
+
+ expected = { field => 'some text /sent_notifications/REDACTED/unsubscribe more text',
+ "#{field}_html" => nil }
+ expect_any_instance_of("Gitlab::BackgroundMigration::RedactLinks::#{model}".constantize).to receive(:update_columns).with(expected).and_call_original
+
+ subject.perform(model, field, 2, 4)
+
+ expect(model.where(field => matching_text).pluck(:id)).to eq [1, 5]
+ expect(model.find(3).reload[field]).to eq redacted_text
+ end
+ end
+
+ context 'resource is Issue' do
+ it_behaves_like 'redactable resource' do
+ let(:model) { Issue }
+ let(:field) { :description }
+ end
+ end
+
+ context 'resource is Merge Request' do
+ it_behaves_like 'redactable resource' do
+ let(:model) { MergeRequest }
+ let(:field) { :description }
+ end
+ end
+
+ context 'resource is Note' do
+ it_behaves_like 'redactable resource' do
+ let(:model) { Note }
+ let(:field) { :note }
+ end
+ end
+
+ context 'resource is Snippet' do
+ it_behaves_like 'redactable resource' do
+ let(:model) { Snippet }
+ let(:field) { :description }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index 4d5081b0a75..e5999a1c509 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -282,6 +282,21 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
expect(pipeline_status.status).to eq(status)
expect(pipeline_status.ref).to eq(ref)
end
+
+ context 'when status is empty string' do
+ before do
+ Gitlab::Redis::Cache.with do |redis|
+ redis.mapped_hmset(cache_key,
+ { sha: sha, status: '', ref: ref })
+ end
+ end
+
+ it 'reads the status as nil' do
+ pipeline_status.load_from_cache
+
+ expect(pipeline_status.status).to eq(nil)
+ end
+ end
end
describe '#has_cache?' do
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index 2ce858836e3..854c4cb718c 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -54,7 +54,7 @@ describe Gitlab::Ci::Build::Policy::Variables do
expect(policy).not_to be_satisfied_by(pipeline, seed)
end
- it 'allows to evaluate regular secret variables' do
+ it 'allows to evaluate regular CI variables' do
create(:ci_variable, project: project, key: 'SECRET', value: 'my secret')
policy = described_class.new(["$SECRET == 'my secret'"])
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 624e2add860..8df0facdab3 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -29,6 +29,16 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true
end
+ it 'returns true for localhost IPs' do
+ expect(described_class.blocked_url?('https://0.0.0.0/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true
+ end
+
+ it 'returns true for loopback IP' do
+ expect(described_class.blocked_url?('https://127.0.0.2/foo/foo.git')).to be true
+ end
+
it 'returns true for alternative version of 127.0.0.1 (0177.1)' do
expect(described_class.blocked_url?('https://0177.1:65535/foo/foo.git')).to be true
end
@@ -84,6 +94,16 @@ describe Gitlab::UrlBlocker do
end
end
+ it 'allows localhost endpoints' do
+ expect(described_class).not_to be_blocked_url('http://0.0.0.0', allow_localhost: true)
+ expect(described_class).not_to be_blocked_url('http://localhost', allow_localhost: true)
+ expect(described_class).not_to be_blocked_url('http://127.0.0.1', allow_localhost: true)
+ end
+
+ it 'allows loopback endpoints' do
+ expect(described_class).not_to be_blocked_url('http://127.0.0.2', allow_localhost: true)
+ end
+
it 'allows IPv4 link-local endpoints' do
expect(described_class).not_to be_blocked_url('http://169.254.169.254')
expect(described_class).not_to be_blocked_url('http://169.254.168.100')
@@ -122,7 +142,7 @@ describe Gitlab::UrlBlocker do
end
def stub_domain_resolv(domain, ip)
- allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false)])
+ allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)])
end
def unstub_domain_resolv
diff --git a/spec/migrations/enqueue_redact_links_spec.rb b/spec/migrations/enqueue_redact_links_spec.rb
new file mode 100644
index 00000000000..a5da76977b7
--- /dev/null
+++ b/spec/migrations/enqueue_redact_links_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20181014121030_enqueue_redact_links.rb')
+
+describe EnqueueRedactLinks, :migration, :sidekiq do
+ let(:merge_requests) { table(:merge_requests) }
+ let(:issues) { table(:issues) }
+ let(:notes) { table(:notes) }
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+ let(:snippets) { table(:snippets) }
+ let(:users) { table(:users) }
+ let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+
+ text = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
+ group = namespaces.create!(name: 'gitlab', path: 'gitlab')
+ project = projects.create!(namespace_id: group.id)
+
+ merge_requests.create!(id: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master', description: text)
+ issues.create!(id: 1, description: text)
+ notes.create!(id: 1, note: text)
+ notes.create!(id: 2, note: text)
+ snippets.create!(id: 1, description: text, author_id: user.id)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Note", "note", 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, "Note", "note", 2, 2)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Issue", "description", 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "MergeRequest", "description", 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Snippet", "description", 1, 1)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 5
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_digest_personal_access_tokens_spec.rb b/spec/migrations/schedule_digest_personal_access_tokens_spec.rb
new file mode 100644
index 00000000000..6d155f78342
--- /dev/null
+++ b/spec/migrations/schedule_digest_personal_access_tokens_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180913142237_schedule_digest_personal_access_tokens.rb')
+
+describe ScheduleDigestPersonalAccessTokens, :migration, :sidekiq do
+ let(:personal_access_tokens) { table(:personal_access_tokens) }
+ let(:users) { table(:users) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 4)
+
+ users.create(id: 1, email: 'user@example.com', projects_limit: 10)
+
+ personal_access_tokens.create!(id: 1, user_id: 1, name: 'pat-01', token: 'token-01')
+ personal_access_tokens.create!(id: 2, user_id: 1, name: 'pat-02', token: 'token-02')
+ personal_access_tokens.create!(id: 3, user_id: 1, name: 'pat-03', token_digest: 'token_digest')
+ personal_access_tokens.create!(id: 4, user_id: 1, name: 'pat-04', token: 'token-04')
+ personal_access_tokens.create!(id: 5, user_id: 1, name: 'pat-05', token: 'token-05')
+ personal_access_tokens.create!(id: 6, user_id: 1, name: 'pat-06', token: 'token-06')
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ migrate!
+
+ expect(described_class::MIGRATION).to(
+ be_scheduled_delayed_migration(
+ 5.minutes, 'PersonalAccessToken', 'token', 'token_digest', 1, 5))
+ expect(described_class::MIGRATION).to(
+ be_scheduled_delayed_migration(
+ 10.minutes, 'PersonalAccessToken', 'token', 'token_digest', 6, 6))
+ expect(BackgroundMigrationWorker.jobs.size).to eq 2
+ end
+ end
+
+ it 'schedules background migrations' do
+ perform_enqueued_jobs do
+ plain_text_token = 'token IS NOT NULL'
+
+ expect(personal_access_tokens.where(plain_text_token).count).to eq 5
+
+ migrate!
+
+ expect(personal_access_tokens.where(plain_text_token).count).to eq 0
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index a046541031e..65e06f27f35 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2027,17 +2027,17 @@ describe Ci::Build do
it { is_expected.to include(tag_variable) }
end
- context 'when secret variable is defined' do
- let(:secret_variable) do
+ context 'when CI variable is defined' do
+ let(:ci_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
before do
create(:ci_variable,
- secret_variable.slice(:key, :value).merge(project: project))
+ ci_variable.slice(:key, :value).merge(project: project))
end
- it { is_expected.to include(secret_variable) }
+ it { is_expected.to include(ci_variable) }
end
context 'when protected variable is defined' do
@@ -2072,17 +2072,17 @@ describe Ci::Build do
end
end
- context 'when group secret variable is defined' do
- let(:secret_variable) do
+ context 'when group CI variable is defined' do
+ let(:ci_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
before do
create(:ci_group_variable,
- secret_variable.slice(:key, :value).merge(group: group))
+ ci_variable.slice(:key, :value).merge(group: group))
end
- it { is_expected.to include(secret_variable) }
+ it { is_expected.to include(ci_variable) }
end
context 'when group protected variable is defined' do
@@ -2357,7 +2357,7 @@ describe Ci::Build do
.to receive(:predefined_variables) { [project_pre_var] }
allow_any_instance_of(Project)
- .to receive(:secret_variables_for)
+ .to receive(:ci_variables_for)
.with(ref: 'master', environment: nil) do
[create(:ci_variable, key: 'secret', value: 'value')]
end
@@ -2508,7 +2508,7 @@ describe Ci::Build do
end
describe '#scoped_variables_hash' do
- context 'when overriding secret variables' do
+ context 'when overriding CI variables' do
before do
project.variables.create!(key: 'MY_VAR', value: 'my value 1')
pipeline.variables.create!(key: 'MY_VAR', value: 'my value 2')
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index f5c4b0b66ae..c245e8df815 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -4,7 +4,10 @@ require 'spec_helper'
describe Clusters::Cluster do
it { is_expected.to belong_to(:user) }
+ it { is_expected.to have_many(:cluster_projects) }
it { is_expected.to have_many(:projects) }
+ it { is_expected.to have_many(:cluster_groups) }
+ it { is_expected.to have_many(:groups) }
it { is_expected.to have_one(:provider_gcp) }
it { is_expected.to have_one(:platform_kubernetes) }
it { is_expected.to have_one(:application_helm) }
@@ -178,6 +181,53 @@ describe Clusters::Cluster do
it { expect(cluster.update(enabled: false)).to be_truthy }
end
end
+
+ describe 'cluster_type validations' do
+ let(:instance_cluster) { create(:cluster, :instance) }
+ let(:group_cluster) { create(:cluster, :group) }
+ let(:project_cluster) { create(:cluster, :project) }
+
+ it 'validates presence' do
+ cluster = build(:cluster, :project, cluster_type: nil)
+
+ expect(cluster).not_to be_valid
+ expect(cluster.errors.full_messages).to include("Cluster type can't be blank")
+ end
+
+ context 'project_type cluster' do
+ it 'does not allow setting group' do
+ project_cluster.groups << build(:group)
+
+ expect(project_cluster).not_to be_valid
+ expect(project_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
+ end
+ end
+
+ context 'group_type cluster' do
+ it 'does not allow setting project' do
+ group_cluster.projects << build(:project)
+
+ expect(group_cluster).not_to be_valid
+ expect(group_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
+ end
+ end
+
+ context 'instance_type cluster' do
+ it 'does not allow setting group' do
+ instance_cluster.groups << build(:group)
+
+ expect(instance_cluster).not_to be_valid
+ expect(instance_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
+ end
+
+ it 'does not allow setting project' do
+ instance_cluster.projects << build(:project)
+
+ expect(instance_cluster).not_to be_valid
+ expect(instance_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
+ end
+ end
+ end
end
describe '#provider' do
@@ -229,6 +279,23 @@ describe Clusters::Cluster do
end
end
+ describe '#group' do
+ subject { cluster.group }
+
+ context 'when cluster belongs to a group' do
+ let(:cluster) { create(:cluster, :group) }
+ let(:group) { cluster.groups.first }
+
+ it { is_expected.to eq(group) }
+ end
+
+ context 'when cluster does not belong to any group' do
+ let(:cluster) { create(:cluster) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
describe '#applications' do
set(:cluster) { create(:cluster) }
diff --git a/spec/models/clusters/group_spec.rb b/spec/models/clusters/group_spec.rb
new file mode 100644
index 00000000000..ba145342cb8
--- /dev/null
+++ b/spec/models/clusters/group_spec.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Group do
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to belong_to(:group) }
+end
diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb
new file mode 100644
index 00000000000..7d320edd492
--- /dev/null
+++ b/spec/models/concerns/redactable_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Redactable do
+ shared_examples 'model with redactable field' do
+ it 'redacts unsubscribe token' do
+ model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
+
+ model.save!
+
+ expect(model[field]).to eq 'some text /sent_notifications/REDACTED/unsubscribe more text'
+ end
+
+ it 'ignores not hexadecimal tokens' do
+ text = 'some text /sent_notifications/token/unsubscribe more text'
+ model[field] = text
+
+ model.save!
+
+ expect(model[field]).to eq text
+ end
+
+ it 'ignores not matching texts' do
+ text = 'some text /sent_notifications/.*/unsubscribe more text'
+ model[field] = text
+
+ model.save!
+
+ expect(model[field]).to eq text
+ end
+
+ it 'redacts the field when saving the model before creating markdown cache' do
+ model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
+
+ model.save!
+
+ expected = 'some text /sent_notifications/REDACTED/unsubscribe more text'
+ expect(model[field]).to eq expected
+ expect(model["#{field}_html"]).to eq "<p dir=\"auto\">#{expected}</p>"
+ end
+ end
+
+ context 'when model is an issue' do
+ it_behaves_like 'model with redactable field' do
+ let(:model) { create(:issue) }
+ let(:field) { :description }
+ end
+ end
+
+ context 'when model is a merge request' do
+ it_behaves_like 'model with redactable field' do
+ let(:model) { create(:merge_request) }
+ let(:field) { :description }
+ end
+ end
+
+ context 'when model is a note' do
+ it_behaves_like 'model with redactable field' do
+ let(:model) { create(:note) }
+ let(:field) { :note }
+ end
+ end
+
+ context 'when model is a snippet' do
+ it_behaves_like 'model with redactable field' do
+ let(:model) { create(:snippet) }
+ let(:field) { :description }
+ end
+ end
+end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 9b804429138..782687516ae 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -2,8 +2,6 @@ require 'spec_helper'
shared_examples 'TokenAuthenticatable' do
describe 'dynamically defined methods' do
- it { expect(described_class).to be_private_method_defined(:generate_token) }
- it { expect(described_class).to be_private_method_defined(:write_new_token) }
it { expect(described_class).to respond_to("find_by_#{token_field}") }
it { is_expected.to respond_to("ensure_#{token_field}") }
it { is_expected.to respond_to("set_#{token_field}") }
@@ -66,13 +64,275 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
end
describe 'multiple token fields' do
- before do
+ before(:all) do
described_class.send(:add_authentication_token_field, :yet_another_token)
end
- describe '.token_fields' do
- subject { described_class.authentication_token_fields }
- it { is_expected.to include(:runners_registration_token, :yet_another_token) }
+ it { is_expected.to respond_to(:ensure_runners_registration_token) }
+ it { is_expected.to respond_to(:ensure_yet_another_token) }
+ end
+
+ describe 'setting same token field multiple times' do
+ subject { described_class.send(:add_authentication_token_field, :runners_registration_token) }
+
+ it 'raises error' do
+ expect {subject}.to raise_error(ArgumentError)
+ end
+ end
+end
+
+describe PersonalAccessToken, 'TokenAuthenticatable' do
+ let(:personal_access_token_name) { 'test-pat-01' }
+ let(:token_value) { 'token' }
+ let(:user) { create(:user) }
+ let(:personal_access_token) do
+ described_class.new(name: personal_access_token_name,
+ user_id: user.id,
+ scopes: [:api],
+ token: token,
+ token_digest: token_digest)
+ end
+
+ before do
+ allow(Devise).to receive(:friendly_token).and_return(token_value)
+ end
+
+ describe '.find_by_token' do
+ subject { PersonalAccessToken.find_by_token(token_value) }
+
+ before do
+ personal_access_token.save
+ end
+
+ context 'token_digest already exists' do
+ let(:token) { nil }
+ let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
+
+ it 'finds the token' do
+ expect(subject).not_to be_nil
+ expect(subject.name).to eql(personal_access_token_name)
+ end
+ end
+
+ context 'token_digest does not exist' do
+ let(:token) { token_value }
+ let(:token_digest) { nil }
+
+ it 'finds the token' do
+ expect(subject).not_to be_nil
+ expect(subject.name).to eql(personal_access_token_name)
+ end
+ end
+ end
+
+ describe '#set_token' do
+ let(:new_token_value) { 'new-token' }
+ subject { personal_access_token.set_token(new_token_value) }
+
+ context 'token_digest already exists' do
+ let(:token) { nil }
+ let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
+
+ it 'overwrites token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(new_token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(new_token_value))
+ end
+ end
+
+ context 'token_digest does not exist but token does' do
+ let(:token) { token_value }
+ let(:token_digest) { nil }
+
+ it 'creates new token_digest and clears token' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(new_token_value)
+ expect(personal_access_token.token_digest).to eql(Gitlab::CryptoHelper.sha256(new_token_value))
+ end
+ end
+
+ context 'token_digest does not exist, nor token' do
+ let(:token) { nil }
+ let(:token_digest) { nil }
+
+ it 'creates new token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(new_token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(new_token_value))
+ end
+ end
+ end
+
+ describe '#ensure_token' do
+ subject { personal_access_token.ensure_token }
+
+ context 'token_digest already exists' do
+ let(:token) { nil }
+ let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
+
+ it 'does not change token fields' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to be_nil
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+
+ context 'token_digest does not exist but token does' do
+ let(:token) { token_value }
+ let(:token_digest) { nil }
+
+ it 'does not change token fields' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to eql(token_value)
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to be_nil
+ end
+ end
+
+ context 'token_digest does not exist, nor token' do
+ let(:token) { nil }
+ let(:token_digest) { nil }
+
+ it 'creates token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+ end
+
+ describe '#ensure_token!' do
+ subject { personal_access_token.ensure_token! }
+
+ context 'token_digest already exists' do
+ let(:token) { nil }
+ let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
+
+ it 'does not change token fields' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to be_nil
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+
+ context 'token_digest does not exist but token does' do
+ let(:token) { token_value }
+ let(:token_digest) { nil }
+
+ it 'does not change token fields' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to eql(token_value)
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to be_nil
+ end
+ end
+
+ context 'token_digest does not exist, nor token' do
+ let(:token) { nil }
+ let(:token_digest) { nil }
+
+ it 'creates token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+ end
+
+ describe '#reset_token!' do
+ subject { personal_access_token.reset_token! }
+
+ context 'token_digest already exists' do
+ let(:token) { nil }
+ let(:token_digest) { Gitlab::CryptoHelper.sha256('old-token') }
+
+ it 'creates new token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+
+ context 'token_digest does not exist but token does' do
+ let(:token) { 'old-token' }
+ let(:token_digest) { nil }
+
+ it 'creates new token_digest and clears token' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to eql(Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+
+ context 'token_digest does not exist, nor token' do
+ let(:token) { nil }
+ let(:token_digest) { nil }
+
+ it 'creates new token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+
+ context 'token_digest exists and newly generated token would be the same' do
+ let(:token) { nil }
+ let(:token_digest) { Gitlab::CryptoHelper.sha256('old-token') }
+
+ before do
+ personal_access_token.save
+ allow(Devise).to receive(:friendly_token).and_return(
+ 'old-token', token_value, 'boom!')
+ end
+
+ it 'regenerates a new token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+
+ context 'token exists and newly generated token would be the same' do
+ let(:token) { 'old-token' }
+ let(:token_digest) { nil }
+
+ before do
+ personal_access_token.save
+ allow(Devise).to receive(:friendly_token).and_return(
+ 'old-token', token_value, 'boom!')
+ end
+
+ it 'regenerates a new token_digest' do
+ subject
+
+ expect(personal_access_token.read_attribute('token')).to be_nil
+ expect(personal_access_token.token).to eql(token_value)
+ expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
+ end
end
end
end
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index f2eb263c98c..e7805d52d75 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -5,13 +5,15 @@ describe EnvironmentStatus do
let(:environment) { deployment.environment}
let(:project) { deployment.project }
let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
+ let(:sha) { deployment.sha }
- subject(:environment_status) { described_class.new(environment, merge_request) }
+ subject(:environment_status) { described_class.new(environment, merge_request, sha) }
it { is_expected.to delegate_method(:id).to(:environment) }
it { is_expected.to delegate_method(:name).to(:environment) }
it { is_expected.to delegate_method(:project).to(:environment) }
it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) }
+ it { is_expected.to delegate_method(:status).to(:deployment) }
describe '#project' do
subject { environment_status.project }
@@ -58,4 +60,32 @@ describe EnvironmentStatus do
)
end
end
+
+ describe '.for_merge_request' do
+ let(:admin) { create(:admin) }
+ let(:pipeline) { create(:ci_pipeline, sha: sha) }
+
+ it 'is based on merge_request.head_pipeline' do
+ expect(merge_request).to receive(:head_pipeline).and_return(pipeline)
+ expect(merge_request).not_to receive(:merge_pipeline)
+
+ described_class.for_merge_request(merge_request, admin)
+ end
+ end
+
+ describe '.after_merge_request' do
+ let(:admin) { create(:admin) }
+ let(:pipeline) { create(:ci_pipeline, sha: sha) }
+
+ before do
+ merge_request.mark_as_merged!
+ end
+
+ it 'is based on merge_request.merge_pipeline' do
+ expect(merge_request).to receive(:merge_pipeline).and_return(pipeline)
+ expect(merge_request).not_to receive(:head_pipeline)
+
+ described_class.after_merge_request(merge_request, admin)
+ end
+ end
end
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index ab58f5c5021..b6355455c1d 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -92,41 +92,6 @@ describe GlobalMilestone do
end
end
- describe '.states_count' do
- context 'when the projects have milestones' do
- before do
- create(:closed_milestone, title: 'Active Group Milestone', project: project3)
- create(:active_milestone, title: 'Active Group Milestone', project: project1)
- create(:active_milestone, title: 'Active Group Milestone', project: project2)
- create(:closed_milestone, title: 'Closed Group Milestone', project: project1)
- create(:closed_milestone, title: 'Closed Group Milestone', project: project2)
- create(:closed_milestone, title: 'Closed Group Milestone', project: project3)
- end
-
- it 'returns the quantity of global milestones in each possible state' do
- expected_count = { opened: 1, closed: 2, all: 2 }
-
- count = described_class.states_count(Project.all)
-
- expect(count).to eq(expected_count)
- end
- end
-
- context 'when the projects do not have milestones' do
- before do
- project1
- end
-
- it 'returns 0 as the quantity of global milestones in each state' do
- expected_count = { opened: 0, closed: 0, all: 0 }
-
- count = described_class.states_count(Project.all)
-
- expect(count).to eq(expected_count)
- end
- end
- end
-
describe '#initialize' do
let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) }
let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 1bf8f89e126..ada00f03928 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -19,6 +19,8 @@ describe Group do
it { is_expected.to have_one(:chat_team) }
it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
it { is_expected.to have_many(:badges).class_name('GroupBadge') }
+ it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
+ it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
describe '#members & #requesters' do
let(:requester) { create(:user) }
@@ -651,10 +653,10 @@ describe Group do
end
end
- describe '#secret_variables_for' do
+ describe '#ci_variables_for' do
let(:project) { create(:project, group: group) }
- let!(:secret_variable) do
+ let!(:ci_variable) do
create(:ci_group_variable, value: 'secret', group: group)
end
@@ -662,11 +664,11 @@ describe Group do
create(:ci_group_variable, :protected, value: 'protected', group: group)
end
- subject { group.secret_variables_for('ref', project) }
+ subject { group.ci_variables_for('ref', project) }
shared_examples 'ref is protected' do
it 'contains all the variables' do
- is_expected.to contain_exactly(secret_variable, protected_variable)
+ is_expected.to contain_exactly(ci_variable, protected_variable)
end
end
@@ -676,8 +678,8 @@ describe Group do
default_branch_protection: Gitlab::Access::PROTECTION_NONE)
end
- it 'contains only the secret variables' do
- is_expected.to contain_exactly(secret_variable)
+ it 'contains only the CI variables' do
+ is_expected.to contain_exactly(ci_variable)
end
end
@@ -710,9 +712,9 @@ describe Group do
end
it 'returns all variables belong to the group and parent groups' do
- expected_array1 = [protected_variable, secret_variable]
+ expected_array1 = [protected_variable, ci_variable]
expected_array2 = [variable_child, variable_child_2, variable_child_3]
- got_array = group_child_3.secret_variables_for('ref', project).to_a
+ got_array = group_child_3.ci_variables_for('ref', project).to_a
expect(got_array.shift(2)).to contain_exactly(*expected_array1)
expect(got_array).to eq(expected_array2)
diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb
index 6e35511e848..911f85d7b28 100644
--- a/spec/models/lfs_object_spec.rb
+++ b/spec/models/lfs_object_spec.rb
@@ -2,12 +2,6 @@ require 'spec_helper'
describe LfsObject do
describe '#local_store?' do
- it 'returns true when file_store is nil' do
- subject.file_store = nil
-
- expect(subject.local_store?).to eq true
- end
-
it 'returns true when file_store is equal to LfsObjectUploader::Store::LOCAL' do
subject.file_store = LfsObjectUploader::Store::LOCAL
@@ -83,19 +77,6 @@ describe LfsObject do
describe 'file is being stored' do
let(:lfs_object) { create(:lfs_object, :with_file) }
- context 'when object has nil store' do
- before do
- lfs_object.update_column(:file_store, nil)
- lfs_object.reload
- end
-
- it 'is stored locally' do
- expect(lfs_object.file_store).to be(nil)
- expect(lfs_object.file).to be_file_storage
- expect(lfs_object.file.object_store).to eq(ObjectStorage::Store::LOCAL)
- end
- end
-
context 'when existing object has local store' do
it 'is stored locally' do
expect(lfs_object.file_store).to be(ObjectStorage::Store::LOCAL)
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 666d7e69f89..c8943f2d86f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1058,6 +1058,26 @@ describe MergeRequest do
end
end
+ describe '#merge_pipeline' do
+ it 'returns nil when not merged' do
+ expect(subject.merge_pipeline).to be_nil
+ end
+
+ context 'when the MR is merged' do
+ let(:sha) { subject.target_project.commit.id }
+ let(:pipeline) { create(:ci_empty_pipeline, sha: sha, ref: subject.target_branch, project: subject.target_project) }
+
+ before do
+ subject.mark_as_merged!
+ subject.update_attribute(:merge_commit_sha, pipeline.sha)
+ end
+
+ it 'returns the post-merge pipeline' do
+ expect(subject.merge_pipeline).to eq(pipeline)
+ end
+ end
+ end
+
describe '#has_ci?' do
let(:merge_request) { build_stubbed(:merge_request) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 27d4e622710..d11eb46159e 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -348,4 +348,41 @@ describe Milestone do
end
end
end
+
+ describe '.states_count' do
+ context 'when the projects have milestones' do
+ before do
+ project_1 = create(:project)
+ project_2 = create(:project)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ create(:active_milestone, title: 'Active Group Milestone', project: project_1)
+ create(:closed_milestone, title: 'Closed Group Milestone', project: project_1)
+ create(:active_milestone, title: 'Active Group Milestone', project: project_2)
+ create(:closed_milestone, title: 'Closed Group Milestone', project: project_2)
+ create(:closed_milestone, title: 'Active Group Milestone', group: group_1)
+ create(:closed_milestone, title: 'Closed Group Milestone', group: group_1)
+ create(:closed_milestone, title: 'Active Group Milestone', group: group_2)
+ create(:closed_milestone, title: 'Closed Group Milestone', group: group_2)
+ end
+
+ it 'returns the quantity of milestones in each possible state' do
+ expected_count = { opened: 5, closed: 6, all: 11 }
+
+ count = described_class.states_count(Project.all, Group.all)
+ expect(count).to eq(expected_count)
+ end
+ end
+
+ context 'when the projects do not have milestones' do
+ it 'returns 0 as the quantity of global milestones in each state' do
+ expected_count = { opened: 0, closed: 0, all: 0 }
+
+ count = described_class.states_count([project])
+
+ expect(count).to eq(expected_count)
+ end
+ end
+ end
end
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 2bb1c49b740..c82ab9c9e62 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -49,18 +49,36 @@ describe PersonalAccessToken do
describe 'Redis storage' do
let(:user_id) { 123 }
- let(:token) { 'abc000foo' }
+ let(:token) { 'KS3wegQYXBLYhQsciwsj' }
- before do
- subject.redis_store!(user_id, token)
+ context 'reading encrypted data' do
+ before do
+ subject.redis_store!(user_id, token)
+ end
+
+ it 'returns stored data' do
+ expect(subject.redis_getdel(user_id)).to eq(token)
+ end
end
- it 'returns stored data' do
- expect(subject.redis_getdel(user_id)).to eq(token)
+ context 'reading unencrypted data' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(described_class.redis_shared_state_key(user_id),
+ token,
+ ex: PersonalAccessToken::REDIS_EXPIRY_TIME)
+ end
+ end
+
+ it 'returns stored data unmodified' do
+ expect(subject.redis_getdel(user_id)).to eq(token)
+ end
end
context 'after deletion' do
before do
+ subject.redis_store!(user_id, token)
+
expect(subject.redis_getdel(user_id)).to eq(token)
end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 0cd712e2f40..b0fd2ceead0 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -387,4 +387,22 @@ describe HipchatService do
end
end
end
+
+ context 'with UrlBlocker' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:hipchat) { described_class.new(project: project) }
+ let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+
+ describe '#execute' do
+ before do
+ hipchat.server = 'http://localhost:9123'
+ end
+
+ it 'raises UrlBlocker for localhost' do
+ expect(Gitlab::UrlBlocker).to receive(:validate!).and_call_original
+ expect { hipchat.execute(push_sample_data) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 62a38c66d99..e66838edd1a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2432,10 +2432,10 @@ describe Project do
end
end
- describe '#secret_variables_for' do
+ describe '#ci_variables_for' do
let(:project) { create(:project) }
- let!(:secret_variable) do
+ let!(:ci_variable) do
create(:ci_variable, value: 'secret', project: project)
end
@@ -2443,7 +2443,7 @@ describe Project do
create(:ci_variable, :protected, value: 'protected', project: project)
end
- subject { project.reload.secret_variables_for(ref: 'ref') }
+ subject { project.reload.ci_variables_for(ref: 'ref') }
before do
stub_application_setting(
@@ -2452,13 +2452,13 @@ describe Project do
shared_examples 'ref is protected' do
it 'contains all the variables' do
- is_expected.to contain_exactly(secret_variable, protected_variable)
+ is_expected.to contain_exactly(ci_variable, protected_variable)
end
end
context 'when the ref is not protected' do
- it 'contains only the secret variables' do
- is_expected.to contain_exactly(secret_variable)
+ it 'contains only the CI variables' do
+ is_expected.to contain_exactly(ci_variable)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b3474e74aa4..4e7c8523e65 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -730,6 +730,14 @@ describe User do
expect(user.incoming_email_token).not_to be_blank
end
+
+ it 'uses SecureRandom to generate the incoming email token' do
+ expect(SecureRandom).to receive(:hex).and_return('3b8ca303')
+
+ user = create(:user)
+
+ expect(user.incoming_email_token).to eql('gitlab')
+ end
end
describe '#ensure_user_rights_and_limits' do
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index a1b52d8692d..bafcddebbb7 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -403,6 +403,15 @@ describe MergeRequestPresenter do
is_expected
.to eq("<a href=\"/#{resource.source_project.full_path}/tree/#{resource.source_branch}\">#{resource.source_branch}</a>")
end
+
+ it 'escapes html, when source_branch does not exist' do
+ xss_attempt = "<img src='x' onerror=alert('bad stuff') />"
+
+ allow(resource).to receive(:source_branch) { xss_attempt }
+ allow(resource).to receive(:source_branch_exists?) { false }
+
+ is_expected.to eq(ERB::Util.html_escape(xss_attempt))
+ end
end
describe '#rebase_path' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 2ebcb787d06..5ea869796b0 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -683,7 +683,7 @@ describe API::Internal do
expect(json_response).to match [{
"branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch",
"new_merge_request" => true
}]
end
@@ -704,7 +704,7 @@ describe API::Internal do
expect(json_response).to match [{
"branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch",
"new_merge_request" => true
}]
end
@@ -837,7 +837,7 @@ describe API::Internal do
expect(json_response['merge_request_urls']).to match [{
"branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch",
"new_merge_request" => true
}]
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index c0d5a3ad74b..909703a8d47 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -806,6 +806,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it { expect(job).to be_unknown_failure }
end
+
+ context 'when failure_reason is job_execution_timeout' do
+ before do
+ update_job(state: 'failed', failure_reason: 'job_execution_timeout')
+ job.reload
+ end
+
+ it { expect(job).to be_job_execution_timeout }
+ end
end
context 'when trace is given' do
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index c40d01e1a14..08bada44178 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -158,6 +158,16 @@ describe API::Wikis do
expect(json_response.size).to eq(1)
expect(json_response['error']).to eq('file is missing')
end
+
+ it 'responds with validation error on invalid temp file' do
+ payload[:file] = { tempfile: '/etc/hosts' }
+
+ post(api(url, user), payload)
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is invalid')
+ end
end
describe 'GET /projects/:id/wikis' do
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
index 6894c65d639..1b4d8b70aa6 100644
--- a/spec/serializers/environment_status_entity_spec.rb
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -9,7 +9,7 @@ describe EnvironmentStatusEntity do
let(:project) { deployment.project }
let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
- let(:environment_status) { EnvironmentStatus.new(environment, merge_request) }
+ let(:environment_status) { EnvironmentStatus.new(environment, merge_request, merge_request.diff_head_sha) }
let(:entity) { described_class.new(environment_status, request: request) }
subject { entity.as_json }
@@ -26,6 +26,7 @@ describe EnvironmentStatusEntity do
it { is_expected.to include(:deployed_at) }
it { is_expected.to include(:deployed_at_formatted) }
it { is_expected.to include(:changes) }
+ it { is_expected.to include(:status) }
it { is_expected.not_to include(:stop_url) }
it { is_expected.not_to include(:metrics_url) }
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 5bf8aa7f23f..561421d5ac8 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -52,6 +52,40 @@ describe MergeRequestWidgetEntity do
end
end
+ describe 'merge_pipeline' do
+ it 'returns nil' do
+ expect(subject[:merge_pipeline]).to be_nil
+ end
+
+ context 'when is merged' do
+ let(:resource) { create(:merged_merge_request, source_project: project, merge_commit_sha: project.commit.id) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.target_branch, sha: resource.merge_commit_sha) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns merge_pipeline' do
+ pipeline.reload
+ pipeline_payload = PipelineDetailsEntity
+ .represent(pipeline, request: request)
+ .as_json
+
+ expect(subject[:merge_pipeline]).to eq(pipeline_payload)
+ end
+
+ context 'when user cannot read pipelines on target project' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns nil' do
+ expect(subject[:merge_pipeline]).to be_nil
+ end
+ end
+ end
+ end
+
describe 'metrics' do
context 'when metrics record exists with merged data' do
before do
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index d71ccfb4334..1289d3ce01f 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -177,7 +177,7 @@ describe Groups::TransferService, :postgresql do
it 'should add an error on group' do
transfer_service.execute(new_parent_group)
- expect(transfer_service.error).to eq('Transfer failed: Validation failed: Path has already been taken')
+ expect(transfer_service.error).to eq('Transfer failed: Validation failed: Group URL has already been taken')
end
end
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 274624aa8bb..3e33a165e55 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -6,7 +6,7 @@ describe MergeRequests::GetUrlsService do
let(:project) { create(:project, :public, :repository) }
let(:service) { described_class.new(project) }
let(:source_branch) { "merge-test" }
- let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
+ let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/#{source_branch}" }
let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
@@ -117,7 +117,7 @@ describe MergeRequests::GetUrlsService do
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/markdown" }
let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" }
- let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
+ let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch" }
it 'returns 2 urls for both creating new and showing merge request' do
result = service.execute(changes)
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 2536c6e2514..61c6ba7d550 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -306,6 +306,66 @@ describe MergeRequests::RefreshService do
end
end
+ context 'forked projects with the same source branch name as target branch' do
+ let!(:first_commit) do
+ @fork_project.repository.create_file(@user, 'test1.txt', 'Test data',
+ message: 'Test commit',
+ branch_name: 'master')
+ end
+ let!(:second_commit) do
+ @fork_project.repository.create_file(@user, 'test2.txt', 'More test data',
+ message: 'Second test commit',
+ branch_name: 'master')
+ end
+ let!(:forked_master_mr) do
+ create(:merge_request,
+ source_project: @fork_project,
+ source_branch: 'master',
+ target_branch: 'master',
+ target_project: @project)
+ end
+ let(:force_push_commit) { @project.commit('feature').id }
+
+ it 'should reload a new diff for a push to the forked project' do
+ expect do
+ service.new(@fork_project, @user).execute(@oldrev, first_commit, 'refs/heads/master')
+ reload_mrs
+ end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
+ end
+
+ it 'should reload a new diff for a force push to the source branch' do
+ expect do
+ service.new(@fork_project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
+ reload_mrs
+ end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
+ end
+
+ it 'should reload a new diff for a force push to the target branch' do
+ expect do
+ service.new(@project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
+ reload_mrs
+ end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
+ end
+
+ it 'should reload a new diff for a push to the target project that contains a commit in the MR' do
+ expect do
+ service.new(@project, @user).execute(@oldrev, first_commit, 'refs/heads/master')
+ reload_mrs
+ end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
+ end
+
+ it 'should not increase the diff count for a new push to target branch' do
+ new_commit = @project.repository.create_file(@user, 'new-file.txt', 'A new file',
+ message: 'This is a test',
+ branch_name: 'master')
+
+ expect do
+ service.new(@project, @user).execute(@newrev, new_commit, 'refs/heads/master')
+ reload_mrs
+ end.not_to change { forked_master_mr.merge_request_diffs.count }
+ end
+ end
+
context 'push to origin repo target branch after fork project was removed' do
before do
@fork_project.destroy
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index f7f851eb1eb..bce1fb01355 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -5,7 +5,7 @@ shared_examples 'variable list' do
end
end
- it 'adds new secret variable' do
+ it 'adds new CI variable' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
find('.js-ci-variable-input-value').set('key value')
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index 7038a366144..9373de5aeab 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -17,10 +17,10 @@ RSpec.shared_examples 'a creatable merge request' do
sign_in(user)
visit project_new_merge_request_path(
target_project,
+ merge_request_source_branch: 'fix',
merge_request: {
source_project_id: source_project.id,
target_project_id: target_project.id,
- source_branch: 'fix',
target_branch: 'master'
})
end
diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml
index 049ffcc3407..24136a7aca5 100644
--- a/vendor/jupyter/values.yaml
+++ b/vendor/jupyter/values.yaml
@@ -16,7 +16,7 @@ singleuser:
lifecycleHooks:
postStart:
exec:
- command: ["git", "clone", "https://gitlab.com/gitlab-org/nurtch-demo.git", "DevOps-Runbook-Demo"]
+ command: ["sh", "-c", "git clone https://gitlab.com/gitlab-org/nurtch-demo.git DevOps-Runbook-Demo || true"]
ingress:
enabled: true
diff --git a/yarn.lock b/yarn.lock
index 5da401c1d43..79f1b757252 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -626,10 +626,10 @@
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.33.0.tgz#068566e8ee00795f6f09f58236f08e1716f9f04a"
integrity sha512-8ajtUHk6gQ1xosL/CO5IzHSFM/t18hx5pfzQ3cd0VuQXcyR6QKGuXTLwbYdmJDYOw1Etoo5DqDWxPEClHyZpiA==
-"@gitlab-org/gitlab-ui@^1.8.0":
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-ui/-/gitlab-ui-1.8.0.tgz#dee33d78f68c91644273dbd51734b796108263ee"
- integrity sha512-Owm8bkP4vEihiLD3pmMw1r+UWr3WYGaGUtj0JcwaAg3d05ZneozFEZjazIOWeYTcFsk+ZvNmSk1UA+ARIauhgQ==
+"@gitlab-org/gitlab-ui@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-ui/-/gitlab-ui-1.9.0.tgz#c47851587316f60926e8304747d1fcdd1222c779"
+ integrity sha512-OQ/mhWnbeG4pmjnCGwLsyvmHDYdLh2IRnt4Jx6G9jf96oyjEHzY1rveImfqcQ2bvx9azfuI6CU9dmDSY3aWvvQ==
dependencies:
"@gitlab-org/gitlab-svgs" "^1.23.0"
bootstrap-vue "^2.0.0-rc.11"
@@ -5502,12 +5502,7 @@ mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
dependencies:
minimist "0.0.8"
-moment@2.x:
- version "2.19.2"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe"
- integrity sha512-Rf6jiHPEfxp9+dlzxPTmRHbvoFXsh2L/U8hOupUMpnuecHQmI6cF6lUbJl3QqKPko1u6ujO+FxtcajLVfLpAtA==
-
-moment@^2.21.0:
+moment@2.x, moment@^2.21.0:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=