Commit 841584cb authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-02-01

# Conflicts:
#	app/assets/javascripts/pages/projects/tree/show/index.js
#	scripts/lint-rugged

[ci skip]
parents 9a7d44c5 9e239f30
...@@ -763,8 +763,9 @@ cache gems: ...@@ -763,8 +763,9 @@ cache gems:
gitlab_git_test: gitlab_git_test:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
<<: *pull-cache
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
before_script: []
cache: {}
script: script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
/* eslint-disable no-new */ /* eslint-disable no-new */
import Flash from './flash'; import flash from './flash';
import axios from './lib/utils/axios_utils';
/** /**
* In each pipelines table we have a mini pipeline graph for each pipeline. * In each pipelines table we have a mini pipeline graph for each pipeline.
...@@ -78,26 +79,21 @@ export default class MiniPipelineGraph { ...@@ -78,26 +79,21 @@ export default class MiniPipelineGraph {
const button = e.relatedTarget; const button = e.relatedTarget;
const endpoint = button.dataset.stageEndpoint; const endpoint = button.dataset.stageEndpoint;
return $.ajax({
dataType: 'json',
type: 'GET',
url: endpoint,
beforeSend: () => {
this.renderBuildsList(button, ''); this.renderBuildsList(button, '');
this.toggleLoading(button); this.toggleLoading(button);
},
success: (data) => { axios.get(endpoint)
.then(({ data }) => {
this.toggleLoading(button); this.toggleLoading(button);
this.renderBuildsList(button, data.html); this.renderBuildsList(button, data.html);
this.stopDropdownClickPropagation(); this.stopDropdownClickPropagation();
}, })
error: () => { .catch(() => {
this.toggleLoading(button); this.toggleLoading(button);
if ($(button).parent().hasClass('open')) { if ($(button).parent().hasClass('open')) {
$(button).dropdown('toggle'); $(button).dropdown('toggle');
} }
new Flash('An error occurred while fetching the builds.', 'alert'); flash('An error occurred while fetching the builds.', 'alert');
},
}); });
} }
......
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
import { __ } from '../locale';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
import Raphael from './raphael'; import Raphael from './raphael';
export default (function() { export default (function() {
...@@ -26,16 +29,13 @@ export default (function() { ...@@ -26,16 +29,13 @@ export default (function() {
} }
BranchGraph.prototype.load = function() { BranchGraph.prototype.load = function() {
return $.ajax({ axios.get(this.options.url)
url: this.options.url, .then(({ data }) => {
method: "get",
dataType: "json",
success: $.proxy(function(data) {
$(".loading", this.element).hide(); $(".loading", this.element).hide();
this.prepareData(data.days, data.commits); this.prepareData(data.days, data.commits);
return this.buildGraph(); this.buildGraph();
}, this) })
}); .catch(() => __('Error fetching network graph.'));
}; };
BranchGraph.prototype.prepareData = function(days, commits) { BranchGraph.prototype.prepareData = function(days, commits) {
......
...@@ -16,6 +16,7 @@ import Autosize from 'autosize'; ...@@ -16,6 +16,7 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho'; import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache'; import AjaxCache from '~/lib/utils/ajax_cache';
import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility'; import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash'; import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle'; import CommentTypeToggle from './comment_type_toggle';
...@@ -252,26 +253,20 @@ export default class Notes { ...@@ -252,26 +253,20 @@ export default class Notes {
return; return;
} }
this.refreshing = true; this.refreshing = true;
return $.ajax({ axios.get(this.notes_url, {
url: this.notes_url, headers: {
headers: { 'X-Last-Fetched-At': this.last_fetched_at }, 'X-Last-Fetched-At': this.last_fetched_at,
dataType: 'json', },
success: (function(_this) { }).then(({ data }) => {
return function(data) { const notes = data.notes;
var notes; this.last_fetched_at = data.last_fetched_at;
notes = data.notes; this.setPollingInterval(data.notes.length);
_this.last_fetched_at = data.last_fetched_at; $.each(notes, (i, note) => this.renderNote(note));
_this.setPollingInterval(data.notes.length);
return $.each(notes, function(i, note) { this.refreshing = false;
_this.renderNote(note); }).catch(() => {
this.refreshing = false;
}); });
};
})(this)
}).always((function(_this) {
return function() {
return _this.refreshing = false;
};
})(this));
} }
/** /**
......
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
export default class NotificationsForm { export default class NotificationsForm {
constructor() { constructor() {
this.toggleCheckbox = this.toggleCheckbox.bind(this); this.toggleCheckbox = this.toggleCheckbox.bind(this);
...@@ -27,15 +31,10 @@ export default class NotificationsForm { ...@@ -27,15 +31,10 @@ export default class NotificationsForm {
saveEvent($checkbox, $parent) { saveEvent($checkbox, $parent) {
const form = $parent.parents('form:first'); const form = $parent.parents('form:first');
return $.ajax({
url: form.attr('action'),
method: form.attr('method'),
dataType: 'json',
data: form.serialize(),
beforeSend: () => {
this.showCheckboxLoadingSpinner($parent); this.showCheckboxLoadingSpinner($parent);
},
}).done((data) => { axios[form.attr('method')](form.attr('action'), form.serialize())
.then(({ data }) => {
$checkbox.enable(); $checkbox.enable();
if (data.saved) { if (data.saved) {
$parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done'); $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
...@@ -45,6 +44,7 @@ export default class NotificationsForm { ...@@ -45,6 +44,7 @@ export default class NotificationsForm {
.toggleClass('fa-spin fa-spinner fa-check is-done'); .toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000); }, 2000);
} }
}); })
.catch(() => flash(__('There was an error saving your notification settings.')));
} }
} }
import { getParameterByName } from '~/lib/utils/common_utils'; import { getParameterByName } from '~/lib/utils/common_utils';
import axios from './lib/utils/axios_utils';
import { removeParams } from './lib/utils/url_utility'; import { removeParams } from './lib/utils/url_utility';
const ENDLESS_SCROLL_BOTTOM_PX = 400; const ENDLESS_SCROLL_BOTTOM_PX = 400;
...@@ -22,13 +23,12 @@ export default { ...@@ -22,13 +23,12 @@ export default {
getOld() { getOld() {
this.loading.show(); this.loading.show();
$.ajax({ axios.get(this.url, {
type: 'GET', params: {
url: this.url, limit: this.limit,
data: `limit=${this.limit}&offset=${this.offset}`, offset: this.offset,
dataType: 'json', },
error: () => this.loading.hide(), }).then(({ data }) => {
success: (data) => {
this.append(data.count, this.prepareData(data.html)); this.append(data.count, this.prepareData(data.html));
this.callback(); this.callback();
...@@ -38,8 +38,7 @@ export default { ...@@ -38,8 +38,7 @@ export default {
} else { } else {
this.loading.hide(); this.loading.hide();
} }
}, }).catch(() => this.loading.hide());
});
}, },
append(count, html) { append(count, html) {
......
import axios from '../../../lib/utils/axios_utils';
import { __ } from '../../../locale';
import flash from '../../../flash';
export default function UsagePing() { export default function UsagePing() {
const usageDataUrl = $('.usage-data').data('endpoint'); const el = document.querySelector('.usage-data');
$.ajax({ axios.get(el.dataset.endpoint, {
type: 'GET', responseType: 'text',
url: usageDataUrl, }).then(({ data }) => {
dataType: 'html', el.innerHTML = data;
success(html) { }).catch(() => flash(__('Error fetching usage ping data.')));
$('.usage-data').html(html);
},
});
} }
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select'; import UsersSelect from '~/users_select';
import { isMetaClick } from '~/lib/utils/common_utils'; import { isMetaClick } from '~/lib/utils/common_utils';
import { __ } from '../../../../locale';
import flash from '../../../../flash';
import axios from '../../../../lib/utils/axios_utils';
export default class Todos { export default class Todos {
constructor() { constructor() {
...@@ -59,18 +62,12 @@ export default class Todos { ...@@ -59,18 +62,12 @@ export default class Todos {
const target = e.target; const target = e.target;
target.setAttribute('disabled', true); target.setAttribute('disabled', true);
target.classList.add('disabled'); target.classList.add('disabled');
$.ajax({
type: 'POST', axios[target.dataset.method](target.dataset.href)
url: target.dataset.href, .then(({ data }) => {
dataType: 'json',
data: {
'_method': target.dataset.method,
},
success: (data) => {
this.updateRowState(target); this.updateRowState(target);
return this.updateBadges(data); this.updateBadges(data);
}, }).catch(() => flash(__('Error updating todo status.')));
});
} }
updateRowState(target) { updateRowState(target) {
...@@ -98,19 +95,15 @@ export default class Todos { ...@@ -98,19 +95,15 @@ export default class Todos {
e.preventDefault(); e.preventDefault();
const target = e.currentTarget; const target = e.currentTarget;
const requestData = { '_method': target.dataset.method, ids: this.todo_ids };
target.setAttribute('disabled', true); target.setAttribute('disabled', true);
target.classList.add('disabled'); target.classList.add('disabled');
$.ajax({
type: 'POST', axios[target.dataset.method](target.dataset.href, {
url: target.dataset.href, ids: this.todo_ids,
dataType: 'json', }).then(({ data }) => {
data: requestData,
success: (data) => {
this.updateAllState(target, data); this.updateAllState(target, data);
return this.updateBadges(data); this.updateBadges(data);
}, }).catch(() => flash(__('Error updating status for all todos.')));
});
} }
updateAllState(target, data) { updateAllState(target, data) {
......
<<<<<<< HEAD
import initPathLocks from 'ee/path_locks'; import initPathLocks from 'ee/path_locks';
=======
import Vue from 'vue';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
>>>>>>> upstream/master
import TreeView from '../../../../tree'; import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../shortcuts_navigation'; import ShortcutsNavigation from '../../../../shortcuts_navigation';
import BlobViewer from '../../../../blob/viewer'; import BlobViewer from '../../../../blob/viewer';
...@@ -12,6 +17,30 @@ export default () => { ...@@ -12,6 +17,30 @@ export default () => {
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
$('#tree-slider').waitForImages(() => $('#tree-slider').waitForImages(() =>
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath)); ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
<<<<<<< HEAD
=======
const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
const statusLink = document.querySelector('.commit-actions .ci-status-link');
if (statusLink != null) {
statusLink.remove();
// eslint-disable-next-line no-new
new Vue({
el: commitPipelineStatusEl,
components: {
commitPipelineStatus,
},
render(createElement) {
return createElement('commit-pipeline-status', {
props: {
endpoint: commitPipelineStatusEl.dataset.endpoint,
},
});
},
});
}
};
>>>>>>> upstream/master
if (document.querySelector('.js-tree-content').dataset.pathLocksAvailable === 'true') { if (document.querySelector('.js-tree-content').dataset.pathLocksAvailable === 'true') {
initPathLocks( initPathLocks(
......
<script>
import Visibility from 'visibilityjs';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import Poll from '~/lib/utils/poll';
import Flash from '~/flash';
import { s__, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import CommitPipelineService from '../services/commit_pipeline_service';
export default {
directives: {
tooltip,
},
components: {
ciIcon,
loadingIcon,
},
props: {
endpoint: {
type: String,
required: true,
},
/* This prop can be used to replace some of the `render_commit_status`
used across GitLab, this way we could use this vue component and add a
realtime status where it makes sense
realtime: {
type: Boolean,
required: false,
default: true,
}, */
},
data() {
return {
ciStatus: {},
isLoading: true,
};
},
computed: {
statusTitle() {
return sprintf(s__('Commits|Commit: %{commitText}'), { commitText: this.ciStatus.text });
},
},
mounted() {
this.service = new CommitPipelineService(this.endpoint);
this.initPolling();
},
methods: {
successCallback(res) {
const pipelines = res.data.pipelines;
if (pipelines.length > 0) {
// The pipeline entity always keeps the latest pipeline info on the `details.status`
this.ciStatus = pipelines[0].details.status;
}
this.isLoading = false;
},
errorCallback() {
this.ciStatus = {
text: 'not found',
icon: 'status_notfound',
group: 'notfound',
};
this.isLoading = false;
Flash(s__('Something went wrong on our end'));
},
initPolling() {
this.poll = new Poll({
resource: this.service,
method: 'fetchData',
successCallback: response => this.successCallback(response),
errorCallback: this.errorCallback,
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
} else {
this.fetchPipelineCommitData();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
},
fetchPipelineCommitData() {
this.service.fetchData()
.then(this.successCallback)
.catch(this.errorCallback);
},
},
destroy() {
this.poll.stop();
},
};
</script>
<template>
<div>
<loading-icon
label="Loading pipeline status"
size="3"
v-if="isLoading"
/>
<a
v-else
:href="ciStatus.details_path"
>
<ci-icon
v-tooltip
:title="statusTitle"
:aria-label="statusTitle"
data-container="body"
:status="ciStatus"
/>
</a>
</div>
</template>
import axios from '~/lib/utils/axios_utils';
export default class CommitPipelineService {
constructor(endpoint) {
this.endpoint = endpoint;
}
fetchData() {
return axios.get(this.endpoint);
}
}
...@@ -199,6 +199,18 @@ ...@@ -199,6 +199,18 @@
.commit-actions { .commit-actions {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
font-size: 0; font-size: 0;
div {
display: inline;
}
.fa-spinner {
font-size: 12px;
}
span {
font-size: 6px;
}
} }
.ci-status-link { .ci-status-link {
...@@ -223,6 +235,11 @@ ...@@ -223,6 +235,11 @@
font-size: 14px; font-size: 14px;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
.ci-status-icon {
position: relative;
top: 1px;
}
} }
.commit, .commit,
......
...@@ -10,6 +10,10 @@ module Ci ...@@ -10,6 +10,10 @@ module Ci
can?(:developer_access) && pipeline_schedule.owned_by?(@user) can?(:developer_access) && pipeline_schedule.owned_by?(@user)
end end
condition(:non_owner_of_schedule) do
!pipeline_schedule.owned_by?(@user)
end
rule { can?(:developer_access) }.policy do rule { can?(:developer_access) }.policy do
enable :play_pipeline_schedule enable :play_pipeline_schedule
end end
...@@ -19,6 +23,10 @@ module Ci ...@@ -19,6 +23,10 @@ module Ci
enable :admin_pipeline_schedule enable :admin_pipeline_schedule
end end
rule { can?(:master_access) & non_owner_of_schedule }.policy do
enable :take_ownership_pipeline_schedule
end
rule { protected_ref }.prevent :play_pipeline_schedule rule { protected_ref }.prevent :play_pipeline_schedule
end end
end end
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: ref) = render_commit_status(commit, ref: ref)
#commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
= link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link" = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
......
...@@ -29,9 +29,10 @@ ...@@ -29,9 +29,10 @@
- if can?(current_user, :play_pipeline_schedule, pipeline_schedule) - 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 = link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('Play'), class: 'btn' do
= icon('play') = 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 = link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do
= s_('PipelineSchedules|Take ownership') = 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 = link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn' do
= icon('pencil') = icon('pencil')
- if can?(current_user, :admin_pipeline_schedule, pipeline_schedule) - if can?(current_user, :admin_pipeline_schedule, pipeline_schedule)
......
---
title: Hide pipeline schedule take ownership for current owner
merge_request: 12986
author:
type: fixed
---
title: Add realtime ci status for the repository -> files view
merge_request: 16523
author:
type: added
#!/usr/bin/env ruby #!/usr/bin/env ruby
ALLOWED = [ ALLOWED = [
<<<<<<< HEAD
# https://gitlab.com/gitlab-org/gitaly/issues/760 # https://gitlab.com/gitlab-org/gitaly/issues/760
'lib/elasticsearch/git/repository.rb', 'lib/elasticsearch/git/repository.rb',
=======
>>>>>>> upstream/master
# Can be fixed once Rugged is no longer used in production. Doesn't make Rugged calls. # Can be fixed once Rugged is no longer used in production. Doesn't make Rugged calls.
'config/initializers/8_metrics.rb', 'config/initializers/8_metrics.rb',
......
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Commit pipeline status component', () => {
let vm;
let Component;
let mock;
const mockCiStatus = {
details_path: '/root/hello-world/pipelines/1',
favicon: 'canceled.ico',
group: 'canceled',
has_details: true,
icon: 'status_canceled',
label: 'canceled',
text: 'canceled',
};
beforeEach(() => {
Component = Vue.extend(commitPipelineStatus);
});
describe('While polling pipeline data succesfully', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet('/dummy/endpoint').reply(() => {
const res = Promise.resolve([200, {
pipelines: [
{
details: {
status: mockCiStatus,
},
},
],
}]);
return res;
});
vm = mountComponent(Component, {
endpoint: '/dummy/endpoint',
});
});
afterEach(() => {
vm.poll.stop();
vm.$destroy();
mock.restore();
});
it('shows the loading icon when polling is starting', (done) => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
setTimeout(() => {
expect(vm.$el.querySelector('.loading-container')).toBe(null);
done();
});
});
it('contains a ciStatus when the polling is succesful ', (done) => {
setTimeout(() => {
expect(vm.ciStatus).toEqual(mockCiStatus);
done();
});
});
it('contains a ci-status icon when polling is succesful', (done) => {
setTimeout(() => {
expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(`ci-status-icon-${mockCiStatus.group}`);
done();
});
});
});
describe('When polling data was not succesful', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet('/dummy/endpoint').reply(() => {
const res = Promise.reject([502, { }]);
return res;
});
vm = new Component({
props: {
endpoint: '/dummy/endpoint',
},
});
});
afterEach(() => {
vm.poll.stop();
vm.$destroy();
mock.restore();
});
it('calls an errorCallback', (done) => {
spyOn(vm, 'errorCallback').and.callThrough();
vm.$mount();
setTimeout(() => {
expect(vm.errorCallback.calls.count()).toEqual(1);
done();
});
});
});
});
/* eslint-disable no-new */ /* eslint-disable no-new */
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import '~/flash'; import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Mini Pipeline Graph Dropdown', () => { describe('Mini Pipeline Graph Dropdown', () => {
preloadFixtures('static/mini_dropdown_graph.html.raw'); preloadFixtures('static/mini_dropdown_graph.html.raw');
...@@ -27,6 +29,16 @@ describe('Mini Pipeline Graph Dropdown', () => { ...@@ -27,6 +29,16 @@ describe('Mini Pipeline Graph Dropdown', () => {
}); });
describe('When dropdown is clicked', () => { describe('When dropdown is clicked', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('should call getBuildsList', () => { it('should call getBuildsList', () => {
const getBuildsListSpy = spyOn( const getBuildsListSpy = spyOn(
MiniPipelineGraph.prototype, MiniPipelineGraph.prototype,
...@@ -41,17 +53,20 @@ describe('Mini Pipeline Graph Dropdown', () => { ...@@ -41,17 +53,20 @@ describe('Mini Pipeline Graph Dropdown', () => {
}); });
it('should make a request to the endpoint provided in the html', () => { it('should make a request to the endpoint provided in the html', () => {
const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {}); const ajaxSpy = spyOn(axios, 'get').and.callThrough();
mock.onGet('foobar').reply(200, {
html: '',
});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click(); document.querySelector('.js-builds-dropdown-button').click();
expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar'); expect(ajaxSpy.calls.allArgs()[0][0]).toEqual('foobar');
}); });
it('should not close when user uses cmd/ctrl + click', () => { it('should not close when user uses cmd/ctrl + click', (done) => {
spyOn($, 'ajax').and.callFake(function (params) { mock.onGet('foobar').reply(200, {
params.success({
html: `<li> html: `<li>
<a class="mini-pipeline-graph-dropdown-item" href="#"> <a class="mini-pipeline-graph-dropdown-item" href="#">
<span class="ci-status-icon ci-status-icon-failed"></span> <span class="ci-status-icon ci-status-icon-failed"></span>
...@@ -60,19 +75,24 @@ describe('Mini Pipeline Graph Dropdown', () => { ...@@ -60,19 +75,24 @@ describe('Mini Pipeline Graph Dropdown', () => {
<a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a> <a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
</li>`, </li>`,
}); });
});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click(); document.querySelector('.js-builds-dropdown-button').click();
timeoutPromise()
.then(() => {
document.querySelector('a.mini-pipeline-graph-dropdown-item').click(); document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
})
.then(timeoutPromise)
.then(() => {
expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true); expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
}); })
.then(done)
.catch(done.fail);
}); });
it('should close the dropdown when request returns an error', (done) => { it('should close the dropdown when request returns an error', (done) => {
spyOn($, 'ajax').and.callFake(options => options.error()); mock.onGet('foobar').networkError();
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
...@@ -81,6 +101,7 @@ describe('Mini Pipeline Graph Dropdown', () => { ...@@ -81,6 +101,7 @@ describe('Mini Pipeline Graph Dropdown', () => {
setTimeout(() => { setTimeout(() => {
expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false); expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
done(); done();
}, 0); });
});
}); });
}); });
/* global fixture */ /* global fixture */
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import * as utils from '~/lib/utils/url_utility'; import * as utils from '~/lib/utils/url_utility';
import Pager from '~/pager'; import Pager from '~/pager';
...@@ -9,7 +10,6 @@ describe('pager', () => { ...@@ -9,7 +10,6 @@ describe('pager', () => {
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="content_list"></div><div class="loading"></div>'); setFixtures('<div class="content_list"></div><div class="loading"></div>');
spyOn($, 'ajax');
}); });
afterEach(() => { afterEach(() => {
...@@ -47,39 +47,90 @@ describe('pager', () => { ...@@ -47,39 +47,90 @@ describe('pager', () => {
}); });
describe('getOld', () => { describe('getOld', () => {
const urlRegex = /(.*)some_list(.*)$/;
let mock;
function mockSuccess() {
mock.onGet(urlRegex).reply(200, {
count: 0,
html: '',
});
}
function mockError() {
mock.onGet(urlRegex).networkError();
}
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="content_list" data-href="/some_list"></div><div class="loading"></div>'); setFixtures('<div class="content_list" data-href="/some_list"></div><div class="loading"></div>');
spyOn(axios, 'get').and.callThrough();
mock = new MockAdapter(axios);
Pager.init(); Pager.init();
}); });
it('shows loader while loading next page', () => { afterEach(() => {
mock.restore();
});
it('shows loader while loading next page', (done) => {
mockSuccess();
spyOn(Pager.loading, 'show'); spyOn(Pager.loading, 'show');
Pager.getOld(); Pager.getOld();
setTimeout(() => {
expect(Pager.loading.show).toHaveBeenCalled(); expect(Pager.loading.show).toHaveBeenCalled();
done();
});
}); });
it('hides loader on success', () => { it('hides loader on success', (done) => {
spyOn($, 'ajax').and.callFake(options => options.success({})); mockSuccess();
spyOn(Pager.loading, 'hide'); spyOn(Pager.loading, 'hide');
Pager.getOld(); Pager.getOld();
setTimeout(() => {
expect(Pager.loading.hide).toHaveBeenCalled(); expect(Pager.loading.hide).toHaveBeenCalled();
done();
});
}); });
it('hides loader on error', () => { it('hides loader on error', (done) => {
spyOn($, 'ajax').and.callFake(options => options.error()); mockError();
spyOn(Pager.loading, 'hide'); spyOn(Pager.loading, 'hide');
Pager.getOld(); Pager.getOld();
setTimeout(() => {
expect(Pager.loading.hide).toHaveBeenCalled(); expect(Pager.loading.hide).toHaveBeenCalled();
done();
});
}); });
it('sends request to url with offset and limit params', () => { it('sends request to url with offset and limit params', (done) => {
spyOn($, 'ajax');
Pager.offset = 100; Pager.offset = 100;
Pager.limit = 20; Pager.limit = 20;
Pager.getOld(); Pager.getOld();
const [{ data, url }] = $.ajax.calls.argsFor(0);
expect(data).toBe('limit=20&offset=100'); setTimeout(() => {
const [url, params] = axios.get.calls.argsFor(0);
expect(params).toEqual({
params: {
limit: 20,
offset: 100,
},
});
expect(url).toBe('/some_list'); expect(url).toBe('/some_list');
done();
});
}); });
}); });
}); });
...@@ -88,5 +88,19 @@ describe Ci::PipelineSchedulePolicy, :models do ...@@ -88,5 +88,19 @@ describe Ci::PipelineSchedulePolicy, :models do
expect(policy).to be_allowed :admin_pipeline_schedule expect(policy).to be_allowed :admin_pipeline_schedule
end end
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
end end
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
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