Commit 102cf55e authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 2222-deploy-boards-polling

* master:
  Fix MR diff blob_fork_suggestion after jQuery update
  Refactor deploy boards into vue files
  Bump mysql2 gem from 0.3.16 -> 0.4.5
  Fix diffs with edit-forking needs
  Add Fork/Cancel confirmation to "Replace"/"Delete" buttons
  Fix tests and features to make specs pass
  Fix incorrect EE spec filenames which were ingored by CI
  Fix outdated specs that were not executed due to wrong filename
  Fix incorrect spec filenames which were ingored by CI
  Use jQuery niceness on blob_fork_suggestion
parents 4c72cdb0 c4527291
...@@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.0' ...@@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.0'
gem 'default_value_for', '~> 3.0.0' gem 'default_value_for', '~> 3.0.0'
# Supported DBs # Supported DBs
gem 'mysql2', '~> 0.3.16', group: :mysql gem 'mysql2', '~> 0.4.5', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.25.1.1' gem 'rugged', '~> 0.25.1.1'
......
...@@ -482,7 +482,7 @@ GEM ...@@ -482,7 +482,7 @@ GEM
tool (~> 0.2) tool (~> 0.2)
mustermann-grape (0.4.0) mustermann-grape (0.4.0)
mustermann (= 0.4.0) mustermann (= 0.4.0)
mysql2 (0.3.20) mysql2 (0.4.5)
net-ldap (0.12.1) net-ldap (0.12.1)
net-ssh (3.0.1) net-ssh (3.0.1)
netrc (0.11.0) netrc (0.11.0)
...@@ -984,7 +984,7 @@ DEPENDENCIES ...@@ -984,7 +984,7 @@ DEPENDENCIES
method_source (~> 0.8) method_source (~> 0.8)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16) mysql2 (~> 0.4.5)
net-ldap net-ldap
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
......
function BlobForkSuggestion(openButton, cancelButton, suggestionSection) { const defaults = {
if (openButton) { // Buttons that will show the `suggestionSections`
openButton.addEventListener('click', () => { // has `data-fork-path`, and `data-action`
suggestionSection.classList.remove('hidden'); openButtons: [],
}); // Update the href(from `openButton` -> `data-fork-path`)
// whenever a `openButton` is clicked
forkButtons: [],
// Buttons to hide the `suggestionSections`
cancelButtons: [],
// Section to show/hide
suggestionSections: [],
// Pieces of text that need updating depending on the action, `edit`, `replace`, `delete`
actionTextPieces: [],
};
class BlobForkSuggestion {
constructor(options) {
this.elementMap = Object.assign({}, defaults, options);
this.onOpenButtonClick = this.onOpenButtonClick.bind(this);
this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
}
init() {
this.bindEvents();
return this;
}
bindEvents() {
$(this.elementMap.openButtons).on('click', this.onOpenButtonClick);
$(this.elementMap.cancelButtons).on('click', this.onCancelButtonClick);
}
showSuggestionSection(forkPath, action = 'edit') {
$(this.elementMap.suggestionSections).removeClass('hidden');
$(this.elementMap.forkButtons).attr('href', forkPath);
$(this.elementMap.actionTextPieces).text(action);
}
hideSuggestionSection() {
$(this.elementMap.suggestionSections).addClass('hidden');
}
onOpenButtonClick(e) {
const forkPath = $(e.currentTarget).attr('data-fork-path');
const action = $(e.currentTarget).attr('data-action');
this.showSuggestionSection(forkPath, action);
}
onCancelButtonClick() {
this.hideSuggestionSection();
} }
if (cancelButton) { destroy() {
cancelButton.addEventListener('click', () => { $(this.elementMap.openButtons).off('click', this.onOpenButtonClick);
suggestionSection.classList.add('hidden'); $(this.elementMap.cancelButtons).off('click', this.onCancelButtonClick);
});
} }
} }
......
// ECMAScript polyfills // ECMAScript polyfills
import 'core-js/fn/array/find'; import 'core-js/fn/array/find';
import 'core-js/fn/array/from'; import 'core-js/fn/array/from';
import 'core-js/fn/array/includes';
import 'core-js/fn/object/assign'; import 'core-js/fn/object/assign';
import 'core-js/fn/promise'; import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at'; import 'core-js/fn/string/code-point-at';
......
...@@ -97,11 +97,14 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -97,11 +97,14 @@ const ShortcutsBlob = require('./shortcuts_blob');
fileBlobPermalinkUrl, fileBlobPermalinkUrl,
}); });
new BlobForkSuggestion( new BlobForkSuggestion({
document.querySelector('.js-edit-blob-link-fork-toggler'), openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
document.querySelector('.js-cancel-fork-suggestion'), forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
document.querySelector('.js-file-fork-suggestion-section'), cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
); suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
})
.init();
} }
switch (page) { switch (page) {
......
<script>
/* eslint-disable no-new, no-undef */
/* global Flash */ /* global Flash */
/** /**
* Renders a deploy board. * Renders a deploy board.
...@@ -19,7 +21,7 @@ ...@@ -19,7 +21,7 @@
*/ */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import deployBoardSvg from 'empty_states/icons/_deploy_board.svg'; import deployBoardSvg from 'empty_states/icons/_deploy_board.svg';
import instanceComponent from './deploy_board_instance_component'; import instanceComponent from './deploy_board_instance_component.vue';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import '../../flash'; import '../../flash';
...@@ -131,12 +133,15 @@ export default { ...@@ -131,12 +133,15 @@ export default {
return '<projectname>'; return '<projectname>';
}, },
}, },
};
template: ` </script>
<template>
<div class="js-deploy-board deploy-board"> <div class="js-deploy-board deploy-board">
<div v-if="isLoading"> <div v-if="isLoading">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i> <i
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</div> </div>
<div v-if="canRenderDeployBoard"> <div v-if="canRenderDeployBoard">
...@@ -161,8 +166,11 @@ export default { ...@@ -161,8 +166,11 @@ export default {
</div> </div>
</section> </section>
<section class="deploy-board-actions" v-if="deployBoardData.rollback_url || deployBoardData.abort_url"> <section
<a class="btn" class="deploy-board-actions"
v-if="deployBoardData.rollback_url || deployBoardData.abort_url">
<a
class="btn"
data-method="post" data-method="post"
rel="nofollow" rel="nofollow"
v-if="deployBoardData.rollback_url" v-if="deployBoardData.rollback_url"
...@@ -170,7 +178,8 @@ export default { ...@@ -170,7 +178,8 @@ export default {
Rollback Rollback
</a> </a>
<a class="btn btn-red btn-inverted" <a
class="btn btn-red btn-inverted"
data-method="post" data-method="post"
rel="nofollow" rel="nofollow"
v-if="deployBoardData.abort_url" v-if="deployBoardData.abort_url"
...@@ -181,8 +190,9 @@ export default { ...@@ -181,8 +190,9 @@ export default {
</div> </div>
<div v-if="canRenderEmptyState"> <div v-if="canRenderEmptyState">
<section class="deploy-board-empty-state-svg"> <section
${deployBoardSvg} class="deploy-board-empty-state-svg"
v-html="deployBoardSvg">
</section> </section>
<section class="deploy-board-empty-state-text"> <section class="deploy-board-empty-state-text">
...@@ -194,9 +204,10 @@ export default { ...@@ -194,9 +204,10 @@ export default {
</section> </section>
</div> </div>
<div v-if="canRenderErrorState" class="deploy-board-error-message"> <div
v-if="canRenderErrorState"
class="deploy-board-error-message">
We can't fetch the data right now. Please try again later. We can't fetch the data right now. Please try again later.
</div> </div>
</div> </div>
`, </script>
};
<script>
/** /**
* An instance in deploy board is represented by a square in this mockup: * An instance in deploy board is represented by a square in this mockup:
* https://gitlab.com/gitlab-org/gitlab-ce/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png * https://gitlab.com/gitlab-org/gitlab-ce/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png
...@@ -50,8 +51,9 @@ export default { ...@@ -50,8 +51,9 @@ export default {
return cssClassName; return cssClassName;
}, },
}, },
};
template: ` </script>
<template>
<div <div
class="deploy-board-instance has-tooltip" class="deploy-board-instance has-tooltip"
:class="cssClass" :class="cssClass"
...@@ -59,5 +61,4 @@ export default { ...@@ -59,5 +61,4 @@ export default {
data-toggle="tooltip" data-toggle="tooltip"
data-placement="top"> data-placement="top">
</div> </div>
`, </template>
};
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* Render environments table. * Render environments table.
*/ */
import EnvironmentTableRowComponent from './environment_item.vue'; import EnvironmentTableRowComponent from './environment_item.vue';
import DeployBoard from './deploy_board_component'; import DeployBoard from './deploy_board_component.vue';
export default { export default {
components: { components: {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import './breakpoints'; import './breakpoints';
import './flash'; import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
/* eslint-disable max-len */ /* eslint-disable max-len */
// MergeRequestTabs // MergeRequestTabs
...@@ -266,6 +267,17 @@ import './flash'; ...@@ -266,6 +267,17 @@ import './flash';
new gl.Diff(); new gl.Diff();
this.scrollToElement('#diffs'); this.scrollToElement('#diffs');
$('.diff-file').each((i, el) => {
new BlobForkSuggestion({
openButtons: $(el).find('.js-edit-blob-link-fork-toggler'),
forkButtons: $(el).find('.js-fork-suggestion-button'),
cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
})
.init();
});
}, },
}); });
} }
......
...@@ -38,9 +38,8 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -38,9 +38,8 @@ class Projects::DeployKeysController < Projects::ApplicationController
deploy_key_project = @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]) deploy_key_project = @project.deploy_keys_projects.find_by(deploy_key_id: params[:id])
return render_404 unless deploy_key_project return render_404 unless deploy_key_project
deploy_key_project.destroy!
load_key load_key
deploy_key_project.destroy!
log_audit_event(@key.title, action: :destroy) log_audit_event(@key.title, action: :destroy)
redirect_to_repository_settings(@project) redirect_to_repository_settings(@project)
......
...@@ -14,15 +14,6 @@ module BlobHelper ...@@ -14,15 +14,6 @@ module BlobHelper
options[:link_opts]) options[:link_opts])
end end
def fork_path(project = @project, ref = @ref, path = @path, options = {})
continue_params = {
to: edit_path,
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
end
def edit_blob_link(project = @project, ref = @ref, path = @path, options = {}) def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
blob = options.delete(:blob) blob = options.delete(:blob)
blob ||= project.repository.blob_at(ref, path) rescue nil blob ||= project.repository.blob_at(ref, path) rescue nil
...@@ -37,7 +28,16 @@ module BlobHelper ...@@ -37,7 +28,16 @@ module BlobHelper
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref)) elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm" link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm"
elsif current_user && can?(current_user, :fork_project, project) elsif current_user && can?(current_user, :fork_project, project)
button_tag 'Edit', class: "#{common_classes} js-edit-blob-link-fork-toggler" continue_params = {
to: edit_path(project, ref, path, options),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
button_tag 'Edit',
class: "#{common_classes} js-edit-blob-link-fork-toggler",
data: { action: 'edit', fork_path: fork_path }
end end
end end
...@@ -48,12 +48,14 @@ module BlobHelper ...@@ -48,12 +48,14 @@ module BlobHelper
return unless blob return unless blob
common_classes = "btn btn-#{btn_class}"
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } button_tag label, class: "#{common_classes} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer? elsif blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } button_tag label, class: "#{common_classes} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_modify_blob?(blob, project, ref) elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
continue_params = { continue_params = {
to: request.fullpath, to: request.fullpath,
...@@ -62,7 +64,9 @@ module BlobHelper ...@@ -62,7 +64,9 @@ module BlobHelper
} }
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post button_tag label,
class: "#{common_classes} js-edit-blob-link-fork-toggler",
data: { action: action, fork_path: fork_path }
end end
end end
......
- if current_user
.js-file-fork-suggestion-section.file-fork-suggestion.hidden
%span.file-fork-suggestion-note
You're not allowed to
%span.js-file-fork-suggestion-section-action
edit
files in this project directly. Please fork this project,
make your changes there, and submit a merge request.
= link_to 'Fork', nil, method: :post, class: 'js-fork-suggestion-button btn btn-grouped btn-inverted btn-new'
%button.js-cancel-fork-suggestion-button.btn.btn-grouped{ type: 'button' }
Cancel
...@@ -40,13 +40,8 @@ ...@@ -40,13 +40,8 @@
- if current_user - if current_user
= replace_blob_link = replace_blob_link
= delete_blob_link = delete_blob_link
- if current_user
.js-file-fork-suggestion-section.file-fork-suggestion.hidden = render 'projects/fork_suggestion'
%span.file-fork-suggestion-note
You don't have permission to edit this file. Try forking this project to edit the file.
= link_to 'Fork', fork_path, method: :post, class: 'btn btn-grouped btn-inverted btn-new'
%button.js-cancel-fork-suggestion.btn.btn-grouped{ type: 'button' }
Cancel
- if license_allows_file_locks? - if license_allows_file_locks?
:javascript :javascript
......
...@@ -18,4 +18,6 @@ ...@@ -18,4 +18,6 @@
= view_file_button(diff_commit.id, diff_file.new_path, project) = view_file_button(diff_commit.id, diff_file.new_path, project)
= view_on_environment_button(diff_commit.id, diff_file.new_path, environment) if environment = view_on_environment_button(diff_commit.id, diff_file.new_path, environment) if environment
= render 'projects/fork_suggestion'
= render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, blob: blob, project: project = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, blob: blob, project: project
...@@ -31,3 +31,9 @@ ...@@ -31,3 +31,9 @@
= link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do
%span %span
Pages Pages
= nav_link(controller: :audit_events) do
= link_to namespace_project_audit_events_path(@project.namespace, @project), title: "Audit Events" do
%span
Audit Events
...@@ -117,6 +117,8 @@ Feature: Project Source Browse Files ...@@ -117,6 +117,8 @@ Feature: Project Source Browse Files
And I click on ".gitignore" file in repo And I click on ".gitignore" file in repo
And I see the ".gitignore" And I see the ".gitignore"
And I click on "Replace" And I click on "Replace"
Then I should see a Fork/Cancel combo
And I click button "Fork"
Then I should see a notice about a new fork having been created Then I should see a notice about a new fork having been created
When I click on "Replace" When I click on "Replace"
And I replace it with a text file And I replace it with a text file
...@@ -265,6 +267,8 @@ Feature: Project Source Browse Files ...@@ -265,6 +267,8 @@ Feature: Project Source Browse Files
And I click on ".gitignore" file in repo And I click on ".gitignore" file in repo
And I see the ".gitignore" And I see the ".gitignore"
And I click on "Delete" And I click on "Delete"
Then I should see a Fork/Cancel combo
And I click button "Fork"
Then I should see a notice about a new fork having been created Then I should see a notice about a new fork having been created
When I click on "Delete" When I click on "Delete"
And I fill the commit message And I fill the commit message
......
...@@ -377,7 +377,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -377,7 +377,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I should see a Fork/Cancel combo' do step 'I should see a Fork/Cancel combo' do
expect(page).to have_link 'Fork' expect(page).to have_link 'Fork'
expect(page).to have_button 'Cancel' expect(page).to have_button 'Cancel'
expect(page).to have_content 'You don\'t have permission to edit this file. Try forking this project to edit the file.'
end end
step 'I should see a notice about a new fork having been created' do step 'I should see a notice about a new fork having been created' do
......
...@@ -63,4 +63,44 @@ describe Projects::BuildsController do ...@@ -63,4 +63,44 @@ describe Projects::BuildsController do
expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end end
end end
describe 'GET trace.json' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:user) { create(:user) }
context 'when user is logged in as developer' do
before do
project.add_developer(user)
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
context 'when user is logged in as non member' do
before do
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
def get_trace
get :trace, namespace_id: project.namespace,
project_id: project,
id: build.id,
format: :json
end
end
end end
require 'spec_helper'
describe Projects::BuildsController do
include ApiHelpers
let(:project) { create(:empty_project, :public) }
describe 'GET trace.json' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:user) { create(:user) }
context 'when user is logged in as developer' do
before do
project.add_developer(user)
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
context 'when user is logged in as non member' do
before do
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
def get_trace
get :trace, namespace_id: project.namespace,
project_id: project,
id: build.id,
format: :json
end
end
end
...@@ -18,10 +18,10 @@ feature 'Groups > Audit Events', js: true, feature: true do ...@@ -18,10 +18,10 @@ feature 'Groups > Audit Events', js: true, feature: true do
click_link 'Members' click_link 'Members'
group_member = group.members.find_by(user_id: pete) group_member = group.members.find_by(user_id: pete)
page.within "#group_member_#{group_member.id}" do page.within "#group_member_#{group_member.id}" do
click_button 'Edit access level' click_button 'Developer'
select 'Master', from: 'group_member_access_level' click_link 'Master'
click_button 'Save'
end end
# This is to avoid a Capybara::Poltergeist::MouseEventFailed error # This is to avoid a Capybara::Poltergeist::MouseEventFailed error
......
...@@ -36,11 +36,9 @@ feature 'Groups > Pipeline Quota', feature: true do ...@@ -36,11 +36,9 @@ feature 'Groups > Pipeline Quota', feature: true do
let!(:project) { create(:empty_project, namespace: group, shared_runners_enabled: false) } let!(:project) { create(:empty_project, namespace: group, shared_runners_enabled: false) }
it 'is not linked within the group settings dropdown' do it 'is not linked within the group settings dropdown' do
visit group_path(group) visit edit_group_path(group)
page.within('.layout-nav') do expect(page).not_to have_link('Pipelines quota')
expect(page).not_to have_selector(:link_or_button, 'Pipeline Quota')
end
end end
it 'shows correct group quota info' do it 'shows correct group quota info' do
...@@ -60,12 +58,10 @@ feature 'Groups > Pipeline Quota', feature: true do ...@@ -60,12 +58,10 @@ feature 'Groups > Pipeline Quota', feature: true do
context 'minutes under quota' do context 'minutes under quota' do
let(:group) { create(:group, :with_not_used_build_minutes_limit) } let(:group) { create(:group, :with_not_used_build_minutes_limit) }
it 'is linked within the group settings dropdown' do it 'is linked within the group settings tab' do
visit group_path(group) visit edit_group_path(group)
page.within('.layout-nav') do expect(page).to have_link('Pipelines quota')
expect(page).to have_selector(:link_or_button, 'Pipeline Quota')
end
end end
it 'shows correct group quota info' do it 'shows correct group quota info' do
...@@ -83,12 +79,10 @@ feature 'Groups > Pipeline Quota', feature: true do ...@@ -83,12 +79,10 @@ feature 'Groups > Pipeline Quota', feature: true do
let(:group) { create(:group, :with_used_build_minutes_limit) } let(:group) { create(:group, :with_used_build_minutes_limit) }
let!(:other_project) { create(:empty_project, namespace: group, shared_runners_enabled: false) } let!(:other_project) { create(:empty_project, namespace: group, shared_runners_enabled: false) }
it 'is linked within the group settings dropdown' do it 'is linked within the group settings tab' do
visit group_path(group) visit edit_group_path(group)
page.within('.layout-nav') do expect(page).to have_link('Pipelines quota')
expect(page).to have_selector(:link_or_button, 'Pipeline Quota')
end
end end
it 'shows correct group quota and projects info' do it 'shows correct group quota and projects info' do
......
...@@ -4,7 +4,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe ...@@ -4,7 +4,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) } let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) }
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
let!(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request, noteable: merge_request, project: project)]).first } let!(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
describe 'As a user with access to the project' do describe 'As a user with access to the project' do
before do before do
...@@ -74,8 +74,8 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe ...@@ -74,8 +74,8 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
it 'Shows a notice to ask someone else to resolve the discussions' do it 'Shows a notice to ask someone else to resolve the discussions' do
expect(page).to have_content("The discussion at #{merge_request.to_reference}"\ expect(page).to have_content("The discussion at #{merge_request.to_reference}"\
"(discussion #{discussion.first_note.id}) will stay unresolved."\ " (discussion #{discussion.first_note.id}) will stay unresolved."\
"Ask someone with permission to resolve it.") " Ask someone with permission to resolve it.")
end end
end end
end end
require 'spec_helper' require 'spec_helper'
feature 'Diffs URL', js: true, feature: true do feature 'Diffs URL', js: true, feature: true do
before do include ApplicationHelper
login_as :admin
@merge_request = create(:merge_request) let(:author_user) { create(:user) }
@project = @merge_request.source_project let(:user) { create(:user) }
end let(:project) { create(:project, :public) }
let(:forked_project) { Projects::ForkService.new(project, author_user).execute }
let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, target_project: project, author: author_user) }
context 'when visit with */* as accept header' do context 'when visit with */* as accept header' do
before(:each) do before(:each) do
...@@ -13,9 +15,9 @@ feature 'Diffs URL', js: true, feature: true do ...@@ -13,9 +15,9 @@ feature 'Diffs URL', js: true, feature: true do
end end
it 'renders the notes' do it 'renders the notes' do
create :note_on_merge_request, project: @project, noteable: @merge_request, note: 'Rebasing with master' create :note_on_merge_request, project: project, noteable: merge_request, note: 'Rebasing with master'
visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
# Load notes and diff through AJAX # Load notes and diff through AJAX
expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master') expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master')
...@@ -28,11 +30,38 @@ feature 'Diffs URL', js: true, feature: true do ...@@ -28,11 +30,38 @@ feature 'Diffs URL', js: true, feature: true do
allow_any_instance_of(MergeRequestDiff).to receive(:overflow?).and_return(true) allow_any_instance_of(MergeRequestDiff).to receive(:overflow?).and_return(true)
allow(Commit).to receive(:max_diff_options).and_return(max_files: 20, max_lines: 20) allow(Commit).to receive(:max_diff_options).and_return(max_files: 20, max_lines: 20)
visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
page.within('.alert') do page.within('.alert') do
expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve
performance only 3 of 3+ files are displayed.") performance only 3 of 3 files are displayed.")
end
end
end
describe 'when editing file' do
let(:changelog_id) { hexdigest("CHANGELOG") }
context 'as author' do
it 'shows direct edit link' do
login_as(author_user)
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
expect(page).to have_selector("[id=\"#{changelog_id}\"] a.js-edit-blob")
end
end
context 'as user who needs to fork' do
it 'shows fork/cancel confirmation' do
login_as(user)
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
find("[id=\"#{changelog_id}\"] .js-edit-blob").click
expect(page).to have_selector('.js-fork-suggestion-button', count: 1)
expect(page).to have_selector('.js-cancel-fork-suggestion-button', count: 1)
end end
end end
end end
......
...@@ -17,14 +17,17 @@ feature 'Projects > Audit Events', js: true, feature: true do ...@@ -17,14 +17,17 @@ feature 'Projects > Audit Events', js: true, feature: true do
fill_in 'deploy_key_title', with: 'laptop' fill_in 'deploy_key_title', with: 'laptop'
fill_in 'deploy_key_key', with: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop' fill_in 'deploy_key_key', with: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop'
click_button 'Create' click_button 'Add key'
visit namespace_project_audit_events_path(project.namespace, project) visit namespace_project_audit_events_path(project.namespace, project)
expect(page).to have_content('Add deploy key') expect(page).to have_content('Add deploy key')
visit namespace_project_deploy_keys_path(project.namespace, project) visit namespace_project_deploy_keys_path(project.namespace, project)
accept_confirm do
click_link 'Remove' click_link 'Remove'
end
visit namespace_project_audit_events_path(project.namespace, project) visit namespace_project_audit_events_path(project.namespace, project)
...@@ -38,15 +41,13 @@ feature 'Projects > Audit Events', js: true, feature: true do ...@@ -38,15 +41,13 @@ feature 'Projects > Audit Events', js: true, feature: true do
end end
it "appears in the project's audit events" do it "appears in the project's audit events" do
visit namespace_project_path(project.namespace, project) visit namespace_project_settings_members_path(project.namespace, project)
click_link 'Members'
project_member = project.project_member(pete) project_member = project.project_member(pete)
page.within "#project_member_#{project_member.id}" do page.within "#project_member_#{project_member.id}" do
click_button 'Edit access level' click_button 'Developer'
select 'Master', from: 'project_member_access_level' click_link 'Master'
click_button 'Save'
end end
# This is to avoid a Capybara::Poltergeist::MouseEventFailed error # This is to avoid a Capybara::Poltergeist::MouseEventFailed error
......
import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
describe('BlobForkSuggestion', () => {
let blobForkSuggestion;
const openButton = document.createElement('div');
const forkButton = document.createElement('a');
const cancelButton = document.createElement('div');
const suggestionSection = document.createElement('div');
const actionTextPiece = document.createElement('div');
beforeEach(() => {
blobForkSuggestion = new BlobForkSuggestion({
openButtons: openButton,
forkButtons: forkButton,
cancelButtons: cancelButton,
suggestionSections: suggestionSection,
actionTextPieces: actionTextPiece,
})
.init();
});
afterEach(() => {
blobForkSuggestion.destroy();
});
it('showSuggestionSection', () => {
blobForkSuggestion.showSuggestionSection('/foo', 'foo');
expect(suggestionSection.classList.contains('hidden')).toEqual(false);
expect(forkButton.getAttribute('href')).toEqual('/foo');
expect(actionTextPiece.textContent).toEqual('foo');
});
it('hideSuggestionSection', () => {
blobForkSuggestion.hideSuggestionSection();
expect(suggestionSection.classList.contains('hidden')).toEqual(true);
});
});
import Vue from 'vue'; import Vue from 'vue';
import DeployBoard from '~/environments/components/deploy_board_component'; import DeployBoard from '~/environments/components/deploy_board_component.vue';
import Service from '~/environments/services/environments_service'; import Service from '~/environments/services/environments_service';
const { deployBoardMockData, invalidDeployBoardMockData } = require('./mock_data'); const { deployBoardMockData, invalidDeployBoardMockData } = require('./mock_data');
......
import Vue from 'vue'; import Vue from 'vue';
import DeployBoardInstance from '~/environments/components/deploy_board_instance_component'; import DeployBoardInstance from '~/environments/components/deploy_board_instance_component.vue';
describe('Deploy Board Instance', () => { describe('Deploy Board Instance', () => {
let DeployBoardInstanceComponent; let DeployBoardInstanceComponent;
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::OtherMarkup, lib: true do describe Gitlab::OtherMarkup, lib: true do
let(:context) { {} }
context "XSS Checks" do context "XSS Checks" do
links = { links = {
'links' => { 'links' => {
file: 'file.rdoc', file: 'file.rdoc',
input: 'XSS[JaVaScriPt:alert(1)]', input: 'XSS[JaVaScriPt:alert(1)]',
output: '<p><a>XSS</a></p>' output: "\n" + '<p><a>XSS</a></p>' + "\n"
} }
} }
links.each do |name, data| links.each do |name, data|
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment