Commit 4100653c authored by Mike Terhar's avatar Mike Terhar Committed by Dmytro Zaporozhets

Remove feature availability check for design_management

This allows any project that meets the technical specs to use
design_management features.
parent b22ee346
---
title: Remove design management as a license feature
merge_request: 28589
author:
type: added
...@@ -57,7 +57,7 @@ The following table depicts the various user permission levels in a project. ...@@ -57,7 +57,7 @@ The following table depicts the various user permission levels in a project.
| View Dependency list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View Dependency list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View License list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View License list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View licenses in Dependency list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View licenses in Dependency list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View [Design Management](project/issues/design_management.md) pages **(PREMIUM)** | ✓ | ✓ | ✓ | ✓ | ✓ | | View [Design Management](project/issues/design_management.md) pages | ✓ | ✓ | ✓ | ✓ | ✓ |
| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core) | ✓ | ✓ | ✓ | ✓ | ✓ | | View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core) | ✓ | ✓ | ✓ | ✓ | ✓ |
...@@ -87,7 +87,7 @@ The following table depicts the various user permission levels in a project. ...@@ -87,7 +87,7 @@ The following table depicts the various user permission levels in a project.
| Create/edit/delete [Releases](project/releases/index.md)| | | ✓ | ✓ | ✓ | | Create/edit/delete [Releases](project/releases/index.md)| | | ✓ | ✓ | ✓ |
| Pull from [Conan repository](packages/conan_repository/index.md), [Maven repository](packages/maven_repository/index.md), or [NPM registry](packages/npm_registry/index.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ | | Pull from [Conan repository](packages/conan_repository/index.md), [Maven repository](packages/maven_repository/index.md), or [NPM registry](packages/npm_registry/index.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
| Publish to [Conan repository](packages/conan_repository/index.md), [Maven repository](packages/maven_repository/index.md), or [NPM registry](packages/npm_registry/index.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ | | Publish to [Conan repository](packages/conan_repository/index.md), [Maven repository](packages/maven_repository/index.md), or [NPM registry](packages/npm_registry/index.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Upload [Design Management](project/issues/design_management.md) files **(PREMIUM)** | | | ✓ | ✓ | ✓ | | Upload [Design Management](project/issues/design_management.md) files | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ | | Force push to non-protected branches | | | ✓ | ✓ | ✓ |
......
# Design Management **(PREMIUM)** # Design Management
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/660) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2. > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/660) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
......
...@@ -126,7 +126,7 @@ associated label or assignee will change to match that of the new column. The en ...@@ -126,7 +126,7 @@ associated label or assignee will change to match that of the new column. The en
board can also be filtered to only include issues from a certain milestone or an overarching board can also be filtered to only include issues from a certain milestone or an overarching
label. label.
### Design Management **(PREMIUM)** ### Design Management
With [Design Management](design_management.md), you can upload design With [Design Management](design_management.md), you can upload design
assets to issues and view them all together to easily share and assets to issues and view them all together to easily share and
......
...@@ -75,7 +75,7 @@ The following items will be exported: ...@@ -75,7 +75,7 @@ The following items will be exported:
- Project configuration, including services - Project configuration, including services
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, - Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
and other project entities and other project entities
- Design Management files and data **(PREMIUM)** - Design Management files and data
- LFS objects - LFS objects
- Issue boards - Issue boards
- Pipelines history - Pipelines history
......
...@@ -650,8 +650,7 @@ module EE ...@@ -650,8 +650,7 @@ module EE
# a few releases until we are able to understand the impact of the # a few releases until we are able to understand the impact of the
# hashed storage requirement for existing design management projects. # hashed storage requirement for existing design management projects.
# See https://gitlab.com/gitlab-org/gitlab/issues/13428#note_238729038 # See https://gitlab.com/gitlab-org/gitlab/issues/13428#note_238729038
(hashed_storage?(:repository) || ::Feature.disabled?(:design_management_require_hashed_storage, self, default_enabled: true)) && (hashed_storage?(:repository) || ::Feature.disabled?(:design_management_require_hashed_storage, self, default_enabled: true))
feature_available?(:design_management)
end end
def design_repository def design_repository
......
...@@ -64,7 +64,6 @@ class License < ApplicationRecord ...@@ -64,7 +64,6 @@ class License < ApplicationRecord
default_project_deletion_protection default_project_deletion_protection
dependency_proxy dependency_proxy
deploy_board deploy_board
design_management
disable_name_update_for_users disable_name_update_for_users
email_additional_text email_additional_text
epics epics
......
...@@ -2048,18 +2048,19 @@ describe Project do ...@@ -2048,18 +2048,19 @@ describe Project do
describe '#design_management_enabled?' do describe '#design_management_enabled?' do
let(:project) { build(:project) } let(:project) { build(:project) }
where(:license_enabled, :lfs_enabled, :hashed_storage_enabled, :hash_storage_required, :expectation) do where(:lfs_enabled, :hashed_storage_enabled, :hash_storage_required, :expectation) do
false | false | false | false | false false | false | false | false
true | false | false | false | false false | true | true | false
true | true | false | false | true false | true | false | false
true | true | false | true | false false | false | true | false
true | true | true | false | true true | false | false | true
true | true | true | true | true true | true | true | true
true | true | false | true
true | false | true | false
end end
with_them do with_them do
before do before do
stub_licensed_features(design_management: license_enabled)
stub_feature_flags(design_management_require_hashed_storage: hash_storage_required) stub_feature_flags(design_management_require_hashed_storage: hash_storage_required)
expect(project).to receive(:lfs_enabled?).and_return(lfs_enabled) expect(project).to receive(:lfs_enabled?).and_return(lfs_enabled)
allow(project).to receive(:hashed_storage?).with(:repository).and_return(hashed_storage_enabled) allow(project).to receive(:hashed_storage?).with(:repository).and_return(hashed_storage_enabled)
......
...@@ -99,19 +99,9 @@ describe DesignManagement::DesignPolicy do ...@@ -99,19 +99,9 @@ describe DesignManagement::DesignPolicy do
it { is_expected.to be_disallowed(:create_design, :destroy_design) } it { is_expected.to be_disallowed(:create_design, :destroy_design) }
end end
context "when the license does not include the feature" do context "when DesignManagement is not enabled" do
before do before do
stub_licensed_features(design_management: false) enable_design_management(false)
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
it_behaves_like "design abilities not available"
end
context "when LFS is not enabled" do
before do
stub_licensed_features(design_management: true)
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
end end
it_behaves_like "design abilities not available" it_behaves_like "design abilities not available"
......
...@@ -45,25 +45,6 @@ describe 'Getting designs related to an issue' do ...@@ -45,25 +45,6 @@ describe 'Getting designs related to an issue' do
Gitlab::UrlBuilder.build(design, ref: ref, size: size) Gitlab::UrlBuilder.build(design, ref: ref, size: size)
end end
context 'when the feature is not available' do
before do
stub_licensed_features(design_management: false)
stub_feature_flags(design_managment: false)
end
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
end
end
it 'returns no designs' do
post_graphql(query, current_user: current_user)
expect(design_collection).to be_nil
end
end
context 'when the feature is available' do context 'when the feature is available' do
before do before do
enable_design_management enable_design_management
......
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
require 'spec_helper' require 'spec_helper'
describe DesignManagement::SaveDesignsService do describe DesignManagement::SaveDesignsService do
let(:issue) { create(:issue, project: project) } include DesignManagementTestHelpers
let(:project) { create(:project) }
let(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
let(:project) { issue.project }
let(:issue) { create(:issue) }
let(:user) { developer } let(:user) { developer }
let(:files) { [rails_sample] } let(:files) { [rails_sample] }
let(:design_repository) { EE::Gitlab::GlRepository::DESIGN.repository_resolver.call(project) } let(:design_repository) { EE::Gitlab::GlRepository::DESIGN.repository_resolver.call(project) }
...@@ -70,7 +72,7 @@ describe DesignManagement::SaveDesignsService do ...@@ -70,7 +72,7 @@ describe DesignManagement::SaveDesignsService do
describe '#execute' do describe '#execute' do
context 'when the feature is not available' do context 'when the feature is not available' do
before do before do
stub_licensed_features(design_management: false) enable_design_management(false)
end end
it_behaves_like 'a service error' it_behaves_like 'a service error'
...@@ -78,279 +80,269 @@ describe DesignManagement::SaveDesignsService do ...@@ -78,279 +80,269 @@ describe DesignManagement::SaveDesignsService do
context 'when the feature is available' do context 'when the feature is available' do
before do before do
stub_licensed_features(design_management: true) enable_design_management(true)
end end
context 'when LFS is not enabled' do describe 'repository existence' do
it_behaves_like 'a service error' def repository_exists
end # Expire the memoized value as the service creates it's own instance
design_repository.expire_exists_cache
design_repository.exists?
end
context 'when LFS is enabled' do it 'creates a design repository when it did not exist' do
before do expect { run_service }.to change { repository_exists }.from(false).to(true)
allow(project).to receive(:lfs_enabled?).and_return(true)
end end
end
describe 'repository existence' do it 'updates the creation count' do
def repository_exists counter = Gitlab::UsageCounters::DesignsCounter
# Expire the memoized value as the service creates it's own instance expect { run_service }.to change { counter.read(:create) }.by(1)
design_repository.expire_exists_cache end
design_repository.exists?
end it 'creates a commit in the repository' do
run_service
expect(design_repository.commit).to have_attributes(
author: user,
message: include(rails_sample_name)
)
end
it 'causes diff_refs not to be nil' do
expect(response).to include(
designs: all(have_attributes(diff_refs: be_present))
)
end
it 'creates a design & a version for the filename if it did not exist' do
expect(issue.designs.size).to eq(0)
updated_designs = response[:designs]
expect(updated_designs.size).to eq(1)
expect(updated_designs.first.versions.size).to eq(1)
end
it 'saves the user as the author' do
updated_designs = response[:designs]
it 'creates a design repository when it did not exist' do expect(updated_designs.first.versions.first.author).to eq(user)
expect { run_service }.to change { repository_exists }.from(false).to(true) end
describe 'saving the file to LFS' do
before do
expect_next_instance_of(Lfs::FileTransformer) do |transformer|
expect(transformer).to receive(:lfs_file?).and_return(true)
end end
end end
it 'updates the creation count' do it 'saves the design to LFS' do
counter = Gitlab::UsageCounters::DesignsCounter expect { run_service }.to change { LfsObject.count }.by(1)
expect { run_service }.to change { counter.read(:create) }.by(1)
end end
it 'creates a commit in the repository' do it 'saves the repository_type of the LfsObjectsProject as design' do
run_service expect do
run_service
end.to change { project.lfs_objects_projects.count }.from(0).to(1)
expect(design_repository.commit).to have_attributes( expect(project.lfs_objects_projects.first.repository_type).to eq('design')
author: user,
message: include(rails_sample_name)
)
end end
end
it 'causes diff_refs not to be nil' do context 'when a design is being updated' do
expect(response).to include( before do
designs: all(have_attributes(diff_refs: be_present)) run_service
) touch_files
end end
it 'creates a design & a version for the filename if it did not exist' do it 'creates a new version for the existing design and updates the file' do
expect(issue.designs.size).to eq(0) expect(issue.designs.size).to eq(1)
expect(DesignManagement::Version.for_designs(issue.designs).size).to eq(1)
updated_designs = response[:designs] updated_designs = response[:designs]
expect(updated_designs.size).to eq(1) expect(updated_designs.size).to eq(1)
expect(updated_designs.first.versions.size).to eq(1) expect(updated_designs.first.versions.size).to eq(2)
end end
it 'saves the user as the author' do it 'increments the update counter' do
updated_designs = response[:designs] counter = Gitlab::UsageCounters::DesignsCounter
expect { run_service }.to change { counter.read(:update) }.by 1
expect(updated_designs.first.versions.first.author).to eq(user)
end end
describe 'saving the file to LFS' do context 'when uploading a new design' do
before do it 'does not link the new version to the existing design' do
expect_next_instance_of(Lfs::FileTransformer) do |transformer| existing_design = issue.designs.first
expect(transformer).to receive(:lfs_file?).and_return(true)
end
end
it 'saves the design to LFS' do
expect { run_service }.to change { LfsObject.count }.by(1)
end
it 'saves the repository_type of the LfsObjectsProject as design' do updated_designs = run_service([dk_png])[:designs]
expect do
run_service
end.to change { project.lfs_objects_projects.count }.from(0).to(1)
expect(project.lfs_objects_projects.first.repository_type).to eq('design') expect(existing_design.versions.reload.size).to eq(1)
expect(updated_designs.size).to eq(1)
expect(updated_designs.first.versions.size).to eq(1)
end end
end end
context 'when a design is being updated' do it 'calls repository#log_geo_updated_event' do
before do expect(design_repository).to receive(:log_geo_updated_event)
run_service
touch_files
end
it 'creates a new version for the existing design and updates the file' do
expect(issue.designs.size).to eq(1)
expect(DesignManagement::Version.for_designs(issue.designs).size).to eq(1)
updated_designs = response[:designs]
expect(updated_designs.size).to eq(1) allow_next_instance_of(described_class) do |instance|
expect(updated_designs.first.versions.size).to eq(2) allow(instance).to receive(:repository).and_return(design_repository)
end end
it 'increments the update counter' do run_service
counter = Gitlab::UsageCounters::DesignsCounter end
expect { run_service }.to change { counter.read(:update) }.by 1 end
end
context 'when uploading a new design' do context 'when a design has not changed since its previous version' do
it 'does not link the new version to the existing design' do before do
existing_design = issue.designs.first run_service
end
updated_designs = run_service([dk_png])[:designs] it 'does not create a new version' do
expect { run_service }.not_to change { issue.design_versions.count }
end
expect(existing_design.versions.reload.size).to eq(1) it 'returns the design in `skipped_designs` instead of `designs`' do
expect(updated_designs.size).to eq(1) response = run_service
expect(updated_designs.first.versions.size).to eq(1)
end
end
it 'calls repository#log_geo_updated_event' do expect(response[:designs]).to be_empty
expect(design_repository).to receive(:log_geo_updated_event) expect(response[:skipped_designs].size).to eq(1)
end
end
allow_next_instance_of(described_class) do |instance| context 'when doing a mixture of updates and creations' do
allow(instance).to receive(:repository).and_return(design_repository) let(:files) { [rails_sample, dk_png] }
end
run_service before do
end # Create just the first one, which we will later update.
run_service([files.first])
touch_files([files.first])
end end
context 'when a design has not changed since its previous version' do it 'counts one creation and one update' do
before do counter = Gitlab::UsageCounters::DesignsCounter
run_service expect { run_service }
end .to change { counter.read(:create) }.by(1)
.and change { counter.read(:update) }.by(1)
end
it 'does not create a new version' do it 'creates a single commit' do
expect { run_service }.not_to change { issue.design_versions.count } commit_count = -> do
design_repository.expire_all_method_caches
design_repository.commit_count
end end
it 'returns the design in `skipped_designs` instead of `designs`' do expect { run_service }.to change { commit_count.call }.by(1)
response = run_service
expect(response[:designs]).to be_empty
expect(response[:skipped_designs].size).to eq(1)
end
end end
context 'when doing a mixture of updates and creations' do it 'enqueues just one new version worker' do
let(:files) { [rails_sample, dk_png] } expect { run_service }.to enqueue_worker
end
end
before do context 'when uploading multiple files' do
# Create just the first one, which we will later update. let(:files) { [rails_sample, dk_png] }
run_service([files.first])
touch_files([files.first])
end
it 'counts one creation and one update' do it 'returns information about both designs in the response' do
counter = Gitlab::UsageCounters::DesignsCounter expect(response).to include(designs: have_attributes(size: 2), status: :success)
expect { run_service } end
.to change { counter.read(:create) }.by(1)
.and change { counter.read(:update) }.by(1)
end
it 'creates a single commit' do it 'creates 2 designs with a single version' do
commit_count = -> do expect { run_service }.to change { issue.designs.count }.from(0).to(2)
design_repository.expire_all_method_caches
design_repository.commit_count
end
expect { run_service }.to change { commit_count.call }.by(1) expect(DesignManagement::Version.for_designs(issue.designs).size).to eq(1)
end end
it 'enqueues just one new version worker' do it 'increments the creation count by 2' do
expect { run_service }.to enqueue_worker counter = Gitlab::UsageCounters::DesignsCounter
end expect { run_service }.to change { counter.read(:create) }.by 2
end end
context 'when uploading multiple files' do it 'enqueues a new version worker' do
let(:files) { [rails_sample, dk_png] } expect { run_service }.to enqueue_worker
end
it 'returns information about both designs in the response' do it 'creates a single commit' do
expect(response).to include(designs: have_attributes(size: 2), status: :success) commit_count = -> do
design_repository.expire_all_method_caches
design_repository.commit_count
end end
it 'creates 2 designs with a single version' do expect { run_service }.to change { commit_count.call }.by(1)
expect { run_service }.to change { issue.designs.count }.from(0).to(2) end
expect(DesignManagement::Version.for_designs(issue.designs).size).to eq(1) it 'only does 5 gitaly calls', :request_store, :sidekiq_might_not_need_inline do
end service = described_class.new(project, user, issue: issue, files: files)
# Some unrelated calls that are usually cached or happen only once
service.__send__(:repository).create_if_not_exists
service.__send__(:repository).has_visible_content?
it 'increments the creation count by 2' do request_count = -> { Gitlab::GitalyClient.get_request_count }
counter = Gitlab::UsageCounters::DesignsCounter
expect { run_service }.to change { counter.read(:create) }.by 2
end
it 'enqueues a new version worker' do # An exists?, a check for existing blobs, default branch, an after_commit
expect { run_service }.to enqueue_worker # callback on LfsObjectsProject, and the creation of commits
end expect { service.execute }.to change(&request_count).by(5)
end
it 'creates a single commit' do context 'when uploading too many files' do
commit_count = -> do let(:files) { Array.new(DesignManagement::SaveDesignsService::MAX_FILES + 1) { dk_png } }
design_repository.expire_all_method_caches
design_repository.commit_count
end
expect { run_service }.to change { commit_count.call }.by(1) it 'returns the correct error' do
expect(response[:message]).to match(/only \d+ files are allowed simultaneously/i)
end end
end
end
it 'only does 5 gitaly calls', :request_store, :sidekiq_might_not_need_inline do context 'when the user is not allowed to upload designs' do
service = described_class.new(project, user, issue: issue, files: files) let(:user) { create(:user) }
# Some unrelated calls that are usually cached or happen only once
service.__send__(:repository).create_if_not_exists
service.__send__(:repository).has_visible_content?
request_count = -> { Gitlab::GitalyClient.get_request_count }
# An exists?, a check for existing blobs, default branch, an after_commit it_behaves_like 'a service error'
# callback on LfsObjectsProject, and the creation of commits end
expect { service.execute }.to change(&request_count).by(5)
end
context 'when uploading too many files' do describe 'failure modes' do
let(:files) { Array.new(DesignManagement::SaveDesignsService::MAX_FILES + 1) { dk_png } } let(:service) { described_class.new(project, user, issue: issue, files: files) }
let(:response) { service.execute }
it 'returns the correct error' do before do
expect(response[:message]).to match(/only \d+ files are allowed simultaneously/i) expect(service).to receive(:run_actions).and_raise(some_error)
end
end
end end
context 'when the user is not allowed to upload designs' do context 'when creating the commit fails' do
let(:user) { create(:user) } let(:some_error) { Gitlab::Git::BaseError }
it_behaves_like 'a service error' it_behaves_like 'an execution error'
end end
describe 'failure modes' do context 'when creating the versions fails' do
let(:service) { described_class.new(project, user, issue: issue, files: files) } let(:some_error) { ActiveRecord::RecordInvalid }
let(:response) { service.execute }
before do
expect(service).to receive(:run_actions).and_raise(some_error)
end
context 'when creating the commit fails' do
let(:some_error) { Gitlab::Git::BaseError }
it_behaves_like 'an execution error'
end
context 'when creating the versions fails' do
let(:some_error) { ActiveRecord::RecordInvalid }
it_behaves_like 'a service error' it_behaves_like 'a service error'
end
end end
end
context "when a design already existed in the repo but we didn't know about it in the database" do context "when a design already existed in the repo but we didn't know about it in the database" do
let(:filename) { rails_sample_name } let(:filename) { rails_sample_name }
before do before do
path = File.join(build(:design, issue: issue, filename: filename).full_path) path = File.join(build(:design, issue: issue, filename: filename).full_path)
design_repository.create_if_not_exists design_repository.create_if_not_exists
design_repository.create_file(user, path, 'something fake', design_repository.create_file(user, path, 'something fake',
branch_name: 'master', branch_name: 'master',
message: 'Somehow created without being tracked in db') message: 'Somehow created without being tracked in db')
end end
it 'creates the design and a new version for it' do it 'creates the design and a new version for it' do
first_updated_design = response[:designs].first first_updated_design = response[:designs].first
expect(first_updated_design.filename).to eq(filename) expect(first_updated_design.filename).to eq(filename)
expect(first_updated_design.versions.size).to eq(1) expect(first_updated_design.versions.size).to eq(1)
end
end end
end end
describe 'scalability' do describe 'scalability', skip: 'See: https://gitlab.com/gitlab-org/gitlab/-/issues/213169' do
before do before do
run_service([dk_png]) # ensure project, issue, etc are created run_service([sample_image('banana_sample.gif')]) # ensure project, issue, etc are created
end end
it 'runs the same queries for all requests, regardless of number of files' do it 'runs the same queries for all requests, regardless of number of files' do
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
module DesignManagementTestHelpers module DesignManagementTestHelpers
def enable_design_management(enabled = true, ref_filter = true) def enable_design_management(enabled = true, ref_filter = true)
stub_licensed_features(design_management: enabled)
stub_lfs_setting(enabled: enabled) stub_lfs_setting(enabled: enabled)
stub_feature_flags(design_management_reference_filter_gfm_pipeline: ref_filter) stub_feature_flags(design_management_reference_filter_gfm_pipeline: ref_filter)
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