diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-29 12:06:31 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-29 12:06:31 +0300 |
commit | 6b13a226ddfc49140d58e7e88f8703ae0ed90574 (patch) | |
tree | 9a92431e484354f43230fa87adc00a2edbf6f09c /app | |
parent | 2ac93cb80c4c0a57fde86de8262b569d1e9b9e51 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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 |