Skip to content
Snippets Groups Projects
Commit 63a015fd authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 55693cc1
No related branches found
No related tags found
No related merge requests found
Showing
with 326 additions and 26 deletions
# frozen_string_literal: true
module API
module Entities
class ExternalIssue < Grape::Entity
expose :title
expose :id
end
end
end
# frozen_string_literal: true
module API
module Entities
class IssuableTimeStats < Grape::Entity
format_with(:time_tracking_formatter) do |time_spent|
Gitlab::TimeTrackingFormatter.output(time_spent)
end
expose :time_estimate
expose :total_time_spent
expose :human_time_estimate
with_options(format_with: :time_tracking_formatter) do
expose :total_time_spent, as: :human_total_time_spent
end
# rubocop: disable CodeReuse/ActiveRecord
def total_time_spent
# Avoids an N+1 query since timelogs are preloaded
object.timelogs.map(&:time_spent).sum
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
# frozen_string_literal: true
module API
module Entities
class Issue < IssueBasic
include ::API::Helpers::RelatedResourcesHelpers
expose(:has_tasks) do |issue, _|
!issue.task_list_items.empty?
end
expose :task_status, if: -> (issue, _) do
!issue.task_list_items.empty?
end
expose :_links do
expose :self do |issue|
expose_url(api_v4_project_issue_path(id: issue.project_id, issue_iid: issue.iid))
end
expose :notes do |issue|
expose_url(api_v4_projects_issues_notes_path(id: issue.project_id, noteable_id: issue.iid))
end
expose :award_emoji do |issue|
expose_url(api_v4_projects_issues_award_emoji_path(id: issue.project_id, issue_iid: issue.iid))
end
expose :project do |issue|
expose_url(api_v4_projects_path(id: issue.project_id))
end
end
expose :references, with: IssuableReferences do |issue|
issue
end
# Calculating the value of subscribed field triggers Markdown
# processing. We can't do that for multiple issues / merge
# requests in a single API request.
expose :subscribed, if: -> (_, options) { options.fetch(:include_subscribed, true) } do |issue, options|
issue.subscribed?(options[:current_user], options[:project] || issue.project)
end
expose :moved_to_id
end
end
end
# frozen_string_literal: true
module API
module Entities
class IssueBasic < IssuableEntity
expose :closed_at
expose :closed_by, using: Entities::UserBasic
expose :labels do |issue, options|
if options[:with_labels_details]
::API::Entities::LabelBasic.represent(issue.labels.sort_by(&:title))
else
issue.labels.map(&:title).sort
end
end
expose :milestone, using: Entities::Milestone
expose :assignees, :author, using: Entities::UserBasic
expose :assignee, using: ::API::Entities::UserBasic do |issue|
issue.assignees.first
end
expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) }
expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count, options[:current_user]) }
expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) }
expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
expose :due_date
expose :confidential
expose :discussion_locked
expose :web_url do |issue|
Gitlab::UrlBuilder.build(issue)
end
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
issue
end
expose :task_completion_status
end
end
end
# frozen_string_literal: true
module API
module Entities
class Milestone < Grape::Entity
expose :id, :iid
expose :project_id, if: -> (entity, options) { entity&.project_id }
expose :group_id, if: -> (entity, options) { entity&.group_id }
expose :title, :description
expose :state, :created_at, :updated_at
expose :due_date
expose :start_date
expose :web_url do |milestone, _options|
Gitlab::UrlBuilder.build(milestone)
end
end
end
end
Loading
Loading
@@ -22,6 +22,12 @@ module Gitlab
environment: ::Gitlab.dev_env_or_com?,
enabled_ratio: 0.25,
tracking_category: 'Growth::Acquisition::Experiment::PaidSignUpFlow'
},
suggest_pipeline: {
feature_toggle: :suggest_pipeline,
environment: ::Gitlab.dev_env_or_com?,
enabled_ratio: 0.1,
tracking_category: 'Growth::Expansion::Experiment::SuggestPipeline'
}
}.freeze
 
Loading
Loading
Loading
Loading
@@ -33,8 +33,8 @@ module Gitlab
backend.read(cache_key(key))
end
 
def write(key, value)
backend.write(cache_key(key), value)
def write(key, value, *args)
backend.write(cache_key(key), value, *args)
end
 
def fetch_without_caching_false(key, &block)
Loading
Loading
Loading
Loading
@@ -2104,6 +2104,9 @@ msgstr ""
msgid "Approver"
msgstr ""
 
msgid "Approvers"
msgstr ""
msgid "Apr"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -14,7 +14,7 @@ exports[`IDE pipeline stage renders stage details & icon 1`] = `
/>
<strong
class="prepend-left-8 ide-stage-title"
class="prepend-left-8 text-truncate"
data-container="body"
data-original-title=""
title=""
Loading
Loading
@@ -42,7 +42,7 @@ exports[`IDE pipeline stage renders stage details & icon 1`] = `
</div>
<div
class="card-body"
class="card-body p-0"
>
<item-stub
job="[object Object]"
Loading
Loading
Loading
Loading
@@ -165,7 +165,11 @@ export const mergeRequests = [
iid: 1,
title: 'Test merge request',
project_id: 1,
web_url: `${TEST_HOST}/namespace/project-path/merge_requests/1`,
web_url: `${TEST_HOST}/namespace/project-path/-/merge_requests/1`,
references: {
short: '!1',
full: 'namespace/project-path!1',
},
},
];
 
Loading
Loading
Loading
Loading
@@ -6,7 +6,8 @@ describe GitlabSchema.types['SnippetBlob'] do
it 'has the correct fields' do
expected_fields = [:highlighted_data, :raw_path,
:size, :binary, :name, :path,
:simple_viewer, :rich_viewer]
:simple_viewer, :rich_viewer,
:mode]
 
is_expected.to have_graphql_fields(*expected_fields)
end
Loading
Loading
Loading
Loading
@@ -14,7 +14,7 @@ describe('new dropdown upload', () => {
 
vm.entryName = 'testing';
 
spyOn(vm, '$emit');
spyOn(vm, '$emit').and.callThrough();
});
 
afterEach(() => {
Loading
Loading
@@ -61,31 +61,44 @@ describe('new dropdown upload', () => {
const binaryTarget = {
result: 'base64,w4I=',
};
const textFile = {
name: 'textFile',
type: 'text/plain',
};
const textFile = new File(['plain text'], 'textFile');
const binaryFile = {
name: 'binaryFile',
type: 'image/png',
};
 
it('creates file in plain text (without encoding) if the file content is plain text', () => {
beforeEach(() => {
spyOn(FileReader.prototype, 'readAsText').and.callThrough();
});
it('calls readAsText and creates file in plain text (without encoding) if the file content is plain text', done => {
const waitForCreate = new Promise(resolve => vm.$on('create', resolve));
vm.createFile(textTarget, textFile);
 
expect(vm.$emit).toHaveBeenCalledWith('create', {
name: textFile.name,
type: 'blob',
content: 'plain text',
base64: false,
binary: false,
rawPath: '',
});
expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(textFile);
waitForCreate
.then(() => {
expect(vm.$emit).toHaveBeenCalledWith('create', {
name: textFile.name,
type: 'blob',
content: 'plain text',
base64: false,
binary: false,
rawPath: '',
});
})
.then(done)
.catch(done.fail);
});
 
it('splits content on base64 if binary', () => {
vm.createFile(binaryTarget, binaryFile);
 
expect(FileReader.prototype.readAsText).not.toHaveBeenCalledWith(textFile);
expect(vm.$emit).toHaveBeenCalledWith('create', {
name: binaryFile.name,
type: 'blob',
Loading
Loading
Loading
Loading
@@ -58,14 +58,34 @@ describe('ContentViewer', () => {
 
it('renders fallback download control', done => {
createComponent({
path: 'test.abc',
path: 'somepath/test.abc',
fileSize: 1024,
});
 
setTimeout(() => {
expect(vm.$el.querySelector('.file-info').textContent.trim()).toContain('test.abc');
expect(vm.$el.querySelector('.file-info').textContent.trim()).toContain('(1.00 KiB)');
expect(vm.$el.querySelector('.btn.btn-default').textContent.trim()).toContain('Download');
expect(
vm.$el
.querySelector('.file-info')
.textContent.trim()
.replace(/\s+/, ' '),
).toEqual('test.abc (1.00 KiB)');
expect(vm.$el.querySelector('.btn.btn-default').textContent.trim()).toEqual('Download');
done();
});
});
it('renders fallback download control for file with a data URL path properly', done => {
createComponent({
path: 'data:application/octet-stream;base64,U0VMRUNUICfEhHNnc2cnIGZyb20gVGFibGVuYW1lOwoK',
filePath: 'somepath/test.abc',
});
setTimeout(() => {
expect(vm.$el.querySelector('.file-info').textContent.trim()).toEqual('test.abc');
expect(vm.$el.querySelector('.btn.btn-default')).toHaveAttr('download', 'test.abc');
expect(vm.$el.querySelector('.btn.btn-default').textContent.trim()).toEqual('Download');
 
done();
});
Loading
Loading
Loading
Loading
@@ -50,6 +50,18 @@ describe Gitlab::RepositoryCache do
end
end
 
describe '#write' do
it 'writes the given key and value to the cache' do
cache.write(:test, 'test')
expect(backend).to have_received(:write).with("test:#{namespace}", 'test')
end
it 'passes additional options to the backend' do
cache.write(:test, 'test', expires_in: 10.minutes)
expect(backend).to have_received(:write).with("test:#{namespace}", 'test', expires_in: 10.minutes)
end
end
describe '#fetch_without_caching_false', :use_clean_rails_memory_store_caching do
let(:key) { :foo }
let(:backend) { Rails.cache }
Loading
Loading
Loading
Loading
@@ -494,6 +494,100 @@ describe Repository do
it { is_expected.to eq(commit.sha) }
end
 
describe "#merged_branch_names", :clean_gitlab_redis_cache do
subject { repository.merged_branch_names(branch_names) }
let(:branch_names) { %w(test beep boop definitely_merged) }
let(:already_merged) { Set.new(["definitely_merged"]) }
let(:merge_state_hash) do
{
"test" => false,
"beep" => false,
"boop" => false,
"definitely_merged" => true
}
end
let_it_be(:cache) do
caching_config_hash = Gitlab::Redis::Cache.params
ActiveSupport::Cache.lookup_store(:redis_cache_store, caching_config_hash)
end
let(:repository_cache) do
Gitlab::RepositoryCache.new(repository, backend: Rails.cache)
end
let(:cache_key) { repository_cache.cache_key(:merged_branch_names) }
before do
allow(Rails).to receive(:cache) { cache }
allow(repository).to receive(:cache) { repository_cache }
allow(repository.raw_repository).to receive(:merged_branch_names).with(branch_names).and_return(already_merged)
end
it { is_expected.to eq(already_merged) }
it { is_expected.to be_a(Set) }
context "cache is empty" do
before do
cache.delete(cache_key)
end
it { is_expected.to eq(already_merged) }
describe "cache values" do
it "writes the values to redis" do
expect(cache).to receive(:write).with(cache_key, merge_state_hash, expires_in: Repository::MERGED_BRANCH_NAMES_CACHE_DURATION)
subject
end
it "matches the supplied hash" do
subject
expect(cache.read(cache_key)).to eq(merge_state_hash)
end
end
end
context "cache is not empty" do
before do
cache.write(cache_key, merge_state_hash)
end
it { is_expected.to eq(already_merged) }
it "doesn't fetch from the disk" do
expect(repository.raw_repository).not_to receive(:merged_branch_names)
subject
end
end
context "cache is partially complete" do
before do
allow(repository.raw_repository).to receive(:merged_branch_names).with(["boop"]).and_return([])
hash = merge_state_hash.except("boop")
cache.write(cache_key, hash)
end
it { is_expected.to eq(already_merged) }
it "does fetch from the disk" do
expect(repository.raw_repository).to receive(:merged_branch_names).with(["boop"])
subject
end
end
context "requested branches array is empty" do
let(:branch_names) { [] }
it { is_expected.to eq(already_merged) }
end
end
describe '#can_be_merged?' do
context 'mergeable branches' do
subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
Loading
Loading
@@ -1784,6 +1878,7 @@ describe Repository do
:avatar,
:exists?,
:root_ref,
:merged_branch_names,
:has_visible_content?,
:issue_template_names,
:merge_request_template_names,
Loading
Loading
@@ -1959,7 +2054,7 @@ describe Repository do
describe '#expire_branches_cache' do
it 'expires the cache' do
expect(repository).to receive(:expire_method_caches)
.with(%i(branch_names branch_count has_visible_content?))
.with(%i(branch_names merged_branch_names branch_count has_visible_content?))
.and_call_original
 
repository.expire_branches_cache
Loading
Loading
Loading
Loading
@@ -608,7 +608,7 @@ describe API::Branches do
expect(json_response['message']).to eq('Branch name is invalid')
end
 
it 'returns 400 if branch already exists' do
it 'returns 400 if branch already exists', :clean_gitlab_redis_cache do
post api(route, user), params: { branch: 'new_design1', ref: branch_sha }
 
expect(response).to have_gitlab_http_status(201)
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment