Commit 8789d082 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-05-11

# Conflicts:
#	lib/gitlab/git_access.rb
#	spec/lib/gitlab/git_access_spec.rb

[ci skip]
parents 678747be 9532ca43
<script>
/* eslint-disable no-alert */
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
components: {
loadingIcon,
icon,
},
props: {
endpoint: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
cssClass: {
type: String,
required: true,
},
pipelineId: {
type: Number,
required: true,
},
type: {
type: String,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
computed: {
buttonClass() {
return `btn ${this.cssClass}`;
},
},
created() {
// We're using eventHub to listen to the modal here instead of
// using props because it would would make the parent components
// much more complex to keep track of the loading state of each button
eventHub.$on('postAction', this.setLoading);
},
beforeDestroy() {
eventHub.$off('postAction', this.setLoading);
},
methods: {
onClick() {
eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipelineId,
endpoint: this.endpoint,
type: this.type,
});
},
setLoading(endpoint) {
if (endpoint === this.endpoint) {
this.isLoading = true;
}
},
},
};
</script>
<template>
<button
v-tooltip
type="button"
@click="onClick"
:class="buttonClass"
:title="title"
:aria-label="title"
data-container="body"
data-placement="top"
:disabled="isLoading">
<icon
:name="icon"
/>
<loading-icon v-if="isLoading" />
</button>
</template>
<script>
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import Modal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
import pipelinesTableRowComponent from './pipelines_table_row.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
import eventHub from '../event_hub';
/**
......@@ -11,8 +11,8 @@
*/
export default {
components: {
pipelinesTableRowComponent,
DeprecatedModal,
PipelinesTableRowComponent,
Modal,
},
props: {
pipelines: {
......@@ -37,30 +37,18 @@
return {
pipelineId: '',
endpoint: '',
type: '',
};
},
computed: {
modalTitle() {
return this.type === 'stop' ?
sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
pipelineId: `'${this.pipelineId}'`,
}, false) :
sprintf(s__('Pipeline|Retry pipeline #%{pipelineId}?'), {
pipelineId: `'${this.pipelineId}'`,
}, false);
return sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
pipelineId: `${this.pipelineId}`,
}, false);
},
modalText() {
return this.type === 'stop' ?
sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false) :
sprintf(s__('Pipeline|You’re about to retry pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false);
},
primaryButtonLabel() {
return this.type === 'stop' ? s__('Pipeline|Stop pipeline') : s__('Pipeline|Retry pipeline');
return sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false);
},
},
created() {
......@@ -73,7 +61,6 @@
setModalData(data) {
this.pipelineId = data.pipelineId;
this.endpoint = data.endpoint;
this.type = data.type;
},
onSubmit() {
eventHub.$emit('postAction', this.endpoint);
......@@ -120,20 +107,16 @@
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
<deprecated-modal
<modal
id="confirmation-modal"
:title="modalTitle"
:text="modalText"
kind="danger"
:primary-button-label="primaryButtonLabel"
:header-title-text="modalTitle"
footer-primary-button-variant="danger"
:footer-primary-button-text="s__('Pipeline|Stop pipeline')"
@submit="onSubmit"
>
<template
slot="body"
slot-scope="props"
>
<p v-html="props.text"></p>
</template>
</deprecated-modal>
<span v-html="modalText"></span>
</modal>
</div>
</template>
<script>
/* eslint-disable no-param-reassign */
import asyncButtonComponent from './async_button.vue';
import pipelinesActionsComponent from './pipelines_actions.vue';
import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
import pipelineStage from './stage.vue';
import pipelineUrl from './pipeline_url.vue';
import pipelinesTimeago from './time_ago.vue';
import commitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
import PipelinesActionsComponent from './pipelines_actions.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
import PipelineStage from './stage.vue';
import PipelineUrl from './pipeline_url.vue';
import PipelinesTimeago from './time_ago.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
import Icon from '../../vue_shared/components/icon.vue';
/**
* Pipeline table row.
......@@ -16,14 +17,15 @@
*/
export default {
components: {
asyncButtonComponent,
pipelinesActionsComponent,
pipelinesArtifactsComponent,
commitComponent,
pipelineStage,
pipelineUrl,
ciBadge,
pipelinesTimeago,
PipelinesActionsComponent,
PipelinesArtifactsComponent,
CommitComponent,
PipelineStage,
PipelineUrl,
CiBadge,
PipelinesTimeago,
LoadingButton,
Icon,
},
props: {
pipeline: {
......@@ -44,6 +46,12 @@
required: true,
},
},
data() {
return {
isRetrying: false,
isCancelling: false,
};
},
computed: {
/**
* If provided, returns the commit tag.
......@@ -119,8 +127,10 @@
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
// eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop];
} else {
// eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
......@@ -216,6 +226,21 @@
return this.viewType === 'child';
},
},
methods: {
handleCancelClick() {
this.isCancelling = true;
eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipeline.id,
endpoint: this.pipeline.cancel_path,
});
},
handleRetryClick() {
this.isRetrying = true;
eventHub.$emit('retryPipeline', this.pipeline.retry_path);
},
},
};
</script>
<template>
......@@ -287,7 +312,8 @@
<div
v-if="displayPipelineActions"
class="table-section section-20 table-button-footer pipeline-actions">
class="table-section section-20 table-button-footer pipeline-actions"
>
<div class="btn-group table-action-buttons">
<pipelines-actions-component
v-if="pipeline.details.manual_actions.length"
......@@ -300,29 +326,27 @@
:artifacts="pipeline.details.artifacts"
/>
<async-button-component
<loading-button
v-if="pipeline.flags.retryable"
:endpoint="pipeline.retry_path"
css-class="js-pipelines-retry-button btn-default btn-retry"
title="Retry"
icon="repeat"
:pipeline-id="pipeline.id"
data-toggle="modal"
data-target="#confirmation-modal"
type="retry"
/>
@click="handleRetryClick"
container-class="js-pipelines-retry-button btn btn-default btn-retry"
:loading="isRetrying"
:disabled="isRetrying"
>
<icon name="repeat" />
</loading-button>
<async-button-component
<loading-button
v-if="pipeline.flags.cancelable"
:endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove"
title="Stop"
icon="close"
:pipeline-id="pipeline.id"
@click="handleCancelClick"
data-toggle="modal"
data-target="#confirmation-modal"
type="stop"
/>
container-class="js-pipelines-cancel-button btn btn-remove"
:loading="isCancelling"
:disabled="isCancelling"
>
<icon name="close" />
</loading-button>
</div>
</div>
</div>
......
......@@ -53,10 +53,12 @@ export default {
});
eventHub.$on('postAction', this.postAction);
eventHub.$on('retryPipeline', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('retryPipeline', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
},
destroyed() {
......
......@@ -70,12 +70,14 @@
/>
</transition>
<transition name="fade">
<span
v-if="label"
class="js-loading-button-label"
>
{{ label }}
</span>
<slot>
<span
v-if="label"
class="js-loading-button-label"
>
{{ label }}
</span>
</slot>
</transition>
</button>
</template>
......@@ -3,6 +3,10 @@ module Users
include InternalRedirect
skip_before_action :enforce_terms!
skip_before_action :check_password_expiration
skip_before_action :check_two_factor_requirement
skip_before_action :require_email
before_action :terms
layout 'terms'
......
---
title: Remove modalbox confirmation when retrying a pipeline
merge_request: 18879
author:
type: changed
......@@ -2,10 +2,13 @@
# class return an instance of `GitlabAccessStatus`
module Gitlab
class GitAccess
<<<<<<< HEAD
prepend ::EE::Gitlab::GitAccess
include ActionView::Helpers::SanitizeHelper
include PathLocksHelper
=======
>>>>>>> upstream/master
UnauthorizedError = Class.new(StandardError)
NotFoundError = Class.new(StandardError)
ProjectCreationError = Class.new(StandardError)
......
......@@ -4,7 +4,8 @@ module Gitlab
def self.parse(repo_path)
wiki = false
project_path = strip_storage_path(repo_path.sub(/\.git\z/, ''), fail_on_not_found: false)
project_path = repo_path.sub(/\.git\z/, '').sub(%r{\A/}, '')
project, was_redirected = find_project(project_path)
if project_path.end_with?('.wiki') && project.nil?
......@@ -17,22 +18,6 @@ module Gitlab
[project, wiki, redirected_path]
end
def self.strip_storage_path(repo_path, fail_on_not_found: true)
result = repo_path
storage = Gitlab.config.repositories.storages.values.find do |params|
repo_path.start_with?(params.legacy_disk_path)
end
if storage
result = result.sub(storage.legacy_disk_path, '')
elsif fail_on_not_found
raise NotFoundError.new("No known storage path matches #{repo_path.inspect}")
end
result.sub(%r{\A/*}, '')
end
def self.find_project(project_path)
project = Project.find_by_full_path(project_path, follow_redirects: true)
was_redirected = project && project.full_path.casecmp(project_path) != 0
......
......@@ -125,7 +125,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
find('.js-primary-button').click
find('.js-modal-primary-action').click
wait_for_requests
end
......@@ -156,7 +156,6 @@ describe 'Pipelines', :js do
context 'when retrying' do
before do
find('.js-pipelines-retry-button').click
find('.js-primary-button').click
wait_for_requests
end
......@@ -256,7 +255,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
find('.js-primary-button').click
find('.js-modal-primary-action').click
end
it 'indicates that pipeline was canceled' do
......
......@@ -451,5 +451,107 @@ feature 'Login' do
expect(current_path).to eq(root_path)
end
context 'when 2FA is required for the user' do
before do
group = create(:group, require_two_factor_authentication: true)
group.add_developer(user)
end
context 'when the user did not enable 2FA' do
it 'asks to set 2FA before asking to accept the terms' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(profile_two_factor_auth_path)
fill_in 'pin_code', with: user.reload.current_otp
click_button 'Register with two-factor app'
click_link 'Proceed'
expect(current_path).to eq(profile_account_path)
end
end
context 'when the user already enabled 2FA' do
before do
user.update!(otp_required_for_login: true,
otp_secret: User.generate_otp_secret(32))
end
it 'asks the user to accept the terms' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
fill_in 'user_otp_attempt', with: user.reload.current_otp
click_button 'Verify code'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(root_path)
end
end
end
context 'when the users password is expired' do
before do
user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC'))
end
it 'asks the user to accept the terms before setting a new password' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(new_profile_password_path)
fill_in 'user_current_password', with: '12345678'
fill_in 'user_password', with: 'new password'
fill_in 'user_password_confirmation', with: 'new password'
click_button 'Set new password'
expect(page).to have_content('Password successfully changed')
end
end
context 'when the user does not have an email configured' do
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml', email: 'temp-email-for-oauth-user@gitlab.localhost') }
before do
stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
end
it 'asks the user to accept the terms before setting an email' do
gitlab_sign_in_via('saml', user, 'my-uid')
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(profile_path)
fill_in 'Email', with: 'hello@world.com'
click_button 'Update profile settings'
expect(page).to have_content('Profile was successfully updated')
end
end
end
end
import Vue from 'vue';
import asyncButtonComp from '~/pipelines/components/async_button.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Async Button', () => {
let component;
let AsyncButtonComponent;
beforeEach(() => {
AsyncButtonComponent = Vue.extend(asyncButtonComp);
component = new AsyncButtonComponent({
propsData: {
endpoint: '/foo',
title: 'Foo',
icon: 'repeat',
cssClass: 'bar',
pipelineId: 123,
type: 'explode',
},
}).$mount();
});
it('should render a button', () => {
expect(component.$el.tagName).toEqual('BUTTON');
});
it('should render svg icon', () => {
expect(component.$el.querySelector('svg')).not.toBeNull();
});
it('should render the provided title', () => {
expect(component.$el.getAttribute('data-original-title')).toContain('Foo');
expect(component.$el.getAttribute('aria-label')).toContain('Foo');
});
it('should render the provided cssClass', () => {
expect(component.$el.getAttribute('class')).toContain('bar');
});
describe('With confirm dialog', () => {
it('should call the service when confimation is positive', () => {
eventHub.$on('openConfirmationModal', (data) => {
expect(data.pipelineId).toEqual(123);
expect(data.type).toEqual('explode');
});
component = new AsyncButtonComponent({
propsData: {
endpoint: '/foo',
title: 'Foo',
icon: 'fa fa-foo',
cssClass: 'bar',
pipelineId: 123,
type: 'explode',
},
}).$mount();
component.$el.click();
});
});
});
import Vue from 'vue';
import tableRowComp from '~/pipelines/components/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
......@@ -151,13 +152,37 @@ describe('Pipelines Table Row', () => {
describe('actions column', () => {
beforeEach(() => {
component = buildComponent(pipeline);
const withActions = Object.assign({}, pipeline);
withActions.flags.cancelable = true;
withActions.flags.retryable = true;
withActions.cancel_path = '/cancel';
withActions.retry_path = '/retry';
component = buildComponent(withActions);
});
it('should render the provided actions', () => {
expect(
component.$el.querySelectorAll('.table-section:nth-child(6) ul li').length,
).toEqual(pipeline.details.manual_actions.length);
expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull();
expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull();
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
eventHub.$on('retryPipeline', (endpoint) => {
expect(endpoint).toEqual('/retry');
});
component.$el.querySelector('.js-pipelines-retry-button').click();
expect(component.isRetrying).toEqual(true);
});
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
eventHub.$on('openConfirmationModal', (data) => {
expect(data.endpoint).toEqual('/cancel');
expect(data.pipelineId).toEqual(pipeline.id);
});
component.$el.querySelector('.js-pipelines-cancel-button').click();
expect(component.isCancelling).toEqual(true);
});
});
});
......@@ -1163,6 +1163,7 @@ describe Gitlab::GitAccess do
end
end
<<<<<<< HEAD
describe 'Geo system permissions' do
let(:actor) { :geo }
......@@ -1170,6 +1171,8 @@ describe Gitlab::GitAccess do
it { expect { push_access_check }.to raise_unauthorized(Gitlab::GitAccess::ERROR_MESSAGES[:upload]) }
end
=======
>>>>>>> upstream/master
context 'terms are enforced' do
before do
enforce_terms
......
......@@ -45,25 +45,6 @@ describe ::Gitlab::RepoPath do
end
end
describe '.strip_storage_path' do
before do
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'storage1' => Gitlab::GitalyClient::StorageSettings.new('path' => '/foo'),
'storage2' => Gitlab::GitalyClient::StorageSettings.new('path' => '/bar')
})
end
it 'strips the storage path' do
expect(described_class.strip_storage_path('/bar/foo/qux/baz.git')).to eq('foo/qux/baz.git')
end
it 'raises NotFoundError if no storage matches the path' do
expect { described_class.strip_storage_path('/doesnotexist/foo.git') }.to raise_error(
described_class::NotFoundError
)
end
end
describe '.find_project' do
let(:project) { create(:project) }
let(:redirect) { project.route.create_redirect('foo/bar/baz') }
......
......@@ -12,8 +12,10 @@
# AUTO_DEVOPS_DOMAIN must also be set as a variable at the group or project
# level, or manually added below.
#
# If you want to deploy to staging first, or enable canary deploys,
# uncomment the relevant jobs in the pipeline below.
# Continuous deployment to production is enabled by default.
# If you want to deploy to staging first, or enable incremental rollouts,
# set STAGING_ENABLED or INCREMENTAL_ROLLOUT_ENABLED environment variables.
# If you want to use canary deployments, uncomment the canary job.
#
# If Auto DevOps fails to detect the proper buildpack, or if you want to
# specify a custom buildpack, set a project variable `BUILDPACK_URL` to the
......@@ -88,14 +90,6 @@ codequality:
artifacts:
paths: [codeclimate.json]
license_management:
image: registry.gitlab.com/gitlab-org/security-products/license-management:latest
allow_failure: true
script:
- license_management
artifacts:
paths: [gl-license-report.json]
performance:
stage: performance
image: docker:stable
......@@ -223,8 +217,8 @@ stop_review:
# Staging deploys are disabled by default since
# continuous deployment to production is enabled by default
# If you prefer to automatically deploy to staging and
# only manually promote to production, enable this job by removing the dot (.),
# and uncomment the `when: manual` line in the `production` job.
# only manually promote to production, enable this job by setting
# STAGING_ENABLED.
staging:
stage: staging
......@@ -245,13 +239,9 @@ staging:
kubernetes: active
variables:
- $STAGING_ENABLED
except:
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
# Canaries are disabled by default, but if you want them,
# and know what the downsides are, enable this job by removing the dot (.),
# and uncomment the `when: manual` line in the `production` job.
# and know what the downsides are, enable this job by removing the dot (.).
.canary:
stage: canary
......@@ -272,11 +262,6 @@ staging:
- master
kubernetes: active
# This job continuously deploys to production on every push to `master`.
# To make this a manual process, either because you're enabling `staging`
# or `canary` deploys, or you simply want more control over when you deploy
# to production, uncomment the `when: manual` line in the `production` job.
.production: &production_template
stage: production
script:
......@@ -310,6 +295,7 @@ production:
production_manual:
<<: *production_template
when: manual
allow_failure: false
only:
refs:
- master
......@@ -345,6 +331,7 @@ rollout 10%:
<<: *rollout_template
variables:
ROLLOUT_PERCENTAGE: 10
when: manual
only:
refs:
- master
......@@ -379,6 +366,7 @@ rollout 50%:
rollout 100%:
<<: *production_template
when: manual
allow_failure: false
only:
refs:
- master
......@@ -428,14 +416,6 @@ rollout 100%:
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
}
function license_management() {
if echo $GITLAB_FEATURES |grep license_management > /dev/null ; then
/run.sh .
else
echo "License management is not available in your subscription"
fi
}
function sast() {
case "$CI_SERVER_VERSION" in
*-ee)
......@@ -562,12 +542,14 @@ rollout 100%:
replicas=$(get_replicas "$track" "$percentage")
helm upgrade --reuse-values \
--wait \
--set replicaCount="$replicas" \
--namespace="$KUBE_NAMESPACE" \
"$name" \
chart/
if [[ -n "$(helm ls -q "^$name$")" ]]; then
helm upgrade --reuse-values \
--wait \
--set replicaCount="$replicas" \
--namespace="$KUBE_NAMESPACE" \
"$name" \
chart/
fi
}
function install_dependencies() {
......
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