Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-12 00:09:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-12 00:09:19 +0300
commitfca89bb73ff5b1d14c98c72481f9268fee107ea0 (patch)
treee1c8a2c4fe5df7f054fd09e49f53bcfb51e51c84 /app
parent76e9fc7b29c1ce716c26932e9fbec0f3c99f53f4 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/index.js12
-rw-r--r--app/assets/javascripts/boards/models/assignee.js4
-rw-r--r--app/assets/javascripts/boards/models/issue.js8
-rw-r--r--app/assets/javascripts/boards/models/list.js5
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js8
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue21
-rw-r--r--app/assets/javascripts/ide/utils.js53
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue16
-rw-r--r--app/helpers/boards_helper.rb1
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_link_list_service.rb30
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