Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md10
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock21
-rw-r--r--app/assets/javascripts/dispatcher.js6
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js6
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue3
-rw-r--r--app/assets/javascripts/groups/store/groups_store.js2
-rw-r--r--app/assets/javascripts/jobs/components/header.vue8
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue49
-rw-r--r--app/assets/javascripts/monitoring/components/graph/deployment.vue129
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue205
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js5
-rw-r--r--app/assets/javascripts/monitoring/utils/date_time_formatters.js2
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js (renamed from app/assets/javascripts/todos.js)6
-rw-r--r--app/assets/javascripts/pipelines/components/nav_controls.vue13
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue2
-rw-r--r--app/assets/javascripts/users_select.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue12
-rw-r--r--app/assets/stylesheets/framework/images.scss11
-rw-r--r--app/assets/stylesheets/framework/layout.scss4
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss12
-rw-r--r--app/assets/stylesheets/pages/environments.scss67
-rw-r--r--app/controllers/projects/application_controller.rb4
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/controllers/projects/clusters/gcp_controller.rb29
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb10
-rw-r--r--app/helpers/selects_helper.rb1
-rw-r--r--app/models/ci/build.rb11
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/project.rb22
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/serializers/group_child_entity.rb9
-rw-r--r--app/serializers/job_entity.rb2
-rw-r--r--app/services/check_gcp_project_billing_service.rb8
-rw-r--r--app/services/git_push_service.rb19
-rw-r--r--app/services/protected_branches/create_service.rb4
-rw-r--r--app/services/reset_project_cache_service.rb5
-rw-r--r--app/views/dashboard/projects/_nav.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/devise_empty.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml12
-rw-r--r--app/views/projects/clusters/gcp/_header.html.haml2
-rw-r--r--app/views/projects/clusters/gcp/login.html.haml2
-rw-r--r--app/views/projects/jobs/_empty_state.html.haml17
-rw-r--r--app/views/projects/jobs/show.html.haml76
-rw-r--r--app/views/projects/pipelines/index.html.haml3
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/background_migration_worker.rb45
-rw-r--r--app/workers/check_gcp_project_billing_worker.rb59
-rw-r--r--changelogs/unreleased/28260-fix-pages-custom-domain-url.yml5
-rw-r--r--changelogs/unreleased/38030-add-graph-value-to-hover.yml5
-rw-r--r--changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml5
-rw-r--r--changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml5
-rw-r--r--changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml5
-rw-r--r--changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml5
-rw-r--r--changelogs/unreleased/40549-render-emoj-in-groups-overview.yml5
-rw-r--r--changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml5
-rw-r--r--changelogs/unreleased/41249-clearing-the-cache.yml5
-rw-r--r--changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml5
-rw-r--r--changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml5
-rw-r--r--changelogs/unreleased/delay-background-migrations.yml5
-rw-r--r--changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml6
-rw-r--r--changelogs/unreleased/sh-store-user-in-api-logs.yml5
-rw-r--r--config/routes/project.rb4
-rw-r--r--config/webpack.config.js30
-rw-r--r--db/fixtures/development/04_project.rb3
-rw-r--r--db/migrate/20160621123729_add_rebase_commit_sha_to_merge_requests.rb22
-rw-r--r--db/migrate/20171222183504_add_jobs_cache_index_to_project.rb13
-rw-r--r--db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests.rb7
-rw-r--r--db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests_ce.rb15
-rw-r--r--db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb151
-rw-r--r--db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb32
-rw-r--r--db/schema.rb1
-rw-r--r--doc/development/fe_guide/style_guide_scss.md2
-rw-r--r--doc/development/writing_documentation.md4
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/user/project/settings/import_export.md3
-rw-r--r--features/explore/groups.feature105
-rw-r--r--features/invites.feature45
-rw-r--r--features/steps/explore/groups.rb88
-rw-r--r--features/steps/invites.rb80
-rw-r--r--features/steps/shared/builds.rb2
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/api/helpers.rb7
-rw-r--r--lib/gitlab/database/migration_helpers.rb6
-rw-r--r--lib/gitlab/exclusive_lease.rb11
-rw-r--r--lib/gitlab/git/blob.rb4
-rw-r--r--lib/gitlab/git/commit.rb7
-rw-r--r--lib/gitlab/git/repository.rb28
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb13
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb7
-rw-r--r--lib/gitlab/grape_logging/loggers/user_logger.rb18
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml4
-rw-r--r--lib/gitlab/import_export/relation_factory.rb18
-rw-r--r--lib/gitlab/regex.rb2
-rw-r--r--lib/gitlab/shell.rb2
-rw-r--r--lib/google_api/cloud_platform/client.rb18
-rw-r--r--package.json2
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/runtime/browser.rb41
-rw-r--r--qa/qa/runtime/env.rb15
-rw-r--r--qa/spec/runtime/env_spec.rb64
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb10
-rw-r--r--spec/controllers/projects/clusters/gcp_controller_spec.rb18
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb47
-rw-r--r--spec/factories/ci/builds.rb25
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb36
-rw-r--r--spec/features/explore/groups_spec.rb87
-rw-r--r--spec/features/groups/show_spec.rb16
-rw-r--r--spec/features/invites_spec.rb97
-rw-r--r--spec/features/issues/keyboard_shortcut_spec.rb36
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb155
-rw-r--r--spec/features/projects/clusters_spec.rb14
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin343232 -> 343092 bytes
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb28
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb34
-rw-r--r--spec/javascripts/fixtures/pipelines.html.haml4
-rw-r--r--spec/javascripts/jobs/header_spec.js3
-rw-r--r--spec/javascripts/merge_request_spec.js15
-rw-r--r--spec/javascripts/monitoring/graph/deployment_spec.js144
-rw-r--r--spec/javascripts/monitoring/graph/flag_spec.js114
-rw-r--r--spec/javascripts/pipelines/nav_controls_spec.js27
-rw-r--r--spec/javascripts/todos_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js81
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js2
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb14
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb14
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb15
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb4
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb1
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb42
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb13
-rw-r--r--spec/lib/gitlab/import_export/project.json770
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/regex_spec.rb3
-rw-r--r--spec/lib/gitlab/shell_spec.rb20
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb24
-rw-r--r--spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb312
-rw-r--r--spec/migrations/normalize_ldap_extern_uids_spec.rb6
-rw-r--r--spec/models/ci/build_spec.rb36
-rw-r--r--spec/models/commit_spec.rb1
-rw-r--r--spec/models/pages_domain_spec.rb2
-rw-r--r--spec/models/project_spec.rb42
-rw-r--r--spec/requests/api/helpers_spec.rb6
-rw-r--r--spec/requests/api/jobs_spec.rb4
-rw-r--r--spec/serializers/group_child_entity_spec.rb12
-rw-r--r--spec/services/check_gcp_project_billing_service_spec.rb31
-rw-r--r--spec/services/protected_branches/create_service_spec.rb16
-rw-r--r--spec/services/reset_project_cache_service_spec.rb28
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb6
-rw-r--r--spec/workers/background_migration_worker_spec.rb23
-rw-r--r--spec/workers/check_gcp_project_billing_worker_spec.rb61
-rw-r--r--spec/workers/repository_import_worker_spec.rb1
-rw-r--r--vendor/gitignore/Eagle.gitignore10
-rw-r--r--vendor/gitignore/Global/Eclipse.gitignore3
-rw-r--r--vendor/gitignore/Global/JetBrains.gitignore1
-rw-r--r--vendor/gitignore/Global/Matlab.gitignore20
-rw-r--r--vendor/gitignore/Go.gitignore1
-rw-r--r--vendor/gitignore/Node.gitignore2
-rw-r--r--vendor/gitignore/Rails.gitignore4
-rw-r--r--vendor/gitignore/Umbraco.gitignore7
-rw-r--r--vendor/gitignore/VisualStudio.gitignore8
-rw-r--r--vendor/gitignore/WordPress.gitignore1
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml25
-rw-r--r--vendor/gitlab-ci-yml/Mono.gitlab-ci.yml42
-rw-r--r--vendor/gitlab-ci-yml/Rust.gitlab-ci.yml2
-rw-r--r--vendor/licenses.csv19
-rw-r--r--vendor/project_templates/express.tar.gzbin5651 -> 5614 bytes
-rw-r--r--vendor/project_templates/rails.tar.gzbin25065 -> 25007 bytes
-rw-r--r--vendor/project_templates/spring.tar.gzbin50845 -> 50945 bytes
181 files changed, 3167 insertions, 1434 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2b79f0825e2..057e2d6e0dc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -104,9 +104,13 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
-If you want to contribute to GitLab, [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight] is a great place to start. Issues with a lower weight (1 or 2) are deemed suitable for beginners.
-These issues will be of reasonable size and challenge, for anyone to start
-contributing to GitLab.
+If you want to contribute to GitLab [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight]
+is a great place to start. Issues with a lower weight (1 or 2) are deemed
+suitable for beginners. These issues will be of reasonable size and challenge,
+for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
+learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
+please consider we favor
+[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
## Workflow labels
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index afed694eede..e40e4fc339c 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.65.0
+0.66.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index bea438e9ade..18091983f59 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-3.3.1
+3.4.0
diff --git a/Gemfile b/Gemfile
index 38381d34b6b..23d0b3d9b3e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -78,7 +78,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
gem 'grape', '~> 1.0'
gem 'grape-entity', '~> 0.6.0'
-gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
+gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
# Disable strong_params so that Mash does not respond to :permitted?
gem 'hashie-forbidden_attributes'
@@ -339,7 +339,7 @@ group :development, :test do
gem 'rubocop', '~> 0.52.0'
gem 'rubocop-rspec', '~> 1.20.1'
- gem 'scss_lint', '~> 0.54.0', require: false
+ gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 2a81c81b0f8..d10da1bd1c3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -651,7 +651,7 @@ GEM
rack (>= 0.4)
rack-attack (4.4.1)
rack
- rack-cors (0.4.0)
+ rack-cors (1.0.2)
rack-oauth2 (1.2.3)
activesupport (>= 2.3)
attr_required (>= 0.0.5)
@@ -695,6 +695,9 @@ GEM
rake
raindrops (0.18.0)
rake (12.3.0)
+ rb-fsevent (0.10.2)
+ rb-inotify (0.9.10)
+ ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
@@ -809,7 +812,11 @@ GEM
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
- sass (3.4.22)
+ sass (3.5.5)
+ sass-listen (~> 4.0.0)
+ sass-listen (4.0.0)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
@@ -819,9 +826,9 @@ GEM
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
- scss_lint (0.54.0)
+ scss_lint (0.56.0)
rake (>= 0.9, < 13)
- sass (~> 3.4.20)
+ sass (~> 3.5.3)
securecompare (1.0.0)
seed-fu (2.3.6)
activerecord (>= 3.1)
@@ -1126,7 +1133,7 @@ DEPENDENCIES
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
- rack-cors (~> 0.4.0)
+ rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 4.2.10)
@@ -1162,7 +1169,7 @@ DEPENDENCIES
rugged (~> 0.26.0)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
- scss_lint (~> 0.54.0)
+ scss_lint (~> 0.56.0)
seed-fu (= 2.3.6)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
@@ -1205,4 +1212,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.16.0
+ 1.16.1
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 42f61d33f6e..9e8b2acfe1b 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -56,7 +56,6 @@ import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import Star from './star';
-import Todos from './todos';
import TreeView from './tree';
import UsagePing from './usage_ping';
import UsernameValidator from './username_validator';
@@ -111,6 +110,7 @@ import Activities from './activities';
}
const fail = () => Flash('Error loading dynamic module');
+ const callDefault = m => m.default();
path = page.split(':');
shortcut_handler = null;
@@ -212,7 +212,7 @@ import Activities from './activities';
projectSelect();
break;
case 'dashboard:todos:index':
- new Todos();
+ import('./pages/dashboard/todos/index').then(callDefault).catch(fail);
break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
@@ -542,7 +542,7 @@ import Activities from './activities';
new CILintEditor();
break;
case 'users:show':
- import('./pages/users/show').then(m => m.default()).catch(fail);
+ import('./pages/users/show').then(callDefault).catch(fail);
break;
case 'admin:conversational_development_index:show':
new UserCallout();
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index d918d80df8d..df20e1e9c88 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -57,12 +57,12 @@ class GfmAutoComplete {
displayTpl(value) {
if (GfmAutoComplete.isLoading(value)) return GfmAutoComplete.Loading.template;
// eslint-disable-next-line no-template-curly-in-string
- let tpl = '<li>/${name}';
+ let tpl = '<li><span class="name">/${name}</span>';
if (value.aliases.length > 0) {
- tpl += ' <small>(or /<%- aliases.join(", /") %>)</small>';
+ tpl += ' <small class="aliases">(or /<%- aliases.join(", /") %>)</small>';
}
if (value.params.length > 0) {
- tpl += ' <small><%- params.join(" ") %></small>';
+ tpl += ' <small class="params"><%- params.join(" ") %></small>';
}
if (value.description !== '') {
tpl += '<small class="description"><i><%- description %></i></small>';
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 02129d39846..42e79a9e17a 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -141,7 +141,8 @@ export default {
<div
v-if="group.description"
class="description">
- {{group.description}}
+ <span v-html="group.description">
+ </span>
</div>
</div>
<group-folder
diff --git a/app/assets/javascripts/groups/store/groups_store.js b/app/assets/javascripts/groups/store/groups_store.js
index ffc86175548..4a7569078a1 100644
--- a/app/assets/javascripts/groups/store/groups_store.js
+++ b/app/assets/javascripts/groups/store/groups_store.js
@@ -71,7 +71,7 @@ export default class GroupsStore {
id: rawGroupItem.id,
name: rawGroupItem.name,
fullName: rawGroupItem.full_name,
- description: rawGroupItem.description,
+ description: rawGroupItem.markdown_description,
visibility: rawGroupItem.visibility,
avatarUrl: rawGroupItem.avatar_url,
relativePath: rawGroupItem.relative_path,
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index 6d671845f8e..c660828b30e 100644
--- a/app/assets/javascripts/jobs/components/header.vue
+++ b/app/assets/javascripts/jobs/components/header.vue
@@ -30,6 +30,9 @@
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length;
},
+ jobStarted() {
+ return this.job.started;
+ },
},
methods: {
getActions() {
@@ -63,8 +66,9 @@
:time="job.created_at"
:user="job.user"
:actions="actions"
- :hasSidebarButton="true"
- />
+ :has-sidebar-button="true"
+ :should-render-triggered-label="jobStarted"
+ />
<loading-icon
v-if="isLoading"
size="2"
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index eede04a06cd..a50b80c23d0 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -69,8 +69,8 @@
currentFlagPosition: 0,
showFlag: false,
showFlagContent: false,
- showDeployInfo: true,
timeSeries: [],
+ realPixelRatio: 1,
};
},
@@ -87,10 +87,7 @@
},
innerViewBox() {
- if ((this.baseGraphWidth - 150) > 0) {
- return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
- }
- return '0 0 0 0';
+ return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
},
axisTransform() {
@@ -102,6 +99,10 @@
paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
};
},
+
+ deploymentFlagData() {
+ return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
+ },
},
methods: {
@@ -122,6 +123,10 @@
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight;
this.baseGraphWidth = this.graphWidth;
+
+ // pixel offsets inside the svg and outside are not 1:1
+ this.realPixelRatio = (this.$refs.baseSvg.clientWidth / this.baseGraphWidth);
+
this.renderAxesPaths();
this.formatDeployments();
},
@@ -261,6 +266,11 @@
:line-color="path.lineColor"
:area-color="path.areaColor"
/>
+ <graph-deployment
+ :deployment-data="reducedDeploymentData"
+ :graph-height="graphHeight"
+ :graph-height-offset="graphHeightOffset"
+ />
<rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
@@ -269,24 +279,21 @@
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
- <graph-deployment
- :show-deploy-info="showDeployInfo"
- :deployment-data="reducedDeploymentData"
- :graph-width="graphWidth"
- :graph-height="graphHeight"
- :graph-height-offset="graphHeightOffset"
- />
- <graph-flag
- v-if="showFlag"
- :current-x-coordinate="currentXCoordinate"
- :current-data="currentData"
- :current-flag-position="currentFlagPosition"
- :graph-height="graphHeight"
- :graph-height-offset="graphHeightOffset"
- :show-flag-content="showFlagContent"
- />
</svg>
</svg>
+ <graph-flag
+ :real-pixel-ratio="realPixelRatio"
+ :current-x-coordinate="currentXCoordinate"
+ :current-data="currentData"
+ :graph-height="graphHeight"
+ :graph-height-offset="graphHeightOffset"
+ :show-flag-content="showFlagContent"
+ :time-series="timeSeries"
+ :unit-of-display="unitOfDisplay"
+ :current-data-index="currentDataIndex"
+ :legend-title="legendTitle"
+ :deployment-flag-data="deploymentFlagData"
+ />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue
index 026e2fd0c49..8d6393d4ce5 100644
--- a/app/assets/javascripts/monitoring/components/graph/deployment.vue
+++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue
@@ -1,13 +1,6 @@
<script>
- import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
- import Icon from '../../../vue_shared/components/icon.vue';
-
export default {
props: {
- showDeployInfo: {
- type: Boolean,
- required: true,
- },
deploymentData: {
type: Array,
required: true,
@@ -20,14 +13,6 @@
type: Number,
required: true,
},
- graphWidth: {
- type: Number,
- required: true,
- },
- },
-
- components: {
- Icon,
},
computed: {
@@ -37,52 +22,17 @@
},
methods: {
- refText(d) {
- return d.tag ? d.ref : d.sha.slice(0, 8);
- },
-
- formatTime(deploymentTime) {
- return timeFormat(deploymentTime);
- },
-
- formatDate(deploymentTime) {
- return dateFormatWithName(deploymentTime);
- },
-
- nameDeploymentClass(deployment) {
- return `deploy-info-${deployment.id}`;
- },
-
transformDeploymentGroup(deployment) {
- return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
- },
-
- positionFlag(deployment) {
- let xPosition = 3;
- if (deployment.xPos > (this.graphWidth - 225)) {
- xPosition = -142;
- }
- return xPosition;
- },
-
- svgContainerHeight(tag) {
- let svgHeight = 80;
- if (!tag) {
- svgHeight -= 20;
- }
- return svgHeight;
+ return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
},
},
};
</script>
<template>
- <g
- class="deploy-info"
- v-if="showDeployInfo">
+ <g class="deploy-info">
<g
v-for="(deployment, index) in deploymentData"
:key="index"
- :class="nameDeploymentClass(deployment)"
:transform="transformDeploymentGroup(deployment)">
<rect
x="0"
@@ -99,81 +49,6 @@
:y2="calculatedHeight"
stroke="#000">
</line>
- <svg
- v-if="deployment.showDeploymentFlag"
- class="js-deploy-info-box"
- :x="positionFlag(deployment)"
- y="0"
- width="134"
- :height="svgContainerHeight(deployment.tag)">
- <rect
- class="rect-text-metric deploy-info-rect rect-metric"
- x="1"
- y="1"
- rx="2"
- width="132"
- :height="svgContainerHeight(deployment.tag) - 2">
- </rect>
- <text
- class="deploy-info-text text-metric-bold"
- transform="translate(5, 2)">
- Deployed
- </text>
- <!--The date info-->
- <g transform="translate(5, 20)">
- <text class="deploy-info-text">
- {{formatDate(deployment.time)}}
- </text>
- <text
- class="deploy-info-text text-metric-bold"
- x="62">
- {{formatTime(deployment.time)}}
- </text>
- </g>
- <line
- class="divider-line"
- x1="0"
- y1="38"
- x2="132"
- :y2="38"
- stroke="#000">
- </line>
- <!--Commit information-->
- <g transform="translate(5, 40)">
- <icon
- name="commit"
- :width="12"
- :height="12"
- :y="3">
- </icon>
- <a :xlink:href="deployment.commitUrl">
- <text
- class="deploy-info-text deploy-info-text-link"
- transform="translate(20, 2)">
- {{refText(deployment)}}
- </text>
- </a>
- </g>
- <!--Tag information-->
- <g
- transform="translate(5, 55)"
- v-if="deployment.tag">
- <icon
- name="label"
- :width="12"
- :height="12"
- :y="5">
- </icon>
- <a :xlink:href="deployment.tagUrl">
- <text
- class="deploy-info-text deploy-info-text-link"
- transform="translate(20, 2)"
- y="2">
- {{deployment.tag}}
- </text>
- </a>
- </g>
- </svg>
</g>
<svg
height="0"
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index 10fb7ff6803..62ebc3f419c 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -1,5 +1,7 @@
<script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
+ import { formatRelevantDigits } from '../../../lib/utils/number_utils';
+ import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
@@ -7,14 +9,15 @@
type: Number,
required: true,
},
- currentFlagPosition: {
- type: Number,
- required: true,
- },
currentData: {
type: Object,
required: true,
},
+ deploymentFlagData: {
+ type: Object,
+ required: false,
+ default: null,
+ },
graphHeight: {
type: Number,
required: true,
@@ -23,71 +26,173 @@
type: Number,
required: true,
},
+ realPixelRatio: {
+ type: Number,
+ required: true,
+ },
showFlagContent: {
type: Boolean,
required: true,
},
+ timeSeries: {
+ type: Array,
+ required: true,
+ },
+ unitOfDisplay: {
+ type: String,
+ required: true,
+ },
+ currentDataIndex: {
+ type: Number,
+ required: true,
+ },
+ legendTitle: {
+ type: String,
+ required: true,
+ },
},
- data() {
- return {
- circleColorRgb: '#8fbce8',
- };
+ components: {
+ Icon,
},
computed: {
formatTime() {
- return timeFormat(this.currentData.time);
+ return this.deploymentFlagData ?
+ timeFormat(this.deploymentFlagData.time) :
+ timeFormat(this.currentData.time);
},
formatDate() {
- return dateFormat(this.currentData.time);
+ return this.deploymentFlagData ?
+ dateFormat(this.deploymentFlagData.time) :
+ dateFormat(this.currentData.time);
+ },
+
+ cursorStyle() {
+ const xCoordinate = this.deploymentFlagData ?
+ this.deploymentFlagData.xPos :
+ this.currentXCoordinate;
+
+ const offsetTop = 20 * this.realPixelRatio;
+ const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
+ const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
+
+ return {
+ top: `${offsetTop}px`,
+ left: `${offsetLeft}px`,
+ height: `${height}px`,
+ };
+ },
+
+ flagOrientation() {
+ if (this.currentXCoordinate * this.realPixelRatio > 120) {
+ return 'left';
+ }
+ return 'right';
+ },
+ },
+
+ methods: {
+ seriesMetricValue(series) {
+ const index = this.deploymentFlagData ?
+ this.deploymentFlagData.seriesIndex :
+ this.currentDataIndex;
+ const value = series.values[index] &&
+ series.values[index].value;
+ if (isNaN(value)) {
+ return '-';
+ }
+ return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
},
- calculatedHeight() {
- return this.graphHeight - this.graphHeightOffset;
+ seriesMetricLabel(index, series) {
+ if (this.timeSeries.length < 2) {
+ return this.legendTitle;
+ }
+ if (series.metricTag) {
+ return series.metricTag;
+ }
+ return `series ${index + 1}`;
+ },
+
+ strokeDashArray(type) {
+ if (type === 'dashed') return '6, 3';
+ if (type === 'dotted') return '3, 3';
+ return null;
},
},
};
</script>
+
<template>
- <g class="mouse-over-flag">
- <line
- class="selected-metric-line"
- :x1="currentXCoordinate"
- :y1="0"
- :x2="currentXCoordinate"
- :y2="calculatedHeight"
- transform="translate(-5, 20)">
- </line>
- <svg
+ <div
+ class="prometheus-graph-cursor"
+ :style="cursorStyle"
+ >
+ <div
v-if="showFlagContent"
- class="rect-text-metric"
- :x="currentFlagPosition"
- y="0">
- <rect
- class="rect-metric"
- x="4"
- y="1"
- rx="2"
- width="90"
- height="40"
- transform="translate(-3, 20)">
- </rect>
- <text
- class="text-metric text-metric-bold"
- x="16"
- y="35"
- transform="translate(-5, 20)">
- {{formatTime}}
- </text>
- <text
- class="text-metric"
- x="16"
- y="15"
- transform="translate(-5, 20)">
- {{formatDate}}
- </text>
- </svg>
- </g>
+ class="prometheus-graph-flag popover"
+ :class="flagOrientation"
+ >
+ <div class="arrow"></div>
+ <div class="popover-title">
+ <h5 v-if="this.deploymentFlagData">
+ Deployed
+ </h5>
+ {{formatDate}} at
+ <strong>{{formatTime}}</strong>
+ </div>
+ <div
+ v-if="this.deploymentFlagData"
+ class="popover-content deploy-meta-content"
+ >
+ <div>
+ <icon
+ name="commit"
+ :size="12">
+ </icon>
+ <a :href="deploymentFlagData.commitUrl">
+ {{deploymentFlagData.sha.slice(0, 8)}}
+ </a>
+ </div>
+ <div
+ v-if="deploymentFlagData.tag">
+ <icon
+ name="label"
+ :size="12">
+ </icon>
+ <a :href="deploymentFlagData.tagUrl">
+ {{deploymentFlagData.ref}}
+ </a>
+ </div>
+ </div>
+ <div class="popover-content">
+ <table>
+ <tr
+ v-for="(series, index) in timeSeries"
+ :key="index"
+ >
+ <td>
+ <svg width="15" height="6">
+ <line
+ :stroke="series.lineColor"
+ :stroke-dasharray="strokeDashArray(series.lineStyle)"
+ stroke-width="4"
+ x1="0"
+ x2="15"
+ y1="2"
+ y2="2">
+ </line>
+ </svg>
+ </td>
+ <td>{{seriesMetricLabel(index, series)}}</td>
+ <td>
+ <strong>{{seriesMetricValue(series)}}</strong>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
</template>
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index cbca14ede02..6cc67ba57ee 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -29,15 +29,18 @@ const mixins = {
time.setSeconds(this.timeSeries[0].values[0].time.getSeconds());
if (xPos >= 0) {
+ const seriesIndex = bisectDate(this.timeSeries[0].values, time, 1);
+
deploymentDataArray.push({
id: deployment.id,
time,
sha: deployment.sha,
commitUrl: `${this.projectPath}/commit/${deployment.sha}`,
tag: deployment.tag,
- tagUrl: `${this.tagsPath}/${deployment.tag}`,
+ tagUrl: deployment.tag ? `${this.tagsPath}/${deployment.ref.name}` : null,
ref: deployment.ref.name,
xPos,
+ seriesIndex,
showDeploymentFlag: false,
});
}
diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
index 068813ddee6..f3c9acdd93e 100644
--- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js
+++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
@@ -14,7 +14,7 @@ const d3 = {
timeYear,
};
-export const dateFormat = d3.time('%b %-d, %Y');
+export const dateFormat = d3.time('%a, %b %-d');
export const timeFormat = d3.time('%-I:%M%p');
export const dateFormatWithName = d3.time('%a, %b %-d');
export const bisectDate = d3.bisector(d => d.time).left;
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/index.js b/app/assets/javascripts/pages/dashboard/todos/index/index.js
new file mode 100644
index 00000000000..77c23685943
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/todos/index/index.js
@@ -0,0 +1,3 @@
+import Todos from './todos';
+
+export default () => new Todos();
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index 748caecf153..e976a3d2f1d 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
-import { visitUrl } from './lib/utils/url_utility';
-import UsersSelect from './users_select';
-import { isMetaClick } from './lib/utils/common_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import UsersSelect from '~/users_select';
+import { isMetaClick } from '~/lib/utils/common_utils';
export default class Todos {
constructor() {
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue
index 632fc167f2b..f31a91c3403 100644
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/nav_controls.vue
@@ -17,6 +17,11 @@ export default {
required: true,
},
+ resetCachePath: {
+ type: String,
+ required: true,
+ },
+
ciLintPath: {
type: String,
required: true,
@@ -46,6 +51,14 @@ export default {
</a>
<a
+ data-method="post"
+ rel="nofollow"
+ :href="resetCachePath"
+ class="btn btn-default">
+ Clear runner caches
+ </a>
+
+ <a
:href="ciLintPath"
class="btn btn-default">
CI Lint
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index fe1f3b4246a..8fa416168e7 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -50,6 +50,7 @@
canCreatePipeline: pipelinesData.canCreatePipeline,
hasCi: pipelinesData.hasCi,
ciLintPath: pipelinesData.ciLintPath,
+ resetCachePath: pipelinesData.resetCachePath,
state: this.store.state,
scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1',
@@ -220,6 +221,7 @@
:new-pipeline-path="newPipelinePath"
:has-ci-enabled="hasCiEnabled"
:help-page-path="helpPagePath"
+ :resetCachePath="resetCachePath"
:ci-lint-path="ciLintPath"
:can-create-pipeline="canCreatePipelineParsed "
/>
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 759cc9925f4..f249bd036d6 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -541,7 +541,6 @@ function UsersSelect(currentUser, els, options = {}) {
options.projectId = $(select).data('project-id');
options.groupId = $(select).data('group-id');
options.showCurrentUser = $(select).data('current-user');
- options.pushCodeToProtectedBranches = $(select).data('push-code-to-protected-branches');
options.authorId = $(select).data('author-id');
options.skipUsers = $(select).data('skip-users');
showNullUser = $(select).data('null-user');
@@ -688,7 +687,6 @@ UsersSelect.prototype.users = function(query, options, callback) {
todo_filter: options.todoFilter || null,
todo_state_filter: options.todoStateFilter || null,
current_user: options.showCurrentUser || null,
- push_code_to_protected_branches: options.pushCodeToProtectedBranches || null,
author_id: options.authorId || null,
skip_users: options.skipUsers || null
},
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index d305bd6acdc..2209bc0f9cf 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -45,6 +45,11 @@ export default {
required: false,
default: false,
},
+ shouldRenderTriggeredLabel: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
directives: {
@@ -82,7 +87,12 @@ export default {
{{itemName}} #{{itemId}}
</strong>
- triggered
+ <template v-if="shouldRenderTriggeredLabel">
+ triggered
+ </template>
+ <template v-else>
+ created
+ </template>
<timeago-tooltip :time="time" />
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index aa2d30a3cef..fd5c3c81a53 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -20,10 +20,13 @@
width: 100%;
}
- &.svg-250 {
- img,
- svg {
- width: 250px;
+ $image-widths: 250 306 394;
+ @each $width in $image-widths {
+ &.svg-#{$width} {
+ img,
+ svg {
+ width: #{$width + 'px'};
+ }
}
}
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 3f0268541a4..fab3270b9f5 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -106,10 +106,6 @@ body {
}
}
-.layout-page > .content-wrapper {
- min-height: calc(100vh - #{$header-height});
-}
-
.with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height;
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 6b07ffdbd61..938f5f49c09 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -192,6 +192,17 @@
overflow-y: auto;
overflow-x: hidden;
+ .name,
+ small.aliases,
+ small.params {
+ float: left;
+ }
+
+ small.aliases,
+ small.params {
+ padding: 2px 5px;
+ }
+
small.description {
float: right;
padding: 3px 5px;
@@ -209,6 +220,7 @@
}
ul > li {
+ @include clearfix;
white-space: nowrap;
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index f4882305c57..794bc668562 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -248,6 +248,73 @@
}
}
+.prometheus-graph-cursor {
+ position: absolute;
+ background: $theme-gray-600;
+ width: 1px;
+}
+
+.prometheus-graph-flag {
+ display: block;
+ min-width: 160px;
+
+ h5 {
+ padding: 0;
+ margin: 0;
+ font-size: 14px;
+ line-height: 1.2;
+ }
+
+ table {
+ border-collapse: collapse;
+ padding: 0;
+ margin: 0;
+ }
+
+ td {
+ vertical-align: middle;
+
+ + td {
+ padding-left: 5px;
+ vertical-align: top;
+ }
+ }
+
+ .deploy-meta-content {
+ border-bottom: 1px solid $white-dark;
+
+ svg {
+ height: 15px;
+ vertical-align: bottom;
+ }
+ }
+
+ &.popover {
+ &.left {
+ left: auto;
+ right: 0;
+ margin-right: 10px;
+ }
+
+ &.right {
+ left: 0;
+ right: auto;
+ margin-left: 10px;
+ }
+
+ > .arrow {
+ top: 40px;
+ }
+
+ > .popover-title,
+ > .popover-content {
+ padding: 5px 8px;
+ font-size: 12px;
+ white-space: nowrap;
+ }
+ }
+}
+
.prometheus-svg-container {
position: relative;
height: 0;
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 9e79852e378..6025a40348b 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -86,4 +86,8 @@ class Projects::ApplicationController < ApplicationController
def require_pages_enabled!
not_found unless @project.pages_available?
end
+
+ def check_issues_available!
+ return render_404 unless @project.feature_available?(:issues, current_user)
+ end
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index e36105ddc11..949e54ff819 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -2,6 +2,7 @@ class Projects::BoardsController < Projects::ApplicationController
include BoardsResponses
include IssuableCollections
+ before_action :check_issues_available!
before_action :authorize_read_board!, only: [:index, :show]
before_action :assign_endpoint_vars
diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb
index d3b9d8a9bbc..25608df0b9c 100644
--- a/app/controllers/projects/clusters/gcp_controller.rb
+++ b/app/controllers/projects/clusters/gcp_controller.rb
@@ -1,6 +1,7 @@
class Projects::Clusters::GcpController < Projects::ApplicationController
before_action :authorize_read_cluster!
before_action :authorize_google_api, except: [:login]
+ before_action :authorize_google_project_billing, only: [:new]
before_action :authorize_create_cluster!, only: [:new, :create]
def login
@@ -22,15 +23,20 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
end
def create
- @cluster = ::Clusters::CreateService
- .new(project, current_user, create_params)
- .execute(token_in_session)
+ case google_project_billing_status
+ when 'true'
+ @cluster = ::Clusters::CreateService
+ .new(project, current_user, create_params)
+ .execute(token_in_session)
- if @cluster.persisted?
- redirect_to project_cluster_path(project, @cluster)
+ return redirect_to project_cluster_path(project, @cluster) if @cluster.persisted?
+ when 'false'
+ flash[:error] = _('Please enable billing for one of your projects to be able to create a cluster.')
else
- render :new
+ flash[:error] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end
+
+ render :new
end
private
@@ -58,6 +64,17 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
end
end
+ def authorize_google_project_billing
+ redis_token_key = CheckGcpProjectBillingWorker.store_session_token(token_in_session)
+ CheckGcpProjectBillingWorker.perform_async(redis_token_key)
+ end
+
+ def google_project_billing_status
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session))
+ end
+ end
+
def token_in_session
@token_in_session ||=
session[GoogleApi::CloudPlatform::Client.session_key_for_token]
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index d7a3441a245..384f18b316c 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -194,10 +194,6 @@ class Projects::IssuesController < Projects::ApplicationController
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
end
- def check_issues_available!
- return render_404 unless @project.feature_available?(:issues, current_user)
- end
-
def render_issue_json
if @issue.valid?
render json: serializer.represent(@issue)
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index b029b31f9af..86717bb7242 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -11,6 +11,16 @@ module Projects
define_auto_devops_variables
end
+ def reset_cache
+ if ResetProjectCacheService.new(@project, current_user).execute
+ flash[:notice] = _("Project cache successfully reset.")
+ else
+ flash[:error] = _("Unable to reset project cache.")
+ end
+
+ redirect_to project_pipelines_path(@project)
+ end
+
private
def define_runners_variables
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 1a4f1431bdc..6cefcde558a 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -73,7 +73,6 @@ module SelectsHelper
email_user: opts[:email_user] || false,
first_user: opts[:first_user] && current_user ? current_user.username : false,
current_user: opts[:current_user] || false,
- "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
author_id: opts[:author_id] || '',
skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil
}
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 83fe23606d1..6012dbba1b9 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -79,7 +79,7 @@ module Ci
before_save :ensure_token
before_destroy { unscoped_project }
- after_create do |build|
+ after_create unless: :importing? do |build|
run_after_commit { BuildHooksWorker.perform_async(build.id) }
end
@@ -461,7 +461,14 @@ module Ci
end
def cache
- [options[:cache]]
+ cache = options[:cache]
+
+ if cache && project.jobs_cache_index
+ cache = cache.merge(
+ key: "#{cache[:key]}:#{project.jobs_cache_index}")
+ end
+
+ [cache]
end
def credentials
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 2be07ca7d3c..39d7f5b159d 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -371,7 +371,7 @@ class Commit
#
# Returns a symbol
def uri_type(path)
- entry = @raw.tree.path(path)
+ entry = @raw.rugged_tree_entry(path)
if entry[:type] == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project)
blob.image? || blob.video? ? :raw : :blob
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 8de42ff9d2e..d8bf54e0c40 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -27,7 +27,7 @@ class PagesDomain < ActiveRecord::Base
def url
return unless domain
- if certificate
+ if certificate.present?
"https://#{domain}"
else
"http://#{domain}"
diff --git a/app/models/project.rb b/app/models/project.rb
index 4cb9d9fe637..fbe65e700a4 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1450,6 +1450,7 @@ class Project < ActiveRecord::Base
import_finish
remove_import_jid
update_project_counter_caches
+ after_create_default_branch
end
def update_project_counter_caches
@@ -1463,6 +1464,27 @@ class Project < ActiveRecord::Base
end
end
+ def after_create_default_branch
+ return unless default_branch
+
+ # Ensure HEAD points to the default branch in case it is not master
+ change_head(default_branch)
+
+ if current_application_settings.default_branch_protection != Gitlab::Access::PROTECTION_NONE && !ProtectedBranch.protected?(self, default_branch)
+ params = {
+ name: default_branch,
+ push_access_levels_attributes: [{
+ access_level: current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ }],
+ merge_access_levels_attributes: [{
+ access_level: current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ }]
+ }
+
+ ProtectedBranches::CreateService.new(self, creator, params).execute(skip_authorization: true)
+ end
+ end
+
def remove_import_jid
return unless import_jid
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f599eab42f2..1dd8f0a25a9 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -241,7 +241,6 @@ class ProjectPolicy < BasePolicy
rule { repository_disabled }.policy do
prevent :push_code
- prevent :push_code_to_protected_branches
prevent :download_code
prevent :fork_project
prevent :read_commit_status
diff --git a/app/serializers/group_child_entity.rb b/app/serializers/group_child_entity.rb
index 37240bfb0b1..aca4e4ca488 100644
--- a/app/serializers/group_child_entity.rb
+++ b/app/serializers/group_child_entity.rb
@@ -1,6 +1,7 @@
class GroupChildEntity < Grape::Entity
include ActionView::Helpers::NumberHelper
include RequestAwareEntity
+ include MarkupHelper
expose :id, :name, :description, :visibility, :full_name,
:created_at, :updated_at, :avatar_url
@@ -59,6 +60,10 @@ class GroupChildEntity < Grape::Entity
number_with_delimiter(instance.member_count)
end
+ expose :markdown_description do |instance|
+ markdown_description
+ end
+
private
def membership
@@ -74,4 +79,8 @@ class GroupChildEntity < Grape::Entity
def type
object.class.name.downcase
end
+
+ def markdown_description
+ markdown_field(object, :description)
+ end
end
diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb
index 72e56a2c77f..523b522d449 100644
--- a/app/serializers/job_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -4,6 +4,8 @@ class JobEntity < Grape::Entity
expose :id
expose :name
+ expose :started?, as: :started
+
expose :build_path do |build|
build.target_url || path_to(:namespace_project_job, build)
end
diff --git a/app/services/check_gcp_project_billing_service.rb b/app/services/check_gcp_project_billing_service.rb
new file mode 100644
index 00000000000..854adf2177d
--- /dev/null
+++ b/app/services/check_gcp_project_billing_service.rb
@@ -0,0 +1,8 @@
+class CheckGcpProjectBillingService
+ def execute(token)
+ client = GoogleApi::CloudPlatform::Client.new(token, nil)
+ client.projects_list.select do |project|
+ client.projects_get_billing_info(project.name).billingEnabled
+ end
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index bb61136e33b..e6fd193ffb3 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -154,24 +154,7 @@ class GitPushService < BaseService
offset = [@push_commits_count - PROCESS_COMMIT_LIMIT, 0].max
@push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
- # Ensure HEAD points to the default branch in case it is not master
- project.change_head(branch_name)
-
- # Set protection on the default branch if configured
- if current_application_settings.default_branch_protection != PROTECTION_NONE && !ProtectedBranch.protected?(@project, @project.default_branch)
-
- params = {
- name: @project.default_branch,
- push_access_levels_attributes: [{
- access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
- }],
- merge_access_levels_attributes: [{
- access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
- }]
- }
-
- ProtectedBranches::CreateService.new(@project, current_user, params).execute
- end
+ @project.after_create_default_branch
end
def build_push_data
diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb
index a84e335340d..6212fd69077 100644
--- a/app/services/protected_branches/create_service.rb
+++ b/app/services/protected_branches/create_service.rb
@@ -2,8 +2,8 @@ module ProtectedBranches
class CreateService < BaseService
attr_reader :protected_branch
- def execute
- raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
+ def execute(skip_authorization: false)
+ raise Gitlab::Access::AccessDeniedError unless skip_authorization || can?(current_user, :admin_project, project)
project.protected_branches.create(params)
end
diff --git a/app/services/reset_project_cache_service.rb b/app/services/reset_project_cache_service.rb
new file mode 100644
index 00000000000..a162a6eedb9
--- /dev/null
+++ b/app/services/reset_project_cache_service.rb
@@ -0,0 +1,5 @@
+class ResetProjectCacheService < BaseService
+ def execute
+ @project.increment!(:jobs_cache_index)
+ end
+end
diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml
index 3701e1c0578..c18077bc66f 100644
--- a/app/views/dashboard/projects/_nav.html.haml
+++ b/app/views/dashboard/projects/_nav.html.haml
@@ -1,4 +1,4 @@
-.top-area
+.nav-block
%ul.nav-links
= nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
= link_to s_('DashboardProjects|All'), dashboard_projects_path
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 691d2528022..4e9ea33e675 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html.devise-layout-html
= render "layouts/head"
- %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page } }
+ %body.ui_indigo.login-page.application.navless{ data: { page: body_data_page } }
.page-wrap
= render "layouts/header/empty"
.login-page-broadcast
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
index ed6731bde95..8718bb3db1a 100644
--- a/app/views/layouts/devise_empty.html.haml
+++ b/app/views/layouts/devise_empty.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en" }
= render "layouts/head"
- %body.ui_charcoal.login-page.application.navless
+ %body.ui_indigo.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index be39f577ba7..1fa3a3041fd 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -299,9 +299,10 @@
Charts
-# Shortcut to Issues > New Issue
- %li.hidden
- = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
- Create a new issue
+ - if project_nav_tab?(:issues)
+ %li.hidden
+ = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
+ Create a new issue
-# Shortcut to Pipelines > Jobs
- if project_nav_tab? :builds
@@ -316,5 +317,6 @@
Commits
-# Shortcut to issue boards
- %li.hidden
- = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+ - if project_nav_tab?(:issues)
+ %li.hidden
+ = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml
index f23d5b80e4f..e2d7326a312 100644
--- a/app/views/projects/clusters/gcp/_header.html.haml
+++ b/app/views/projects/clusters/gcp/_header.html.haml
@@ -10,5 +10,5 @@
- link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements }
%li
- - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), target: '_blank', rel: 'noopener noreferrer')
+ - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project }
diff --git a/app/views/projects/clusters/gcp/login.html.haml b/app/views/projects/clusters/gcp/login.html.haml
index e97ce01893a..878ebaded88 100644
--- a/app/views/projects/clusters/gcp/login.html.haml
+++ b/app/views/projects/clusters/gcp/login.html.haml
@@ -12,6 +12,8 @@
- if @authorize_url
= link_to @authorize_url do
= image_tag('auth_buttons/signin_with_google.png', width: '191px')
+ = _('or')
+ = link_to('create a new Google account', 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral', target: '_blank', rel: 'noopener noreferrer')
- else
- link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer')
= s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link }
diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml
new file mode 100644
index 00000000000..c66313bdbf3
--- /dev/null
+++ b/app/views/projects/jobs/_empty_state.html.haml
@@ -0,0 +1,17 @@
+- illustration = local_assigns.fetch(:illustration)
+- illustration_size = local_assigns.fetch(:illustration_size)
+- title = local_assigns.fetch(:title)
+- content = local_assigns.fetch(:content)
+- action = local_assigns.fetch(:action, nil)
+
+.row.empty-state
+ .col-xs-12
+ .svg-content{ class: illustration_size }
+ = image_tag illustration
+ .col-xs-12
+ .text-content
+ %h4.text-center= title
+ %p= content
+ - if action
+ .text-center
+ = action
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index fd24bbbb9ba..8b05440fc78 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -54,41 +54,53 @@
Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
- else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
+ - if @build.started?
+ .build-trace-container.prepend-top-default
+ .top-bar.js-top-bar
+ .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden<
+ Showing last
+ %span.js-truncated-info-size.truncated-info-size><
+ of log -
+ %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
- .build-trace-container.prepend-top-default
- .top-bar.js-top-bar
- .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden<
- Showing last
- %span.js-truncated-info-size.truncated-info-size><
- of log -
- %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
+ .controllers.pull-right
+ - if @build.has_trace?
+ = link_to raw_project_job_path(@project, @build),
+ title: 'Show complete raw',
+ data: { placement: 'top', container: 'body' },
+ class: 'js-raw-link-controller has-tooltip controllers-buttons' do
+ = icon('file-text-o')
- .controllers.pull-right
- - if @build.has_trace?
- = link_to raw_project_job_path(@project, @build),
- title: 'Show complete raw',
- data: { placement: 'top', container: 'body' },
- class: 'js-raw-link-controller has-tooltip controllers-buttons' do
- = icon('file-text-o')
-
- - if @build.erasable? && can?(current_user, :erase_build, @build)
- = link_to erase_project_job_path(@project, @build),
- method: :post,
- data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
- title: 'Erase job log',
- class: 'has-tooltip js-erase-link controllers-buttons' do
- = icon('trash')
- .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} }
- %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
- = custom_icon('scroll_up')
- .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
- %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
- = custom_icon('scroll_down')
-
- %pre.build-trace#build-trace
- %code.bash.js-build-output
- .build-loader-animation.js-build-refresh
+ - if @build.erasable? && can?(current_user, :erase_build, @build)
+ = link_to erase_project_job_path(@project, @build),
+ method: :post,
+ data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
+ title: 'Erase job log',
+ class: 'has-tooltip js-erase-link controllers-buttons' do
+ = icon('trash')
+ .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} }
+ %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
+ = custom_icon('scroll_up')
+ .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
+ %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
+ = custom_icon('scroll_down')
+ %pre.build-trace#build-trace
+ %code.bash.js-build-output
+ .build-loader-animation.js-build-refresh
+ - elsif @build.playable?
+ = render 'empty_state',
+ illustration: 'illustrations/manual_action.svg',
+ illustration_size: 'svg-394',
+ title: _('This job requires a manual action'),
+ content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.'),
+ action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), class: 'btn btn-primary', title: _('Trigger this manual action') )
+ - else
+ = render 'empty_state',
+ illustration: 'illustrations/job_not_triggered.svg',
+ illustration_size: 'svg-306',
+ title: _('This job has not been triggered yet'),
+ content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered.')
= render "sidebar"
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index b2e71cff6ce..f8555f11aab 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -10,7 +10,8 @@
"new-pipeline-path" => new_project_pipeline_path(@project),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"has-ci" => @repository.gitlab_ci_yml,
- "ci-lint-path" => ci_lint_path } }
+ "ci-lint-path" => ci_lint_path,
+ "reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } }
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pipelines')
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index fafd9e5ef00..50e876b1d19 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -22,6 +22,7 @@
- gcp_cluster:cluster_provision
- gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation
+- gcp_cluster:check_gcp_project_billing
- github_import_advance_stage
- github_importer:github_import_import_diff_note
diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb
index aeb3bc019b9..376703f6319 100644
--- a/app/workers/background_migration_worker.rb
+++ b/app/workers/background_migration_worker.rb
@@ -1,10 +1,53 @@
class BackgroundMigrationWorker
include ApplicationWorker
+ # The minimum amount of time between processing two jobs of the same migration
+ # class.
+ #
+ # This interval is set to 5 minutes so autovacuuming and other maintenance
+ # related tasks have plenty of time to clean up after a migration has been
+ # performed.
+ MIN_INTERVAL = 5.minutes.to_i
+
# Performs the background migration.
#
# See Gitlab::BackgroundMigration.perform for more information.
+ #
+ # class_name - The class name of the background migration to run.
+ # arguments - The arguments to pass to the migration class.
def perform(class_name, arguments = [])
- Gitlab::BackgroundMigration.perform(class_name, arguments)
+ should_perform, ttl = perform_and_ttl(class_name)
+
+ if should_perform
+ Gitlab::BackgroundMigration.perform(class_name, arguments)
+ else
+ # If the lease could not be obtained this means either another process is
+ # running a migration of this class or we ran one recently. In this case
+ # we'll reschedule the job in such a way that it is picked up again around
+ # the time the lease expires.
+ self.class.perform_in(ttl || MIN_INTERVAL, class_name, arguments)
+ end
+ end
+
+ def perform_and_ttl(class_name)
+ if always_perform?
+ # In test environments `perform_in` will run right away. This can then
+ # lead to stack level errors in the above `#perform`. To work around this
+ # we'll just perform the migration right away in the test environment.
+ [true, nil]
+ else
+ lease = lease_for(class_name)
+
+ [lease.try_obtain, lease.ttl]
+ end
+ end
+
+ def lease_for(class_name)
+ Gitlab::ExclusiveLease
+ .new("#{self.class.name}:#{class_name}", timeout: MIN_INTERVAL)
+ end
+
+ def always_perform?
+ Rails.env.test?
end
end
diff --git a/app/workers/check_gcp_project_billing_worker.rb b/app/workers/check_gcp_project_billing_worker.rb
new file mode 100644
index 00000000000..557af14ee57
--- /dev/null
+++ b/app/workers/check_gcp_project_billing_worker.rb
@@ -0,0 +1,59 @@
+require 'securerandom'
+
+class CheckGcpProjectBillingWorker
+ include ApplicationWorker
+ include ClusterQueue
+
+ LEASE_TIMEOUT = 15.seconds.to_i
+ SESSION_KEY_TIMEOUT = 5.minutes
+ BILLING_TIMEOUT = 1.hour
+
+ def self.get_session_token(token_key)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.get(get_redis_session_key(token_key))
+ end
+ end
+
+ def self.store_session_token(token)
+ generate_token_key.tap do |token_key|
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(get_redis_session_key(token_key), token, ex: SESSION_KEY_TIMEOUT)
+ end
+ end
+ end
+
+ def self.redis_shared_state_key_for(token)
+ "gitlab:gcp:#{token.hash}:billing_enabled"
+ end
+
+ def perform(token_key)
+ return unless token_key
+
+ token = self.get_session_token(token_key)
+ return unless token
+ return unless try_obtain_lease_for(token)
+
+ billing_enabled_projects = CheckGcpProjectBillingService.new.execute(token)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(self.class.redis_shared_state_key_for(token),
+ !billing_enabled_projects.empty?,
+ ex: BILLING_TIMEOUT)
+ end
+ end
+
+ private
+
+ def self.generate_token_key
+ SecureRandom.uuid
+ end
+
+ def self.get_redis_session_key(token_key)
+ "gitlab:gcp:session:#{token_key}"
+ end
+
+ def try_obtain_lease_for(token)
+ Gitlab::ExclusiveLease
+ .new("check_gcp_project_billing_worker:#{token.hash}", timeout: LEASE_TIMEOUT)
+ .try_obtain
+ end
+end
diff --git a/changelogs/unreleased/28260-fix-pages-custom-domain-url.yml b/changelogs/unreleased/28260-fix-pages-custom-domain-url.yml
new file mode 100644
index 00000000000..edd63c4ea9c
--- /dev/null
+++ b/changelogs/unreleased/28260-fix-pages-custom-domain-url.yml
@@ -0,0 +1,5 @@
+---
+title: Generate HTTP URLs for custom Pages domains when appropriate
+merge_request: 16279
+author:
+type: fixed
diff --git a/changelogs/unreleased/38030-add-graph-value-to-hover.yml b/changelogs/unreleased/38030-add-graph-value-to-hover.yml
new file mode 100644
index 00000000000..233db2b19c9
--- /dev/null
+++ b/changelogs/unreleased/38030-add-graph-value-to-hover.yml
@@ -0,0 +1,5 @@
+---
+title: Display graph values on hover within monitoring page
+merge_request: 16261
+author:
+type: changed
diff --git a/changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml b/changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml
new file mode 100644
index 00000000000..e972ac6d54a
--- /dev/null
+++ b/changelogs/unreleased/3968-protected-branch-is-not-set-for-default-branch-on-import.yml
@@ -0,0 +1,5 @@
+---
+title: Protected branch is now created for default branch on import
+merge_request: 16198
+author:
+type: fixed
diff --git a/changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml b/changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml
new file mode 100644
index 00000000000..d8fd1f14bd4
--- /dev/null
+++ b/changelogs/unreleased/39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled.yml
@@ -0,0 +1,5 @@
+---
+title: Implement checking GCP project billing status in cluster creation form.
+merge_request: 15665
+author:
+type: changed
diff --git a/changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml b/changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml
new file mode 100644
index 00000000000..71a606ff607
--- /dev/null
+++ b/changelogs/unreleased/40190-fix-slash-commands-dropdown-description-mis-alignement-on-firefox.yml
@@ -0,0 +1,5 @@
+---
+title: "Fix slash commands dropdown description mis-alignment on Firefox"
+merge_request: 16125
+author: Maurizio De Santis
+type: fixed
diff --git a/changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml b/changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml
new file mode 100644
index 00000000000..5e158d831a6
--- /dev/null
+++ b/changelogs/unreleased/40418-migrate-existing-data-from-kubernetesservice-to-clusters-platforms-kubernetes.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate existing data from KubernetesService to Clusters::Platforms::Kubernetes
+merge_request: 15589
+author:
+type: changed
diff --git a/changelogs/unreleased/40549-render-emoj-in-groups-overview.yml b/changelogs/unreleased/40549-render-emoj-in-groups-overview.yml
new file mode 100644
index 00000000000..9b2f58df440
--- /dev/null
+++ b/changelogs/unreleased/40549-render-emoj-in-groups-overview.yml
@@ -0,0 +1,5 @@
+---
+title: Rendering of emoji's in Group-Overview
+merge_request: 16098
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml b/changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml
new file mode 100644
index 00000000000..b2c3a86551b
--- /dev/null
+++ b/changelogs/unreleased/41244-issue-board-shortcut-working-while-no-issues.yml
@@ -0,0 +1,5 @@
+---
+title: disables shortcut to issue boards when issues are not enabled
+merge_request: 16020
+author: Christiaan Van den Poel
+type: fixed
diff --git a/changelogs/unreleased/41249-clearing-the-cache.yml b/changelogs/unreleased/41249-clearing-the-cache.yml
new file mode 100644
index 00000000000..221589a1239
--- /dev/null
+++ b/changelogs/unreleased/41249-clearing-the-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Implement project jobs cache reset
+merge_request: 16067
+author:
+type: added
diff --git a/changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml b/changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml
new file mode 100644
index 00000000000..593d3741a09
--- /dev/null
+++ b/changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml
@@ -0,0 +1,5 @@
+---
+title: Substitute deprecated ui_charcoal with new default ui_indigo
+merge_request: 16271
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml b/changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml
new file mode 100644
index 00000000000..b96dd376cec
--- /dev/null
+++ b/changelogs/unreleased/41754-update-scss-lint-to-0-56-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update scss-lint to 0.56.0
+merge_request: 16278
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/delay-background-migrations.yml b/changelogs/unreleased/delay-background-migrations.yml
new file mode 100644
index 00000000000..aa12591e7d2
--- /dev/null
+++ b/changelogs/unreleased/delay-background-migrations.yml
@@ -0,0 +1,5 @@
+---
+title: Run background migrations with a minimum interval
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml b/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml
new file mode 100644
index 00000000000..2f6a07bb234
--- /dev/null
+++ b/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml
@@ -0,0 +1,5 @@
+---
+title: Fix dashboard projects nav links height
+merge_request: 16204
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml b/changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml
new file mode 100644
index 00000000000..58df0024d61
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-import-export-restoring-associations.yml
@@ -0,0 +1,6 @@
+---
+title: Fix missing references to pipeline objects when restoring project with import/export
+ feature
+merge_request: 16221
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-store-user-in-api-logs.yml b/changelogs/unreleased/sh-store-user-in-api-logs.yml
new file mode 100644
index 00000000000..d904dcaf6d3
--- /dev/null
+++ b/changelogs/unreleased/sh-store-user-in-api-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Save user ID and username in Grape API log (api_json.log)
+merge_request:
+author:
+type: changed
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 1354c4c5537..bdf4b199c0a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -408,7 +408,9 @@ constraints(ProjectUrlConstrainer.new) do
end
namespace :settings do
get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
- resource :ci_cd, only: [:show], controller: 'ci_cd'
+ resource :ci_cd, only: [:show], controller: 'ci_cd' do
+ post :reset_cache
+ end
resource :integrations, only: [:show]
resource :repository, only: [:show], controller: :repository
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 5f95255334c..95fa79990e2 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -1,5 +1,6 @@
'use strict';
+var crypto = require('crypto');
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
@@ -179,15 +180,34 @@ var config = {
if (chunk.name) {
return chunk.name;
}
- return chunk.mapModules((m) => {
+
+ const moduleNames = [];
+
+ function collectModuleNames(m) {
+ // handle ConcatenatedModule which does not have resource nor context set
+ if (m.modules) {
+ m.modules.forEach(collectModuleNames);
+ return;
+ }
+
const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
+
if (m.resource.indexOf(pagesBase) === 0) {
- return path.relative(pagesBase, m.resource)
+ moduleNames.push(path.relative(pagesBase, m.resource)
.replace(/\/index\.[a-z]+$/, '')
- .replace(/\//g, '__');
+ .replace(/\//g, '__'));
+ } else {
+ moduleNames.push(path.relative(m.context, m.resource));
}
- return path.relative(m.context, m.resource);
- }).join('_');
+ }
+
+ chunk.forEachModule(collectModuleNames);
+
+ const hash = crypto.createHash('sha256')
+ .update(moduleNames.join('_'))
+ .digest('hex');
+
+ return `${moduleNames[0]}-${hash.substr(0, 6)}`;
}),
// create cacheable common library bundle for all vue chunks
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 1f8f5cfc82b..213c8bca639 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -63,7 +63,8 @@ Sidekiq::Testing.inline! do
namespace_id: group.id,
name: project_path.titleize,
description: FFaker::Lorem.sentence,
- visibility_level: Gitlab::VisibilityLevel.values.sample
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ skip_disk_validation: true
}
project = Projects::CreateService.new(User.first, params).execute
diff --git a/db/migrate/20160621123729_add_rebase_commit_sha_to_merge_requests.rb b/db/migrate/20160621123729_add_rebase_commit_sha_to_merge_requests.rb
new file mode 100644
index 00000000000..1222dc640a8
--- /dev/null
+++ b/db/migrate/20160621123729_add_rebase_commit_sha_to_merge_requests.rb
@@ -0,0 +1,22 @@
+# This migration is a duplicate of 20171230123729_add_rebase_commit_sha_to_merge_requests_ce.rb
+#
+# We backported this feature from EE using the same migration, but with a new
+# timestamp, which caused an error when the backport was then to be merged back
+# into EE.
+#
+# See discussion at https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3932
+class AddRebaseCommitShaToMergeRequests < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ unless column_exists?(:merge_requests, :rebase_commit_sha)
+ add_column :merge_requests, :rebase_commit_sha, :string
+ end
+ end
+
+ def down
+ if column_exists?(:merge_requests, :rebase_commit_sha)
+ remove_column :merge_requests, :rebase_commit_sha
+ end
+ end
+end
diff --git a/db/migrate/20171222183504_add_jobs_cache_index_to_project.rb b/db/migrate/20171222183504_add_jobs_cache_index_to_project.rb
new file mode 100644
index 00000000000..607e9d027d7
--- /dev/null
+++ b/db/migrate/20171222183504_add_jobs_cache_index_to_project.rb
@@ -0,0 +1,13 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddJobsCacheIndexToProject < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :jobs_cache_index, :integer
+ end
+end
diff --git a/db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests.rb b/db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests.rb
deleted file mode 100644
index 2ce156fa92e..00000000000
--- a/db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class AddRebaseCommitShaToMergeRequests < ActiveRecord::Migration
- DOWNTIME = false
-
- def change
- add_column :merge_requests, :rebase_commit_sha, :string
- end
-end
diff --git a/db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests_ce.rb b/db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests_ce.rb
new file mode 100644
index 00000000000..94a7c1019d8
--- /dev/null
+++ b/db/migrate/20171230123729_add_rebase_commit_sha_to_merge_requests_ce.rb
@@ -0,0 +1,15 @@
+class AddRebaseCommitShaToMergeRequestsCe < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ unless column_exists?(:merge_requests, :rebase_commit_sha)
+ add_column :merge_requests, :rebase_commit_sha, :string
+ end
+ end
+
+ def down
+ if column_exists?(:merge_requests, :rebase_commit_sha)
+ remove_column :merge_requests, :rebase_commit_sha
+ end
+ end
+end
diff --git a/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb b/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb
new file mode 100644
index 00000000000..11b581e4b57
--- /dev/null
+++ b/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb
@@ -0,0 +1,151 @@
+class MigrateKubernetesServiceToNewClustersArchitectures < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DEFAULT_KUBERNETES_SERVICE_CLUSTER_NAME = 'KubernetesService'.freeze
+
+ disable_ddl_transaction!
+
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+
+ has_many :cluster_projects, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::ClustersProject'
+ has_many :clusters, through: :cluster_projects, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Cluster'
+ has_many :services, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Service'
+ has_one :kubernetes_service, -> { where(category: 'deployment', type: 'KubernetesService') }, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Service', inverse_of: :project, foreign_key: :project_id
+ end
+
+ class Cluster < ActiveRecord::Base
+ self.table_name = 'clusters'
+
+ has_many :cluster_projects, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::ClustersProject'
+ has_many :projects, through: :cluster_projects, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Project'
+ has_one :platform_kubernetes, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::PlatformsKubernetes'
+
+ accepts_nested_attributes_for :platform_kubernetes
+
+ enum platform_type: {
+ kubernetes: 1
+ }
+
+ enum provider_type: {
+ user: 0,
+ gcp: 1
+ }
+ end
+
+ class ClustersProject < ActiveRecord::Base
+ self.table_name = 'cluster_projects'
+
+ belongs_to :cluster, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Cluster'
+ belongs_to :project, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Project'
+ end
+
+ class PlatformsKubernetes < ActiveRecord::Base
+ self.table_name = 'cluster_platforms_kubernetes'
+
+ belongs_to :cluster, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Cluster'
+
+ attr_encrypted :token,
+ mode: :per_attribute_iv,
+ key: Gitlab::Application.secrets.db_key_base,
+ algorithm: 'aes-256-cbc'
+ end
+
+ class Service < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'services'
+ self.inheritance_column = :_type_disabled # Disable STI, otherwise KubernetesModel will be looked up
+
+ belongs_to :project, class_name: 'MigrateKubernetesServiceToNewClustersArchitectures::Project', foreign_key: :project_id
+
+ scope :unmanaged_kubernetes_service, -> do
+ joins('LEFT JOIN projects ON projects.id = services.project_id')
+ .joins('LEFT JOIN cluster_projects ON cluster_projects.project_id = projects.id')
+ .joins('LEFT JOIN cluster_platforms_kubernetes ON cluster_platforms_kubernetes.cluster_id = cluster_projects.cluster_id')
+ .where(category: 'deployment', type: 'KubernetesService', template: false)
+ .where("services.properties LIKE '%api_url%'")
+ .where("(services.properties NOT LIKE CONCAT('%', cluster_platforms_kubernetes.api_url, '%')) OR cluster_platforms_kubernetes.api_url IS NULL")
+ .group(:id)
+ .order(id: :asc)
+ end
+
+ scope :kubernetes_service_without_template, -> do
+ where(category: 'deployment', type: 'KubernetesService', template: false)
+ end
+
+ def api_url
+ parsed_properties['api_url']
+ end
+
+ def ca_pem
+ parsed_properties['ca_pem']
+ end
+
+ def namespace
+ parsed_properties['namespace']
+ end
+
+ def token
+ parsed_properties['token']
+ end
+
+ private
+
+ def parsed_properties
+ @parsed_properties ||= JSON.parse(self.properties)
+ end
+ end
+
+ def find_dedicated_environement_scope(project)
+ environment_scopes = project.clusters.map(&:environment_scope)
+
+ return '*' if environment_scopes.exclude?('*') # KubernetesService should be added as a default cluster (environment_scope: '*') at first place
+ return 'migrated/*' if environment_scopes.exclude?('migrated/*') # If it's conflicted, the KubernetesService added as a migrated cluster
+
+ unique_iid = 0
+
+ # If it's still conflicted, finding an unique environment scope incrementaly
+ loop do
+ candidate = "migrated#{unique_iid}/*"
+ return candidate if environment_scopes.exclude?(candidate)
+
+ unique_iid += 1
+ end
+ end
+
+ def up
+ ActiveRecord::Base.transaction do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service
+ .unmanaged_kubernetes_service.find_each(batch_size: 1) do |kubernetes_service|
+ MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create(
+ enabled: kubernetes_service.active,
+ user_id: nil, # KubernetesService doesn't have
+ name: DEFAULT_KUBERNETES_SERVICE_CLUSTER_NAME,
+ provider_type: MigrateKubernetesServiceToNewClustersArchitectures::Cluster.provider_types[:user],
+ platform_type: MigrateKubernetesServiceToNewClustersArchitectures::Cluster.platform_types[:kubernetes],
+ projects: [kubernetes_service.project],
+ environment_scope: find_dedicated_environement_scope(kubernetes_service.project),
+ platform_kubernetes_attributes: {
+ api_url: kubernetes_service.api_url,
+ ca_cert: kubernetes_service.ca_pem,
+ namespace: kubernetes_service.namespace,
+ username: nil, # KubernetesService doesn't have
+ encrypted_password: nil, # KubernetesService doesn't have
+ encrypted_password_iv: nil, # KubernetesService doesn't have
+ token: kubernetes_service.token # encrypted_token and encrypted_token_iv
+ } )
+ end
+ end
+
+ MigrateKubernetesServiceToNewClustersArchitectures::Service
+ .kubernetes_service_without_template.each_batch(of: 100) do |kubernetes_service|
+ kubernetes_service.update_all(active: false)
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
index 547cc68e10e..fce1829c982 100644
--- a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
+++ b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
@@ -15,8 +15,6 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio
end
def up
- merge_requests = MergeRequest.where("id IN (#{updatable_merge_requests_union_sql})").reorder(:id)
-
say 'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs'
# It will update around 4_000_000 records in batches of 10_000 merge
# requests (running between 10 minutes) and should take around 66 hours to complete.
@@ -25,7 +23,7 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio
#
# More information about the updates in `PopulateMergeRequestMetricsWithEventsData` class.
#
- merge_requests.each_batch(of: BATCH_SIZE) do |relation, index|
+ MergeRequest.all.each_batch(of: BATCH_SIZE) do |relation, index|
range = relation.pluck('MIN(id)', 'MAX(id)').first
BackgroundMigrationWorker.perform_in(index * 10.minutes, MIGRATION, range)
@@ -37,32 +35,4 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio
execute "update merge_request_metrics set latest_closed_by_id = null"
execute "update merge_request_metrics set merged_by_id = null"
end
-
- private
-
- # On staging:
- # Planning time: 0.682 ms
- # Execution time: 22033.158 ms
- #
- def updatable_merge_requests_union_sql
- metrics_not_exists_clause =
- 'NOT EXISTS (SELECT 1 FROM merge_request_metrics WHERE merge_request_metrics.merge_request_id = merge_requests.id)'
-
- without_metrics_data = <<-SQL.strip_heredoc
- merge_request_metrics.merged_by_id IS NULL OR
- merge_request_metrics.latest_closed_by_id IS NULL OR
- merge_request_metrics.latest_closed_at IS NULL
- SQL
-
- mrs_without_metrics_record = MergeRequest
- .where(metrics_not_exists_clause)
- .select(:id)
-
- mrs_without_events_data = MergeRequest
- .joins('INNER JOIN merge_request_metrics ON merge_requests.id = merge_request_metrics.merge_request_id')
- .where(without_metrics_data)
- .select(:id)
-
- Gitlab::SQL::Union.new([mrs_without_metrics_record, mrs_without_events_data]).to_sql
- end
end
diff --git a/db/schema.rb b/db/schema.rb
index 740e80ccfd4..e6a2ea4c862 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1451,6 +1451,7 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.boolean "repository_read_only"
t.boolean "merge_requests_ff_only_enabled", default: false
t.boolean "merge_requests_rebase_enabled", default: false, null: false
+ t.integer "jobs_cache_index"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md
index 77b308c4a43..86a8b4135af 100644
--- a/doc/development/fe_guide/style_guide_scss.md
+++ b/doc/development/fe_guide/style_guide_scss.md
@@ -216,7 +216,7 @@ If you want a line or set of lines to be ignored by the linter, you can use
```scss
// This lint rule is disabled because the class name comes from a gem.
// scss-lint:disable SelectorFormat
-.ui_charcoal {
+.ui_indigo {
background-color: #333;
}
// scss-lint:enable SelectorFormat
diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md
index 43a79ffcaa5..133ac0234cf 100644
--- a/doc/development/writing_documentation.md
+++ b/doc/development/writing_documentation.md
@@ -15,7 +15,7 @@ request introducing these changes must be accompanied by the documentation
(either updating existing ones or creating new ones). This is also valid when
changes are introduced to the UI.
-The one resposible for writing the first piece of documentation is the developer who
+The one responsible for writing the first piece of documentation is the developer who
wrote the code. It's the job of the Product Manager to ensure all features are
shipped with its docs, whether is a small or big change. At the pace GitLab evolves,
this is the only way to keep the docs up-to-date. If you have any questions about it,
@@ -31,7 +31,7 @@ Every major feature (regardless if present in GitLab Community or Enterprise edi
should present, at the beginning of the document, two main sections: **overview** and
**use cases**. Every GitLab EE-only feature should also contain these sections.
-**Overview**: at the name suggests, the goal here is to provide an overview of the feature.
+**Overview**: as the name suggests, the goal here is to provide an overview of the feature.
Describe what is it, what it does, why it is important/cool/nice-to-have,
what problem it solves, and what you can do with this feature that you couldn't
do before.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 67b89d608cc..2b7352d3561 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -299,9 +299,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-3-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-4-stable gitlab
-**Note:** You can change `10-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `10-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 1b8a84c9599..b8f865679a2 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -30,7 +30,8 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version |
| ---------------- | --------------------- |
-| 10.3 to current | 0.2.1 |
+| 10.4 to current | 0.2.2 |
+| 10.3 | 0.2.1 |
| 10.0 | 0.2.0 |
| 9.4.0 | 0.1.8 |
| 9.2.0 | 0.1.7 |
diff --git a/features/explore/groups.feature b/features/explore/groups.feature
deleted file mode 100644
index 830810615e0..00000000000
--- a/features/explore/groups.feature
+++ /dev/null
@@ -1,105 +0,0 @@
-@public
-Feature: Explore Groups
- Background:
- Given group "TestGroup" has private project "Enterprise"
-
- @javascript
- Scenario: I should see group with private and internal projects as user
- Given group "TestGroup" has internal project "Internal"
- When I sign in as a user
- And I visit group "TestGroup" page
- Then I should see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group issues for internal project as user
- Given group "TestGroup" has internal project "Internal"
- When I sign in as a user
- And I visit group "TestGroup" issues page
- Then I should see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group merge requests for internal project as user
- Given group "TestGroup" has internal project "Internal"
- When I sign in as a user
- And I visit group "TestGroup" merge requests page
- Then I should see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group with private, internal and public projects as visitor
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- When I visit group "TestGroup" page
- Then I should see project "Community" items
- And I should not see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group issues for public project as visitor
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- When I visit group "TestGroup" issues page
- Then I should see project "Community" items
- And I should not see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group merge requests for public project as visitor
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- When I visit group "TestGroup" merge requests page
- Then I should see project "Community" items
- And I should not see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group with private, internal and public projects as user
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- When I sign in as a user
- And I visit group "TestGroup" page
- Then I should see project "Community" items
- And I should see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group issues for internal and public projects as user
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- When I sign in as a user
- And I visit group "TestGroup" issues page
- Then I should see project "Community" items
- And I should see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group merge requests for internal and public projects as user
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- When I sign in as a user
- And I visit group "TestGroup" merge requests page
- Then I should see project "Community" items
- And I should see project "Internal" items
- And I should not see project "Enterprise" items
-
- @javascript
- Scenario: I should see group with public project in public groups area
- Given group "TestGroup" has public project "Community"
- When I visit the public groups area
- Then I should see group "TestGroup"
-
- @javascript
- Scenario: I should see group with public project in public groups area as user
- Given group "TestGroup" has public project "Community"
- When I sign in as a user
- And I visit the public groups area
- Then I should see group "TestGroup"
-
- @javascript
- Scenario: I should see group with internal project in public groups area as user
- Given group "TestGroup" has internal project "Internal"
- When I sign in as a user
- And I visit the public groups area
- Then I should see group "TestGroup"
diff --git a/features/invites.feature b/features/invites.feature
deleted file mode 100644
index dc8eefaeaed..00000000000
--- a/features/invites.feature
+++ /dev/null
@@ -1,45 +0,0 @@
-Feature: Invites
- Background:
- Given "John Doe" is owner of group "Owned"
- And "John Doe" has invited "user@example.com" to group "Owned"
-
- Scenario: Viewing invitation when signed out
- When I visit the invitation page
- Then I should be redirected to the sign in page
- And I should see a notice telling me to sign in
-
- Scenario: Signing in to view invitation
- When I visit the invitation page
- And I sign in as "Mary Jane"
- Then I should be redirected to the invitation page
-
- Scenario: Viewing invitation when signed in
- Given I sign in as "Mary Jane"
- And I visit the invitation page
- Then I should see the invitation details
- And I should see an "Accept invitation" button
- And I should see a "Decline" button
-
- Scenario: Viewing invitation as an existing member
- Given I sign in as "John Doe"
- And I visit the invitation page
- Then I should see a message telling me I'm already a member
-
- Scenario: Accepting the invitation
- Given I sign in as "Mary Jane"
- And I visit the invitation page
- And I click the "Accept invitation" button
- Then I should be redirected to the group page
- And I should see a notice telling me I have access
-
- Scenario: Declining the application when signed in
- Given I sign in as "Mary Jane"
- And I visit the invitation page
- And I click the "Decline" button
- Then I should be redirected to the dashboard
- And I should see a notice telling me I have declined
-
- Scenario: Declining the application when signed out
- When I visit the invitation's decline page
- Then I should be redirected to the sign in page
- And I should see a notice telling me I have declined
diff --git a/features/steps/explore/groups.rb b/features/steps/explore/groups.rb
deleted file mode 100644
index 409bf0cb416..00000000000
--- a/features/steps/explore/groups.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedGroup
- include SharedProject
-
- step 'group "TestGroup" has private project "Enterprise"' do
- group_has_project("TestGroup", "Enterprise", Gitlab::VisibilityLevel::PRIVATE)
- end
-
- step 'group "TestGroup" has internal project "Internal"' do
- group_has_project("TestGroup", "Internal", Gitlab::VisibilityLevel::INTERNAL)
- end
-
- step 'group "TestGroup" has public project "Community"' do
- group_has_project("TestGroup", "Community", Gitlab::VisibilityLevel::PUBLIC)
- end
-
- step '"John Doe" is owner of group "TestGroup"' do
- group = Group.find_by(name: "TestGroup") || create(:group, name: "TestGroup")
- user = create(:user, name: "John Doe")
- group.add_owner(user)
- end
-
- step 'I visit group "TestGroup" page' do
- visit group_path(Group.find_by(name: "TestGroup"))
- end
-
- step 'I visit group "TestGroup" issues page' do
- visit issues_group_path(Group.find_by(name: "TestGroup"))
- end
-
- step 'I visit group "TestGroup" merge requests page' do
- visit merge_requests_group_path(Group.find_by(name: "TestGroup"))
- end
-
- step 'I visit group "TestGroup" members page' do
- visit group_group_members_path(Group.find_by(name: "TestGroup"))
- end
-
- step 'I should not see project "Enterprise" items' do
- expect(page).not_to have_content "Enterprise"
- end
-
- step 'I should see project "Internal" items' do
- expect(page).to have_content "Internal"
- end
-
- step 'I should not see project "Internal" items' do
- expect(page).not_to have_content "Internal"
- end
-
- step 'I should see project "Community" items' do
- expect(page).to have_content "Community"
- end
-
- step 'I change filter to Everyone\'s' do
- click_link "Everyone's"
- end
-
- step 'I should see group member "John Doe"' do
- expect(page).to have_content "John Doe"
- end
-
- protected
-
- def group_has_project(groupname, projectname, visibility_level)
- group = Group.find_by(name: groupname) || create(:group, name: groupname)
- project = create(:project,
- namespace: group,
- name: projectname,
- path: "#{groupname}-#{projectname}",
- visibility_level: visibility_level
- )
- create(:issue,
- title: "#{projectname} feature",
- project: project
- )
- create(:merge_request,
- title: "#{projectname} feature implemented",
- source_project: project,
- target_project: project
- )
- create(:closed_issue_event,
- project: project
- )
- end
-end
diff --git a/features/steps/invites.rb b/features/steps/invites.rb
deleted file mode 100644
index dac972172aa..00000000000
--- a/features/steps/invites.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-class Spinach::Features::Invites < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedUser
- include SharedGroup
-
- step '"John Doe" has invited "user@example.com" to group "Owned"' do
- user = User.find_by(name: "John Doe")
- group = Group.find_by(name: "Owned")
- group.add_developer("user@example.com", user)
- end
-
- step 'I visit the invitation page' do
- group = Group.find_by(name: "Owned")
- invite = group.group_members.invite.last
- invite.generate_invite_token!
- @raw_invite_token = invite.raw_invite_token
- visit invite_path(@raw_invite_token)
- end
-
- step 'I should be redirected to the sign in page' do
- expect(current_path).to eq(new_user_session_path)
- end
-
- step 'I should see a notice telling me to sign in' do
- expect(page).to have_content "To accept this invitation, sign in"
- end
-
- step 'I should be redirected to the invitation page' do
- expect(current_path).to eq(invite_path(@raw_invite_token))
- end
-
- step 'I should see the invitation details' do
- expect(page).to have_content("You have been invited by John Doe to join group Owned as Developer.")
- end
-
- step "I should see a message telling me I'm already a member" do
- expect(page).to have_content("However, you are already a member of this group.")
- end
-
- step 'I should see an "Accept invitation" button' do
- expect(page).to have_link("Accept invitation")
- end
-
- step 'I should see a "Decline" button' do
- expect(page).to have_link("Decline")
- end
-
- step 'I click the "Accept invitation" button' do
- page.click_link "Accept invitation"
- end
-
- step 'I should be redirected to the group page' do
- group = Group.find_by(name: "Owned")
- expect(current_path).to eq(group_path(group))
- end
-
- step 'I should see a notice telling me I have access' do
- expect(page).to have_content("You have been granted Developer access to group Owned.")
- end
-
- step 'I click the "Decline" button' do
- page.click_link "Decline"
- end
-
- step 'I should be redirected to the dashboard' do
- expect(current_path).to eq(dashboard_projects_path)
- end
-
- step 'I should see a notice telling me I have declined' do
- expect(page).to have_content("You have declined the invitation to join group Owned.")
- end
-
- step "I visit the invitation's decline page" do
- group = Group.find_by(name: "Owned")
- invite = group.group_members.invite.last
- invite.generate_invite_token!
- @raw_invite_token = invite.raw_invite_token
- visit decline_invite_path(@raw_invite_token)
- end
-end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index c267195f0e8..a3e4459f169 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -11,7 +11,7 @@ module SharedBuilds
step 'project has a recent build' do
@pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
- @build = create(:ci_build, :coverage, pipeline: @pipeline)
+ @build = create(:ci_build, :running, :coverage, pipeline: @pipeline)
end
step 'recent build is successful' do
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e0d14281c96..ae161efb358 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -13,7 +13,8 @@ module API
formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
include: [
GrapeLogging::Loggers::FilterParameters.new,
- GrapeLogging::Loggers::ClientEnv.new
+ GrapeLogging::Loggers::ClientEnv.new,
+ Gitlab::GrapeLogging::Loggers::UserLogger.new
]
allow_access_with_scope :api
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index bf388163ec8..d6ce368efd5 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -5,6 +5,7 @@ module API
SUDO_HEADER = "HTTP_SUDO".freeze
SUDO_PARAM = :sudo
+ API_USER_ENV = 'gitlab.api.user'.freeze
def declared_params(options = {})
options = { include_parent_namespaces: false }.merge(options)
@@ -48,10 +49,16 @@ module API
validate_access_token!(scopes: scopes_registered_for_endpoint) unless sudo?
+ save_current_user_in_env(@current_user) if @current_user
+
@current_user
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ def save_current_user_in_env(user)
+ env[API_USER_ENV] = { user_id: user.id, username: user.username }
+ end
+
def sudo?
initial_current_user != current_user
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 33171f83692..7b35c24d153 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -842,6 +842,12 @@ into similar problems in the future (e.g. when new tables are created).
def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE)
raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
+ # To not overload the worker too much we enforce a minimum interval both
+ # when scheduling and performing jobs.
+ if delay_interval < BackgroundMigrationWorker::MIN_INTERVAL
+ delay_interval = BackgroundMigrationWorker::MIN_INTERVAL
+ end
+
model_class.each_batch(of: batch_size) do |relation, index|
start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 3f7b42456af..dbb8f317afe 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -71,5 +71,16 @@ module Gitlab
redis.exists(@redis_shared_state_key)
end
end
+
+ # Returns the TTL of the Redis key.
+ #
+ # This method will return `nil` if no TTL could be obtained.
+ def ttl
+ Gitlab::Redis::SharedState.with do |redis|
+ ttl = redis.ttl(@redis_shared_state_key)
+
+ ttl if ttl.positive?
+ end
+ end
end
end
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index a1755143abe..031fccba92b 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -173,8 +173,8 @@ module Gitlab
end
def find_by_rugged(repository, sha, path, limit:)
- commit = repository.lookup(sha)
- root_tree = commit.tree
+ rugged_commit = repository.lookup(sha)
+ root_tree = rugged_commit.tree
blob_entry = find_entry_by_path(repository, root_tree.oid, path)
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 145721dea76..016437b2419 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -15,8 +15,6 @@ module Gitlab
attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator
- delegate :tree, to: :rugged_commit
-
def ==(other)
return false unless other.is_a?(Gitlab::Git::Commit)
@@ -452,6 +450,11 @@ module Gitlab
)
end
+ # Is this the same as Blob.find_entry_by_path ?
+ def rugged_tree_entry(path)
+ rugged_commit.tree.path(path)
+ end
+
private
def init_from_hash(hash)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 84105501d1e..283134e043e 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1163,23 +1163,13 @@ module Gitlab
end
def fetch_repository_as_mirror(repository)
- remote_name = "tmp-#{SecureRandom.hex}"
-
- # Notice that this feature flag is not for `fetch_repository_as_mirror`
- # as a whole but for the fetching mechanism (file path or gitaly-ssh).
- url, env = gitaly_migrate(:fetch_internal) do |is_enabled|
+ gitaly_migrate(:remote_fetch_internal_remote) do |is_enabled|
if is_enabled
- repository = RemoteRepository.new(repository) unless repository.is_a?(RemoteRepository)
- [GITALY_INTERNAL_URL, repository.fetch_env]
+ gitaly_remote_client.fetch_internal_remote(repository)
else
- [repository.path, nil]
+ rugged_fetch_repository_as_mirror(repository)
end
end
-
- add_remote(remote_name, url, mirror_refmap: :all_refs)
- fetch_remote(remote_name, env: env)
- ensure
- remove_remote(remote_name)
end
def blob_at(sha, path)
@@ -1187,7 +1177,7 @@ module Gitlab
end
# Items should be of format [[commit_id, path], [commit_id1, path1]]
- def batch_blobs(items, blob_size_limit: nil)
+ def batch_blobs(items, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit)
end
@@ -2070,6 +2060,16 @@ module Gitlab
false
end
+ def rugged_fetch_repository_as_mirror(repository)
+ remote_name = "tmp-#{SecureRandom.hex}"
+ repository = RemoteRepository.new(repository) unless repository.is_a?(RemoteRepository)
+
+ add_remote(remote_name, GITALY_INTERNAL_URL, mirror_refmap: :all_refs)
+ fetch_remote(remote_name, env: repository.fetch_env)
+ ensure
+ remove_remote(remote_name)
+ end
+
def fetch_remote(remote_name = 'origin', env: nil)
run_git(['fetch', remote_name], env: env).last.zero?
end
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 9218f6cfd68..559a901b9a3 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -23,6 +23,19 @@ module Gitlab
response.result
end
+
+ def fetch_internal_remote(repository)
+ request = Gitaly::FetchInternalRemoteRequest.new(
+ repository: @gitaly_repo,
+ remote_repository: repository.gitaly_repository
+ )
+
+ response = GitalyClient.call(@storage, :remote_service,
+ :fetch_internal_remote, request,
+ remote_storage: repository.storage)
+
+ response.result
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index d43d80da960..66006f5dc5b 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -43,8 +43,11 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
end
- def fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false)
- request = Gitaly::FetchRemoteRequest.new(repository: @gitaly_repo, remote: remote, force: forced, no_tags: no_tags)
+ def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:)
+ request = Gitaly::FetchRemoteRequest.new(
+ repository: @gitaly_repo, remote: remote, force: forced,
+ no_tags: no_tags, timeout: timeout
+ )
if ssh_auth&.ssh_import?
if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present?
diff --git a/lib/gitlab/grape_logging/loggers/user_logger.rb b/lib/gitlab/grape_logging/loggers/user_logger.rb
new file mode 100644
index 00000000000..fa172861967
--- /dev/null
+++ b/lib/gitlab/grape_logging/loggers/user_logger.rb
@@ -0,0 +1,18 @@
+# This grape_logging module (https://github.com/aserafin/grape_logging) makes it
+# possible to log the user who performed the Grape API action by retrieving
+# the user context from the request environment.
+module Gitlab
+ module GrapeLogging
+ module Loggers
+ class UserLogger < ::GrapeLogging::Loggers::Base
+ def parameters(request, _)
+ params = request.env[::API::Helpers::API_USER_ENV]
+
+ return {} unless params
+
+ params.slice(:user_id, :username)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index 2066005dddc..af203ff711d 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -3,7 +3,7 @@ module Gitlab
extend self
# For every version update, the version history in import_export.md has to be kept up to date.
- VERSION = '0.2.1'.freeze
+ VERSION = '0.2.2'.freeze
FILENAME_LIMIT = 50
def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index f2b193c79cb..2daed10f678 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -49,8 +49,8 @@ project_tree:
- :author
- events:
- :push_event_payload
- - :stages
- - :statuses
+ - stages:
+ - :statuses
- :auto_devops
- :triggers
- :pipeline_schedules
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index d7d1b05e8b9..05dbaf6322c 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -62,6 +62,7 @@ module Gitlab
when :notes then setup_note
when :project_label, :project_labels then setup_label
when :milestone, :milestones then setup_milestone
+ when 'Ci::Pipeline' then setup_pipeline
else
@relation_hash['project_id'] = @project.id
end
@@ -112,9 +113,7 @@ module Gitlab
@relation_hash.delete('trace') # old export files have trace
@relation_hash.delete('token')
- imported_object do |object|
- object.commit_id = nil
- end
+ imported_object
elsif @relation_name == :merge_requests
MergeRequestParser.new(@project, @relation_hash.delete('diff_head_sha'), imported_object, @relation_hash).parse!
else
@@ -182,8 +181,9 @@ module Gitlab
end
def imported_object
- yield(existing_or_new_object) if block_given?
- existing_or_new_object.importing = true if existing_or_new_object.respond_to?(:importing)
+ if existing_or_new_object.respond_to?(:importing)
+ existing_or_new_object.importing = true
+ end
existing_or_new_object
rescue ActiveRecord::RecordNotUnique
@@ -211,6 +211,14 @@ module Gitlab
@relation_hash['diff'] = @relation_hash.delete('utf8_diff')
end
+ def setup_pipeline
+ @relation_hash.fetch('stages').each do |stage|
+ stage.statuses.each do |status|
+ status.pipeline = imported_object
+ end
+ end
+ end
+
def existing_or_new_object
# Only find existing records to avoid mapping tables such as milestones
# Otherwise always create the record, skipping the extra SELECT clause.
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 2c7b8af83f2..0002c7da8f1 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def environment_name_regex_chars
- 'a-zA-Z0-9_/\\$\\{\\}\\. -'
+ 'a-zA-Z0-9_/\\$\\{\\}\\. \\-'
end
def environment_name_regex
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 564047bbd34..a8a4ec996c4 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -128,7 +128,7 @@ module Gitlab
def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false)
gitaly_migrate(:fetch_remote) do |is_enabled|
if is_enabled
- repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
+ repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, timeout: git_timeout)
else
storage_path = Gitlab.config.repositories.storages[repository.storage]["path"]
local_fetch_remote(storage_path, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index b0563fb2d69..f05d001fd02 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -1,4 +1,6 @@
require 'google/apis/container_v1'
+require 'google/apis/cloudbilling_v1'
+require 'google/apis/cloudresourcemanager_v1'
module GoogleApi
module CloudPlatform
@@ -40,6 +42,22 @@ module GoogleApi
true
end
+ def projects_list
+ service = Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new
+ service.authorization = access_token
+
+ service.fetch_all(items: :projects) do |token|
+ service.list_projects(page_token: token)
+ end
+ end
+
+ def projects_get_billing_info(project_name)
+ service = Google::Apis::CloudbillingV1::CloudbillingService.new
+ service.authorization = access_token
+
+ service.get_project_billing_info("projects/#{project_name}")
+ end
+
def projects_zones_clusters_get(project_id, zone, cluster_id)
service = Google::Apis::ContainerV1::ContainerService.new
service.authorization = access_token
diff --git a/package.json b/package.json
index 1f83f62e7f7..5b9b90f0e77 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"dependencies": {
"autosize": "^4.0.0",
"axios": "^0.17.1",
- "axios-mock-adapter": "^1.10.0",
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.2",
"babel-loader": "^7.1.2",
@@ -89,6 +88,7 @@
},
"devDependencies": {
"@gitlab-org/gitlab-svgs": "^1.5.0",
+ "axios-mock-adapter": "^1.10.0",
"babel-plugin-istanbul": "^4.1.5",
"eslint": "^3.10.1",
"eslint-config-airbnb-base": "^10.0.1",
diff --git a/qa/qa.rb b/qa/qa.rb
index 453e4e9e164..71b80a6adcb 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -10,6 +10,7 @@ module QA
autoload :Namespace, 'qa/runtime/namespace'
autoload :Scenario, 'qa/runtime/scenario'
autoload :Browser, 'qa/runtime/browser'
+ autoload :Env, 'qa/runtime/env'
end
##
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 220bb45741b..14b2a488760 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -38,22 +38,49 @@ module QA
Capybara.register_driver :chrome do |app|
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
- 'chromeOptions' => {
- 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1680]
+ # This enables access to logs with `page.driver.manage.get_log(:browser)`
+ loggingPrefs: {
+ browser: "ALL",
+ client: "ALL",
+ driver: "ALL",
+ server: "ALL"
}
)
- Capybara::Selenium::Driver
- .new(app, browser: :chrome, desired_capabilities: capabilities)
- end
+ options = Selenium::WebDriver::Chrome::Options.new
+ options.add_argument("window-size=1240,1680")
- Capybara::Screenshot.register_driver(:chrome) do |driver, path|
- driver.browser.save_screenshot(path)
+ # Chrome won't work properly in a Docker container in sandbox mode
+ options.add_argument("no-sandbox")
+
+ # Run headless by default unless CHROME_HEADLESS is false
+ if QA::Runtime::Env.chrome_headless?
+ options.add_argument("headless")
+
+ # Chrome documentation says this flag is needed for now
+ # https://developers.google.com/web/updates/2017/04/headless-chrome#cli
+ options.add_argument("disable-gpu")
+ end
+
+ # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
+ options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci?
+
+ Capybara::Selenium::Driver.new(
+ app,
+ browser: :chrome,
+ desired_capabilities: capabilities,
+ options: options
+ )
end
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
+ # From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326
+ Capybara::Screenshot.register_driver(:chrome) do |driver, path|
+ driver.browser.save_screenshot(path)
+ end
+
Capybara.configure do |config|
config.default_driver = :chrome
config.javascript_driver = :chrome
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
new file mode 100644
index 00000000000..d5c28e9a7db
--- /dev/null
+++ b/qa/qa/runtime/env.rb
@@ -0,0 +1,15 @@
+module QA
+ module Runtime
+ module Env
+ extend self
+
+ def chrome_headless?
+ (ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i) != 0
+ end
+
+ def running_in_ci?
+ ENV['CI'] || ENV['CI_SERVER']
+ end
+ end
+ end
+end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
new file mode 100644
index 00000000000..57a72a04507
--- /dev/null
+++ b/qa/spec/runtime/env_spec.rb
@@ -0,0 +1,64 @@
+describe QA::Runtime::Env do
+ before do
+ allow(ENV).to receive(:[]).and_call_original
+ end
+
+ describe '.chrome_headless?' do
+ context 'when there is an env variable set' do
+ it 'returns false when falsey values specified' do
+ stub_env('CHROME_HEADLESS', 'false')
+ expect(described_class.chrome_headless?).to be_falsey
+
+ stub_env('CHROME_HEADLESS', 'no')
+ expect(described_class.chrome_headless?).to be_falsey
+
+ stub_env('CHROME_HEADLESS', '0')
+ expect(described_class.chrome_headless?).to be_falsey
+ end
+
+ it 'returns true when anything else specified' do
+ stub_env('CHROME_HEADLESS', 'true')
+ expect(described_class.chrome_headless?).to be_truthy
+
+ stub_env('CHROME_HEADLESS', '1')
+ expect(described_class.chrome_headless?).to be_truthy
+
+ stub_env('CHROME_HEADLESS', 'anything')
+ expect(described_class.chrome_headless?).to be_truthy
+ end
+ end
+
+ context 'when there is no env variable set' do
+ it 'returns the default, true' do
+ stub_env('CHROME_HEADLESS', nil)
+ expect(described_class.chrome_headless?).to be_truthy
+ end
+ end
+ end
+
+ describe '.running_in_ci?' do
+ context 'when there is an env variable set' do
+ it 'returns true if CI' do
+ stub_env('CI', 'anything')
+ expect(described_class.running_in_ci?).to be_truthy
+ end
+
+ it 'returns true if CI_SERVER' do
+ stub_env('CI_SERVER', 'anything')
+ expect(described_class.running_in_ci?).to be_truthy
+ end
+ end
+
+ context 'when there is no env variable set' do
+ it 'returns true' do
+ stub_env('CI', nil)
+ stub_env('CI_SERVER', nil)
+ expect(described_class.running_in_ci?).to be_falsey
+ end
+ end
+ end
+
+ def stub_env(name, value)
+ allow(ENV).to receive(:[]).with(name).and_return(value)
+ end
+end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index 305af289531..4d765229bde 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -55,6 +55,16 @@ describe Projects::BoardsController do
end
end
+ context 'issues are disabled' do
+ let(:project) { create(:project, :issues_disabled) }
+
+ it 'returns a not found 404 response' do
+ list_boards
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
def list_boards(format: :html)
get :index, namespace_id: project.namespace,
project_id: project,
diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb
index ee7928beb7e..be19fa93183 100644
--- a/spec/controllers/projects/clusters/gcp_controller_spec.rb
+++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb
@@ -17,7 +17,6 @@ describe Projects::Clusters::GcpController do
context 'when omniauth has been configured' do
let(:key) { 'secret-key' }
-
let(:session_key_for_redirect_uri) do
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key)
end
@@ -78,6 +77,8 @@ describe Projects::Clusters::GcpController do
end
it 'has new object' do
+ expect(controller).to receive(:authorize_google_project_billing)
+
go
expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster)
@@ -138,7 +139,11 @@ describe Projects::Clusters::GcpController do
stub_google_api_validate_token
end
- context 'when creates a cluster on gke' do
+ context 'when google project billing is enabled' do
+ before do
+ stub_google_project_billing_status
+ end
+
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { go }.to change { Clusters::Cluster.count }
@@ -148,6 +153,15 @@ describe Projects::Clusters::GcpController do
expect(project.clusters.first).to be_kubernetes
end
end
+
+ context 'when google project billing is not enabled' do
+ it 'renders the cluster form with an error' do
+ go
+
+ expect(response).to set_flash[:error]
+ expect(response).to render_template('new')
+ end
+ end
end
context 'when access token is expired' do
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 77a47f0ad13..0202149f335 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -17,4 +17,51 @@ describe Projects::Settings::CiCdController do
expect(response).to render_template(:show)
end
end
+
+ describe '#reset_cache' do
+ before do
+ sign_in(user)
+
+ project.add_master(user)
+
+ allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true)
+ end
+
+ subject { post :reset_cache, namespace_id: project.namespace, project_id: project }
+
+ it 'calls reset project cache service' do
+ expect(ResetProjectCacheService).to receive_message_chain(:new, :execute)
+
+ subject
+ end
+
+ it 'redirects to project pipelines path' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(response).to redirect_to(project_pipelines_path(project))
+ end
+
+ context 'when service returns successfully' do
+ it 'sets the flash notice variable' do
+ subject
+
+ expect(controller).to set_flash[:notice]
+ expect(controller).not_to set_flash[:error]
+ end
+ end
+
+ context 'when service does not return successfully' do
+ before do
+ allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false)
+ end
+
+ it 'sets the flash error variable' do
+ subject
+
+ expect(controller).not_to set_flash[:notice]
+ expect(controller).to set_flash[:error]
+ end
+ end
+ end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index dc1d88c92dc..6f66468570f 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -7,12 +7,10 @@ FactoryBot.define do
stage_idx 0
ref 'master'
tag false
- status 'pending'
- created_at 'Di 29. Okt 09:50:00 CET 2013'
- started_at 'Di 29. Okt 09:51:28 CET 2013'
- finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a'
protected false
+ created_at 'Di 29. Okt 09:50:00 CET 2013'
+ pending
options do
{
@@ -29,23 +27,37 @@ FactoryBot.define do
pipeline factory: :ci_pipeline
+ trait :started do
+ started_at 'Di 29. Okt 09:51:28 CET 2013'
+ end
+
+ trait :finished do
+ started
+ finished_at 'Di 29. Okt 09:53:28 CET 2013'
+ end
+
trait :success do
+ finished
status 'success'
end
trait :failed do
+ finished
status 'failed'
end
trait :canceled do
+ finished
status 'canceled'
end
trait :skipped do
+ started
status 'skipped'
end
trait :running do
+ started
status 'running'
end
@@ -114,11 +126,6 @@ FactoryBot.define do
build.project ||= build.pipeline.project
end
- factory :ci_not_started_build do
- started_at nil
- finished_at nil
- end
-
trait :tag do
tag true
end
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index 435de3861cf..d820a59aa16 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -1,20 +1,38 @@
require 'rails_helper'
describe 'Issue Boards shortcut', :js do
- let(:project) { create(:project) }
+ context 'issues are enabled' do
+ let(:project) { create(:project) }
- before do
- create(:board, project: project)
+ before do
+ create(:board, project: project)
- sign_in(create(:admin))
+ sign_in(create(:admin))
- visit project_path(project)
+ visit project_path(project)
+ end
+
+ it 'takes user to issue board index' do
+ find('body').native.send_keys('gb')
+ expect(page).to have_selector('.boards-list')
+
+ wait_for_requests
+ end
end
- it 'takes user to issue board index' do
- find('body').native.send_keys('gb')
- expect(page).to have_selector('.boards-list')
+ context 'issues are not enabled' do
+ let(:project) { create(:project, :issues_disabled) }
+
+ before do
+ sign_in(create(:admin))
+
+ visit project_path(project)
+ end
+
+ it 'does not take user to the issue board index' do
+ find('body').native.send_keys('gb')
- wait_for_requests
+ expect(page).to have_selector("body[data-page='projects:show']")
+ end
end
end
diff --git a/spec/features/explore/groups_spec.rb b/spec/features/explore/groups_spec.rb
new file mode 100644
index 00000000000..e4ef47d88dd
--- /dev/null
+++ b/spec/features/explore/groups_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe 'Explore Groups', :js do
+ let(:user) { create :user }
+ let(:group) { create :group }
+ let!(:private_project) do
+ create :project, :private, namespace: group do |project|
+ create(:issue, project: internal_project)
+ create(:merge_request, source_project: project, target_project: project)
+ end
+ end
+
+ let!(:internal_project) do
+ create :project, :internal, namespace: group do |project|
+ create(:issue, project: project)
+ create(:merge_request, source_project: project, target_project: project)
+ end
+ end
+
+ let!(:public_project) do
+ create(:project, :public, namespace: group) do |project|
+ create(:issue, project: project)
+ create(:merge_request, source_project: project, target_project: project)
+ end
+ end
+
+ shared_examples 'renders public and internal projects' do
+ it do
+ visit_page
+ expect(page).to have_content(public_project.name)
+ expect(page).to have_content(internal_project.name)
+ expect(page).not_to have_content(private_project.name)
+ end
+ end
+
+ shared_examples 'renders only public project' do
+ it do
+ visit_page
+ expect(page).to have_content(public_project.name)
+ expect(page).not_to have_content(internal_project.name)
+ expect(page).not_to have_content(private_project.name)
+ end
+ end
+
+ shared_examples 'renders group in public groups area' do
+ it do
+ visit explore_groups_path
+ expect(page).to have_content(group.name)
+ end
+ end
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it_behaves_like 'renders public and internal projects' do
+ subject(:visit_page) { visit group_path(group) }
+ end
+
+ it_behaves_like 'renders public and internal projects' do
+ subject(:visit_page) { visit issues_group_path(group) }
+ end
+
+ it_behaves_like 'renders public and internal projects' do
+ subject(:visit_page) { visit merge_requests_group_path(group) }
+ end
+
+ it_behaves_like 'renders group in public groups area'
+ end
+
+ context 'when signed out' do
+ it_behaves_like 'renders only public project' do
+ subject(:visit_page) { visit group_path(group) }
+ end
+
+ it_behaves_like 'renders only public project' do
+ subject(:visit_page) { visit issues_group_path(group) }
+ end
+
+ it_behaves_like 'renders only public project' do
+ subject(:visit_page) { visit merge_requests_group_path(group) }
+ end
+
+ it_behaves_like 'renders group in public groups area'
+ end
+end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 7fc2b383749..ceccc471405 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -55,4 +55,20 @@ feature 'Group show page' do
end
end
end
+
+ context 'group has a project with emoji in description', :js do
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, description: ':smile:', namespace: group) }
+
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ visit path
+ end
+
+ it 'shows the project info' do
+ expect(page).to have_content(project.title)
+ expect(page).to have_selector('gl-emoji[data-name="smile"]')
+ end
+ end
end
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
new file mode 100644
index 00000000000..e4be6193b8b
--- /dev/null
+++ b/spec/features/invites_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe 'Invites' do
+ let(:user) { create(:user) }
+ let(:owner) { create(:user, name: 'John Doe') }
+ let(:group) { create(:group, name: 'Owned') }
+ let(:project) { create(:project, :repository, namespace: group) }
+ let(:invite) { group.group_members.invite.last }
+
+ before do
+ project.add_master(owner)
+ group.add_user(owner, Gitlab::Access::OWNER)
+ group.add_developer('user@example.com', owner)
+ invite.generate_invite_token!
+ end
+
+ context 'when signed out' do
+ before do
+ visit invite_path(invite.raw_invite_token)
+ end
+
+ it 'renders sign in page with sign in notice' do
+ expect(current_path).to eq(new_user_session_path)
+ expect(page).to have_content('To accept this invitation, sign in')
+ end
+
+ it 'sign in and redirects to invitation page' do
+ fill_in 'user_login', with: user.email
+ fill_in 'user_password', with: user.password
+ check 'user_remember_me'
+ click_button 'Sign in'
+
+ expect(current_path).to eq(invite_path(invite.raw_invite_token))
+ expect(page).to have_content(
+ 'You have been invited by John Doe to join group Owned as Developer.'
+ )
+ expect(page).to have_link('Accept invitation')
+ expect(page).to have_link('Decline')
+ end
+ end
+
+ context 'when signed in as an exists member' do
+ before do
+ sign_in(owner)
+ end
+
+ it 'shows message user already a member' do
+ visit invite_path(invite.raw_invite_token)
+ expect(page).to have_content('However, you are already a member of this group.')
+ end
+ end
+
+ describe 'accepting the invitation' do
+ before do
+ sign_in(user)
+ visit invite_path(invite.raw_invite_token)
+ end
+
+ it 'grants access and redirects to group page' do
+ page.click_link 'Accept invitation'
+ expect(current_path).to eq(group_path(group))
+ expect(page).to have_content(
+ 'You have been granted Developer access to group Owned.'
+ )
+ end
+ end
+
+ describe 'declining the application' do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ visit invite_path(invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to dashboard' do
+ page.click_link 'Decline'
+ expect(current_path).to eq(dashboard_projects_path)
+ expect(page).to have_content(
+ 'You have declined the invitation to join group Owned.'
+ )
+ end
+ end
+
+ context 'when signed out' do
+ before do
+ visit decline_invite_path(invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to sign in page' do
+ expect(current_path).to eq(new_user_session_path)
+ expect(page).to have_content(
+ 'You have declined the invitation to join group Owned.'
+ )
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/keyboard_shortcut_spec.rb b/spec/features/issues/keyboard_shortcut_spec.rb
new file mode 100644
index 00000000000..961de9d3d25
--- /dev/null
+++ b/spec/features/issues/keyboard_shortcut_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+describe 'Issues shortcut', :js do
+ context 'New Issue shortcut' do
+ context 'issues are enabled' do
+ let(:project) { create(:project) }
+
+ before do
+ sign_in(create(:admin))
+
+ visit project_path(project)
+ end
+
+ it 'takes user to the new issue page' do
+ find('body').native.send_keys('i')
+ expect(page).to have_selector('#new_issue')
+ end
+ end
+
+ context 'issues are not enabled' do
+ let(:project) { create(:project, :issues_disabled) }
+
+ before do
+ sign_in(create(:admin))
+
+ visit project_path(project)
+ end
+
+ it 'does not take user to the new issue page' do
+ find('body').native.send_keys('i')
+
+ expect(page).to have_selector("body[data-page='projects:show']")
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 882a2756b72..523cc08496b 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -20,105 +20,126 @@ feature 'Gcp Cluster', :js do
.to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
end
- context 'when user does not have a cluster and visits cluster index page' do
+ context 'when user has a GCP project with billing enabled' do
before do
- visit project_clusters_path(project)
-
- click_link 'Add cluster'
- click_link 'Create on GKE'
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
+ stub_google_project_billing_status
end
- context 'when user filled form with valid parameters' do
+ context 'when user does not have a cluster and visits cluster index page' do
before do
- allow_any_instance_of(GoogleApi::CloudPlatform::Client)
- .to receive(:projects_zones_clusters_create) do
- OpenStruct.new(
- self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
- status: 'RUNNING'
- )
+ visit project_clusters_path(project)
+
+ click_link 'Add cluster'
+ click_link 'Create on GKE'
+ end
+
+ context 'when user filled form with valid parameters' do
+ before do
+ allow_any_instance_of(GoogleApi::CloudPlatform::Client)
+ .to receive(:projects_zones_clusters_create) do
+ OpenStruct.new(
+ self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
+ status: 'RUNNING'
+ )
+ end
+
+ allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
+
+ fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
+ fill_in 'cluster_name', with: 'dev-cluster'
+ click_button 'Create cluster'
end
- allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
+ it 'user sees a cluster details page and creation status' do
+ expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
- fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
- fill_in 'cluster_name', with: 'dev-cluster'
- click_button 'Create cluster'
- end
+ Clusters::Cluster.last.provider.make_created!
- it 'user sees a cluster details page and creation status' do
- expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
+ expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine')
+ end
- Clusters::Cluster.last.provider.make_created!
+ it 'user sees a error if something worng during creation' do
+ expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
- expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine')
- end
+ Clusters::Cluster.last.provider.make_errored!('Something wrong!')
- it 'user sees a error if something worng during creation' do
- expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
+ expect(page).to have_content('Something wrong!')
+ end
+ end
- Clusters::Cluster.last.provider.make_errored!('Something wrong!')
+ context 'when user filled form with invalid parameters' do
+ before do
+ click_button 'Create cluster'
+ end
- expect(page).to have_content('Something wrong!')
+ it 'user sees a validation error' do
+ expect(page).to have_css('#error_explanation')
+ end
end
end
- context 'when user filled form with invalid parameters' do
+ context 'when user does have a cluster and visits cluster page' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+
before do
- click_button 'Create cluster'
+ visit project_cluster_path(project, cluster)
end
- it 'user sees a validation error' do
- expect(page).to have_css('#error_explanation')
+ it 'user sees a cluster details page' do
+ expect(page).to have_button('Save changes')
+ expect(page.find(:css, '.cluster-name').value).to eq(cluster.name)
end
- end
- end
- context 'when user does have a cluster and visits cluster page' do
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+ context 'when user disables the cluster' do
+ before do
+ page.find(:css, '.js-toggle-cluster').click
+ page.within('#cluster-integration') { click_button 'Save changes' }
+ end
- before do
- visit project_cluster_path(project, cluster)
- end
+ it 'user sees the successful message' do
+ expect(page).to have_content('Cluster was successfully updated.')
+ end
+ end
- it 'user sees a cluster details page' do
- expect(page).to have_button('Save changes')
- expect(page.find(:css, '.cluster-name').value).to eq(cluster.name)
- end
+ context 'when user changes cluster parameters' do
+ before do
+ fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
+ page.within('#js-cluster-details') { click_button 'Save changes' }
+ end
- context 'when user disables the cluster' do
- before do
- page.find(:css, '.js-toggle-cluster').click
- page.within('#cluster-integration') { click_button 'Save changes' }
+ it 'user sees the successful message' do
+ expect(page).to have_content('Cluster was successfully updated.')
+ expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace')
+ end
end
- it 'user sees the successful message' do
- expect(page).to have_content('Cluster was successfully updated.')
+ context 'when user destroy the cluster' do
+ before do
+ page.accept_confirm do
+ click_link 'Remove integration'
+ end
+ end
+
+ it 'user sees creation form with the successful message' do
+ expect(page).to have_content('Cluster integration was successfully removed.')
+ expect(page).to have_link('Add cluster')
+ end
end
end
+ end
- context 'when user changes cluster parameters' do
- before do
- fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
- page.within('#js-cluster-details') { click_button 'Save changes' }
- end
+ context 'when user does not have a GCP project with billing enabled' do
+ before do
+ visit project_clusters_path(project)
- it 'user sees the successful message' do
- expect(page).to have_content('Cluster was successfully updated.')
- expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace')
- end
+ click_link 'Add cluster'
+ click_link 'Create on GKE'
end
- context 'when user destroy the cluster' do
- before do
- page.accept_confirm do
- click_link 'Remove integration'
- end
- end
-
- it 'user sees creation form with the successful message' do
- expect(page).to have_content('Cluster integration was successfully removed.')
- expect(page).to have_link('Add cluster')
- end
+ it 'user sees a check page' do
+ pending 'the frontend still has not been implemented'
+ expect(page).to have_link('Continue')
end
end
end
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index 93929bf6814..eae2910a8f6 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -77,4 +77,18 @@ feature 'Clusters', :js do
end
end
end
+
+ context 'when user has not signed in Google' do
+ before do
+ visit project_clusters_path(project)
+
+ click_link 'Add cluster'
+ click_link 'Create on GKE'
+ end
+
+ it 'user sees a login page' do
+ expect(page).to have_css('.signin-with-google')
+ expect(page).to have_link('Google account')
+ end
+ end
end
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 0c354298433..0cc68aff494 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 5d9208ebadd..4c49cff30d4 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'User browses a job', :js do
- let!(:build) { create(:ci_build, :coverage, pipeline: pipeline) }
+ let!(:build) { create(:ci_build, :running, :coverage, pipeline: pipeline) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
let(:project) { create(:project, :repository, namespace: user.namespace) }
let(:user) { create(:user) }
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index f8ea1a52656..9a6b27c00f8 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -369,6 +369,34 @@ feature 'Jobs' do
end
end
end
+
+ context 'Playable manual action' do
+ let(:job) { create(:ci_build, :playable, pipeline: pipeline) }
+
+ before do
+ project.add_developer(user)
+ visit project_job_path(project, job)
+ end
+
+ it 'shows manual action empty state' do
+ expect(page).to have_content('This job requires a manual action')
+ expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.')
+ expect(page).to have_link('Trigger this manual action')
+ end
+ end
+
+ context 'Non triggered job' do
+ let(:job) { create(:ci_build, :created, pipeline: pipeline) }
+
+ before do
+ visit project_job_path(project, job)
+ end
+
+ it 'shows manual action empty state' do
+ expect(page).to have_content('This job has not been triggered yet')
+ expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered.')
+ end
+ end
end
describe "POST /:project/jobs/:id/cancel", :js do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index df261c246f7..592c99fc64a 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -545,6 +545,40 @@ describe 'Pipelines', :js do
end
end
end
+
+ describe 'Reset runner caches' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ create(:ci_empty_pipeline, status: 'success', project: project, sha: project.commit.id, ref: 'master')
+ project.add_master(user)
+ visit project_pipelines_path(project)
+ end
+
+ it 'has a clear caches button' do
+ expect(page).to have_link 'Clear runner caches'
+ end
+
+ describe 'user clicks the button' do
+ context 'when project already has jobs_cache_index' do
+ before do
+ project.update_attributes(jobs_cache_index: 1)
+ end
+
+ it 'increments jobs_cache_index' do
+ click_link 'Clear runner caches'
+ expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
+ end
+ end
+
+ context 'when project does not have jobs_cache_index' do
+ it 'sets jobs_cache_index to 1' do
+ click_link 'Clear runner caches'
+ expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
+ end
+ end
+ end
+ end
end
context 'when user is not logged in' do
diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml
index 85ee61f0b54..0161c0550d1 100644
--- a/spec/javascripts/fixtures/pipelines.html.haml
+++ b/spec/javascripts/fixtures/pipelines.html.haml
@@ -7,4 +7,6 @@
"new-pipeline-path" => 'foo',
"can-create-pipeline" => 'true',
"has-ci" => 'foo',
- "ci-lint-path" => 'foo' } }
+ "ci-lint-path" => 'foo',
+ "reset-cache-path" => 'foo' } }
+
diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js
index 4a210faa017..83395ea451e 100644
--- a/spec/javascripts/jobs/header_spec.js
+++ b/spec/javascripts/jobs/header_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import headerComponent from '~/jobs/components/header.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
describe('Job details header', () => {
let HeaderComponent;
@@ -35,7 +36,7 @@ describe('Job details header', () => {
isLoading: false,
};
- vm = new HeaderComponent({ propsData: props }).$mount();
+ vm = mountComponent(HeaderComponent, props);
});
afterEach(() => {
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 2f02c11482f..9d6ea3781bc 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -19,17 +19,24 @@ import IssuablesHelper from '~/helpers/issuables_helper';
$('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- return it('submits an ajax request on tasklist:changed', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
+
+ it('submits an ajax request on tasklist:changed', (done) => {
+ spyOn(jQuery, 'ajax').and.callFake((req) => {
expect(req.type).toBe('PATCH');
expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`);
- return expect(req.data.merge_request.description).not.toBe(null);
+ expect(req.data.merge_request.description).not.toBe(null);
+ done();
});
- return $('.js-task-list-field').trigger('tasklist:changed');
+
+ $('.js-task-list-field').trigger('tasklist:changed');
});
});
describe('class constructor', () => {
+ beforeEach(() => {
+ spyOn(jQuery, 'ajax').and.stub();
+ });
+
it('calls .initCloseReopenReport', () => {
spyOn(IssuablesHelper, 'initCloseReopenReport');
diff --git a/spec/javascripts/monitoring/graph/deployment_spec.js b/spec/javascripts/monitoring/graph/deployment_spec.js
index bf6ada8185e..d07db871d69 100644
--- a/spec/javascripts/monitoring/graph/deployment_spec.js
+++ b/spec/javascripts/monitoring/graph/deployment_spec.js
@@ -11,168 +11,38 @@ const createComponent = (propsData) => {
};
describe('MonitoringDeployment', () => {
- const reducedDeploymentData = [deploymentData[0]];
- reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name;
- reducedDeploymentData[0].xPos = 10;
- reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at);
describe('Methods', () => {
- it('refText shows the ref when a tag is available', () => {
- reducedDeploymentData[0].tag = '1.0';
- const component = createComponent({
- showDeployInfo: false,
- deploymentData: reducedDeploymentData,
- graphWidth: 440,
- graphHeight: 300,
- graphHeightOffset: 120,
- });
-
- expect(
- component.refText(reducedDeploymentData[0]),
- ).toEqual(reducedDeploymentData[0].ref);
- });
-
- it('refText shows the sha when no tag is available', () => {
- reducedDeploymentData[0].tag = null;
- const component = createComponent({
- showDeployInfo: false,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.refText(reducedDeploymentData[0]),
- ).toContain('f5bcd1');
- });
-
- it('nameDeploymentClass creates a class with the prefix deploy-info-', () => {
+ it('should contain a hidden gradient', () => {
const component = createComponent({
- showDeployInfo: false,
- deploymentData: reducedDeploymentData,
+ showDeployInfo: true,
+ deploymentData,
graphHeight: 300,
graphWidth: 440,
graphHeightOffset: 120,
});
- expect(
- component.nameDeploymentClass(reducedDeploymentData[0]),
- ).toContain('deploy-info');
+ expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull();
});
it('transformDeploymentGroup translates an available deployment', () => {
const component = createComponent({
showDeployInfo: false,
- deploymentData: reducedDeploymentData,
+ deploymentData,
graphHeight: 300,
graphWidth: 440,
graphHeightOffset: 120,
});
expect(
- component.transformDeploymentGroup(reducedDeploymentData[0]),
+ component.transformDeploymentGroup({ xPos: 16 }),
).toContain('translate(11, 20)');
});
- it('hides the deployment flag', () => {
- reducedDeploymentData[0].showDeploymentFlag = false;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphWidth: 440,
- graphHeight: 300,
- graphHeightOffset: 120,
- });
-
- expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull();
- });
-
- it('positions the flag to the left when the xPos is too far right', () => {
- reducedDeploymentData[0].showDeploymentFlag = false;
- reducedDeploymentData[0].xPos = 250;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphWidth: 440,
- graphHeight: 300,
- graphHeightOffset: 120,
- });
-
- expect(
- component.positionFlag(reducedDeploymentData[0]),
- ).toBeLessThan(0);
- });
-
- it('shows the deployment flag', () => {
- reducedDeploymentData[0].showDeploymentFlag = true;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.$el.querySelector('.js-deploy-info-box').style.display,
- ).not.toEqual('display: none;');
- });
-
- it('contains date, refs and the "deployed" text', () => {
- reducedDeploymentData[0].showDeploymentFlag = true;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text'),
- ).toContainText('Deployed');
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text'),
- ).toContainText('Wed, May 31');
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text'),
- ).toContainText(component.refText(reducedDeploymentData[0]));
- });
-
- it('contains a link to the commit contents', () => {
- reducedDeploymentData[0].showDeploymentFlag = true;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text-link')[0].parentElement.getAttribute('xlink:href'),
- ).not.toEqual('');
- });
-
- it('should contain a hidden gradient', () => {
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull();
- });
-
describe('Computed props', () => {
it('calculatedHeight', () => {
const component = createComponent({
showDeployInfo: true,
- deploymentData: reducedDeploymentData,
+ deploymentData,
graphHeight: 300,
graphWidth: 440,
graphHeightOffset: 120,
diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js
index 8ee1171419d..2d474e9092f 100644
--- a/spec/javascripts/monitoring/graph/flag_spec.js
+++ b/spec/javascripts/monitoring/graph/flag_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import GraphFlag from '~/monitoring/components/graph/flag.vue';
+import { deploymentData } from '../mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(GraphFlag);
@@ -9,11 +10,6 @@ const createComponent = (propsData) => {
}).$mount();
};
-function getCoordinate(component, selector, coordinate) {
- const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate);
- return parseInt(coordinateVal, 10);
-}
-
const defaultValuesComponent = {
currentXCoordinate: 200,
currentYCoordinate: 100,
@@ -25,31 +21,111 @@ const defaultValuesComponent = {
graphHeight: 300,
graphHeightOffset: 120,
showFlagContent: true,
+ realPixelRatio: 1,
+ timeSeries: [{
+ values: [{
+ time: new Date('2017-06-04T18:17:33.501Z'),
+ value: '1.49609375',
+ }],
+ }],
+ unitOfDisplay: 'ms',
+ currentDataIndex: 0,
+ legendTitle: 'Average',
+};
+
+const deploymentFlagData = {
+ ...deploymentData[0],
+ ref: deploymentData[0].ref.name,
+ xPos: 10,
+ time: new Date(deploymentData[0].created_at),
};
describe('GraphFlag', () => {
- it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => {
- const component = createComponent(defaultValuesComponent);
+ let component;
- expect(getCoordinate(component, '.selected-metric-line', 'x1'))
- .toEqual(component.currentXCoordinate);
- expect(getCoordinate(component, '.selected-metric-line', 'x2'))
- .toEqual(component.currentXCoordinate);
+ it('has a line at the currentXCoordinate', () => {
+ component = createComponent(defaultValuesComponent);
+
+ expect(component.$el.style.left)
+ .toEqual(`${70 + component.currentXCoordinate}px`);
});
- it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
- const component = createComponent(defaultValuesComponent);
+ describe('Deployment flag', () => {
+ it('shows a deployment flag when deployment data provided', () => {
+ const deploymentFlagComponent = createComponent({
+ ...defaultValuesComponent,
+ deploymentFlagData,
+ });
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.popover-title'),
+ ).toContainText('Deployed');
+ });
+
+ it('contains the ref when a tag is available', () => {
+ const deploymentFlagComponent = createComponent({
+ ...defaultValuesComponent,
+ deploymentFlagData: {
+ ...deploymentFlagData,
+ sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ tag: true,
+ ref: '1.0',
+ },
+ });
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).toContainText('f5bcd1d9');
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).toContainText('1.0');
+ });
+
+ it('does not contain the ref when a tag is unavailable', () => {
+ const deploymentFlagComponent = createComponent({
+ ...defaultValuesComponent,
+ deploymentFlagData: {
+ ...deploymentFlagData,
+ sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ tag: false,
+ ref: '1.0',
+ },
+ });
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).toContainText('f5bcd1d9');
- const svg = component.$el.querySelector('.rect-text-metric');
- expect(svg.tagName).toEqual('svg');
- expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition);
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).not.toContainText('1.0');
+ });
});
describe('Computed props', () => {
- it('calculatedHeight', () => {
- const component = createComponent(defaultValuesComponent);
+ beforeEach(() => {
+ component = createComponent(defaultValuesComponent);
+ });
+
+ it('formatTime', () => {
+ expect(component.formatTime).toMatch(/\d:17PM/);
+ });
+
+ it('formatDate', () => {
+ expect(component.formatDate).toEqual('Sun, Jun 4');
+ });
+
+ it('cursorStyle', () => {
+ expect(component.cursorStyle).toEqual({
+ top: '20px',
+ left: '270px',
+ height: '180px',
+ });
+ });
- expect(component.calculatedHeight).toEqual(180);
+ it('flagOrientation', () => {
+ expect(component.flagOrientation).toEqual('left');
});
});
});
diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js
index f1697840fcd..09a0c14d96c 100644
--- a/spec/javascripts/pipelines/nav_controls_spec.js
+++ b/spec/javascripts/pipelines/nav_controls_spec.js
@@ -14,6 +14,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
@@ -31,6 +32,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: false,
};
@@ -41,12 +43,31 @@ describe('Pipelines Nav Controls', () => {
expect(component.$el.querySelector('.btn-create')).toEqual(null);
});
+ it('should render link for resetting runner caches', () => {
+ const mockData = {
+ newPipelinePath: 'foo',
+ hasCiEnabled: true,
+ helpPagePath: 'foo',
+ ciLintPath: 'foo',
+ resetCachePath: 'foo',
+ canCreatePipeline: false,
+ };
+
+ const component = new NavControlsComponent({
+ propsData: mockData,
+ }).$mount();
+
+ expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches');
+ expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath);
+ });
+
it('should render link for CI lint', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
@@ -54,8 +75,8 @@ describe('Pipelines Nav Controls', () => {
propsData: mockData,
}).$mount();
- expect(component.$el.querySelector('.btn-default').textContent).toContain('CI Lint');
- expect(component.$el.querySelector('.btn-default').getAttribute('href')).toEqual(mockData.ciLintPath);
+ expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint');
+ expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath);
});
it('should render link to help page when CI is not enabled', () => {
@@ -64,6 +85,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: false,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
@@ -81,6 +103,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 59e16f0786e..35871dddf89 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,5 +1,5 @@
import * as urlUtils from '~/lib/utils/url_utility';
-import Todos from '~/todos';
+import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index b4553acb341..b378a0bd896 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import headerCi from '~/vue_shared/components/header_ci_component.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Header CI Component', () => {
let HeaderCi;
@@ -8,7 +9,6 @@ describe('Header CI Component', () => {
beforeEach(() => {
HeaderCi = Vue.extend(headerCi);
-
props = {
status: {
group: 'failed',
@@ -45,54 +45,65 @@ describe('Header CI Component', () => {
],
hasSidebarButton: true,
};
-
- vm = new HeaderCi({
- propsData: props,
- }).$mount();
});
afterEach(() => {
vm.$destroy();
});
- it('should render status badge', () => {
- expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
- expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
- expect(
- vm.$el.querySelector('.ci-failed').getAttribute('href'),
- ).toEqual(props.status.details_path);
- });
+ describe('render', () => {
+ beforeEach(() => {
+ vm = mountComponent(HeaderCi, props);
+ });
- it('should render item name and id', () => {
- expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
- });
+ it('should render status badge', () => {
+ expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
+ expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
+ expect(
+ vm.$el.querySelector('.ci-failed').getAttribute('href'),
+ ).toEqual(props.status.details_path);
+ });
- it('should render timeago date', () => {
- expect(vm.$el.querySelector('time')).toBeDefined();
- });
+ it('should render item name and id', () => {
+ expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
+ });
- it('should render user icon and name', () => {
- expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name);
- });
+ it('should render timeago date', () => {
+ expect(vm.$el.querySelector('time')).toBeDefined();
+ });
- it('should render provided actions', () => {
- expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON');
- expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label);
- expect(vm.$el.querySelector('.link').tagName).toEqual('A');
- expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
- expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
- });
+ it('should render user icon and name', () => {
+ expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name);
+ });
+
+ it('should render provided actions', () => {
+ expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON');
+ expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label);
+ expect(vm.$el.querySelector('.link').tagName).toEqual('A');
+ expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
+ expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
+ });
- it('should show loading icon', (done) => {
- vm.actions[0].isLoading = true;
+ it('should show loading icon', (done) => {
+ vm.actions[0].isLoading = true;
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy();
- done();
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy();
+ done();
+ });
+ });
+
+ it('should render sidebar toggle button', () => {
+ expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined();
});
});
- it('should render sidebar toggle button', () => {
- expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined();
+ describe('shouldRenderTriggeredLabel', () => {
+ it('should rendered created keyword when the shouldRenderTriggeredLabel is false', () => {
+ vm = mountComponent(HeaderCi, { ...props, shouldRenderTriggeredLabel: false });
+
+ expect(vm.$el.textContent).toContain('created');
+ expect(vm.$el.textContent).not.toContain('triggered');
+ });
});
});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 1465ef5855f..b3b5dd1d10a 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -32,7 +32,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.innerHTML).not.toBeDefined();
+ expect(component.$el.childNodes.length).toEqual(0);
});
describe('prev button', () => {
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 28ea7d4c303..38a47a159e1 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -122,17 +122,18 @@ describe 'cycle analytics events' do
let(:stage) { :test }
let(:merge_request) { MergeRequest.first }
+
let!(:pipeline) do
create(:ci_pipeline,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- project: context.project,
+ project: project,
head_pipeline_of: merge_request)
end
before do
- create(:ci_build, pipeline: pipeline, status: :success, author: user)
- create(:ci_build, pipeline: pipeline, status: :success, author: user)
+ create(:ci_build, :success, pipeline: pipeline, author: user)
+ create(:ci_build, :success, pipeline: pipeline, author: user)
pipeline.run!
pipeline.succeed!
@@ -219,17 +220,18 @@ describe 'cycle analytics events' do
describe '#staging_events' do
let(:stage) { :staging }
let(:merge_request) { MergeRequest.first }
+
let!(:pipeline) do
create(:ci_pipeline,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- project: context.project,
+ project: project,
head_pipeline_of: merge_request)
end
before do
- create(:ci_build, pipeline: pipeline, status: :success, author: user)
- create(:ci_build, pipeline: pipeline, status: :success, author: user)
+ create(:ci_build, :success, pipeline: pipeline, author: user)
+ create(:ci_build, :success, pipeline: pipeline, author: user)
pipeline.run!
pipeline.succeed!
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 7727a1d81b1..43761c2fe0c 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1006,12 +1006,12 @@ describe Gitlab::Database::MigrationHelpers do
context 'with batch_size option' do
it 'queues jobs correctly' do
Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.seconds, batch_size: 2)
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
end
end
end
@@ -1019,10 +1019,10 @@ describe Gitlab::Database::MigrationHelpers do
context 'without batch_size option' do
it 'queues jobs correctly' do
Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.seconds)
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes)
expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
end
end
end
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index d81774c8b8f..a067c42b75b 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -19,4 +19,18 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
diff_files
end
+
+ shared_examples 'initializes a DiffCollection' do
+ it 'returns a valid instance of a DiffCollection' do
+ expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
+ end
+ end
+
+ context 'with Gitaly disabled', :disable_gitaly do
+ it_behaves_like 'initializes a DiffCollection'
+ end
+
+ context 'with Gitaly enabled' do
+ it_behaves_like 'initializes a DiffCollection'
+ end
end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 7322a326b01..6193e177668 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -73,4 +73,19 @@ describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
described_class.new(key, timeout: 3600).try_obtain
end
end
+
+ describe '#ttl' do
+ it 'returns the TTL of the Redis key' do
+ lease = described_class.new('kittens', timeout: 100)
+ lease.try_obtain
+
+ expect(lease.ttl <= 100).to eq(true)
+ end
+
+ it 'returns nil when the lease does not exist' do
+ lease = described_class.new('kittens', timeout: 10)
+
+ expect(lease.ttl).to be_nil
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 7f5946b1658..07eb5b82d5f 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -146,7 +146,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
context 'when sha references a tree' do
it 'returns nil' do
- tree = Gitlab::Git::Commit.find(repository, 'master').tree
+ tree = repository.rugged.rev_parse('master^{tree}')
blob = Gitlab::Git::Blob.raw(repository, tree.oid)
@@ -230,7 +230,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
describe '.batch_lfs_pointers' do
- let(:tree_object) { Gitlab::Git::Commit.find(repository, 'master').tree }
+ let(:tree_object) { repository.rugged.rev_parse('master^{tree}') }
let(:non_lfs_blob) do
Gitlab::Git::Blob.find(
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 6d35734d306..6a07a3ca8b8 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -55,7 +55,6 @@ describe Gitlab::Git::Commit, seed_helper: true do
it { expect(@commit.parents).to eq(@gitlab_parents) }
it { expect(@commit.parent_id).to eq(@parents.first.oid) }
it { expect(@commit.no_commit_message).to eq("--no commit message") }
- it { expect(@commit.tree).to eq(@tree) }
after do
# Erase the new commit so other tests get the original repo
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index f94234f6010..f346a345f00 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -649,29 +649,39 @@ describe Gitlab::Git::Repository, seed_helper: true do
Gitlab::Shell.new.remove_repository(storage_path, 'my_project')
end
- it 'fetches a repository as a mirror remote' do
- subject
+ shared_examples 'repository mirror fecthing' do
+ it 'fetches a repository as a mirror remote' do
+ subject
- expect(refs(new_repository.path)).to eq(refs(repository.path))
- end
+ expect(refs(new_repository.path)).to eq(refs(repository.path))
+ end
- context 'with keep-around refs' do
- let(:sha) { SeedRepo::Commit::ID }
- let(:keep_around_ref) { "refs/keep-around/#{sha}" }
- let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+ context 'with keep-around refs' do
+ let(:sha) { SeedRepo::Commit::ID }
+ let(:keep_around_ref) { "refs/keep-around/#{sha}" }
+ let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
- before do
- repository.rugged.references.create(keep_around_ref, sha, force: true)
- repository.rugged.references.create(tmp_ref, sha, force: true)
- end
+ before do
+ repository.rugged.references.create(keep_around_ref, sha, force: true)
+ repository.rugged.references.create(tmp_ref, sha, force: true)
+ end
- it 'includes the temporary and keep-around refs' do
- subject
+ it 'includes the temporary and keep-around refs' do
+ subject
- expect(refs(new_repository.path)).to include(keep_around_ref)
- expect(refs(new_repository.path)).to include(tmp_ref)
+ expect(refs(new_repository.path)).to include(keep_around_ref)
+ expect(refs(new_repository.path)).to include(tmp_ref)
+ end
end
end
+
+ context 'with gitaly enabled' do
+ it_behaves_like 'repository mirror fecthing'
+ end
+
+ context 'with gitaly enabled', :skip_gitaly_mock do
+ it_behaves_like 'repository mirror fecthing'
+ end
end
describe '#remote_tags' do
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index 69c6f054016..9d540446532 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -31,4 +31,17 @@ describe Gitlab::GitalyClient::RemoteService do
expect(client.remove_remote(remote_name)).to be(true)
end
end
+
+ describe '#fetch_internal_remote' do
+ let(:remote_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+
+ it 'sends an fetch_internal_remote message and returns the result value' do
+ expect_any_instance_of(Gitaly::RemoteService::Stub)
+ .to receive(:fetch_internal_remote)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(result: true))
+
+ expect(client.fetch_internal_remote(remote_repository)).to be(true)
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index f0752649121..7580b62cfc0 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6465,78 +6465,100 @@
}
}
],
- "statuses": [
- {
- "id": 71,
- "project_id": 5,
- "status": "failed",
- "finished_at": "2016-03-29T06:28:12.630Z",
- "trace": null,
- "created_at": "2016-03-22T15:20:35.772Z",
- "updated_at": "2016-03-29T06:28:12.634Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 36,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": null
- },
- "artifacts_metadata": {
- "url": null
- },
- "erased_by_id": null,
- "erased_at": null,
- "type": "Ci::Build",
- "token": "abcd"
- },
- {
- "id": 72,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Porro ea qui ut dolores. Labore ab nemo explicabo aspernatur quis voluptates corporis. Et quasi delectus est sit aperiam perspiciatis asperiores. Repudiandae cum aut consectetur accusantium officia sunt.\n\nQuidem dolore iusto quaerat ut aut inventore et molestiae. Libero voluptates atque nemo qui. Nulla temporibus ipsa similique facere.\n\nAliquam ipsam perferendis qui fugit accusantium omnis id voluptatum. Dignissimos aliquid dicta eos voluptatem assumenda quia. Sed autem natus unde dolor et non nisi et. Consequuntur nihil consequatur rerum est.\n\nSimilique neque est iste ducimus qui fuga cupiditate. Libero autem est aut fuga. Consectetur natus quis non ducimus ut dolore. Magni voluptatibus eius et maxime aut.\n\nAd officiis tempore voluptate vitae corrupti explicabo labore est. Consequatur expedita et sunt nihil aut. Deleniti porro iusto molestiae et beatae.\n\nDeleniti modi nulla qui et labore sequi corrupti. Qui voluptatem assumenda eum cupiditate et. Nesciunt ipsam ut ea possimus eum. Consectetur quidem suscipit atque dolore itaque voluptatibus et cupiditate.",
- "created_at": "2016-03-22T15:20:35.777Z",
- "updated_at": "2016-03-22T15:20:35.777Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 36,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts.zip"
- },
- "artifacts_metadata": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts_metadata.gz"
- },
- "erased_by_id": null,
- "erased_at": null
+ "stages": [
+ {
+ "id": 11,
+ "project_id": 5,
+ "pipeline_id": 36,
+ "name": "test",
+ "status": 1,
+ "created_at": "2016-03-22T15:44:44.772Z",
+ "updated_at": "2016-03-29T06:44:44.634Z",
+ "statuses": [
+ {
+ "id": 71,
+ "project_id": 5,
+ "status": "failed",
+ "finished_at": "2016-03-29T06:28:12.630Z",
+ "trace": null,
+ "created_at": "2016-03-22T15:20:35.772Z",
+ "updated_at": "2016-03-29T06:28:12.634Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 36,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 1",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "stage_id": 11,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": null
+ },
+ "artifacts_metadata": {
+ "url": null
+ },
+ "erased_by_id": null,
+ "erased_at": null,
+ "type": "Ci::Build",
+ "token": "abcd"
+ },
+ {
+ "id": 72,
+ "project_id": 5,
+ "status": "success",
+ "finished_at": null,
+ "trace": "Porro ea qui ut dolores. Labore ab nemo explicabo aspernatur quis voluptates corporis. Et quasi delectus est sit aperiam perspiciatis asperiores. Repudiandae cum aut consectetur accusantium officia sunt.\n\nQuidem dolore iusto quaerat ut aut inventore et molestiae. Libero voluptates atque nemo qui. Nulla temporibus ipsa similique facere.\n\nAliquam ipsam perferendis qui fugit accusantium omnis id voluptatum. Dignissimos aliquid dicta eos voluptatem assumenda quia. Sed autem natus unde dolor et non nisi et. Consequuntur nihil consequatur rerum est.\n\nSimilique neque est iste ducimus qui fuga cupiditate. Libero autem est aut fuga. Consectetur natus quis non ducimus ut dolore. Magni voluptatibus eius et maxime aut.\n\nAd officiis tempore voluptate vitae corrupti explicabo labore est. Consequatur expedita et sunt nihil aut. Deleniti porro iusto molestiae et beatae.\n\nDeleniti modi nulla qui et labore sequi corrupti. Qui voluptatem assumenda eum cupiditate et. Nesciunt ipsam ut ea possimus eum. Consectetur quidem suscipit atque dolore itaque voluptatibus et cupiditate.",
+ "created_at": "2016-03-22T15:20:35.777Z",
+ "updated_at": "2016-03-22T15:20:35.777Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 36,
+ "commands": "$ deploy command",
+ "job_id": null,
+ "name": "test build 2",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "deploy",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "stage_id": 12,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts.zip"
+ },
+ "artifacts_metadata": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts_metadata.gz"
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ }
+ ]
+ },
+ {
+ "id": 12,
+ "project_id": 5,
+ "pipeline_id": 36,
+ "name": "deploy",
+ "status": 2,
+ "created_at": "2016-03-22T15:45:45.772Z",
+ "updated_at": "2016-03-29T06:45:45.634Z"
}
]
},
@@ -6556,76 +6578,87 @@
"started_at": null,
"finished_at": null,
"duration": null,
- "statuses": [
- {
- "id": 74,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Ad ut quod repudiandae iste dolor doloribus. Adipisci consequuntur deserunt omnis quasi eveniet et sed fugit. Aut nemo omnis molestiae impedit ex consequatur ducimus. Voluptatum exercitationem quia aut est et hic dolorem.\n\nQuasi repellendus et eaque magni eum facilis. Dolorem aperiam nam nihil pariatur praesentium ad aliquam. Commodi enim et eos tenetur. Odio voluptatibus laboriosam mollitia rerum exercitationem magnam consequuntur. Tenetur ea vel eum corporis.\n\nVoluptatibus optio in aliquid est voluptates. Ad a ut ab placeat vero blanditiis. Earum aspernatur quia beatae expedita voluptatem dignissimos provident. Quis minima id nemo ut aut est veritatis provident.\n\nRerum voluptatem quidem eius maiores magnam veniam. Voluptatem aperiam aut voluptate et nulla deserunt voluptas. Quaerat aut accusantium laborum est dolorem architecto reiciendis. Aliquam asperiores doloribus omnis maxime enim nesciunt. Eum aut rerum repellendus debitis et ut eius.\n\nQuaerat assumenda ea sit consequatur autem in. Cum eligendi voluptatem quo sed. Ut fuga iusto cupiditate autem sint.\n\nOfficia totam officiis architecto corporis molestiae amet ut. Tempora sed dolorum rerum omnis voluptatem accusantium sit eum. Quia debitis ipsum quidem aliquam inventore sunt consequatur qui.",
- "created_at": "2016-03-22T15:20:35.846Z",
- "updated_at": "2016-03-22T15:20:35.846Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 37,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts.zip"
- },
- "artifacts_metadata": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts_metadata.gz"
- },
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 73,
- "project_id": 5,
- "status": "canceled",
- "finished_at": null,
- "trace": null,
- "created_at": "2016-03-22T15:20:35.842Z",
- "updated_at": "2016-03-22T15:20:35.842Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 37,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": null
- },
- "artifacts_metadata": {
- "url": null
- },
- "erased_by_id": null,
- "erased_at": null
+ "stages": [
+ {
+ "id": 21,
+ "project_id": 5,
+ "pipeline_id": 37,
+ "name": "test",
+ "status": 1,
+ "created_at": "2016-03-22T15:44:44.772Z",
+ "updated_at": "2016-03-29T06:44:44.634Z",
+ "statuses": [
+ {
+ "id": 74,
+ "project_id": 5,
+ "status": "success",
+ "finished_at": null,
+ "trace": "Ad ut quod repudiandae iste dolor doloribus. Adipisci consequuntur deserunt omnis quasi eveniet et sed fugit. Aut nemo omnis molestiae impedit ex consequatur ducimus. Voluptatum exercitationem quia aut est et hic dolorem.\n\nQuasi repellendus et eaque magni eum facilis. Dolorem aperiam nam nihil pariatur praesentium ad aliquam. Commodi enim et eos tenetur. Odio voluptatibus laboriosam mollitia rerum exercitationem magnam consequuntur. Tenetur ea vel eum corporis.\n\nVoluptatibus optio in aliquid est voluptates. Ad a ut ab placeat vero blanditiis. Earum aspernatur quia beatae expedita voluptatem dignissimos provident. Quis minima id nemo ut aut est veritatis provident.\n\nRerum voluptatem quidem eius maiores magnam veniam. Voluptatem aperiam aut voluptate et nulla deserunt voluptas. Quaerat aut accusantium laborum est dolorem architecto reiciendis. Aliquam asperiores doloribus omnis maxime enim nesciunt. Eum aut rerum repellendus debitis et ut eius.\n\nQuaerat assumenda ea sit consequatur autem in. Cum eligendi voluptatem quo sed. Ut fuga iusto cupiditate autem sint.\n\nOfficia totam officiis architecto corporis molestiae amet ut. Tempora sed dolorum rerum omnis voluptatem accusantium sit eum. Quia debitis ipsum quidem aliquam inventore sunt consequatur qui.",
+ "created_at": "2016-03-22T15:20:35.846Z",
+ "updated_at": "2016-03-22T15:20:35.846Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 37,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 2",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts.zip"
+ },
+ "artifacts_metadata": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts_metadata.gz"
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ },
+ {
+ "id": 73,
+ "project_id": 5,
+ "status": "canceled",
+ "finished_at": null,
+ "trace": null,
+ "created_at": "2016-03-22T15:20:35.842Z",
+ "updated_at": "2016-03-22T15:20:35.842Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 37,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 1",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": null
+ },
+ "artifacts_metadata": {
+ "url": null
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ }
+ ]
}
]
},
@@ -6645,76 +6678,87 @@
"started_at": null,
"finished_at": null,
"duration": null,
- "statuses": [
- {
- "id": 76,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Et rerum quia ea cumque ut modi non. Libero eaque ipsam architecto maiores expedita deleniti. Ratione quia qui est id.\n\nQuod sit officiis sed unde inventore veniam quisquam velit. Ea harum cum quibusdam quisquam minima quo possimus non. Temporibus itaque aliquam aut rerum veritatis at.\n\nMagnam ipsum eius recusandae qui quis sit maiores eum. Et animi iusto aut itaque. Doloribus harum deleniti nobis accusantium et libero.\n\nRerum fuga perferendis magni commodi officiis id repudiandae. Consequatur ratione consequatur suscipit facilis sunt iure est dicta. Qui unde quasi facilis et quae nesciunt. Magnam iste et nobis officiis tenetur. Aspernatur quo et temporibus non in.\n\nNisi rerum velit est ad enim sint molestiae consequuntur. Quaerat nisi nesciunt quasi officiis. Possimus non blanditiis laborum quos.\n\nRerum laudantium facere animi qui. Ipsa est iusto magnam nihil. Enim omnis occaecati non dignissimos ut recusandae eum quasi. Qui maxime dolor et nemo voluptates incidunt quia.",
- "created_at": "2016-03-22T15:20:35.882Z",
- "updated_at": "2016-03-22T15:20:35.882Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 38,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts.zip"
- },
- "artifacts_metadata": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts_metadata.gz"
- },
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 75,
- "project_id": 5,
- "status": "failed",
- "finished_at": null,
- "trace": "Sed et iste recusandae dicta corporis. Sunt alias porro fugit sunt. Fugiat omnis nihil dignissimos aperiam explicabo doloremque sit aut. Harum fugit expedita quia rerum ut consequatur laboriosam aliquam.\n\nNatus libero ut ut tenetur earum. Tempora omnis autem omnis et libero dolores illum autem. Deleniti eos sunt mollitia ipsam. Cum dolor repellendus dolorum sequi officia. Ullam sunt in aut pariatur excepturi.\n\nDolor nihil debitis et est eos. Cumque eos eum saepe ducimus autem. Alias architecto consequatur aut pariatur possimus. Aut quos aut incidunt quam velit et. Quas voluptatum ad dolorum dignissimos.\n\nUt voluptates consectetur illo et. Est commodi accusantium vel quo. Eos qui fugiat soluta porro.\n\nRatione possimus alias vel maxime sint totam est repellat. Ipsum corporis eos sint voluptatem eos odit. Temporibus libero nulla harum eligendi labore similique ratione magnam. Suscipit sequi in omnis neque.\n\nLaudantium dolor amet omnis placeat mollitia aut molestiae. Aut rerum similique ipsum quod illo quas unde. Sunt aut veritatis eos omnis porro. Rem veritatis mollitia praesentium dolorem. Consequatur sequi ad cumque earum omnis quia necessitatibus.",
- "created_at": "2016-03-22T15:20:35.864Z",
- "updated_at": "2016-03-22T15:20:35.864Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 38,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts.zip"
- },
- "artifacts_metadata": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts_metadata.gz"
- },
- "erased_by_id": null,
- "erased_at": null
+ "stages": [
+ {
+ "id": 22,
+ "project_id": 5,
+ "pipeline_id": 38,
+ "name": "test",
+ "status": 1,
+ "created_at": "2016-03-22T15:44:44.772Z",
+ "updated_at": "2016-03-29T06:44:44.634Z",
+ "statuses": [
+ {
+ "id": 76,
+ "project_id": 5,
+ "status": "success",
+ "finished_at": null,
+ "trace": "Et rerum quia ea cumque ut modi non. Libero eaque ipsam architecto maiores expedita deleniti. Ratione quia qui est id.\n\nQuod sit officiis sed unde inventore veniam quisquam velit. Ea harum cum quibusdam quisquam minima quo possimus non. Temporibus itaque aliquam aut rerum veritatis at.\n\nMagnam ipsum eius recusandae qui quis sit maiores eum. Et animi iusto aut itaque. Doloribus harum deleniti nobis accusantium et libero.\n\nRerum fuga perferendis magni commodi officiis id repudiandae. Consequatur ratione consequatur suscipit facilis sunt iure est dicta. Qui unde quasi facilis et quae nesciunt. Magnam iste et nobis officiis tenetur. Aspernatur quo et temporibus non in.\n\nNisi rerum velit est ad enim sint molestiae consequuntur. Quaerat nisi nesciunt quasi officiis. Possimus non blanditiis laborum quos.\n\nRerum laudantium facere animi qui. Ipsa est iusto magnam nihil. Enim omnis occaecati non dignissimos ut recusandae eum quasi. Qui maxime dolor et nemo voluptates incidunt quia.",
+ "created_at": "2016-03-22T15:20:35.882Z",
+ "updated_at": "2016-03-22T15:20:35.882Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 38,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 2",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts.zip"
+ },
+ "artifacts_metadata": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts_metadata.gz"
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ },
+ {
+ "id": 75,
+ "project_id": 5,
+ "status": "failed",
+ "finished_at": null,
+ "trace": "Sed et iste recusandae dicta corporis. Sunt alias porro fugit sunt. Fugiat omnis nihil dignissimos aperiam explicabo doloremque sit aut. Harum fugit expedita quia rerum ut consequatur laboriosam aliquam.\n\nNatus libero ut ut tenetur earum. Tempora omnis autem omnis et libero dolores illum autem. Deleniti eos sunt mollitia ipsam. Cum dolor repellendus dolorum sequi officia. Ullam sunt in aut pariatur excepturi.\n\nDolor nihil debitis et est eos. Cumque eos eum saepe ducimus autem. Alias architecto consequatur aut pariatur possimus. Aut quos aut incidunt quam velit et. Quas voluptatum ad dolorum dignissimos.\n\nUt voluptates consectetur illo et. Est commodi accusantium vel quo. Eos qui fugiat soluta porro.\n\nRatione possimus alias vel maxime sint totam est repellat. Ipsum corporis eos sint voluptatem eos odit. Temporibus libero nulla harum eligendi labore similique ratione magnam. Suscipit sequi in omnis neque.\n\nLaudantium dolor amet omnis placeat mollitia aut molestiae. Aut rerum similique ipsum quod illo quas unde. Sunt aut veritatis eos omnis porro. Rem veritatis mollitia praesentium dolorem. Consequatur sequi ad cumque earum omnis quia necessitatibus.",
+ "created_at": "2016-03-22T15:20:35.864Z",
+ "updated_at": "2016-03-22T15:20:35.864Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 38,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 1",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts.zip"
+ },
+ "artifacts_metadata": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts_metadata.gz"
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ }
+ ]
}
]
},
@@ -6734,76 +6778,87 @@
"started_at": null,
"finished_at": null,
"duration": null,
- "statuses": [
- {
- "id": 78,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Dolorem deserunt quas quia error hic quo cum vel. Natus voluptatem cumque expedita numquam odit. Eos expedita nostrum corporis consequatur est recusandae.\n\nCulpa blanditiis rerum repudiandae alias voluptatem. Velit iusto est ullam consequatur doloribus porro. Corporis voluptas consectetur est veniam et quia quae.\n\nEt aut magni fuga nesciunt officiis molestias. Quaerat et nam necessitatibus qui rerum. Architecto quia officiis voluptatem laborum est recusandae. Quasi ducimus soluta odit necessitatibus labore numquam dignissimos. Quia facere sint temporibus inventore sunt nihil saepe dolorum.\n\nFacere dolores quis dolores a. Est minus nostrum nihil harum. Earum laborum et ipsum unde neque sit nemo. Corrupti est consequatur minima fugit. Illum voluptatem illo error ducimus officia qui debitis.\n\nDignissimos porro a autem harum aut. Aut id reprehenderit et exercitationem. Est et quisquam ipsa temporibus molestiae. Architecto natus dolore qui fugiat incidunt. Autem odit veniam excepturi et voluptatibus culpa ipsum eos.\n\nAmet quo quisquam dignissimos soluta modi dolores. Sint omnis eius optio corporis dolor. Eligendi animi porro quia placeat ut.",
- "created_at": "2016-03-22T15:20:35.927Z",
- "updated_at": "2016-03-22T15:20:35.927Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 39,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts.zip"
- },
- "artifacts_metadata": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts_metadata.gz"
- },
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 77,
- "project_id": 5,
- "status": "failed",
- "finished_at": null,
- "trace": "Rerum ut et suscipit est perspiciatis. Inventore debitis cum eius vitae. Ex incidunt id velit aut quo nisi. Laboriosam repellat deserunt eius reiciendis architecto et. Est harum quos nesciunt nisi consectetur.\n\nAlias esse omnis sint officia est consequatur in nobis. Dignissimos dolorum vel eligendi nesciunt dolores sit. Veniam mollitia ducimus et exercitationem molestiae libero sed. Atque omnis debitis laudantium voluptatibus qui. Repellendus tempore est commodi pariatur.\n\nExpedita voluptate illum est alias non. Modi nesciunt ab assumenda laborum nulla consequatur molestias doloremque. Magnam quod officia vel explicabo accusamus ut voluptatem incidunt. Rerum ut aliquid ullam saepe. Est eligendi debitis beatae blanditiis reiciendis.\n\nQui fuga sit dolores libero maiores et suscipit. Consectetur asperiores omnis minima impedit eos fugiat. Similique omnis nisi sed vero inventore ipsum aliquam exercitationem.\n\nBlanditiis magni iure dolorum omnis ratione delectus molestiae. Atque officia dolor voluptatem culpa quod. Incidunt suscipit quidem possimus veritatis non vel. Iusto aliquid et id quia quasi.\n\nVel facere velit blanditiis incidunt cupiditate sed maiores consequuntur. Quasi quia dicta consequuntur et quia voluptatem iste id. Incidunt et rerum fuga esse sint.",
- "created_at": "2016-03-22T15:20:35.905Z",
- "updated_at": "2016-03-22T15:20:35.905Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 39,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts.zip"
- },
- "artifacts_metadata": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts_metadata.gz"
- },
- "erased_by_id": null,
- "erased_at": null
+ "stages": [
+ {
+ "id": 23,
+ "project_id": 5,
+ "pipeline_id": 39,
+ "name": "test",
+ "status": 1,
+ "created_at": "2016-03-22T15:44:44.772Z",
+ "updated_at": "2016-03-29T06:44:44.634Z",
+ "statuses": [
+ {
+ "id": 78,
+ "project_id": 5,
+ "status": "success",
+ "finished_at": null,
+ "trace": "Dolorem deserunt quas quia error hic quo cum vel. Natus voluptatem cumque expedita numquam odit. Eos expedita nostrum corporis consequatur est recusandae.\n\nCulpa blanditiis rerum repudiandae alias voluptatem. Velit iusto est ullam consequatur doloribus porro. Corporis voluptas consectetur est veniam et quia quae.\n\nEt aut magni fuga nesciunt officiis molestias. Quaerat et nam necessitatibus qui rerum. Architecto quia officiis voluptatem laborum est recusandae. Quasi ducimus soluta odit necessitatibus labore numquam dignissimos. Quia facere sint temporibus inventore sunt nihil saepe dolorum.\n\nFacere dolores quis dolores a. Est minus nostrum nihil harum. Earum laborum et ipsum unde neque sit nemo. Corrupti est consequatur minima fugit. Illum voluptatem illo error ducimus officia qui debitis.\n\nDignissimos porro a autem harum aut. Aut id reprehenderit et exercitationem. Est et quisquam ipsa temporibus molestiae. Architecto natus dolore qui fugiat incidunt. Autem odit veniam excepturi et voluptatibus culpa ipsum eos.\n\nAmet quo quisquam dignissimos soluta modi dolores. Sint omnis eius optio corporis dolor. Eligendi animi porro quia placeat ut.",
+ "created_at": "2016-03-22T15:20:35.927Z",
+ "updated_at": "2016-03-22T15:20:35.927Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 39,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 2",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts.zip"
+ },
+ "artifacts_metadata": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts_metadata.gz"
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ },
+ {
+ "id": 77,
+ "project_id": 5,
+ "status": "failed",
+ "finished_at": null,
+ "trace": "Rerum ut et suscipit est perspiciatis. Inventore debitis cum eius vitae. Ex incidunt id velit aut quo nisi. Laboriosam repellat deserunt eius reiciendis architecto et. Est harum quos nesciunt nisi consectetur.\n\nAlias esse omnis sint officia est consequatur in nobis. Dignissimos dolorum vel eligendi nesciunt dolores sit. Veniam mollitia ducimus et exercitationem molestiae libero sed. Atque omnis debitis laudantium voluptatibus qui. Repellendus tempore est commodi pariatur.\n\nExpedita voluptate illum est alias non. Modi nesciunt ab assumenda laborum nulla consequatur molestias doloremque. Magnam quod officia vel explicabo accusamus ut voluptatem incidunt. Rerum ut aliquid ullam saepe. Est eligendi debitis beatae blanditiis reiciendis.\n\nQui fuga sit dolores libero maiores et suscipit. Consectetur asperiores omnis minima impedit eos fugiat. Similique omnis nisi sed vero inventore ipsum aliquam exercitationem.\n\nBlanditiis magni iure dolorum omnis ratione delectus molestiae. Atque officia dolor voluptatem culpa quod. Incidunt suscipit quidem possimus veritatis non vel. Iusto aliquid et id quia quasi.\n\nVel facere velit blanditiis incidunt cupiditate sed maiores consequuntur. Quasi quia dicta consequuntur et quia voluptatem iste id. Incidunt et rerum fuga esse sint.",
+ "created_at": "2016-03-22T15:20:35.905Z",
+ "updated_at": "2016-03-22T15:20:35.905Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 39,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 1",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts.zip"
+ },
+ "artifacts_metadata": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts_metadata.gz"
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ }
+ ]
}
]
},
@@ -6823,76 +6878,87 @@
"started_at": null,
"finished_at": null,
"duration": null,
- "statuses": [
- {
- "id": 79,
- "project_id": 5,
- "status": "failed",
- "finished_at": "2016-03-29T06:28:12.695Z",
- "trace": "Sed culpa est et facere saepe vel id ab. Quas temporibus aut similique dolorem consequatur corporis aut praesentium. Cum officia molestiae sit earum excepturi.\n\nSint possimus aut ratione quia. Quis nesciunt ratione itaque illo. Tenetur est dolor assumenda possimus voluptatem quia minima. Accusamus reprehenderit ut et itaque non reiciendis incidunt.\n\nRerum suscipit quibusdam dolore nam omnis. Consequatur ipsa nihil ut enim blanditiis delectus. Nulla quis hic occaecati mollitia qui placeat. Quo rerum sed perferendis a accusantium consequatur commodi ut. Sit quae et cumque vel eius tempora nostrum.\n\nUllam dolorem et itaque sint est. Ea molestias quia provident dolorem vitae error et et. Ea expedita officiis iste non. Qui vitae odit saepe illum. Dolores enim ratione deserunt tempore expedita amet non neque.\n\nEligendi asperiores voluptatibus omnis repudiandae expedita distinctio qui aliquid. Autem aut doloremque distinctio ab. Nostrum sapiente repudiandae aspernatur ea et quae voluptas. Officiis perspiciatis nisi laudantium asperiores error eligendi ab. Eius quia amet magni omnis exercitationem voluptatum et.\n\nVoluptatem ullam labore quas dicta est ex voluptas. Pariatur ea modi voluptas consequatur dolores perspiciatis similique. Numquam in distinctio perspiciatis ut qui earum. Quidem omnis mollitia facere aut beatae. Ea est iure et voluptatem.",
- "created_at": "2016-03-22T15:20:35.950Z",
- "updated_at": "2016-03-29T06:28:12.696Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 40,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": null
- },
- "artifacts_metadata": {
- "url": null
- },
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 80,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Impedit et optio nemo ipsa. Non ad non quis ut sequi laudantium omnis velit. Corporis a enim illo eos. Quia totam tempore inventore ad est.\n\nNihil recusandae cupiditate eaque voluptatem molestias sint. Consequatur id voluptatem cupiditate harum. Consequuntur iusto quaerat reiciendis aut autem libero est. Quisquam dolores veritatis rerum et sint maxime ullam libero. Id quas porro ut perspiciatis rem amet vitae.\n\nNemo inventore minus blanditiis magnam. Modi consequuntur nostrum aut voluptatem ex. Sunt rerum rem optio mollitia qui aliquam officiis officia. Aliquid eos et id aut minus beatae reiciendis.\n\nDolores non in temporibus dicta. Fugiat voluptatem est aspernatur expedita voluptatum nam qui. Quia et eligendi sit quae sint tempore exercitationem eos. Est sapiente corrupti quidem at. Qui magni odio repudiandae saepe tenetur optio dolore.\n\nEos placeat soluta at dolorem adipisci provident. Quo commodi id reprehenderit possimus quo tenetur. Ipsum et quae eligendi laborum. Et qui nesciunt at quasi quidem voluptatem cum rerum. Excepturi non facilis aut sunt vero sed.\n\nQui explicabo ratione ut eligendi recusandae. Quis quasi quas molestiae consequatur voluptatem et voluptatem. Ex repellat saepe occaecati aperiam ea eveniet dignissimos facilis.",
- "created_at": "2016-03-22T15:20:35.966Z",
- "updated_at": "2016-03-22T15:20:35.966Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 40,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "artifacts_file": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts.zip"
- },
- "artifacts_metadata": {
- "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts_metadata.gz"
- },
- "erased_by_id": null,
- "erased_at": null
+ "stages": [
+ {
+ "id": 24,
+ "project_id": 5,
+ "pipeline_id": 40,
+ "name": "test",
+ "status": 1,
+ "created_at": "2016-03-22T15:44:44.772Z",
+ "updated_at": "2016-03-29T06:44:44.634Z",
+ "statuses": [
+ {
+ "id": 79,
+ "project_id": 5,
+ "status": "failed",
+ "finished_at": "2016-03-29T06:28:12.695Z",
+ "trace": "Sed culpa est et facere saepe vel id ab. Quas temporibus aut similique dolorem consequatur corporis aut praesentium. Cum officia molestiae sit earum excepturi.\n\nSint possimus aut ratione quia. Quis nesciunt ratione itaque illo. Tenetur est dolor assumenda possimus voluptatem quia minima. Accusamus reprehenderit ut et itaque non reiciendis incidunt.\n\nRerum suscipit quibusdam dolore nam omnis. Consequatur ipsa nihil ut enim blanditiis delectus. Nulla quis hic occaecati mollitia qui placeat. Quo rerum sed perferendis a accusantium consequatur commodi ut. Sit quae et cumque vel eius tempora nostrum.\n\nUllam dolorem et itaque sint est. Ea molestias quia provident dolorem vitae error et et. Ea expedita officiis iste non. Qui vitae odit saepe illum. Dolores enim ratione deserunt tempore expedita amet non neque.\n\nEligendi asperiores voluptatibus omnis repudiandae expedita distinctio qui aliquid. Autem aut doloremque distinctio ab. Nostrum sapiente repudiandae aspernatur ea et quae voluptas. Officiis perspiciatis nisi laudantium asperiores error eligendi ab. Eius quia amet magni omnis exercitationem voluptatum et.\n\nVoluptatem ullam labore quas dicta est ex voluptas. Pariatur ea modi voluptas consequatur dolores perspiciatis similique. Numquam in distinctio perspiciatis ut qui earum. Quidem omnis mollitia facere aut beatae. Ea est iure et voluptatem.",
+ "created_at": "2016-03-22T15:20:35.950Z",
+ "updated_at": "2016-03-29T06:28:12.696Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 40,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 1",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": null
+ },
+ "artifacts_metadata": {
+ "url": null
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ },
+ {
+ "id": 80,
+ "project_id": 5,
+ "status": "success",
+ "finished_at": null,
+ "trace": "Impedit et optio nemo ipsa. Non ad non quis ut sequi laudantium omnis velit. Corporis a enim illo eos. Quia totam tempore inventore ad est.\n\nNihil recusandae cupiditate eaque voluptatem molestias sint. Consequatur id voluptatem cupiditate harum. Consequuntur iusto quaerat reiciendis aut autem libero est. Quisquam dolores veritatis rerum et sint maxime ullam libero. Id quas porro ut perspiciatis rem amet vitae.\n\nNemo inventore minus blanditiis magnam. Modi consequuntur nostrum aut voluptatem ex. Sunt rerum rem optio mollitia qui aliquam officiis officia. Aliquid eos et id aut minus beatae reiciendis.\n\nDolores non in temporibus dicta. Fugiat voluptatem est aspernatur expedita voluptatum nam qui. Quia et eligendi sit quae sint tempore exercitationem eos. Est sapiente corrupti quidem at. Qui magni odio repudiandae saepe tenetur optio dolore.\n\nEos placeat soluta at dolorem adipisci provident. Quo commodi id reprehenderit possimus quo tenetur. Ipsum et quae eligendi laborum. Et qui nesciunt at quasi quidem voluptatem cum rerum. Excepturi non facilis aut sunt vero sed.\n\nQui explicabo ratione ut eligendi recusandae. Quis quasi quas molestiae consequatur voluptatem et voluptatem. Ex repellat saepe occaecati aperiam ea eveniet dignissimos facilis.",
+ "created_at": "2016-03-22T15:20:35.966Z",
+ "updated_at": "2016-03-22T15:20:35.966Z",
+ "started_at": null,
+ "runner_id": null,
+ "coverage": null,
+ "commit_id": 40,
+ "commands": "$ build command",
+ "job_id": null,
+ "name": "test build 2",
+ "deploy": false,
+ "options": null,
+ "allow_failure": false,
+ "stage": "test",
+ "trigger_request_id": null,
+ "stage_idx": 1,
+ "tag": null,
+ "ref": "master",
+ "user_id": null,
+ "target_url": null,
+ "description": null,
+ "artifacts_file": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts.zip"
+ },
+ "artifacts_metadata": {
+ "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts_metadata.gz"
+ },
+ "erased_by_id": null,
+ "erased_at": null
+ }
+ ]
}
]
}
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 0ab3afd0074..9dfd879a1bc 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -179,6 +179,32 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
end
+
+ context 'when restoring hierarchy of pipeline, stages and jobs' do
+ it 'restores pipelines' do
+ expect(Ci::Pipeline.all.count).to be 5
+ end
+
+ it 'restores pipeline stages' do
+ expect(Ci::Stage.all.count).to be 6
+ end
+
+ it 'correctly restores association between stage and a pipeline' do
+ expect(Ci::Stage.all).to all(have_attributes(pipeline_id: a_value > 0))
+ end
+
+ it 'restores statuses' do
+ expect(CommitStatus.all.count).to be 10
+ end
+
+ it 'correctly restores association between a stage and a job' do
+ expect(CommitStatus.all).to all(have_attributes(stage_id: a_value > 0))
+ end
+
+ it 'correctly restores association between a pipeline and a job' do
+ expect(CommitStatus.all).to all(have_attributes(pipeline_id: a_value > 0))
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 6faf3d82981..08e5bbbd400 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -109,12 +109,20 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json['merge_requests'].first['notes'].first['author']).not_to be_empty
end
+ it 'has pipeline stages' do
+ expect(saved_project_json.dig('pipelines', 0, 'stages')).not_to be_empty
+ end
+
it 'has pipeline statuses' do
- expect(saved_project_json['pipelines'].first['statuses']).not_to be_empty
+ expect(saved_project_json.dig('pipelines', 0, 'stages', 0, 'statuses')).not_to be_empty
end
it 'has pipeline builds' do
- expect(saved_project_json['pipelines'].first['statuses'].count { |hash| hash['type'] == 'Ci::Build' }).to eq(1)
+ builds_count = saved_project_json
+ .dig('pipelines', 0, 'stages', 0, 'statuses')
+ .count { |hash| hash['type'] == 'Ci::Build' }
+
+ expect(builds_count).to eq(1)
end
it 'has no when YML attributes but only the DB column' do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ec8fa99e0da..ec577903eb5 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -459,6 +459,7 @@ Project:
- delete_error
- merge_requests_ff_only_enabled
- merge_requests_rebase_enabled
+- jobs_cache_index
Author:
- name
ProjectFeature:
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 68a57826647..8b54d72d6f7 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Regex do
it { is_expected.not_to match('?gitlab') }
end
- describe '.environment_slug_regex' do
+ describe '.environment_name_regex' do
subject { described_class.environment_name_regex }
it { is_expected.to match('foo') }
@@ -24,6 +24,7 @@ describe Gitlab::Regex do
it { is_expected.to match('foo.1') }
it { is_expected.not_to match('9&foo') }
it { is_expected.not_to match('foo-^') }
+ it { is_expected.not_to match('!!()()') }
end
describe '.environment_slug_regex' do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 81d9e6a8f82..ffd2d2c7afc 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -4,6 +4,7 @@ require 'stringio'
describe Gitlab::Shell do
set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
let(:gitlab_projects) { double('gitlab_projects') }
@@ -201,8 +202,6 @@ describe Gitlab::Shell do
end
shared_examples 'fetch_remote' do |gitaly_on|
- let(:repository) { project.repository }
-
def fetch_remote(ssh_auth = nil)
gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth)
end
@@ -325,6 +324,23 @@ describe Gitlab::Shell do
describe '#fetch_remote gitaly' do
it_should_behave_like 'fetch_remote', true
+
+ context 'gitaly call' do
+ let(:remote_name) { 'remote-name' }
+ let(:ssh_auth) { double(:ssh_auth) }
+
+ subject do
+ gitlab_shell.fetch_remote(repository.raw_repository, remote_name,
+ forced: true, no_tags: true, ssh_auth: ssh_auth)
+ end
+
+ it 'passes the correct params to the gitaly service' do
+ expect(repository.gitaly_repository_client).to receive(:fetch_remote)
+ .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, timeout: timeout)
+
+ subject
+ end
+ end
end
describe '#import_repository' do
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index ecb4034ec8b..f65e41dfea3 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -50,6 +50,30 @@ describe GoogleApi::CloudPlatform::Client do
end
end
+ describe '#projects_list' do
+ subject { client.projects_list }
+ let(:projects) { double }
+
+ before do
+ allow_any_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService)
+ .to receive(:fetch_all).and_return(projects)
+ end
+
+ it { is_expected.to eq(projects) }
+ end
+
+ describe '#projects_get_billing_info' do
+ subject { client.projects_get_billing_info('project') }
+ let(:billing_info) { double }
+
+ before do
+ allow_any_instance_of(Google::Apis::CloudbillingV1::CloudbillingService)
+ .to receive(:get_project_billing_info).and_return(billing_info)
+ end
+
+ it { is_expected.to eq(billing_info) }
+ end
+
describe '#projects_zones_clusters_get' do
subject { client.projects_zones_clusters_get(spy, spy, spy) }
let(:gke_cluster) { double }
diff --git a/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb
new file mode 100644
index 00000000000..df0015b6dd3
--- /dev/null
+++ b/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb
@@ -0,0 +1,312 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb')
+
+describe MigrateKubernetesServiceToNewClustersArchitectures, :migration do
+ context 'when unique KubernetesService exists' do
+ shared_examples 'KubernetesService migration' do
+ let(:sample_num) { 2 }
+
+ let(:projects) do
+ (1..sample_num).each_with_object([]) do |n, array|
+ array << MigrateKubernetesServiceToNewClustersArchitectures::Project.create!
+ end
+ end
+
+ let!(:kubernetes_services) do
+ projects.map do |project|
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ active: active,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"namespace\":\"prod\",\"api_url\":\"https://kubernetes#{project.id}.com\",\"ca_pem\":\"ca_pem#{project.id}\",\"token\":\"token#{project.id}\"}")
+ end
+ end
+
+ it 'migrates the KubernetesService to Platform::Kubernetes' do
+ expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(sample_num)
+
+ projects.each do |project|
+ project.clusters.last.tap do |cluster|
+ expect(cluster.enabled).to eq(active)
+ expect(cluster.platform_kubernetes.api_url).to eq(project.kubernetes_service.api_url)
+ expect(cluster.platform_kubernetes.ca_cert).to eq(project.kubernetes_service.ca_pem)
+ expect(cluster.platform_kubernetes.token).to eq(project.kubernetes_service.token)
+ expect(project.kubernetes_service).not_to be_active
+ end
+ end
+ end
+ end
+
+ context 'when KubernetesService is active' do
+ let(:active) { true }
+
+ it_behaves_like 'KubernetesService migration'
+ end
+ end
+
+ context 'when unique KubernetesService spawned from Service Template' do
+ let(:sample_num) { 2 }
+
+ let(:projects) do
+ (1..sample_num).each_with_object([]) do |n, array|
+ array << MigrateKubernetesServiceToNewClustersArchitectures::Project.create!
+ end
+ end
+
+ let!(:kubernetes_service_template) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ template: true,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"namespace\":\"prod\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}")
+ end
+
+ let!(:kubernetes_services) do
+ projects.map do |project|
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"namespace\":\"prod\",\"api_url\":\"#{kubernetes_service_template.api_url}\",\"ca_pem\":\"#{kubernetes_service_template.ca_pem}\",\"token\":\"#{kubernetes_service_template.token}\"}")
+ end
+ end
+
+ it 'migrates the KubernetesService to Platform::Kubernetes without template' do
+ expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(sample_num)
+
+ projects.each do |project|
+ project.clusters.last.tap do |cluster|
+ expect(cluster.platform_kubernetes.api_url).to eq(project.kubernetes_service.api_url)
+ expect(cluster.platform_kubernetes.ca_cert).to eq(project.kubernetes_service.ca_pem)
+ expect(cluster.platform_kubernetes.token).to eq(project.kubernetes_service.token)
+ expect(project.kubernetes_service).not_to be_active
+ end
+ end
+ end
+ end
+
+ context 'when managed KubernetesService exists' do
+ let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
+
+ let(:cluster) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
+ projects: [project],
+ name: 'sample-cluster',
+ platform_type: :kubernetes,
+ provider_type: :user,
+ platform_kubernetes_attributes: {
+ api_url: 'https://sample.kubernetes.com',
+ ca_cert: 'ca_pem-sample',
+ token: 'token-sample'
+ } )
+ end
+
+ let!(:kubernetes_service) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ active: cluster.enabled,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"api_url\":\"#{cluster.platform_kubernetes.api_url}\"}")
+ end
+
+ it 'does not migrate the KubernetesService and disables the kubernetes_service' do # Because the corresponding Platform::Kubernetes already exists
+ expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }
+
+ kubernetes_service.reload
+ expect(kubernetes_service).not_to be_active
+ end
+ end
+
+ context 'when production cluster has already been existed' do # i.e. There are no environment_scope conflicts
+ let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
+
+ let(:cluster) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
+ projects: [project],
+ name: 'sample-cluster',
+ platform_type: :kubernetes,
+ provider_type: :user,
+ environment_scope: 'production/*',
+ platform_kubernetes_attributes: {
+ api_url: 'https://sample.kubernetes.com',
+ ca_cert: 'ca_pem-sample',
+ token: 'token-sample'
+ } )
+ end
+
+ let!(:kubernetes_service) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ active: true,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"api_url\":\"https://debug.kube.com\"}")
+ end
+
+ it 'migrates the KubernetesService to Platform::Kubernetes' do
+ expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
+
+ kubernetes_service.reload
+ project.clusters.last.tap do |cluster|
+ expect(cluster.environment_scope).to eq('*')
+ expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url)
+ expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem)
+ expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token)
+ expect(kubernetes_service).not_to be_active
+ end
+ end
+ end
+
+ context 'when default cluster has already been existed' do
+ let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
+
+ let!(:cluster) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
+ projects: [project],
+ name: 'sample-cluster',
+ platform_type: :kubernetes,
+ provider_type: :user,
+ environment_scope: '*',
+ platform_kubernetes_attributes: {
+ api_url: 'https://sample.kubernetes.com',
+ ca_cert: 'ca_pem-sample',
+ token: 'token-sample'
+ } )
+ end
+
+ let!(:kubernetes_service) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ active: true,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"api_url\":\"https://debug.kube.com\"}")
+ end
+
+ it 'migrates the KubernetesService to Platform::Kubernetes with dedicated environment_scope' do # Because environment_scope is duplicated
+ expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
+
+ kubernetes_service.reload
+ project.clusters.last.tap do |cluster|
+ expect(cluster.environment_scope).to eq('migrated/*')
+ expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url)
+ expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem)
+ expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token)
+ expect(kubernetes_service).not_to be_active
+ end
+ end
+ end
+
+ context 'when default cluster and migrated cluster has already been existed' do
+ let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
+
+ let!(:cluster) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
+ projects: [project],
+ name: 'sample-cluster',
+ platform_type: :kubernetes,
+ provider_type: :user,
+ environment_scope: '*',
+ platform_kubernetes_attributes: {
+ api_url: 'https://sample.kubernetes.com',
+ ca_cert: 'ca_pem-sample',
+ token: 'token-sample'
+ } )
+ end
+
+ let!(:migrated_cluster) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
+ projects: [project],
+ name: 'sample-cluster',
+ platform_type: :kubernetes,
+ provider_type: :user,
+ environment_scope: 'migrated/*',
+ platform_kubernetes_attributes: {
+ api_url: 'https://sample.kubernetes.com',
+ ca_cert: 'ca_pem-sample',
+ token: 'token-sample'
+ } )
+ end
+
+ let!(:kubernetes_service) do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ active: true,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"api_url\":\"https://debug.kube.com\"}")
+ end
+
+ it 'migrates the KubernetesService to Platform::Kubernetes with dedicated environment_scope' do # Because environment_scope is duplicated
+ expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
+
+ kubernetes_service.reload
+ project.clusters.last.tap do |cluster|
+ expect(cluster.environment_scope).to eq('migrated0/*')
+ expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url)
+ expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem)
+ expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token)
+ expect(kubernetes_service).not_to be_active
+ end
+ end
+ end
+
+ context 'when KubernetesService has nullified parameters' do
+ let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
+
+ before do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ active: false,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{}")
+ end
+
+ it 'does not migrate the KubernetesService and disables the kubernetes_service' do
+ expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }
+
+ expect(project.kubernetes_service).not_to be_active
+ end
+ end
+
+ # Platforms::Kubernetes validates `token` reagdless of the activeness,
+ # whereas KubernetesService validates `token` if only it's activated
+ # However, in this migration file, there are no validations because of the re-defined model class
+ # therefore, we should safely add this raw to Platform::Kubernetes
+ context 'when KubernetesService has empty token' do
+ let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
+
+ before do
+ MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
+ project: project,
+ active: false,
+ category: 'deployment',
+ type: 'KubernetesService',
+ properties: "{\"namespace\":\"prod\",\"api_url\":\"http://111.111.111.111\",\"ca_pem\":\"a\",\"token\":\"\"}")
+ end
+
+ it 'does not migrate the KubernetesService and disables the kubernetes_service' do
+ expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
+
+ project.clusters.last.tap do |cluster|
+ expect(cluster.environment_scope).to eq('*')
+ expect(cluster.platform_kubernetes.namespace).to eq('prod')
+ expect(cluster.platform_kubernetes.api_url).to eq('http://111.111.111.111')
+ expect(cluster.platform_kubernetes.ca_cert).to eq('a')
+ expect(cluster.platform_kubernetes.token).to be_empty
+ expect(project.kubernetes_service).not_to be_active
+ end
+ end
+ end
+
+ context 'when KubernetesService does not exist' do
+ let!(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
+
+ it 'does not migrate the KubernetesService' do
+ expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }
+ end
+ end
+end
diff --git a/spec/migrations/normalize_ldap_extern_uids_spec.rb b/spec/migrations/normalize_ldap_extern_uids_spec.rb
index 262d7742aaf..56a78f52802 100644
--- a/spec/migrations/normalize_ldap_extern_uids_spec.rb
+++ b/spec/migrations/normalize_ldap_extern_uids_spec.rb
@@ -27,11 +27,11 @@ describe NormalizeLdapExternUids, :migration, :sidekiq do
migrate!
expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(5.minutes.from_now.to_f)
expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]])
- expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(10.minutes.from_now.to_f)
expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]])
- expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(30.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(15.minutes.from_now.to_f)
expect(BackgroundMigrationWorker.jobs.size).to eq 3
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 871e8b47650..3eaeeebf97d 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -255,6 +255,42 @@ describe Ci::Build do
end
end
+ describe '#cache' do
+ let(:options) { { cache: { key: "key", paths: ["public"], policy: "pull-push" } } }
+
+ subject { build.cache }
+
+ context 'when build has cache' do
+ before do
+ allow(build).to receive(:options).and_return(options)
+ end
+
+ context 'when project has jobs_cache_index' do
+ before do
+ allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
+ end
+
+ it { is_expected.to be_an(Array).and all(include(key: "key:1")) }
+ end
+
+ context 'when project does not have jobs_cache_index' do
+ before do
+ allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
+ end
+
+ it { is_expected.to eq([options[:cache]]) }
+ end
+ end
+
+ context 'when build does not have cache' do
+ before do
+ allow(build).to receive(:options).and_return({})
+ end
+
+ it { is_expected.to eq([nil]) }
+ end
+ end
+
describe '#depends_on_builds' do
let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 4f02dc33cd8..817254c7d1e 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -181,7 +181,6 @@ eos
it { is_expected.to respond_to(:parents) }
it { is_expected.to respond_to(:date) }
it { is_expected.to respond_to(:diffs) }
- it { is_expected.to respond_to(:tree) }
it { is_expected.to respond_to(:id) }
it { is_expected.to respond_to(:to_patch) }
end
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 7d835511dfb..9d12f96c642 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -68,7 +68,7 @@ describe PagesDomain do
subject { domain.url }
context 'without the certificate' do
- let(:domain) { build(:pages_domain) }
+ let(:domain) { build(:pages_domain, certificate: '') }
it { is_expected.to eq('http://my.domain.com') }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 32f40f8c365..00afa09f1a3 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -3079,9 +3079,51 @@ describe Project do
expect(project).to receive(:import_finish)
expect(project).to receive(:update_project_counter_caches)
expect(project).to receive(:remove_import_jid)
+ expect(project).to receive(:after_create_default_branch)
project.after_import
end
+
+ context 'branch protection' do
+ let(:project) { create(:project, :repository) }
+
+ it 'does not protect when branch protection is disabled' do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+
+ project.after_import
+
+ expect(project.protected_branches).to be_empty
+ end
+
+ it "gives developer access to push when branch protection is set to 'developers can push'" do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ project.after_import
+
+ expect(project.protected_branches).not_to be_empty
+ expect(project.default_branch).to eq(project.protected_branches.first.name)
+ expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
+ end
+
+ it "gives developer access to merge when branch protection is set to 'developers can merge'" do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ project.after_import
+
+ expect(project.protected_branches).not_to be_empty
+ expect(project.default_branch).to eq(project.protected_branches.first.name)
+ expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
+ end
+
+ it 'protects default branch' do
+ project.after_import
+
+ expect(project.protected_branches).not_to be_empty
+ expect(project.default_branch).to eq(project.protected_branches.first.name)
+ expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
+ expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
+ end
+ end
end
describe '#update_project_counter_caches' do
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 0462f494e15..837389451e8 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -68,6 +68,12 @@ describe API::Helpers do
end
it { is_expected.to eq(user) }
+
+ it 'sets the environment with data of the current user' do
+ subject
+
+ expect(env[API::Helpers::API_USER_ENV]).to eq({ user_id: subject.id, username: subject.username })
+ end
end
context "HEAD request" do
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index e77745acbb7..805496e4a54 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -11,7 +11,7 @@ describe API::Jobs do
ref: project.default_branch)
end
- let!(:job) { create(:ci_build, pipeline: pipeline) }
+ let!(:job) { create(:ci_build, :success, pipeline: pipeline) }
let(:user) { create(:user) }
let(:api_user) { user }
@@ -443,7 +443,7 @@ describe API::Jobs do
context 'user with :update_build persmission' do
it 'cancels running or pending job' do
expect(response).to have_gitlab_http_status(201)
- expect(project.builds.first.status).to eq('canceled')
+ expect(project.builds.first.status).to eq('success')
end
end
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index 452754d7a79..505a9eaac5a 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -22,6 +22,7 @@ describe GroupChildEntity do
avatar_url
name
description
+ markdown_description
visibility
type
can_edit
@@ -60,9 +61,10 @@ describe GroupChildEntity do
end
describe 'for a group', :nested_groups do
+ let(:description) { 'Awesomeness' }
let(:object) do
create(:group, :nested, :with_avatar,
- description: 'Awesomeness')
+ description: description)
end
before do
@@ -96,6 +98,14 @@ describe GroupChildEntity do
expect(json[:edit_path]).to eq(edit_group_path(object))
end
+ context 'emoji in description' do
+ let(:description) { ':smile:' }
+
+ it 'has the correct markdown_description' do
+ expect(json[:markdown_description]).to eq('<p dir="auto"><gl-emoji title="smiling face with open mouth and smiling eyes" data-name="smile" data-unicode-version="6.0">😄</gl-emoji></p>')
+ end
+ end
+
it_behaves_like 'group child json'
end
end
diff --git a/spec/services/check_gcp_project_billing_service_spec.rb b/spec/services/check_gcp_project_billing_service_spec.rb
new file mode 100644
index 00000000000..f0e39ba6f49
--- /dev/null
+++ b/spec/services/check_gcp_project_billing_service_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe CheckGcpProjectBillingService do
+ let(:service) { described_class.new }
+ let(:projects) { [double(name: 'first_project'), double(name: 'second_project')] }
+
+ describe '#execute' do
+ before do
+ expect_any_instance_of(GoogleApi::CloudPlatform::Client)
+ .to receive(:projects_list).and_return(projects)
+
+ allow_any_instance_of(GoogleApi::CloudPlatform::Client)
+ .to receive_message_chain(:projects_get_billing_info, :billingEnabled)
+ .and_return(project_billing_enabled)
+ end
+
+ subject { service.execute('bogustoken') }
+
+ context 'google account has a billing enabled gcp project' do
+ let(:project_billing_enabled) { true }
+
+ it { is_expected.to eq(projects) }
+ end
+
+ context 'google account does not have a billing enabled gcp project' do
+ let(:project_billing_enabled) { false }
+
+ it { is_expected.to eq([]) }
+ end
+ end
+end
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 835e83d6dba..53b3e5e365d 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -19,5 +19,21 @@ describe ProtectedBranches::CreateService do
expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
end
+
+ context 'when user does not have permission' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates a new protected branch if we skip authorization step' do
+ expect { service.execute(skip_authorization: true) }.to change(ProtectedBranch, :count).by(1)
+ end
+
+ it 'raises Gitlab::Access:AccessDeniedError' do
+ expect { service.execute }.to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
end
end
diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb
new file mode 100644
index 00000000000..de475d16586
--- /dev/null
+++ b/spec/services/reset_project_cache_service_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe ResetProjectCacheService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ subject { described_class.new(project, user).execute }
+
+ context 'when project cache_index is nil' do
+ before do
+ project.jobs_cache_index = nil
+ end
+
+ it 'sets project cache_index to one' do
+ expect { subject }.to change { project.reload.jobs_cache_index }.from(nil).to(1)
+ end
+ end
+
+ context 'when project cache_index is a numeric value' do
+ before do
+ project.update_attributes(jobs_cache_index: 1)
+ end
+
+ it 'increments project cache index' do
+ expect { subject }.to change { project.reload.jobs_cache_index }.by(1)
+ end
+ end
+end
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index 8a073e58db8..99752ed396e 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -10,6 +10,12 @@ module GoogleApi
request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.ago.to_i.to_s
end
+ def stub_google_project_billing_status
+ redis_double = double
+ allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double)
+ allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for('token')).and_return('true')
+ end
+
def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options)
WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id))
.to_return(cloud_platform_response(cloud_platform_cluster_body(options)))
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 1c54cf55fa0..d67e7698635 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -1,13 +1,32 @@
require 'spec_helper'
-describe BackgroundMigrationWorker, :sidekiq do
+describe BackgroundMigrationWorker, :sidekiq, :clean_gitlab_redis_shared_state do
+ let(:worker) { described_class.new }
+
describe '.perform' do
it 'performs a background migration' do
expect(Gitlab::BackgroundMigration)
.to receive(:perform)
.with('Foo', [10, 20])
- described_class.new.perform('Foo', [10, 20])
+ worker.perform('Foo', [10, 20])
+ end
+
+ it 'reschedules a migration if it was performed recently' do
+ expect(worker)
+ .to receive(:always_perform?)
+ .and_return(false)
+
+ worker.lease_for('Foo').try_obtain
+
+ expect(Gitlab::BackgroundMigration)
+ .not_to receive(:perform)
+
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(a_kind_of(Numeric), 'Foo', [10, 20])
+
+ worker.perform('Foo', [10, 20])
end
end
end
diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb
new file mode 100644
index 00000000000..f52a903327c
--- /dev/null
+++ b/spec/workers/check_gcp_project_billing_worker_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe CheckGcpProjectBillingWorker do
+ describe '.perform' do
+ let(:token) { 'bogustoken' }
+
+ subject { described_class.new.perform('token_key') }
+
+ context 'when there is a token in redis' do
+ before do
+ allow_any_instance_of(described_class).to receive(:get_session_token).and_return(token)
+ end
+
+ context 'when there is no lease' do
+ before do
+ allow_any_instance_of(described_class).to receive(:try_obtain_lease_for).and_return('randomuuid')
+ end
+
+ it 'calls the service' do
+ expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double])
+
+ subject
+ end
+
+ it 'stores billing status in redis' do
+ redis_double = double
+
+ expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double])
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double)
+ expect(redis_double).to receive(:set).with(described_class.redis_shared_state_key_for(token), anything, anything)
+
+ subject
+ end
+ end
+
+ context 'when there is a lease' do
+ before do
+ allow_any_instance_of(described_class).to receive(:try_obtain_lease_for).and_return(false)
+ end
+
+ it 'does not call the service' do
+ expect(CheckGcpProjectBillingService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+
+ context 'when there is no token in redis' do
+ before do
+ allow_any_instance_of(described_class).to receive(:get_session_token).and_return(nil)
+ end
+
+ it 'does not call the service' do
+ expect(CheckGcpProjectBillingService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 85ac14eb347..7274a9f00f9 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -32,6 +32,7 @@ describe RepositoryImportWorker do
expect_any_instance_of(Projects::ImportService).to receive(:execute)
.and_return({ status: :ok })
+ expect_any_instance_of(Project).to receive(:after_import).and_call_original
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
expect_any_instance_of(Project).to receive(:import_finish)
diff --git a/vendor/gitignore/Eagle.gitignore b/vendor/gitignore/Eagle.gitignore
index 9ced1260266..9afc324d6ae 100644
--- a/vendor/gitignore/Eagle.gitignore
+++ b/vendor/gitignore/Eagle.gitignore
@@ -4,6 +4,9 @@
*.s#?
*.b#?
*.l#?
+*.b$?
+*.s$?
+*.l$?
# Eagle project file
# It contains a serial number and references to the file structure
@@ -31,14 +34,19 @@ eagle.epf
*.drl
*.gpi
*.pls
+*.ger
+*.gpi
+*.xln
*.drd
*.drd.*
+*.s#*
+*.b#*
+
*.info
*.eps
# file locks introduced since 7.x
*.lck
-
diff --git a/vendor/gitignore/Global/Eclipse.gitignore b/vendor/gitignore/Global/Eclipse.gitignore
index ce1c12cdb7a..0eb8a5e8571 100644
--- a/vendor/gitignore/Global/Eclipse.gitignore
+++ b/vendor/gitignore/Global/Eclipse.gitignore
@@ -23,6 +23,9 @@ local.properties
# CDT-specific (C/C++ Development Tooling)
.cproject
+# CDT- autotools
+.autotools
+
# Java annotation processor (APT)
.factorypath
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
index 345e61ae3f2..a30eacf1d98 100644
--- a/vendor/gitignore/Global/JetBrains.gitignore
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -21,6 +21,7 @@
# CMake
cmake-build-debug/
+cmake-build-release/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
diff --git a/vendor/gitignore/Global/Matlab.gitignore b/vendor/gitignore/Global/Matlab.gitignore
index 7996ad5058e..d87a6bdbeeb 100644
--- a/vendor/gitignore/Global/Matlab.gitignore
+++ b/vendor/gitignore/Global/Matlab.gitignore
@@ -1,8 +1,3 @@
-##---------------------------------------------------
-## Remove autosaves generated by the MATLAB editor
-## We have git for backups!
-##---------------------------------------------------
-
# Windows default autosave extension
*.asv
@@ -12,12 +7,19 @@
# Compiled MEX binaries (all platforms)
*.mex*
-# Simulink Code Generation
+# Packaged app and toolbox files
+*.mlappinstall
+*.mltbx
+
+# Generated helpsearch folders
+helpsearch*/
+
+# Simulink code generation folders
slprj/
sccprj/
-# Session info
-octave-workspace
-
# Simulink autosave extension
*.autosave
+
+# Octave session info
+octave-workspace
diff --git a/vendor/gitignore/Go.gitignore b/vendor/gitignore/Go.gitignore
index ea58090bd21..f1c181ec9c5 100644
--- a/vendor/gitignore/Go.gitignore
+++ b/vendor/gitignore/Go.gitignore
@@ -1,5 +1,6 @@
# Binaries for programs and plugins
*.exe
+*.exe~
*.dll
*.so
*.dylib
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
index 97e28736892..d1bed128fa8 100644
--- a/vendor/gitignore/Node.gitignore
+++ b/vendor/gitignore/Node.gitignore
@@ -57,3 +57,5 @@ typings/
# dotenv environment variables file
.env
+# next.js build output
+.next
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index 42aeb55000a..828ab1d556a 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -42,3 +42,7 @@ bower.json
# Ignore Byebug command history file.
.byebug_history
+
+# Ignore node_modules
+node_modules/
+
diff --git a/vendor/gitignore/Umbraco.gitignore b/vendor/gitignore/Umbraco.gitignore
index b6b0743f62a..10fc2b4d825 100644
--- a/vendor/gitignore/Umbraco.gitignore
+++ b/vendor/gitignore/Umbraco.gitignore
@@ -16,8 +16,11 @@
# Don't ignore Umbraco packages (VisualStudio.gitignore mistakes this for a NuGet packages folder)
# Make sure to include details from VisualStudio.gitignore BEFORE this
-!**/App_Data/[Pp]ackages/
-!**/[Uu]mbraco/[Dd]eveloper/[Pp]ackages
+!**/App_Data/[Pp]ackages/*
+!**/[Uu]mbraco/[Dd]eveloper/[Pp]ackages/*
# ImageProcessor DiskCache
**/App_Data/cache/
+
+# Ignore the Models Builder models out of date flag
+**/App_Data/Models/ood.flag
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 6217e6c48e9..d3d5371b415 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -219,6 +219,10 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
@@ -313,3 +317,7 @@ OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
diff --git a/vendor/gitignore/WordPress.gitignore b/vendor/gitignore/WordPress.gitignore
index 97923503c4c..3b181ec0cf2 100644
--- a/vendor/gitignore/WordPress.gitignore
+++ b/vendor/gitignore/WordPress.gitignore
@@ -7,6 +7,7 @@ wp-content/blogs.dir/
wp-content/cache/
wp-content/upgrade/
wp-content/uploads/
+wp-content/mu-plugins/
wp-content/wp-cache-config.php
wp-content/plugins/hello.php
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 06473fba8e1..75de266369d 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -112,6 +112,19 @@ sast:
- sast .
artifacts:
paths: [gl-sast-report.json]
+
+sast:container:
+ image: docker:latest
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:dind
+ script:
+ - setup_docker
+ - sast_container
+ artifacts:
+ paths: [gl-sast-container-report.json]
review:
stage: review
@@ -247,6 +260,18 @@ production:
export CI_APPLICATION_TAG=$CI_COMMIT_SHA
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
+
+ function sast_container() {
+ docker run -d --name db arminc/clair-db:latest
+ docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
+ apk add -U wget ca-certificates
+ docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
+ wget https://github.com/arminc/clair-scanner/releases/download/v6/clair-scanner_linux_386
+ mv clair-scanner_linux_386 clair-scanner
+ chmod +x clair-scanner
+ touch clair-whitelist.yml
+ ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
+ }
function codeclimate() {
cc_opts="--env CODECLIMATE_CODE="$PWD" \
diff --git a/vendor/gitlab-ci-yml/Mono.gitlab-ci.yml b/vendor/gitlab-ci-yml/Mono.gitlab-ci.yml
new file mode 100644
index 00000000000..3585f99760f
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Mono.gitlab-ci.yml
@@ -0,0 +1,42 @@
+# This is a simple gitlab continuous integration template (compatible with the shared runner provided on gitlab.com)
+# using the official mono docker image to build a visual studio project.
+#
+# MyProject.sln
+# MyProject\
+# MyProject\
+# MyProject.csproj (console application)
+# MyProject.Test\
+# MyProject.Test.csproj (test library using nuget packages "NUnit" and "NUnit.ConsoleRunner")
+#
+# Please find the full example project here:
+# https://gitlab.com/tobiaskoch/gitlab-ci-example-mono
+
+# see https://hub.docker.com/_/mono/
+image: mono:latest
+
+stages:
+ - test
+ - deploy
+
+before_script:
+ - nuget restore -NonInteractive
+
+release:
+ stage: deploy
+ only:
+ - master
+ artifacts:
+ paths:
+ - build/release/MyProject.exe
+ script:
+ # The output path is relative to the position of the csproj-file
+ - msbuild /p:Configuration="Release" /p:Platform="Any CPU"
+ /p:OutputPath="./../../build/release/" "MyProject.sln"
+
+debug:
+ stage: test
+ script:
+ # The output path is relative to the position of the csproj-file
+ - msbuild /p:Configuration="Debug" /p:Platform="Any CPU"
+ /p:OutputPath="./../../build/debug/" "MyProject.sln"
+ - mono packages/NUnit.ConsoleRunner.3.6.0/tools/nunit3-console.exe build/debug/MyProject.Test.dll \ No newline at end of file
diff --git a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
index 1463161a04b..cab087c48c7 100644
--- a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
@@ -20,4 +20,4 @@ image: "rust:latest"
test:cargo:
script:
- rustc --version && cargo --version # Print version info for debugging
- - cargo test --verbose --jobs 1 --release # Don't parallelise to make errors more readable
+ - cargo test --all --verbose
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index b6a5c2f81a0..e3ccf080f74 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -23,7 +23,7 @@ autoprefixer-rails,6.2.3,MIT
axiom-types,0.1.1,MIT
babosa,1.0.2,MIT
base32,0.3.2,MIT
-batch-loader,1.1.1,MIT
+batch-loader,1.2.1,MIT
bcrypt,3.1.11,MIT
bcrypt_pbkdf,1.0.0,MIT
bindata,2.4.1,ruby
@@ -73,8 +73,9 @@ faraday_middleware,0.11.0.1,MIT
faraday_middleware-multi_json,0.0.6,MIT
fast_gettext,1.4.0,"MIT,ruby"
ffi,1.9.18,New BSD
-flipper,0.10.2,MIT
-flipper-active_record,0.10.2,MIT
+flipper,0.11.0,MIT
+flipper-active_record,0.11.0,MIT
+flipper-active_support_cache_store,0.11.0,MIT
flowdock,0.7.1,MIT
fog-aliyun,0.2.0,MIT
fog-aws,1.4.0,MIT
@@ -92,7 +93,7 @@ gemojione,3.3.0,MIT
get_process_mem,0.2.0,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.2.0,MIT
-gitaly-proto,0.59.0,MIT
+gitaly-proto,0.64.0,MIT
github-linguist,4.7.6,MIT
github-markup,1.6.1,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
@@ -164,7 +165,7 @@ multi_xml,0.6.0,MIT
multipart-post,2.0.0,MIT
mustermann,1.0.0,MIT
mustermann-grape,1.0.0,MIT
-mysql2,0.4.5,MIT
+mysql2,0.4.10,MIT
net-ldap,0.16.0,MIT
net-ssh,4.1.0,MIT
netrc,0.11.0,MIT
@@ -210,7 +211,7 @@ po_to_json,1.0.1,MIT
posix-spawn,0.3.13,MIT
premailer,1.10.4,New BSD
premailer-rails,1.9.7,MIT
-prometheus-client-mmap,0.7.0.beta43,Apache 2.0
+prometheus-client-mmap,0.7.0.beta44,Apache 2.0
public_suffix,3.0.0,MIT
pyu-ruby-sasl,0.0.3.3,MIT
rack,1.6.8,MIT
@@ -237,11 +238,11 @@ re2,1.1.1,New BSD
recaptcha,3.0.0,MIT
recursive-open-struct,1.0.0,MIT
redcarpet,3.4.0,MIT
-redis,3.3.3,MIT
+redis,3.3.5,MIT
redis-actionpack,5.0.2,MIT
redis-activesupport,5.0.4,MIT
redis-namespace,1.5.2,MIT
-redis-rack,2.0.3,MIT
+redis-rack,2.0.4,MIT
redis-rails,5.0.2,MIT
redis-store,1.4.1,MIT
representable,3.0.4,MIT
@@ -273,7 +274,7 @@ select2-rails,3.5.9.3,MIT
sentry-raven,2.5.3,Apache 2.0
settingslogic,2.0.9,MIT
sexp_processor,4.9.0,MIT
-sidekiq,5.0.4,LGPL
+sidekiq,5.0.5,LGPL
sidekiq-cron,0.6.0,MIT
sidekiq-limit_fetch,3.4.0,MIT
signet,0.7.3,Apache 2.0
diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz
index 7a811e1986b..dcf5e4a0416 100644
--- a/vendor/project_templates/express.tar.gz
+++ b/vendor/project_templates/express.tar.gz
Binary files differ
diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz
index 7db63ecc65f..d4856090ed9 100644
--- a/vendor/project_templates/rails.tar.gz
+++ b/vendor/project_templates/rails.tar.gz
Binary files differ
diff --git a/vendor/project_templates/spring.tar.gz b/vendor/project_templates/spring.tar.gz
index 96f51ee804c..6ee7e76f676 100644
--- a/vendor/project_templates/spring.tar.gz
+++ b/vendor/project_templates/spring.tar.gz
Binary files differ