Commit 70a4d996 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2018-01-30

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents da3d22f8 40b6c91f
......@@ -422,7 +422,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.82.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -309,7 +309,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.82.0)
gitaly-proto (0.83.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -365,7 +365,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.4.1.1)
google-protobuf (3.5.1.1-universal-darwin)
googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0)
googleauth (0.5.3)
......@@ -394,7 +394,7 @@ GEM
rake
grape_logging (1.7.0)
grape
grpc (1.8.3)
grpc (1.8.3-universal-darwin)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7)
......@@ -1091,7 +1091,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.82.0)
gitaly-proto (~> 0.83.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......
import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
const Api = {
groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id.json',
groupPath: '/api/:version/groups/:id',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
......@@ -25,42 +25,40 @@ const Api = {
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId);
return $.ajax({
url,
dataType: 'json',
})
.done(group => callback(group));
return axios.get(url)
.then(({ data }) => callback(data));
},
// Return groups list. Filtered by query
groups(query, options, callback = $.noop) {
const url = Api.buildUrl(Api.groupsPath);
return $.ajax({
url,
data: Object.assign({
return axios.get(url, {
params: Object.assign({
search: query,
per_page: 20,
}, options),
dataType: 'json',
})
.done(groups => callback(groups));
.then(({ data }) => {
callback(data);
return data;
});
},
// Return namespaces list. Filtered by query
namespaces(query, callback) {
const url = Api.buildUrl(Api.namespacesPath);
return $.ajax({
url,
data: {
return axios.get(url, {
params: {
search: query,
per_page: 20,
},
dataType: 'json',
}).done(namespaces => callback(namespaces));
})
.then(({ data }) => callback(data));
},
// Return projects list. Filtered by query
projects(query, options, callback) {
projects(query, options, callback = _.noop) {
const url = Api.buildUrl(Api.projectsPath);
const defaults = {
search: query,
......@@ -72,12 +70,14 @@ const Api = {
defaults.membership = true;
}
return $.ajax({
url,
data: Object.assign(defaults, options),
dataType: 'json',
return axios.get(url, {
params: Object.assign(defaults, options),
})
.done(projects => callback(projects));
.then(({ data }) => {
callback(data);
return data;
});
},
// Return single project
......@@ -99,41 +99,34 @@ const Api = {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
}
return $.ajax({
url,
type: 'POST',
data: { label: data },
dataType: 'json',
return axios.post(url, {
label: data,
})
.done(label => callback(label))
.fail(message => callback(message.responseJSON));
.then(res => callback(res.data))
.catch(e => callback(e.response.data));
},
// Return group projects list. Filtered by query
groupProjects(groupId, query, callback) {
const url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', groupId);
return $.ajax({
url,
data: {
return axios.get(url, {
params: {
search: query,
per_page: 20,
},
dataType: 'json',
})
.done(projects => callback(projects));
.then(({ data }) => callback(data));
},
commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath)
.replace(':id', encodeURIComponent(id));
return this.wrapAjaxCall({
url,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
dataType: 'json',
return axios.post(url, JSON.stringify(data), {
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
});
},
......@@ -142,40 +135,37 @@ const Api = {
.replace(':id', encodeURIComponent(id))
.replace(':branch', branch);
return this.wrapAjaxCall({
url,
type: 'GET',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
});
return axios.get(url);
},
// Return text for a specific license
licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath)
.replace(':key', key);
return $.ajax({
url,
data,
return axios.get(url, {
params: data,
})
.done(license => callback(license));
.then(res => callback(res.data));
},
gitignoreText(key, callback) {
const url = Api.buildUrl(Api.gitignorePath)
.replace(':key', key);
return $.get(url, gitignore => callback(gitignore));
return axios.get(url)
.then(({ data }) => callback(data));
},
gitlabCiYml(key, callback) {
const url = Api.buildUrl(Api.gitlabCiYmlPath)
.replace(':key', key);
return $.get(url, file => callback(file));
return axios.get(url)
.then(({ data }) => callback(data));
},
dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
$.get(url, callback);
return axios.get(url)
.then(({ data }) => callback(data));
},
issueTemplate(namespacePath, projectPath, key, type, callback) {
......@@ -184,49 +174,48 @@ const Api = {
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
$.ajax({
url,
dataType: 'json',
})
.done(file => callback(null, file))
.fail(callback);
return axios.get(url)
.then(({ data }) => callback(null, data))
.catch(callback);
},
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return Api.wrapAjaxCall({
url,
data: Object.assign({
return axios.get(url, {
params: Object.assign({
search: query,
per_page: 20,
}, options),
dataType: 'json',
});
},
approverUsers(search, options, callback = $.noop) {
const url = Api.buildUrl('/autocomplete/users.json');
return $.ajax({
url,
data: $.extend({
return axios.get(url, {
params: Object.assign({
search,
per_page: 20,
}, options),
dataType: 'json',
}).done(callback);
}).then(({ data }) => {
callback(data);
return data;
});
},
ldap_groups(query, provider, callback) {
const url = Api.buildUrl(this.ldapGroupsPath).replace(':provider', provider);
return Api.wrapAjaxCall({
url,
data: Object.assign({
return axios.get(url, {
params: {
search: query,
per_page: 20,
active: true,
}),
dataType: 'json',
}).then(groups => callback(groups));
},
}).then(({ data }) => {
callback(data);
return data;
});
},
buildUrl(url) {
......@@ -236,21 +225,6 @@ const Api = {
}
return urlRoot + url.replace(':version', gon.api_version);
},
wrapAjaxCall(options) {
return new Promise((resolve, reject) => {
// jQuery 2 is not Promises/A+ compatible (missing catch)
$.ajax(options) // eslint-disable-line promise/catch-or-return
.then(data => resolve(data),
(jqXHR, textStatus, errorThrown) => {
const error = new Error(`${options.url}: ${errorThrown}`);
error.textStatus = textStatus;
if (jqXHR && jqXHR.responseJSON) error.responseJSON = jqXHR.responseJSON;
reject(error);
},
);
});
},
};
export default Api;
import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
export default class BlobViewer {
constructor() {
......@@ -127,25 +128,18 @@ export default class BlobViewer {
const viewer = viewerParam;
const url = viewer.getAttribute('data-url');
return new Promise((resolve, reject) => {
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
resolve(viewer);
return;
return Promise.resolve(viewer);
}
viewer.setAttribute('data-loading', 'true');
$.ajax({
url,
dataType: 'JSON',
})
.fail(reject)
.done((data) => {
return axios.get(url)
.then(({ data }) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
resolve(viewer);
});
return viewer;
});
}
}
......@@ -5,6 +5,7 @@
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
import axios from './lib/utils/axios_utils';
export default (function () {
const CommitsList = {};
......@@ -43,28 +44,29 @@ export default (function () {
CommitsList.filterResults = function () {
const form = $('.commits-search-form');
const search = CommitsList.searchField.val();
if (search === CommitsList.lastSearch) return;
if (search === CommitsList.lastSearch) return Promise.resolve();
const commitsUrl = form.attr('action') + '?' + form.serialize();
CommitsList.content.fadeTo('fast', 0.5);
return $.ajax({
type: 'GET',
url: form.attr('action'),
data: form.serialize(),
complete: function () {
return CommitsList.content.fadeTo('fast', 1.0);
},
success: function (data) {
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
[obj.name]: obj.value,
}), {});
return axios.get(form.attr('action'), {
params,
})
.then(({ data }) => {
CommitsList.lastSearch = search;
CommitsList.content.html(data.html);
return history.replaceState({
page: commitsUrl,
CommitsList.content.fadeTo('fast', 1.0);
// Change url so if user reload a page - search results are saved
history.replaceState({
page: commitsUrl,
}, document.title, commitsUrl);
},
error: function () {
})
.catch(() => {
CommitsList.content.fadeTo('fast', 1.0);
CommitsList.lastSearch = null;
},
dataType: 'json',
});
};
......
......@@ -71,7 +71,7 @@ export const setResizingStatus = ({ commit }, resizing) => {
export const checkCommitStatus = ({ state }) =>
service
.getBranchData(state.currentProjectId, state.currentBranchId)
.then((data) => {
.then(({ data }) => {
const { id } = data.commit;
const selectedBranch =
state.projects[state.currentProjectId].branches[state.currentBranchId];
......@@ -90,7 +90,7 @@ export const commitChanges = (
) =>
service
.commit(state.currentProjectId, payload)
.then((data) => {
.then(({ data }) => {
const { branch } = payload;
if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true);
......@@ -139,8 +139,8 @@ export const commitChanges = (
})
.catch((err) => {
let errMsg = 'Error committing changes. Please try again.';
if (err.responseJSON && err.responseJSON.message) {
errMsg += ` (${stripHtml(err.responseJSON.message)})`;
if (err.response.data && err.response.data.message) {
errMsg += ` (${stripHtml(err.response.data.message)})`;
}
flash(errMsg, 'alert', document, null, false, true);
window.dispatchEvent(new Event('resize'));
......
......@@ -10,7 +10,7 @@ export const getBranchData = (
!state.projects[`${projectId}`].branches[branchId])
|| force) {
service.getBranchData(`${projectId}`, branchId)
.then((data) => {
.then(({ data }) => {
const { id } = data.commit;
commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
......
......@@ -8,16 +8,16 @@ class UsersCache extends Cache {
}
return Api.users('', { username })
.then((users) => {
if (!users.length) {
.then(({ data }) => {
if (!data.length) {
throw new Error(`User "${username}" could not be found!`);
}
if (users.length > 1) {
if (data.length > 1) {
throw new Error(`Expected username "${username}" to be unique!`);
}
const user = users[0];
const user = data[0];
this.internalStorage[username] = user;
return user;
});
......
class Admin::GitalyServersController < Admin::ApplicationController
def index
@gitaly_servers = Gitaly::Server.all
end
end
......@@ -153,10 +153,6 @@
GitLab API
%span.pull-right
= API::API::version
%p
Gitaly
%span.pull-right
= Gitlab::GitalyClient.expected_server_version
- if Gitlab.config.pages.enabled
%p
GitLab Pages
......@@ -172,10 +168,6 @@
- else
Undefined
%p
Git
%span.pull-right
= Gitlab::Git.version
%p
Ruby
%span.pull-right
......@@ -188,6 +180,8 @@
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%p
= link_to "Gitaly Servers", admin_gitaly_servers_path
.row
.col-md-4
.info-well
......
- breadcrumb_title _("Gitaly Servers")
%h3.page-title= _("Gitaly Servers")
%hr
.gitaly_servers
- if @gitaly_servers.any?
.table-holder
%table.table.responsive-table
%thead.hidden-sm.hidden-xs
%tr
%th= _("Storage")
%th= n_("Gitaly|Address")
%th= _("Server version")
%th= _("Git version")
%th= _("Up to date")
- @gitaly_servers.each do |server|
%tr
%td
= server.storage
%td
= server.address
%td
= server.server_version
%td
= server.git_binary_version
%td
= boolean_to_icon(server.up_to_date?)
- else
.empty-state
.text-center
%h4= _("No connection could be made to a Gitaly Server, please check your logs!")
---
title: Add Gitaly Servers admin dashboard
merge_request:
author:
type: added
......@@ -29,6 +29,8 @@ namespace :admin do
resource :impersonation, only: :destroy
resources :abuse_reports, only: [:index, :destroy]
resources :gitaly_servers, only: [:index]
resources :spam_logs, only: [:index, :destroy] do
member do
post :mark_as_ham
......
......@@ -79,9 +79,9 @@ export default class ApproversSelect {
query: (query) => {
const fetchGroups = this.fetchGroups(query.term);
const fetchUsers = this.fetchUsers(query.term);
return $.when(fetchGroups, fetchUsers).then((groups, users) => {
return Promise.all([fetchGroups, fetchUsers]).then(([groups, users]) => {
const data = {
results: groups[0].concat(users[0]),
results: groups.concat(users),
};
return query.callback(data);
});
......
......@@ -13,22 +13,17 @@ function AdminEmailSelect() {
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query: function(query) {
var group_result, project_result;
group_result = Api.groups(query.term, {}, function(groups) {
return groups;
});
project_result = Api.projects(query.term, {
const groupsFetch = Api.groups(query.term, {});
const projectsFetch = Api.projects(query.term, {
order_by: 'id',
membership: false
}, function(projects) {
return projects;
});
return $.when(project_result, group_result).done(function(projects, groups) {
return Promise.all([projectsFetch, groupsFetch]).then(function([projects, groups]) {
var all, data;
all = {
id: "all"
};
data = [all].concat(groups[0], projects[0]);
data = [all].concat(groups, projects);
return query.callback({
results: data
});
......
......@@ -17,9 +17,7 @@ class Groups::EpicsController < Groups::ApplicationController
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("groups/epics/_epics")
}
render json: serializer.represent(@epics)
end
end
end
......@@ -38,6 +36,10 @@ class Groups::EpicsController < Groups::ApplicationController
private
def pagination_disabled?
request.format.json?
end
def epic
@issuable = @epic ||= @group.epics.find_by(iid: params[:epic_id] || params[:id])
......
module Gitaly
class Server
def self.all
Gitlab.config.repositories.storages.keys.map { |s| Gitaly::Server.new(s) }
end
attr_reader :storage
def initialize(storage)
@storage = storage
end
def server_version
info.server_version
end
def git_binary_version
info.git_version
end
def up_to_date?
server_version == Gitlab::GitalyClient.expected_server_version
end
def address
Gitlab::GitalyClient.address(@storage)
rescue RuntimeError => e
"Error getting the address: #{e.message}"
end
private
def info
@info ||=
begin
Gitlab::GitalyClient::ServerService.new(@storage).info
rescue GRPC::Unavailable, GRPC::GRPC::DeadlineExceeded
# This will show the server as being out of date
Gitaly::ServerInfoResponse.new(git_version: '', server_version: '')
end
end
end
end
module Gitlab
module GitalyClient
# Meant for extraction of server data, and later maybe to perform misc task
#
# Not meant for connection logic, look in Gitlab::GitalyClient
class ServerService
def initialize(storage)
@storage = storage
end
def info
GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new)
end
end
end
end
......@@ -145,9 +145,11 @@ module Gitlab
#
# See `Gitlab::Metrics::Transaction#add_event` for more details.
def add_event(*args)
trans = current_transaction
current_transaction&.add_event(*args)
end
trans&.add_event(*args)
def add_event_with_values(*args)
current_transaction&.add_event_with_values(*args)
end
# Returns the prefix to use for the name of a series.
......
This diff is collapsed.
require 'spec_helper'
describe Admin::GitalyServersController do
describe '#index' do
before do
sign_in(create(:admin))
end
it 'shows the gitaly servers page' do
get :index
expect(response).to have_gitlab_http_status(200)
end
end
end
......@@ -76,6 +76,39 @@ describe Groups::EpicsController do
expect(response).to have_gitlab_http_status(200)
end
end
context 'when format is JSON' do
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
end
def list_epics
get :index, group_id: group, format: :json
end
it 'returns a list of epics' do
list_epics
expect(json_response).to be_an Array
end
it 'does not use pagination' do
list_epics
expect(json_response.size).to eq(2)
end
it 'returns correct epic attributes' do
list_epics
item = json_response.first
epic = Epic.find(item['id'])
expect(item['group_id']).to eq(group.id)
expect(item['start_date']).to eq(epic.start_date)
expect(item['end_date']).to eq(epic.end_date)
expect(item['web_url']).to eq(group_epic_path(group, epic))
end
end
end
describe 'GET #show' do
......
require 'spec_helper'
describe API::Jobs do
set(:project) do
create(:project, :repository, public_builds: false)
end
set(:pipeline) do
create(:ci_empty_pipeline, project: project,
sha: project.commit.id,
ref: project.default_branch)
end
let!(:job) { create(:ci_build, :success, pipeline: pipeline) }
let(:user) { create(:user) }
let(:api_user) { user }
let(:reporter) { create(:project_member, :reporter, project: project).user }
let(:cross_project_pipeline_enabled) { true }
let(:object_storage_enabled) { true }
before do
stub_licensed_features(cross_project_pipelines: cross_project_pipeline_enabled,
object_storage: object_storage_enabled)
project.add_developer(user)
end
describe 'GET /projects/:id/jobs/:job_id/artifacts' do
shared_examples 'downloads artifact' do
let(:download_headers) do
{ 'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
it 'returns specific job artifacts' do
expect(response).to have_gitlab_http_status(200)
expect(response.headers).to include(download_headers)
expect(response.body).to match_file(job.artifacts_file.file.file)
end
end
context 'normal authentication' do
before do
stub_artifacts_object_storage
end
context 'when job with artifacts are stored remotely' do
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
before do
job.reload
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
end
it 'returns location redirect' do
expect(response).to have_gitlab_http_status(302)
end
end
end
context 'authorized by job_token' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
before do
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts"), job_token: job.token
end
context 'user is developer' do
let(:api_user) { user }
it_behaves_like 'downloads artifact'
end
context 'when anonymous user is accessing private artifacts' do
let(:api_user) { nil }
it 'hides artifacts and rejects request' do
expect(project).to be_private
expect(response).to have_gitlab_http_status(404)
end
end
context 'feature is disabled for EES' do
let(:api_user) { user }
let(:cross_project_pipeline_enabled) { false }
it 'disallows access to the artifacts' do
expect(response).to have_gitlab_http_status(404)
end
end
end
end
describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
let(:api_user) { reporter }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
before do
stub_artifacts_object_storage(licensed: :skip)
job.success
end
def get_for_ref(ref = pipeline.ref, job_name = job.name)
get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), job: job_name
end
context 'find proper job' do
shared_examples 'a valid file' do
context 'when artifacts are stored remotely' do
let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
before do
job.reload
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
end
it 'returns location redirect' do
expect(response).to have_gitlab_http_status(302)
end
end
end
context 'with regular branch' do
before do
pipeline.reload
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
get_for_ref('master')
end
it_behaves_like 'a valid file'
end
context 'with branch name containing slash' do
before do
pipeline.reload
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
get_for_ref('improve/awesome')
end
it_behaves_like 'a valid file'
end
context 'when using job_token to authenticate' do
before do
pipeline.reload
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
get api("/projects/#{project.id}/jobs/artifacts/master/download"), job: job.name, job_token: job.token
end
context 'when user is reporter' do
it_behaves_like 'a valid file'
end
context 'when user is admin, but not member' do
let(:api_user) { create(:admin) }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
it 'does not allow to see that artfiact is present' do
expect(response).to have_gitlab_http_status(404)
end
end
end
end
end
end
require 'spec_helper'
describe API::V3::Builds do
set(:user) { create(:user) }
let(:api_user) { user }
set(:project) { create(:project, :repository, creator: user, public_builds: false) }
let!(:developer) { create(:project_member, :developer, user: user, project: project) }
let(:reporter) { create(:project_member, :reporter, project: project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
let(:build) { create(:ci_build, pipeline: pipeline) }
before do
stub_artifacts_object_storage
end
describe 'GET /projects/:id/builds/:build_id/artifacts' do
before do
get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
context 'when job with artifacts are stored remotely' do
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) }
it 'returns location redirect' do
get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
expect(response).to have_gitlab_http_status(302)
end
end
end
describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
let(:api_user) { reporter.user }
before do
build.success
end
def path_for_ref(ref = pipeline.ref, job = build.name)
v3_api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user)
end
shared_examples 'a valid file' do
context 'when artifacts are stored remotely' do
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) }
before do
build.reload
get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
it 'returns location redirect' do
expect(response).to have_gitlab_http_status(302)
end
end
end
context 'with regular branch' do
before do
pipeline.reload
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
get path_for_ref('master')
end
it_behaves_like 'a valid file'
end
context 'with branch name containing slash' do
before do
pipeline.reload
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
get path_for_ref('improve/awesome')
end
it_behaves_like 'a valid file'
end
end
end
......@@ -122,7 +122,7 @@ feature 'Project > Members > Share with Group', :js do
select2 group.id, from: '#link_group_id'
fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d')
page.find('body').click
click_on 'share-with-group-tab'
find('.btn-create').click
end
......
This diff is collapsed.
/* eslint-disable no-new */
import MockAdapter from 'axios-mock-adapter';
import BlobViewer from '~/blob/viewer/index';
import axios from '~/lib/utils/axios_utils';
describe('Blob viewer', () => {
let blob;
let mock;
preloadFixtures('snippets/show.html.raw');
beforeEach(() => {
mock = new MockAdapter(axios);
loadFixtures('snippets/show.html.raw');
$('#modal-upload-blob').remove();
blob = new BlobViewer();
spyOn($, 'ajax').and.callFake(() => {
const d = $.Deferred();
d.resolve({
mock.onGet('http://test.host/snippets/1.json?viewer=rich').reply(200, {
html: '<div>testing</div>',
});
return d.promise();
mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, {
html: '<div>testing</div>',
});
spyOn(axios, 'get').and.callThrough();
});
afterEach(() => {
mock.restore();
location.hash = '';
});
......@@ -30,7 +37,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
expect($.ajax).toHaveBeenCalled();
expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'),
......@@ -46,7 +52,6 @@ describe('Blob viewer', () => {
new BlobViewer();
setTimeout(() => {
expect($.ajax).toHaveBeenCalled();
expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'),
......@@ -64,12 +69,8 @@ describe('Blob viewer', () => {
});
asyncClick()
.then(() => asyncClick())
.then(() => {
expect($.ajax).toHaveBeenCalled();
return asyncClick();
})
.then(() => {
expect($.ajax.calls.count()).toBe(1);
expect(
document.querySelector('.blob-viewer[data-type="simple"]').getAttribute('data-loaded'),
).toBe('true');
......@@ -122,7 +123,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
expect($.ajax).toHaveBeenCalled();
expect(
copyButton.classList.contains('disabled'),
).toBeFalsy();
......@@ -135,8 +135,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
expect($.ajax).toHaveBeenCalled();
expect(
copyButton.getAttribute('data-original-title'),
).toBe('Copy source to clipboard');
......@@ -171,14 +169,14 @@ describe('Blob viewer', () => {
it('sends AJAX request when switching to simple view', () => {
blob.switchToViewer('simple');
expect($.ajax).toHaveBeenCalled();
expect(axios.get).toHaveBeenCalled();
});
it('does not send AJAX request when switching to rich view', () => {
blob.switchToViewer('simple');
blob.switchToViewer('rich');
expect($.ajax.calls.count()).toBe(1);
expect(axios.get.calls.count()).toBe(1);
});
});
});
import 'vendor/jquery.endless-scroll';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import CommitsList from '~/commits';
describe('Commits List', () => {
......@@ -43,30 +45,47 @@ describe('Commits List', () => {
describe('on entering input', () => {
let ajaxSpy;
let mock;
beforeEach(() => {
CommitsList.init(25);
CommitsList.searchField.val('');
spyOn(history, 'replaceState').and.stub();
ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => {
req.success({
data: '<li>Result</li>',
mock = new MockAdapter(axios);
mock.onGet('/h5bp/html5-boilerplate/commits/master').reply(200, {
html: '<li>Result</li>',
});
ajaxSpy = spyOn(axios, 'get').and.callThrough();
});
afterEach(() => {
mock.restore();
});
it('should save the last search string', () => {
it('should save the last search string', (done) => {
CommitsList.searchField.val('GitLab');
CommitsList.filterResults();
CommitsList.filterResults()
.then(() => {
expect(ajaxSpy).toHaveBeenCalled();
expect(CommitsList.lastSearch).toEqual('GitLab');
done();
})
.catch(done.fail);
});
it('should not make ajax call if the input does not change', () => {
CommitsList.filterResults();
it('should not make ajax call if the input does not change', (done) => {
CommitsList.filterResults()
.then(() => {
expect(ajaxSpy).not.toHaveBeenCalled();
expect(CommitsList.lastSearch).toEqual('');
done();
})
.catch(done.fail);
});
});
});
......@@ -92,7 +92,9 @@ describe('UsersCache', () => {
apiSpy = (query, options) => {
expect(query).toBe('');
expect(options).toEqual({ username: dummyUsername });
return Promise.resolve([dummyUser]);
return Promise.resolve({
data: [dummyUser],
});
};
UsersCache.retrieve(dummyUsername)
......
......@@ -18,9 +18,11 @@ describe('new file modal component', () => {
}));
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: {
id: '123branch',
},
},
}));
spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
......
......@@ -17,9 +17,11 @@ describe('new dropdown upload', () => {
}));
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: {
id: '123branch',
},
},
}));
spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
......
......@@ -106,8 +106,10 @@ describe('RepoCommitSection', () => {
changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles));
spyOn(service, 'commit').and.returnValue(Promise.resolve({
data: {
short_id: '1',
stats: {},
},
}));
});
......
......@@ -178,7 +178,9 @@ describe('Multi-file store actions', () => {
it('calls service', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '123' },
},
}));
store.dispatch('checkCommitStatus')
......@@ -192,7 +194,9 @@ describe('Multi-file store actions', () => {
it('returns true if current ref does not equal returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '123' },
},
}));
store.dispatch('checkCommitStatus')
......@@ -206,7 +210,9 @@ describe('Multi-file store actions', () => {
it('returns false if current ref equals returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '1' },
},
}));
store.dispatch('checkCommitStatus')
......@@ -250,6 +256,7 @@ describe('Multi-file store actions', () => {
describe('success', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
data: {
id: '123456',
short_id: '123',
message: 'test message',
......@@ -258,6 +265,7 @@ describe('Multi-file store actions', () => {
additions: '1',
deletions: '2',
},
},
}));
});
......@@ -321,7 +329,9 @@ describe('Multi-file store actions', () => {
describe('failed', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
data: {
message: 'failed message',
},
}));
});
......
require 'spec_helper'
describe Gitaly::Server do
describe '.all' do
let(:storages) { Gitlab.config.repositories.storages }
it 'includes all storages' do
expect(storages.count).to eq(described_class.all.count)
expect(storages.keys).to eq(described_class.all.map(&:storage))
end
end
subject { described_class.all.first }
it { is_expected.to respond_to(:server_version) }
it { is_expected.to respond_to(:git_binary_version) }
it { is_expected.to respond_to(:up_to_date?) }
it { is_expected.to respond_to(:address) }
describe 'request memoization' do
context 'when requesting multiple properties', :request_store do
it 'uses memoization for the info request' do
expect do
subject.server_version
subject.up_to_date?
end.to change { Gitlab::GitalyClient.get_request_count }.by(1)
end
end
end
end
......@@ -17,12 +17,8 @@ describe API::Jobs do
let(:api_user) { user }
let(:reporter) { create(:project_member, :reporter, project: project).user }
let(:guest) { create(:project_member, :guest, project: project).user }
let(:cross_project_pipeline_enabled) { true }
let(:object_storage_enabled) { true }
before do
stub_licensed_features(cross_project_pipelines: cross_project_pipeline_enabled,
object_storage: object_storage_enabled)
project.add_developer(user)
end
......@@ -30,8 +26,6 @@ describe API::Jobs do
let(:query) { Hash.new }
before do |example|
job
unless example.metadata[:skip_before_request]
get api("/projects/#{project.id}/jobs", api_user), query
end
......@@ -118,7 +112,6 @@ describe API::Jobs do
let(:query) { Hash.new }
before do
job
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
end
......@@ -321,10 +314,6 @@ describe API::Jobs do
end
context 'normal authentication' do
before do
stub_artifacts_object_storage
end
context 'job with artifacts' do
context 'when artifacts are stored locally' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
......@@ -346,21 +335,6 @@ describe API::Jobs do
end
end
context 'when artifacts are stored remotely' do
let(:job) { create(:ci_build, pipeline: pipeline) }
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
before do
job.reload
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
end
it 'returns location redirect' do
expect(response).to have_gitlab_http_status(302)
end
end
it 'does not return job artifacts if not uploaded' do
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
......@@ -368,38 +342,6 @@ describe API::Jobs do
end
end
end
context 'authorized by job_token' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
before do
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts"), job_token: job.token
end
context 'user is developer' do
let(:api_user) { user }
it_behaves_like 'downloads artifact'
end
context 'when anonymous user is accessing private artifacts' do
let(:api_user) { nil }
it 'hides artifacts and rejects request' do
expect(project).to be_private
expect(response).to have_gitlab_http_status(404)
end
end
context 'feature is disabled for EES' do
let(:api_user) { user }
let(:cross_project_pipeline_enabled) { false }
it 'disallows access to the artifacts' do
expect(response).to have_gitlab_http_status(404)
end
end
end
end
describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
......@@ -407,7 +349,6 @@ describe API::Jobs do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
before do
stub_artifacts_object_storage(licensed: :skip)
job.success
end
......@@ -474,21 +415,6 @@ describe API::Jobs do
it { expect(response).to have_gitlab_http_status(200) }
it { expect(response.headers).to include(download_headers) }
end
context 'when artifacts are stored remotely' do
let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
before do
job.reload
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
end
it 'returns location redirect' do
expect(response).to have_gitlab_http_status(302)
end
end
end
context 'with regular branch' do
......@@ -516,29 +442,6 @@ describe API::Jobs do
it_behaves_like 'a valid file'
end
context 'when using job_token to authenticate' do
before do
pipeline.reload
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
get api("/projects/#{project.id}/jobs/artifacts/master/download"), job: job.name, job_token: job.token
end
context 'when user is reporter' do
it_behaves_like 'a valid file'
end
context 'when user is admin, but not member' do
let(:api_user) { create(:admin) }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
it 'does not allow to see that artfiact is present' do
expect(response).to have_gitlab_http_status(404)
end
end
end
end
end
......
......@@ -216,7 +216,6 @@ describe API::V3::Builds do
describe 'GET /projects/:id/builds/:build_id/artifacts' do
before do
stub_artifacts_object_storage
get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
......@@ -238,17 +237,6 @@ describe API::V3::Builds do
end
end
context 'when artifacts are stored remotely' do
let(:build) { create(:ci_build, pipeline: pipeline) }
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) }
it 'returns location redirect' do
get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
expect(response).to have_gitlab_http_status(302)
end
end
context 'unauthorized user' do
let(:api_user) { nil }
......@@ -268,7 +256,6 @@ describe API::V3::Builds do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
before do
stub_artifacts_object_storage
build.success
end
......@@ -334,21 +321,6 @@ describe API::V3::Builds do
it { expect(response).to have_gitlab_http_status(200) }
it { expect(response.headers).to include(download_headers) }
end
context 'when artifacts are stored remotely' do
let(:build) { create(:ci_build, pipeline: pipeline) }
let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) }
before do
build.reload
get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
it 'returns location redirect' do
expect(response).to have_gitlab_http_status(302)
end
end
end
context 'with regular branch' 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