diff options
Diffstat (limited to 'app/assets')
12 files changed, 267 insertions, 26 deletions
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 @@ +<script> +import { GlButton, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui'; +import { s__, __ } from '~/locale'; +import { createProject } from '~/rest_api'; +import { createAlert } from '~/alert'; +import { openWebIDE } from '~/lib/utils/web_ide_navigator'; +import { README_MODAL_ID, GITLAB_README_PROJECT, README_FILE } from '../constants'; + +export default { + name: 'GroupSettingsReadme', + i18n: { + readme: __('README'), + addReadme: __('Add README'), + cancel: __('Cancel'), + createProjectAndReadme: s__('Groups|Create and add README'), + creatingReadme: s__('Groups|Creating README'), + existingProjectNewReadme: s__('Groups|This will create a README.md for project %{path}.'), + newProjectAndReadme: s__('Groups|This will create a project %{path} and add a README.md.'), + errorCreatingProject: s__('Groups|There was an error creating the Group README.'), + }, + components: { + GlButton, + GlModal, + GlSprintf, + }, + directives: { + GlModal: GlModalDirective, + }, + props: { + groupReadmePath: { + type: String, + required: false, + default: '', + }, + readmeProjectPath: { + type: String, + required: false, + default: '', + }, + groupPath: { + type: String, + required: true, + }, + groupId: { + type: String, + required: true, + }, + }, + data() { + return { + creatingReadme: false, + }; + }, + computed: { + hasReadme() { + return this.groupReadmePath.length > 0; + }, + hasReadmeProject() { + return this.readmeProjectPath.length > 0; + }, + pathToReadmeProject() { + return this.hasReadmeProject + ? this.readmeProjectPath + : `${this.groupPath}/${GITLAB_README_PROJECT}`; + }, + modalBody() { + return this.hasReadmeProject + ? this.$options.i18n.existingProjectNewReadme + : this.$options.i18n.newProjectAndReadme; + }, + modalSubmitButtonText() { + return this.hasReadmeProject + ? this.$options.i18n.addReadme + : this.$options.i18n.createProjectAndReadme; + }, + }, + methods: { + hideModal() { + this.$refs.modal.hide(); + }, + createReadme() { + if (this.hasReadmeProject) { + openWebIDE(this.readmeProjectPath, README_FILE); + } else { + this.createProjectWithReadme(); + } + }, + createProjectWithReadme() { + this.creatingReadme = true; + + const projectData = { + name: GITLAB_README_PROJECT, + namespace_id: this.groupId, + }; + + createProject(projectData) + .then(({ path_with_namespace: pathWithNamespace }) => { + openWebIDE(pathWithNamespace, README_FILE); + }) + .catch(() => { + this.hideModal(); + this.creatingReadme = false; + createAlert({ message: this.$options.i18n.errorCreatingProject }); + }); + }, + }, + README_MODAL_ID, +}; +</script> + +<template> + <div> + <gl-button v-if="hasReadme" icon="doc-text" :href="groupReadmePath">{{ + $options.i18n.readme + }}</gl-button> + <gl-button + v-else + v-gl-modal="$options.README_MODAL_ID" + variant="dashed" + icon="file-addition" + data-testid="group-settings-add-readme-button" + >{{ $options.i18n.addReadme }}</gl-button + > + <gl-modal ref="modal" :modal-id="$options.README_MODAL_ID" :title="$options.i18n.addReadme"> + <div data-testid="group-settings-modal-readme-body"> + <gl-sprintf :message="modalBody"> + <template #path> + <code>{{ pathToReadmeProject }}</code> + </template> + </gl-sprintf> + </div> + <template #modal-footer> + <gl-button variant="default" @click="hideModal">{{ $options.i18n.cancel }}</gl-button> + <gl-button v-if="creatingReadme" variant="default" loading disabled>{{ + $options.i18n.creatingReadme + }}</gl-button> + <gl-button + v-else + variant="confirm" + data-testid="group-settings-modal-create-readme-button" + @click="createReadme" + >{{ modalSubmitButtonText }}</gl-button + > + </template> + </gl-modal> + </div> +</template> 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 }} </div> - <gl-button - v-if="isSyncButtonAvailable" - :disabled="forkDetails.isSyncing" - @click="checkIfSyncIsPossible" - > - <gl-loading-icon v-if="forkDetails.isSyncing" class="gl-display-inline" size="sm" /> - <span>{{ $options.i18n.sync }}</span> - </gl-button> + <div class="gl-display-flex gl-xs-display-none!"> + <gl-button + v-if="hasCreateMrButton" + class="gl-ml-4" + :href="createMrPath" + data-testid="create-mr-button" + > + <span>{{ $options.i18n.createMergeRequest }}</span> + </gl-button> + <gl-button + v-if="hasUpdateButton" + class="gl-ml-4" + :disabled="forkDetails.isSyncing" + data-testid="update-fork-button" + @click="checkIfSyncIsPossible" + > + <gl-loading-icon v-if="forkDetails.isSyncing" class="gl-display-inline" size="sm" /> + <span>{{ $options.i18n.updateFork }}</span> + </gl-button> + </div> <conflicts-modal ref="modal" :source-name="sourceName" diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 8dc67b97a60..b1217881bc3 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -74,8 +74,10 @@ export default function setupVueRepositoryList() { sourceName, sourcePath, sourceDefaultBranch, + createMrPath, aheadComparePath, behindComparePath, + canUserCreateMrInFork, } = forkEl.dataset; return new Vue({ el: forkEl, @@ -90,6 +92,8 @@ export default function setupVueRepositoryList() { sourceDefaultBranch, aheadComparePath, behindComparePath, + createMrPath, + canUserCreateMrInFork, }, }); }, @@ -153,8 +157,8 @@ export default function setupVueRepositoryList() { initLastCommitApp(); initBlobControlsApp(); - initForkInfo(); initRefSwitcher(); + initForkInfo(); router.afterEach(({ params: { path } }) => { 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 { </script> <template> - <gl-disclosure-dropdown :items="items" placement="center"> + <gl-disclosure-dropdown + :items="items" + placement="center" + @shown="$emit('shown')" + @hidden="$emit('hidden')" + > <template #toggle> <slot></slot> </template> diff --git a/app/assets/javascripts/super_sidebar/components/user_bar.vue b/app/assets/javascripts/super_sidebar/components/user_bar.vue index e03c587567e..498c082ddb2 100644 --- a/app/assets/javascripts/super_sidebar/components/user_bar.vue +++ b/app/assets/javascripts/super_sidebar/components/user_bar.vue @@ -56,6 +56,11 @@ export default { required: true, }, }, + data() { + return { + mrMenuShown: false, + }; + }, methods: { collapseSidebar() { toggleSuperSidebarCollapsed(true, true, true); @@ -144,9 +149,11 @@ export default { <merge-request-menu class="gl-flex-basis-third gl-display-block!" :items="sidebarData.merge_request_menu" + @shown="mrMenuShown = true" + @hidden="mrMenuShown = false" > <counter - v-gl-tooltip:super-sidebar.hover.bottom="$options.i18n.mergeRequests" + v-gl-tooltip:super-sidebar.hover.bottom="mrMenuShown ? '' : $options.i18n.mergeRequests" class="gl-w-full" icon="merge-request-open" :count="sidebarData.total_merge_requests_count" diff --git a/app/assets/stylesheets/components/whats_new.scss b/app/assets/stylesheets/components/whats_new.scss index c1c68f64d86..35c619a2e2f 100644 --- a/app/assets/stylesheets/components/whats_new.scss +++ b/app/assets/stylesheets/components/whats_new.scss @@ -1,5 +1,5 @@ .whats-new-drawer { - margin-top: $header-height; + margin-top: calc(#{$header-height} + #{$calc-application-bars-height}); @include gl-shadow-none; overflow-y: hidden; width: 500px; @@ -35,18 +35,6 @@ } } -.with-performance-bar .whats-new-drawer { - margin-top: calc(#{$performance-bar-height} + #{$header-height}); -} - -.with-system-header .whats-new-drawer { - margin-top: calc(#{$system-header-height} + #{$header-height}); -} - -.with-performance-bar.with-system-header .whats-new-drawer { - margin-top: calc(#{$performance-bar-height} + #{$system-header-height} + #{$header-height}); -} - .whats-new-item-title-link { &:hover, &:focus, |