diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-12 00:09:19 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-12 00:09:19 +0300 |
commit | fca89bb73ff5b1d14c98c72481f9268fee107ea0 (patch) | |
tree | e1c8a2c4fe5df7f054fd09e49f53bcfb51e51c84 /app | |
parent | 76e9fc7b29c1ce716c26932e9fbec0f3c99f53f4 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/boards/index.js | 12 | ||||
-rw-r--r-- | app/assets/javascripts/boards/models/assignee.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/boards/models/issue.js | 8 | ||||
-rw-r--r-- | app/assets/javascripts/boards/models/list.js | 5 | ||||
-rw-r--r-- | app/assets/javascripts/boards/stores/boards_store.js | 8 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/new_dropdown/upload.vue | 21 | ||||
-rw-r--r-- | app/assets/javascripts/ide/utils.js | 53 | ||||
-rw-r--r-- | app/assets/javascripts/monitoring/components/panel_type.vue | 16 | ||||
-rw-r--r-- | app/helpers/boards_helper.rb | 1 | ||||
-rw-r--r-- | app/policies/group_policy.rb | 1 | ||||
-rw-r--r-- | app/services/projects/lfs_pointers/lfs_download_link_list_service.rb | 30 |
11 files changed, 115 insertions, 44 deletions
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index f72fc8d54b3..781cb0c1cc9 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -84,7 +84,6 @@ export default () => { rootPath: $boardApp.dataset.rootPath, bulkUpdatePath: $boardApp.dataset.bulkUpdatePath, detailIssue: boardsStore.detail, - defaultAvatar: $boardApp.dataset.defaultAvatar, }, computed: { detailIssueVisible() { @@ -130,13 +129,10 @@ export default () => { position = -1; } - boardsStore.addList( - { - ...listObj, - position, - }, - this.defaultAvatar, - ); + boardsStore.addList({ + ...listObj, + position, + }); }); boardsStore.addBlankState(); diff --git a/app/assets/javascripts/boards/models/assignee.js b/app/assets/javascripts/boards/models/assignee.js index 4a29b0d0581..5f5758583bb 100644 --- a/app/assets/javascripts/boards/models/assignee.js +++ b/app/assets/javascripts/boards/models/assignee.js @@ -1,9 +1,9 @@ export default class ListAssignee { - constructor(obj, defaultAvatar) { + constructor(obj) { this.id = obj.id; this.name = obj.name; this.username = obj.username; - this.avatar = obj.avatar_url || obj.avatar || defaultAvatar; + this.avatar = obj.avatar_url || obj.avatar || gon.default_avatar_url; this.path = obj.path; this.state = obj.state; this.webUrl = obj.web_url || obj.webUrl; diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 4f5d583e61f..d099c4b930c 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -10,7 +10,7 @@ import IssueProject from './project'; import boardsStore from '../stores/boards_store'; class ListIssue { - constructor(obj, defaultAvatar) { + constructor(obj) { this.subscribed = obj.subscribed; this.labels = []; this.assignees = []; @@ -22,11 +22,11 @@ class ListIssue { this.closed = obj.closed; this.isLoading = {}; - this.refreshData(obj, defaultAvatar); + this.refreshData(obj); } - refreshData(obj, defaultAvatar) { - boardsStore.refreshIssueData(this, obj, defaultAvatar); + refreshData(obj) { + boardsStore.refreshIssueData(this, obj); } addLabel(label) { diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index ff50b8ed7d1..990b648190a 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -36,7 +36,7 @@ const TYPES = { }; class List { - constructor(obj, defaultAvatar) { + constructor(obj) { this.id = obj.id; this._uid = this.guid(); this.position = obj.position; @@ -55,7 +55,6 @@ class List { this.maxIssueCount = Object.hasOwnProperty.call(obj, 'max_issue_count') ? obj.max_issue_count : 0; - this.defaultAvatar = defaultAvatar; if (obj.label) { this.label = new ListLabel(obj.label); @@ -156,7 +155,7 @@ class List { createIssues(data) { data.forEach(issueObj => { - this.addIssue(new ListIssue(issueObj, this.defaultAvatar)); + this.addIssue(new ListIssue(issueObj)); }); } diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 2a2cff3d07d..528dc4ed68e 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -74,8 +74,8 @@ const boardsStore = { showPage(page) { this.state.currentPage = page; }, - addList(listObj, defaultAvatar) { - const list = new List(listObj, defaultAvatar); + addList(listObj) { + const list = new List(listObj); this.state.lists = _.sortBy([...this.state.lists, list], 'position'); return list; @@ -602,7 +602,7 @@ const boardsStore = { clearMultiSelect() { this.multiSelect.list = []; }, - refreshIssueData(issue, obj, defaultAvatar) { + refreshIssueData(issue, obj) { issue.id = obj.id; issue.iid = obj.iid; issue.title = obj.title; @@ -631,7 +631,7 @@ const boardsStore = { } if (obj.assignees) { - issue.assignees = obj.assignees.map(a => new ListAssignee(a, defaultAvatar)); + issue.assignees = obj.assignees.map(a => new ListAssignee(a)); } }, }; diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue index 0efb0012246..7261e0590c8 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue @@ -1,5 +1,6 @@ <script> import ItemButton from './button.vue'; +import { isTextFile } from '~/ide/utils'; export default { components: { @@ -23,29 +24,11 @@ export default { }, }, methods: { - isText(content, fileType) { - const knownBinaryFileTypes = ['image/']; - const knownTextFileTypes = ['text/']; - const isKnownBinaryFileType = knownBinaryFileTypes.find(type => fileType.includes(type)); - const isKnownTextFileType = knownTextFileTypes.find(type => fileType.includes(type)); - const asciiRegex = /^[ -~\t\n\r]+$/; // tests whether a string contains ascii characters only (ranges from space to tilde, tabs and new lines) - - if (isKnownBinaryFileType) { - return false; - } - - if (isKnownTextFileType) { - return true; - } - - // if it's not a known file type, determine the type by evaluating the file contents - return asciiRegex.test(content); - }, createFile(target, file) { const { name } = file; const encodedContent = target.result.split('base64,')[1]; const rawContent = encodedContent ? atob(encodedContent) : ''; - const isText = this.isText(rawContent, file.type); + const isText = isTextFile(rawContent, file.type, name); const emitCreateEvent = content => this.$emit('create', { diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js index ae579fef25f..64ac539a4ff 100644 --- a/app/assets/javascripts/ide/utils.js +++ b/app/assets/javascripts/ide/utils.js @@ -1,4 +1,57 @@ import { commitItemIconMap } from './constants'; +import { languages } from 'monaco-editor'; +import { flatten } from 'lodash'; + +const toLowerCase = x => x.toLowerCase(); + +const monacoLanguages = languages.getLanguages(); +const monacoExtensions = new Set( + flatten(monacoLanguages.map(lang => lang.extensions?.map(toLowerCase) || [])), +); +const monacoMimetypes = new Set( + flatten(monacoLanguages.map(lang => lang.mimetypes?.map(toLowerCase) || [])), +); +const monacoFilenames = new Set( + flatten(monacoLanguages.map(lang => lang.filenames?.map(toLowerCase) || [])), +); + +const KNOWN_TYPES = [ + { + isText: false, + isMatch(mimeType) { + return mimeType.toLowerCase().includes('image/'); + }, + }, + { + isText: true, + isMatch(mimeType) { + return mimeType.toLowerCase().includes('text/'); + }, + }, + { + isText: true, + isMatch(mimeType, fileName) { + const fileExtension = fileName.includes('.') ? `.${fileName.split('.').pop()}` : ''; + + return ( + monacoExtensions.has(fileExtension.toLowerCase()) || + monacoMimetypes.has(mimeType.toLowerCase()) || + monacoFilenames.has(fileName.toLowerCase()) + ); + }, + }, +]; + +export function isTextFile(content, mimeType, fileName) { + const knownType = KNOWN_TYPES.find(type => type.isMatch(mimeType, fileName)); + + if (knownType) return knownType.isText; + + // does the string contain ascii characters only (ranges from space to tilde, tabs and new lines) + const asciiRegex = /^[ -~\t\n\r]+$/; + // for unknown types, determine the type by evaluating the file contents + return asciiRegex.test(content); +} export const getCommitIconMap = file => { if (file.deleted) { diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index f2f0fff694e..4573ec58ab8 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -11,7 +11,7 @@ import { GlTooltip, GlTooltipDirective, } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { __, n__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import MonitorTimeSeriesChart from './charts/time_series.vue'; import MonitorAnomalyChart from './charts/anomaly.vue'; @@ -120,6 +120,12 @@ export default { !this.isPanelType('stacked-column') ); }, + editCustomMetricLink() { + return this.graphData?.metrics[0].edit_path; + }, + editCustomMetricLinkText() { + return n__('Metrics|Edit metric', 'Metrics|Edit metrics', this.graphData.metrics.length); + }, }, mounted() { this.refreshTitleTooltip(); @@ -195,7 +201,13 @@ export default { <template slot="button-content"> <icon name="ellipsis_v" class="text-secondary" /> </template> - + <gl-dropdown-item + v-if="editCustomMetricLink" + ref="editMetricLink" + :href="editCustomMetricLink" + > + {{ editCustomMetricLinkText }} + </gl-dropdown-item> <gl-dropdown-item v-if="logsPathWithTimeRange" ref="viewLogsLink" diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index 8bb079e6447..c14bc454bb9 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -15,7 +15,6 @@ module BoardsHelper root_path: root_path, full_path: full_path, bulk_update_path: @bulk_issues_path, - default_avatar: image_path(default_avatar), time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s, recent_boards_endpoint: recent_boards_path } diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 404734ef30a..abd63753908 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -95,6 +95,7 @@ class GroupPolicy < BasePolicy enable :admin_cluster enable :destroy_deploy_token enable :read_deploy_token + enable :create_deploy_token end rule { owner }.policy do diff --git a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb index 5ef7e03ea02..48a21bf94ba 100644 --- a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb +++ b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb @@ -7,8 +7,13 @@ module Projects class LfsDownloadLinkListService < BaseService DOWNLOAD_ACTION = 'download' + # This could be different per server, but it seems like a reasonable value to start with. + # https://github.com/git-lfs/git-lfs/issues/419 + REQUEST_BATCH_SIZE = 100 + DownloadLinksError = Class.new(StandardError) DownloadLinkNotFound = Class.new(StandardError) + DownloadLinksRequestEntityTooLargeError = Class.new(StandardError) attr_reader :remote_uri @@ -25,16 +30,39 @@ module Projects def execute(oids) return [] unless project&.lfs_enabled? && remote_uri && oids.present? - get_download_links(oids) + get_download_links_in_batches(oids) end private + def get_download_links_in_batches(oids, batch_size = REQUEST_BATCH_SIZE) + download_links = [] + + oids.each_slice(batch_size) do |batch| + download_links += get_download_links(batch) + end + + download_links + + rescue DownloadLinksRequestEntityTooLargeError => e + # Log this exceptions to see how open it happens + Gitlab::ErrorTracking + .track_exception(e, project_id: project&.id, batch_size: batch_size, oids_count: oids.count) + + # Try again with a smaller batch + batch_size /= 2 + + retry if batch_size > REQUEST_BATCH_SIZE / 3 + + raise DownloadLinksError, 'Unable to download due to RequestEntityTooLarge errors' + end + def get_download_links(oids) response = Gitlab::HTTP.post(remote_uri, body: request_body(oids), headers: headers) + raise DownloadLinksRequestEntityTooLargeError if response.request_entity_too_large? raise DownloadLinksError, response.message unless response.success? # Since the LFS Batch API may return a Content-Ttpe of |