Commit 1a8053a4 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch '11367-expose-design-blobs-through-controller' into 'master'

Expose design images to front-end

Closes #11367

See merge request gitlab-org/gitlab-ee!13037
parents 4a6d48e1 7d018487
...@@ -15,13 +15,9 @@ const defaultClient = createDefaultClient({ ...@@ -15,13 +15,9 @@ const defaultClient = createDefaultClient({
variables: { fullPath: projectPath, iid: issueIid }, variables: { fullPath: projectPath, iid: issueIid },
}); });
return { return result.project.issue.designs.designs.edges.find(
...result.project.issue.designs.designs.edges.find(
({ node }) => parseInt(node.id, 10) === id, ({ node }) => parseInt(node.id, 10) === id,
).node, ).node;
// TODO: Remove this once backend exposes raw images
image: 'http://via.placeholder.com/1000',
};
}, },
}, },
}); });
......
...@@ -19,12 +19,7 @@ export default { ...@@ -19,12 +19,7 @@ export default {
iid: this.issueIid, iid: this.issueIid,
}; };
}, },
update: data => update: data => data.project.issue.designs.designs.edges.map(({ node }) => node),
data.project.issue.designs.designs.edges.map(({ node }) => ({
...node,
// TODO: Remove this once backend exposes raw images
image: 'http://via.placeholder.com/1000',
})),
error() { error() {
this.error = true; this.error = true;
}, },
......
fragment DesignListItem on Design { fragment DesignListItem on Design {
id id
image
filename filename
} }
# frozen_string_literal: true
class Projects::DesignsController < Projects::ApplicationController
include SendsBlob
before_action :authorize_read_design!
def show
blob = design_repository.blob_at(ref, design.full_path)
send_blob(design_repository, blob, inline: (params[:inline] != 'false'))
end
private
def ref
@ref ||= params[:ref] || design_repository.root_ref
end
def design
@design ||= project.designs.find(params[:id])
end
def design_repository
@design_repository ||= @project.design_repository
end
def authorize_read_design!
unless can?(current_user, :read_design, design)
access_denied!
end
end
end
...@@ -11,6 +11,9 @@ module Types ...@@ -11,6 +11,9 @@ module Types
field :project, Types::ProjectType, null: false field :project, Types::ProjectType, null: false
field :issue, Types::IssueType, null: false field :issue, Types::IssueType, null: false
field :filename, GraphQL::STRING_TYPE, null: false field :filename, GraphQL::STRING_TYPE, null: false
field :image, GraphQL::STRING_TYPE, null: false, resolve: -> (design, _args, _ctx) do
Gitlab::Routing.url_helpers.project_design_url(design.project, design)
end
field :versions, field :versions,
Types::DesignManagement::VersionType.connection_type, Types::DesignManagement::VersionType.connection_type,
resolver: Resolvers::DesignManagement::VersionResolver, resolver: Resolvers::DesignManagement::VersionResolver,
......
...@@ -4,7 +4,7 @@ module DesignManagement ...@@ -4,7 +4,7 @@ module DesignManagement
class Design < ApplicationRecord class Design < ApplicationRecord
include Gitlab::FileTypeDetection include Gitlab::FileTypeDetection
belongs_to :project belongs_to :project, inverse_of: :designs
belongs_to :issue belongs_to :issue
has_many :design_versions has_many :design_versions
......
...@@ -20,7 +20,7 @@ module DesignManagement ...@@ -20,7 +20,7 @@ module DesignManagement
end end
def repository def repository
@repository ||= ::DesignManagement::Repository.new(project) project.design_repository
end end
end end
end end
...@@ -51,6 +51,7 @@ module EE ...@@ -51,6 +51,7 @@ module EE
has_many :approver_groups, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :approver_groups, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :approval_rules, class_name: 'ApprovalProjectRule' has_many :approval_rules, class_name: 'ApprovalProjectRule'
has_many :audit_events, as: :entity has_many :audit_events, as: :entity
has_many :designs, inverse_of: :project, class_name: 'DesignManagement::Design'
has_many :path_locks has_many :path_locks
has_many :vulnerability_feedback, class_name: 'Vulnerabilities::Feedback' has_many :vulnerability_feedback, class_name: 'Vulnerabilities::Feedback'
has_many :vulnerabilities, class_name: 'Vulnerabilities::Occurrence' has_many :vulnerabilities, class_name: 'Vulnerabilities::Occurrence'
...@@ -582,6 +583,10 @@ module EE ...@@ -582,6 +583,10 @@ module EE
::Gitlab::Graphql.enabled? ::Gitlab::Graphql.enabled?
end end
def design_repository
@design_repository ||= DesignManagement::Repository.new(self)
end
private private
def set_override_pull_mirror_available def set_override_pull_mirror_available
......
---
title: Expose Design blobs through GraphQL
merge_request: 13037
author:
type: added
...@@ -71,6 +71,14 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -71,6 +71,14 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
end end
end end
scope '-' do
resources :designs, only: [], constraints: { id: /\d+/ } do
member do
get '(*ref)', action: 'show', as: '', constraints: { ref: Gitlab::PathRegex.git_reference_regex }
end
end
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::DesignsController do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:design) { create(:design, :with_file, issue: issue) }
before do
stub_licensed_features(design_management: true)
end
describe 'GET #show' do
it 'serves the file using workhorse' do
get(:show,
params: {
namespace_id: project.namespace,
project_id: project,
id: design.id,
ref: 'HEAD'
})
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Disposition']).to eq('inline')
expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end
...@@ -5,5 +5,37 @@ FactoryBot.define do ...@@ -5,5 +5,37 @@ FactoryBot.define do
issue issue
project { issue.project } project { issue.project }
sequence(:filename) { |n| "homescreen-#{n}.jpg" } sequence(:filename) { |n| "homescreen-#{n}.jpg" }
trait :with_file do
transient do
versions_count 1
file File.join(Rails.root, 'spec/fixtures/dk.png')
end
after :create do |design, evaluator|
unless evaluator.versions_count.zero?
project = design.project
repository = project.design_repository
repository.create_if_not_exists
evaluator.versions_count.times do |i|
actions = [{
action: i.zero? ? :create : :update, # First version is :create, successive versions are :update
file_path: design.full_path,
content: evaluator.file
}]
sha = repository.multi_action(
project.creator,
branch_name: 'master',
message: "Automatically created file #{design.filename}",
actions: actions
)
FactoryBot.create(:design_version, designs: [design], sha: sha)
end
end
end
end
end end
end end
...@@ -5,8 +5,8 @@ describe 'User paginates issue designs', :js do ...@@ -5,8 +5,8 @@ describe 'User paginates issue designs', :js do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
before do before do
create(:design, issue: issue, filename: 'world.png') create(:design, :with_file, issue: issue, filename: 'world.png')
create(:design, issue: issue, filename: 'dk.png') create(:design, :with_file, issue: issue, filename: 'dk.png')
stub_licensed_features(design_management: true) stub_licensed_features(design_management: true)
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Users views raw design image files' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:design) { create(:design, :with_file, issue: issue, versions_count: 2) }
let(:newest_version) { design.versions.ordered.first }
let(:oldest_version) { design.versions.ordered.last }
before do
stub_licensed_features(design_management: true)
end
it 'serves the latest design version when no ref is given' do
visit project_design_path(design.project, design)
expect(response_headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to eq(
workhorse_data_header_for_version(oldest_version.sha)
)
end
it 'serves the correct design version when a ref is given' do
visit project_design_path(design.project, design, oldest_version.sha)
expect(response_headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to eq(
workhorse_data_header_for_version(oldest_version.sha)
)
end
private
def workhorse_data_header_for_version(ref)
blob = project.design_repository.blob_at(ref, design.full_path)
Gitlab::Workhorse.send_git_blob(project.design_repository, blob).last
end
end
...@@ -5,7 +5,7 @@ describe 'User views issue designs', :js do ...@@ -5,7 +5,7 @@ describe 'User views issue designs', :js do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
before do before do
create(:design, issue: issue, filename: 'world.png') create(:design, :with_file, issue: issue, filename: 'world.png')
stub_licensed_features(design_management: true) stub_licensed_features(design_management: true)
......
...@@ -5,7 +5,7 @@ describe 'User views issue designs', :js do ...@@ -5,7 +5,7 @@ describe 'User views issue designs', :js do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
before do before do
create(:design, issue: issue, filename: 'world.png') create(:design, :with_file, issue: issue, filename: 'world.png')
stub_licensed_features(design_management: true) stub_licensed_features(design_management: true)
......
...@@ -35,7 +35,7 @@ describe Resolvers::DesignManagement::VersionResolver do ...@@ -35,7 +35,7 @@ describe Resolvers::DesignManagement::VersionResolver do
end end
end end
context "when the is anonymous" do context "when the user is anonymous" do
let(:current_user) { nil } let(:current_user) { nil }
it "returns nothing" do it "returns nothing" do
...@@ -43,7 +43,7 @@ describe Resolvers::DesignManagement::VersionResolver do ...@@ -43,7 +43,7 @@ describe Resolvers::DesignManagement::VersionResolver do
end end
end end
context "when the is cannot see designs" do context "when the user cannot see designs" do
it "returns nothing" do it "returns nothing" do
expect(resolve_versions(first_design, {}, current_user: create(:user))).to be_empty expect(resolve_versions(first_design, {}, current_user: create(:user))).to be_empty
end end
......
...@@ -5,5 +5,5 @@ require 'spec_helper' ...@@ -5,5 +5,5 @@ require 'spec_helper'
describe GitlabSchema.types['Design'] do describe GitlabSchema.types['Design'] do
it { expect(described_class).to require_graphql_authorizations(:read_design) } it { expect(described_class).to require_graphql_authorizations(:read_design) }
it { expect(described_class).to have_graphql_fields(:id, :project, :issue, :filename, :versions) } it { expect(described_class).to have_graphql_fields(:id, :project, :issue, :filename, :image, :versions) }
end end
...@@ -88,6 +88,7 @@ project: ...@@ -88,6 +88,7 @@ project:
- reviews - reviews
- incident_management_setting - incident_management_setting
- merge_trains - merge_trains
- designs
prometheus_metrics: prometheus_metrics:
- project - project
- prometheus_alerts - prometheus_alerts
......
...@@ -28,4 +28,12 @@ describe 'EE-specific project routing' do ...@@ -28,4 +28,12 @@ describe 'EE-specific project routing' do
expect(get('/gitlab/gitlabhq/pipelines/12/security')).to route_to('projects/pipelines#security', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '12') expect(get('/gitlab/gitlabhq/pipelines/12/security')).to route_to('projects/pipelines#security', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '12')
end end
end end
describe Projects::DesignsController, 'routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq/-/designs/1/master')).to route_to('projects/designs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', ref: 'master')
expect(get('/gitlab/gitlabhq/-/designs/1/my/branch')).to route_to('projects/designs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', ref: 'my/branch')
expect(get('/gitlab/gitlabhq/-/designs/1/f166f5c7afaed9e1236e4e5965585f235795db4c3f45e8a9f6ea9dde098c')).to route_to('projects/designs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', ref: 'f166f5c7afaed9e1236e4e5965585f235795db4c3f45e8a9f6ea9dde098c')
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