diff options
38 files changed, 687 insertions, 607 deletions
@@ -300,7 +300,7 @@ group :metrics do end group :development do - gem 'foreman', '~> 0.78.0' + gem 'foreman', '~> 0.84.0' gem 'brakeman', '~> 3.6.0', require: false gem 'letter_opener_web', '~> 1.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index b85c7085d07..86b4e5301d8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -265,7 +265,7 @@ GEM nokogiri (>= 1.5.11, < 2.0.0) font-awesome-rails (4.7.0.1) railties (>= 3.2, < 5.1) - foreman (0.78.0) + foreman (0.84.0) thor (~> 0.19.1) formatador (0.2.5) fuubar (2.2.0) @@ -1055,7 +1055,7 @@ DEPENDENCIES fog-openstack (~> 0.1) fog-rackspace (~> 0.1.1) font-awesome-rails (~> 4.7) - foreman (~> 0.78.0) + foreman (~> 0.84.0) fuubar (~> 2.2.0) gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.3) diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 43a5325cf71..8259133c95b 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -132,9 +132,8 @@ class GfmAutoComplete { callbacks: { ...this.getDefaultCallbacks(), matcher(flag, subtext) { - const relevantText = subtext.trim().split(/\s/).pop(); const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi'); - const match = regexp.exec(relevantText); + const match = regexp.exec(subtext); return match && match.length ? match[1] : null; }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue new file mode 100644 index 00000000000..7bef2e97349 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -0,0 +1,144 @@ +<script> +import timeagoMixin from '../../vue_shared/mixins/timeago'; +import tooltip from '../../vue_shared/directives/tooltip'; +import LoadingButton from '../../vue_shared/components/loading_button.vue'; +import { visitUrl } from '../../lib/utils/url_utility'; +import createFlash from '../../flash'; +import MemoryUsage from './memory_usage.vue'; +import StatusIcon from './mr_widget_status_icon.vue'; +import MRWidgetService from '../services/mr_widget_service'; + +export default { + name: 'Deployment', + components: { + LoadingButton, + MemoryUsage, + StatusIcon, + }, + directives: { + tooltip, + }, + mixins: [ + timeagoMixin, + ], + props: { + deployment: { + type: Object, + required: true, + }, + }, + data() { + return { + isStopping: false, + }; + }, + computed: { + deployTimeago() { + return this.timeFormated(this.deployment.deployed_at); + }, + hasExternalUrls() { + return !!(this.deployment.external_url && this.deployment.external_url_formatted); + }, + hasDeploymentTime() { + return !!(this.deployment.deployed_at && this.deployment.deployed_at_formatted); + }, + hasDeploymentMeta() { + return !!(this.deployment.url && this.deployment.name); + }, + hasMetrics() { + return !!(this.deployment.metrics_url); + }, + }, + methods: { + stopEnvironment() { + const msg = 'Are you sure you want to stop this environment?'; + const isConfirmed = confirm(msg); // eslint-disable-line + + if (isConfirmed) { + this.isStopping = true; + + MRWidgetService.stopEnvironment(this.deployment.stop_url) + .then(res => res.data) + .then((data) => { + if (data.redirect_url) { + visitUrl(data.redirect_url); + } + + this.isStopping = false; + }) + .catch(() => { + createFlash('Something went wrong while stopping this environment. Please try again.'); + this.isStopping = false; + }); + } + }, + }, +}; +</script> + +<template> + <div class="mr-widget-heading deploy-heading"> + <div class="ci-widget media"> + <div class="ci-status-icon ci-status-icon-success"> + <span class="js-icon-link icon-link"> + <status-icon status="success" /> + </span> + </div> + <div class="media-body"> + <div class="deploy-body"> + <template v-if="hasDeploymentMeta"> + <span> + Deployed to + </span> + <a + :href="deployment.url" + target="_blank" + rel="noopener noreferrer nofollow" + class="deploy-link js-deploy-meta" + > + {{ deployment.name }} + </a> + </template> + <template v-if="hasExternalUrls"> + <span> + on + </span> + <a + :href="deployment.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="deploy-link js-deploy-url" + > + <i + class="fa fa-external-link" + aria-hidden="true" + > + </i> + {{ deployment.external_url_formatted }} + </a> + </template> + <span + v-if="hasDeploymentTime" + v-tooltip + :title="deployment.deployed_at_formatted" + class="js-deploy-time" + > + {{ deployTimeago }} + </span> + <loading-button + v-if="deployment.stop_url" + container-class="btn btn-default btn-xs prepend-left-default" + label="Stop environment" + :loading="isStopping" + @click="stopEnvironment" + /> + </div> + <memory-usage + v-if="hasMetrics" + :metrics-url="deployment.metrics_url" + :metrics-monitoring-url="deployment.metrics_monitoring_url" + /> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js deleted file mode 100644 index c7f992384c8..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js +++ /dev/null @@ -1,113 +0,0 @@ -import { getTimeago } from '~/lib/utils/datetime_utility'; -import { visitUrl } from '../../lib/utils/url_utility'; -import Flash from '../../flash'; -import MemoryUsage from './memory_usage.vue'; -import StatusIcon from './mr_widget_status_icon.vue'; -import MRWidgetService from '../services/mr_widget_service'; - -export default { - name: 'MRWidgetDeployment', - props: { - mr: { type: Object, required: true }, - service: { type: Object, required: true }, - }, - components: { - MemoryUsage, - StatusIcon, - }, - methods: { - formatDate(date) { - return getTimeago().format(date); - }, - hasExternalUrls(deployment = {}) { - return deployment.external_url && deployment.external_url_formatted; - }, - hasDeploymentTime(deployment = {}) { - return deployment.deployed_at && deployment.deployed_at_formatted; - }, - hasDeploymentMeta(deployment = {}) { - return deployment.url && deployment.name; - }, - stopEnvironment(deployment) { - const msg = 'Are you sure you want to stop this environment?'; - const isConfirmed = confirm(msg); // eslint-disable-line - - if (isConfirmed) { - MRWidgetService.stopEnvironment(deployment.stop_url) - .then(res => res.data) - .then((data) => { - if (data.redirect_url) { - visitUrl(data.redirect_url); - } - }) - .catch(() => { - new Flash('Something went wrong while stopping this environment. Please try again.'); // eslint-disable-line - }); - } - }, - }, - template: ` - <div class="mr-widget-heading deploy-heading"> - <div v-for="deployment in mr.deployments"> - <div class="ci-widget media"> - <div class="ci-status-icon ci-status-icon-success"> - <span class="js-icon-link icon-link"> - <status-icon status="success" /> - </span> - </div> - <div class="media-body space-children"> - <span> - <span - v-if="hasDeploymentMeta(deployment)"> - Deployed to - </span> - <a - v-if="hasDeploymentMeta(deployment)" - :href="deployment.url" - target="_blank" - rel="noopener noreferrer nofollow" - class="js-deploy-meta inline"> - {{deployment.name}} - </a> - <span - v-if="hasExternalUrls(deployment)"> - on - </span> - <a - v-if="hasExternalUrls(deployment)" - :href="deployment.external_url" - target="_blank" - rel="noopener noreferrer nofollow" - class="js-deploy-url inline"> - <i - class="fa fa-external-link" - aria-hidden="true" /> - {{deployment.external_url_formatted}} - </a> - <span - v-if="hasDeploymentTime(deployment)" - :data-title="deployment.deployed_at_formatted" - class="js-deploy-time" - data-toggle="tooltip" - data-placement="top"> - {{formatDate(deployment.deployed_at)}} - </span> - </span> - <button - type="button" - v-if="deployment.stop_url" - @click="stopEnvironment(deployment)" - class="btn btn-default btn-xs"> - Stop environment - </button> - <memory-usage - v-if="deployment.metrics_url" - :metrics-url="deployment.metrics_url" - :metrics-monitoring-url="deployment.metrics_monitoring_url" - /> - </div> - </div> - </div> - </div> - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 20624aad0ad..efbe1c96d1c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -14,7 +14,7 @@ export { default as SmartInterval } from '~/smart_interval'; export { default as WidgetHeader } from './components/mr_widget_header.vue'; export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue'; export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue'; -export { default as WidgetDeployment } from './components/mr_widget_deployment'; +export { default as Deployment } from './components/deployment.vue'; export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue'; export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue'; export { default as MergedState } from './components/states/mr_widget_merged.vue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index cc8bc6af1e1..169adfe0a1d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -5,7 +5,7 @@ import { WidgetHeader, WidgetMergeHelp, WidgetPipeline, - WidgetDeployment, + Deployment, WidgetMaintainerEdit, WidgetRelatedLinks, MergedState, @@ -67,9 +67,6 @@ export default { shouldRenderRelatedLinks() { return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState; }, - shouldRenderDeployments() { - return this.mr.deployments.length; - }, shouldRenderSourceBranchRemovalStatus() { return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch && (!this.mr.isNothingToMergeState && !this.mr.isMergedState); @@ -216,7 +213,7 @@ export default { 'mr-widget-header': WidgetHeader, 'mr-widget-merge-help': WidgetMergeHelp, 'mr-widget-pipeline': WidgetPipeline, - 'mr-widget-deployment': WidgetDeployment, + Deployment, 'mr-widget-maintainer-edit': WidgetMaintainerEdit, 'mr-widget-related-links': WidgetRelatedLinks, 'mr-widget-merged': MergedState, @@ -250,10 +247,11 @@ export default { :ci-status="mr.ciStatus" :has-ci="mr.hasCI" /> - <mr-widget-deployment - v-if="shouldRenderDeployments" - :mr="mr" - :service="service" /> + <deployment + v-for="deployment in mr.deployments" + :key="deployment.id" + :deployment="deployment" + /> <div class="mr-widget-section"> <component :is="componentName" diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f887a11004f..4692d0fb873 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -718,6 +718,8 @@ } .mr-memory-usage { + width: 100%; + p.usage-info-loading .usage-info-load-spinner { margin-right: 10px; font-size: 16px; @@ -727,3 +729,36 @@ .fork-sprite { margin-right: -5px; } + +.deploy-heading { + .media-body { + min-width: 0; + } +} + +.deploy-body { + display: flex; + flex-wrap: wrap; + + @media (min-width: $screen-xs) { + flex-wrap: nowrap; + white-space: nowrap; + } + + > *:not(:last-child) { + margin-right: .3em; + } +} + +.deploy-link { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 100px; + max-width: 150px; + + @media (min-width: $screen-xs) { + min-width: 0; + max-width: 100%; + } +} diff --git a/app/models/project.rb b/app/models/project.rb index 5f9d9785d64..0183e3d0a38 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1083,7 +1083,7 @@ class Project < ActiveRecord::Base # Forked import is handled asynchronously return if forked? && !force - if gitlab_shell.add_repository(repository_storage, disk_path) + if gitlab_shell.create_repository(repository_storage, disk_path) repository.after_create true else diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index f6041da986c..52e067cb44c 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -169,7 +169,7 @@ class ProjectWiki private def create_repo!(raw_repository) - gitlab_shell.add_repository(project.repository_storage, disk_path) + gitlab_shell.create_repository(project.repository_storage, disk_path) raise CouldNotCreateWikiError unless raw_repository.exists? diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 185e9d7b35d..37269862de6 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -9,6 +9,10 @@ %span.runner-state.runner-state-specific Specific +- add_to_breadcrumbs _("Runners"), admin_runners_path +- breadcrumb_title "##{@runner.id}" +- @no_container = true + - if @runner.shared? .bs-callout.bs-callout-success %h4 This Runner will process jobs from ALL UNASSIGNED projects diff --git a/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml new file mode 100644 index 00000000000..3aec71d5ac4 --- /dev/null +++ b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml @@ -0,0 +1,5 @@ +--- +title: Set breadcrumb for admin/runners/show +merge_request: 17431 +author: Takuya Noguchi +type: fixed diff --git a/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml new file mode 100644 index 00000000000..990d188eb78 --- /dev/null +++ b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml @@ -0,0 +1,5 @@ +--- +title: Update foreman from 0.78.0 to 0.84.0 +merge_request: 17690 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/fix-emoji-popup.yml b/changelogs/unreleased/fix-emoji-popup.yml new file mode 100644 index 00000000000..c81d061a5d7 --- /dev/null +++ b/changelogs/unreleased/fix-emoji-popup.yml @@ -0,0 +1,5 @@ +--- +title: Hide emoji popup after multiple spaces +merge_request: +author: Jan Beckmann +type: fixed diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md index 6c5a466ced5..27a3710632d 100644 --- a/doc/administration/incoming_email.md +++ b/doc/administration/incoming_email.md @@ -187,6 +187,7 @@ for a real-world example of this exploit. ```sh sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart ``` 1. Verify that everything is configured correctly: diff --git a/features/project/graph.feature b/features/project/graph.feature deleted file mode 100644 index b25c73ad870..00000000000 --- a/features/project/graph.feature +++ /dev/null @@ -1,33 +0,0 @@ -Feature: Project Graph - Background: - Given I sign in as a user - And I own project "Shop" - - @javascript - Scenario: I should see project graphs - When I visit project "Shop" graph page - Then page should have graphs - - @javascript - Scenario: I should see project languages & commits graphs on commits graph url - When I visit project "Shop" commits graph page - Then page should have commits graphs - Then page should have languages graphs - - @javascript - Scenario: I should see project ci graphs - Given project "Shop" has CI enabled - When I visit project "Shop" CI graph page - Then page should have CI graphs - - @javascript - Scenario: I should see project languages & commits graphs on language graph url - When I visit project "Shop" languages graph page - Then page should have languages graphs - Then page should have commits graphs - - @javascript - Scenario: I should see project languages & commits graphs on charts url - When I visit project "Shop" chart page - Then page should have languages graphs - Then page should have commits graphs diff --git a/features/project/redirects.feature b/features/project/redirects.feature deleted file mode 100644 index a2e77e7bf30..00000000000 --- a/features/project/redirects.feature +++ /dev/null @@ -1,38 +0,0 @@ -Feature: Project Redirects - Background: - Given public project "Community" - And private project "Enterprise" - - Scenario: I visit public project page - When I visit project "Community" page - Then I should see project "Community" home page - - Scenario: I visit private project page - When I visit project "Enterprise" page - Then I should be redirected to sign in page - - Scenario: I visit a non-existent project page - When I visit project "CommunityDoesNotExist" page - Then I should be redirected to sign in page - - Scenario: I visit a non-existent project page as user - Given I sign in as a user - When I visit project "CommunityDoesNotExist" page - Then page status code should be 404 - - Scenario: I visit unauthorized project page as user - Given I sign in as a user - When I visit project "Enterprise" page - Then page status code should be 404 - - Scenario: I visit a public project without signing in - When I visit project "Community" page - And I should see project "Community" home page - And I click on "Sign In" - And Authenticate - Then I should be redirected to "Community" page - - Scenario: I visit private project page without signing in - When I visit project "Enterprise" page - And I get redirected to signin page where I sign in - Then I should be redirected to "Enterprise" page diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb deleted file mode 100644 index b9cddf4041d..00000000000 --- a/features/steps/project/graph.rb +++ /dev/null @@ -1,50 +0,0 @@ -class Spinach::Features::ProjectGraph < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - - step 'page should have graphs' do - expect(page).to have_selector ".stat-graph" - end - - When 'I visit project "Shop" graph page' do - visit project_graph_path(project, "master") - end - - step 'I visit project "Shop" commits graph page' do - visit commits_project_graph_path(project, "master") - end - - step 'I visit project "Shop" languages graph page' do - visit languages_project_graph_path(project, "master") - end - - step 'I visit project "Shop" chart page' do - visit charts_project_graph_path(project, "master") - end - - step 'page should have languages graphs' do - expect(page).to have_content /Ruby 66.* %/ - expect(page).to have_content /JavaScript 22.* %/ - end - - step 'page should have commits graphs' do - expect(page).to have_content "Commit statistics for master" - expect(page).to have_content "Commits per day of month" - end - - step 'I visit project "Shop" CI graph page' do - visit ci_project_graph_path(project, 'master') - end - - step 'page should have CI graphs' do - expect(page).to have_content 'Overall' - expect(page).to have_content 'Pipelines for last week' - expect(page).to have_content 'Pipelines for last month' - expect(page).to have_content 'Pipelines for last year' - expect(page).to have_content 'Commit duration in minutes for last 30 commits' - end - - def project - @project ||= Project.find_by(name: "Shop") - end -end diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb deleted file mode 100644 index 9ce86ca45d0..00000000000 --- a/features/steps/project/redirects.rb +++ /dev/null @@ -1,67 +0,0 @@ -class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - - step 'public project "Community"' do - create(:project, :public, name: 'Community') - end - - step 'private project "Enterprise"' do - create(:project, :private, name: 'Enterprise') - end - - step 'I visit project "Community" page' do - project = Project.find_by(name: 'Community') - visit project_path(project) - end - - step 'I should see project "Community" home page' do - Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com") - page.within '.breadcrumbs .breadcrumb-item-text' do - expect(page).to have_content 'Community' - end - end - - step 'I visit project "Enterprise" page' do - project = Project.find_by(name: 'Enterprise') - visit project_path(project) - end - - step 'I visit project "CommunityDoesNotExist" page' do - project = Project.find_by(name: 'Community') - visit project_path(project) + 'DoesNotExist' - end - - step 'I click on "Sign In"' do - first(:link, "Sign in").click - end - - step 'Authenticate' do - admin = create(:admin) - fill_in "user_login", with: admin.email - fill_in "user_password", with: admin.password - click_button "Sign in" - Thread.current[:current_user] = admin - end - - step 'I should be redirected to "Community" page' do - project = Project.find_by(name: 'Community') - expect(current_path).to eq "/#{project.full_path}" - expect(status_code).to eq 200 - end - - step 'I get redirected to signin page where I sign in' do - admin = create(:admin) - fill_in "user_login", with: admin.email - fill_in "user_password", with: admin.password - click_button "Sign in" - Thread.current[:current_user] = admin - end - - step 'I should be redirected to "Enterprise" page' do - project = Project.find_by(name: 'Enterprise') - expect(current_path).to eq "/#{project.full_path}" - expect(status_code).to eq 200 - end -end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index dda7afc0999..3a8f5826818 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -69,13 +69,14 @@ module Gitlab # name - project disk path # # Ex. - # add_repository("/path/to/storage", "gitlab/gitlab-ci") + # create_repository("/path/to/storage", "gitlab/gitlab-ci") # - def add_repository(storage, name) + def create_repository(storage, name) relative_path = name.dup relative_path << '.git' unless relative_path.end_with?('.git') - gitaly_migrate(:create_repository) do |is_enabled| + gitaly_migrate(:create_repository, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled repository = Gitlab::Git::Repository.new(storage, relative_path, '') repository.gitaly_repository_client.create_repository @@ -85,7 +86,7 @@ module Gitlab Gitlab::Git::Repository.create(repo_path, bare: true, symlink_hooks_to: gitlab_shell_hooks_path) end end - rescue => err + rescue => err # Once the Rugged codes gets removes this can be improved Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}") false end @@ -487,8 +488,8 @@ module Gitlab Gitlab.config.gitlab_shell.git_timeout end - def gitaly_migrate(method, &block) - Gitlab::GitalyClient.migrate(method, &block) + def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block) + Gitlab::GitalyClient.migrate(method, status: status, &block) rescue GRPC::NotFound, GRPC::BadStatus => e # Old Popen code returns [Error, output] to the caller, so we # need to do the same here... diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 844664b12d4..4fcbbbf8c9d 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -69,7 +69,7 @@ namespace :gitlab do if File.exist?(path_to_repo) print '-' else - if Gitlab::Shell.new.add_repository(project.repository_storage, + if Gitlab::Shell.new.create_repository(project.repository_storage, project.disk_path) print '.' else diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb index 3abe363d523..f744d7941f5 100644 --- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb +++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb @@ -22,7 +22,7 @@ describe 'Merge request > User sees deployment widget', :js do wait_for_requests expect(page).to have_content("Deployed to #{environment.name}") - expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) + expect(find('.js-deploy-time')['data-original-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) end context 'with stop action' do diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb new file mode 100644 index 00000000000..57172610aed --- /dev/null +++ b/spec/features/projects/graph_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe 'Project Graph', :js do + let(:user) { create :user } + let(:project) { create(:project, :repository, namespace: user.namespace) } + + before do + project.add_master(user) + + sign_in(user) + end + + shared_examples 'page should have commits graphs' do + it 'renders commits' do + expect(page).to have_content('Commit statistics for master') + expect(page).to have_content('Commits per day of month') + end + end + + shared_examples 'page should have languages graphs' do + it 'renders languages' do + expect(page).to have_content(/Ruby 66.* %/) + expect(page).to have_content(/JavaScript 22.* %/) + end + end + + it 'renders graphs' do + visit project_graph_path(project, 'master') + + expect(page).to have_selector('.stat-graph', visible: false) + end + + context 'commits graph' do + before do + visit commits_project_graph_path(project, 'master') + end + + it_behaves_like 'page should have commits graphs' + it_behaves_like 'page should have languages graphs' + end + + context 'languages graph' do + before do + visit languages_project_graph_path(project, 'master') + end + + it_behaves_like 'page should have commits graphs' + it_behaves_like 'page should have languages graphs' + end + + context 'charts graph' do + before do + visit charts_project_graph_path(project, 'master') + end + + it_behaves_like 'page should have commits graphs' + it_behaves_like 'page should have languages graphs' + end + + context 'when CI enabled' do + before do + project.enable_ci + + visit ci_project_graph_path(project, 'master') + end + + it 'renders CI graphs' do + expect(page).to have_content 'Overall' + expect(page).to have_content 'Pipelines for last week' + expect(page).to have_content 'Pipelines for last month' + expect(page).to have_content 'Pipelines for last year' + expect(page).to have_content 'Commit duration in minutes for last 30 commits' + end + end +end diff --git a/spec/features/projects/redirects_spec.rb b/spec/features/projects/redirects_spec.rb new file mode 100644 index 00000000000..d1d8ca07035 --- /dev/null +++ b/spec/features/projects/redirects_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe 'Project redirects' do + let(:user) { create :user } + let(:public_project) { create :project, :public } + let(:private_project) { create :project, :private } + + before do + allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com') + end + + it 'shows public project page' do + visit project_path(public_project) + + page.within '.breadcrumbs .breadcrumb-item-text' do + expect(page).to have_content(public_project.name) + end + end + + it 'redirects to sign in page when project is private' do + visit project_path(private_project) + + expect(current_path).to eq(new_user_session_path) + end + + it 'redirects to sign in page when project does not exist' do + visit project_path(build(:project, :public)) + + expect(current_path).to eq(new_user_session_path) + end + + it 'redirects to public project page after signing in' do + visit project_path(public_project) + + first(:link, 'Sign in').click + + fill_in 'user_login', with: user.email + fill_in 'user_password', with: user.password + click_button 'Sign in' + + expect(status_code).to eq(200) + expect(current_path).to eq("/#{public_project.full_path}") + end + + it 'redirects to private project page after sign in' do + visit project_path(private_project) + + owner = private_project.owner + fill_in 'user_login', with: owner.email + fill_in 'user_password', with: owner.password + click_button 'Sign in' + + expect(status_code).to eq(200) + expect(current_path).to eq("/#{private_project.full_path}") + end + + context 'when signed in' do + before do + sign_in(user) + end + + it 'returns 404 status when project does not exist' do + visit project_path(build(:project, :public)) + + expect(status_code).to eq(404) + end + + it 'returns 404 when project is private' do + visit project_path(private_project) + + expect(status_code).to eq(404) + end + end +end diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js new file mode 100644 index 00000000000..ff8d54c029f --- /dev/null +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -0,0 +1,172 @@ +import Vue from 'vue'; +import * as urlUtils from '~/lib/utils/url_utility'; +import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue'; +import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; +import { getTimeago } from '~/lib/utils/datetime_utility'; + +const deploymentMockData = { + id: 15, + name: 'review/diplo', + url: '/root/acets-review-apps/environments/15', + stop_url: '/root/acets-review-apps/environments/15/stop', + metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', + metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics', + external_url: 'http://diplo.', + external_url_formatted: 'diplo.', + deployed_at: '2017-03-22T22:44:42.258Z', + deployed_at_formatted: 'Mar 22, 2017 10:44pm', +}; +const createComponent = () => { + const Component = Vue.extend(deploymentComponent); + + return new Component({ + el: document.createElement('div'), + propsData: { deployment: { ...deploymentMockData } }, + }); +}; + +describe('Deployment component', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('deployTimeago', () => { + it('return formatted date', () => { + const readable = getTimeago().format(deploymentMockData.deployed_at); + expect(vm.deployTimeago).toEqual(readable); + }); + }); + + describe('hasExternalUrls', () => { + it('should return true', () => { + expect(vm.hasExternalUrls).toEqual(true); + }); + + it('should return false when deployment has no external_url_formatted', () => { + vm.deployment.external_url_formatted = null; + + expect(vm.hasExternalUrls).toEqual(false); + }); + + it('should return false when deployment has no external_url', () => { + vm.deployment.external_url = null; + + expect(vm.hasExternalUrls).toEqual(false); + }); + }); + + describe('hasDeploymentTime', () => { + it('should return true', () => { + expect(vm.hasDeploymentTime).toEqual(true); + }); + + it('should return false when deployment has no deployed_at', () => { + vm.deployment.deployed_at = null; + + expect(vm.hasDeploymentTime).toEqual(false); + }); + + it('should return false when deployment has no deployed_at_formatted', () => { + vm.deployment.deployed_at_formatted = null; + + expect(vm.hasDeploymentTime).toEqual(false); + }); + }); + + describe('hasDeploymentMeta', () => { + it('should return true', () => { + expect(vm.hasDeploymentMeta).toEqual(true); + }); + + it('should return false when deployment has no url', () => { + vm.deployment.url = null; + + expect(vm.hasDeploymentMeta).toEqual(false); + }); + + it('should return false when deployment has no name', () => { + vm.deployment.name = null; + + expect(vm.hasDeploymentMeta).toEqual(false); + }); + }); + }); + + describe('methods', () => { + describe('stopEnvironment', () => { + const url = '/foo/bar'; + const returnPromise = () => new Promise((resolve) => { + resolve({ + data: { + redirect_url: url, + }, + }); + }); + const mockStopEnvironment = () => { + vm.stopEnvironment(deploymentMockData); + return vm; + }; + + it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => { + spyOn(window, 'confirm').and.returnValue(true); + spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true)); + spyOn(urlUtils, 'visitUrl').and.returnValue(true); + vm = mockStopEnvironment(); + + expect(window.confirm).toHaveBeenCalled(); + expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url); + setTimeout(() => { + expect(urlUtils.visitUrl).toHaveBeenCalledWith(url); + done(); + }, 333); + }); + + it('should show a confirm dialog but should not work if the dialog is rejected', () => { + spyOn(window, 'confirm').and.returnValue(false); + spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false)); + vm = mockStopEnvironment(); + + expect(window.confirm).toHaveBeenCalled(); + expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled(); + }); + }); + }); + + describe('template', () => { + let el; + + beforeEach(() => { + vm = createComponent(deploymentMockData); + el = vm.$el; + }); + + it('renders deployment name', () => { + expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deploymentMockData.url); + expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name); + }); + + it('renders external URL', () => { + expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deploymentMockData.external_url); + expect(el.querySelector('.js-deploy-url').innerText).toContain(deploymentMockData.external_url_formatted); + }); + + it('renders stop button', () => { + expect(el.querySelector('.btn')).not.toBeNull(); + }); + + it('renders deployment time', () => { + expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago); + }); + + it('renders metrics component', () => { + expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js deleted file mode 100644 index 6a59dc3c87e..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js +++ /dev/null @@ -1,179 +0,0 @@ -import Vue from 'vue'; -import * as urlUtils from '~/lib/utils/url_utility'; -import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment'; -import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; -import { getTimeago } from '~/lib/utils/datetime_utility'; - -const deploymentMockData = [ - { - id: 15, - name: 'review/diplo', - url: '/root/acets-review-apps/environments/15', - stop_url: '/root/acets-review-apps/environments/15/stop', - metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', - metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics', - external_url: 'http://diplo.', - external_url_formatted: 'diplo.', - deployed_at: '2017-03-22T22:44:42.258Z', - deployed_at_formatted: 'Mar 22, 2017 10:44pm', - }, -]; -const createComponent = () => { - const Component = Vue.extend(deploymentComponent); - const mr = { - deployments: deploymentMockData, - }; - const service = {}; - - return new Component({ - el: document.createElement('div'), - propsData: { mr, service }, - }); -}; - -describe('MRWidgetDeployment', () => { - describe('props', () => { - it('should have props', () => { - const { mr, service } = deploymentComponent.props; - - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - - expect(service.type instanceof Object).toBeTruthy(); - expect(service.required).toBeTruthy(); - }); - }); - - describe('methods', () => { - let vm = createComponent(); - const deployment = deploymentMockData[0]; - - describe('formatDate', () => { - it('should work', () => { - const readable = getTimeago().format(deployment.deployed_at); - expect(vm.formatDate(deployment.deployed_at)).toEqual(readable); - }); - }); - - describe('hasExternalUrls', () => { - it('should return true', () => { - expect(vm.hasExternalUrls(deployment)).toBeTruthy(); - }); - - it('should return false when there is not enough information', () => { - expect(vm.hasExternalUrls()).toBeFalsy(); - expect(vm.hasExternalUrls({ external_url: 'Diplo' })).toBeFalsy(); - expect(vm.hasExternalUrls({ external_url_formatted: 'Diplo' })).toBeFalsy(); - }); - }); - - describe('hasDeploymentTime', () => { - it('should return true', () => { - expect(vm.hasDeploymentTime(deployment)).toBeTruthy(); - }); - - it('should return false when there is not enough information', () => { - expect(vm.hasDeploymentTime()).toBeFalsy(); - expect(vm.hasDeploymentTime({ deployed_at: 'Diplo' })).toBeFalsy(); - expect(vm.hasDeploymentTime({ deployed_at_formatted: 'Diplo' })).toBeFalsy(); - }); - }); - - describe('hasDeploymentMeta', () => { - it('should return true', () => { - expect(vm.hasDeploymentMeta(deployment)).toBeTruthy(); - }); - - it('should return false when there is not enough information', () => { - expect(vm.hasDeploymentMeta()).toBeFalsy(); - expect(vm.hasDeploymentMeta({ url: 'Diplo' })).toBeFalsy(); - expect(vm.hasDeploymentMeta({ name: 'Diplo' })).toBeFalsy(); - }); - }); - - describe('stopEnvironment', () => { - const url = '/foo/bar'; - const returnPromise = () => new Promise((resolve) => { - resolve({ - data: { - redirect_url: url, - }, - }); - }); - const mockStopEnvironment = () => { - vm.stopEnvironment(deploymentMockData); - return vm; - }; - - it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => { - spyOn(window, 'confirm').and.returnValue(true); - spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true)); - spyOn(urlUtils, 'visitUrl').and.returnValue(true); - vm = mockStopEnvironment(); - - expect(window.confirm).toHaveBeenCalled(); - expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url); - setTimeout(() => { - expect(urlUtils.visitUrl).toHaveBeenCalledWith(url); - done(); - }, 333); - }); - - it('should show a confirm dialog but should not work if the dialog is rejected', () => { - spyOn(window, 'confirm').and.returnValue(false); - spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false)); - vm = mockStopEnvironment(); - - expect(window.confirm).toHaveBeenCalled(); - expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled(); - }); - }); - }); - - describe('template', () => { - let vm; - let el; - const [deployment] = deploymentMockData; - - beforeEach(() => { - vm = createComponent(deploymentMockData); - el = vm.$el; - }); - - it('should render template elements correctly', () => { - expect(el.classList.contains('mr-widget-heading')).toBeTruthy(); - expect(el.querySelector('.js-icon-link')).toBeDefined(); - expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deployment.url); - expect(el.querySelector('.js-deploy-meta').innerText).toContain(deployment.name); - expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deployment.external_url); - expect(el.querySelector('.js-deploy-url').innerText).toContain(deployment.external_url_formatted); - expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.formatDate(deployment.deployed_at)); - expect(el.querySelector('.js-mr-memory-usage')).toBeDefined(); - expect(el.querySelector('button')).toBeDefined(); - }); - - it('should list multiple deployments', (done) => { - vm.mr.deployments.push(deployment); - vm.mr.deployments.push(deployment); - - Vue.nextTick(() => { - expect(el.querySelectorAll('.ci-widget').length).toEqual(3); - expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(3); - done(); - }); - }); - - it('should not have some elements when there is not enough data', (done) => { - vm.mr.deployments = [{}]; - - Vue.nextTick(() => { - expect(el.querySelectorAll('.js-deploy-meta').length).toEqual(0); - expect(el.querySelectorAll('.js-deploy-url').length).toEqual(0); - expect(el.querySelectorAll('.js-deploy-time').length).toEqual(0); - expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(0); - expect(el.querySelectorAll('.button').length).toEqual(0); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 32876ca66ac..e55c7649d40 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -123,17 +123,6 @@ describe('mrWidgetOptions', () => { expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false); }); }); - - describe('shouldRenderDeployments', () => { - it('should return false for the initial data', () => { - expect(vm.shouldRenderDeployments).toBeFalsy(); - }); - - it('should return true if there is deployments', () => { - vm.mr.deployments.push({}, {}); - expect(vm.shouldRenderDeployments).toBeTruthy(); - }); - }); }); describe('methods', () => { @@ -189,16 +178,16 @@ describe('mrWidgetOptions', () => { describe('fetchDeployments', () => { it('should fetch deployments', (done) => { - spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ deployment: 1 }])); + spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }])); vm.fetchDeployments(); setTimeout(() => { expect(vm.service.fetchDeployments).toHaveBeenCalled(); expect(vm.mr.deployments.length).toEqual(1); - expect(vm.mr.deployments[0].deployment).toEqual(1); + expect(vm.mr.deployments[0].id).toBe(1); done(); - }, 333); + }); }); }); @@ -368,34 +357,6 @@ describe('mrWidgetOptions', () => { }); }); - describe('components', () => { - it('should register all components', () => { - const comps = mrWidgetOptions.components; - expect(comps['mr-widget-header']).toBeDefined(); - expect(comps['mr-widget-merge-help']).toBeDefined(); - expect(comps['mr-widget-pipeline']).toBeDefined(); - expect(comps['mr-widget-deployment']).toBeDefined(); - expect(comps['mr-widget-related-links']).toBeDefined(); - expect(comps['mr-widget-merged']).toBeDefined(); - expect(comps['mr-widget-closed']).toBeDefined(); - expect(comps['mr-widget-merging']).toBeDefined(); - expect(comps['mr-widget-failed-to-merge']).toBeDefined(); - expect(comps['mr-widget-wip']).toBeDefined(); - expect(comps['mr-widget-archived']).toBeDefined(); - expect(comps['mr-widget-conflicts']).toBeDefined(); - expect(comps['mr-widget-nothing-to-merge']).toBeDefined(); - expect(comps['mr-widget-not-allowed']).toBeDefined(); - expect(comps['mr-widget-missing-branch']).toBeDefined(); - expect(comps['mr-widget-ready-to-merge']).toBeDefined(); - expect(comps['mr-widget-checking']).toBeDefined(); - expect(comps['mr-widget-unresolved-discussions']).toBeDefined(); - expect(comps['mr-widget-pipeline-blocked']).toBeDefined(); - expect(comps['mr-widget-pipeline-failed']).toBeDefined(); - expect(comps['mr-widget-merge-when-pipeline-succeeds']).toBeDefined(); - expect(comps['mr-widget-maintainer-edit']).toBeDefined(); - }); - }); - describe('rendering relatedLinks', () => { beforeEach((done) => { vm.mr.relatedLinks = { @@ -454,4 +415,34 @@ describe('mrWidgetOptions', () => { }); }); }); + + describe('rendering deployments', () => { + const deploymentMockData = { + id: 15, + name: 'review/diplo', + url: '/root/acets-review-apps/environments/15', + stop_url: '/root/acets-review-apps/environments/15/stop', + metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', + metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics', + external_url: 'http://diplo.', + external_url_formatted: 'diplo.', + deployed_at: '2017-03-22T22:44:42.258Z', + deployed_at_formatted: 'Mar 22, 2017 10:44pm', + }; + + beforeEach((done) => { + vm.mr.deployments.push({ + ...deploymentMockData, + }, { + ...deploymentMockData, + id: deploymentMockData.id + 1, + }); + + vm.$nextTick(done); + }); + + it('renders multiple deployments', () => { + expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2); + }); + }); }); diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 9f42cf1dfca..5cb1f4deb5f 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -61,7 +61,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") } before do - gitlab_shell.add_repository(repository_storage, hashed_path) + gitlab_shell.create_repository(repository_storage, hashed_path) repository = Rugged::Repository.new(repo_path) repository.config['gitlab.fullpath'] = 'to/repo' end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 52c9876cbb6..54ada3e423f 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -681,7 +681,7 @@ describe Gitlab::Git::Repository, seed_helper: true do subject { new_repository.fetch_repository_as_mirror(repository) } before do - Gitlab::Shell.new.add_repository('default', 'my_project') + Gitlab::Shell.new.create_repository('default', 'my_project') end after do diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 56b45d8da3c..14b59c5e945 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::Shell do it { is_expected.to respond_to :add_key } it { is_expected.to respond_to :remove_key } - it { is_expected.to respond_to :add_repository } + it { is_expected.to respond_to :create_repository } it { is_expected.to respond_to :remove_repository } it { is_expected.to respond_to :fork_repository } @@ -402,8 +402,8 @@ describe Gitlab::Shell do allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800) end - describe '#add_repository' do - shared_examples '#add_repository' do + describe '#create_repository' do + shared_examples '#create_repository' do let(:repository_storage) { 'default' } let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } let(:repo_name) { 'project/path' } @@ -414,7 +414,7 @@ describe Gitlab::Shell do end it 'creates a repository' do - expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_truthy + expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_truthy expect(File.stat(created_path).mode & 0o777).to eq(0o770) @@ -426,19 +426,19 @@ describe Gitlab::Shell do it 'returns false when the command fails' do FileUtils.mkdir_p(File.dirname(created_path)) # This file will block the creation of the repo's .git directory. That - # should cause #add_repository to fail. + # should cause #create_repository to fail. FileUtils.touch(created_path) - expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_falsy + expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_falsy end end context 'with gitaly' do - it_behaves_like '#add_repository' + it_behaves_like '#create_repository' end context 'without gitaly', :skip_gitaly_mock do - it_behaves_like '#add_repository' + it_behaves_like '#create_repository' end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index c27313ed88b..6e202de0db9 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1424,6 +1424,17 @@ describe Ci::Build do { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true }, { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, + { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, + { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false }, + { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }, + { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, + { key: 'CI_BUILD_TOKEN', value: build.token, public: false }, + { key: 'CI_BUILD_REF', value: build.sha, public: true }, + { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, + { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true }, + { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true }, + { key: 'CI_BUILD_NAME', value: 'test', public: true }, + { key: 'CI_BUILD_STAGE', value: 'test', public: true }, { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true }, { key: 'CI_PROJECT_NAME', value: project.path, public: true }, { key: 'CI_PROJECT_PATH', value: project.full_path, public: true }, @@ -1433,9 +1444,7 @@ describe Ci::Build do { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, - { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, - { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false }, - { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false } + { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true } ] end @@ -1834,39 +1843,6 @@ describe Ci::Build do it { is_expected.to include(ci_config_path) } end - context 'returns variables in valid order' do - let(:build_pre_var) { { key: 'build', value: 'value' } } - let(:project_pre_var) { { key: 'project', value: 'value' } } - let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } } - let(:build_yaml_var) { { key: 'yaml', value: 'value' } } - - before do - allow(build).to receive(:predefined_variables) { [build_pre_var] } - allow(build).to receive(:yaml_variables) { [build_yaml_var] } - - allow_any_instance_of(Project) - .to receive(:predefined_variables) { [project_pre_var] } - - allow_any_instance_of(Project) - .to receive(:secret_variables_for) - .with(ref: 'master', environment: nil) do - [create(:ci_variable, key: 'secret', value: 'value')] - end - - allow_any_instance_of(Ci::Pipeline) - .to receive(:predefined_variables) { [pipeline_pre_var] } - end - - it do - is_expected.to eq( - [build_pre_var, - project_pre_var, - pipeline_pre_var, - build_yaml_var, - { key: 'secret', value: 'value', public: false }]) - end - end - context 'when using auto devops' do context 'and is enabled' do before do @@ -1890,6 +1866,81 @@ describe Ci::Build do end end end + + context 'when pipeline variable overrides build variable' do + before do + build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }] + pipeline.variables.build(key: 'MYVAR', value: 'pipeline value') + end + + it 'overrides YAML variable using a pipeline variable' do + variables = subject.reverse.uniq { |variable| variable[:key] }.reverse + + expect(variables) + .not_to include(key: 'MYVAR', value: 'myvar', public: true) + expect(variables) + .to include(key: 'MYVAR', value: 'pipeline value', public: false) + end + end + + describe 'variables ordering' do + context 'when variables hierarchy is stubbed' do + let(:build_pre_var) { { key: 'build', value: 'value' } } + let(:project_pre_var) { { key: 'project', value: 'value' } } + let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } } + let(:build_yaml_var) { { key: 'yaml', value: 'value' } } + + before do + allow(build).to receive(:predefined_variables) { [build_pre_var] } + allow(build).to receive(:yaml_variables) { [build_yaml_var] } + + allow_any_instance_of(Project) + .to receive(:predefined_variables) { [project_pre_var] } + + allow_any_instance_of(Project) + .to receive(:secret_variables_for) + .with(ref: 'master', environment: nil) do + [create(:ci_variable, key: 'secret', value: 'value')] + end + + allow_any_instance_of(Ci::Pipeline) + .to receive(:predefined_variables) { [pipeline_pre_var] } + end + + it 'returns variables in order depending on resource hierarchy' do + is_expected.to eq( + [build_pre_var, + project_pre_var, + pipeline_pre_var, + build_yaml_var, + { key: 'secret', value: 'value', public: false }]) + end + end + + context 'when build has environment and user-provided variables' do + let(:expected_variables) do + predefined_variables.map { |variable| variable.fetch(:key) } + + %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG + CI_ENVIRONMENT_URL] + end + + before do + create(:environment, project: build.project, + name: 'staging') + + build.yaml_variables = [{ key: 'YAML_VARIABLE', + value: 'var', + public: true }] + build.environment = 'staging' + end + + it 'matches explicit variables ordering' do + received_variables = subject.map { |variable| variable.fetch(:key) } + + expect(received_variables).to eq expected_variables + end + end + end end describe 'state transition: any => [:pending]' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 14d234f6aab..86bb2fefae1 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -172,10 +172,10 @@ describe Ci::Pipeline, :mailer do it { is_expected.to be_an(Array) } - it 'includes the defined keys' do - keys = subject.map { |v| v[:key] } + it 'includes all predefined variables in a valid order' do + keys = subject.map { |variable| variable.fetch(:key) } - expect(keys).to include('CI_PIPELINE_ID', 'CI_CONFIG_PATH', 'CI_PIPELINE_SOURCE') + expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE] end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e970cd7dfdb..4cf8d861595 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1378,7 +1378,7 @@ describe Project do context 'using a regular repository' do it 'creates the repository' do - expect(shell).to receive(:add_repository) + expect(shell).to receive(:create_repository) .with(project.repository_storage, project.disk_path) .and_return(true) @@ -1388,7 +1388,7 @@ describe Project do end it 'adds an error if the repository could not be created' do - expect(shell).to receive(:add_repository) + expect(shell).to receive(:create_repository) .with(project.repository_storage, project.disk_path) .and_return(false) @@ -1402,7 +1402,7 @@ describe Project do context 'using a forked repository' do it 'does nothing' do expect(project).to receive(:forked?).and_return(true) - expect(shell).not_to receive(:add_repository) + expect(shell).not_to receive(:create_repository) project.create_repository end @@ -1421,7 +1421,7 @@ describe Project do allow(project).to receive(:repository_exists?) .and_return(false) - allow(shell).to receive(:add_repository) + allow(shell).to receive(:create_repository) .with(project.repository_storage_path, project.disk_path) .and_return(true) @@ -1445,7 +1445,7 @@ describe Project do allow(project).to receive(:repository_exists?) .and_return(false) - expect(shell).to receive(:add_repository) + expect(shell).to receive(:create_repository) .with(project.repository_storage, project.disk_path) .and_return(true) diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 8b4b5873704..d87c1ca14f0 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -74,7 +74,7 @@ describe ProjectWiki do # Create a fresh project which will not have a wiki project_wiki = described_class.new(create(:project), user) gitlab_shell = double(:gitlab_shell) - allow(gitlab_shell).to receive(:add_repository) + allow(gitlab_shell).to receive(:create_repository) allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell) expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError) diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 9a44dfde41b..8471467d2fa 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -164,7 +164,7 @@ describe Projects::CreateService, '#execute' do context 'with legacy storage' do before do - gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") + gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing") end after do @@ -200,7 +200,7 @@ describe Projects::CreateService, '#execute' do end before do - gitlab_shell.add_repository(repository_storage, hashed_path) + gitlab_shell.create_repository(repository_storage, hashed_path) end after do diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 409d5de8d43..d1011b07db6 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -108,7 +108,7 @@ describe Projects::ForkService do let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } before do - gitlab_shell.add_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}") + gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}") end after do diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index ae0e22e3dc0..ce567fe3879 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -151,7 +151,7 @@ describe Projects::TransferService do before do group.add_owner(user) - unless gitlab_shell.add_repository(repository_storage, "#{group.full_path}/#{project.path}") + unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}") raise 'failed to add repository' end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index d454ac0bda5..f3f97b6b921 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -196,7 +196,7 @@ describe Projects::UpdateService do let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) } before do - gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") + gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing") end after do |