From ec918997eef4bdc4e2df1fe961083350eddf2492 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Mon, 29 Jan 2018 00:43:47 +0200 Subject: Improve issue mr and branch dropdown button --- app/assets/stylesheets/pages/note_form.scss | 1 - app/views/projects/issues/_new_branch.html.haml | 4 ++-- changelogs/unreleased/fix-improve-issue-note-dropdown.yml | 5 +++++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/fix-improve-issue-note-dropdown.yml diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index bf8eb4c1f06..4a528bc2bb1 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -410,7 +410,6 @@ width: 298px; } - @media (max-width: $screen-xs-max) { display: flex; width: 100%; diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 331d62cf247..37b00a14fc6 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -15,10 +15,10 @@ %span.text Checking branch availability… .btn-group.available.hide - %button.btn.js-create-merge-request.btn-default{ type: 'button', data: { action: data_action } } + %button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } } = value - %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-default.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' } } } + %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' } } } = icon('caret-down') %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors{ data: { dropdown: true } } diff --git a/changelogs/unreleased/fix-improve-issue-note-dropdown.yml b/changelogs/unreleased/fix-improve-issue-note-dropdown.yml new file mode 100644 index 00000000000..aaf4811c64a --- /dev/null +++ b/changelogs/unreleased/fix-improve-issue-note-dropdown.yml @@ -0,0 +1,5 @@ +--- +title: Improve issue note dropdown and mr button +merge_request: 16758 +author: George Tsiolis +type: changed -- cgit v1.2.1 From 1533cf1053d0dc5b7122ac3ac805a6f567b53618 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 31 Jan 2018 11:37:30 +0000 Subject: Moves merge help into a vue component --- .../components/mr_widget_merge_help.js | 23 --------- .../components/mr_widget_merge_help.vue | 39 +++++++++++++++ .../components/states/mr_widget_missing_branch.js | 2 +- .../vue_merge_request_widget/dependencies.js | 2 +- .../components/mr_widget_merge_help_spec.js | 57 +++++++++------------- 5 files changed, 63 insertions(+), 60 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js deleted file mode 100644 index 1d9f9863dd9..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js +++ /dev/null @@ -1,23 +0,0 @@ -export default { - name: 'MRWidgetMergeHelp', - props: { - missingBranch: { type: String, required: false, default: '' }, - }, - template: ` -
- - - can merge this merge request manually using the - - command line - -
- `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue new file mode 100644 index 00000000000..bc27bbdd364 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue @@ -0,0 +1,39 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js index 303877d6fbf..7733fb74afe 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js @@ -1,6 +1,6 @@ import statusIcon from '../mr_widget_status_icon.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; -import mrWidgetMergeHelp from '../../components/mr_widget_merge_help'; +import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue'; export default { name: 'MRWidgetMissingBranch', diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 2917090e073..0e869a2aaac 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -12,7 +12,7 @@ export { default as Vue } from 'vue'; export { default as SmartInterval } from '~/smart_interval'; export { default as WidgetHeader } from './components/mr_widget_header'; -export { default as WidgetMergeHelp } from './components/mr_widget_merge_help'; +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 WidgetRelatedLinks } from './components/mr_widget_related_links'; diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js index 4da4fc82c26..1ed872fafe7 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js @@ -1,51 +1,38 @@ import Vue from 'vue'; -import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help'; +import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; -const props = { - missingBranch: 'this-is-not-the-branch-you-are-looking-for', -}; -const text = `If the ${props.missingBranch} branch exists in your local repository`; -const createComponent = () => { - const Component = Vue.extend(mergeHelpComponent); - return new Component({ - el: document.createElement('div'), - propsData: props, - }); -}; +const text = `If the ${props.missingBranch} branch exists in your local repository`; describe('MRWidgetMergeHelp', () => { - describe('props', () => { - it('should have props', () => { - const { missingBranch } = mergeHelpComponent.props; - const MissingBranchTypeClass = missingBranch.type; - - expect(new MissingBranchTypeClass() instanceof String).toBeTruthy(); - expect(missingBranch.required).toBeFalsy(); - expect(missingBranch.default).toEqual(''); - }); + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(mergeHelpComponent); }); - describe('template', () => { - let vm; - let el; + afterEach(() => { + vm.$destroy(); + }); + fdescribe('with missing branch', () => { beforeEach(() => { - vm = createComponent(); - el = vm.$el; + vm = mountComponent(Component, { + missingBranch: 'this-is-not-the-branch-you-are-looking-for', + }); }); - it('should have the correct elements', () => { - expect(el.classList.contains('mr-widget-help')).toBeTruthy(); - expect(el.textContent).toContain(text); + it('renders missing branch information', () => { + console.log('', vm.$el); + }); + }); - it('should not show missing branch name if missingBranch props is not provided', (done) => { - vm.missingBranch = null; - Vue.nextTick(() => { - expect(el.textContent).not.toContain(text); - done(); - }); + describe('without missing branch', () => { + beforeEach(() => { + vm = mountComponent(Component); }); }); }); -- cgit v1.2.1 From 28bbb4cb47ebb8669643e8fad34b75ea34f18e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20=22BKC=22=20Carlb=C3=A4cker?= Date: Fri, 26 Jan 2018 09:00:22 +0100 Subject: Migrate Gitlab::Git::Repository#write_config to Gitaly - Add tests for Repository#write_config --- GITALY_SERVER_VERSION | 2 +- lib/gitlab/git.rb | 1 + lib/gitlab/git/repository.rb | 14 +++++++++- lib/gitlab/gitaly_client/repository_service.rb | 13 +++++++++ spec/lib/gitlab/git/repository_spec.rb | 38 ++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 62df9f538d8..4a7076db09a 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.76.0 +0.77.0 diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 71647099f83..9e8caa259d2 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -6,6 +6,7 @@ module Gitlab CommandError = Class.new(StandardError) CommitError = Class.new(StandardError) + OSError = Class.new(StandardError) class << self include Gitlab::EncodingHelper diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 8137c582c0f..8ba12808c10 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1311,7 +1311,15 @@ module Gitlab # rubocop:enable Metrics/ParameterLists def write_config(full_path:) - rugged.config['gitlab.fullpath'] = full_path if full_path.present? + return unless full_path.present? + + gitaly_migrate(:write_config) do |is_enabled| + if is_enabled + gitaly_repository_client.write_config(full_path: full_path) + else + rugged_write_config(full_path: full_path) + end + end end def gitaly_repository @@ -1451,6 +1459,10 @@ module Gitlab end end + def rugged_write_config(full_path:) + rugged.config['gitlab.fullpath'] = full_path + end + def shell_write_ref(ref_path, ref, old_ref) raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ') raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00") diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 7adf32af209..60706b4f0d8 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -219,6 +219,19 @@ module Gitlab true end + + def write_config(full_path:) + request = Gitaly::WriteConfigRequest.new(repository: @gitaly_repo, full_path: full_path) + response = GitalyClient.call( + @storage, + :repository_service, + :write_config, + request, + timeout: GitalyClient.fast_timeout + ) + + raise Gitlab::Git::OSError.new(response.error) unless response.error.empty? + end end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index bf01e6ef8e8..6206e9bb0fb 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1790,6 +1790,44 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe '#write_config' do + before do + repository.rugged.config["gitlab.fullpath"] = repository.path + end + + shared_examples 'writing repo config' do + context 'is given a path' do + it 'writes it to disk' do + repository.write_config(full_path: "not-the/real-path.git") + + config = File.read(File.join(repository.path, "config")) + + expect(config).to include("[gitlab]") + expect(config).to include("fullpath = not-the/real-path.git") + end + end + + context 'it is given an empty path' do + it 'does not write it to disk' do + repository.write_config(full_path: "") + + config = File.read(File.join(repository.path, "config")) + + expect(config).to include("[gitlab]") + expect(config).to include("fullpath = #{repository.path}") + end + end + end + + context "when gitaly_write_config is enabled" do + it_behaves_like "writing repo config" + end + + context "when gitaly_write_config is disabled", :disable_gitaly do + it_behaves_like "writing repo config" + end + end + describe '#merge' do let(:repository) do Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') -- cgit v1.2.1 From 27863b10d110e38b0ae7bfd1fefc0cc32646b748 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 31 Jan 2018 17:53:09 +0000 Subject: Refactor repository_storages_options_for_select --- app/helpers/application_settings_helper.rb | 4 ++-- app/views/admin/application_settings/_form.html.haml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 8ef561d90e6..e91e1d29d64 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -96,12 +96,12 @@ module ApplicationSettingsHelper ] end - def repository_storages_options_for_select + def repository_storages_options_for_select(selected) options = Gitlab.config.repositories.storages.map do |name, storage| ["#{name} - #{storage['path']}", name] end - options_for_select(options, @application_setting.repository_storages) + options_for_select(options, selected) end def sidekiq_queue_options_for_select diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index ba4ca88a8a9..fb5e6f337a7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -537,7 +537,8 @@ .form-group = f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2' .col-sm-10 - = f.select :repository_storages, repository_storages_options_for_select, {include_hidden: false}, multiple: true, class: 'form-control' + = f.select :repository_storages, repository_storages_options_for_select(@application_setting.repository_storages), + {include_hidden: false}, multiple: true, class: 'form-control' .help-block Manage repository storage paths. Learn more in the = succeed "." do -- cgit v1.2.1 From 3d89a03e727e9e991f0caab74ce31d4c7a1c0246 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 1 Feb 2018 13:05:37 +0000 Subject: Moves widget header into a vue file --- .../components/mr_widget_header.js | 117 --------- .../components/mr_widget_header.vue | 139 +++++++++++ .../components/mr_widget_merge_help.vue | 4 +- .../vue_merge_request_widget/dependencies.js | 2 +- .../components/mr_widget_header_spec.js | 273 ++++++++++++++------- .../components/mr_widget_merge_help_spec.js | 28 ++- 6 files changed, 351 insertions(+), 212 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js deleted file mode 100644 index de6e5149a87..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js +++ /dev/null @@ -1,117 +0,0 @@ -import tooltip from '../../vue_shared/directives/tooltip'; -import { pluralize } from '../../lib/utils/text_utility'; -import icon from '../../vue_shared/components/icon.vue'; - -export default { - name: 'MRWidgetHeader', - props: { - mr: { type: Object, required: true }, - }, - directives: { - tooltip, - }, - components: { - icon, - }, - computed: { - shouldShowCommitsBehindText() { - return this.mr.divergedCommitsCount > 0; - }, - commitsText() { - return pluralize('commit', this.mr.divergedCommitsCount); - }, - branchNameClipboardData() { - // This supports code in app/assets/javascripts/copy_to_clipboard.js that - // works around ClipboardJS limitations to allow the context-specific - // copy/pasting of plain text or GFM. - return JSON.stringify({ - text: this.mr.sourceBranch, - gfm: `\`${this.mr.sourceBranch}\``, - }); - }, - }, - methods: { - isBranchTitleLong(branchTitle) { - return branchTitle.length > 32; - }, - }, - template: ` - - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue new file mode 100644 index 00000000000..b82026ee1bb --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -0,0 +1,139 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue index bc27bbdd364..fe09c53272e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue @@ -7,14 +7,14 @@ missingBranch: { type: String, required: false, - default: '' + default: '', }, }, computed: { missingBranchInfo() { return sprintf( s__('mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the'), - { branch: this.missingBranch } + { branch: this.missingBranch }, ); }, }, diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 0e869a2aaac..04c09d4eda7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -11,7 +11,7 @@ export { default as Vue } from 'vue'; export { default as SmartInterval } from '~/smart_interval'; -export { default as WidgetHeader } from './components/mr_widget_header'; +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'; diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js index 93bb83ca8bd..5fb7093a078 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js @@ -1,121 +1,220 @@ import Vue from 'vue'; -import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header'; - -const createComponent = (mr) => { - const Component = Vue.extend(headerComponent); - return new Component({ - el: document.createElement('div'), - propsData: { mr }, - }); -}; +import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('MRWidgetHeader', () => { - describe('props', () => { - it('should have props', () => { - const { mr } = headerComponent.props; + let vm; + let Component; - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - }); + beforeEach(() => { + Component = Vue.extend(headerComponent); + }); + + afterEach(() => { + vm.$destroy(); }); describe('computed', () => { - let vm; - beforeEach(() => { - vm = createComponent({ - divergedCommitsCount: 12, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '/foo/bar/mr-widget-refactor', - targetBranch: 'master', + describe('shouldShowCommitsBehindText', () => { + it('return true when there are divergedCommitsCount', () => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 12, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'Link', + targetBranch: 'master', + } }); + + expect(vm.shouldShowCommitsBehindText).toEqual(true); + }); + + it('returns false where there are no divergedComits count', () => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 0, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'Link', + targetBranch: 'master', + } }); + expect(vm.shouldShowCommitsBehindText).toEqual(false); }); }); - it('shouldShowCommitsBehindText', () => { - expect(vm.shouldShowCommitsBehindText).toBeTruthy(); + describe('commitsText', () => { + it('returns singular when there is one commit', () => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 1, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'Link', + targetBranch: 'master', + } }); - vm.mr.divergedCommitsCount = 0; - expect(vm.shouldShowCommitsBehindText).toBeFalsy(); - }); + expect(vm.commitsText).toEqual('commit behind'); + }); - it('commitsText', () => { - expect(vm.commitsText).toEqual('commits'); + it('returns plural when there is more than one commit', () => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 2, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'Link', + targetBranch: 'master', + } }); - vm.mr.divergedCommitsCount = 1; - expect(vm.commitsText).toEqual('commit'); + expect(vm.commitsText).toEqual('commits behind'); + }); }); }); describe('template', () => { - let vm; - let el; - let mr; - const sourceBranchPath = '/foo/bar/mr-widget-refactor'; - - beforeEach(() => { - mr = { - divergedCommitsCount: 12, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: `mr-widget-refactor`, - sourceBranchRemoved: false, - targetBranchPath: 'foo/bar/commits-path', - targetBranchTreePath: 'foo/bar/tree/path', - targetBranch: 'master', - isOpen: true, - emailPatchesPath: '/mr/email-patches', - plainDiffPath: '/mr/plainDiffPath', - }; - - vm = createComponent(mr); - el = vm.$el; + describe('common elements', () => { + beforeEach(() => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 12, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'mr-widget-refactor', + sourceBranchRemoved: false, + targetBranchPath: 'foo/bar/commits-path', + targetBranchTreePath: 'foo/bar/tree/path', + targetBranch: 'master', + isOpen: true, + emailPatchesPath: '/mr/email-patches', + plainDiffPath: '/mr/plainDiffPath', + } }); + }); + + it('renders source branch link', () => { + expect( + vm.$el.querySelector('.js-source-branch').innerHTML, + ).toEqual('mr-widget-refactor'); + }); + + it('renders clipboard button', () => { + expect(vm.$el.querySelector('.btn-clipboard')).not.toEqual(null); + }); + + it('renders target branch', () => { + expect(vm.$el.querySelector('.js-target-branch').textContent.trim()).toEqual('master'); + }); }); - it('should render template elements correctly', () => { - expect(el.classList.contains('mr-source-target')).toBeTruthy(); - const sourceBranchLink = el.querySelectorAll('.label-branch')[0]; - const targetBranchLink = el.querySelectorAll('.label-branch')[1]; - const commitsCount = el.querySelector('.diverged-commits-count'); + describe('with an open merge request', () => { + afterEach(() => { + vm.$destroy(); + }); - expect(sourceBranchLink.textContent).toContain(mr.sourceBranch); - expect(targetBranchLink.textContent).toContain(mr.targetBranch); - expect(sourceBranchLink.querySelector('a').getAttribute('href')).toEqual(sourceBranchPath); - expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchTreePath); - expect(commitsCount.textContent).toContain('12 commits behind'); - expect(commitsCount.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath); + beforeEach(() => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 12, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'mr-widget-refactor', + sourceBranchRemoved: false, + targetBranchPath: 'foo/bar/commits-path', + targetBranchTreePath: 'foo/bar/tree/path', + targetBranch: 'master', + isOpen: true, + emailPatchesPath: '/mr/email-patches', + plainDiffPath: '/mr/plainDiffPath', + } }); + }); + + it('renders checkout branch button with modal trigger', () => { + const button = vm.$el.querySelector('.js-check-out-branch'); + + expect(button.textContent.trim()).toEqual('Check out branch'); + expect(button.getAttribute('data-target')).toEqual('#modal_merge_info'); + expect(button.getAttribute('data-toggle')).toEqual('modal'); + }); + + it('renders download dropdown with links', () => { + expect( + vm.$el.querySelector('.js-download-email-patches').textContent.trim(), + ).toEqual('Email patches'); - expect(el.textContent).toContain('Check out branch'); - expect(el.querySelectorAll('.dropdown li a')[0].getAttribute('href')).toEqual(mr.emailPatchesPath); - expect(el.querySelectorAll('.dropdown li a')[1].getAttribute('href')).toEqual(mr.plainDiffPath); + expect( + vm.$el.querySelector('.js-download-email-patches').getAttribute('href'), + ).toEqual('/mr/email-patches'); - expect(el.querySelector('a[href="#modal_merge_info"]').getAttribute('disabled')).toBeNull(); + expect( + vm.$el.querySelector('.js-download-plain-diff').textContent.trim(), + ).toEqual('Plain diff'); + + expect( + vm.$el.querySelector('.js-download-plain-diff').getAttribute('href'), + ).toEqual('/mr/plainDiffPath'); + }); }); - it('should not have right action links if the MR state is not open', (done) => { - vm.mr.isOpen = false; - Vue.nextTick(() => { - expect(el.textContent).not.toContain('Check out branch'); - expect(el.querySelectorAll('.dropdown li a').length).toEqual(0); - done(); + describe('with a closed merge request', () => { + beforeEach(() => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 12, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'mr-widget-refactor', + sourceBranchRemoved: false, + targetBranchPath: 'foo/bar/commits-path', + targetBranchTreePath: 'foo/bar/tree/path', + targetBranch: 'master', + isOpen: false, + emailPatchesPath: '/mr/email-patches', + plainDiffPath: '/mr/plainDiffPath', + } }); + }); + + it('does not render checkout branch button with modal trigger', () => { + const button = vm.$el.querySelector('.js-check-out-branch'); + + expect(button).toEqual(null); + }); + + it('does not render download dropdown with links', () => { + expect( + vm.$el.querySelector('.js-download-email-patches'), + ).toEqual(null); + + expect( + vm.$el.querySelector('.js-download-plain-diff'), + ).toEqual(null); }); }); - it('should not render diverged commits count if the MR has no diverged commits', (done) => { - vm.mr.divergedCommitsCount = null; - Vue.nextTick(() => { - expect(el.textContent).not.toContain('commits behind'); - expect(el.querySelectorAll('.diverged-commits-count').length).toEqual(0); - done(); + describe('without diverged commits', () => { + beforeEach(() => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 0, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'mr-widget-refactor', + sourceBranchRemoved: false, + targetBranchPath: 'foo/bar/commits-path', + targetBranchTreePath: 'foo/bar/tree/path', + targetBranch: 'master', + isOpen: true, + emailPatchesPath: '/mr/email-patches', + plainDiffPath: '/mr/plainDiffPath', + } }); + }); + + it('does not render diverged commits info', () => { + expect(vm.$el.querySelector('.diverged-commits-count')).toEqual(null); }); }); - it('should disable check out branch button if source branch has been removed', (done) => { - vm.mr.sourceBranchRemoved = true; + describe('with diverged commits', () => { + beforeEach(() => { + vm = mountComponent(Component, { mr: { + divergedCommitsCount: 12, + sourceBranch: 'mr-widget-refactor', + sourceBranchLink: 'mr-widget-refactor', + sourceBranchRemoved: false, + targetBranchPath: 'foo/bar/commits-path', + targetBranchTreePath: 'foo/bar/tree/path', + targetBranch: 'master', + isOpen: true, + emailPatchesPath: '/mr/email-patches', + plainDiffPath: '/mr/plainDiffPath', + } }); + }); - Vue.nextTick() - .then(() => { - expect(el.querySelector('a[href="#modal_merge_info"]').getAttribute('disabled')).toBe('disabled'); - done(); - }) - .catch(done.fail); + it('renders diverged commits info', () => { + expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual('(12 commits behind)'); + }); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js index 1ed872fafe7..f6656ad2e80 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js @@ -2,9 +2,6 @@ import Vue from 'vue'; import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; - -const text = `If the ${props.missingBranch} branch exists in your local repository`; - describe('MRWidgetMergeHelp', () => { let vm; let Component; @@ -17,7 +14,7 @@ describe('MRWidgetMergeHelp', () => { vm.$destroy(); }); - fdescribe('with missing branch', () => { + describe('with missing branch', () => { beforeEach(() => { vm = mountComponent(Component, { missingBranch: 'this-is-not-the-branch-you-are-looking-for', @@ -25,8 +22,16 @@ describe('MRWidgetMergeHelp', () => { }); it('renders missing branch information', () => { - console.log('', vm.$el); + expect( + vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ').replace(/\s\s+/g, ' '), + ).toEqual( + 'If the this-is-not-the-branch-you-are-looking-for branch exists in your local repository, you can merge this merge request manually using the command line', + ); + }); + it('renders element to open a modal', () => { + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info'); + expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal'); }); }); @@ -34,5 +39,18 @@ describe('MRWidgetMergeHelp', () => { beforeEach(() => { vm = mountComponent(Component); }); + + it('renders information about how to merge manually', () => { + expect( + vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ').replace(/\s\s+/g, ' '), + ).toEqual( + 'You can merge this merge request manually using the command line', + ); + }); + + it('renders element to open a modal', () => { + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info'); + expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal'); + }); }); }); -- cgit v1.2.1 From a82a31c799aadb0000d305dccfa0a47de2e13e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 1 Feb 2018 09:35:26 +0100 Subject: Ensure we save QA screenshots to an absolute path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to open the file address printed in the terminal, otherwise the address started with `tmp` which was relatove to the `qa/` dir. Signed-off-by: Rémy Coutable --- qa/qa/runtime/browser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index ce888b51ea5..a12d95683af 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -84,7 +84,7 @@ module QA config.javascript_driver = :chrome config.default_max_wait_time = 10 # https://github.com/mattheworiordan/capybara-screenshot/issues/164 - config.save_path = 'tmp' + config.save_path = File.expand_path('../../tmp', __dir__) end end -- cgit v1.2.1 From 922154ce44e0e9a86b3c1d28e4e0c35e03f0bb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 1 Feb 2018 15:11:33 +0100 Subject: QA::Page::Component::Dropzone#initialize needs a QA::Page::Base object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- qa/qa/page/component/dropzone.rb | 2 ++ qa/qa/page/project/issue/show.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/qa/qa/page/component/dropzone.rb b/qa/qa/page/component/dropzone.rb index 5e6fdff20eb..15bdc742fda 100644 --- a/qa/qa/page/component/dropzone.rb +++ b/qa/qa/page/component/dropzone.rb @@ -4,6 +4,8 @@ module QA class Dropzone attr_reader :page, :container + # page - A QA::Page::Base object + # container - CSS selector of the comment textarea's container def initialize(page, container) @page = page @container = container diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 364a2c61665..5bc0598a524 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -27,7 +27,7 @@ module QA fill_in(with: text, name: 'note[note]') unless attachment.nil? - QA::Page::Component::Dropzone.new(page, '.new-note') + QA::Page::Component::Dropzone.new(self, '.new-note') .attach_file(attachment) end -- cgit v1.2.1 From c8a17cd1bd5fe16b6374ba93cc70a04cdeedbe21 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 1 Feb 2018 15:16:34 +0000 Subject: Update spec to match link being changed into a button --- spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb index fb73ab05f87..dbca279569a 100644 --- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb +++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb @@ -61,7 +61,7 @@ describe 'Merge request > User selects branches for new MR', :js do fill_in "merge_request_title", with: "Orphaned MR test" click_button "Submit merge request" - click_link "Check out branch" + click_button "Check out branch" expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch' end -- cgit v1.2.1 From f01e9c1ef65d77d10807f98cde9ce5f5911787c8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 1 Feb 2018 16:23:32 +0100 Subject: Finish any remaining jobs for issues.closed_at In the event of Sidekiq jobs getting lost there may be some rows left to migrate. This migration ensures any remaining jobs are completed and that all data has been migrated. This fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/41595 --- changelogs/unreleased/issues-closed-at-steal.yml | 5 +++ ...201145907_migrate_remaining_issues_closed_at.rb | 42 ++++++++++++++++++++++ db/schema.rb | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/issues-closed-at-steal.yml create mode 100644 db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb diff --git a/changelogs/unreleased/issues-closed-at-steal.yml b/changelogs/unreleased/issues-closed-at-steal.yml new file mode 100644 index 00000000000..a5f0898995f --- /dev/null +++ b/changelogs/unreleased/issues-closed-at-steal.yml @@ -0,0 +1,5 @@ +--- +title: Finish any remaining jobs for issues.closed_at +merge_request: +author: +type: other diff --git a/db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb b/db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb new file mode 100644 index 00000000000..7cb913bb2bf --- /dev/null +++ b/db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb @@ -0,0 +1,42 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + include EachBatch + end + + def up + Gitlab::BackgroundMigration.steal('CopyColumn') + Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange') + + # It's possible the cleanup job was killed which means we need to manually + # migrate any remaining rows. + migrate_remaining_rows if migrate_column_type? + end + + def down + end + + def migrate_remaining_rows + Issue.where('closed_at_for_type_change IS NULL AND closed_at IS NOT NULL').each_batch do |batch| + batch.update_all('closed_at_for_type_change = closed_at') + end + + cleanup_concurrent_column_type_change(:issues, :closed_at) + end + + def migrate_column_type? + # Some environments may have already executed the previous version of this + # migration, thus we don't need to migrate those environments again. + column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 4e82a688725..0d97b6f9ddd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180115201419) do +ActiveRecord::Schema.define(version: 20180201145907) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 3fed0302cb383ca3714c2fc540ac777409020862 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 1 Feb 2018 16:13:10 +0000 Subject: Transform `a` tag with button role into a `button` --- .../components/mr_widget_merge_help.vue | 10 ++++++---- .../vue_mr_widget/components/mr_widget_merge_help_spec.js | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue index fe09c53272e..62b61e1f41f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue @@ -29,11 +29,13 @@ {{ s__("mrWidget|You can merge this merge request manually using the") }} - + data-target="#modal_merge_info" + > {{ s__("mrWidget|command line") }} - + diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js index f6656ad2e80..cc43639f576 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js @@ -29,9 +29,9 @@ describe('MRWidgetMergeHelp', () => { ); }); - it('renders element to open a modal', () => { - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info'); - expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal'); + it('renders button to open help modal', () => { + expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual('#modal_merge_info'); + expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual('modal'); }); }); @@ -49,8 +49,8 @@ describe('MRWidgetMergeHelp', () => { }); it('renders element to open a modal', () => { - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info'); - expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal'); + expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual('#modal_merge_info'); + expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual('modal'); }); }); }); -- cgit v1.2.1 From 9ee5a9039b0d8cde6f0eef290b90a6dca229016e Mon Sep 17 00:00:00 2001 From: Alexandre Figura Date: Thu, 1 Feb 2018 16:41:47 +0000 Subject: Fix typo in gitlab_runner_chart.md When Gitlab URL is set as `gitlabURL` in the configuration file, `helm install` is not happy and complains about a missing `gitlabUrl`. --- doc/install/kubernetes/gitlab_runner_chart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md index ca9c95aeced..1f53e12d5f8 100644 --- a/doc/install/kubernetes/gitlab_runner_chart.md +++ b/doc/install/kubernetes/gitlab_runner_chart.md @@ -31,7 +31,7 @@ The default configuration can always be found in the [values.yaml](https://gitla In order for GitLab Runner to function, your config file **must** specify the following: - - `gitlabURL` - the GitLab Server URL (with protocol) to register the runner against + - `gitlabUrl` - the GitLab Server URL (with protocol) to register the runner against - `runnerRegistrationToken` - The Registration Token for adding new Runners to the GitLab Server. This must be retrieved from your GitLab Instance. See the [GitLab Runner Documentation](../../ci/runners/README.md#creating-and-registering-a-runner) for more information. @@ -47,7 +47,7 @@ Here is a snippet of the important settings: ## The GitLab Server URL (with protocol) that want to register the runner against ## ref: https://docs.gitlab.com/runner/commands/README.html#gitlab-runner-register ## -gitlabURL: http://gitlab.your-domain.com/ +gitlabUrl: http://gitlab.your-domain.com/ ## The Registration Token for adding new Runners to the GitLab Server. This must ## be retreived from your GitLab Instance. -- cgit v1.2.1 From 8128ecaecc038224285d713ca08b136462d6f365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 1 Feb 2018 18:01:47 +0100 Subject: Speed-up the gitlab_git_test job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be18520b876..b4afa953175 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -738,8 +738,9 @@ cache gems: gitlab_git_test: <<: *dedicated-runner <<: *except-docs-and-qa - <<: *pull-cache variables: SETUP_DB: "false" + before_script: [] + cache: {} script: - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes -- cgit v1.2.1 From 391d1915c8a9e6f723594a6f4930dcd90fe1bc1a Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Thu, 20 Jul 2017 17:54:36 +0700 Subject: Hide pipeline schedule 'take ownership' for current owner --- app/policies/ci/pipeline_schedule_policy.rb | 8 ++++ .../_pipeline_schedule.html.haml | 3 +- ...-user-interface-bugs-for-schedule-pipelines.yml | 5 +++ spec/policies/ci/pipeline_schedule_policy_spec.rb | 14 +++++++ .../_pipeline_schedule.html.haml_spec.rb | 47 ++++++++++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml create mode 100644 spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb diff --git a/app/policies/ci/pipeline_schedule_policy.rb b/app/policies/ci/pipeline_schedule_policy.rb index abcf536b2f7..dc7a4aed577 100644 --- a/app/policies/ci/pipeline_schedule_policy.rb +++ b/app/policies/ci/pipeline_schedule_policy.rb @@ -10,6 +10,10 @@ module Ci can?(:developer_access) && pipeline_schedule.owned_by?(@user) end + condition(:non_owner_of_schedule) do + !pipeline_schedule.owned_by?(@user) + end + rule { can?(:developer_access) }.policy do enable :play_pipeline_schedule end @@ -19,6 +23,10 @@ module Ci enable :admin_pipeline_schedule end + rule { can?(:master_access) & non_owner_of_schedule }.policy do + enable :take_ownership_pipeline_schedule + end + rule { protected_ref }.prevent :play_pipeline_schedule end end diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index 800e234275c..a8692b83b07 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -29,9 +29,10 @@ - if can?(current_user, :play_pipeline_schedule, pipeline_schedule) = link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('Play'), class: 'btn' do = icon('play') - - if can?(current_user, :update_pipeline_schedule, pipeline_schedule) + - if can?(current_user, :take_ownership_pipeline_schedule, pipeline_schedule) = link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do = s_('PipelineSchedules|Take ownership') + - if can?(current_user, :update_pipeline_schedule, pipeline_schedule) = link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn' do = icon('pencil') - if can?(current_user, :admin_pipeline_schedule, pipeline_schedule) diff --git a/changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml b/changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml new file mode 100644 index 00000000000..f3a04469884 --- /dev/null +++ b/changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Hide pipeline schedule take ownership for current owner +merge_request: 12986 +author: +type: fixed diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb index 1b0e9fac355..c0c3eda4911 100644 --- a/spec/policies/ci/pipeline_schedule_policy_spec.rb +++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb @@ -88,5 +88,19 @@ describe Ci::PipelineSchedulePolicy, :models do expect(policy).to be_allowed :admin_pipeline_schedule end end + + describe 'rules for non-owner of schedule' do + let(:owner) { create(:user) } + + before do + project.add_master(owner) + project.add_master(user) + pipeline_schedule.update(owner: owner) + end + + it 'includes abilities to take ownership' do + expect(policy).to be_allowed :take_ownership_pipeline_schedule + end + end end end diff --git a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb new file mode 100644 index 00000000000..6e7d8db99c4 --- /dev/null +++ b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'projects/pipeline_schedules/_pipeline_schedule' do + let(:owner) { create(:user) } + let(:master) { create(:user) } + let(:project) { create(:project) } + let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) } + + before do + assign(:project, project) + + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:pipeline_schedule).and_return(pipeline_schedule) + + allow(view).to receive(:can?).and_return(true) + end + + context 'taking ownership of schedule' do + context 'when non-owner is signed in' do + let(:user) { master } + + before do + allow(view).to receive(:can?).with(master, :take_ownership_pipeline_schedule, pipeline_schedule).and_return(true) + end + + it 'non-owner can take ownership of pipeline' do + render + + expect(rendered).to have_link('Take ownership') + end + end + + context 'when owner is signed in' do + let(:user) { owner } + + before do + allow(view).to receive(:can?).with(owner, :take_ownership_pipeline_schedule, pipeline_schedule).and_return(false) + end + + it 'owner cannot take ownership of pipeline' do + render + + expect(rendered).not_to have_link('Take ownership') + end + end + end +end -- cgit v1.2.1 From ebc32465b6ee007c11cf1c277746cbfe750f837e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 1 Feb 2018 17:37:54 +0000 Subject: Moves more mr widget components into vue files --- .../components/mr_widget_author.js | 28 -------- .../components/mr_widget_author.vue | 33 +++++++++ .../components/mr_widget_author_time.js | 27 -------- .../components/mr_widget_author_time.vue | 42 ++++++++++++ .../components/states/mr_widget_closed.vue | 2 +- .../mr_widget_merge_when_pipeline_succeeds.vue | 2 +- .../components/states/mr_widget_merged.vue | 2 +- .../components/mr_widget_author_spec.js | 58 ++++++++-------- .../components/mr_widget_author_time_spec.js | 79 ++++++++-------------- 9 files changed, 136 insertions(+), 137 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js deleted file mode 100644 index 982b5e8e373..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js +++ /dev/null @@ -1,28 +0,0 @@ -import tooltip from '../../vue_shared/directives/tooltip'; - -export default { - name: 'MRWidgetAuthor', - props: { - author: { type: Object, required: true }, - showAuthorName: { type: Boolean, required: false, default: true }, - showAuthorTooltip: { type: Boolean, required: false, default: false }, - }, - directives: { - tooltip, - }, - template: ` - - - {{author.name}} - - - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue new file mode 100644 index 00000000000..7ac9eadcde0 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue @@ -0,0 +1,33 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js deleted file mode 100644 index 6d2ed5fda64..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js +++ /dev/null @@ -1,27 +0,0 @@ -import MRWidgetAuthor from './mr_widget_author'; - -export default { - name: 'MRWidgetAuthorTime', - props: { - actionText: { type: String, required: true }, - author: { type: Object, required: true }, - dateTitle: { type: String, required: true }, - dateReadable: { type: String, required: true }, - }, - components: { - 'mr-widget-author': MRWidgetAuthor, - }, - template: ` -

- {{actionText}} - - -

- `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue new file mode 100644 index 00000000000..8f1fd809a81 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue @@ -0,0 +1,42 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue index 71bfdaf801e..68b691fc914 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue @@ -1,5 +1,5 @@ + + diff --git a/changelogs/unreleased/add-confirmation-input-for-modals.yml b/changelogs/unreleased/add-confirmation-input-for-modals.yml new file mode 100644 index 00000000000..ff1027bc55a --- /dev/null +++ b/changelogs/unreleased/add-confirmation-input-for-modals.yml @@ -0,0 +1,5 @@ +--- +title: Add confirmation-input component +merge_request: 16816 +author: +type: other diff --git a/spec/javascripts/vue_shared/components/confirmation_input_spec.js b/spec/javascripts/vue_shared/components/confirmation_input_spec.js new file mode 100644 index 00000000000..a6a12614e77 --- /dev/null +++ b/spec/javascripts/vue_shared/components/confirmation_input_spec.js @@ -0,0 +1,63 @@ +import Vue from 'vue'; +import confirmationInput from '~/vue_shared/components/confirmation_input.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Confirmation input component', () => { + const Component = Vue.extend(confirmationInput); + const props = { + inputId: 'dummy-id', + confirmationKey: 'confirmation-key', + confirmationValue: 'confirmation-value', + }; + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('props', () => { + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + it('sets id of the input field to inputId', () => { + expect(vm.$refs.enteredValue.id).toBe(props.inputId); + }); + + it('sets name of the input field to confirmationKey', () => { + expect(vm.$refs.enteredValue.name).toBe(props.confirmationKey); + }); + }); + + describe('computed', () => { + describe('inputLabel', () => { + it('escapes confirmationValue by default', () => { + vm = mountComponent(Component, { ...props, confirmationValue: 'nds escap"ng' }); + expect(vm.inputLabel).toBe('Type n<e></e>ds escap"ng to confirm:'); + }); + + it('does not escape confirmationValue if escapeValue is false', () => { + vm = mountComponent(Component, { ...props, confirmationValue: 'nds escap"ng', shouldEscapeConfirmationValue: false }); + expect(vm.inputLabel).toBe('Type nds escap"ng to confirm:'); + }); + }); + }); + + describe('methods', () => { + describe('hasCorrectValue', () => { + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + it('returns false if entered value is incorrect', () => { + vm.$refs.enteredValue.value = 'incorrect'; + expect(vm.hasCorrectValue()).toBe(false); + }); + + it('returns true if entered value is correct', () => { + vm.$refs.enteredValue.value = props.confirmationValue; + expect(vm.hasCorrectValue()).toBe(true); + }); + }); + }); +}); -- cgit v1.2.1 From ece547f4f9523279ab5940dc53499b9b03bfeceb Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Thu, 1 Feb 2018 19:49:07 +0000 Subject: Docs: update GitLab products description --- doc/README.md | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/doc/README.md b/doc/README.md index 330670587f5..c8b6b4f32b8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -8,23 +8,13 @@ comments: false Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured platform for software development! -GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans: +GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans. -- **GitLab Community Edition (CE)** is an [open source product](https://gitlab.com/gitlab-org/gitlab-ce/), -self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com. -- **GitLab Enterprise Edition (EE)** is an [open-core product](https://gitlab.com/gitlab-org/gitlab-ee/), -self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)**, **GitLab Enterprise Edition Premium (EEP)**, and **GitLab Enterprise Edition Ultimate (EEU)**. -- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings). +With GitLab self-hosted, you deploy your own GitLab instance on-premises or on a private cloud of your choice. GitLab self-hosted is available for [free and with paid subscriptions](https://about.gitlab.com/products/): Libre, Starter, Premium, and Ultimate. -> **GitLab EE** contains all features available in **GitLab CE**, -plus premium features available in each version: **Enterprise Edition Starter** -(**EES**), **Enterprise Edition Premium** (**EEP**), and **Enterprise Edition Ultimate** -(**EEU**). Everything available in **EES** is also available in **EEP**. Every feature -available in **EEP** is also available in **EEU**. +GitLab.com is our SaaS offering. It's hosted, managed, and administered by GitLab, with [free and paid plans](https://about.gitlab.com/gitlab-com/) for individuals and teams: Free, Bronze, Silver, and Gold. ----- - -Shortcuts to GitLab's most visited docs: +## Shortcuts to GitLab's most visited docs | [GitLab CI/CD](ci/README.md) | Other | | :----- | :----- | @@ -134,14 +124,8 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i ## Administrator documentation -[Administration documentation](administration/index.md) applies to admin users of GitLab -self-hosted instances: - -- GitLab Community Edition -- GitLab [Enterprise Editions](https://about.gitlab.com/gitlab-ee/) - - Enterprise Edition Starter (EES) - - Enterprise Edition Premium (EEP) - - Enterprise Edition Ultimate (EEU) +[Administration documentation](administration/index.md) applies to admin users of [GitLab +self-hosted instances](#self-hosted-gitlab): Libre, Starter, Premium, Ultimate. Learn how to install, configure, update, upgrade, integrate, and maintain your own instance. Regular users don't have access to GitLab administration tools and settings. -- cgit v1.2.1 From 658749ddd38f9e0fa6ceb75eddd882e662ba86a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Thu, 1 Feb 2018 17:04:03 -0300 Subject: Fix an issue where sparse checkout wasn't configured correctly This was affecting the performance of `Gitlab::Git::Repository#squash`. --- lib/gitlab/git/repository.rb | 2 +- spec/lib/gitlab/git/repository_spec.rb | 46 +++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 92af5a8e1de..f28624ff37a 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1519,7 +1519,7 @@ module Gitlab if sparse_checkout_files # Create worktree without checking out run_git!(base_args + ['--no-checkout', worktree_path], env: env) - worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path) + worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path).chomp configure_sparse_checkout(worktree_git_path, sparse_checkout_files) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 8e0ebb1f6fa..96a442f782f 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -20,6 +20,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } let(:storage_path) { TestEnv.repos_path } + let(:user) { build(:user) } describe '.create_hooks' do let(:repo_path) { File.join(storage_path, 'hook-test.git') } @@ -693,7 +694,6 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#remote_tags' do let(:remote_name) { 'upstream' } let(:target_commit_id) { SeedRepo::Commit::ID } - let(:user) { create(:user) } let(:tag_name) { 'v0.0.1' } let(:tag_message) { 'My tag' } let(:remote_repository) do @@ -1711,7 +1711,6 @@ describe Gitlab::Git::Repository, seed_helper: true do shared_examples "user deleting a branch" do let(:project) { create(:project, :repository) } let(:repository) { project.repository.raw } - let(:user) { create(:user) } let(:branch_name) { "to-be-deleted-soon" } before do @@ -1795,7 +1794,6 @@ describe Gitlab::Git::Repository, seed_helper: true do Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') end let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' } - let(:user) { build(:user) } let(:target_branch) { 'test-merge-target-branch' } before do @@ -1848,7 +1846,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } - let(:user) { build(:user) } let(:target_branch) { 'test-ff-target-branch' } before do @@ -2167,6 +2164,47 @@ describe Gitlab::Git::Repository, seed_helper: true do expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') end end + + describe '#squash' do + let(:squash_id) { '1' } + let(:branch_name) { 'fix' } + let(:start_sha) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' } + let(:end_sha) { '12d65c8dd2b2676fa3ac47d955accc085a37a9c1' } + + subject do + opts = { + branch: branch_name, + start_sha: start_sha, + end_sha: end_sha, + author: user, + message: 'Squash commit message' + } + + repository.squash(user, squash_id, opts) + end + + context 'sparse checkout' do + let(:expected_files) { %w(files files/js files/js/application.js) } + + before do + allow(repository).to receive(:with_worktree).and_wrap_original do |m, *args| + m.call(*args) do + worktree_path = args[0] + files_pattern = File.join(worktree_path, '**', '*') + expected = expected_files.map do |path| + File.expand_path(path, worktree_path) + end + + expect(Dir[files_pattern]).to eq(expected) + end + end + end + + it 'checkouts only the files in the diff' do + subject + end + end + end end def create_remote_branch(repository, remote_name, branch_name, source_branch_name) -- cgit v1.2.1