diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
commit | a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4 (patch) | |
tree | fb69158581673816a8cd895f9d352dcb3c678b1e /app/assets/javascripts/repository | |
parent | d16b2e8639e99961de6ddc93909f3bb5c1445ba1 (diff) | |
download | gitlab-ce-a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4.tar.gz |
Add latest changes from gitlab-org/gitlab@14-0-stable-eev14.0.0-rc42
Diffstat (limited to 'app/assets/javascripts/repository')
14 files changed, 279 insertions, 50 deletions
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index a9701c8f8aa..7fbf331d585 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -8,11 +8,13 @@ import createFlash from '~/flash'; import { __ } from '~/locale'; import blobInfoQuery from '../queries/blob_info.query.graphql'; import BlobHeaderEdit from './blob_header_edit.vue'; +import BlobReplace from './blob_replace.vue'; export default { components: { BlobHeader, BlobHeaderEdit, + BlobReplace, BlobContent, GlLoadingIcon, }, @@ -87,6 +89,9 @@ export default { }; }, computed: { + isLoggedIn() { + return Boolean(gon.current_user_id); + }, isLoading() { return this.$apollo.queries.project.loading; }, @@ -126,7 +131,17 @@ export default { @viewer-changed="switchViewer" > <template #actions> - <blob-header-edit :edit-path="blobInfo.editBlobPath" /> + <blob-header-edit + :edit-path="blobInfo.editBlobPath" + :web-ide-path="blobInfo.ideEditPath" + /> + <blob-replace + v-if="isLoggedIn" + :path="path" + :name="blobInfo.name" + :replace-path="blobInfo.replacePath" + :can-push-code="blobInfo.canModifyBlob" + /> </template> </blob-header> <blob-content diff --git a/app/assets/javascripts/repository/components/blob_header_edit.vue b/app/assets/javascripts/repository/components/blob_header_edit.vue index f3649895736..3d97ebe89e4 100644 --- a/app/assets/javascripts/repository/components/blob_header_edit.vue +++ b/app/assets/javascripts/repository/components/blob_header_edit.vue @@ -1,25 +1,47 @@ <script> import { GlButton } from '@gitlab/ui'; import { __ } from '~/locale'; +import WebIdeLink from '~/vue_shared/components/web_ide_link.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { i18n: { edit: __('Edit'), + webIde: __('Web IDE'), }, components: { GlButton, + WebIdeLink, }, + mixins: [glFeatureFlagsMixin()], props: { editPath: { type: String, required: true, }, + webIdePath: { + type: String, + required: true, + }, }, }; </script> <template> - <gl-button category="primary" variant="confirm" class="gl-mr-3" :href="editPath"> - {{ $options.i18n.edit }} - </gl-button> + <web-ide-link + v-if="glFeatures.consolidatedEditButton" + 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"> + {{ $options.i18n.edit }} + </gl-button> + + <gl-button class="gl-mr-3" category="primary" variant="confirm" :href="webIdePath"> + {{ $options.i18n.webIde }} + </gl-button> + </div> </template> diff --git a/app/assets/javascripts/repository/components/blob_replace.vue b/app/assets/javascripts/repository/components/blob_replace.vue new file mode 100644 index 00000000000..91d7811eb6d --- /dev/null +++ b/app/assets/javascripts/repository/components/blob_replace.vue @@ -0,0 +1,75 @@ +<script> +import { GlButton, GlModalDirective } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; +import { sprintf, __ } from '~/locale'; +import getRefMixin from '../mixins/get_ref'; +import UploadBlobModal from './upload_blob_modal.vue'; + +export default { + i18n: { + replace: __('Replace'), + replacePrimaryBtnText: __('Replace file'), + }, + components: { + GlButton, + UploadBlobModal, + }, + directives: { + GlModal: GlModalDirective, + }, + mixins: [getRefMixin], + inject: { + targetBranch: { + default: '', + }, + originalBranch: { + default: '', + }, + }, + props: { + name: { + type: String, + required: true, + }, + path: { + type: String, + required: true, + }, + replacePath: { + type: String, + required: true, + }, + canPushCode: { + type: Boolean, + required: true, + }, + }, + computed: { + replaceModalId() { + return uniqueId('replace-modal'); + }, + title() { + return sprintf(__('Replace %{name}'), { name: this.name }); + }, + }, +}; +</script> + +<template> + <div class="gl-mr-3"> + <gl-button v-gl-modal="replaceModalId"> + {{ $options.i18n.replace }} + </gl-button> + <upload-blob-modal + :modal-id="replaceModalId" + :modal-title="title" + :commit-message="title" + :target-branch="targetBranch || ref" + :original-branch="originalBranch || ref" + :can-push-code="canPushCode" + :path="path" + :replace-path="replacePath" + :primary-btn-text="$options.i18n.replacePrimaryBtnText" + /> + </div> +</template> diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index 22dffb7d2db..ca5711de49c 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -51,6 +51,9 @@ export default { }; }, computed: { + totalEntries() { + return Object.values(this.entries).flat().length; + }, tableCaption() { if (this.isLoading) { return sprintf( @@ -111,6 +114,7 @@ export default { :submodule-tree-url="entry.treeUrl" :lfs-oid="entry.lfsOid" :loading-path="loadingPath" + :total-entries="totalEntries" /> </template> <template v-if="isLoading"> diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index 8ea5fce92fa..62f863db871 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -7,13 +7,17 @@ import { GlTooltipDirective, GlLoadingIcon, GlIcon, + GlHoverLoadDirective, } from '@gitlab/ui'; import { escapeRegExp } from 'lodash'; +import filesQuery from 'shared_queries/repository/files.query.graphql'; import { escapeFileUrl } from '~/lib/utils/url_utility'; +import { TREE_PAGE_SIZE } from '~/repository/constants'; import FileIcon from '~/vue_shared/components/file_icon.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import getRefMixin from '../../mixins/get_ref'; +import blobInfoQuery from '../../queries/blob_info.query.graphql'; import commitQuery from '../../queries/commit.query.graphql'; export default { @@ -28,6 +32,7 @@ export default { }, directives: { GlTooltip: GlTooltipDirective, + GlHoverLoad: GlHoverLoadDirective, }, apollo: { commit: { @@ -38,12 +43,17 @@ export default { type: this.type, path: this.currentPath, projectPath: this.projectPath, + maxOffset: this.totalEntries, }; }, }, }, mixins: [getRefMixin, glFeatureFlagMixin()], props: { + totalEntries: { + type: Number, + required: true, + }, id: { type: String, required: true, @@ -139,6 +149,33 @@ export default { return this.commit && this.commit.lockLabel; }, }, + methods: { + handlePreload() { + return this.isFolder ? this.loadFolder() : this.loadBlob(); + }, + loadFolder() { + this.apolloQuery(filesQuery, { + projectPath: this.projectPath, + ref: this.ref, + path: this.path, + nextPageCursor: '', + pageSize: TREE_PAGE_SIZE, + }); + }, + loadBlob() { + if (!this.refactorBlobViewerEnabled) { + return; + } + + this.apolloQuery(blobInfoQuery, { + projectPath: this.projectPath, + filePath: this.path, + }); + }, + apolloQuery(query, variables) { + this.$apollo.query({ query, variables }); + }, + }, }; </script> @@ -148,6 +185,7 @@ export default { <component :is="linkComponent" ref="link" + v-gl-hover-load="handlePreload" :to="routerLinkTo" :href="url" :class="{ diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index 336237abd8a..794a8a85cc5 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -1,17 +1,14 @@ <script> import filesQuery from 'shared_queries/repository/files.query.graphql'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; +import createFlash from '~/flash'; import { __ } from '../../locale'; +import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT } from '../constants'; import getRefMixin from '../mixins/get_ref'; import projectPathQuery from '../queries/project_path.query.graphql'; import { readmeFile } from '../utils/readme'; import FilePreview from './preview/index.vue'; import FileTable from './table/index.vue'; -const LIMIT = 1000; -const PAGE_SIZE = 100; -export const INITIAL_FETCH_COUNT = LIMIT / PAGE_SIZE; - export default { components: { FileTable, @@ -47,7 +44,7 @@ export default { isLoadingFiles: false, isOverLimit: false, clickedShowMore: false, - pageSize: PAGE_SIZE, + pageSize: TREE_PAGE_SIZE, fetchCounter: 0, }; }, @@ -56,7 +53,7 @@ export default { return readmeFile(this.entries.blobs); }, hasShowMore() { - return !this.clickedShowMore && this.fetchCounter === INITIAL_FETCH_COUNT; + return !this.clickedShowMore && this.fetchCounter === TREE_INITIAL_FETCH_COUNT; }, }, @@ -107,14 +104,16 @@ export default { if (pageInfo?.hasNextPage) { this.nextPageCursor = pageInfo.endCursor; this.fetchCounter += 1; - if (this.fetchCounter < INITIAL_FETCH_COUNT || this.clickedShowMore) { + if (this.fetchCounter < TREE_INITIAL_FETCH_COUNT || this.clickedShowMore) { this.fetchFiles(); this.clickedShowMore = false; } } }) .catch((error) => { - createFlash(__('An error occurred while fetching folder content.')); + createFlash({ + message: __('An error occurred while fetching folder content.'), + }); throw error; }); }, diff --git a/app/assets/javascripts/repository/components/upload_blob_modal.vue b/app/assets/javascripts/repository/components/upload_blob_modal.vue index aa087d4c631..7f065dbdf6d 100644 --- a/app/assets/javascripts/repository/components/upload_blob_modal.vue +++ b/app/assets/javascripts/repository/components/upload_blob_modal.vue @@ -43,7 +43,6 @@ export default { GlAlert, }, i18n: { - MODAL_TITLE, COMMIT_LABEL, TARGET_BRANCH_LABEL, TOGGLE_CREATE_MR_LABEL, @@ -51,6 +50,16 @@ export default { NEW_BRANCH_IN_FORK, }, props: { + modalTitle: { + type: String, + default: MODAL_TITLE, + required: false, + }, + primaryBtnText: { + type: String, + default: PRIMARY_OPTIONS_TEXT, + required: false, + }, modalId: { type: String, required: true, @@ -75,6 +84,11 @@ export default { type: String, required: true, }, + replacePath: { + type: String, + default: null, + required: false, + }, }, data() { return { @@ -90,7 +104,7 @@ export default { computed: { primaryOptions() { return { - text: PRIMARY_OPTIONS_TEXT, + text: this.primaryBtnText, attributes: [ { variant: 'confirm', @@ -136,6 +150,45 @@ export default { this.file = null; this.filePreviewURL = null; }, + submitForm() { + return this.replacePath ? this.replaceFile() : this.uploadFile(); + }, + submitRequest(method, url) { + return axios({ + method, + url, + data: this.formData(), + headers: { + ...ContentTypeMultipartFormData, + }, + }) + .then((response) => { + if (!this.replacePath) { + trackFileUploadEvent('click_upload_modal_form_submit'); + } + visitUrl(response.data.filePath); + }) + .catch(() => { + this.loading = false; + createFlash(ERROR_MESSAGE); + }); + }, + formData() { + const formData = new FormData(); + formData.append('branch_name', this.target); + formData.append('create_merge_request', this.createNewMr); + formData.append('commit_message', this.commit); + formData.append('file', this.file); + + return formData; + }, + replaceFile() { + this.loading = true; + + // The PUT path can be geneated from $route (similar to "uploadFile") once router is connected + // Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/332736 + return this.submitRequest('put', this.replacePath); + }, uploadFile() { this.loading = true; @@ -146,26 +199,7 @@ export default { } = this; const uploadPath = joinPaths(this.path, path); - const formData = new FormData(); - formData.append('branch_name', this.target); - formData.append('create_merge_request', this.createNewMr); - formData.append('commit_message', this.commit); - formData.append('file', this.file); - - return axios - .post(uploadPath, formData, { - headers: { - ...ContentTypeMultipartFormData, - }, - }) - .then((response) => { - trackFileUploadEvent('click_upload_modal_form_submit'); - visitUrl(response.data.filePath); - }) - .catch(() => { - this.loading = false; - createFlash(ERROR_MESSAGE); - }); + return this.submitRequest('post', uploadPath); }, }, validFileMimetypes: [], @@ -175,10 +209,10 @@ export default { <gl-form> <gl-modal :modal-id="modalId" - :title="$options.i18n.MODAL_TITLE" + :title="modalTitle" :action-primary="primaryOptions" :action-cancel="cancelOptions" - @primary.prevent="uploadFile" + @primary.prevent="submitForm" > <upload-dropzone class="gl-h-200! gl-mb-4" diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js new file mode 100644 index 00000000000..62d5d3db445 --- /dev/null +++ b/app/assets/javascripts/repository/constants.js @@ -0,0 +1,4 @@ +const TREE_PAGE_LIMIT = 1000; // the maximum amount of items per page + +export const TREE_PAGE_SIZE = 100; // the amount of items to be fetched per (batch) request +export const TREE_INITIAL_FETCH_COUNT = TREE_PAGE_LIMIT / TREE_PAGE_SIZE; // the amount of (batch) requests to make diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js index 4a4b9d115b7..4892e54ebef 100644 --- a/app/assets/javascripts/repository/graphql.js +++ b/app/assets/javascripts/repository/graphql.js @@ -17,15 +17,21 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({ const defaultClient = createDefaultClient( { Query: { - commit(_, { path, fileName, type }) { + commit(_, { path, fileName, type, maxOffset }) { return new Promise((resolve) => { - fetchLogsTree(defaultClient, path, '0', { - resolve, - entry: { - name: fileName, - type, + fetchLogsTree( + defaultClient, + path, + '0', + { + resolve, + entry: { + name: fileName, + type, + }, }, - }); + maxOffset, + ); }); }, readme(_, { url }) { diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 501ae7e9f2f..60a1a0443f7 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -141,6 +141,9 @@ export default function setupVueRepositoryList() { href: `${historyLink}/${ this.$route.params.path ? escapeFileUrl(this.$route.params.path) : '' }`, + // Ideally passing this class to `props` should work + // But it doesn't work here. :( + class: 'btn btn-default btn-md gl-button ml-sm-0', }, }, [__('History')], diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js index 9001bcd8fc3..ac02392d60f 100644 --- a/app/assets/javascripts/repository/log_tree.js +++ b/app/assets/javascripts/repository/log_tree.js @@ -7,6 +7,13 @@ import refQuery from './queries/ref.query.graphql'; const fetchpromises = {}; const resolvers = {}; +let maxOffset; +let nextOffset; +let currentPath; + +function setNextOffset(offset) { + nextOffset = offset || null; +} export function resolveCommit(commits, path, { resolve, entry }) { const commit = commits.find( @@ -18,7 +25,25 @@ export function resolveCommit(commits, path, { resolve, entry }) { } } -export function fetchLogsTree(client, path, offset, resolver = null) { +export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset = null) { + if (_maxOffset) { + maxOffset = _maxOffset; + } + + if (!currentPath || currentPath !== path) { + // ensures the nextOffset is reset if the user changed directories + setNextOffset(null); + } + + currentPath = path; + + const offsetNumber = Number(offset); + + if (!nextOffset && offsetNumber > maxOffset) { + setNextOffset(offsetNumber - 25); // ensures commit data is fetched for newly added rows that need data from the previous request (requests are made in batches of 25). + return Promise.resolve(); + } + if (resolver) { if (!resolvers[path]) { resolvers[path] = [resolver]; @@ -38,7 +63,7 @@ export function fetchLogsTree(client, path, offset, resolver = null) { path.replace(/^\//, ''), )}`, { - params: { format: 'json', offset }, + params: { format: 'json', offset: nextOffset || offset }, }, ) .then(({ data: newData, headers }) => { @@ -57,9 +82,12 @@ export function fetchLogsTree(client, path, offset, resolver = null) { delete fetchpromises[path]; if (headerLogsOffset) { + setNextOffset(null); fetchLogsTree(client, path, headerLogsOffset); } else { delete resolvers[path]; + maxOffset = null; + setNextOffset(null); } }); diff --git a/app/assets/javascripts/repository/mixins/get_ref.js b/app/assets/javascripts/repository/mixins/get_ref.js index 1f1880a48c7..3247938f999 100644 --- a/app/assets/javascripts/repository/mixins/get_ref.js +++ b/app/assets/javascripts/repository/mixins/get_ref.js @@ -6,7 +6,7 @@ export default { query: refQuery, manual: true, result({ data, loading }) { - if (!loading) { + if (data && !loading) { this.ref = data.ref; this.escapedRef = data.escapedRef; } diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql index 07c076af54b..bfd9447d260 100644 --- a/app/assets/javascripts/repository/queries/blob_info.query.graphql +++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql @@ -1,6 +1,5 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) { project(fullPath: $projectPath) { - id repository { blobs(paths: [$filePath]) { nodes { @@ -12,9 +11,11 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) { fileType path editBlobPath + ideEditPath storedExternally rawPath replacePath + canModifyBlob simpleViewer { fileType tooLarge diff --git a/app/assets/javascripts/repository/queries/commit.query.graphql b/app/assets/javascripts/repository/queries/commit.query.graphql index e4aeaaff8fe..7ae4a3b984a 100644 --- a/app/assets/javascripts/repository/queries/commit.query.graphql +++ b/app/assets/javascripts/repository/queries/commit.query.graphql @@ -1,7 +1,7 @@ #import "ee_else_ce/repository/queries/commit.fragment.graphql" -query getCommit($fileName: String!, $type: String!, $path: String!) { - commit(path: $path, fileName: $fileName, type: $type) @client { +query getCommit($fileName: String!, $type: String!, $path: String!, $maxOffset: Number!) { + commit(path: $path, fileName: $fileName, type: $type, maxOffset: $maxOffset) @client { ...TreeEntryCommit } } |