summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-05 19:58:23 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-05 19:58:23 +0000
commit09432c7f561449734e2ae298496dc0f1d7da0d6a (patch)
treeb444ddf7f6f8d25aa1af899f9c1f9a41feafaec7
parente2aba30891c3deff4174df08b92817095eec38d5 (diff)
downloadgitlab-ce-09432c7f561449734e2ae298496dc0f1d7da0d6a.tar.gz
Add latest changes from gitlab-org/gitlab@12-8-stable-ee
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue1
-rw-r--r--app/assets/javascripts/ide/lib/files.js5
-rw-r--r--app/assets/javascripts/ide/stores/utils.js5
-rw-r--r--app/assets/javascripts/lib/graphql.js4
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue5
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue2
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue5
-rw-r--r--app/assets/javascripts/repository/log_tree.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue3
-rw-r--r--app/assets/stylesheets/pages/projects.scss8
-rw-r--r--app/assets/stylesheets/pages/tree.scss6
-rw-r--r--app/assets/stylesheets/utilities.scss6
-rw-r--r--app/models/broadcast_message.rb10
-rw-r--r--app/uploaders/import_export_uploader.rb4
-rw-r--r--app/views/dashboard/projects/_projects.html.haml2
-rw-r--r--app/views/explore/projects/_projects.html.haml2
-rw-r--r--app/views/projects/blob/_breadcrumb.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml49
-rw-r--r--app/views/shared/projects/_project.html.haml7
-rw-r--r--changelogs/unreleased/207837-circular-encoding.yml5
-rw-r--r--changelogs/unreleased/207857-fix-web-ide-modal-no-text.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-fix-import-export-uploader.yml5
-rw-r--r--changelogs/unreleased/lm-fix-error-query.yml5
-rw-r--r--changelogs/unreleased/ph-p207499-fixNonAsciiChars.yml5
-rw-r--r--changelogs/unreleased/revert-e0613e64.yml5
-rw-r--r--changelogs/unreleased/sh-disable-line-in-marginalia.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-broadcast-message-redis.yml5
-rw-r--r--changelogs/unreleased/use-addressable-for-asset-proxy-badge-render.yml5
-rw-r--r--changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml5
-rw-r--r--config/initializers/0_marginalia.rb8
-rw-r--r--doc/user/incident_management/index.md31
-rw-r--r--doc/user/project/integrations/img/embedded_metrics_markdown_v12_8.pngbin0 -> 13818 bytes
-rw-r--r--doc/user/project/integrations/img/embedded_metrics_rendered_v12_8.pngbin0 -> 66002 bytes
-rw-r--r--doc/user/project/integrations/prometheus.md15
-rw-r--r--lib/gitlab/asset_proxy.rb4
-rw-r--r--lib/gitlab/git/rugged_impl/repository.rb2
-rw-r--r--lib/gitlab/tree_summary.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb55
-rw-r--r--spec/features/error_tracking/user_searches_sentry_errors_spec.rb40
-rw-r--r--spec/features/projects/tree/tree_show_spec.rb10
-rw-r--r--spec/fixtures/sentry/error_list_search_response.json42
-rw-r--r--spec/fixtures/sentry/issues_sample_response.json48
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js6
-rw-r--r--spec/frontend/ide/lib/files_spec.js3
-rw-r--r--spec/frontend/ide/stores/mutations_spec.js6
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js2
-rw-r--r--spec/frontend/repository/components/table/row_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/file_row_spec.js117
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js87
-rw-r--r--spec/lib/gitlab/asset_proxy_spec.rb8
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb11
-rw-r--r--spec/lib/marginalia_spec.rb3
-rw-r--r--spec/lib/sentry/client/issue_spec.rb6
-rw-r--r--spec/models/broadcast_message_spec.rb11
-rw-r--r--spec/models/repository_spec.rb9
-rw-r--r--spec/support_specs/helpers/active_record/query_recorder_spec.rb7
-rw-r--r--spec/uploaders/import_export_uploader_spec.rb6
57 files changed, 568 insertions, 169 deletions
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index 70f257180c6..552e8cac3a7 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -236,6 +236,7 @@ export default {
</gl-dropdown>
<div class="filtered-search-input-container flex-fill">
<gl-form-input
+ v-model="errorSearchQuery"
class="pl-2 filtered-search"
:disabled="loading"
:placeholder="__('Search or filter results…')"
diff --git a/app/assets/javascripts/ide/lib/files.js b/app/assets/javascripts/ide/lib/files.js
index bee867fa47c..26518a2abac 100644
--- a/app/assets/javascripts/ide/lib/files.js
+++ b/app/assets/javascripts/ide/lib/files.js
@@ -1,5 +1,4 @@
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
-import { escapeFileUrl } from '~/lib/utils/url_utility';
import { decorateData, sortTree } from '../stores/utils';
export const splitParent = path => {
@@ -48,7 +47,7 @@ export const decorateFiles = ({
id: path,
name,
path,
- url: `/${projectId}/tree/${branchId}/-/${escapeFileUrl(path)}/`,
+ url: `/${projectId}/tree/${branchId}/-/${path}/`,
type: 'tree',
parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
tempFile,
@@ -85,7 +84,7 @@ export const decorateFiles = ({
id: path,
name,
path,
- url: `/${projectId}/blob/${branchId}/-/${escapeFileUrl(path)}`,
+ url: `/${projectId}/blob/${branchId}/-/${path}`,
type: 'blob',
parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
tempFile,
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 06e66da1069..4e5b01596d8 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,5 +1,4 @@
import { commitActionTypes, FILE_VIEW_MODE_EDITOR } from '../constants';
-import { escapeFileUrl } from '~/lib/utils/url_utility';
export const dataStructure = () => ({
id: '',
@@ -220,9 +219,7 @@ export const mergeTrees = (fromTree, toTree) => {
export const replaceFileUrl = (url, oldPath, newPath) => {
// Add `/-/` so that we don't accidentally replace project path
- const result = url.replace(`/-/${escapeFileUrl(oldPath)}`, `/-/${escapeFileUrl(newPath)}`);
-
- return result;
+ return url.replace(`/-/${oldPath}`, `/-/${newPath}`);
};
export const swapInStateArray = (state, arr, key, entryPath) =>
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index b49fe9362c2..8d3b87d5cc0 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -26,6 +26,10 @@ export default (resolvers = {}, config = {}) => {
headers: {
[csrf.headerKey]: csrf.token,
},
+ // fetch won’t send cookies in older browsers, unless you set the credentials init option.
+ // We set to `same-origin` which is default value in modern browsers.
+ // See https://github.com/whatwg/fetch/pull/585 for more information.
+ credentials: 'same-origin',
};
return new ApolloClient({
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 751565ad542..be70bfc7399 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -1,5 +1,6 @@
<script>
import { GlDropdown, GlDropdownDivider, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
+import { joinPaths } from '~/lib/utils/url_utility';
import { __ } from '../../locale';
import Icon from '../../vue_shared/components/icon.vue';
import getRefMixin from '../mixins/get_ref';
@@ -102,12 +103,12 @@ export default {
.filter(p => p !== '')
.reduce(
(acc, name, i) => {
- const path = `${i > 0 ? acc[i].path : ''}/${name}`;
+ const path = joinPaths(i > 0 ? acc[i].path : '', encodeURIComponent(name));
return acc.concat({
name,
path,
- to: `/-/tree/${escape(this.ref)}${escape(path)}`,
+ to: `/-/tree/${joinPaths(escape(this.ref), path)}`,
});
},
[
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 968bd9af84f..64003630271 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -79,7 +79,7 @@ export default {
return this.$apollo.queries.commit.loading;
},
showCommitId() {
- return this.commit.sha.substr(0, 8);
+ return this.commit?.sha?.substr(0, 8);
},
},
watch: {
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index c905c39bbba..b81e6a38b4c 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -91,7 +91,9 @@ export default {
},
computed: {
routerLinkTo() {
- return this.isFolder ? { path: `/-/tree/${escape(this.ref)}/${escape(this.path)}` } : null;
+ return this.isFolder
+ ? { path: `/-/tree/${escape(this.ref)}/${encodeURIComponent(this.path)}` }
+ : null;
},
iconName() {
return `fa-${getIconName(this.type, this.path)}`;
@@ -141,6 +143,7 @@ export default {
<i v-else :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i>
<component
:is="linkComponent"
+ ref="link"
:to="routerLinkTo"
:href="url"
class="str-truncated"
diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js
index 192e410b36f..ade92cc92e0 100644
--- a/app/assets/javascripts/repository/log_tree.js
+++ b/app/assets/javascripts/repository/log_tree.js
@@ -27,7 +27,7 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
fetchpromise = axios
.get(
- `${gon.relative_url_root}/${projectPath}/-/refs/${escape(ref)}/logs_tree/${escape(
+ `${gon.relative_url_root}/${projectPath}/-/refs/${escape(ref)}/logs_tree/${encodeURIComponent(
path.replace(/^\//, ''),
)}`,
{
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 578fcc819b0..4d60cf5b1cc 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -1,6 +1,7 @@
<script>
import FileHeader from '~/vue_shared/components/file_row_header.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
+import { escapeFileUrl } from '~/lib/utils/url_utility';
export default {
name: 'FileRow',
@@ -94,7 +95,7 @@ export default {
hasUrlAtCurrentRoute() {
if (!this.$router || !this.$router.currentRoute) return true;
- return this.$router.currentRoute.path === `/project${this.file.url}`;
+ return this.$router.currentRoute.path === `/project${escapeFileUrl(this.file.url)}`;
},
},
};
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f8832047d49..8b2c67378d9 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -1006,6 +1006,14 @@ pre.light-well {
}
}
+ &:not(.with-pipeline-status) {
+ .icon-wrapper:first-of-type {
+ @include media-breakpoint-up(lg) {
+ margin-left: $gl-padding-32;
+ }
+ }
+ }
+
.ci-status-link {
display: inline-flex;
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index db1b8c559e5..81e910f91c2 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -17,12 +17,6 @@
.tree-controls {
text-align: right;
- > .btn,
- .project-action-button > .btn,
- .git-clone-holder > .btn {
- margin-left: 8px;
- }
-
.control {
float: left;
margin-left: 10px;
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 0fd6aafef0d..e27ec571531 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -45,6 +45,12 @@
.border-bottom-color-default { border-bottom-color: $border-color; }
.box-shadow-default { box-shadow: 0 2px 4px 0 $black-transparent; }
+.gl-children-ml-sm-3 > * {
+ @include media-breakpoint-up(sm) {
+ @include gl-ml-3;
+ }
+}
+
.mh-50vh { max-height: 50vh; }
.font-size-inherit { font-size: inherit; }
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index b3d72ebdcf3..0a536a01f72 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -52,7 +52,9 @@ class BroadcastMessage < ApplicationRecord
end
def cache
- Gitlab::JsonCache.new(cache_key_with_version: false)
+ ::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do
+ Gitlab::JsonCache.new(cache_key_with_version: false)
+ end
end
def cache_expires_in
@@ -68,9 +70,9 @@ class BroadcastMessage < ApplicationRecord
now_or_future = messages.select(&:now_or_future?)
- # If there are cached entries but none are to be displayed we'll purge the
- # cache so we don't keep running this code all the time.
- cache.expire(cache_key) if now_or_future.empty?
+ # If there are cached entries but they don't match the ones we are
+ # displaying we'll refresh the cache so we don't need to keep filtering.
+ cache.expire(cache_key) if now_or_future != messages
now_or_future.select(&:now?).select { |message| message.matches_current_path(current_path) }
end
diff --git a/app/uploaders/import_export_uploader.rb b/app/uploaders/import_export_uploader.rb
index 104d5d3b3dd..b0e6464f5b1 100644
--- a/app/uploaders/import_export_uploader.rb
+++ b/app/uploaders/import_export_uploader.rb
@@ -3,6 +3,10 @@
class ImportExportUploader < AttachmentUploader
EXTENSION_WHITELIST = %w[tar.gz gz].freeze
+ def self.workhorse_local_upload_path
+ File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
+ end
+
def extension_whitelist
EXTENSION_WHITELIST
end
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index ca201e626b8..5122164dbcb 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1 +1 @@
-= render 'shared/projects/list', projects: @projects, user: current_user
+= render 'shared/projects/list', projects: @projects, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true), user: current_user
diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 35b32662b8a..d819c4ea554 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1,2 +1,2 @@
- is_explore_page = defined?(explore_page) && explore_page
-= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page
+= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true)
diff --git a/app/views/projects/blob/_breadcrumb.html.haml b/app/views/projects/blob/_breadcrumb.html.haml
index e611df8df2a..810c8b9082f 100644
--- a/app/views/projects/blob/_breadcrumb.html.haml
+++ b/app/views/projects/blob/_breadcrumb.html.haml
@@ -17,7 +17,7 @@
- else
= link_to title, project_tree_path(@project, tree_join(@ref, path))
- .tree-controls<
+ .tree-controls.gl-children-ml-sm-3<
= render 'projects/find_file_link'
-# only show normal/blame view links for text files
- if blob.readable_text?
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 4d3c24aee6b..d5f7673488f 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -75,34 +75,35 @@
= link_to new_project_tag_path(@project) do
#{ _('New tag') }
-.tree-controls{ class: ("gl-font-size-0" if vue_file_list_enabled?) }<
- = render_if_exists 'projects/tree/lock_link'
- - if vue_file_list_enabled?
- #js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
- - else
- = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
-
- = render 'projects/find_file_link'
-
- - if can_collaborate || current_user&.already_forked?(@project)
+.tree-controls
+ .d-block.d-sm-flex.flex-wrap.align-items-start.gl-children-ml-sm-3<
+ = render_if_exists 'projects/tree/lock_link'
- if vue_file_list_enabled?
- #js-tree-web-ide-link.d-inline-block
+ #js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
- else
- = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
+ = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
+
+ = render 'projects/find_file_link'
+
+ - if can_collaborate || current_user&.already_forked?(@project)
+ - if vue_file_list_enabled?
+ #js-tree-web-ide-link.d-inline-block
+ - else
+ = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
+ = _('Web IDE')
+ - elsif can_create_mr_from_fork
+ = link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do
= _('Web IDE')
- - elsif can_create_mr_from_fork
- = link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do
- = _('Web IDE')
- = render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path)
+ = render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path)
- - if show_xcode_link?(@project)
- .project-action-button.project-xcode.inline<
- = render "projects/buttons/xcode_link"
+ - if show_xcode_link?(@project)
+ .project-action-button.project-xcode.inline<
+ = render "projects/buttons/xcode_link"
- = render 'projects/buttons/download', project: @project, ref: @ref
+ = render 'projects/buttons/download', project: @project, ref: @ref
- .project-clone-holder.d-block.d-md-none.mt-sm-2.mt-md-0>
- = render "shared/mobile_clone_panel"
+ .project-clone-holder.d-none.d-md-inline-block>
+ = render "projects/buttons/clone", dropdown_class: 'dropdown-menu-right'
- .project-clone-holder.d-none.d-md-inline-block>
- = render "projects/buttons/clone", dropdown_class: 'dropdown-menu-right'
+ .project-clone-holder.d-block.d-md-none.mt-sm-2.mt-md-0.ml-sm-2>
+ = render "shared/mobile_clone_panel"
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 07a61b71b8e..45e95685677 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,7 +12,9 @@
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
+- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
- css_controls_class = compact_mode ? [] : ["flex-lg-row", "justify-content-lg-between"]
+- css_controls_class << "with-pipeline-status" if show_pipeline_status_icon
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
%li.project-row.d-flex{ class: css_class }
@@ -60,6 +62,11 @@
.controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") }
.icon-container.d-flex.align-items-center
+ - if show_pipeline_status_icon
+ - pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
+ %span.icon-wrapper.pipeline-status
+ = render 'ci/status/icon', status: project.last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
+
= render_if_exists 'shared/projects/archived', project: project
- if stars
= link_to project_starrers_path(project),
diff --git a/changelogs/unreleased/207837-circular-encoding.yml b/changelogs/unreleased/207837-circular-encoding.yml
new file mode 100644
index 00000000000..30aa7bf88a5
--- /dev/null
+++ b/changelogs/unreleased/207837-circular-encoding.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed regression when URL was encoded in a loop
+merge_request: 25849
+author:
+type: fixed
diff --git a/changelogs/unreleased/207857-fix-web-ide-modal-no-text.yml b/changelogs/unreleased/207857-fix-web-ide-modal-no-text.yml
new file mode 100644
index 00000000000..74bbb312f19
--- /dev/null
+++ b/changelogs/unreleased/207857-fix-web-ide-modal-no-text.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Web IDE fork modal showing no text
+merge_request: 25842
+author:
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-fix-import-export-uploader.yml b/changelogs/unreleased/georgekoltsov-fix-import-export-uploader.yml
new file mode 100644
index 00000000000..0c43c93ce89
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-fix-import-export-uploader.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Group Import API file upload when object storage is disabled
+merge_request: 25715
+author:
+type: fixed
diff --git a/changelogs/unreleased/lm-fix-error-query.yml b/changelogs/unreleased/lm-fix-error-query.yml
new file mode 100644
index 00000000000..2baa316dd5a
--- /dev/null
+++ b/changelogs/unreleased/lm-fix-error-query.yml
@@ -0,0 +1,5 @@
+---
+title: Fix search for Sentry error list
+merge_request: 26129
+author:
+type: fixed
diff --git a/changelogs/unreleased/ph-p207499-fixNonAsciiChars.yml b/changelogs/unreleased/ph-p207499-fixNonAsciiChars.yml
new file mode 100644
index 00000000000..806f4372f89
--- /dev/null
+++ b/changelogs/unreleased/ph-p207499-fixNonAsciiChars.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed repository browsing for folders with non-ascii characters
+merge_request: 25877
+author:
+type: fixed
diff --git a/changelogs/unreleased/revert-e0613e64.yml b/changelogs/unreleased/revert-e0613e64.yml
new file mode 100644
index 00000000000..e94f1df2f4b
--- /dev/null
+++ b/changelogs/unreleased/revert-e0613e64.yml
@@ -0,0 +1,5 @@
+---
+title: Show CI status in project dashboards
+merge_request: 26403
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-disable-line-in-marginalia.yml b/changelogs/unreleased/sh-disable-line-in-marginalia.yml
new file mode 100644
index 00000000000..51be4db1965
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-line-in-marginalia.yml
@@ -0,0 +1,5 @@
+---
+title: Disable Marginalia line backtrace in production
+merge_request: 26199
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-optimize-broadcast-message-redis.yml b/changelogs/unreleased/sh-optimize-broadcast-message-redis.yml
new file mode 100644
index 00000000000..c69f3ded73d
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-broadcast-message-redis.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unnecessary Redis deletes for broadcast messages
+merge_request: 26541
+author:
+type: performance
diff --git a/changelogs/unreleased/use-addressable-for-asset-proxy-badge-render.yml b/changelogs/unreleased/use-addressable-for-asset-proxy-badge-render.yml
new file mode 100644
index 00000000000..6a021df8027
--- /dev/null
+++ b/changelogs/unreleased/use-addressable-for-asset-proxy-badge-render.yml
@@ -0,0 +1,5 @@
+---
+title: Rescue invalid URLs during badge retrieval in asset proxy
+merge_request: 26524
+author:
+type: fixed
diff --git a/changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml b/changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml
new file mode 100644
index 00000000000..1863f0de26f
--- /dev/null
+++ b/changelogs/unreleased/vs-add-credentials-option-for-apollo-link.yml
@@ -0,0 +1,5 @@
+---
+title: Send credentials with GraphQL fetch requests
+merge_request: 26386
+author:
+type: fixed
diff --git a/config/initializers/0_marginalia.rb b/config/initializers/0_marginalia.rb
index f88a90854e3..a697f67dbf2 100644
--- a/config/initializers/0_marginalia.rb
+++ b/config/initializers/0_marginalia.rb
@@ -9,7 +9,13 @@ require 'marginalia'
# Refer: https://github.com/basecamp/marginalia/blob/v1.8.0/lib/marginalia/railtie.rb#L67
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Gitlab::Marginalia::ActiveRecordInstrumentation)
-Marginalia::Comment.components = [:application, :controller, :action, :correlation_id, :jid, :job_class, :line]
+Marginalia::Comment.components = [:application, :controller, :action, :correlation_id, :jid, :job_class]
+
+# As mentioned in https://github.com/basecamp/marginalia/pull/93/files,
+# adding :line has some overhead because a regexp on the backtrace has
+# to be run on every SQL query. Only enable this in development because
+# we've seen it slow things down.
+Marginalia::Comment.components << :line if Rails.env.development?
Gitlab::Marginalia.set_application_name
diff --git a/doc/user/incident_management/index.md b/doc/user/incident_management/index.md
index c644c5801df..880083bf815 100644
--- a/doc/user/incident_management/index.md
+++ b/doc/user/incident_management/index.md
@@ -65,6 +65,11 @@ alert is resolved.
Metrics can be embedded anywhere where GitLab Markdown is used, for example,
descriptions and comments on issues and merge requests.
+This can be useful for when you're sharing metrics, such as for discussing
+an incident or performance issues, so you can output the dashboard directly
+into any issue, merge request, epic, or any other Markdown text field in GitLab
+by simply [copying and pasting the link to the metrics dashboard](../project/integrations/prometheus.md#embedding-gitlab-managed-kubernetes-metrics).
+
TIP: **Tip:**
Both GitLab-hosted and Grafana metrics can also be
[embedded in issue templates](../project/integrations/prometheus.md#embedding-metrics-in-issue-templates).
@@ -73,6 +78,32 @@ Both GitLab-hosted and Grafana metrics can also be
Learn how to embed [GitLab hosted metric charts](../project/integrations/prometheus.md#embedding-metric-charts-within-gitlab-flavored-markdown).
+#### Context menu
+
+From each of the embedded metrics panels, you can access more details
+about the data you are viewing from a context menu.
+
+You can access the context menu by clicking the **{ellipsis_v}** **More actions**
+dropdown box above the upper right corner of the panel:
+
+The options are:
+
+- [View logs](#view-logs-ultimate) **(ULTIMATE)**
+- [Download CSV](#download-csv)
+
+##### View logs **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/201846) in GitLab Ultimate 12.8.
+
+This can be useful if you are triaging an application incident and need to
+[explore logs](../project/integrations/prometheus.md#view-pod-logs-ultimate)
+from across your application. It also helps you to understand
+what is affecting your application's performance and quickly resolve any problems.
+
+##### Download CSV
+
+Data from embedded charts can be [downloaded as CSV](../project/integrations/prometheus.md#downloading-data-as-csv).
+
### Grafana metrics
Learn how to embed [Grafana hosted metric charts](../project/integrations/prometheus.md#embedding-grafana-charts).
diff --git a/doc/user/project/integrations/img/embedded_metrics_markdown_v12_8.png b/doc/user/project/integrations/img/embedded_metrics_markdown_v12_8.png
new file mode 100644
index 00000000000..ffd34705464
--- /dev/null
+++ b/doc/user/project/integrations/img/embedded_metrics_markdown_v12_8.png
Binary files differ
diff --git a/doc/user/project/integrations/img/embedded_metrics_rendered_v12_8.png b/doc/user/project/integrations/img/embedded_metrics_rendered_v12_8.png
new file mode 100644
index 00000000000..b024daaaa8e
--- /dev/null
+++ b/doc/user/project/integrations/img/embedded_metrics_rendered_v12_8.png
Binary files differ
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index b8ba27bb2ca..a1c551db604 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -492,7 +492,9 @@ The options are:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/122013) in GitLab 12.8.
-If you have [Kubernetes Pod Logs](../clusters/kubernetes_pod_logs.md) enabled, you can navigate from the charts in the dashboard to view Pod Logs by clicking on the context menu in the upper-right corner.
+If you have [Pod Logs](../clusters/kubernetes_pod_logs.md) enabled,
+you can navigate from the charts in the dashboard to view Pod Logs by
+clicking on the context menu in the upper-right corner.
If you use the **Timeline zoom** function at the bottom of the chart, logs will narrow down to the time range you selected.
@@ -608,10 +610,19 @@ Prometheus server.
It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm). The maximum number of embeds allowed in a GitLab Flavored Markdown field is 100.
+This can be useful if you are sharing an application incident or performance
+metrics to others and want to have relevant information directly available.
+
NOTE: **Note:**
Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
-To display a metric chart, include a link of the form `https://<root_url>/<project>/-/environments/<environment_id>/metrics`.
+To display metric charts, include a link of the form `https://<root_url>/<project>/-/environments/<environment_id>/metrics`:
+
+![Embedded Metrics Markdown](img/embedded_metrics_markdown_v12_8.png)
+
+GitLab unfurls the link as an embedded metrics panel:
+
+![Embedded Metrics Rendered](img/embedded_metrics_rendered_v12_8.png)
A single chart may also be embedded. You can generate a link to the chart via the dropdown located on the right side of the chart:
diff --git a/lib/gitlab/asset_proxy.rb b/lib/gitlab/asset_proxy.rb
index fd7c58ba68f..fb4369e01d8 100644
--- a/lib/gitlab/asset_proxy.rb
+++ b/lib/gitlab/asset_proxy.rb
@@ -11,12 +11,14 @@ module Gitlab
return url if asset_host_whitelisted?(url)
"#{Gitlab.config.asset_proxy.url}/#{asset_url_hash(url)}/#{hexencode(url)}"
+ rescue Addressable::URI::InvalidURIError
+ url
end
private
def asset_host_whitelisted?(url)
- parsed_url = URI.parse(url)
+ parsed_url = Addressable::URI.parse(url)
Gitlab.config.asset_proxy.domain_regexp&.match?(parsed_url.host)
end
diff --git a/lib/gitlab/git/rugged_impl/repository.rb b/lib/gitlab/git/rugged_impl/repository.rb
index 8fde93e71e2..8679d977773 100644
--- a/lib/gitlab/git/rugged_impl/repository.rb
+++ b/lib/gitlab/git/rugged_impl/repository.rb
@@ -70,7 +70,7 @@ module Gitlab
# Lookup for rugged object by oid or ref name
def lookup(oid_or_ref_name)
- rugged.rev_parse(oid_or_ref_name)
+ rev_parse_target(oid_or_ref_name)
end
end
end
diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb
index 76018cb23c4..5df53b5adde 100644
--- a/lib/gitlab/tree_summary.rb
+++ b/lib/gitlab/tree_summary.rb
@@ -69,7 +69,7 @@ module Gitlab
end
def entry_path(entry)
- File.join(*[path, entry[:file_name]].compact)
+ File.join(*[path, entry[:file_name]].compact).force_encoding(Encoding::ASCII_8BIT)
end
def build_entry(entry)
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 9bd2e85e3b8..73f759f8a54 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -152,6 +152,61 @@ describe 'Dashboard Projects' do
end
end
+ describe 'with a pipeline', :clean_gitlab_redis_shared_state do
+ let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha, ref: project.default_branch) }
+
+ before do
+ # Since the cache isn't updated when a new pipeline is created
+ # we need the pipeline to advance in the pipeline since the cache was created
+ # by visiting the login page.
+ pipeline.succeed
+ end
+
+ it 'shows that the last pipeline passed' do
+ visit dashboard_projects_path
+
+ page.within('.controls') do
+ expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
+ expect(page).to have_css('.ci-status-link')
+ expect(page).to have_css('.ci-status-icon-success')
+ expect(page).to have_link('Pipeline: passed')
+ end
+ end
+
+ shared_examples 'hidden pipeline status' do
+ it 'does not show the pipeline status' do
+ visit dashboard_projects_path
+
+ page.within('.controls') do
+ expect(page).not_to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
+ expect(page).not_to have_css('.ci-status-link')
+ expect(page).not_to have_css('.ci-status-icon-success')
+ expect(page).not_to have_link('Pipeline: passed')
+ end
+ end
+ end
+
+ context 'guest user of project and project has private pipelines' do
+ let(:guest_user) { create(:user) }
+
+ before do
+ project.update(public_builds: false)
+ project.add_guest(guest_user)
+ sign_in(guest_user)
+ end
+
+ it_behaves_like 'hidden pipeline status'
+ end
+
+ context 'when dashboard_pipeline_status is disabled' do
+ before do
+ stub_feature_flags(dashboard_pipeline_status: false)
+ end
+
+ it_behaves_like 'hidden pipeline status'
+ end
+ end
+
context 'last push widget', :use_clean_rails_memory_store_caching do
before do
event = create(:push_event, project: project, author: user)
diff --git a/spec/features/error_tracking/user_searches_sentry_errors_spec.rb b/spec/features/error_tracking/user_searches_sentry_errors_spec.rb
new file mode 100644
index 00000000000..690c60a3c3f
--- /dev/null
+++ b/spec/features/error_tracking/user_searches_sentry_errors_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'When a user searches for Sentry errors', :js, :use_clean_rails_memory_store_caching, :sidekiq_inline do
+ include_context 'sentry error tracking context feature'
+
+ let_it_be(:issues_response_body) { fixture_file('sentry/issues_sample_response.json') }
+ let_it_be(:error_search_response_body) { fixture_file('sentry/error_list_search_response.json') }
+ let(:issues_api_url) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved" }
+ let(:issues_api_url_search) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved%20NotFound" }
+
+ before do
+ stub_request(:get, issues_api_url).with(
+ headers: { 'Authorization' => 'Bearer access_token_123' }
+ ).to_return(status: 200, body: issues_response_body, headers: { 'Content-Type' => 'application/json' })
+
+ stub_request(:get, issues_api_url_search).with(
+ headers: { 'Authorization' => 'Bearer access_token_123', 'Content-Type' => 'application/json' }
+ ).to_return(status: 200, body: error_search_response_body, headers: { 'Content-Type' => 'application/json' })
+ end
+
+ it 'displays the results' do
+ sign_in(project.owner)
+ visit project_error_tracking_index_path(project)
+
+ page.within(find('.gl-table')) do
+ results = page.all('.table-row')
+ expect(results.count).to be(2)
+ end
+
+ find('.gl-form-input').set('NotFound').native.send_keys(:return)
+
+ page.within(find('.gl-table')) do
+ results = page.all('.table-row')
+ expect(results.count).to be(1)
+ expect(results.first).to have_content('NotFound')
+ end
+ end
+end
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index 23b13858096..2407a9e6ea3 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -37,6 +37,16 @@ describe 'Projects tree', :js do
expect(page).not_to have_selector('.flash-alert')
end
+ it 'renders tree table with non-ASCII filenames without errors' do
+ visit project_tree_path(project, File.join(test_sha, 'encoding'))
+ wait_for_requests
+
+ expect(page).to have_selector('.tree-item')
+ expect(page).to have_content('Files, encoding and much more')
+ expect(page).to have_content('テスト.txt')
+ expect(page).not_to have_selector('.flash-alert')
+ end
+
context 'gravatar disabled' do
let(:gravatar_enabled) { false }
diff --git a/spec/fixtures/sentry/error_list_search_response.json b/spec/fixtures/sentry/error_list_search_response.json
new file mode 100644
index 00000000000..e77c837b48c
--- /dev/null
+++ b/spec/fixtures/sentry/error_list_search_response.json
@@ -0,0 +1,42 @@
+[{
+ "lastSeen": "2018-12-31T12:00:11Z",
+ "numComments": 0,
+ "userCount": 0,
+ "stats": {
+ "24h": [
+ [
+ 1546437600,
+ 0
+ ]
+ ]
+ },
+ "culprit": "sentry.tasks.reports.deliver_organization_user_report",
+ "title": "NotFound desc = GetRepoPath: not a git repository",
+ "id": "13",
+ "assignedTo": null,
+ "logger": null,
+ "type": "error",
+ "annotations": [],
+ "metadata": {
+ "type": "gaierror",
+ "value": "[Errno -2] Name or service not known"
+ },
+ "status": "unresolved",
+ "subscriptionDetails": null,
+ "isPublic": false,
+ "hasSeen": false,
+ "shortId": "INTERNAL-4",
+ "shareId": null,
+ "firstSeen": "2018-12-17T12:00:14Z",
+ "count": "17283712",
+ "permalink": "35.228.54.90/sentry/internal/issues/13/",
+ "level": "error",
+ "isSubscribed": true,
+ "isBookmarked": false,
+ "project": {
+ "slug": "internal",
+ "id": "1",
+ "name": "Internal"
+ },
+ "statusDetails": {}
+}]
diff --git a/spec/fixtures/sentry/issues_sample_response.json b/spec/fixtures/sentry/issues_sample_response.json
index ed22499cfa1..495562ac960 100644
--- a/spec/fixtures/sentry/issues_sample_response.json
+++ b/spec/fixtures/sentry/issues_sample_response.json
@@ -1,4 +1,5 @@
-[{
+[
+ {
"lastSeen": "2018-12-31T12:00:11Z",
"numComments": 0,
"userCount": 0,
@@ -39,4 +40,47 @@
"name": "Internal"
},
"statusDetails": {}
- }]
+ },
+ {
+ "lastSeen": "2018-12-31T12:00:11Z",
+ "numComments": 0,
+ "userCount": 0,
+ "stats": {
+ "24h": [
+ [
+ 1546437600,
+ 0
+ ]
+ ]
+ },
+ "culprit": "sentry.tasks.reports.deliver_organization_user_report",
+ "title": "NotFound desc = GetRepoPath: not a git repository",
+ "id": "13",
+ "assignedTo": null,
+ "logger": null,
+ "type": "error",
+ "annotations": [],
+ "metadata": {
+ "type": "gaierror",
+ "value": "GetRepoPath: not a git repository"
+ },
+ "status": "unresolved",
+ "subscriptionDetails": null,
+ "isPublic": false,
+ "hasSeen": false,
+ "shortId": "INTERNAL-4",
+ "shareId": null,
+ "firstSeen": "2018-12-17T12:00:14Z",
+ "count": "17283712",
+ "permalink": "35.228.54.90/sentry/internal/issues/13/",
+ "level": "error",
+ "isSubscribed": true,
+ "isBookmarked": false,
+ "project": {
+ "slug": "internal",
+ "id": "1",
+ "name": "Internal"
+ },
+ "statusDetails": {}
+ }
+]
diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
index b632b461eb9..f852a3091aa 100644
--- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -42,9 +42,6 @@ describe('ErrorTrackingList', () => {
...stubChildren(ErrorTrackingList),
...stubs,
},
- data() {
- return { errorSearchQuery: 'search' };
- },
});
}
@@ -164,8 +161,9 @@ describe('ErrorTrackingList', () => {
});
it('it searches by query', () => {
+ findSearchBox().vm.$emit('input', 'search');
findSearchBox().trigger('keyup.enter');
- expect(actions.searchByQuery.mock.calls[0][1]).toEqual(wrapper.vm.errorSearchQuery);
+ expect(actions.searchByQuery.mock.calls[0][1]).toBe('search');
});
it('it sorts by fields', () => {
diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js
index 34eb57ae0d3..2b15aef6454 100644
--- a/spec/frontend/ide/lib/files_spec.js
+++ b/spec/frontend/ide/lib/files_spec.js
@@ -1,7 +1,6 @@
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import { decorateFiles, splitParent } from '~/ide/lib/files';
import { decorateData } from '~/ide/stores/utils';
-import { escapeFileUrl } from '~/lib/utils/url_utility';
const TEST_BRANCH_ID = 'lorem-ipsum';
const TEST_PROJECT_ID = 10;
@@ -22,7 +21,7 @@ const createEntries = paths => {
id: path,
name,
path,
- url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${escapeFileUrl(path)}`),
+ url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${path}`),
type,
previewMode,
binary: (previewMode && previewMode.binary) || false,
diff --git a/spec/frontend/ide/stores/mutations_spec.js b/spec/frontend/ide/stores/mutations_spec.js
index 9fe75d596fb..d9ce59ad378 100644
--- a/spec/frontend/ide/stores/mutations_spec.js
+++ b/spec/frontend/ide/stores/mutations_spec.js
@@ -494,7 +494,7 @@ describe('Multi-file store mutations', () => {
it('properly handles files with spaces in name', () => {
const path = 'my fancy path';
const newPath = 'new path';
- const oldEntry = { ...file(path, path, 'blob'), url: `project/-/${encodeURI(path)}` };
+ const oldEntry = { ...file(path, path, 'blob'), url: `project/-/${path}` };
localState.entries[path] = oldEntry;
@@ -510,12 +510,12 @@ describe('Multi-file store mutations', () => {
id: newPath,
path: newPath,
name: newPath,
- url: `project/-/new%20path`,
+ url: `project/-/new path`,
key: expect.stringMatching(newPath),
prevId: path,
prevName: path,
prevPath: path,
- prevUrl: `project/-/my%20fancy%20path`,
+ prevUrl: `project/-/my fancy path`,
prevKey: oldEntry.key,
prevParentPath: oldEntry.parentPath,
});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index 0271db25468..38e5c9aaca5 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -41,7 +41,7 @@ describe('Repository breadcrumbs component', () => {
.findAll(RouterLinkStub)
.at(3)
.props('to'),
- ).toEqual('/-/tree//app/assets/javascripts%23');
+ ).toEqual('/-/tree/app/assets/javascripts%23');
});
it('renders last link as active', () => {
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index fec9ba3aa2e..a51846023ac 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -109,6 +109,26 @@ describe('Repository table row component', () => {
});
});
+ it.each`
+ path
+ ${'test#'}
+ ${'Änderungen'}
+ `('renders link for $path', ({ path }) => {
+ factory({
+ id: '1',
+ sha: '123',
+ path,
+ type: 'tree',
+ currentPath: '/',
+ });
+
+ return vm.vm.$nextTick().then(() => {
+ expect(vm.find({ ref: 'link' }).props('to')).toEqual({
+ path: `/-/tree/master/${encodeURIComponent(path)}`,
+ });
+ });
+ });
+
it('pushes new route for directory with hash', () => {
factory({
id: '1',
diff --git a/spec/frontend/vue_shared/components/file_row_spec.js b/spec/frontend/vue_shared/components/file_row_spec.js
new file mode 100644
index 00000000000..b3ced84ddb5
--- /dev/null
+++ b/spec/frontend/vue_shared/components/file_row_spec.js
@@ -0,0 +1,117 @@
+import { file } from 'jest/ide/helpers';
+import FileRow from '~/vue_shared/components/file_row.vue';
+import FileHeader from '~/vue_shared/components/file_row_header.vue';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { escapeFileUrl } from '~/lib/utils/url_utility';
+
+describe('File row component', () => {
+ let wrapper;
+
+ function createComponent(propsData, $router = undefined) {
+ wrapper = shallowMount(FileRow, {
+ propsData,
+ mocks: {
+ $router,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders name', () => {
+ const fileName = 't4';
+ createComponent({
+ file: file(fileName),
+ level: 0,
+ });
+
+ const name = wrapper.find('.file-row-name');
+
+ expect(name.text().trim()).toEqual(fileName);
+ });
+
+ it('emits toggleTreeOpen on click', () => {
+ const fileName = 't3';
+ createComponent({
+ file: {
+ ...file(fileName),
+ type: 'tree',
+ },
+ level: 0,
+ });
+ jest.spyOn(wrapper.vm, '$emit');
+
+ wrapper.element.click();
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', fileName);
+ });
+
+ it('calls scrollIntoView if made active', () => {
+ createComponent({
+ file: {
+ ...file(),
+ type: 'blob',
+ active: false,
+ },
+ level: 0,
+ });
+
+ jest.spyOn(wrapper.vm, 'scrollIntoView');
+
+ wrapper.setProps({
+ file: Object.assign({}, wrapper.props('file'), {
+ active: true,
+ }),
+ });
+
+ return nextTick().then(() => {
+ expect(wrapper.vm.scrollIntoView).toHaveBeenCalled();
+ });
+ });
+
+ it('indents row based on level', () => {
+ createComponent({
+ file: file('t4'),
+ level: 2,
+ });
+
+ expect(wrapper.find('.file-row-name').element.style.marginLeft).toBe('32px');
+ });
+
+ it('renders header for file', () => {
+ createComponent({
+ file: {
+ isHeader: true,
+ path: 'app/assets',
+ tree: [],
+ },
+ level: 0,
+ });
+
+ expect(wrapper.contains(FileHeader)).toBe(true);
+ });
+
+ it('matches the current route against encoded file URL', () => {
+ const fileName = 'with space';
+ const rowFile = Object.assign({}, file(fileName), {
+ url: `/${fileName}`,
+ });
+ const routerPath = `/project/${escapeFileUrl(fileName)}`;
+ createComponent(
+ {
+ file: rowFile,
+ level: 0,
+ },
+ {
+ currentRoute: {
+ path: routerPath,
+ },
+ },
+ );
+
+ expect(wrapper.vm.hasUrlAtCurrentRoute()).toBe(true);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
deleted file mode 100644
index 11fcb9b89c1..00000000000
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import Vue from 'vue';
-import { file } from 'spec/ide/helpers';
-import FileRow from '~/vue_shared/components/file_row.vue';
-import mountComponent from '../../helpers/vue_mount_component_helper';
-
-describe('File row component', () => {
- let vm;
-
- function createComponent(propsData) {
- const FileRowComponent = Vue.extend(FileRow);
-
- vm = mountComponent(FileRowComponent, propsData);
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders name', () => {
- createComponent({
- file: file('t4'),
- level: 0,
- });
-
- const name = vm.$el.querySelector('.file-row-name');
-
- expect(name.textContent.trim()).toEqual(vm.file.name);
- });
-
- it('emits toggleTreeOpen on click', () => {
- createComponent({
- file: {
- ...file('t3'),
- type: 'tree',
- },
- level: 0,
- });
- spyOn(vm, '$emit').and.stub();
-
- vm.$el.click();
-
- expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
- });
-
- it('calls scrollIntoView if made active', done => {
- createComponent({
- file: {
- ...file(),
- type: 'blob',
- active: false,
- },
- level: 0,
- });
-
- spyOn(vm, 'scrollIntoView').and.stub();
-
- vm.file.active = true;
-
- vm.$nextTick(() => {
- expect(vm.scrollIntoView).toHaveBeenCalled();
-
- done();
- });
- });
-
- it('indents row based on level', () => {
- createComponent({
- file: file('t4'),
- level: 2,
- });
-
- expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
- });
-
- it('renders header for file', () => {
- createComponent({
- file: {
- isHeader: true,
- path: 'app/assets',
- tree: [],
- },
- level: 0,
- });
-
- expect(vm.$el.classList).toContain('js-file-row-header');
- });
-});
diff --git a/spec/lib/gitlab/asset_proxy_spec.rb b/spec/lib/gitlab/asset_proxy_spec.rb
index f5aa1819982..e406917a5a4 100644
--- a/spec/lib/gitlab/asset_proxy_spec.rb
+++ b/spec/lib/gitlab/asset_proxy_spec.rb
@@ -33,9 +33,15 @@ describe Gitlab::AssetProxy do
expect(described_class.proxy_url(url)).to eq(proxied_url)
end
+ it 'returns original URL for invalid domains' do
+ url = 'foo_bar://'
+
+ expect(described_class.proxy_url(url)).to eq(url)
+ end
+
context 'whitelisted domain' do
it 'returns original URL for single domain whitelist' do
- url = 'http://gitlab.com/test.png'
+ url = 'http://gitlab.com/${default_branch}/test.png'
expect(described_class.proxy_url(url)).to eq(url)
end
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index e15463ed0eb..d64b826ba9b 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -129,6 +129,17 @@ describe Gitlab::TreeSummary do
expect(commits).to satisfy_one { |c| c.id == whitespace_commit_sha }
end
end
+
+ context 'in a subdirectory with non-ASCII filenames' do
+ let(:path) { 'encoding' }
+
+ it 'returns commits for entries in the subdirectory' do
+ entry = entries.find { |x| x[:file_name] == 'テスト.txt' }
+
+ expect(entry).to be_a(Hash)
+ expect(entry).to include(:commit)
+ end
+ end
end
describe '#more?' do
diff --git a/spec/lib/marginalia_spec.rb b/spec/lib/marginalia_spec.rb
index db428bb65c4..d4b84c5cdc4 100644
--- a/spec/lib/marginalia_spec.rb
+++ b/spec/lib/marginalia_spec.rb
@@ -59,7 +59,6 @@ describe 'Marginalia spec' do
"application" => "test",
"controller" => "marginalia_test",
"action" => "first_user",
- "line" => "/spec/support/helpers/query_recorder.rb",
"correlation_id" => correlation_id
}
end
@@ -116,7 +115,6 @@ describe 'Marginalia spec' do
{
"application" => "sidekiq",
"job_class" => "MarginaliaTestJob",
- "line" => "/spec/support/sidekiq_middleware.rb",
"correlation_id" => sidekiq_job['correlation_id'],
"jid" => sidekiq_job['jid']
}
@@ -145,7 +143,6 @@ describe 'Marginalia spec' do
let(:component_map) do
{
"application" => "sidekiq",
- "line" => "/lib/gitlab/i18n.rb",
"jid" => delivery_job.job_id,
"job_class" => delivery_job.arguments.first
}
diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb
index 2762c5b5cb9..d35e4b83d7f 100644
--- a/spec/lib/sentry/client/issue_spec.rb
+++ b/spec/lib/sentry/client/issue_spec.rb
@@ -49,7 +49,7 @@ describe Sentry::Client::Issue do
it_behaves_like 'calls sentry api'
it_behaves_like 'issues have correct return type', Gitlab::ErrorTracking::Error
- it_behaves_like 'issues have correct length', 1
+ it_behaves_like 'issues have correct length', 2
shared_examples 'has correct external_url' do
context 'external_url' do
@@ -184,7 +184,7 @@ describe Sentry::Client::Issue do
it_behaves_like 'calls sentry api'
it_behaves_like 'issues have correct return type', Gitlab::ErrorTracking::Error
- it_behaves_like 'issues have correct length', 1
+ it_behaves_like 'issues have correct length', 2
end
context 'when cursor is present' do
@@ -194,7 +194,7 @@ describe Sentry::Client::Issue do
it_behaves_like 'calls sentry api'
it_behaves_like 'issues have correct return type', Gitlab::ErrorTracking::Error
- it_behaves_like 'issues have correct length', 1
+ it_behaves_like 'issues have correct length', 2
end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 67d8284bebe..6cef81d6e44 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -65,6 +65,17 @@ describe BroadcastMessage do
end
end
+ it 'expires the value if a broadcast message has ended', :request_store do
+ message = create(:broadcast_message, broadcast_type: broadcast_type, ends_at: Time.now.utc + 1.day)
+
+ expect(subject.call).to match_array([message])
+ expect(described_class.cache).to receive(:expire).and_call_original
+
+ Timecop.travel(1.week) do
+ 2.times { expect(subject.call).to be_empty }
+ end
+ end
+
it 'does not create new records' do
create(:broadcast_message, broadcast_type: broadcast_type)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 00ffc3cae54..a8cdebd2b9c 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2366,7 +2366,7 @@ describe Repository do
end
end
- describe '#tree' do
+ shared_examples '#tree' do
context 'using a non-existing repository' do
before do
allow(repository).to receive(:head_commit).and_return(nil)
@@ -2384,10 +2384,17 @@ describe Repository do
context 'using an existing repository' do
it 'returns a Tree' do
expect(repository.tree(:head)).to be_an_instance_of(Tree)
+ expect(repository.tree('v1.1.1')).to be_an_instance_of(Tree)
end
end
end
+ it_behaves_like '#tree'
+
+ describe '#tree? with Rugged enabled', :enable_rugged do
+ it_behaves_like '#tree'
+ end
+
describe '#size' do
context 'with a non-existing repository' do
it 'returns 0' do
diff --git a/spec/support_specs/helpers/active_record/query_recorder_spec.rb b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
index 48069c6a766..0827ce37b07 100644
--- a/spec/support_specs/helpers/active_record/query_recorder_spec.rb
+++ b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
@@ -14,9 +14,6 @@ describe ActiveRecord::QueryRecorder do
TestQueries.first
end
- # Test first_only flag works as expected
- expect(control.find_query(/.*query_recorder_spec.rb.*/, 0, first_only: true))
- .to eq(control.find_query(/.*query_recorder_spec.rb.*/, 0).first)
# Check #find_query
expect(control.find_query(/.*/, 0).size)
.to eq(control.data.keys.size)
@@ -32,9 +29,7 @@ describe ActiveRecord::QueryRecorder do
# Ensure memoization value match the raw value above
expect(control.count).to eq(control.log.size)
# Ensure we have only two sources of queries
- expect(control.data.keys.size).to eq(2)
- # Ensure we detect only queries from this file
- expect(control.data.keys.find_all { |i| i.match(/query_recorder_spec.rb/) }.count).to eq(2)
+ expect(control.data.keys.size).to eq(1)
end
end
end
diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb
index 7e8937ff5a6..33cab911f86 100644
--- a/spec/uploaders/import_export_uploader_spec.rb
+++ b/spec/uploaders/import_export_uploader_spec.rb
@@ -51,4 +51,10 @@ describe ImportExportUploader do
end
end
end
+
+ describe '.workhorse_local_upload_path' do
+ it 'returns path that includes uploads dir' do
+ expect(described_class.workhorse_local_upload_path).to end_with('/uploads/tmp/uploads')
+ end
+ end
end