Commit 7184b409 authored by Nick Thomas's avatar Nick Thomas

Merge branch '27144-gitlab-hosted-codesandbox' into 'master'

Add custom bundlerURL to clientside preview

See merge request gitlab-org/gitlab!21520
parents becb0bfd d7300369
...@@ -21,7 +21,7 @@ export default { ...@@ -21,7 +21,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['entries', 'promotionSvgPath', 'links']), ...mapState(['entries', 'promotionSvgPath', 'links', 'codesandboxBundlerUrl']),
...mapGetters(['packageJson', 'currentProject']), ...mapGetters(['packageJson', 'currentProject']),
normalizedEntries() { normalizedEntries() {
return Object.keys(this.entries).reduce((acc, path) => { return Object.keys(this.entries).reduce((acc, path) => {
...@@ -106,12 +106,7 @@ export default { ...@@ -106,12 +106,7 @@ export default {
return this.loadFileContent(this.mainEntry) return this.loadFileContent(this.mainEntry)
.then(() => this.$nextTick()) .then(() => this.$nextTick())
.then(() => { .then(() => {
this.initManager('#ide-preview', this.sandboxOpts, { this.initManager();
fileResolver: {
isFile: p => Promise.resolve(Boolean(this.entries[createPathWithExt(p)])),
readFile: p => this.loadFileContent(createPathWithExt(p)).then(content => content),
},
});
this.listener = listen(e => { this.listener = listen(e => {
switch (e.type) { switch (e.type) {
...@@ -139,8 +134,18 @@ export default { ...@@ -139,8 +134,18 @@ export default {
this.manager.updatePreview(this.sandboxOpts); this.manager.updatePreview(this.sandboxOpts);
}, 250); }, 250);
}, },
initManager(el, opts, resolver) { initManager() {
this.manager = new Manager(el, opts, resolver); const { codesandboxBundlerUrl: bundlerURL } = this;
const settings = {
fileResolver: {
isFile: p => Promise.resolve(Boolean(this.entries[createPathWithExt(p)])),
readFile: p => this.loadFileContent(createPathWithExt(p)).then(content => content),
},
...(bundlerURL ? { bundlerURL } : {}),
};
this.manager = new Manager('#ide-preview', this.sandboxOpts, settings);
}, },
}, },
}; };
......
...@@ -53,6 +53,7 @@ export function initIde(el, options = {}) { ...@@ -53,6 +53,7 @@ export function initIde(el, options = {}) {
clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled), clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled),
renderWhitespaceInCode: parseBoolean(el.dataset.renderWhitespaceInCode), renderWhitespaceInCode: parseBoolean(el.dataset.renderWhitespaceInCode),
editorTheme: window.gon?.user_color_scheme || DEFAULT_THEME, editorTheme: window.gon?.user_color_scheme || DEFAULT_THEME,
codesandboxBundlerUrl: el.dataset.codesandboxBundlerUrl,
}); });
}, },
methods: { methods: {
......
...@@ -34,4 +34,5 @@ export default () => ({ ...@@ -34,4 +34,5 @@ export default () => ({
clientsidePreviewEnabled: false, clientsidePreviewEnabled: false,
renderWhitespaceInCode: false, renderWhitespaceInCode: false,
editorTheme: DEFAULT_THEME, editorTheme: DEFAULT_THEME,
codesandboxBundlerUrl: null,
}); });
# frozen_string_literal: true
module ClientsidePreviewCSP
extend ActiveSupport::Concern
included do
content_security_policy do |p|
next if p.directives.blank?
next unless Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?
default_frame_src = p.directives['frame-src'] || p.directives['default-src']
frame_src_values = Array.wrap(default_frame_src) | [Gitlab::CurrentSettings.web_ide_clientside_preview_bundler_url].compact
p.frame_src(*frame_src_values)
end
end
end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class IdeController < ApplicationController class IdeController < ApplicationController
layout 'fullscreen' layout 'fullscreen'
include ClientsidePreviewCSP
include StaticObjectExternalStorageCSP include StaticObjectExternalStorageCSP
def index def index
......
...@@ -10,8 +10,9 @@ module IdeHelper ...@@ -10,8 +10,9 @@ module IdeHelper
"promotion-svg-path": image_path('illustrations/web-ide_promotion.svg'), "promotion-svg-path": image_path('illustrations/web-ide_promotion.svg'),
"ci-help-page-path" => help_page_path('ci/quick_start/README'), "ci-help-page-path" => help_page_path('ci/quick_start/README'),
"web-ide-help-page-path" => help_page_path('user/project/web_ide/index.html'), "web-ide-help-page-path" => help_page_path('user/project/web_ide/index.html'),
"clientside-preview-enabled": Gitlab::CurrentSettings.current_application_settings.web_ide_clientside_preview_enabled.to_s, "clientside-preview-enabled": Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?.to_s,
"render-whitespace-in-code": current_user.render_whitespace_in_code.to_s "render-whitespace-in-code": current_user.render_whitespace_in_code.to_s,
"codesandbox-bundler-url": Gitlab::CurrentSettings.web_ide_clientside_preview_bundler_url
} }
end end
end end
...@@ -351,6 +351,12 @@ module ApplicationSettingImplementation ...@@ -351,6 +351,12 @@ module ApplicationSettingImplementation
static_objects_external_storage_url.present? static_objects_external_storage_url.present?
end end
# This will eventually be configurable
# https://gitlab.com/gitlab-org/gitlab/issues/208161
def web_ide_clientside_preview_bundler_url
'https://sandbox-prod.gitlab-static.net'
end
private private
def separate_whitelists(string_array) def separate_whitelists(string_array)
......
---
title: Update Web IDE clientside preview bundler to use GitLab managed server
merge_request: 21520
author:
type: changed
# frozen_string_literal: true
require 'spec_helper'
describe 'IDE Clientside Preview CSP' do
let_it_be(:user) { create(:user) }
shared_context 'disable feature' do
before do
allow_next_instance_of(ApplicationSetting) do |instance|
allow(instance).to receive(:web_ide_clientside_preview_enabled?).and_return(false)
end
end
end
it_behaves_like 'setting CSP', 'frame-src' do
let(:whitelisted_url) { 'https://sandbox.gitlab-static.test' }
let(:extended_controller_class) { IdeController }
subject do
visit ide_path
response_headers['Content-Security-Policy']
end
before do
allow_next_instance_of(ApplicationSetting) do |instance|
allow(instance).to receive(:web_ide_clientside_preview_enabled?).and_return(true)
allow(instance).to receive(:web_ide_clientside_preview_bundler_url).and_return(whitelisted_url)
end
sign_in(user)
end
end
end
...@@ -11,7 +11,7 @@ describe 'Static Object External Storage Content Security Policy' do ...@@ -11,7 +11,7 @@ describe 'Static Object External Storage Content Security Policy' do
end end
end end
it_behaves_like 'setting CSP connect-src' do it_behaves_like 'setting CSP', 'connect-src' do
let_it_be(:whitelisted_url) { 'https://static-objects.test' } let_it_be(:whitelisted_url) { 'https://static-objects.test' }
let_it_be(:extended_controller_class) { IdeController } let_it_be(:extended_controller_class) { IdeController }
......
...@@ -12,7 +12,7 @@ describe 'Sourcegraph Content Security Policy' do ...@@ -12,7 +12,7 @@ describe 'Sourcegraph Content Security Policy' do
end end
end end
it_behaves_like 'setting CSP connect-src' do it_behaves_like 'setting CSP', 'connect-src' do
let_it_be(:whitelisted_url) { 'https://sourcegraph.test' } let_it_be(:whitelisted_url) { 'https://sourcegraph.test' }
let_it_be(:extended_controller_class) { Projects::BlobController } let_it_be(:extended_controller_class) { Projects::BlobController }
......
...@@ -16,6 +16,17 @@ const dummyPackageJson = () => ({ ...@@ -16,6 +16,17 @@ const dummyPackageJson = () => ({
main: 'index.js', main: 'index.js',
}), }),
}); });
const expectedSandpackOptions = () => ({
files: {},
entry: '/index.js',
showOpenInCodeSandbox: true,
});
const expectedSandpackSettings = () => ({
fileResolver: {
isFile: expect.any(Function),
readFile: expect.any(Function),
},
});
describe('IDE clientside preview', () => { describe('IDE clientside preview', () => {
let wrapper; let wrapper;
...@@ -84,6 +95,46 @@ describe('IDE clientside preview', () => { ...@@ -84,6 +95,46 @@ describe('IDE clientside preview', () => {
return waitForCalls(); return waitForCalls();
}); });
it('creates sandpack manager', () => {
expect(smooshpack.Manager).toHaveBeenCalledWith(
'#ide-preview',
expectedSandpackOptions(),
expectedSandpackSettings(),
);
});
it('pings usage', () => {
expect(storeClientsideActions.pingUsage).toHaveBeenCalledTimes(1);
});
});
describe('with codesandboxBundlerUrl', () => {
const TEST_BUNDLER_URL = 'https://test.gitlab-static.test';
beforeEach(() => {
createComponent({
getters: { packageJson: dummyPackageJson },
state: { codesandboxBundlerUrl: TEST_BUNDLER_URL },
});
return waitForCalls();
});
it('creates sandpack manager with bundlerURL', () => {
expect(smooshpack.Manager).toHaveBeenCalledWith('#ide-preview', expectedSandpackOptions(), {
...expectedSandpackSettings(),
bundlerURL: TEST_BUNDLER_URL,
});
});
});
describe('with codesandboxBundlerURL', () => {
beforeEach(() => {
createComponent({ getters: { packageJson: dummyPackageJson } });
return waitForCalls();
});
it('creates sandpack manager', () => { it('creates sandpack manager', () => {
expect(smooshpack.Manager).toHaveBeenCalledWith( expect(smooshpack.Manager).toHaveBeenCalledWith(
'#ide-preview', '#ide-preview',
...@@ -100,10 +151,6 @@ describe('IDE clientside preview', () => { ...@@ -100,10 +151,6 @@ describe('IDE clientside preview', () => {
}, },
); );
}); });
it('pings usage', () => {
expect(storeClientsideActions.pingUsage).toHaveBeenCalledTimes(1);
});
}); });
describe('computed', () => { describe('computed', () => {
......
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'setting CSP connect-src' do RSpec.shared_examples 'setting CSP' do |rule_name|
let_it_be(:default_csp_values) { "'self' https://some-cdn.test" } let_it_be(:default_csp_values) { "'self' https://some-cdn.test" }
shared_context 'csp config' do |csp_rule| shared_context 'csp config' do |csp_rule|
...@@ -10,7 +10,7 @@ RSpec.shared_examples 'setting CSP connect-src' do ...@@ -10,7 +10,7 @@ RSpec.shared_examples 'setting CSP connect-src' do
end end
expect_next_instance_of(extended_controller_class) do |controller| expect_next_instance_of(extended_controller_class) do |controller|
expect(controller).to receive(:current_content_security_policy).and_return(csp) expect(controller).to receive(:current_content_security_policy).at_least(:once).and_return(csp)
end end
end end
end end
...@@ -23,55 +23,55 @@ RSpec.shared_examples 'setting CSP connect-src' do ...@@ -23,55 +23,55 @@ RSpec.shared_examples 'setting CSP connect-src' do
end end
end end
describe 'when a CSP config exists for connect-src' do describe "when a CSP config exists for #{rule_name}" do
include_context 'csp config', :connect_src include_context 'csp config', rule_name.parameterize.underscore.to_sym
context 'when feature is enabled' do context 'when feature is enabled' do
it 'appends to connect-src' do it "appends to #{rule_name}" do
is_expected.to eql("connect-src #{default_csp_values} #{whitelisted_url}") is_expected.to eql("#{rule_name} #{default_csp_values} #{whitelisted_url}")
end end
end end
context 'when feature is disabled' do context 'when feature is disabled' do
include_context 'disable feature' include_context 'disable feature'
it 'keeps original connect-src' do it "keeps original #{rule_name}" do
is_expected.to eql("connect-src #{default_csp_values}") is_expected.to eql("#{rule_name} #{default_csp_values}")
end end
end end
end end
describe 'when a CSP config exists for default-src but not connect-src' do describe "when a CSP config exists for default-src but not #{rule_name}" do
include_context 'csp config', :default_src include_context 'csp config', :default_src
context 'when feature is enabled' do context 'when feature is enabled' do
it 'uses default-src values in connect-src' do it "uses default-src values in #{rule_name}" do
is_expected.to eql("default-src #{default_csp_values}; connect-src #{default_csp_values} #{whitelisted_url}") is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{whitelisted_url}")
end end
end end
context 'when feature is disabled' do context 'when feature is disabled' do
include_context 'disable feature' include_context 'disable feature'
it 'does not add connect-src' do it "does not add #{rule_name}" do
is_expected.to eql("default-src #{default_csp_values}") is_expected.to eql("default-src #{default_csp_values}")
end end
end end
end end
describe 'when a CSP config exists for font-src but not connect-src' do describe "when a CSP config exists for font-src but not #{rule_name}" do
include_context 'csp config', :font_src include_context 'csp config', :font_src
context 'when feature is enabled' do context 'when feature is enabled' do
it 'uses default-src values in connect-src' do it "uses default-src values in #{rule_name}" do
is_expected.to eql("font-src #{default_csp_values}; connect-src #{whitelisted_url}") is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{whitelisted_url}")
end end
end end
context 'when feature is disabled' do context 'when feature is disabled' do
include_context 'disable feature' include_context 'disable feature'
it 'does not add connect-src' do it "does not add #{rule_name}" do
is_expected.to eql("font-src #{default_csp_values}") is_expected.to eql("font-src #{default_csp_values}")
end end
end end
......
...@@ -2779,10 +2779,10 @@ code-point-at@^1.0.0: ...@@ -2779,10 +2779,10 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codesandbox-api@^0.0.20: codesandbox-api@0.0.23:
version "0.0.20" version "0.0.23"
resolved "https://registry.yarnpkg.com/codesandbox-api/-/codesandbox-api-0.0.20.tgz#174bcd76c9f31521175c6bceabc37da6b1fbc30b" resolved "https://registry.yarnpkg.com/codesandbox-api/-/codesandbox-api-0.0.23.tgz#bf650a21b5f3c2369e03f0c19d10b4e2ba255b4f"
integrity sha512-jhxZzAmjCKBZad8QWMeueiQVFE87igK6F2DBOEVFFJO6jgTXT8qjuzGYepr+B8bjgo/icN7bc/2xmEMBA63s2w== integrity sha512-fFGBkIghDkQILh7iHYlpZU5sfWncCDb92FQSFE4rR3VBcTfUsD5VZgpQi+JjZQuwWIdfl4cOhcIFrUYwshUezA==
codesandbox-import-util-types@^1.2.11: codesandbox-import-util-types@^1.2.11:
version "1.2.11" version "1.2.11"
...@@ -10334,12 +10334,12 @@ slugify@^1.3.1: ...@@ -10334,12 +10334,12 @@ slugify@^1.3.1:
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2" resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2"
integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw== integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw==
smooshpack@^0.0.54: smooshpack@^0.0.62:
version "0.0.54" version "0.0.62"
resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.54.tgz#9044358b85052d348b801f385678c8a0c76f2bb6" resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.62.tgz#cb31b9f808f73de3146b050f84d044eb353b5503"
integrity sha512-yIwEWb17hqoW5IaWyzO6O6nxY89I5UdRoGIZy5hihoqXP9OYcoMbBTxKwS57MeXSKdNA2rtk86rlCcOgAYIgrA== integrity sha512-lFuJV2f504/U78sifWy0V2FyoE/8mTgOXM4DL918ncNxAxbtu236XSCLAH3SQwXZWn0JdmRnWs/XU4+sIUVVmQ==
dependencies: dependencies:
codesandbox-api "^0.0.20" codesandbox-api "0.0.23"
codesandbox-import-utils "^1.2.3" codesandbox-import-utils "^1.2.3"
lodash.isequal "^4.5.0" lodash.isequal "^4.5.0"
......
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