diff --git a/Gemfile b/Gemfile index e0ba0f8cccf..55d915dbad7 100644 --- a/Gemfile +++ b/Gemfile @@ -456,7 +456,7 @@ group :test do gem 'rspec-benchmark', '~> 0.6.0' gem 'rspec-parameterized', '~> 1.0', require: false - gem 'capybara', '~> 3.35.3' + gem 'capybara', '~> 3.39' gem 'capybara-screenshot', '~> 1.0.22' gem 'selenium-webdriver', '~> 3.142' diff --git a/Gemfile.checksum b/Gemfile.checksum index 5bf69f044d0..f3399786244 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -67,7 +67,7 @@ {"name":"bullet","version":"7.0.2","platform":"ruby","checksum":"4b7986b366f694bb05d5c1b4ea8ba949a99224d4511bf02f0c3944112f719c81"}, {"name":"bundler-audit","version":"0.7.0.1","platform":"ruby","checksum":"12d853cb0b92fa8868abbb539414d7a33da9e48b792e2ff28271d36c8ace8912"}, {"name":"byebug","version":"11.1.3","platform":"ruby","checksum":"2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b"}, -{"name":"capybara","version":"3.35.3","platform":"ruby","checksum":"3389f8203b05175352b763f4d04c31b29ba606a96224649ac42ef967f56538ee"}, +{"name":"capybara","version":"3.39.0","platform":"ruby","checksum":"a30994beb4b4f318e39e3dc81e73203bd1edf1f9ef237d82b708eb1c21b56419"}, {"name":"capybara-screenshot","version":"1.0.22","platform":"ruby","checksum":"f86040349a0df7f723123460d9456023f7d693068338991529f10f670fa420f5"}, {"name":"carrierwave","version":"1.3.3","platform":"ruby","checksum":"0f0244de2ece54c80745b755993bd26cf47d4650823e5f89c115dbc9d73a13f1"}, {"name":"cbor","version":"0.5.9.6","platform":"ruby","checksum":"434a147658dd1df24ec9e7b3297c1fd4f8a691c97d0e688b3049df8e728b2114"}, @@ -345,6 +345,7 @@ {"name":"mail","version":"2.8.1","platform":"ruby","checksum":"ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad"}, {"name":"marcel","version":"1.0.2","platform":"ruby","checksum":"a013b677ef46cbcb49fd5c59b3d35803d2ee04dd75d8bfdc43533fc5a31f7e4e"}, {"name":"marginalia","version":"1.11.1","platform":"ruby","checksum":"cb63212ab63e42746e27595e912cb20408a1a28bcd0edde55d15b7c45fa289cf"}, +{"name":"matrix","version":"0.4.2","platform":"ruby","checksum":"71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0"}, {"name":"memoist","version":"0.16.2","platform":"ruby","checksum":"a52c53a3f25b5875151670b2f3fd44388633486dc0f09f9a7150ead1e3bf3c45"}, {"name":"memory_profiler","version":"1.0.1","platform":"ruby","checksum":"38cdb42f22d9100df2eba0365c199724b58b05c38e765cd764a07392916901b1"}, {"name":"method_source","version":"1.0.0","platform":"ruby","checksum":"d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede"}, diff --git a/Gemfile.lock b/Gemfile.lock index 5d10f5f4655..d953709bde7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -274,8 +274,9 @@ GEM bundler (>= 1.2.0, < 3) thor (>= 0.18, < 2) byebug (11.1.3) - capybara (3.35.3) + capybara (3.39.0) addressable + matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) @@ -946,6 +947,7 @@ GEM marginalia (1.11.1) actionpack (>= 5.2) activerecord (>= 5.2) + matrix (0.4.2) memoist (0.16.2) memory_profiler (1.0.1) method_source (1.0.0) @@ -1684,7 +1686,7 @@ DEPENDENCIES bullet (~> 7.0.2) bundler-audit (~> 0.7.0.1) bundler-checksum (~> 0.1.0)! - capybara (~> 3.35.3) + capybara (~> 3.39) capybara-screenshot (~> 1.0.22) carrierwave (~> 1.3) charlock_holmes (~> 0.7.7) diff --git a/app/assets/javascripts/api/projects_api.js b/app/assets/javascripts/api/projects_api.js index 5a794dcd035..c72a913aacd 100644 --- a/app/assets/javascripts/api/projects_api.js +++ b/app/assets/javascripts/api/projects_api.js @@ -35,6 +35,13 @@ export function getProjects(query, options, callback = () => {}) { }); } +export function createProject(projectData) { + const url = buildApiUrl(PROJECTS_PATH); + return axios.post(url, projectData).then(({ data }) => { + return data; + }); +} + export function importProjectMembers(sourceId, targetId) { const url = buildApiUrl(PROJECT_IMPORT_MEMBERS_PATH) .replace(':id', sourceId) diff --git a/app/assets/javascripts/groups/settings/components/group_settings_readme.vue b/app/assets/javascripts/groups/settings/components/group_settings_readme.vue new file mode 100644 index 00000000000..123c7fc58f5 --- /dev/null +++ b/app/assets/javascripts/groups/settings/components/group_settings_readme.vue @@ -0,0 +1,147 @@ + + + diff --git a/app/assets/javascripts/groups/settings/constants.js b/app/assets/javascripts/groups/settings/constants.js index c91c2a20529..023ddf29b36 100644 --- a/app/assets/javascripts/groups/settings/constants.js +++ b/app/assets/javascripts/groups/settings/constants.js @@ -1,3 +1,7 @@ export const LEVEL_TYPES = { GROUP: 'group', }; + +export const README_MODAL_ID = 'add_group_readme_modal'; +export const GITLAB_README_PROJECT = 'gitlab-profile'; +export const README_FILE = 'README.md'; diff --git a/app/assets/javascripts/groups/settings/init_group_settings_readme.js b/app/assets/javascripts/groups/settings/init_group_settings_readme.js new file mode 100644 index 00000000000..d126228d854 --- /dev/null +++ b/app/assets/javascripts/groups/settings/init_group_settings_readme.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import GroupSettingsReadme from './components/group_settings_readme.vue'; + +export const initGroupSettingsReadme = () => { + const el = document.getElementById('js-group-settings-readme'); + + if (!el) return false; + + const { groupReadmePath, readmeProjectPath, groupPath, groupId } = el.dataset; + + return new Vue({ + el, + render(createElement) { + return createElement(GroupSettingsReadme, { + props: { + groupReadmePath, + readmeProjectPath, + groupPath, + groupId, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/lib/utils/web_ide_navigator.js b/app/assets/javascripts/lib/utils/web_ide_navigator.js new file mode 100644 index 00000000000..f0579b5886d --- /dev/null +++ b/app/assets/javascripts/lib/utils/web_ide_navigator.js @@ -0,0 +1,24 @@ +import { visitUrl, webIDEUrl } from '~/lib/utils/url_utility'; + +/** + * Takes a project path and optional file path and branch + * and then redirects the user to the web IDE. + * + * @param {string} projectPath - Full path to project including namespace (ex. flightjs/Flight) + * @param {string} filePath - optional path to file to be edited, otherwise will open at base directory (ex. README.md) + * @param {string} branch - optional branch to open the IDE, defaults to 'main' + */ + +export const openWebIDE = (projectPath, filePath, branch = 'main') => { + if (!projectPath) { + throw new TypeError('projectPath parameter is required'); + } + + const pathnameSegments = [projectPath, 'edit', branch, '-']; + + if (filePath) { + pathnameSegments.push(filePath); + } + + visitUrl(webIDEUrl(`/${pathnameSegments.join('/')}/`)); +}; diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js index dec06fe6f4d..721168f6140 100644 --- a/app/assets/javascripts/pages/groups/edit/index.js +++ b/app/assets/javascripts/pages/groups/edit/index.js @@ -9,6 +9,7 @@ import mountBadgeSettings from '~/pages/shared/mount_badge_settings'; import initSearchSettings from '~/search_settings'; import initSettingsPanels from '~/settings_panels'; import initConfirmDanger from '~/init_confirm_danger'; +import { initGroupSettingsReadme } from '~/groups/settings/init_group_settings_readme'; initFilePickers(); initConfirmDanger(); @@ -27,3 +28,5 @@ initProjectSelects(); initSearchSettings(); initCascadingSettingsLockPopovers(); + +initGroupSettingsReadme(); diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index dd39fb7c666..2f8c2a8e86f 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -102,6 +102,7 @@ const initForkInfo = () => { sourceDefaultBranch, aheadComparePath, behindComparePath, + canUserCreateMrInFork, } = forkEl.dataset; return new Vue({ el: forkEl, @@ -116,6 +117,7 @@ const initForkInfo = () => { sourceDefaultBranch, aheadComparePath, behindComparePath, + canUserCreateMrInFork, }, }); }, diff --git a/app/assets/javascripts/repository/components/fork_info.vue b/app/assets/javascripts/repository/components/fork_info.vue index d84e197714e..a7795c8da0a 100644 --- a/app/assets/javascripts/repository/components/fork_info.vue +++ b/app/assets/javascripts/repository/components/fork_info.vue @@ -24,7 +24,8 @@ export const i18n = { behindAhead: s__('ForksDivergence|%{messages} the upstream repository.'), limitedVisibility: s__('ForksDivergence|Source project has a limited visibility.'), error: s__('ForksDivergence|Failed to fetch fork details. Try again later.'), - sync: s__('ForksDivergence|Update fork'), + updateFork: s__('ForksDivergence|Update fork'), + createMergeRequest: s__('ForksDivergence|Create merge request'), }; export default { @@ -103,6 +104,16 @@ export default { required: false, default: '', }, + createMrPath: { + type: String, + required: false, + default: '', + }, + canUserCreateMrInFork: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -173,12 +184,15 @@ export default { hasBehindAheadMessage() { return this.behindAheadMessage.length > 0; }, - isSyncButtonAvailable() { + hasUpdateButton() { return ( this.glFeatures.synchronizeFork && ((this.sourceName && this.forkDetails && this.behind) || this.isUnknownDivergence) ); }, + hasCreateMrButton() { + return this.canUserCreateMrInFork && this.ahead && this.createMrPath; + }, forkDivergenceMessage() { if (!this.forkDetails) { return this.$options.i18n.limitedVisibility; @@ -286,14 +300,26 @@ export default { > {{ $options.i18n.inaccessibleProject }} - - - {{ $options.i18n.sync }} - +
+ + {{ $options.i18n.createMergeRequest }} + + + + {{ $options.i18n.updateFork }} + +
{ setTitle(path, ref, fullName); diff --git a/app/assets/javascripts/super_sidebar/components/merge_request_menu.vue b/app/assets/javascripts/super_sidebar/components/merge_request_menu.vue index 94fc6aedcc0..d37e863bed9 100644 --- a/app/assets/javascripts/super_sidebar/components/merge_request_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/merge_request_menu.vue @@ -16,7 +16,12 @@ export default {