diff --git a/app/assets/javascripts/repository/components/web_ide_link.vue b/app/assets/javascripts/repository/components/web_ide_link.vue
deleted file mode 100644
index 6549d5a387860add3705082c993f22ef504dd296..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/repository/components/web_ide_link.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 187bbfed125624ac55ccb685bd266e31a92466f2..7f72524b6fe136c36eed4e4295cf6354b8060b8d 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -1,30 +1,22 @@
import Vue from 'vue';
-import { escapeFileUrl } from '../lib/utils/url_utility';
+import { escapeFileUrl, joinPaths, webIDEUrl } from '../lib/utils/url_utility';
import createRouter from './router';
import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue';
import TreeActionLink from './components/tree_action_link.vue';
-import WebIdeLink from './components/web_ide_link.vue';
+import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
import DirectoryDownloadLinks from './components/directory_download_links.vue';
import apolloProvider from './graphql';
import { setTitle } from './utils/title';
import { updateFormAction } from './utils/dom';
-import { parseBoolean } from '../lib/utils/common_utils';
+import { convertObjectPropsToCamelCase, parseBoolean } from '../lib/utils/common_utils';
import { __ } from '../locale';
export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list');
const { dataset } = el;
- const {
- canPushCode,
- projectPath,
- projectShortPath,
- forkPath,
- ref,
- escapedRef,
- fullName,
- } = dataset;
+ const { projectPath, projectShortPath, ref, escapedRef, fullName } = dataset;
const router = createRouter(projectPath, escapedRef);
apolloProvider.clients.defaultClient.cache.writeData({
@@ -121,6 +113,10 @@ export default function setupVueRepositoryList() {
const webIdeLinkEl = document.getElementById('js-tree-web-ide-link');
if (webIdeLinkEl) {
+ const { ideBasePath, ...options } = convertObjectPropsToCamelCase(
+ JSON.parse(webIdeLinkEl.dataset.options),
+ );
+
// eslint-disable-next-line no-new
new Vue({
el: webIdeLinkEl,
@@ -128,10 +124,10 @@ export default function setupVueRepositoryList() {
render(h) {
return h(WebIdeLink, {
props: {
- projectPath,
- refSha: ref,
- forkPath,
- canPushCode: parseBoolean(canPushCode),
+ webIdeUrl: webIDEUrl(
+ joinPaths('/', ideBasePath, 'edit', ref, '-', this.$route.params.path || '', '/'),
+ ),
+ ...options,
},
});
},
diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f333ab49ead02409a195715b5b7c24badae1f6ea
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/actions_button.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+ {{ selectedAction.text }}
+
+
+
+
+ {{ action.text }}
+
+
+
+
+
+ {{ selectedAction.text }}
+
+
diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8307c6d3b551c94f3cec3e15791da678e7c1cefa
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index b6039b971a93489653e5f2530d4dbaec81901b77..a9c1652d00dfece1e4698900a88b8e585dbd5e35 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -542,3 +542,13 @@ fieldset[disabled] .btn,
.btn-no-padding {
padding: 0;
}
+
+// This class helps convert `.gl-button` children so that they consistently
+// match the style of `.btn` elements which might be around them. Ideally we
+// wouldn't need this class.
+//
+// Remove by upgrading all buttons in a container to use the new `.gl-button` style.
+.gl-button-deprecated-adapter .gl-button {
+ box-shadow: none;
+ border-width: 1px;
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d41f10202a37d256fb5ccab3f24f818ed7fd539a..0f01f3b38aaeb9b68be4483cc923fa74333e7038 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -1135,3 +1135,17 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
width: $gl-dropdown-width-wide;
}
}
+
+.gl-dropdown-item-deprecated-adapter {
+ .dropdown-item {
+ align-items: flex-start;
+
+ .gl-new-dropdown-item-text-primary {
+ @include gl-font-weight-bold;
+ }
+
+ .gl-new-dropdown-item-text-secondary {
+ color: inherit;
+ }
+ }
+}
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 8653fe3b6ede31498aea16e383b0dff16ac492e0..ea4d3e861be05a9b8a0b557a1eda293ad656afec 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -51,6 +51,7 @@ def preferences_param_names
:view_diffs_file_by_file,
:tab_width,
:sourcegraph_enabled,
+ :gitpod_enabled,
:render_whitespace_in_code
]
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 9a28088ccf665b0dc8623eb83233fc54ae69ca61..248d5755d92f19603355b923e98cc65e8f2c0291 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -104,6 +104,7 @@ def user_params
:bio,
:email,
:role,
+ :gitpod_enabled,
:hide_no_password,
:hide_no_ssh_key,
:hide_project_limit,
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index b56453d60e981f3455ea7598260a5345abf82553..9245cc1cb1c3d38611bbf46af0b9fc64c40553c4 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -222,6 +222,8 @@ def visible_attributes
:gitaly_timeout_default,
:gitaly_timeout_medium,
:gitaly_timeout_fast,
+ :gitpod_enabled,
+ :gitpod_url,
:grafana_enabled,
:grafana_url,
:gravatar_enabled,
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index ff1f9d6d9fe2b33e03c2f89b0d2c130c8e73bad1..2c406641882e4ce82a5d0367d2f0d889c0f97392 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -80,6 +80,13 @@ def language_choices
)
end
+ def integration_views
+ [].tap do |views|
+ views << 'gitpod' if Gitlab::Gitpod.feature_and_settings_enabled?
+ views << 'sourcegraph' if Gitlab::Sourcegraph.feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled
+ end
+ end
+
private
# Ensure that anyone adding new options updates `DASHBOARD_CHOICES` too
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 90a5b6da4c73319d88db191ea31d59d20aa7bd63..7644ed783eb7b877e1337793e80a50d9d9620bd8 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -191,16 +191,46 @@ def breadcrumb_data_attributes
def vue_file_list_data(project, ref)
{
- can_push_code: current_user&.can?(:push_code, project) && "true",
project_path: project.full_path,
project_short_path: project.path,
- fork_path: current_user&.fork_of(project)&.full_path,
ref: ref,
escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref),
full_name: project.name_with_namespace
}
end
+ def ide_base_path(project)
+ can_push_code = current_user&.can?(:push_code, project)
+ fork_path = current_user&.fork_of(project)&.full_path
+
+ if can_push_code
+ project.full_path
+ else
+ fork_path || project.full_path
+ end
+ end
+
+ def vue_ide_link_data(project, ref)
+ can_collaborate = can_collaborate_with_project?(project)
+ can_create_mr_from_fork = can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
+ show_web_ide_button = (can_collaborate || current_user&.already_forked?(project) || can_create_mr_from_fork)
+
+ {
+ ide_base_path: ide_base_path(project),
+ needs_to_fork: !can_collaborate && !current_user&.already_forked?(project),
+ show_web_ide_button: show_web_ide_button,
+ show_gitpod_button: show_web_ide_button && Gitlab::Gitpod.feature_and_settings_enabled?(project),
+ gitpod_url: full_gitpod_url(project, ref),
+ gitpod_enabled: current_user&.gitpod_enabled
+ }
+ end
+
+ def full_gitpod_url(project, ref)
+ return "" unless Gitlab::Gitpod.feature_and_settings_enabled?(project)
+
+ "#{Gitlab::CurrentSettings.gitpod_url}##{project_tree_url(project, tree_join(ref, @path || ''))}"
+ end
+
def directory_download_links(project, ref, archive_prefix)
Gitlab::Workhorse::ARCHIVE_FORMATS.map do |fmt|
{
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index b660c81488189a1a0bb4347fb54f00d20b6deffa..e9a3dcf39df2d64c2534d9a54e588dc008eaf02a 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -132,6 +132,11 @@ def self.repository_storages_weighted_attributes
presence: true,
if: :sourcegraph_enabled
+ validates :gitpod_url,
+ presence: true,
+ addressable_url: { enforce_sanitization: true },
+ if: :gitpod_enabled
+
validates :snowplow_collector_hostname,
presence: true,
hostname: true,
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 206123bb4704eea7278c525a00ffc1f583ea2c57..7a869d16a317a32ba792f0a7189057b4b2cda196 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -74,6 +74,8 @@ def defaults
gitaly_timeout_default: 55,
gitaly_timeout_fast: 10,
gitaly_timeout_medium: 30,
+ gitpod_enabled: false,
+ gitpod_url: 'https://gitpod.io/',
gravatar_enabled: Settings.gravatar['enabled'],
group_download_export_limit: 1,
group_export_limit: 6,
diff --git a/app/models/user.rb b/app/models/user.rb
index 9b8fe4881adf99fafb81e872fed7d7645d857936..0a784b30d8f0537a24faedbfde857d0224d238bb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -279,6 +279,7 @@ def update_tracked_fields!(request)
:view_diffs_file_by_file, :view_diffs_file_by_file=,
:tab_width, :tab_width=,
:sourcegraph_enabled, :sourcegraph_enabled=,
+ :gitpod_enabled, :gitpod_enabled=,
:setup_for_company, :setup_for_company=,
:render_whitespace_in_code, :render_whitespace_in_code=,
:experience_level, :experience_level=,
diff --git a/app/views/admin/application_settings/_gitpod.html.haml b/app/views/admin/application_settings/_gitpod.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..bbad5155ada7a6f0211782642c7fb9dbadb7377d
--- /dev/null
+++ b/app/views/admin/application_settings/_gitpod.html.haml
@@ -0,0 +1,30 @@
+- return unless Gitlab::Gitpod.feature_available?
+- expanded = integration_expanded?('gitpod_')
+- gitpod_link = link_to("Gitpod#{sprite_icon('external-link', size: 12, css_class: 'ml-1 vertical-align-center')}".html_safe, 'https://gitpod.io/', target: '_blank', rel: 'noopener noreferrer')
+
+%section.settings.no-animate#js-gitpod-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Gitpod')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = s_('Enable %{gitpod_link} integration to launch a development environment in your browser directly from GitLab.').html_safe % { gitpod_link: gitpod_link }
+ = link_to sprite_icon('question-o'), help_page_path('integration/gitpod.md'), target: '_blank', class: 'has-tooltip', title: _('More information')
+
+
+ .settings-content
+ = form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-gitpod-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :gitpod_enabled, class: 'form-check-input'
+ = f.label :gitpod_enabled, s_('Gitpod|Enable Gitpod integration'), class: 'form-check-label'
+ .form-group
+ = f.label :gitpod_url, s_('Gitpod|Gitpod URL'), class: 'label-bold'
+ = f.text_field :gitpod_url, class: 'form-control', placeholder: s_('Gitpod|e.g. https://gitpod.example.com')
+ .form-text.text-muted
+ = s_('Gitpod|Add the URL to your Gitpod instance configured to read your GitLab projects.')
+ = f.submit s_('Save changes'), class: 'btn btn-success'
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index 788dc0b0f1bab7d8315d821248a70b4a544230ad..823cee09d4bbfa012489fa948feb1f10ebe51017 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -117,6 +117,7 @@
#js-maintenance-mode-settings
= render_if_exists 'admin/application_settings/elasticsearch_form'
+= render 'admin/application_settings/gitpod'
= render 'admin/application_settings/plantuml'
= render 'admin/application_settings/sourcegraph'
= render_if_exists 'admin/application_settings/slack'
diff --git a/app/views/profiles/preferences/_gitpod.html.haml b/app/views/profiles/preferences/_gitpod.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..69c9443ebbb08a97a4f9c769de8dfdaf84a83028
--- /dev/null
+++ b/app/views/profiles/preferences/_gitpod.html.haml
@@ -0,0 +1,11 @@
+- gitpod_link = link_to("Gitpod#{sprite_icon('external-link', size: 12, css_class: 'ml-1 vertical-align-center')}".html_safe, 'https://gitpod.io/', target: '_blank', rel: 'noopener noreferrer')
+
+%label.label-bold#gitpod
+ = s_('Gitpod')
+= link_to sprite_icon('question-o'), help_page_path('integration/gitpod.md'), target: '_blank', class: 'has-tooltip', title: _('More information')
+.form-group.form-check
+ = f.check_box :gitpod_enabled, class: 'form-check-input'
+ = f.label :gitpod_enabled, class: 'form-check-label' do
+ = s_('Gitpod|Enable Gitpod integration').html_safe
+ .form-text.text-muted
+ = s_('Enable %{gitpod_link} integration to launch a development environment in your browser directly from GitLab.').html_safe % { gitpod_link: gitpod_link }
diff --git a/app/views/profiles/preferences/_integrations.html.haml b/app/views/profiles/preferences/_integrations.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..037fe5df263ed0983031f7c7fedcf9a5124c3346
--- /dev/null
+++ b/app/views/profiles/preferences/_integrations.html.haml
@@ -0,0 +1,18 @@
+- views = integration_views
+- return unless views.any?
+
+.col-sm-12
+ %hr
+
+.col-lg-4.profile-settings-sidebar#integrations
+ %h4.gl-mt-0
+ = s_('Preferences|Integrations')
+ %p
+ = s_('Preferences|Customize integrations with third party services.')
+ = succeed '.' do
+ = link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'integrations'), target: '_blank'
+
+.col-lg-8
+ - views.each do |view|
+ = render view, f: f
+
diff --git a/app/views/profiles/preferences/_sourcegraph.html.haml b/app/views/profiles/preferences/_sourcegraph.html.haml
index 595b70befcce538ac7c3caa04c766ead8744db91..fdd0be22664957c7d971b6a3a972e2f185973a93 100644
--- a/app/views/profiles/preferences/_sourcegraph.html.haml
+++ b/app/views/profiles/preferences/_sourcegraph.html.haml
@@ -1,26 +1,10 @@
-- return unless Gitlab::Sourcegraph::feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled
-- sourcegraph_url = Gitlab::CurrentSettings.sourcegraph_url
-
-.col-sm-12
- %hr
-
-.col-lg-4.profile-settings-sidebar#integrations
- %h4.gl-mt-0
- = s_('Preferences|Integrations')
- %p
- = s_('Preferences|Customize integrations with third party services.')
- = succeed '.' do
- = link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'integrations'), target: '_blank'
-.col-lg-8
- %label.label-bold
- = s_('Preferences|Sourcegraph')
- = link_to sprite_icon('question-o'), help_page_path('user/profile/preferences.md', anchor: 'sourcegraph'), target: '_blank', class: 'has-tooltip', title: _('More information')
- .form-group.form-check
- = f.check_box :sourcegraph_enabled, class: 'form-check-input'
- = f.label :sourcegraph_enabled, class: 'form-check-label' do
- - link_start = ''.html_safe % { url: sourcegraph_url }
- - link_end = ''.html_safe
- = s_('Preferences|Enable integrated code intelligence on code views').html_safe % { link_start: link_start, link_end: link_end }
- .form-text.text-muted
- = sourcegraph_url_message
- = sourcegraph_experimental_message
+%label.label-bold
+ = s_('Preferences|Sourcegraph')
+= link_to sprite_icon('question-o'), help_page_path('user/profile/preferences.md', anchor: 'sourcegraph'), target: '_blank', class: 'has-tooltip', title: _('More information')
+.form-group.form-check
+ = f.check_box :sourcegraph_enabled, class: 'form-check-input'
+ = f.label :sourcegraph_enabled, class: 'form-check-label' do
+ = s_('Preferences|Enable integrated code intelligence on code views').html_safe
+ .form-text.text-muted
+ = sourcegraph_url_message
+ = sourcegraph_experimental_message
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 472185f0b051d705198bc709cce242d387ba80e9..2c705886f4728d1b65a08d58327ba0639c2540d6 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -138,7 +138,7 @@
.form-text.text-muted
= s_('Preferences|For example: 30 mins ago.')
- = render 'sourcegraph', f: f
+ = render 'integrations', f: f
.col-lg-4.profile-settings-sidebar
.col-lg-8
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index eab6d750a02694e7ae853b787ead1fb1dba5ba02..268858f8ff8946fa8c920e8b8615041ce16b512b 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,5 +1,6 @@
- can_collaborate = can_collaborate_with_project?(@project)
- can_create_mr_from_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
+- can_visit_ide = can_collaborate || current_user&.already_forked?(@project)
.tree-ref-container
.tree-ref-holder
@@ -14,12 +15,12 @@
= render 'projects/find_file_link'
- - if can_collaborate || current_user&.already_forked?(@project)
- #js-tree-web-ide-link.d-inline-block
- - elsif can_create_mr_from_fork
- = link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do
- = _('Web IDE')
- = render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path)
+ - if can_visit_ide || can_create_mr_from_fork
+ #js-tree-web-ide-link.d-inline-block{ data: { options: vue_ide_link_data(@project, @ref).to_json } }
+ - if !can_visit_ide
+ = render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path)
+ - unless current_user&.gitpod_enabled
+ = render 'shared/gitpod/enable_gitpod_modal'
- if show_xcode_link?(@project)
.project-action-button.project-xcode.inline<
diff --git a/app/views/shared/gitpod/_enable_gitpod_modal.html.haml b/app/views/shared/gitpod/_enable_gitpod_modal.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a6bd1d10e433ebc9c6905320a1bcd27d2dd0c6ca
--- /dev/null
+++ b/app/views/shared/gitpod/_enable_gitpod_modal.html.haml
@@ -0,0 +1,12 @@
+#modal-enable-gitpod.modal.qa-enable-gitpod-modal
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %h3.page-title= _('Enable Gitpod?')
+ %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
+ %span{ "aria-hidden": true } ×
+ .modal-body.p-3
+ %p= (_("To use Gitpod you must first enable the feature in the integrations section of your %{user_prefs}.") % { user_prefs: link_to(_('user preferences'), profile_preferences_path(anchor: 'gitpod')) }).html_safe
+ .modal-footer
+ = link_to _('Cancel'), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+ = link_to _('Enable Gitpod'), profile_path(user: { gitpod_enabled: true}), class: 'btn btn-success', method: :put
diff --git a/changelogs/unreleased/37985-add-gitpod-button-to-open-project-in-gitpod.yml b/changelogs/unreleased/37985-add-gitpod-button-to-open-project-in-gitpod.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4d4117cdca747fd198a3f26d620974b9a493bc8a
--- /dev/null
+++ b/changelogs/unreleased/37985-add-gitpod-button-to-open-project-in-gitpod.yml
@@ -0,0 +1,5 @@
+---
+title: Add Gitpod integration
+merge_request: 37985
+author: Cornelius Ludmann @corneliusludmann
+type: added
diff --git a/config/feature_flags/development/gitpod.yml b/config/feature_flags/development/gitpod.yml
new file mode 100644
index 0000000000000000000000000000000000000000..148ea7294ba434c7e01ed1c46cff926938ea1f84
--- /dev/null
+++ b/config/feature_flags/development/gitpod.yml
@@ -0,0 +1,7 @@
+---
+name: gitpod
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37985
+rollout_issue_url:
+group: group::editor
+type: development
+default_enabled: false
\ No newline at end of file
diff --git a/db/migrate/20200811154630_add_gitpod_application_settings.rb b/db/migrate/20200811154630_add_gitpod_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e4211d25d0b8ca98f240165fd97a9d3e4540a0bb
--- /dev/null
+++ b/db/migrate/20200811154630_add_gitpod_application_settings.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddGitpodApplicationSettings < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20200727154631_add_gitpod_application_settings_text_limit
+ def change
+ add_column :application_settings, :gitpod_enabled, :boolean, default: false, null: false
+ add_column :application_settings, :gitpod_url, :text, default: 'https://gitpod.io/', null: true
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20200811154631_add_gitpod_application_settings_text_limit.rb b/db/migrate/20200811154631_add_gitpod_application_settings_text_limit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1f43b5d88d56a8182623b85186e48034bf299572
--- /dev/null
+++ b/db/migrate/20200811154631_add_gitpod_application_settings_text_limit.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddGitpodApplicationSettingsTextLimit < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :application_settings, :gitpod_url, 255
+ end
+
+ def down
+ remove_text_limit :application_settings, :gitpod_url
+ end
+end
diff --git a/db/migrate/20200811154632_add_gitpod_user_preferences.rb b/db/migrate/20200811154632_add_gitpod_user_preferences.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0392c80d39c4488eb8a7246d669aaf9c7ca1cef3
--- /dev/null
+++ b/db/migrate/20200811154632_add_gitpod_user_preferences.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddGitpodUserPreferences < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :user_preferences, :gitpod_enabled, :boolean, default: false, null: false
+ end
+end
diff --git a/db/schema_migrations/20200811154630 b/db/schema_migrations/20200811154630
new file mode 100644
index 0000000000000000000000000000000000000000..0498382ef2eb0dc9277128e2d7ba453a49abab4b
--- /dev/null
+++ b/db/schema_migrations/20200811154630
@@ -0,0 +1 @@
+c04fe7e1a56bdcd41b5e1af346f9bfcae170d601954c4a0bcfcc9aea19d55528
\ No newline at end of file
diff --git a/db/schema_migrations/20200811154631 b/db/schema_migrations/20200811154631
new file mode 100644
index 0000000000000000000000000000000000000000..1817460cd30eed85a3ac92b82aae96617d02a556
--- /dev/null
+++ b/db/schema_migrations/20200811154631
@@ -0,0 +1 @@
+0ce17a8ad6c5ca5bba49ff522fede400fe6666490157af123ad98a7643f3ce01
\ No newline at end of file
diff --git a/db/schema_migrations/20200811154632 b/db/schema_migrations/20200811154632
new file mode 100644
index 0000000000000000000000000000000000000000..bb1167e31826afea4caf928c551ae292f21d0656
--- /dev/null
+++ b/db/schema_migrations/20200811154632
@@ -0,0 +1 @@
+523f200c635e37ee1ac52257ffd45443a3e17bfe993d22775a5377865e044a46
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index ec8147b1caed3156f2aefcf2895cdd72b4fd1723..408e6c253125f60e7ac9a8dce840503af56fae26 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9272,6 +9272,9 @@ CREATE TABLE public.application_settings (
enforce_namespace_storage_limit boolean DEFAULT false NOT NULL,
container_registry_delete_tags_service_timeout integer DEFAULT 250 NOT NULL,
elasticsearch_client_request_timeout integer DEFAULT 0 NOT NULL,
+ gitpod_enabled boolean DEFAULT false NOT NULL,
+ gitpod_url text DEFAULT 'https://gitpod.io/'::text,
+ CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)),
CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)),
CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)),
CONSTRAINT check_d03919528d CHECK ((char_length(container_registry_vendor) <= 255)),
@@ -16255,7 +16258,8 @@ CREATE TABLE public.user_preferences (
tab_width smallint,
feature_filter_type bigint,
experience_level smallint,
- view_diffs_file_by_file boolean DEFAULT false NOT NULL
+ view_diffs_file_by_file boolean DEFAULT false NOT NULL,
+ gitpod_enabled boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE public.user_preferences_id_seq
diff --git a/doc/integration/gitpod.md b/doc/integration/gitpod.md
new file mode 100644
index 0000000000000000000000000000000000000000..f26483e3b5eb8e13df5f1d3d7910697913cc8f96
--- /dev/null
+++ b/doc/integration/gitpod.md
@@ -0,0 +1,74 @@
+---
+type: reference, how-to
+stage: Create
+group: Editor
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
+---
+
+# Gitpod Integration
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228893) in GitLab 13.4.
+> - It's [deployed behind a feature flag](#enable-or-disable-the-gitpod-integration), disabled by default.
+> - It's enabled on GitLab.com.
+> - It's recommended for production use.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#configure-your-gitlab-instance-with-gitpod). **(CORE ONLY)**
+
+CAUTION: **Warning:**
+This feature might not be available to you. Check the **version history** note above for details.
+
+With [Gitpod](https://gitpod.io/) you can describe your dev environment as code to get fully set
+up, compiled, and tested dev environments for any GitLab project. The dev environments are not only
+automated but also prebuilt which means that Gitpod continuously builds your Git branches like a CI
+server. By that you don’t have to wait for dependencies to be downloaded and builds to finish, but
+you can start coding immediately.
+
+In short: With Gitpod you can start coding instantly on any project, branch, and merge request from
+any device, at any time.
+
+
+
+You can launch Gitpod directly from GitLab by clicking the **Gitpod** button from the **Web IDE**
+dropdown on the project page:
+
+
+
+To learn more about Gitpod, see their [features](https://www.gitpod.io/features/) and
+[documentation](https://www.gitpod.io/docs/).
+
+To use the GitLab-Gitpod integration, you need to enable it from your user preferences:
+
+1. From the GitLab UI, click your avatar in the top-right corner, then click **Settings**.
+1. On the left-hand nav, click **Preferences**.
+1. Under **Integrations**, find the **Gitpod** section.
+1. Check **Enable Gitpod**.
+
+Users of GitLab.com can enable it and start using straightaway. Users of GitLab self-managed instances
+can follow the same steps once the integration has been enabled and configured by a GitLab administrator.
+
+## Configure your GitLab instance with Gitpod **(CORE ONLY)**
+
+If you are new to Gitpod, head over to the [Gitpod documentation](https://www.gitpod.io/docs/self-hosted/latest/self-hosted/)
+and get your instance up and running.
+
+1. In GitLab, go to **Admin Area > Settings > Integrations**.
+1. Expand the **Gitpod** configuration section.
+1. Check **Enable Gitpod**.
+1. Add your Gitpod instance URL (for example, `https://gitpod.example.com`).
+
+## Enable or disable the Gitpod integration **(CORE ONLY)**
+
+The Gitpod integration is under development and not ready for production use. It is deployed behind a
+feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
+can enable it.
+
+To enable it:
+
+```ruby
+Feature.enable(:gitpod)
+```
+
+To disable it:
+
+```ruby
+Feature.disable(:gitpod)
diff --git a/doc/integration/img/gitpod_button_project_page_v13_4.png b/doc/integration/img/gitpod_button_project_page_v13_4.png
new file mode 100644
index 0000000000000000000000000000000000000000..55a70d891699d0377e8f0a0f4d2d86a16723cbaa
Binary files /dev/null and b/doc/integration/img/gitpod_button_project_page_v13_4.png differ
diff --git a/doc/integration/img/gitpod_web_interface_v13_4.png b/doc/integration/img/gitpod_web_interface_v13_4.png
new file mode 100644
index 0000000000000000000000000000000000000000..5cd9a6aad0fdf35b4f68bb9529d40f417872adba
Binary files /dev/null and b/doc/integration/img/gitpod_web_interface_v13_4.png differ
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index b94ae958d3b8c7f38f5013e85123258e11b91bd2..caf0775a7464d4e1b6653f66a42abf736debd8ac 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -182,6 +182,12 @@ Manage the availability of integrated code intelligence features powered by
Sourcegraph. View [the Sourcegraph feature documentation](../../integration/sourcegraph.md#enable-sourcegraph-in-user-preferences)
for more information.
+### Gitpod
+
+Enable and disable the [GitLab-Gitpod integration](../../integration/gitpod.md). This is only
+visible after the integration is configured by a GitLab administrator. View
+[the Gitpod feature documentation](../../integration/gitpod.md) for more information.
+