diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 09:08:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 09:08:42 +0000 |
commit | b76ae638462ab0f673e5915986070518dd3f9ad3 (patch) | |
tree | bdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/repository | |
parent | 434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff) | |
download | gitlab-ce-b76ae638462ab0f673e5915986070518dd3f9ad3.tar.gz |
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/repository')
9 files changed, 190 insertions, 25 deletions
diff --git a/app/assets/javascripts/repository/components/blob_button_group.vue b/app/assets/javascripts/repository/components/blob_button_group.vue index 273825b996a..4e7ca7b17e4 100644 --- a/app/assets/javascripts/repository/components/blob_button_group.vue +++ b/app/assets/javascripts/repository/components/blob_button_group.vue @@ -2,6 +2,7 @@ import { GlButtonGroup, GlButton, GlModalDirective } from '@gitlab/ui'; import { uniqueId } from 'lodash'; import { sprintf, __ } from '~/locale'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import getRefMixin from '../mixins/get_ref'; import DeleteBlobModal from './delete_blob_modal.vue'; import UploadBlobModal from './upload_blob_modal.vue'; @@ -17,11 +18,12 @@ export default { GlButton, UploadBlobModal, DeleteBlobModal, + LockButton: () => import('ee_component/repository/components/lock_button.vue'), }, directives: { GlModal: GlModalDirective, }, - mixins: [getRefMixin], + mixins: [getRefMixin, glFeatureFlagMixin()], inject: { targetBranch: { default: '', @@ -55,6 +57,18 @@ export default { type: Boolean, required: true, }, + projectPath: { + type: String, + required: true, + }, + isLocked: { + type: Boolean, + required: true, + }, + canLock: { + type: Boolean, + required: true, + }, }, computed: { replaceModalId() { @@ -76,10 +90,19 @@ export default { <template> <div class="gl-mr-3"> <gl-button-group> - <gl-button v-gl-modal="replaceModalId"> + <lock-button + v-if="glFeatures.fileLocks" + :name="name" + :path="path" + :project-path="projectPath" + :is-locked="isLocked" + :can-lock="canLock" + data-testid="lock" + /> + <gl-button v-gl-modal="replaceModalId" data-testid="replace"> {{ $options.i18n.replace }} </gl-button> - <gl-button v-gl-modal="deleteModalId"> + <gl-button v-gl-modal="deleteModalId" data-testid="delete"> {{ $options.i18n.delete }} </gl-button> </gl-button-group> diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index 09ac60c94c7..665b0698cc0 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -8,6 +8,7 @@ import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { isLoggedIn } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; +import getRefMixin from '../mixins/get_ref'; import blobInfoQuery from '../queries/blob_info.query.graphql'; import BlobButtonGroup from './blob_button_group.vue'; import BlobEdit from './blob_edit.vue'; @@ -21,6 +22,12 @@ export default { BlobContent, GlLoadingIcon, }, + mixins: [getRefMixin], + inject: { + originalBranch: { + default: '', + }, + }, apollo: { project: { query: blobInfoQuery, @@ -28,6 +35,7 @@ export default { return { projectPath: this.projectPath, filePath: this.path, + ref: this.originalBranch || this.ref, }; }, result() { @@ -67,6 +75,10 @@ export default { project: { userPermissions: { pushCode: false, + downloadCode: false, + }, + pathLocks: { + nodes: [], }, repository: { empty: true, @@ -87,9 +99,6 @@ export default { externalStorageUrl: '', replacePath: '', deletePath: '', - canLock: false, - isLocked: false, - lockLink: '', forkPath: '', simpleViewer: {}, richViewer: null, @@ -108,8 +117,11 @@ export default { isLoading() { return this.$apollo.queries.project.loading || this.isLoadingLegacyViewer; }, + isBinaryFileType() { + return this.isBinary || this.viewer.fileType === 'download'; + }, blobInfo() { - const nodes = this.project?.repository?.blobs?.nodes; + const nodes = this.project?.repository?.blobs?.nodes || []; return nodes[0] || {}; }, @@ -131,6 +143,14 @@ export default { const { fileType } = this.viewer; return viewerProps(fileType, this.blobInfo); }, + canLock() { + const { pushCode, downloadCode } = this.project.userPermissions; + + return pushCode && downloadCode; + }, + isLocked() { + return this.project.pathLocks.nodes.some((node) => node.path === this.path); + }, }, methods: { loadLegacyViewer() { @@ -161,13 +181,14 @@ export default { <blob-header :blob="blobInfo" :hide-viewer-switcher="!hasRichViewer || isBinary" + :is-binary="isBinaryFileType" :active-viewer-type="viewer.type" :has-render-error="hasRenderError" @viewer-changed="switchViewer" > <template #actions> <blob-edit - v-if="!isBinary" + :show-edit-button="!isBinary" :edit-path="blobInfo.editBlobPath" :web-ide-path="blobInfo.ideEditPath" /> @@ -179,6 +200,9 @@ export default { :delete-path="blobInfo.webPath" :can-push-code="project.userPermissions.pushCode" :empty-repo="project.repository.empty" + :project-path="projectPath" + :is-locked="isLocked" + :can-lock="canLock" /> </template> </blob-header> diff --git a/app/assets/javascripts/repository/components/blob_edit.vue b/app/assets/javascripts/repository/components/blob_edit.vue index 3d97ebe89e4..30ed4cd57f1 100644 --- a/app/assets/javascripts/repository/components/blob_edit.vue +++ b/app/assets/javascripts/repository/components/blob_edit.vue @@ -15,6 +15,10 @@ export default { }, mixins: [glFeatureFlagsMixin()], props: { + showEditButton: { + type: Boolean, + required: true, + }, editPath: { type: String, required: true, @@ -30,17 +34,31 @@ export default { <template> <web-ide-link v-if="glFeatures.consolidatedEditButton" + :show-edit-button="showEditButton" class="gl-mr-3" :edit-url="editPath" :web-ide-url="webIdePath" :is-blob="true" /> <div v-else> - <gl-button class="gl-mr-2" category="primary" variant="confirm" :href="editPath"> + <gl-button + v-if="showEditButton" + class="gl-mr-2" + category="primary" + variant="confirm" + :href="editPath" + data-testid="edit" + > {{ $options.i18n.edit }} </gl-button> - <gl-button class="gl-mr-3" category="primary" variant="confirm" :href="webIdePath"> + <gl-button + class="gl-mr-3" + category="primary" + variant="confirm" + :href="webIdePath" + data-testid="web-ide" + > {{ $options.i18n.webIde }} </gl-button> </div> diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index 0b8408643ac..db84e2b5912 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -247,7 +247,8 @@ export default { return items; }, renderAddToTreeDropdown() { - return this.canCollaborate || this.canCreateMrFromFork; + const isBlobPath = this.$route.name === 'blobPath' || this.$route.name === 'blobPathDecoded'; + return !isBlobPath && (this.canCollaborate || this.canCreateMrFromFork); }, }, methods: { diff --git a/app/assets/javascripts/repository/components/delete_blob_modal.vue b/app/assets/javascripts/repository/components/delete_blob_modal.vue index 6599d99d7bd..a307b7c0b8a 100644 --- a/app/assets/javascripts/repository/components/delete_blob_modal.vue +++ b/app/assets/javascripts/repository/components/delete_blob_modal.vue @@ -1,14 +1,24 @@ <script> -import { GlModal, GlFormGroup, GlFormInput, GlFormTextarea, GlToggle } from '@gitlab/ui'; +import { GlModal, GlFormGroup, GlFormInput, GlFormTextarea, GlToggle, GlForm } from '@gitlab/ui'; import csrf from '~/lib/utils/csrf'; import { __ } from '~/locale'; +import validation from '~/vue_shared/directives/validation'; import { SECONDARY_OPTIONS_TEXT, COMMIT_LABEL, TARGET_BRANCH_LABEL, TOGGLE_CREATE_MR_LABEL, + COMMIT_MESSAGE_SUBJECT_MAX_LENGTH, + COMMIT_MESSAGE_BODY_MAX_LENGTH, } from '../constants'; +const initFormField = ({ value, required = true, skipValidation = false }) => ({ + value, + required, + state: skipValidation ? true : null, + feedback: null, +}); + export default { csrf, components: { @@ -17,6 +27,7 @@ export default { GlFormInput, GlFormTextarea, GlToggle, + GlForm, }, i18n: { PRIMARY_OPTIONS_TEXT: __('Delete file'), @@ -24,6 +35,12 @@ export default { COMMIT_LABEL, TARGET_BRANCH_LABEL, TOGGLE_CREATE_MR_LABEL, + COMMIT_MESSAGE_HINT: __( + 'Try to keep the first line under 52 characters and the others under 72.', + ), + }, + directives: { + validation: validation(), }, props: { modalId: { @@ -60,12 +77,20 @@ export default { }, }, data() { + const form = { + state: false, + showValidation: false, + fields: { + // fields key must match case of form name for validation directive to work + commit_message: initFormField({ value: this.commitMessage }), + branch_name: initFormField({ value: this.targetBranch }), + }, + }; return { loading: false, - commit: this.commitMessage, - target: this.targetBranch, createNewMr: true, error: '', + form, }; }, computed: { @@ -76,7 +101,7 @@ export default { { variant: 'danger', loading: this.loading, - disabled: !this.formCompleted || this.loading, + disabled: this.loading || !this.form.state, }, ], }; @@ -91,18 +116,44 @@ export default { ], }; }, + /* eslint-disable dot-notation */ showCreateNewMrToggle() { - return this.canPushCode && this.target !== this.originalBranch; + return this.canPushCode && this.form.fields['branch_name'].value !== this.originalBranch; }, formCompleted() { - return this.commit && this.target; + return this.form.fields['commit_message'].value && this.form.fields['branch_name'].value; }, + showHint() { + const splitCommitMessageByLineBreak = this.form.fields['commit_message'].value + .trim() + .split('\n'); + const [firstLine, ...otherLines] = splitCommitMessageByLineBreak; + + const hasFirstLineExceedMaxLength = firstLine.length > COMMIT_MESSAGE_SUBJECT_MAX_LENGTH; + + const hasOtherLineExceedMaxLength = + Boolean(otherLines.length) && + otherLines.some((text) => text.length > COMMIT_MESSAGE_BODY_MAX_LENGTH); + + return ( + !this.form.fields['commit_message'].feedback && + (hasFirstLineExceedMaxLength || hasOtherLineExceedMaxLength) + ); + }, + /* eslint-enable dot-notation */ }, methods: { submitForm(e) { e.preventDefault(); // Prevent modal from closing + this.form.showValidation = true; + + if (!this.form.state) { + return; + } + this.loading = true; - this.$refs.form.submit(); + this.form.showValidation = false; + this.$refs.form.$el.submit(); }, }, }; @@ -110,13 +161,15 @@ export default { <template> <gl-modal + v-bind="$attrs" + data-testid="modal-delete" :modal-id="modalId" :title="modalTitle" :action-primary="primaryOptions" :action-cancel="cancelOptions" @primary="submitForm" > - <form ref="form" :action="deletePath" method="post"> + <gl-form ref="form" novalidate :action="deletePath" method="post"> <input type="hidden" name="_method" value="delete" /> <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> <template v-if="emptyRepo"> @@ -129,15 +182,37 @@ export default { <!-- Once "push to branch" permission is made available, will need to add to conditional Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/335462 --> <input v-if="createNewMr" type="hidden" name="create_merge_request" value="1" /> - <gl-form-group :label="$options.i18n.COMMIT_LABEL" label-for="commit_message"> - <gl-form-textarea v-model="commit" name="commit_message" :disabled="loading" /> + <gl-form-group + :label="$options.i18n.COMMIT_LABEL" + label-for="commit_message" + :invalid-feedback="form.fields['commit_message'].feedback" + > + <gl-form-textarea + v-model="form.fields['commit_message'].value" + v-validation:[form.showValidation] + name="commit_message" + :state="form.fields['commit_message'].state" + :disabled="loading" + required + /> + <p v-if="showHint" class="form-text gl-text-gray-600" data-testid="hint"> + {{ $options.i18n.COMMIT_MESSAGE_HINT }} + </p> </gl-form-group> <gl-form-group v-if="canPushCode" :label="$options.i18n.TARGET_BRANCH_LABEL" label-for="branch_name" + :invalid-feedback="form.fields['branch_name'].feedback" > - <gl-form-input v-model="target" :disabled="loading" name="branch_name" /> + <gl-form-input + v-model="form.fields['branch_name'].value" + v-validation:[form.showValidation] + :state="form.fields['branch_name'].state" + :disabled="loading" + name="branch_name" + required + /> </gl-form-group> <gl-toggle v-if="showCreateNewMrToggle" @@ -146,6 +221,6 @@ export default { :label="$options.i18n.TOGGLE_CREATE_MR_LABEL" /> </template> - </form> + </gl-form> </gl-modal> </template> diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index 82c18d13a6a..fa358a75cc1 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -170,6 +170,7 @@ export default { this.apolloQuery(blobInfoQuery, { projectPath: this.projectPath, filePath: this.path, + ref: this.ref, }); }, apolloQuery(query, variables) { diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js index 2d2faa8d9f3..b536bcb1875 100644 --- a/app/assets/javascripts/repository/constants.js +++ b/app/assets/javascripts/repository/constants.js @@ -8,3 +8,6 @@ export const SECONDARY_OPTIONS_TEXT = __('Cancel'); export const COMMIT_LABEL = __('Commit message'); export const TARGET_BRANCH_LABEL = __('Target branch'); export const TOGGLE_CREATE_MR_LABEL = __('Start a new merge request with these changes'); + +export const COMMIT_MESSAGE_SUBJECT_MAX_LENGTH = 52; +export const COMMIT_MESSAGE_BODY_MAX_LENGTH = 72; diff --git a/app/assets/javascripts/repository/mutations/lock_path.mutation.graphql b/app/assets/javascripts/repository/mutations/lock_path.mutation.graphql new file mode 100644 index 00000000000..eaebc4ddf17 --- /dev/null +++ b/app/assets/javascripts/repository/mutations/lock_path.mutation.graphql @@ -0,0 +1,13 @@ +mutation toggleLock($projectPath: ID!, $filePath: String!, $lock: Boolean!) { + projectSetLocked(input: { projectPath: $projectPath, filePath: $filePath, lock: $lock }) { + project { + id + pathLocks { + nodes { + path + } + } + } + errors + } +} diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql index a8f263941e2..45f07f7dc58 100644 --- a/app/assets/javascripts/repository/queries/blob_info.query.graphql +++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql @@ -1,11 +1,18 @@ -query getBlobInfo($projectPath: ID!, $filePath: String!) { +query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) { project(fullPath: $projectPath) { + id userPermissions { pushCode + downloadCode + } + pathLocks { + nodes { + path + } } repository { empty - blobs(paths: [$filePath]) { + blobs(paths: [$filePath], ref: $ref) { nodes { webPath name |