Commit d77181d5 authored by Matija Čupić's avatar Matija Čupić

Merge branch 'master' into persistent-callouts

parents 860c7c4b dc325c67
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
import ClustersService from './services/clusters_service'; import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store'; import ClustersStore from './stores/clusters_store';
import applications from './components/applications.vue'; import applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons';
/** /**
* Cluster page has 2 separate parts: * Cluster page has 2 separate parts:
...@@ -48,12 +49,9 @@ export default class Clusters { ...@@ -48,12 +49,9 @@ export default class Clusters {
installPrometheusEndpoint: installPrometheusPath, installPrometheusEndpoint: installPrometheusPath,
}); });
this.toggle = this.toggle.bind(this);
this.installApplication = this.installApplication.bind(this); this.installApplication = this.installApplication.bind(this);
this.showToken = this.showToken.bind(this); this.showToken = this.showToken.bind(this);
this.toggleButton = document.querySelector('.js-toggle-cluster');
this.toggleInput = document.querySelector('.js-toggle-input');
this.errorContainer = document.querySelector('.js-cluster-error'); this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success'); this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating'); this.creatingContainer = document.querySelector('.js-cluster-creating');
...@@ -63,6 +61,7 @@ export default class Clusters { ...@@ -63,6 +61,7 @@ export default class Clusters {
this.tokenField = document.querySelector('.js-cluster-token'); this.tokenField = document.querySelector('.js-cluster-token');
initSettingsPanels(); initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications(); this.initApplications();
if (this.store.state.status !== 'created') { if (this.store.state.status !== 'created') {
...@@ -101,13 +100,11 @@ export default class Clusters { ...@@ -101,13 +100,11 @@ export default class Clusters {
} }
addListeners() { addListeners() {
this.toggleButton.addEventListener('click', this.toggle);
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication); eventHub.$on('installApplication', this.installApplication);
} }
removeListeners() { removeListeners() {
this.toggleButton.removeEventListener('click', this.toggle);
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken); if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication); eventHub.$off('installApplication', this.installApplication);
} }
...@@ -151,11 +148,6 @@ export default class Clusters { ...@@ -151,11 +148,6 @@ export default class Clusters {
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason); this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
} }
toggle() {
this.toggleButton.classList.toggle('is-checked');
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('is-checked').toString());
}
showToken() { showToken() {
const type = this.tokenField.getAttribute('type'); const type = this.tokenField.getAttribute('type');
......
import Flash from '../flash'; import Flash from '../flash';
import { s__ } from '../locale'; import { s__ } from '../locale';
import setupToggleButtons from '../toggle_buttons';
import ClustersService from './services/clusters_service'; import ClustersService from './services/clusters_service';
/**
* Toggles loading and disabled classes.
* @param {HTMLElement} button
*/
const toggleLoadingButton = (button) => {
if (button.getAttribute('disabled')) {
button.removeAttribute('disabled');
} else {
button.setAttribute('disabled', true);
}
button.classList.toggle('is-loading');
};
/** export default () => {
* Toggles checked class for the given button const clusterList = document.querySelector('.js-clusters-list');
* @param {HTMLElement} button // The empty state won't have a clusterList
*/ if (clusterList) {
const toggleValue = (button) => { setupToggleButtons(
button.classList.toggle('is-checked'); document.querySelector('.js-clusters-list'),
(value, toggle) =>
ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } })
.catch((err) => {
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
throw err;
}),
);
}
}; };
/**
* Handles toggle buttons in the cluster's table.
*
* When the user clicks the toggle button for each cluster, it:
* - toggles the button
* - shows a loading and disables button
* - Makes a put request to the given endpoint
* Once we receive the response, either:
* 1) Show updated status in case of successfull response
* 2) Show initial status in case of failed response
*/
export default function setClusterTableToggles() {
document.querySelectorAll('.js-toggle-cluster-list')
.forEach(button => button.addEventListener('click', (e) => {
const toggleButton = e.currentTarget;
const endpoint = toggleButton.getAttribute('data-endpoint');
toggleValue(toggleButton);
toggleLoadingButton(toggleButton);
const value = toggleButton.classList.contains('is-checked');
ClustersService.updateCluster(endpoint, { cluster: { enabled: value } })
.then(() => {
toggleLoadingButton(toggleButton);
})
.catch(() => {
toggleLoadingButton(toggleButton);
toggleValue(toggleButton);
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
});
}));
}
import $ from 'jquery';
import Flash from './flash';
import { __ } from './locale';
import { convertPermissionToBoolean } from './lib/utils/common_utils';
/*
example HAML:
```
%button.js-project-feature-toggle.project-feature-toggle{ type: "button",
class: "#{'is-checked' if enabled?}",
'aria-label': _('Toggle Cluster') }
%input{ type: "hidden", class: 'js-project-feature-toggle-input', value: enabled? }
```
*/
function updatetoggle(toggle, isOn) {
toggle.classList.toggle('is-checked', isOn);
}
function onToggleClicked(toggle, input, clickCallback) {
const previousIsOn = convertPermissionToBoolean(input.value);
// Visually change the toggle and start loading
updatetoggle(toggle, !previousIsOn);
toggle.setAttribute('disabled', true);
toggle.classList.toggle('is-loading', true);
Promise.resolve(clickCallback(!previousIsOn, toggle))
.then(() => {
// Actually change the input value
input.setAttribute('value', !previousIsOn);
})
.catch(() => {
// Revert the visuals if something goes wrong
updatetoggle(toggle, previousIsOn);
})
.then(() => {
// Remove the loading indicator in any case
toggle.removeAttribute('disabled');
toggle.classList.toggle('is-loading', false);
$(input).trigger('trigger-change');
})
.catch(() => {
Flash(__('Something went wrong when toggling the button'));
});
}
export default function setupToggleButtons(container, clickCallback = () => {}) {
const toggles = container.querySelectorAll('.js-project-feature-toggle');
toggles.forEach((toggle) => {
const input = toggle.querySelector('.js-project-feature-toggle-input');
const isOn = convertPermissionToBoolean(input.value);
// Get the visible toggle in sync with the hidden input
updatetoggle(toggle, isOn);
toggle.addEventListener('click', onToggleClicked.bind(null, toggle, input, clickCallback));
});
}
...@@ -568,6 +568,9 @@ class Project < ActiveRecord::Base ...@@ -568,6 +568,9 @@ class Project < ActiveRecord::Base
RepositoryForkWorker.perform_async(id, RepositoryForkWorker.perform_async(id,
forked_from_project.repository_storage_path, forked_from_project.repository_storage_path,
forked_from_project.disk_path) forked_from_project.disk_path)
elsif gitlab_project_import?
# Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-ce/issues/26189 is solved.
RepositoryImportWorker.set(retry: false).perform_async(self.id)
else else
RepositoryImportWorker.perform_async(self.id) RepositoryImportWorker.perform_async(self.id)
end end
......
...@@ -2,7 +2,7 @@ class EmailsOnPushService < Service ...@@ -2,7 +2,7 @@ class EmailsOnPushService < Service
boolean_accessor :send_from_committer_email boolean_accessor :send_from_committer_email
boolean_accessor :disable_diffs boolean_accessor :disable_diffs
prop_accessor :recipients prop_accessor :recipients
validates :recipients, presence: true, if: :activated? validates :recipients, presence: true, if: :valid_recipients?
def title def title
'Emails on push' 'Emails on push'
......
...@@ -4,7 +4,7 @@ class IrkerService < Service ...@@ -4,7 +4,7 @@ class IrkerService < Service
prop_accessor :server_host, :server_port, :default_irc_uri prop_accessor :server_host, :server_port, :default_irc_uri
prop_accessor :recipients, :channels prop_accessor :recipients, :channels
boolean_accessor :colorize_messages boolean_accessor :colorize_messages
validates :recipients, presence: true, if: :activated? validates :recipients, presence: true, if: :valid_recipients?
before_validation :get_channels before_validation :get_channels
......
class PipelinesEmailService < Service class PipelinesEmailService < Service
prop_accessor :recipients prop_accessor :recipients
boolean_accessor :notify_only_broken_pipelines boolean_accessor :notify_only_broken_pipelines
validates :recipients, presence: true, if: :activated? validates :recipients, presence: true, if: :valid_recipients?
def initialize_properties def initialize_properties
self.properties ||= { notify_only_broken_pipelines: true } self.properties ||= { notify_only_broken_pipelines: true }
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
# and implement a set of methods # and implement a set of methods
class Service < ActiveRecord::Base class Service < ActiveRecord::Base
include Sortable include Sortable
include Importable
serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
default_value_for :active, false default_value_for :active, false
...@@ -295,4 +297,8 @@ class Service < ActiveRecord::Base ...@@ -295,4 +297,8 @@ class Service < ActiveRecord::Base
project.cache_has_external_wiki project.cache_has_external_wiki
end end
end end
def valid_recipients?
activated? && !importing?
end
end end
...@@ -5,15 +5,16 @@ ...@@ -5,15 +5,16 @@
= markdown_field(current_application_settings, :help_page_text) = markdown_field(current_application_settings, :help_page_text)
%hr %hr
- unless current_application_settings.help_page_hide_commercial_content? %h1
%h1 GitLab
GitLab Community Edition
Community Edition - if user_signed_in?
- if user_signed_in? %span= Gitlab::VERSION
%span= Gitlab::VERSION %small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION)
%small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION) = version_status_badge
= version_status_badge %hr
- unless current_application_settings.help_page_hide_commercial_content?
%p.slead %p.slead
GitLab is open source software to collaborate on code. GitLab is open source software to collaborate on code.
%br %br
......
...@@ -12,11 +12,12 @@ ...@@ -12,11 +12,12 @@
.table-section.section-10 .table-section.section-10
.table-mobile-header{ role: "rowheader" } .table-mobile-header{ role: "rowheader" }
.table-mobile-content .table-mobile-content
%button{ type: "button", %button.js-project-feature-toggle.project-feature-toggle{ type: "button",
class: "js-toggle-cluster-list project-feature-toggle #{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}", class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}",
"aria-label": s_("ClusterIntegration|Toggle Cluster"), "aria-label": s_("ClusterIntegration|Toggle Cluster"),
disabled: !cluster.can_toggle_cluster?, disabled: !cluster.can_toggle_cluster?,
data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } } data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
%input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? }
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
%span.toggle-icon %span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
......
...@@ -10,13 +10,12 @@ ...@@ -10,13 +10,12 @@
= s_('ClusterIntegration|Cluster integration is enabled for this project.') = s_('ClusterIntegration|Cluster integration is enabled for this project.')
- else - else
= s_('ClusterIntegration|Cluster integration is disabled for this project.') = s_('ClusterIntegration|Cluster integration is disabled for this project.')
%label.append-bottom-10 %label.append-bottom-10.js-cluster-enable-toggle-area
= field.hidden_field :enabled, { class: 'js-toggle-input'}
%button{ type: 'button', %button{ type: 'button',
class: "js-toggle-cluster project-feature-toggle #{'is-checked' unless !@cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}", class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if @cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}",
"aria-label": s_("ClusterIntegration|Toggle Cluster"), "aria-label": s_("ClusterIntegration|Toggle Cluster"),
disabled: !can?(current_user, :update_cluster, @cluster) } disabled: !can?(current_user, :update_cluster, @cluster) }
= field.hidden_field :enabled, { class: 'js-project-feature-toggle-input'}
%span.toggle-icon %span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
......
...@@ -20,7 +20,11 @@ class RepositoryImportWorker ...@@ -20,7 +20,11 @@ class RepositoryImportWorker
# to those importers to mark the import process as complete. # to those importers to mark the import process as complete.
return if service.async? return if service.async?
raise result[:message] if result[:status] == :error if result[:status] == :error
fail_import(project, result[:message]) if project.gitlab_project_import?
raise result[:message]
end
project.after_import project.after_import
end end
...@@ -33,4 +37,8 @@ class RepositoryImportWorker ...@@ -33,4 +37,8 @@ class RepositoryImportWorker
Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.") Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.")
false false
end end
def fail_import(project, message)
project.mark_import_as_failed(message)
end
end end
---
title: Fixes destination already exists, and some particular service errors on Import/Export
error
merge_request: 16714
author:
type: fixed
---
title: Fix version information not showing on help page if commercial content display
was disabled.
merge_request: 16743
author:
type: fixed
...@@ -19,8 +19,13 @@ module Gitlab ...@@ -19,8 +19,13 @@ module Gitlab
def error(error) def error(error)
error_out(error.message, caller[0].dup) error_out(error.message, caller[0].dup)
@errors << error.message @errors << error.message
# Debug: # Debug:
Rails.logger.error(error.backtrace.join("\n")) if error.backtrace
Rails.logger.error("Import/Export backtrace: #{error.backtrace.join("\n")}")
else
Rails.logger.error("No backtrace found")
end
end end
private private
......
...@@ -95,7 +95,7 @@ feature 'Gcp Cluster', :js do ...@@ -95,7 +95,7 @@ feature 'Gcp Cluster', :js do
context 'when user disables the cluster' do context 'when user disables the cluster' do
before do before do
page.find(:css, '.js-toggle-cluster').click page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click
page.within('#cluster-integration') { click_button 'Save changes' } page.within('#cluster-integration') { click_button 'Save changes' }
end end
......
...@@ -62,7 +62,7 @@ feature 'User Cluster', :js do ...@@ -62,7 +62,7 @@ feature 'User Cluster', :js do
context 'when user disables the cluster' do context 'when user disables the cluster' do
before do before do
page.find(:css, '.js-toggle-cluster').click page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
page.within('#cluster-integration') { click_button 'Save changes' } page.within('#cluster-integration') { click_button 'Save changes' }
end end
......
...@@ -37,13 +37,13 @@ feature 'Clusters', :js do ...@@ -37,13 +37,13 @@ feature 'Clusters', :js do
context 'inline update of cluster' do context 'inline update of cluster' do
it 'user can update cluster' do it 'user can update cluster' do
expect(page).to have_selector('.js-toggle-cluster-list') expect(page).to have_selector('.js-project-feature-toggle')
end end
context 'with sucessfull request' do context 'with sucessfull request' do
it 'user sees updated cluster' do it 'user sees updated cluster' do
expect do expect do
page.find('.js-toggle-cluster-list').click page.find('.js-project-feature-toggle').click
wait_for_requests wait_for_requests
end.to change { cluster.reload.enabled } end.to change { cluster.reload.enabled }
...@@ -57,7 +57,7 @@ feature 'Clusters', :js do ...@@ -57,7 +57,7 @@ feature 'Clusters', :js do
expect_any_instance_of(Clusters::UpdateService).to receive(:execute).and_call_original expect_any_instance_of(Clusters::UpdateService).to receive(:execute).and_call_original
allow_any_instance_of(Clusters::Cluster).to receive(:valid?) { false } allow_any_instance_of(Clusters::Cluster).to receive(:valid?) { false }
page.find('.js-toggle-cluster-list').click page.find('.js-project-feature-toggle').click
expect(page).to have_content('Something went wrong on our end.') expect(page).to have_content('Something went wrong on our end.')
expect(page).to have_selector('.is-checked') expect(page).to have_selector('.is-checked')
......
...@@ -23,16 +23,24 @@ describe('Clusters', () => { ...@@ -23,16 +23,24 @@ describe('Clusters', () => {
}); });
describe('toggle', () => { describe('toggle', () => {
it('should update the button and the input field on click', () => { it('should update the button and the input field on click', (done) => {
cluster.toggleButton.click(); const toggleButton = document.querySelector('.js-cluster-enable-toggle-area .js-project-feature-toggle');
const toggleInput = document.querySelector('.js-cluster-enable-toggle-area .js-project-feature-toggle-input');
expect( toggleButton.click();
cluster.toggleButton.classList,
).not.toContain('is-checked');
expect( getSetTimeoutPromise()
cluster.toggleInput.getAttribute('value'), .then(() => {
).toEqual('false'); expect(
toggleButton.classList,
).not.toContain('is-checked');
expect(
toggleInput.getAttribute('value'),
).toEqual('false');
})
.then(done)
.catch(done.fail);
}); });
}); });
......
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import setClusterTableToggles from '~/clusters/clusters_index';
import { setTimeout } from 'core-js/library/web/timers';
describe('Clusters table', () => {
preloadFixtures('clusters/index_cluster.html.raw');
let mock;
beforeEach(() => {
loadFixtures('clusters/index_cluster.html.raw');
mock = new MockAdapter(axios);
setClusterTableToggles();
});
describe('update cluster', () => {
it('renders loading state while request is made', () => {
const button = document.querySelector('.js-toggle-cluster-list');
button.click();
expect(button.classList).toContain('is-loading');
expect(button.getAttribute('disabled')).toEqual('true');
});
afterEach(() => {
mock.restore();
});
it('shows updated state after sucessfull request', (done) => {
mock.onPut().reply(200, {}, {});
const button = document.querySelector('.js-toggle-cluster-list');
button.click();
expect(button.classList).toContain('is-loading');
setTimeout(() => {
expect(button.classList).not.toContain('is-loading');
expect(button.classList).not.toContain('is-checked');
done();
}, 0);
});
it('shows inital state after failed request', (done) => {
mock.onPut().reply(500, {}, {});
const button = document.querySelector('.js-toggle-cluster-list');
button.click();
expect(button.classList).toContain('is-loading');
setTimeout(() => {
expect(button.classList).not.toContain('is-loading');
expect(button.classList).toContain('is-checked');
done();
}, 0);
});
});
});
...@@ -31,19 +31,4 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle ...@@ -31,19 +31,4 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
expect(response).to be_success expect(response).to be_success
store_frontend_fixture(response, example.description) store_frontend_fixture(response, example.description)
end end
context 'rendering non-empty state' do
before do
cluster
end
it 'clusters/index_cluster.html.raw' do |example|
get :index,
namespace_id: namespace,
project_id: project
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
end end
import setupToggleButtons from '~/toggle_buttons';
import getSetTimeoutPromise from './helpers/set_timeout_promise_helper';
function generateMarkup(isChecked = true) {
return `
<button type="button" class="${isChecked ? 'is-checked' : ''} js-project-feature-toggle">
<input type="hidden" class="js-project-feature-toggle-input" value="${isChecked}" />
</button>
`;
}
function setupFixture(isChecked, clickCallback) {
const wrapper = document.createElement('div');
wrapper.innerHTML = generateMarkup(isChecked);
setupToggleButtons(wrapper, clickCallback);
return wrapper;
}
describe('ToggleButtons', () => {
describe('when input value is true', () => {
it('should initialize as checked', () => {
const wrapper = setupFixture(true);
expect(wrapper.querySelector('.js-project-feature-toggle').classList.contains('is-checked')).toEqual(true);
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true');
});
it('should toggle to unchecked when clicked', (done) => {
const wrapper = setupFixture(true);
const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
toggleButton.click();
getSetTimeoutPromise()
.then(() => {
expect(toggleButton.classList.contains('is-checked')).toEqual(false);
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false');
})
.then(done)
.catch(done.fail);
});
});
describe('when input value is false', () => {
it('should initialize as unchecked', () => {
const wrapper = setupFixture(false);
expect(wrapper.querySelector('.js-project-feature-toggle').classList.contains('is-checked')).toEqual(false);
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false');
});
it('should toggle to checked when clicked', (done) => {
const wrapper = setupFixture(false);
const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
toggleButton.click();
getSetTimeoutPromise()
.then(() => {
expect(toggleButton.classList.contains('is-checked')).toEqual(true);
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true');
})
.then(done)
.catch(done.fail);
});
});
it('should emit `trigger-change` event', (done) => {
const changeSpy = jasmine.createSpy('changeEventHandler');
const wrapper = setupFixture(false);
const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
const input = wrapper.querySelector('.js-project-feature-toggle-input');
$(input).on('trigger-change', changeSpy);
toggleButton.click();
getSetTimeoutPromise()
.then(() => {
expect(changeSpy).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
describe('clickCallback', () => {
it('should show loading indicator while waiting', (done) => {
const isChecked = true;
const clickCallback = (newValue, toggleButton) => {
const input = toggleButton.querySelector('.js-project-feature-toggle-input');
expect(newValue).toEqual(false);
// Check for the loading state
expect(toggleButton.classList.contains('is-checked')).toEqual(false);
expect(toggleButton.classList.contains('is-loading')).toEqual(true);
expect(toggleButton.disabled).toEqual(true);
expect(input.value).toEqual('true');
// After the callback finishes, check that the loading state is gone
getSetTimeoutPromise()
.then(() => {
expect(toggleButton.classList.contains('is-checked')).toEqual(false);
expect(toggleButton.classList.contains('is-loading')).toEqual(false);
expect(toggleButton.disabled).toEqual(false);
expect(input.value).toEqual('false');
})
.then(done)
.catch(done.fail);
};
const wrapper = setupFixture(isChecked, clickCallback);
const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
toggleButton.click();
});
});
});
...@@ -7096,7 +7096,7 @@ ...@@ -7096,7 +7096,7 @@
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:01:51.232Z", "created_at": "2016-06-14T15:01:51.232Z",
"updated_at": "2016-06-14T15:01:51.232Z", "updated_at": "2016-06-14T15:01:51.232Z",
"active": false, "active": true,
"properties": { "properties": {
}, },
......
...@@ -164,6 +164,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do ...@@ -164,6 +164,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService') expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
end end
it 'saves the properties for a service' do
expect(saved_project_json['services'].first['properties']).to eq('one' => 'value')
end
it 'has project feature' do it 'has project feature' do
project_feature = saved_project_json['project_feature'] project_feature = saved_project_json['project_feature']
expect(project_feature).not_to be_empty expect(project_feature).not_to be_empty
...@@ -279,7 +283,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do ...@@ -279,7 +283,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
commit_id: ci_build.pipeline.sha) commit_id: ci_build.pipeline.sha)
create(:event, :created, target: milestone, project: project, author: user) create(:event, :created, target: milestone, project: project, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker') create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' })
create(:project_custom_attribute, project: project) create(:project_custom_attribute, project: project)
create(:project_custom_attribute, project: project) create(:project_custom_attribute, project: project)
......
...@@ -1441,7 +1441,7 @@ describe API::Issues, :mailer do ...@@ -1441,7 +1441,7 @@ describe API::Issues, :mailer do
context 'when source project does not exist' do context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do it 'returns 404 when trying to move an issue' do
post api("/projects/123/issues/#{issue.iid}/move", user), post api("/projects/12345/issues/#{issue.iid}/move", user),
to_project_id: target_project.id to_project_id: target_project.id
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
...@@ -1452,7 +1452,7 @@ describe API::Issues, :mailer do ...@@ -1452,7 +1452,7 @@ describe API::Issues, :mailer do
context 'when target project does not exist' do context 'when target project does not exist' do
it 'returns 404 when trying to move an issue' do it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: 123 to_project_id: 12345
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
......
...@@ -49,9 +49,22 @@ describe RepositoryImportWorker do ...@@ -49,9 +49,22 @@ describe RepositoryImportWorker do
expect do expect do
subject.perform(project.id) subject.perform(project.id)
end.to raise_error(StandardError, error) end.to raise_error(RuntimeError, error)
expect(project.reload.import_jid).not_to be_nil expect(project.reload.import_jid).not_to be_nil
end end
it 'updates the error on Import/Export' do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
project.update_attributes(import_jid: '123', import_type: 'gitlab_project')
expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
expect do
subject.perform(project.id)
end.to raise_error(RuntimeError, error)
expect(project.reload.import_error).not_to be_nil
end
end end
context 'when using an asynchronous importer' do context 'when using an asynchronous importer' do
......
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