summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-29 09:06:31 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-29 09:06:31 +0000
commit6b13a226ddfc49140d58e7e88f8703ae0ed90574 (patch)
tree9a92431e484354f43230fa87adc00a2edbf6f09c /app
parent2ac93cb80c4c0a57fde86de8262b569d1e9b9e51 (diff)
downloadgitlab-ce-6b13a226ddfc49140d58e7e88f8703ae0ed90574.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue11
-rw-r--r--app/assets/javascripts/error_tracking/components/stacktrace.vue2
-rw-r--r--app/assets/javascripts/error_tracking/components/stacktrace_entry.vue63
-rw-r--r--app/assets/javascripts/error_tracking/store/details/getters.js5
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue5
-rw-r--r--app/assets/javascripts/performance_bar/index.js50
-rw-r--r--app/assets/javascripts/performance_bar/stores/performance_bar_store.js10
-rw-r--r--app/assets/javascripts/user_popovers.js1
-rw-r--r--app/assets/stylesheets/pages/error_details.scss6
-rw-r--r--app/models/clusters/applications/knative.rb10
-rw-r--r--app/models/deployment.rb7
-rw-r--r--app/models/environment_status.rb2
-rw-r--r--app/models/project.rb7
-rw-r--r--app/serializers/deployment_entity.rb11
-rw-r--r--app/serializers/environment_status_entity.rb4
-rw-r--r--app/serializers/merge_request_poll_widget_entity.rb4
-rw-r--r--app/serializers/pipeline_entity.rb4
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/views/shared/notifications/_custom_notifications.html.haml2
19 files changed, 164 insertions, 42 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 a001b315d4f..9d8e5396dea 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -8,7 +8,6 @@ import {
GlTable,
GlSearchBoxByClick,
} from '@gitlab/ui';
-import { visitUrl } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale';
@@ -76,8 +75,8 @@ export default {
this.startPolling(`${this.indexPath}?search_term=${this.errorSearchQuery}`);
},
trackViewInSentryOptions,
- viewDetails(errorId) {
- visitUrl(`error_tracking/${errorId}/details`);
+ getDetailsLink(errorId) {
+ return `error_tracking/${errorId}/details`;
},
},
};
@@ -129,11 +128,7 @@ export default {
</template>
<template slot="error" slot-scope="errors">
<div class="d-flex flex-column">
- <gl-link
- class="d-flex text-dark"
- target="_blank"
- @click="viewDetails(errors.item.id)"
- >
+ <gl-link class="d-flex text-dark" :href="getDetailsLink(errors.item.id)">
<strong class="text-truncate">{{ errors.item.title.trim() }}</strong>
</gl-link>
<span class="text-secondary text-truncate">
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace.vue b/app/assets/javascripts/error_tracking/components/stacktrace.vue
index 6b71967624f..f58d54f2933 100644
--- a/app/assets/javascripts/error_tracking/components/stacktrace.vue
+++ b/app/assets/javascripts/error_tracking/components/stacktrace.vue
@@ -27,6 +27,8 @@ export default {
:lines="entry.context"
:file-path="entry.filename"
:error-line="entry.lineNo"
+ :error-fn="entry.function"
+ :error-column="entry.colNo"
:expanded="isFirstEntry(index)"
/>
</div>
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
index ad542c579a9..9ed5b26a1c2 100644
--- a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
+++ b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
@@ -1,4 +1,5 @@
<script>
+import { __, sprintf } from '~/locale';
import { GlTooltip } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@@ -22,9 +23,20 @@ export default {
type: String,
required: true,
},
+ errorFn: {
+ type: String,
+ required: false,
+ default: '',
+ },
errorLine: {
type: Number,
- required: true,
+ required: false,
+ default: 0,
+ },
+ errorColumn: {
+ type: Number,
+ required: false,
+ default: 0,
},
expanded: {
type: Boolean,
@@ -38,12 +50,23 @@ export default {
};
},
computed: {
- linesLength() {
- return this.lines.length;
+ hasCode() {
+ return Boolean(this.lines.length);
},
collapseIcon() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';
},
+ noCodeFn() {
+ return this.errorFn ? sprintf(__('in %{errorFn} '), { errorFn: this.errorFn }) : '';
+ },
+ noCodeLine() {
+ return this.errorLine
+ ? sprintf(__('at line %{errorLine}%{errorColumn}'), {
+ errorLine: this.errorLine,
+ errorColumn: this.errorColumn ? `:${this.errorColumn}` : '',
+ })
+ : '';
+ },
},
methods: {
isHighlighted(lineNum) {
@@ -66,27 +89,31 @@ export default {
<template>
<div class="file-holder">
<div ref="header" class="file-title file-title-flex-parent">
- <div class="file-header-content ">
- <div class="d-inline-block cursor-pointer" @click="toggle()">
+ <div class="file-header-content d-flex align-content-center">
+ <div v-if="hasCode" class="d-inline-block cursor-pointer" @click="toggle()">
<icon :name="collapseIcon" :size="16" aria-hidden="true" class="append-right-5" />
</div>
- <div class="d-inline-block append-right-4">
- <file-icon
- :file-name="filePath"
- :size="18"
- aria-hidden="true"
- css-classes="append-right-5"
- />
- <strong v-gl-tooltip :title="filePath" class="file-title-name" data-container="body">
- {{ filePath }}
- </strong>
- </div>
-
+ <file-icon
+ :file-name="filePath"
+ :size="18"
+ aria-hidden="true"
+ css-classes="append-right-5"
+ />
+ <strong
+ v-gl-tooltip
+ :title="filePath"
+ class="file-title-name d-inline-block overflow-hidden text-truncate"
+ :class="{ 'limited-width': !hasCode }"
+ data-container="body"
+ >
+ {{ filePath }}
+ </strong>
<clipboard-button
:title="__('Copy file path')"
:text="filePath"
- css-class="btn-default btn-transparent btn-clipboard"
+ css-class="btn-default btn-transparent btn-clipboard position-static"
/>
+ <span v-if="!hasCode" class="text-tertiary">{{ noCodeFn }}{{ noCodeLine }}</span>
</div>
</div>
diff --git a/app/assets/javascripts/error_tracking/store/details/getters.js b/app/assets/javascripts/error_tracking/store/details/getters.js
index 7d13439d721..a36c84dc28c 100644
--- a/app/assets/javascripts/error_tracking/store/details/getters.js
+++ b/app/assets/javascripts/error_tracking/store/details/getters.js
@@ -1,3 +1,6 @@
-export const stacktrace = state => state.stacktraceData.stack_trace_entries.reverse();
+export const stacktrace = state =>
+ state.stacktraceData.stack_trace_entries
+ ? state.stacktraceData.stack_trace_entries.reverse()
+ : [];
export default () => {};
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index a0272b148e3..d17c2f33adc 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -52,6 +52,11 @@ export default {
header: s__('PerformanceBar|Redis calls'),
keys: ['cmd'],
},
+ {
+ metric: 'total',
+ header: s__('PerformanceBar|Frontend resources'),
+ keys: ['name', 'size'],
+ },
],
data() {
return { currentRequestId: '' };
diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js
index 735c9d804ee..2ffe07500e0 100644
--- a/app/assets/javascripts/performance_bar/index.js
+++ b/app/assets/javascripts/performance_bar/index.js
@@ -1,3 +1,4 @@
+/* eslint-disable @gitlab/i18n/no-non-i18n-strings */
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
@@ -53,12 +54,61 @@ export default ({ container }) =>
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
this.store.addRequestDetails(requestId, res.data);
+
+ if (this.requestId === requestId) this.collectFrontendPerformanceMetrics();
})
.catch(() =>
// eslint-disable-next-line no-console
console.warn(`Error getting performance bar results for ${requestId}`),
);
},
+ collectFrontendPerformanceMetrics() {
+ if (performance) {
+ const navigationEntries = performance.getEntriesByType('navigation');
+ const paintEntries = performance.getEntriesByType('paint');
+ const resourceEntries = performance.getEntriesByType('resource');
+
+ let durationString = '';
+ if (navigationEntries.length > 0) {
+ durationString = `BE ${this.formatMs(navigationEntries[0].responseEnd)} / `;
+ durationString += `FCP ${this.formatMs(paintEntries[1].startTime)} / `;
+ durationString += `DOM ${this.formatMs(navigationEntries[0].domContentLoadedEventEnd)}`;
+ }
+
+ let newEntries = resourceEntries.map(this.transformResourceEntry);
+
+ this.updateFrontendPerformanceMetrics(durationString, newEntries);
+
+ if ('PerformanceObserver' in window) {
+ // We start observing for more incoming timings
+ const observer = new PerformanceObserver(list => {
+ newEntries = newEntries.concat(list.getEntries().map(this.transformResourceEntry));
+ this.updateFrontendPerformanceMetrics(durationString, newEntries);
+ });
+
+ observer.observe({ entryTypes: ['resource'] });
+ }
+ }
+ },
+ updateFrontendPerformanceMetrics(durationString, requestEntries) {
+ this.store.setRequestDetailsData(this.requestId, 'total', {
+ duration: durationString,
+ calls: requestEntries.length,
+ details: requestEntries,
+ });
+ },
+ transformResourceEntry(entry) {
+ const nf = new Intl.NumberFormat();
+ return {
+ name: entry.name.replace(document.location.origin, ''),
+ duration: Math.round(entry.duration),
+ size: entry.transferSize ? `${nf.format(entry.transferSize)} bytes` : 'cached',
+ };
+ },
+ formatMs(msValue) {
+ const nf = new Intl.NumberFormat();
+ return `${nf.format(Math.round(msValue))}ms`;
+ },
},
render(createElement) {
return createElement('performance-bar-app', {
diff --git a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
index 12d0ee86218..6f443db47ed 100644
--- a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
+++ b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
@@ -32,6 +32,16 @@ export default class PerformanceBarStore {
return request;
}
+ setRequestDetailsData(requestId, metricKey, requestDetailsData) {
+ const selectedRequest = this.findRequest(requestId);
+ if (selectedRequest) {
+ selectedRequest.details = {
+ ...selectedRequest.details,
+ [metricKey]: requestDetailsData,
+ };
+ }
+ }
+
requestsWithDetails() {
return this.requests.filter(request => request.details);
}
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index 7d6a725b30f..157d89a3a40 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -17,6 +17,7 @@ const handleUserPopoverMouseOut = event => {
renderedPopover.$destroy();
renderedPopover = null;
}
+ target.removeAttribute('aria-describedby');
};
/**
diff --git a/app/assets/stylesheets/pages/error_details.scss b/app/assets/stylesheets/pages/error_details.scss
index 0515db914e9..dcd25c126c4 100644
--- a/app/assets/stylesheets/pages/error_details.scss
+++ b/app/assets/stylesheets/pages/error_details.scss
@@ -12,6 +12,12 @@
}
}
+ .file-title-name {
+ &.limited-width {
+ max-width: 80%;
+ }
+ }
+
.line_content.old::before {
content: none !important;
}
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 6ad086211fb..c15d9edf7b2 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -17,11 +17,11 @@ module Clusters
include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
+ alias_method :original_set_initial_status, :set_initial_status
def set_initial_status
- return unless not_installable?
- return unless verify_cluster?
+ return unless cluster&.platform_kubernetes_rbac?
- self.status = status_states[:installable]
+ original_set_initial_status
end
state_machine :status do
@@ -131,10 +131,6 @@ module Clusters
[Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "-f", METRICS_CONFIG)]
end
-
- def verify_cluster?
- cluster&.application_helm_available? && cluster&.platform_kubernetes_rbac?
- end
end
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 8ef49fc56c6..65f5cbf69c6 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -5,6 +5,7 @@ class Deployment < ApplicationRecord
include IidRoutes
include AfterCommitQueue
include UpdatedAtFilterable
+ include Gitlab::Utils::StrongMemoize
belongs_to :project, required: true
belongs_to :environment, required: true
@@ -126,6 +127,12 @@ class Deployment < ApplicationRecord
@scheduled_actions ||= deployable.try(:other_scheduled_actions)
end
+ def playable_build
+ strong_memoize(:playable_build) do
+ deployable.try(:playable?) ? deployable : nil
+ end
+ end
+
def includes_commit?(commit)
return false unless commit
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
index d7dc64190d6..3eca8c91e40 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -78,7 +78,7 @@ class EnvironmentStatus
def self.build_environments_status(mr, user, pipeline)
return [] unless pipeline
- pipeline.environments.available.map do |environment|
+ pipeline.environments.includes(:project).available.map do |environment|
next unless Ability.allowed?(user, :read_environment, environment)
EnvironmentStatus.new(pipeline.project, environment, mr, pipeline.sha)
diff --git a/app/models/project.rb b/app/models/project.rb
index 26392964ced..3177a5b83f9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2250,12 +2250,13 @@ class Project < ApplicationRecord
# Git objects are only poolable when the project is or has:
# - Hashed storage -> The object pool will have a remote to its members, using relative paths.
# If the repository path changes we would have to update the remote.
- # - Public -> User will be able to fetch Git objects that might not exist
- # in their own repository.
+ # - not private -> The visibility level or repository access level has to be greater than private
+ # to prevent fetching objects that might not exist
# - Repository -> Else the disk path will be empty, and there's nothing to pool
def git_objects_poolable?
hashed_storage?(:repository) &&
- public? &&
+ visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
+ repository_access_level > ProjectFeature::PRIVATE &&
repository_exists? &&
Gitlab::CurrentSettings.hashed_storage_enabled
end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index e6421315b34..94773eeebd0 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -37,6 +37,9 @@ class DeploymentEntity < Grape::Entity
expose :commit, using: CommitEntity, if: -> (*) { include_details? }
expose :manual_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
expose :scheduled_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
+ expose :playable_build, expose_nil: false, if: -> (*) { include_details? && can_create_deployment? } do |deployment, options|
+ JobEntity.represent(deployment.playable_build, options.merge(only: [:play_path, :retry_path]))
+ end
expose :cluster, using: ClusterBasicEntity
@@ -47,7 +50,7 @@ class DeploymentEntity < Grape::Entity
end
def can_create_deployment?
- can?(request.current_user, :create_deployment, request.project)
+ can?(request.current_user, :create_deployment, project)
end
def can_read_deployables?
@@ -56,6 +59,10 @@ class DeploymentEntity < Grape::Entity
# because it triggers a policy evaluation that involves multiple
# Gitaly calls that might not be cached.
#
- can?(request.current_user, :read_build, request.project)
+ can?(request.current_user, :read_build, project)
+ end
+
+ def project
+ request.try(:project) || options[:project]
end
end
diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb
index 811cc2ad5af..40db23c143e 100644
--- a/app/serializers/environment_status_entity.rb
+++ b/app/serializers/environment_status_entity.rb
@@ -37,6 +37,10 @@ class EnvironmentStatusEntity < Grape::Entity
es.deployment.try(:formatted_deployment_time)
end
+ expose :deployment, as: :details do |es, options|
+ DeploymentEntity.represent(es.deployment, options.merge(project: es.project, only: [:playable_build]))
+ end
+
expose :changes
private
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
index 2a61187a856..028de38e42a 100644
--- a/app/serializers/merge_request_poll_widget_entity.rb
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
-class MergeRequestPollWidgetEntity < IssuableEntity
+class MergeRequestPollWidgetEntity < Grape::Entity
+ include RequestAwareEntity
+
expose :auto_merge_strategy
expose :available_auto_merge_strategies do |merge_request|
AutoMergeService.new(merge_request.project, current_user).available_strategies(merge_request) # rubocop: disable CodeReuse/ServiceClass
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 94e8b174f0f..cddb894fd64 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -77,6 +77,10 @@ class PipelineEntity < Grape::Entity
cancel_project_pipeline_path(pipeline.project, pipeline)
end
+ expose :failed_builds, if: -> (*) { can_retry? }, using: JobEntity do |pipeline|
+ pipeline.builds.failed
+ end
+
private
alias_method :pipeline, :object
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 48ed5afbc2a..974f7e598ca 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -36,3 +36,5 @@ module Issues
end
end
end
+
+Issues::BaseService.prepend_if_ee('EE::Issues::BaseService')
diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index 1fef43c0c37..be574155436 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -18,7 +18,7 @@
.col-lg-4
%h4.prepend-top-0= _('Notification events')
%p
- - notification_link = link_to _('notification emails'), help_page_path('workflow/notifications'), target: '_blank'
+ - notification_link = link_to _('notification emails'), help_page_path('user/profile/notifications'), target: '_blank'
- paragraph = _('Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.') % { notification_link: notification_link.html_safe }
#{ paragraph.html_safe }
.col-lg-8