Commit d23b2a08 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 1ef4b65f
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
*.rake @gitlab-org/maintainers/rails-backend *.rake @gitlab-org/maintainers/rails-backend
# Technical writing team are the default reviewers for everything in `doc/` # Technical writing team are the default reviewers for everything in `doc/`
/doc/ @axil @marcia @eread @mikelewis /doc/ @gl-docsteam
# Frontend maintainers should see everything in `app/assets/` # Frontend maintainers should see everything in `app/assets/`
app/assets/ @gitlab-org/maintainers/frontend app/assets/ @gitlab-org/maintainers/frontend
......
...@@ -141,11 +141,7 @@ module BlobHelper ...@@ -141,11 +141,7 @@ module BlobHelper
if @build && @entry if @build && @entry
raw_project_job_artifacts_url(@project, @build, path: @entry.path, **kwargs) raw_project_job_artifacts_url(@project, @build, path: @entry.path, **kwargs)
elsif @snippet elsif @snippet
if @snippet.project_id reliable_raw_snippet_url(@snippet)
raw_project_snippet_url(@project, @snippet, **kwargs)
else
raw_snippet_url(@snippet, **kwargs)
end
elsif @blob elsif @blob
project_raw_url(@project, @id, **kwargs) project_raw_url(@project, @id, **kwargs)
end end
......
...@@ -11,22 +11,40 @@ module SnippetsHelper ...@@ -11,22 +11,40 @@ module SnippetsHelper
end end
end end
def reliable_snippet_path(snippet, opts = nil) def reliable_snippet_path(snippet, opts = {})
reliable_snippet_url(snippet, opts.merge(only_path: true))
end
def reliable_raw_snippet_path(snippet, opts = {})
reliable_raw_snippet_url(snippet, opts.merge(only_path: true))
end
def reliable_snippet_url(snippet, opts = {})
if snippet.project_id? if snippet.project_id?
project_snippet_path(snippet.project, snippet, opts) project_snippet_url(snippet.project, snippet, nil, opts)
else else
snippet_path(snippet, opts) snippet_url(snippet, nil, opts)
end end
end end
def download_snippet_path(snippet) def reliable_raw_snippet_url(snippet, opts = {})
if snippet.project_id if snippet.project_id?
raw_project_snippet_path(@project, snippet, inline: false) raw_project_snippet_url(snippet.project, snippet, nil, opts)
else else
raw_snippet_path(snippet, inline: false) raw_snippet_url(snippet, nil, opts)
end end
end end
def download_raw_snippet_button(snippet)
link_to(icon('download'),
reliable_raw_snippet_path(snippet, inline: false),
target: '_blank',
rel: 'noopener noreferrer',
class: "btn btn-sm has-tooltip",
title: 'Download',
data: { container: 'body' })
end
# Return the path of a snippets index for a user or for a project # Return the path of a snippets index for a user or for a project
# #
# @returns String, path to snippet index # @returns String, path to snippet index
...@@ -114,30 +132,45 @@ module SnippetsHelper ...@@ -114,30 +132,45 @@ module SnippetsHelper
{ snippet_object: snippet, snippet_chunks: snippet_chunks } { snippet_object: snippet, snippet_chunks: snippet_chunks }
end end
def snippet_embed def snippet_embed_tag(snippet)
"<script src=\"#{url_for(only_path: false, overwrite_params: nil)}.js\"></script>" content_tag(:script, nil, src: reliable_snippet_url(snippet, format: :js, only_path: false))
end end
def embedded_snippet_raw_button def snippet_badge(snippet)
blob = @snippet.blob return unless attrs = snippet_badge_attributes(snippet)
return if blob.empty? || blob.binary? || blob.stored_externally?
snippet_raw_url = if @snippet.is_a?(PersonalSnippet) css_class, text = attrs
raw_snippet_url(@snippet) tag.span(class: ['badge', 'badge-gray']) do
else concat(tag.i(class: ['fa', css_class]))
raw_project_snippet_url(@snippet.project, @snippet) concat(' ')
concat(text)
end
end end
link_to external_snippet_icon('doc-code'), snippet_raw_url, class: 'btn', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw' def snippet_badge_attributes(snippet)
if snippet.private?
['fa-lock', _('private')]
end
end end
def embedded_snippet_download_button def embedded_raw_snippet_button
download_url = if @snippet.is_a?(PersonalSnippet) blob = @snippet.blob
raw_snippet_url(@snippet, inline: false) return if blob.empty? || blob.binary? || blob.stored_externally?
else
raw_project_snippet_url(@snippet.project, @snippet, inline: false) link_to(external_snippet_icon('doc-code'),
reliable_raw_snippet_url(@snippet),
class: 'btn',
target: '_blank',
rel: 'noopener noreferrer',
title: 'Open raw')
end end
link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', rel: 'noopener noreferrer' def embedded_snippet_download_button
link_to(external_snippet_icon('download'),
reliable_raw_snippet_url(@snippet, inline: false),
class: 'btn',
target: '_blank',
title: 'Download',
rel: 'noopener noreferrer')
end end
end end
...@@ -263,8 +263,8 @@ class Group < Namespace ...@@ -263,8 +263,8 @@ class Group < Namespace
members_with_parents.maintainers.exists?(user_id: user) members_with_parents.maintainers.exists?(user_id: user)
end end
def has_container_repositories? def has_container_repository_including_subgroups?
container_repositories.exists? ::ContainerRepository.for_group_and_its_subgroups(self).exists?
end end
# @deprecated # @deprecated
......
...@@ -75,7 +75,7 @@ module Groups ...@@ -75,7 +75,7 @@ module Groups
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def group_projects_contain_registry_images? def group_projects_contain_registry_images?
@group.has_container_repositories? @group.has_container_repository_including_subgroups?
end end
def update_group_attributes def update_group_attributes
......
...@@ -43,8 +43,9 @@ module Groups ...@@ -43,8 +43,9 @@ module Groups
def renaming_group_with_container_registry_images? def renaming_group_with_container_registry_images?
new_path = params[:path] new_path = params[:path]
new_path && new_path != group.path && new_path &&
group.has_container_repositories? new_path != group.path &&
group.has_container_repository_including_subgroups?
end end
def container_images_error def container_images_error
......
- snippet_blob = chunk_snippet(snippet_blob, @search_term) - snippet_blob = chunk_snippet(snippet_blob, @search_term)
- snippet = snippet_blob[:snippet_object] - snippet = snippet_blob[:snippet_object]
- snippet_chunks = snippet_blob[:snippet_chunks] - snippet_chunks = snippet_blob[:snippet_chunks]
- snippet_path = reliable_snippet_path(snippet)
.search-result-row .search-result-row
%span %span
...@@ -11,7 +12,6 @@ ...@@ -11,7 +12,6 @@
= snippet.author_name = snippet.author_name
%span.light= time_ago_with_tooltip(snippet.created_at) %span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title %h4.snippet-title
- snippet_path = reliable_snippet_path(snippet)
.file-holder .file-holder
.js-file-title.file-title .js-file-title.file-title
= link_to snippet_path do = link_to snippet_path do
......
...@@ -2,10 +2,7 @@ ...@@ -2,10 +2,7 @@
%h4.snippet-title.term %h4.snippet-title.term
= link_to reliable_snippet_path(snippet_title) do = link_to reliable_snippet_path(snippet_title) do
= truncate(snippet_title.title, length: 60) = truncate(snippet_title.title, length: 60)
- if snippet_title.private? = snippet_badge(snippet_title)
%span.badge.badge-gray
%i.fa.fa-lock
= _("private")
%span.cgray.monospace.tiny.float-right.term %span.cgray.monospace.tiny.float-right.term
= snippet_title.file_name = snippet_title.file_name
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
= copy_blob_source_button(blob) = copy_blob_source_button(blob)
= open_raw_blob_button(blob) = open_raw_blob_button(blob)
= download_raw_snippet_button(@snippet)
= link_to icon('download'), download_snippet_path(@snippet), target: '_blank', class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
= render 'projects/blob/content', blob: blob = render 'projects/blob/content', blob: blob
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
.file-actions.d-none.d-sm-block .file-actions.d-none.d-sm-block
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
= embedded_snippet_raw_button = embedded_raw_snippet_button
= embedded_snippet_download_button = embedded_snippet_download_button
%article.file-holder.snippet-file-content %article.file-holder.snippet-file-content
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
%li %li
%button.js-share-btn.btn.btn-transparent{ type: 'button' } %button.js-share-btn.btn.btn-transparent{ type: 'button' }
%strong.embed-toggle-list-item= _("Share") %strong.embed-toggle-list-item= _("Share")
%input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed } %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed_tag(@snippet) }
.input-group-append .input-group-append
= clipboard_button(title: _('Copy'), class: 'js-clipboard-btn snippet-clipboard-btn btn btn-default', target: '.js-snippet-url-area') = clipboard_button(title: _('Copy'), class: 'js-clipboard-btn snippet-clipboard-btn btn btn-default', target: '.js-snippet-url-area')
.clearfix .clearfix
...@@ -8,7 +8,7 @@ describe ContainerRepositoriesFinder do ...@@ -8,7 +8,7 @@ describe ContainerRepositoriesFinder do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:project_repository) { create(:container_repository, project: project) } let!(:project_repository) { create(:container_repository, project: project) }
before do before do
group.add_reporter(reporter) group.add_reporter(reporter)
......
...@@ -3,33 +3,217 @@ ...@@ -3,33 +3,217 @@
require 'spec_helper' require 'spec_helper'
describe SnippetsHelper do describe SnippetsHelper do
include Gitlab::Routing
include IconsHelper include IconsHelper
describe '#embedded_snippet_raw_button' do let_it_be(:public_personal_snippet) { create(:personal_snippet, :public) }
it 'gives view raw button of embedded snippets for project snippets' do let_it_be(:public_project_snippet) { create(:project_snippet, :public) }
@snippet = create(:project_snippet, :public)
describe '#reliable_snippet_path' do
subject { reliable_snippet_path(snippet) }
context 'personal snippets' do
let(:snippet) { public_personal_snippet }
context 'public' do
it 'returns a full path' do
expect(subject).to eq("/snippets/#{snippet.id}")
end
end
end
context 'project snippets' do
let(:snippet) { public_project_snippet }
it 'returns a full path' do
expect(subject).to eq("/#{snippet.project.full_path}/snippets/#{snippet.id}")
end
end
end
describe '#reliable_snippet_url' do
subject { reliable_snippet_url(snippet) }
context 'personal snippets' do
let(:snippet) { public_personal_snippet }
context 'public' do
it 'returns a full url' do
expect(subject).to eq("http://test.host/snippets/#{snippet.id}")
end
end
end
context 'project snippets' do
let(:snippet) { public_project_snippet }
it 'returns a full url' do
expect(subject).to eq("http://test.host/#{snippet.project.full_path}/snippets/#{snippet.id}")
end
end
end
describe '#reliable_raw_snippet_path' do
subject { reliable_raw_snippet_path(snippet) }
expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet)}\">#{external_snippet_icon('doc-code')}</a>") context 'personal snippets' do
let(:snippet) { public_personal_snippet }
context 'public' do
it 'returns a full path' do
expect(subject).to eq("/snippets/#{snippet.id}/raw")
end
end
end
context 'project snippets' do
let(:snippet) { public_project_snippet }
it 'returns a full path' do
expect(subject).to eq("/#{snippet.project.full_path}/snippets/#{snippet.id}/raw")
end
end
end
describe '#reliable_raw_snippet_url' do
subject { reliable_raw_snippet_url(snippet) }
context 'personal snippets' do
let(:snippet) { public_personal_snippet }
context 'public' do
it 'returns a full url' do
expect(subject).to eq("http://test.host/snippets/#{snippet.id}/raw")
end end
end
end
context 'project snippets' do
let(:snippet) { public_project_snippet }
it 'returns a full url' do
expect(subject).to eq("http://test.host/#{snippet.project.full_path}/snippets/#{snippet.id}/raw")
end
end
end
describe '#embedded_raw_snippet_button' do
subject { embedded_raw_snippet_button.to_s }
it 'gives view raw button of embedded snippets for personal snippets' do it 'returns view raw button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public) @snippet = create(:personal_snippet, :public)
expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_snippet_url(@snippet)}\">#{external_snippet_icon('doc-code')}</a>") expect(subject).to eq(download_link("http://test.host/snippets/#{@snippet.id}/raw"))
end
it 'returns view raw button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
expect(subject).to eq(download_link("http://test.host/#{@snippet.project.path_with_namespace}/snippets/#{@snippet.id}/raw"))
end
def download_link(url)
"<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{url}\">#{external_snippet_icon('doc-code')}</a>"
end end
end end
describe '#embedded_snippet_download_button' do describe '#embedded_snippet_download_button' do
it 'gives download button of embedded snippets for project snippets' do subject { embedded_snippet_download_button }
it 'returns download button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public)
expect(subject).to eq(download_link("http://test.host/snippets/#{@snippet.id}/raw"))
end
it 'returns download button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public) @snippet = create(:project_snippet, :public)
expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet, inline: false)}\">#{external_snippet_icon('download')}</a>") expect(subject).to eq(download_link("http://test.host/#{@snippet.project.path_with_namespace}/snippets/#{@snippet.id}/raw"))
end end
it 'gives download button of embedded snippets for personal snippets' do def download_link(url)
@snippet = create(:personal_snippet, :public) "<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{url}?inline=false\">#{external_snippet_icon('download')}</a>"
end
end
describe '#snippet_embed_tag' do
subject { snippet_embed_tag(snippet) }
context 'personal snippets' do
let(:snippet) { public_personal_snippet }
context 'public' do
it 'returns a script tag with the snippet full url' do
expect(subject).to eq(script_embed("http://test.host/snippets/#{snippet.id}"))
end
end
end
context 'project snippets' do
let(:snippet) { public_project_snippet }
it 'returns a script tag with the snippet full url' do
expect(subject).to eq(script_embed("http://test.host/#{snippet.project.path_with_namespace}/snippets/#{snippet.id}"))
end
end
def script_embed(url)
"<script src=\"#{url}.js\"></script>"
end
end
describe '#download_raw_snippet_button' do
subject { download_raw_snippet_button(snippet) }
expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_snippet_url(@snippet, inline: false)}\">#{external_snippet_icon('download')}</a>") context 'with personal snippet' do
let(:snippet) { public_personal_snippet }
it 'returns the download button' do
expect(subject).to eq(download_link("/snippets/#{snippet.id}/raw"))
end
end
context 'with project snippet' do
let(:snippet) { public_project_snippet }
it 'returns the download button' do
expect(subject).to eq(download_link("/#{snippet.project.path_with_namespace}/snippets/#{snippet.id}/raw"))
end
end
def download_link(url)
"<a target=\"_blank\" rel=\"noopener noreferrer\" class=\"btn btn-sm has-tooltip\" title=\"Download\" data-container=\"body\" href=\"#{url}?inline=false\"><i aria-hidden=\"true\" data-hidden=\"true\" class=\"fa fa-download\"></i></a>"
end
end
describe '#snippet_badge' do
let(:snippet) { build(:personal_snippet, visibility) }
subject { snippet_badge(snippet) }
context 'when snippet is private' do
let(:visibility) { :private }
it 'returns the snippet badge' do
expect(subject).to eq "<span class=\"badge badge-gray\"><i class=\"fa fa-lock\"></i> private</span>"
end
end
context 'when snippet is public' do
let(:visibility) { :public }
it 'does not return anything' do
expect(subject).to be_nil
end
end
context 'when snippet is internal' do
let(:visibility) { :internal }
it 'does not return anything' do
expect(subject).to be_nil
end
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe PersonalSnippet do
describe '#embeddable?' do
[
{ snippet: :public, embeddable: true },
{ snippet: :internal, embeddable: false },
{ snippet: :private, embeddable: false }
].each do |combination|
it 'returns true when snippet is public' do
snippet = build(:personal_snippet, combination[:snippet])
expect(snippet.embeddable?).to eq(combination[:embeddable])
end
end
end
end
...@@ -10,4 +10,25 @@ describe ProjectSnippet do ...@@ -10,4 +10,25 @@ describe ProjectSnippet do
describe "Validation" do describe "Validation" do
it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:project) }
end end
describe '#embeddable?' do
[
{ project: :public, snippet: :public, embeddable: true },
{ project: :internal, snippet: :public, embeddable: false },
{ project: :private, snippet: :public, embeddable: false },
{ project: :public, snippet: :internal, embeddable: false },
{ project: :internal, snippet: :internal, embeddable: false },
{ project: :private, snippet: :internal, embeddable: false },
{ project: :public, snippet: :private, embeddable: false },
{ project: :internal, snippet: :private, embeddable: false },
{ project: :private, snippet: :private, embeddable: false }
].each do |combination|
it 'only returns true when both project and snippet are public' do
project = create(:project, combination[:project])
snippet = build(:project_snippet, combination[:snippet], project: project)
expect(snippet.embeddable?).to eq(combination[:embeddable])
end
end
end
end end
...@@ -451,41 +451,4 @@ describe Snippet do ...@@ -451,41 +451,4 @@ describe Snippet do
expect(blob.data).to eq(snippet.content) expect(blob.data).to eq(snippet.content)
end end
end end
describe '#embeddable?' do
context 'project snippet' do
[
{ project: :public, snippet: :public, embeddable: true },
{ project: :internal, snippet: :public, embeddable: false },
{ project: :private, snippet: :public, embeddable: false },
{ project: :public, snippet: :internal, embeddable: false },
{ project: :internal, snippet: :internal, embeddable: false },
{ project: :private, snippet: :internal, embeddable: false },
{ project: :public, snippet: :private, embeddable: false },
{ project: :internal, snippet: :private, embeddable: false },
{ project: :private, snippet: :private, embeddable: false }
].each do |combination|
it 'only returns true when both project and snippet are public' do
project = create(:project, combination[:project])
snippet = create(:project_snippet, combination[:snippet], project: project)
expect(snippet.embeddable?).to eq(combination[:embeddable])
end
end
end
context 'personal snippet' do
[
{ snippet: :public, embeddable: true },
{ snippet: :internal, embeddable: false },
{ snippet: :private, embeddable: false }
].each do |combination|
it 'only returns true when snippet is public' do
snippet = create(:personal_snippet, combination[:snippet])
expect(snippet.embeddable?).to eq(combination[:embeddable])
end
end
end
end
end end
...@@ -427,21 +427,35 @@ describe Groups::TransferService do ...@@ -427,21 +427,35 @@ describe Groups::TransferService do
end end
end end
context 'when a project in group has container images' do context 'when a project has container images' do
let(:group) { create(:group, :public, :nested) } let(:group) { create(:group, :public, :nested) }
let!(:project) { create(:project, :repository, :public, namespace: group) } let!(:container_repository) { create(:container_repository, project: project) }
subject { transfer_service.execute(new_parent_group) }
before do before do
stub_container_registry_tags(repository: /image/, tags: %w[rc1]) group.add_owner(user)
create(:container_repository, project: project, name: :image) new_parent_group.add_owner(user)
create(:group_member, :owner, group: new_parent_group, user: user)
end end
it 'does not allow group to be transferred' do context 'within group' do
transfer_service.execute(new_parent_group) let(:project) { create(:project, :repository, :public, namespace: group) }
it 'does not transfer' do
expect(subject).to be false
expect(transfer_service.error).to match(/Docker images in their Container Registry/) expect(transfer_service.error).to match(/Docker images in their Container Registry/)
end end
end end
context 'within subgroup' do
let(:subgroup) { create(:group, parent: group) }
let(:project) { create(:project, :repository, :public, namespace: subgroup) }
it 'does not transfer' do
expect(subject).to be false
expect(transfer_service.error).to match(/Docker images in their Container Registry/)
end
end
end
end end
end end
...@@ -32,6 +32,43 @@ describe Groups::UpdateService do ...@@ -32,6 +32,43 @@ describe Groups::UpdateService do
expect(service.execute).to be_falsey expect(service.execute).to be_falsey
end end
context 'when a project has container images' do
let(:params) { { path: SecureRandom.hex } }
let!(:container_repository) { create(:container_repository, project: project) }
subject { described_class.new(public_group, user, params).execute }
context 'within group' do
let(:project) { create(:project, group: public_group) }
context 'with path updates' do
it 'does not allow the update' do
expect(subject).to be false
expect(public_group.errors[:base].first).to match(/Docker images in their Container Registry/)
end
end
context 'with name updates' do
let(:params) { { name: 'new-name' } }
it 'allows the update' do
expect(subject).to be true
expect(public_group.reload.name).to eq('new-name')
end
end
end
context 'within subgroup' do
let(:subgroup) { create(:group, parent: public_group) }
let(:project) { create(:project, group: subgroup) }
it 'does not allow path updates' do
expect(subject).to be false
expect(public_group.errors[:base].first).to match(/Docker images in their Container Registry/)
end
end
end
end end
context "internal group with internal project" do context "internal group with internal project" do
...@@ -148,30 +185,6 @@ describe Groups::UpdateService do ...@@ -148,30 +185,6 @@ describe Groups::UpdateService do
end end
end end
context 'projects in group have container images' do
let(:service) { described_class.new(public_group, user, path: SecureRandom.hex) }
let(:project) { create(:project, :internal, group: public_group) }
before do
stub_container_registry_tags(repository: /image/, tags: %w[rc1])
create(:container_repository, project: project, name: :image)
end
it 'does not allow path to be changed' do
result = described_class.new(public_group, user, path: 'new-path').execute
expect(result).to eq false
expect(public_group.errors[:base].first).to match(/Docker images in their Container Registry/)
end
it 'allows other settings to be changed' do
result = described_class.new(public_group, user, name: 'new-name').execute
expect(result).to eq true
expect(public_group.reload.name).to eq('new-name')
end
end
context 'for a subgroup' do context 'for a subgroup' do
let(:subgroup) { create(:group, :private, parent: private_group) } let(:subgroup) { create(:group, :private, parent: private_group) }
......
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