Commit 9a50df2d authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '241166-sse-config-mounts' into 'master'

Introduce configuration value for mounts

See merge request gitlab-org/gitlab!43485
parents 78e23f53 2658f557
......@@ -12,10 +12,19 @@ const initStaticSiteEditor = el => {
namespace,
project,
mergeRequestsIllustrationPath,
// NOTE: The following variables are not yet used, but are supported by the config file,
// so we are adding them here as a convenience for future use.
// eslint-disable-next-line no-unused-vars
staticSiteGenerator,
// eslint-disable-next-line no-unused-vars
imageUploadPath,
mounts,
} = el.dataset;
// NOTE that the object in 'mounts' is a JSON string from the data attribute, so it must be parsed into an object.
// eslint-disable-next-line no-unused-vars
const mountsObject = JSON.parse(mounts);
const { current_username: username } = window.gon;
const returnUrl = el.dataset.returnUrl || null;
const router = createRouter(baseUrl);
const apolloProvider = createApolloProvider({
isSupportedContent: parseBoolean(isSupportedContent),
......
......@@ -25,7 +25,7 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController
).execute
if service_response.success?
@data = service_response.payload
@data = serialize_necessary_payload_values_to_json(service_response.payload)
else
# TODO: For now, if the service returns any error, the user is redirected
# to the root project page with the error message displayed as an alert.
......@@ -38,6 +38,17 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController
private
def serialize_necessary_payload_values_to_json(payload)
# This will convert booleans, Array-like and Hash-like objects to JSON
payload.transform_values do |value|
if value.is_a?(String) || value.is_a?(Integer)
value
else
value.to_json
end
end
end
def assign_ref_and_path
@ref, @path = extract_ref(params.fetch(:id))
......
---
title: Introduce 'mounts' entry support for '.gitlab/static-site-editor.yml' config file.
merge_request: 43485
author:
type: added
......@@ -15,6 +15,7 @@ module Gitlab
ALLOWED_KEYS = %i[
image_upload_path
mounts
static_site_generator
].freeze
......@@ -26,6 +27,8 @@ module Gitlab
entry :image_upload_path, Entry::ImageUploadPath,
description: 'Configuration of the Static Site Editor image upload path.'
entry :mounts, Entry::Mounts,
description: 'Configuration of the Static Site Editor mounts.'
entry :static_site_generator, Entry::StaticSiteGenerator,
description: 'Configuration of the Static Site Editor static site generator.'
end
......
# frozen_string_literal: true
module Gitlab
module StaticSiteEditor
module Config
class FileConfig
module Entry
##
# Entry that represents the mappings of mounted source directories to target paths
#
class Mount < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[source target].freeze
attributes ALLOWED_KEYS
validations do
validates :config, allowed_keys: ALLOWED_KEYS
validates :source, type: String, presence: true
validates :target, type: String, presence: true, allow_blank: true
end
def self.default
# NOTE: This is the default for middleman projects. Ideally, this would be determined
# based on the defaults for whatever `static_site_generator` is configured.
{
source: 'source',
target: ''
}
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module StaticSiteEditor
module Config
class FileConfig
module Entry
##
# Entry that represents the mappings of mounted source directories to target paths
#
class Mounts < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Validatable
entry :mount, Entry::Mount, description: 'Configuration of a Static Site Editor mount.'
validations do
validates :config, type: Array, presence: true
end
def skip_config_hash_validation?
true
end
def self.default
[Entry::Mount.default]
end
end
end
end
end
end
end
......@@ -21,7 +21,7 @@ module Gitlab
project: project.path,
namespace: project.namespace.full_path,
return_url: sanitize_url(return_url),
is_supported_content: supported_content?.to_s,
is_supported_content: supported_content?,
base_url: Gitlab::Routing.url_helpers.project_show_sse_path(project, full_path),
merge_requests_illustration_path: merge_requests_illustration_path
}
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::StaticSiteEditorController do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
let(:data) { instance_double(Hash) }
let(:data) { { key: 'value' } }
describe 'GET show' do
render_views
......@@ -52,42 +52,78 @@ RSpec.describe Projects::StaticSiteEditorController do
end
end
%w[developer maintainer].each do |role|
context "as #{role}" do
before_all do
project.add_role(user, role)
context "as developer" do
before do
project.add_role(user, 'developer')
sign_in(user)
get :show, params: default_params
end
it 'renders the edit page' do
expect(response).to render_template(:show)
end
it 'assigns ref and path variables' do
expect(assigns(:ref)).to eq('master')
expect(assigns(:path)).to eq('README.md')
end
context 'when combination of ref and path is incorrect' do
let(:default_params) { super().merge(id: 'unknown') }
it 'responds with 404 page' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when invalid config file' do
let(:service_response) { ServiceResponse.error(message: 'invalid') }
before do
sign_in(user)
get :show, params: default_params
it 'redirects to project page and flashes error message' do
expect(response).to redirect_to(project_path(project))
expect(response).to set_flash[:alert].to('invalid')
end
end
it 'renders the edit page' do
expect(response).to render_template(:show)
context 'with a service response payload containing multiple data types' do
let(:data) do
{
a_string: 'string',
an_array: [
{
foo: 'bar'
}
],
an_integer: 123,
a_hash: {
a_deeper_hash: {
foo: 'bar'
}
},
a_boolean: true
}
end
it 'assigns a required variables' do
expect(assigns(:data)).to eq(data)
expect(assigns(:ref)).to eq('master')
expect(assigns(:path)).to eq('README.md')
let(:assigns_data) { assigns(:data) }
it 'leaves data values which are strings as strings' do
expect(assigns_data[:a_string]).to eq('string')
end
context 'when combination of ref and path is incorrect' do
let(:default_params) { super().merge(id: 'unknown') }
it 'leaves data values which are integers as integers' do
expect(assigns_data[:an_integer]).to eq(123)
end
it 'responds with 404 page' do
expect(response).to have_gitlab_http_status(:not_found)
end
it 'serializes data values which are booleans to JSON' do
expect(assigns_data[:a_boolean]).to eq('true')
end
context 'when invalid config file' do
let(:service_response) { ServiceResponse.error(message: 'invalid') }
it 'serializes data values which are arrays to JSON' do
expect(assigns_data[:an_array]).to eq('[{"foo":"bar"}]')
end
it 'redirects to project page and flashes error message' do
expect(response).to redirect_to(project_path(project))
expect(response).to set_flash[:alert].to('invalid')
end
it 'serializes data values which are hashes to JSON' do
expect(assigns_data[:a_hash]).to eq('{"a_deeper_hash":{"foo":"bar"}}')
end
end
end
......
......@@ -21,7 +21,7 @@ RSpec.describe 'Static Site Editor' do
visit sse_path
end
it 'renders Static Site Editor page with all generated config values and default config file values' do
it 'renders SSE page with all generated config values and default config file values' do
node = page.find('#static-site-editor')
# assert generated config values are present
......@@ -29,23 +29,30 @@ RSpec.describe 'Static Site Editor' do
expect(node['data-branch']).to eq('master')
expect(node['data-commit-id']).to match(/\A[0-9a-f]{40}\z/)
expect(node['data-is-supported-content']).to eq('true')
expect(node['data-merge-requests-illustration-path']).to match(%r{/assets/illustrations/merge_requests-.*\.svg})
expect(node['data-merge-requests-illustration-path'])
.to match(%r{/assets/illustrations/merge_requests-.*\.svg})
expect(node['data-namespace']).to eq(project.namespace.full_path)
expect(node['data-project']).to eq(project.path)
expect(node['data-project-id']).to eq(project.id.to_s)
# assert default config file values are present
expect(node['data-image-upload-path']).to eq('source/images')
expect(node['data-mounts']).to eq('[{"source":"source","target":""}]')
expect(node['data-static-site-generator']).to eq('middleman')
end
end
context "when a config file is present" do
let(:config_file_yml) do
<<-EOS
<<~YAML
image_upload_path: custom-image-upload-path
mounts:
- source: source1
target: ""
- source: source2
target: target2
static_site_generator: middleman
EOS
YAML
end
before do
......@@ -60,7 +67,9 @@ RSpec.describe 'Static Site Editor' do
node = page.find('#static-site-editor')
# assert user-specified config file values are present
expected_mounts = '[{"source":"source1","target":""},{"source":"source2","target":"target2"}]'
expect(node['data-image-upload-path']).to eq('custom-image-upload-path')
expect(node['data-mounts']).to eq(expected_mounts)
expect(node['data-static-site-generator']).to eq('middleman')
end
end
......
......@@ -4,9 +4,19 @@ require 'spec_helper'
RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Global do
let(:global) { described_class.new(hash) }
let(:default_static_site_generator_value) { 'middleman' }
let(:default_image_upload_path_value) { 'source/images' }
let(:default_mounts_value) do
[
{
source: 'source',
target: ''
}
]
end
let(:default_static_site_generator_value) { 'middleman' }
shared_examples_for 'valid default configuration' do
describe '#compose!' do
before do
......@@ -18,7 +28,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Global do
end
it 'creates node object for each entry' do
expect(global.descendants.count).to eq 2
expect(global.descendants.count).to eq 3
end
it 'creates node object using valid class' do
......@@ -67,6 +77,12 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Global do
end
end
describe '#mounts_value' do
it 'returns correct values' do
expect(global.mounts_value).to eq(default_mounts_value)
end
end
describe '#static_site_generator_value' do
it 'returns correct values' do
expect(global.static_site_generator_value).to eq(default_static_site_generator_value)
......@@ -84,6 +100,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Global do
it 'contains the expected node names' do
expected_node_names = %i[
image_upload_path
mounts
static_site_generator
]
expect(described_class.nodes.keys).to match_array(expected_node_names)
......@@ -96,6 +113,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Global do
let(:expected_node_object_classes) do
[
Gitlab::StaticSiteEditor::Config::FileConfig::Entry::ImageUploadPath,
Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Mounts,
Gitlab::StaticSiteEditor::Config::FileConfig::Entry::StaticSiteGenerator
]
end
......@@ -103,6 +121,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Global do
let(:hash) do
{
image_upload_path: default_image_upload_path_value,
mounts: default_mounts_value,
static_site_generator: default_static_site_generator_value
}
end
......@@ -114,6 +133,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Global do
context 'when value is an empty hash' do
let(:expected_node_object_classes) do
[
Gitlab::Config::Entry::Unspecified,
Gitlab::Config::Entry::Unspecified,
Gitlab::Config::Entry::Unspecified
]
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Mount do
subject(:entry) { described_class.new(config) }
describe 'validations' do
context 'with a valid config' do
context 'and target is a non-empty string' do
let(:config) do
{
source: 'source',
target: 'sub-site'
}
end
it { is_expected.to be_valid }
describe '#value' do
it 'returns mount configuration' do
expect(entry.value).to eq config
end
end
end
context 'and target is an empty string' do
let(:config) do
{
source: 'source',
target: ''
}
end
it { is_expected.to be_valid }
describe '#value' do
it 'returns mount configuration' do
expect(entry.value).to eq config
end
end
end
end
context 'with an invalid config' do
context 'when source is not a string' do
let(:config) { { source: 123, target: 'target' } }
it { is_expected.not_to be_valid }
it 'reports error' do
expect(entry.errors)
.to include 'mount source should be a string'
end
end
context 'when source is not present' do
let(:config) { { target: 'target' } }
it { is_expected.not_to be_valid }
it 'reports error' do
expect(entry.errors)
.to include "mount source can't be blank"
end
end
context 'when target is not a string' do
let(:config) { { source: 'source', target: 123 } }
it { is_expected.not_to be_valid }
it 'reports error' do
expect(entry.errors)
.to include 'mount target should be a string'
end
end
context 'when there is an unknown key present' do
let(:config) { { test: 100 } }
it { is_expected.not_to be_valid }
it 'reports error' do
expect(entry.errors)
.to include 'mount config contains unknown keys: test'
end
end
end
end
describe '.default' do
it 'returns default mount' do
expect(described_class.default)
.to eq({
source: 'source',
target: ''
})
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig::Entry::Mounts do
subject(:entry) { described_class.new(config) }
describe 'validations' do
context 'with a valid config' do
let(:config) do
[
{
source: 'source',
target: ''
},
{
source: 'sub-site/source',
target: 'sub-site'
}
]
end
it { is_expected.to be_valid }
describe '#value' do
it 'returns mounts configuration' do
expect(entry.value).to eq config
end
end
end
context 'with an invalid config' do
let(:config) { { not_an_array: true } }
it { is_expected.not_to be_valid }
it 'reports errors about wrong type' do
expect(entry.errors)
.to include 'mounts config should be a array'
end
end
end
describe '.default' do
it 'returns default mounts' do
expect(described_class.default)
.to eq([{
source: 'source',
target: ''
}])
end
end
end
......@@ -29,7 +29,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::GeneratedConfig do
project: 'project',
project_id: project.id,
return_url: 'http://example.com',
is_supported_content: 'true',
is_supported_content: true,
base_url: '/namespace/project/-/sse/master%2FREADME.md',
merge_requests_illustration_path: %r{illustrations/merge_requests}
})
......@@ -65,7 +65,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::GeneratedConfig do
stub_feature_flags(sse_erb_support: project)
end
it { is_expected.to include(is_supported_content: 'true') }
it { is_expected.to include(is_supported_content: true) }
end
context 'when feature flag is disabled' do
......@@ -75,7 +75,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::GeneratedConfig do
stub_feature_flags(sse_erb_support: false)
end
it { is_expected.to include(is_supported_content: 'false') }
it { is_expected.to include(is_supported_content: false) }
end
end
......@@ -88,31 +88,31 @@ RSpec.describe Gitlab::StaticSiteEditor::Config::GeneratedConfig do
context 'when branch is not master' do
let(:ref) { 'my-branch' }
it { is_expected.to include(is_supported_content: 'false') }
it { is_expected.to include(is_supported_content: false) }
end
context 'when file does not have a markdown extension' do
let(:path) { 'README.txt' }
it { is_expected.to include(is_supported_content: 'false') }
it { is_expected.to include(is_supported_content: false) }
end
context 'when file does not have an extension' do
let(:path) { 'README' }
it { is_expected.to include(is_supported_content: 'false') }
it { is_expected.to include(is_supported_content: false) }
end
context 'when file does not exist' do
let(:path) { 'UNKNOWN.md' }
it { is_expected.to include(is_supported_content: 'false') }
it { is_expected.to include(is_supported_content: false) }
end
context 'when repository is empty' do
let(:repository) { create(:project_empty_repo).repository }
it { is_expected.to include(is_supported_content: 'false') }
it { is_expected.to include(is_supported_content: false) }
end
context 'when return_url is not a valid URL' 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