diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/ide | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) |
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'app/assets/javascripts/ide')
23 files changed, 208 insertions, 105 deletions
diff --git a/app/assets/javascripts/ide/components/branches/item.vue b/app/assets/javascripts/ide/components/branches/item.vue index 2fe435b92ab..35e2f99cb6a 100644 --- a/app/assets/javascripts/ide/components/branches/item.vue +++ b/app/assets/javascripts/ide/components/branches/item.vue @@ -34,7 +34,7 @@ export default { <template> <a :href="branchHref" class="btn-link d-flex align-items-center"> <span class="d-flex gl-mr-3 ide-search-list-current-icon"> - <gl-icon v-if="isActive" :size="18" name="mobile-issue-close" /> + <gl-icon v-if="isActive" :size="18" name="mobile-issue-close" use-deprecated-sizes /> </span> <span> <strong> {{ item.name }} </strong> diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue index 1ae7cf9339d..5e93b7c1bbb 100644 --- a/app/assets/javascripts/ide/components/branches/search_list.vue +++ b/app/assets/javascripts/ide/components/branches/search_list.vue @@ -57,7 +57,10 @@ export default { <template> <div> - <label class="dropdown-input pt-3 pb-3 mb-0 border-bottom block position-relative" @click.stop> + <label + class="dropdown-input gl-pt-3 gl-pb-5 gl-mb-0 gl-border-b-1 gl-border-b-solid gl-display-block" + @click.stop + > <input ref="searchInput" v-model="search" @@ -66,7 +69,7 @@ export default { class="form-control dropdown-input-field" @input="searchBranches" /> - <gl-icon :size="18" name="search" class="ml-3 input-icon" /> + <gl-icon :size="18" name="search" class="ml-3 input-icon" use-deprecated-sizes /> </label> <div class="dropdown-content ide-merge-requests-dropdown-content d-flex"> <gl-loading-icon diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue index cdb3eaff207..2897f4cbf77 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue @@ -1,17 +1,13 @@ <script> import { GlModal, GlSafeHtmlDirective, GlButton, GlTooltipDirective } from '@gitlab/ui'; import { mapState, mapActions, mapGetters } from 'vuex'; -import { n__, s__ } from '~/locale'; +import { n__ } from '~/locale'; import { leftSidebarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants'; import { createUnexpectedCommitError } from '../../lib/errors'; import Actions from './actions.vue'; import CommitMessageField from './message_field.vue'; import SuccessMessage from './success_message.vue'; -const MSG_CANNOT_PUSH_CODE = s__( - 'WebIDE|You need permission to edit files directly in this project.', -); - export default { components: { Actions, @@ -35,14 +31,14 @@ export default { computed: { ...mapState(['changedFiles', 'stagedFiles', 'currentActivityView', 'lastCommitMsg']), ...mapState('commit', ['commitMessage', 'submitCommitLoading', 'commitError']), - ...mapGetters(['someUncommittedChanges', 'canPushCode']), + ...mapGetters(['someUncommittedChanges', 'canPushCodeStatus']), ...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']), commitButtonDisabled() { - return !this.canPushCode || !this.someUncommittedChanges; + return !this.canPushCodeStatus.isAllowed || !this.someUncommittedChanges; }, commitButtonTooltip() { - if (!this.canPushCode) { - return MSG_CANNOT_PUSH_CODE; + if (!this.canPushCodeStatus.isAllowed) { + return this.canPushCodeStatus.messageShort; } return ''; @@ -86,7 +82,7 @@ export default { commit() { // Even though the submit button will be disabled, we need to disable the submission // since hitting enter on the branch name text input also submits the form. - if (!this.canPushCode) { + if (!this.canPushCodeStatus.isAllowed) { return false; } @@ -130,8 +126,6 @@ export default { this.componentHeight = null; }, }, - // Expose for tests - MSG_CANNOT_PUSH_CODE, }; </script> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue index 121dae4b87e..43bf2e1a90c 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue @@ -41,7 +41,6 @@ export default { :disabled="shouldDisableNewMrOption" :checked="shouldCreateMR" type="checkbox" - data-qa-selector="start_new_mr_checkbox" @change="toggleShouldCreateMR" /> <span class="gl-ml-3 ide-option-label"> diff --git a/app/assets/javascripts/ide/components/file_templates/bar.vue b/app/assets/javascripts/ide/components/file_templates/bar.vue index bd4c4f18141..0803925104d 100644 --- a/app/assets/javascripts/ide/components/file_templates/bar.vue +++ b/app/assets/javascripts/ide/components/file_templates/bar.vue @@ -49,7 +49,9 @@ export default { </script> <template> - <div class="d-flex align-items-center ide-file-templates qa-file-templates-bar"> + <div + class="d-flex align-items-center ide-file-templates qa-file-templates-bar gl-relative gl-z-index-1" + > <strong class="gl-mr-3"> {{ __('File templates') }} </strong> <dropdown :data="templateTypes" diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 2816f89d60d..ff2644704d9 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -1,7 +1,7 @@ <script> import { GlAlert, GlButton, GlLoadingIcon } from '@gitlab/ui'; import { mapActions, mapGetters, mapState } from 'vuex'; -import { __, s__ } from '~/locale'; +import { __ } from '~/locale'; import { WEBIDE_MARK_APP_START, WEBIDE_MARK_FILE_FINISH, @@ -25,10 +25,6 @@ eventHub.$on(WEBIDE_MEASURE_FILE_AFTER_INTERACTION, () => ), ); -const MSG_CANNOT_PUSH_CODE = s__( - 'WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request.', -); - export default { components: { IdeSidebar, @@ -63,7 +59,7 @@ export default { 'loading', ]), ...mapGetters([ - 'canPushCode', + 'canPushCodeStatus', 'activeFile', 'someUncommittedChanges', 'isCommitModeActive', @@ -116,7 +112,6 @@ export default { this.loadDeferred = true; }, }, - MSG_CANNOT_PUSH_CODE, }; </script> @@ -125,8 +120,8 @@ export default { class="ide position-relative d-flex flex-column align-items-stretch" :class="{ [`theme-${themeName}`]: themeName }" > - <gl-alert v-if="!canPushCode" :dismissible="false">{{ - $options.MSG_CANNOT_PUSH_CODE + <gl-alert v-if="!canPushCodeStatus.isAllowed" :dismissible="false">{{ + canPushCodeStatus.message }}</gl-alert> <error-message v-if="errorMessage" :message="errorMessage" /> <div class="ide-view flex-grow d-flex"> diff --git a/app/assets/javascripts/ide/components/merge_requests/item.vue b/app/assets/javascripts/ide/components/merge_requests/item.vue index 7aa9a4f864a..639937481f3 100644 --- a/app/assets/javascripts/ide/components/merge_requests/item.vue +++ b/app/assets/javascripts/ide/components/merge_requests/item.vue @@ -41,7 +41,7 @@ export default { <template> <a :href="mergeRequestHref" class="btn-link d-flex align-items-center"> <span class="d-flex gl-mr-3 ide-search-list-current-icon"> - <gl-icon v-if="isActive" :size="18" name="mobile-issue-close" /> + <gl-icon v-if="isActive" :size="18" name="mobile-issue-close" use-deprecated-sizes /> </span> <span> <strong> {{ item.title }} </strong> diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue index 680e8841a1f..f7cfe80df5c 100644 --- a/app/assets/javascripts/ide/components/merge_requests/list.vue +++ b/app/assets/javascripts/ide/components/merge_requests/list.vue @@ -75,7 +75,10 @@ export default { <template> <div> - <label class="dropdown-input pt-3 pb-3 mb-0 border-bottom block" @click.stop> + <label + class="dropdown-input gl-pt-3 gl-pb-5 gl-mb-0 gl-border-b-1 gl-border-b-solid gl-display-block" + @click.stop + > <tokened-input v-model="search" :tokens="searchTokens" @@ -84,7 +87,7 @@ export default { @input="searchMergeRequests" @removeToken="setSearchType(null)" /> - <gl-icon :size="18" name="search" class="ml-3 input-icon" /> + <gl-icon :size="18" name="search" class="ml-3 input-icon" use-deprecated-sizes /> </label> <div class="dropdown-content ide-merge-requests-dropdown-content d-flex"> <gl-loading-icon @@ -102,7 +105,7 @@ export default { @click.stop="setSearchType(searchType)" > <span class="d-flex gl-mr-3 ide-search-list-current-icon"> - <gl-icon :size="18" name="search" /> + <gl-icon :size="18" name="search" use-deprecated-sizes /> </span> <span>{{ searchType.label }}</span> </button> diff --git a/app/assets/javascripts/ide/components/nav_form.vue b/app/assets/javascripts/ide/components/nav_form.vue index 62bb4841760..98f0504298b 100644 --- a/app/assets/javascripts/ide/components/nav_form.vue +++ b/app/assets/javascripts/ide/components/nav_form.vue @@ -1,13 +1,12 @@ <script> -import Tab from '~/vue_shared/components/tabs/tab.vue'; -import Tabs from '~/vue_shared/components/tabs/tabs'; +import { GlTab, GlTabs } from '@gitlab/ui'; import BranchesSearchList from './branches/search_list.vue'; import MergeRequestSearchList from './merge_requests/list.vue'; export default { components: { - Tabs, - Tab, + GlTab, + GlTabs, BranchesSearchList, MergeRequestSearchList, }, @@ -23,20 +22,14 @@ export default { <template> <div class="ide-nav-form p-0"> - <tabs v-if="showMergeRequests" stop-propagation> - <tab active> - <template #title> - {{ __('Branches') }} - </template> + <gl-tabs v-if="showMergeRequests"> + <gl-tab :title="__('Branches')"> <branches-search-list /> - </tab> - <tab> - <template #title> - {{ __('Merge Requests') }} - </template> + </gl-tab> + <gl-tab :title="__('Merge Requests')"> <merge-request-search-list /> - </tab> - </tabs> + </gl-tab> + </gl-tabs> <branches-search-list v-else /> </div> </template> diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index 2526db0cd7b..907ac496982 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -32,7 +32,7 @@ export default { SafeHtml, }, computed: { - ...mapState(['pipelinesEmptyStateSvgPath', 'links']), + ...mapState(['pipelinesEmptyStateSvgPath']), ...mapGetters(['currentProject']), ...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages', 'pipelineFailed']), ...mapState('pipelines', [ @@ -85,7 +85,6 @@ export default { </header> <empty-state v-if="!latestPipeline" - :help-page-path="links.ciHelpPagePath" :empty-state-svg-path="pipelinesEmptyStateSvgPath" :can-set-ci="true" class="mb-auto mt-auto" diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue index 0c6cb041095..4d35e946d89 100644 --- a/app/assets/javascripts/ide/components/preview/navigator.vue +++ b/app/assets/javascripts/ide/components/preview/navigator.vue @@ -117,7 +117,7 @@ export default { class="ide-navigator-btn d-flex align-items-center d-transparent border-0 bg-transparent" @click="refresh" > - <gl-icon :size="18" name="retry" class="m-auto" /> + <gl-icon :size="18" name="retry" use-deprecated-sizes class="m-auto" /> </button> <div class="position-relative w-100 gl-ml-2"> <input diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 690060f5cb0..b57dcd4276c 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -1,6 +1,15 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; +import { + EDITOR_TYPE_DIFF, + EDITOR_CODE_INSTANCE_FN, + EDITOR_DIFF_INSTANCE_FN, +} from '~/editor/constants'; +import EditorLite from '~/editor/editor_lite'; +import { EditorWebIdeExtension } from '~/editor/extensions/editor_lite_webide_ext'; import { deprecatedCreateFlash as flash } from '~/flash'; +import ModelManager from '~/ide/lib/common/model_manager'; +import { defaultDiffEditorOptions, defaultEditorOptions } from '~/ide/lib/editor_options'; import { __ } from '~/locale'; import { WEBIDE_MARK_FILE_CLICKED, @@ -20,7 +29,6 @@ import { FILE_VIEW_MODE_PREVIEW, } from '../constants'; import eventHub from '../eventhub'; -import Editor from '../lib/editor'; import { getRulesWithTraversal } from '../lib/editorconfig/parser'; import mapRulesToMonaco from '../lib/editorconfig/rules_mapper'; import { getFileEditorOrDefault } from '../stores/modules/editor/utils'; @@ -46,6 +54,9 @@ export default { content: '', images: {}, rules: {}, + globalEditor: null, + modelManager: new ModelManager(), + isEditorLoading: true, }; }, computed: { @@ -132,6 +143,7 @@ export default { // Compare key to allow for files opened in review mode to be cached differently if (oldVal.key !== this.file.key) { + this.isEditorLoading = true; this.initEditor(); if (this.currentActivityView !== leftSidebarViews.edit.name) { @@ -149,6 +161,7 @@ export default { } }, viewer() { + this.isEditorLoading = false; if (!this.file.pending) { this.createEditorInstance(); } @@ -181,11 +194,11 @@ export default { }, }, beforeDestroy() { - this.editor.dispose(); + this.globalEditor.dispose(); }, mounted() { - if (!this.editor) { - this.editor = Editor.create(this.$store, this.editorOptions); + if (!this.globalEditor) { + this.globalEditor = new EditorLite(); } this.initEditor(); @@ -211,8 +224,6 @@ export default { return; } - this.editor.clearEditor(); - this.registerSchemaForFile(); Promise.all([this.fetchFileData(), this.fetchEditorconfigRules()]) @@ -251,20 +262,45 @@ export default { return; } - this.editor.dispose(); + const isDiff = this.viewer !== viewerTypes.edit; + const shouldDisposeEditor = isDiff !== (this.editor?.getEditorType() === EDITOR_TYPE_DIFF); - this.$nextTick(() => { - if (this.viewer === viewerTypes.edit) { - this.editor.createInstance(this.$refs.editor); - } else { - this.editor.createDiffInstance(this.$refs.editor); + if (this.editor && !shouldDisposeEditor) { + this.setupEditor(); + } else { + if (this.editor && shouldDisposeEditor) { + this.editor.dispose(); } + const instanceOptions = isDiff ? defaultDiffEditorOptions : defaultEditorOptions; + const method = isDiff ? EDITOR_DIFF_INSTANCE_FN : EDITOR_CODE_INSTANCE_FN; - this.setupEditor(); - }); + this.editor = this.globalEditor[method]({ + el: this.$refs.editor, + blobPath: this.file.path, + blobGlobalId: this.file.key, + blobContent: this.content || this.file.content, + ...instanceOptions, + ...this.editorOptions, + }); + + this.editor.use( + new EditorWebIdeExtension({ + instance: this.editor, + modelManager: this.modelManager, + store: this.$store, + file: this.file, + options: this.editorOptions, + }), + ); + + this.$nextTick(() => { + this.setupEditor(); + }); + } }, + setupEditor() { - if (!this.file || !this.editor.instance || this.file.loading) return; + if (!this.file || !this.editor || this.file.loading) return; const head = this.getStagedFile(this.file.path); @@ -279,6 +315,8 @@ export default { this.editor.attachModel(this.model); } + this.isEditorLoading = false; + this.model.updateOptions(this.rules); this.model.onChange((model) => { @@ -298,7 +336,7 @@ export default { }); }); - this.editor.setPosition({ + this.editor.setPos({ lineNumber: this.fileEditor.editorRow, column: this.fileEditor.editorColumn, }); @@ -308,6 +346,10 @@ export default { fileLanguage: this.model.language, }); + this.$nextTick(() => { + this.editor.updateDimensions(); + }); + this.$emit('editorSetup'); if (performance.getEntriesByName(WEBIDE_MARK_FILE_CLICKED).length) { eventHub.$emit(WEBIDE_MEASURE_FILE_AFTER_INTERACTION); @@ -344,7 +386,7 @@ export default { }); }, onPaste(event) { - const editor = this.editor.instance; + const { editor } = this; const reImage = /^image\/(png|jpg|jpeg|gif)$/; const file = event.clipboardData.files[0]; @@ -395,6 +437,7 @@ export default { <a href="javascript:void(0);" role="button" + data-testid="edit-tab" @click.prevent="updateEditor({ viewMode: $options.FILE_VIEW_MODE_EDITOR })" > {{ __('Edit') }} @@ -404,6 +447,7 @@ export default { <a href="javascript:void(0);" role="button" + data-testid="preview-tab" @click.prevent="updateEditor({ viewMode: $options.FILE_VIEW_MODE_PREVIEW })" >{{ previewMode.previewTitle }}</a > @@ -414,6 +458,7 @@ export default { <div v-show="showEditor" ref="editor" + :key="`content-editor`" :class="{ 'is-readonly': isCommitModeActive, 'is-deleted': file.deleted, @@ -421,6 +466,8 @@ export default { }" class="multi-file-editor-holder" data-qa-selector="editor_container" + data-testid="editor-container" + :data-editor-loading="isEditorLoading" @focusout="triggerFilesChange" ></div> <content-viewer diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue index d28751c9571..64ec2cc67c7 100644 --- a/app/assets/javascripts/ide/components/repo_tab.vue +++ b/app/assets/javascripts/ide/components/repo_tab.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlTab } from '@gitlab/ui'; import { mapActions, mapGetters } from 'vuex'; import { __, sprintf } from '~/locale'; @@ -13,6 +13,7 @@ export default { FileIcon, GlIcon, ChangedFileIcon, + GlTab, }, props: { tab: { @@ -71,29 +72,30 @@ export default { </script> <template> - <li - :class="{ - active: tab.active, - disabled: tab.pending, - }" + <gl-tab + :active="tab.active" + :disabled="tab.pending" + :title="tab.name" @click="clickFile(tab)" @mouseover="mouseOverTab" @mouseout="mouseOutTab" > - <div :title="getUrlForPath(tab.path)" class="multi-file-tab"> - <file-icon :file-name="tab.name" :size="16" /> - {{ tab.name }} - <file-status-icon :file="tab" /> - </div> - <button - :aria-label="closeLabel" - :disabled="tab.pending" - type="button" - class="multi-file-tab-close" - @click.stop.prevent="closeFile(tab)" - > - <gl-icon v-if="!showChangedIcon" :size="12" name="close" /> - <changed-file-icon v-else :file="tab" /> - </button> - </li> + <template #title> + <div :title="getUrlForPath(tab.path)" class="multi-file-tab"> + <file-icon :file-name="tab.name" :size="16" /> + {{ tab.name }} + <file-status-icon :file="tab" /> + </div> + <button + :aria-label="closeLabel" + :disabled="tab.pending" + type="button" + class="multi-file-tab-close" + @click.stop.prevent="closeFile(tab)" + > + <gl-icon v-if="!showChangedIcon" :size="12" name="close" /> + <changed-file-icon v-else :file="tab" /> + </button> + </template> + </gl-tab> </template> diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue index c03694e3619..932040c7fa5 100644 --- a/app/assets/javascripts/ide/components/repo_tabs.vue +++ b/app/assets/javascripts/ide/components/repo_tabs.vue @@ -1,10 +1,12 @@ <script> +import { GlTabs } from '@gitlab/ui'; import { mapActions, mapGetters } from 'vuex'; import RepoTab from './repo_tab.vue'; export default { components: { RepoTab, + GlTabs, }, props: { activeFile: { @@ -42,8 +44,8 @@ export default { <template> <div class="multi-file-tabs"> - <ul ref="tabsScroller" class="list-unstyled gl-mb-0"> + <gl-tabs> <repo-tab v-for="tab in files" :key="tab.key" :tab="tab" /> - </ul> + </gl-tabs> </div> </template> diff --git a/app/assets/javascripts/ide/components/shared/tokened_input.vue b/app/assets/javascripts/ide/components/shared/tokened_input.vue index e7a4c5487d1..ed0dab47947 100644 --- a/app/assets/javascripts/ide/components/shared/tokened_input.vue +++ b/app/assets/javascripts/ide/components/shared/tokened_input.vue @@ -81,7 +81,9 @@ export default { > <div class="value-container rounded"> <div class="value">{{ token.label }}</div> - <div class="remove-token inverted"><gl-icon :size="10" name="close" /></div> + <div class="remove-token inverted"> + <gl-icon :size="10" name="close" use-deprecated-sizes /> + </div> </div> </button> </div> diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index ed6b750480b..056df3739ee 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -15,6 +15,7 @@ export const FILE_VIEW_MODE_PREVIEW = 'preview'; export const PERMISSION_CREATE_MR = 'createMergeRequestIn'; export const PERMISSION_READ_MR = 'readMergeRequest'; export const PERMISSION_PUSH_CODE = 'pushCode'; +export const PUSH_RULE_REJECT_UNSIGNED_COMMITS = 'rejectUnsignedCommits'; // The default permission object to use when the project data isn't available yet. // This helps us encapsulate checks like `canPushCode` without requiring an diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 1b4b59ef62f..f4a0f324e4a 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -53,7 +53,6 @@ export function initIde(el, options = {}) { promotionSvgPath: el.dataset.promotionSvgPath, }); this.setLinks({ - ciHelpPagePath: el.dataset.ciHelpPagePath, webIDEHelpPagePath: el.dataset.webIdeHelpPagePath, }); this.setInitialData({ diff --git a/app/assets/javascripts/ide/messages.js b/app/assets/javascripts/ide/messages.js new file mode 100644 index 00000000000..4298d4c627c --- /dev/null +++ b/app/assets/javascripts/ide/messages.js @@ -0,0 +1,17 @@ +import { s__ } from '~/locale'; + +export const MSG_CANNOT_PUSH_CODE = s__( + 'WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request.', +); + +export const MSG_CANNOT_PUSH_CODE_SHORT = s__( + 'WebIDE|You need permission to edit files directly in this project.', +); + +export const MSG_CANNOT_PUSH_UNSIGNED = s__( + 'WebIDE|This project does not accept unsigned commits. You will not be able to commit your changes through the Web IDE.', +); + +export const MSG_CANNOT_PUSH_UNSIGNED_SHORT = s__( + 'WebIDE|This project does not accept unsigned commits.', +); diff --git a/app/assets/javascripts/ide/queries/getUserPermissions.query.graphql b/app/assets/javascripts/ide/queries/getUserPermissions.query.graphql deleted file mode 100644 index f0b50793226..00000000000 --- a/app/assets/javascripts/ide/queries/getUserPermissions.query.graphql +++ /dev/null @@ -1,9 +0,0 @@ -query getUserPermissions($projectPath: ID!) { - project(fullPath: $projectPath) { - userPermissions { - createMergeRequestIn - readMergeRequest - pushCode - } - } -} diff --git a/app/assets/javascripts/ide/queries/get_ide_project.query.graphql b/app/assets/javascripts/ide/queries/get_ide_project.query.graphql new file mode 100644 index 00000000000..6ceb36909f3 --- /dev/null +++ b/app/assets/javascripts/ide/queries/get_ide_project.query.graphql @@ -0,0 +1,7 @@ +#import "~/ide/queries/ide_project.fragment.graphql" + +query getIdeProject($projectPath: ID!) { + project(fullPath: $projectPath) { + ...IdeProject + } +} diff --git a/app/assets/javascripts/ide/queries/ide_project.fragment.graphql b/app/assets/javascripts/ide/queries/ide_project.fragment.graphql new file mode 100644 index 00000000000..c107f2376f9 --- /dev/null +++ b/app/assets/javascripts/ide/queries/ide_project.fragment.graphql @@ -0,0 +1,7 @@ +fragment IdeProject on Project { + userPermissions { + createMergeRequestIn + readMergeRequest + pushCode + } +} diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js index e98653aedc2..0aa08323d13 100644 --- a/app/assets/javascripts/ide/services/index.js +++ b/app/assets/javascripts/ide/services/index.js @@ -1,14 +1,14 @@ +import getIdeProject from 'ee_else_ce/ide/queries/get_ide_project.query.graphql'; import Api from '~/api'; import axios from '~/lib/utils/axios_utils'; import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility'; -import getUserPermissions from '../queries/getUserPermissions.query.graphql'; import { query } from './gql'; const fetchApiProjectData = (projectPath) => Api.project(projectPath).then(({ data }) => data); const fetchGqlProjectData = (projectPath) => query({ - query: getUserPermissions, + query: getIdeProject, variables: { projectPath }, }).then(({ data }) => data.project); diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index 9b93fc74f76..a5bb32ec44a 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -7,7 +7,14 @@ import { PERMISSION_READ_MR, PERMISSION_CREATE_MR, PERMISSION_PUSH_CODE, + PUSH_RULE_REJECT_UNSIGNED_COMMITS, } from '../constants'; +import { + MSG_CANNOT_PUSH_CODE, + MSG_CANNOT_PUSH_CODE_SHORT, + MSG_CANNOT_PUSH_UNSIGNED, + MSG_CANNOT_PUSH_UNSIGNED_SHORT, +} from '../messages'; import { getChangesCountForFiles, filePathMatches } from './utils'; export const activeFile = (state) => state.openFiles.find((file) => file.active) || null; @@ -153,14 +160,47 @@ export const getDiffInfo = (state, getters) => (path) => { export const findProjectPermissions = (state, getters) => (projectId) => getters.findProject(projectId)?.userPermissions || DEFAULT_PERMISSIONS; +export const findPushRules = (state, getters) => (projectId) => + getters.findProject(projectId)?.pushRules || {}; + export const canReadMergeRequests = (state, getters) => Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_READ_MR]); export const canCreateMergeRequests = (state, getters) => Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_CREATE_MR]); -export const canPushCode = (state, getters) => - Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_PUSH_CODE]); +/** + * Returns an object with `isAllowed` and `message` based on why the user cant push code + */ +export const canPushCodeStatus = (state, getters) => { + const canPushCode = getters.findProjectPermissions(state.currentProjectId)[PERMISSION_PUSH_CODE]; + const rejectUnsignedCommits = getters.findPushRules(state.currentProjectId)[ + PUSH_RULE_REJECT_UNSIGNED_COMMITS + ]; + + if (rejectUnsignedCommits) { + return { + isAllowed: false, + message: MSG_CANNOT_PUSH_UNSIGNED, + messageShort: MSG_CANNOT_PUSH_UNSIGNED_SHORT, + }; + } + if (!canPushCode) { + return { + isAllowed: false, + message: MSG_CANNOT_PUSH_CODE, + messageShort: MSG_CANNOT_PUSH_CODE_SHORT, + }; + } + + return { + isAllowed: true, + message: '', + messageShort: '', + }; +}; + +export const canPushCode = (state, getters) => getters.canPushCodeStatus.isAllowed; export const entryExists = (state) => (path) => Boolean(state.entries[path] && !state.entries[path].deleted); |