提交 09432c7f 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@12-8-stable-ee

上级 e2aba308
......@@ -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…')"
......
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,
......
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) =>
......
......@@ -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({
......
<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)}`,
});
},
[
......
......@@ -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: {
......
......@@ -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"
......
......@@ -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(/^\//, ''),
)}`,
{
......
<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)}`;
},
},
};
......
......@@ -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;
}
......
......@@ -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;
......
......@@ -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; }
......
......@@ -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
......
......@@ -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
......
= 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
- 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)
......@@ -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?
......
......@@ -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"
......@@ -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),
......
---
title: Fixed regression when URL was encoded in a loop
merge_request: 25849
author:
type: fixed
---
title: Fix Web IDE fork modal showing no text
merge_request: 25842
author:
type: fixed
---
title: Fix Group Import API file upload when object storage is disabled
merge_request: 25715
author:
type: fixed
---
title: Fix search for Sentry error list
merge_request: 26129
author:
type: fixed
---
title: Fixed repository browsing for folders with non-ascii characters
merge_request: 25877
author:
type: fixed
---
title: Show CI status in project dashboards
merge_request: 26403
author:
type: fixed
---
title: Disable Marginalia line backtrace in production
merge_request: 26199
author:
type: performance
---
title: Remove unnecessary Redis deletes for broadcast messages
merge_request: 26541
author:
type: performance
---
title: Rescue invalid URLs during badge retrieval in asset proxy
merge_request: 26524
author:
type: fixed
---
title: Send credentials with GraphQL fetch requests
merge_request: 26386
author:
type: fixed
......@@ -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
......
......@@ -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).
......
......@@ -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:
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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)
......
# 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
......@@ -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 }
......
[{
"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": {}
}]
[{
[
{
"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": {}
}
]
......@@ -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', () => {
......
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,
......
......@@ -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,
});
......
......@@ -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', () => {
......
......@@ -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',
......
import Vue from 'vue';
import { file } from 'spec/ide/helpers';
import { file } from 'jest/ide/helpers';
import FileRow from '~/vue_shared/components/file_row.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
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 vm;
let wrapper;
function createComponent(propsData) {
const FileRowComponent = Vue.extend(FileRow);
vm = mountComponent(FileRowComponent, propsData);
function createComponent(propsData, $router = undefined) {
wrapper = shallowMount(FileRow, {
propsData,
mocks: {
$router,
},
});
}
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
it('renders name', () => {
const fileName = 't4';
createComponent({
file: file('t4'),
file: file(fileName),
level: 0,
});
const name = vm.$el.querySelector('.file-row-name');
const name = wrapper.find('.file-row-name');
expect(name.textContent.trim()).toEqual(vm.file.name);
expect(name.text().trim()).toEqual(fileName);
});
it('emits toggleTreeOpen on click', () => {
const fileName = 't3';
createComponent({
file: {
...file('t3'),
...file(fileName),
type: 'tree',
},
level: 0,
});
spyOn(vm, '$emit').and.stub();
jest.spyOn(wrapper.vm, '$emit');
vm.$el.click();
wrapper.element.click();
expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
expect(wrapper.vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', fileName);
});
it('calls scrollIntoView if made active', done => {
it('calls scrollIntoView if made active', () => {
createComponent({
file: {
...file(),
......@@ -52,14 +59,16 @@ describe('File row component', () => {
level: 0,
});
spyOn(vm, 'scrollIntoView').and.stub();
vm.file.active = true;
jest.spyOn(wrapper.vm, 'scrollIntoView');
vm.$nextTick(() => {
expect(vm.scrollIntoView).toHaveBeenCalled();
wrapper.setProps({
file: Object.assign({}, wrapper.props('file'), {
active: true,
}),
});
done();
return nextTick().then(() => {
expect(wrapper.vm.scrollIntoView).toHaveBeenCalled();
});
});
......@@ -69,7 +78,7 @@ describe('File row component', () => {
level: 2,
});
expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
expect(wrapper.find('.file-row-name').element.style.marginLeft).toBe('32px');
});
it('renders header for file', () => {
......@@ -82,6 +91,27 @@ describe('File row component', () => {
level: 0,
});
expect(vm.$el.classList).toContain('js-file-row-header');
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);
});
});
......@@ -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
......
......@@ -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
......
......@@ -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
}
......
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......@@ -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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册