summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-09-30 21:06:41 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-09-30 21:06:41 +0000
commit08f4ce10c04d705148a7e14556443b9e3aee6821 (patch)
treeba58b29874803db12ebec690fb4416b6208ee750
parentb4cdff15ca53312ccbbafe4effac85b1ee4420ae (diff)
downloadgitlab-ce-08f4ce10c04d705148a7e14556443b9e3aee6821.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/performance_bar/components/detailed_metric.vue14
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue8
-rw-r--r--app/assets/javascripts/performance_bar/components/request_selector.vue29
-rw-r--r--app/assets/javascripts/performance_bar/components/request_warning.vue41
-rw-r--r--app/assets/javascripts/performance_bar/index.js4
-rw-r--r--app/assets/javascripts/performance_bar/stores/performance_bar_store.js8
-rw-r--r--app/models/clusters/platforms/kubernetes.rb1
-rw-r--r--app/views/admin/system_info/show.html.haml44
-rw-r--r--changelogs/unreleased/dz-redesign-admin-system-info.yml5
-rw-r--r--changelogs/unreleased/performance-bar-warnings.yml5
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.pngbin0 -> 65296 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.pngbin0 -> 58287 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_request_selector_warning_expanded.pngbin0 -> 62514 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md18
-rw-r--r--locale/gitlab.pot41
-rw-r--r--spec/features/issues/issues_tabs_spec.rb56
-rw-r--r--spec/frontend/performance_bar/components/detailed_metric_spec.js111
-rw-r--r--spec/frontend/performance_bar/components/performance_bar_app_spec.js20
-rw-r--r--spec/frontend/performance_bar/components/request_selector_spec.js64
-rw-r--r--spec/frontend/performance_bar/components/request_warning_spec.js33
-rw-r--r--spec/javascripts/performance_bar/components/detailed_metric_spec.js109
-rw-r--r--spec/javascripts/performance_bar/components/performance_bar_app_spec.js29
-rw-r--r--spec/javascripts/performance_bar/components/request_selector_spec.js46
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb27
25 files changed, 442 insertions, 273 deletions
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 50b29db6e8f..ce34cfb39a8 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -19,6 +19,7 @@ package-and-qa-manual:
except:
refs:
- master
+ - /^\d+-\d+-auto-deploy-\d+$/
when: manual
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
@@ -29,6 +30,7 @@ package-and-qa:
except:
refs:
- master
+ - /^\d+-\d+-auto-deploy-\d+$/
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
allow_failure: true
diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
index f2d98cf07e1..7ce32032ed3 100644
--- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue
+++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
@@ -1,9 +1,12 @@
<script>
+import RequestWarning from './request_warning.vue';
+
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
+ RequestWarning,
GlModal: DeprecatedModal2,
Icon,
},
@@ -39,6 +42,16 @@ export default {
detailsList() {
return this.metricDetails.details;
},
+ warnings() {
+ return this.metricDetails.warnings || [];
+ },
+ htmlId() {
+ if (this.currentRequest) {
+ return `performance-bar-warning-${this.currentRequest.id}-${this.metric}`;
+ }
+
+ return '';
+ },
},
};
</script>
@@ -105,5 +118,6 @@ export default {
<div slot="footer"></div>
</gl-modal>
{{ title }}
+ <request-warning :html-id="htmlId" :warnings="warnings" />
</div>
</template>
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 9ad6e75b86b..3b07eba02b7 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -1,14 +1,14 @@
<script>
import { glEmojiTag } from '~/emoji';
-import detailedMetric from './detailed_metric.vue';
-import requestSelector from './request_selector.vue';
+import DetailedMetric from './detailed_metric.vue';
+import RequestSelector from './request_selector.vue';
import { s__ } from '~/locale';
export default {
components: {
- detailedMetric,
- requestSelector,
+ DetailedMetric,
+ RequestSelector,
},
props: {
store: {
diff --git a/app/assets/javascripts/performance_bar/components/request_selector.vue b/app/assets/javascripts/performance_bar/components/request_selector.vue
index 297507b85af..793aba3189b 100644
--- a/app/assets/javascripts/performance_bar/components/request_selector.vue
+++ b/app/assets/javascripts/performance_bar/components/request_selector.vue
@@ -1,5 +1,12 @@
<script>
+import { glEmojiTag } from '~/emoji';
+import { n__ } from '~/locale';
+import { GlPopover } from '@gitlab/ui';
+
export default {
+ components: {
+ GlPopover,
+ },
props: {
currentRequest: {
type: Object,
@@ -15,6 +22,18 @@ export default {
currentRequestId: this.currentRequest.id,
};
},
+ computed: {
+ requestsWithWarnings() {
+ return this.requests.filter(request => request.hasWarnings);
+ },
+ warningMessage() {
+ return n__(
+ '%d request with warnings',
+ '%d requests with warnings',
+ this.requestsWithWarnings.length,
+ );
+ },
+ },
watch: {
currentRequestId(newRequestId) {
this.$emit('change-current-request', newRequestId);
@@ -31,6 +50,7 @@ export default {
return truncated;
},
+ glEmojiTag,
},
};
</script>
@@ -44,7 +64,16 @@ export default {
class="qa-performance-bar-request"
>
{{ truncatedUrl(request.url) }}
+ <span v-if="request.hasWarnings">(!)</span>
</option>
</select>
+ <span v-if="requestsWithWarnings.length">
+ <span id="performance-bar-request-selector-warning" v-html="glEmojiTag('warning')"></span>
+ <gl-popover
+ target="performance-bar-request-selector-warning"
+ :content="warningMessage"
+ triggers="hover focus"
+ />
+ </span>
</div>
</template>
diff --git a/app/assets/javascripts/performance_bar/components/request_warning.vue b/app/assets/javascripts/performance_bar/components/request_warning.vue
new file mode 100644
index 00000000000..0da3c271214
--- /dev/null
+++ b/app/assets/javascripts/performance_bar/components/request_warning.vue
@@ -0,0 +1,41 @@
+<script>
+import { glEmojiTag } from '~/emoji';
+import { GlPopover } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlPopover,
+ },
+ props: {
+ htmlId: {
+ type: String,
+ required: true,
+ },
+ warnings: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ hasWarnings() {
+ return this.warnings && this.warnings.length;
+ },
+ warningMessage() {
+ if (!this.hasWarnings) {
+ return '';
+ }
+
+ return this.warnings.join('\n');
+ },
+ },
+ methods: {
+ glEmojiTag,
+ },
+};
+</script>
+<template>
+ <span v-if="hasWarnings">
+ <span :id="htmlId" v-html="glEmojiTag('warning')"></span>
+ <gl-popover :target="htmlId" :content="warningMessage" triggers="hover focus" />
+ </span>
+</template>
diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js
index 29bfb7ee5df..1ae9487f391 100644
--- a/app/assets/javascripts/performance_bar/index.js
+++ b/app/assets/javascripts/performance_bar/index.js
@@ -6,7 +6,7 @@ export default ({ container }) =>
new Vue({
el: container,
components: {
- performanceBarApp: () => import('./components/performance_bar_app.vue'),
+ PerformanceBarApp: () => import('./components/performance_bar_app.vue'),
},
data() {
const performanceBarData = document.querySelector(this.$options.el).dataset;
@@ -41,7 +41,7 @@ export default ({ container }) =>
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
- this.store.addRequestDetails(requestId, res.data.data);
+ this.store.addRequestDetails(requestId, res.data);
})
.catch(() =>
// eslint-disable-next-line no-console
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 031e774d533..64f4f5e0c76 100644
--- a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
+++ b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
@@ -3,12 +3,13 @@ export default class PerformanceBarStore {
this.requests = [];
}
- addRequest(requestId, requestUrl, requestDetails) {
+ addRequest(requestId, requestUrl) {
if (!this.findRequest(requestId)) {
this.requests.push({
id: requestId,
url: requestUrl,
- details: requestDetails,
+ details: {},
+ hasWarnings: false,
});
}
@@ -22,7 +23,8 @@ export default class PerformanceBarStore {
addRequestDetails(requestId, requestDetails) {
const request = this.findRequest(requestId);
- request.details = requestDetails;
+ request.details = requestDetails.data;
+ request.hasWarnings = requestDetails.has_warnings;
return request;
}
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index d00a2984a60..943f36299d4 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -6,6 +6,7 @@ module Clusters
include Gitlab::Kubernetes
include EnumWithNil
include AfterCommitQueue
+ include ReactiveCaching
RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index 948a11646f7..b7648979edd 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -1,35 +1,35 @@
-- page_title "System Info"
+- page_title _('System Info')
.prepend-top-default
.row
- .col-sm-4
- .card.bg-light.light-well
- %h4 CPU
+ .col-sm
+ .bg-light.light-well
+ %h4= _('CPU')
.data
- if @cpus
- %h1 #{@cpus.length} cores
+ %h2= _('%{cores} cores') % { cores: @cpus.length }
- else
= icon('warning', class: 'text-warning')
- Unable to collect CPU info
- .col-sm-4
- .card.bg-light.light-well
- %h4 Memory Usage
+ = _('Unable to collect CPU info')
+ .bg-light.light-well.prepend-top-default
+ %h4= _('Memory Usage')
.data
- if @memory
- %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
+ %h2 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
- else
= icon('warning', class: 'text-warning')
- Unable to collect memory info
- .col-sm-4
- .card.bg-light.light-well
- %h4 Disk Usage
+ = _('Unable to collect memory info')
+ .bg-light.light-well.prepend-top-default
+ %h4= _('Uptime')
.data
- - @disks.each do |disk|
- %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
- %p= disk[:disk_name]
- %p= disk[:mount_path]
- .col-sm-4
- .card.bg-light.light-well
- %h4 Uptime
+ %h2= distance_of_time_in_words_to_now(Rails.application.config.booted_at)
+ .col-sm
+ .bg-light.light-well
+ %h4= _('Disk Usage')
.data
- %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at)
+ %ul
+ - @disks.each do |disk|
+ %li
+ %h2 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
+ %p= disk[:disk_name]
+ %p= disk[:mount_path]
diff --git a/changelogs/unreleased/dz-redesign-admin-system-info.yml b/changelogs/unreleased/dz-redesign-admin-system-info.yml
new file mode 100644
index 00000000000..f86f6c6d603
--- /dev/null
+++ b/changelogs/unreleased/dz-redesign-admin-system-info.yml
@@ -0,0 +1,5 @@
+---
+title: Improve admin/system_info page ui
+merge_request: 17829
+author:
+type: changed
diff --git a/changelogs/unreleased/performance-bar-warnings.yml b/changelogs/unreleased/performance-bar-warnings.yml
new file mode 100644
index 00000000000..d1d02ce9b94
--- /dev/null
+++ b/changelogs/unreleased/performance-bar-warnings.yml
@@ -0,0 +1,5 @@
+---
+title: Add warnings to performance bar when page shows signs of poor performance
+merge_request: 17612
+author:
+type: changed
diff --git a/doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.png b/doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.png
new file mode 100644
index 00000000000..d4bf5c69ffa
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_gitaly_threshold.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.png b/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.png
new file mode 100644
index 00000000000..966549554a4
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning_expanded.png b/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning_expanded.png
new file mode 100644
index 00000000000..3362186bb48
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_request_selector_warning_expanded.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index 02f4b78bd60..53c08e32cb2 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -21,6 +21,24 @@ On the far right is a request selector that allows you to view the same metrics
(excluding the page timing and line profiler) for any requests made while the
page was open. Only the first two requests per unique URL are captured.
+## Request warnings
+
+For requests exceeding pre-defined limits, a warning icon will be shown
+next to the failing metric, along with an explanation. In this example,
+the Gitaly call duration exceeded the threshold:
+
+![Gitaly call duration exceeded threshold](img/performance_bar_gitaly_threshold.png)
+
+If any requests on the current page generated warnings, the icon will
+appear next to the request selector:
+
+![Request selector showing two requests with warnings](img/performance_bar_request_selector_warning.png)
+
+And requests with warnings are indicated in the request selector with a
+`(!)` after their path:
+
+![Request selector showing dropdown](img/performance_bar_request_selector_warning_expanded.png)
+
## Enable the Performance Bar via the Admin panel
GitLab Performance Bar is disabled by default. To enable it for a given group,
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 46b45f5a180..183f2db050f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -150,6 +150,11 @@ msgid_plural "%d more comments"
msgstr[0] ""
msgstr[1] ""
+msgid "%d request with warnings"
+msgid_plural "%d requests with warnings"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d second"
msgid_plural "%d seconds"
msgstr[0] ""
@@ -179,6 +184,9 @@ msgstr ""
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
+msgid "%{cores} cores"
+msgstr ""
+
msgid "%{count} LOC/commit"
msgstr ""
@@ -2695,6 +2703,9 @@ msgstr ""
msgid "CONTRIBUTING"
msgstr ""
+msgid "CPU"
+msgstr ""
+
msgid "Callback URL"
msgstr ""
@@ -5347,6 +5358,9 @@ msgstr ""
msgid "Discussion"
msgstr ""
+msgid "Disk Usage"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -8923,6 +8937,9 @@ msgstr ""
msgid "Kubernetes"
msgstr ""
+msgid "Kubernetes API returned status code: %{error_code}"
+msgstr ""
+
msgid "Kubernetes Cluster"
msgstr ""
@@ -9654,6 +9671,9 @@ msgstr ""
msgid "Members with pending access to %{strong_start}%{group_name}%{strong_end}"
msgstr ""
+msgid "Memory Usage"
+msgstr ""
+
msgid "Merge"
msgstr ""
@@ -11500,6 +11520,9 @@ msgstr ""
msgid "Please wait while we import the repository for you. Refresh at will."
msgstr ""
+msgid "Pod not found"
+msgstr ""
+
msgid "Pods in use"
msgstr ""
@@ -14565,6 +14588,9 @@ msgstr ""
msgid "Something went wrong on our end."
msgstr ""
+msgid "Something went wrong on our end. %{message}"
+msgstr ""
+
msgid "Something went wrong on our end. Please try again!"
msgstr ""
@@ -16779,6 +16805,12 @@ msgstr ""
msgid "Unable to build Slack link."
msgstr ""
+msgid "Unable to collect CPU info"
+msgstr ""
+
+msgid "Unable to collect memory info"
+msgstr ""
+
msgid "Unable to connect to Prometheus server"
msgstr ""
@@ -17055,6 +17087,9 @@ msgstr ""
msgid "Upstream"
msgstr ""
+msgid "Uptime"
+msgstr ""
+
msgid "Upvotes"
msgstr ""
@@ -18835,6 +18870,9 @@ msgstr ""
msgid "connecting"
msgstr ""
+msgid "container_name cannot be larger than %{max_length} chars"
+msgstr ""
+
msgid "could not read private key, is the passphrase correct?"
msgstr ""
@@ -19405,6 +19443,9 @@ msgstr ""
msgid "pipeline"
msgstr ""
+msgid "pod_name cannot be larger than %{max_length} chars"
+msgstr ""
+
msgid "point"
msgid_plural "points"
msgstr[0] ""
diff --git a/spec/features/issues/issues_tabs_spec.rb b/spec/features/issues/issues_tabs_spec.rb
deleted file mode 100644
index 66d55dd9021..00000000000
--- a/spec/features/issues/issues_tabs_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'Issue page tabs', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public) }
- let(:issue) { create(:issue, author: user, assignees: [user], project: project) }
-
- describe 'discussions tab counter' do
- before do
- stub_licensed_features(design_management: true)
- stub_feature_flags(design_management_flag: true)
- allow(Ability).to receive(:allowed?) { true }
- end
-
- subject do
- sign_in(user)
-
- visit project_issue_path(project, issue)
-
- wait_for_requests
-
- find('#discussion')
- end
-
- context 'new issue' do
- it 'displays count of 0' do
- is_expected.to have_content('Discussion 0')
- end
- end
-
- context 'issue with 2 system notes and 1 discussion' do
- let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: project, note: "This is good") }
-
- before do
- create(:system_note, noteable: issue, project: project, author: user, note: 'description updated')
- create(:system_note, noteable: issue, project: project, author: user, note: 'description updated')
- end
-
- it 'displays count of 1' do
- is_expected.to have_content('Discussion 1')
- end
-
- context 'with 1 reply' do
- before do
- create(:note, noteable: issue, in_reply_to: discussion, discussion_id: discussion.discussion_id, note: 'I also think this is good')
- end
-
- it 'displays count of 2' do
- is_expected.to have_content('Discussion 2')
- end
- end
- end
- end
-end
diff --git a/spec/frontend/performance_bar/components/detailed_metric_spec.js b/spec/frontend/performance_bar/components/detailed_metric_spec.js
new file mode 100644
index 00000000000..74f242431a1
--- /dev/null
+++ b/spec/frontend/performance_bar/components/detailed_metric_spec.js
@@ -0,0 +1,111 @@
+import DetailedMetric from '~/performance_bar/components/detailed_metric.vue';
+import RequestWarning from '~/performance_bar/components/request_warning.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('detailedMetric', () => {
+ const createComponent = props =>
+ shallowMount(DetailedMetric, {
+ propsData: {
+ ...props,
+ },
+ });
+
+ describe('when the current request has no details', () => {
+ const wrapper = createComponent({
+ currentRequest: {},
+ metric: 'gitaly',
+ header: 'Gitaly calls',
+ details: 'details',
+ keys: ['feature', 'request'],
+ });
+
+ it('does not render the element', () => {
+ expect(wrapper.isEmpty()).toBe(true);
+ });
+ });
+
+ describe('when the current request has details', () => {
+ const requestDetails = [
+ { duration: '100', feature: 'find_commit', request: 'abcdef', backtrace: ['hello', 'world'] },
+ { duration: '23', feature: 'rebase_in_progress', request: '', backtrace: ['world', 'hello'] },
+ ];
+
+ describe('with a default metric name', () => {
+ const wrapper = createComponent({
+ currentRequest: {
+ details: {
+ gitaly: {
+ duration: '123ms',
+ calls: '456',
+ details: requestDetails,
+ warnings: ['gitaly calls: 456 over 30'],
+ },
+ },
+ },
+ metric: 'gitaly',
+ header: 'Gitaly calls',
+ keys: ['feature', 'request'],
+ });
+
+ it('displays details', () => {
+ expect(wrapper.text().replace(/\s+/g, ' ')).toContain('123ms / 456');
+ });
+
+ it('adds a modal with a table of the details', () => {
+ wrapper
+ .findAll('.performance-bar-modal td:nth-child(1)')
+ .wrappers.forEach((duration, index) => {
+ expect(duration.text()).toContain(requestDetails[index].duration);
+ });
+
+ wrapper
+ .findAll('.performance-bar-modal td:nth-child(2)')
+ .wrappers.forEach((feature, index) => {
+ expect(feature.text()).toContain(requestDetails[index].feature);
+ });
+
+ wrapper
+ .findAll('.performance-bar-modal td:nth-child(2)')
+ .wrappers.forEach((request, index) => {
+ expect(request.text()).toContain(requestDetails[index].request);
+ });
+
+ expect(wrapper.find('.text-expander.js-toggle-button')).not.toBeNull();
+
+ wrapper.findAll('.performance-bar-modal td:nth-child(2)').wrappers.forEach(request => {
+ expect(request.text()).toContain('world');
+ });
+ });
+
+ it('displays the metric title', () => {
+ expect(wrapper.text()).toContain('gitaly');
+ });
+
+ it('displays request warnings', () => {
+ expect(wrapper.find(RequestWarning).exists()).toBe(true);
+ });
+ });
+
+ describe('when using a custom metric title', () => {
+ const wrapper = createComponent({
+ currentRequest: {
+ details: {
+ gitaly: {
+ duration: '123ms',
+ calls: '456',
+ details: requestDetails,
+ },
+ },
+ },
+ metric: 'gitaly',
+ title: 'custom',
+ header: 'Gitaly calls',
+ keys: ['feature', 'request'],
+ });
+
+ it('displays the custom title', () => {
+ expect(wrapper.text()).toContain('custom');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/performance_bar/components/performance_bar_app_spec.js b/spec/frontend/performance_bar/components/performance_bar_app_spec.js
new file mode 100644
index 00000000000..ba403dd6209
--- /dev/null
+++ b/spec/frontend/performance_bar/components/performance_bar_app_spec.js
@@ -0,0 +1,20 @@
+import PerformanceBarApp from '~/performance_bar/components/performance_bar_app.vue';
+import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store';
+import { shallowMount } from '@vue/test-utils';
+
+describe('performance bar app', () => {
+ const store = new PerformanceBarStore();
+ const wrapper = shallowMount(PerformanceBarApp, {
+ propsData: {
+ store,
+ env: 'development',
+ requestId: '123',
+ peekUrl: '/-/peek/results',
+ profileUrl: '?lineprofiler=true',
+ },
+ });
+
+ it('sets the class to match the environment', () => {
+ expect(wrapper.element.getAttribute('class')).toContain('development');
+ });
+});
diff --git a/spec/frontend/performance_bar/components/request_selector_spec.js b/spec/frontend/performance_bar/components/request_selector_spec.js
new file mode 100644
index 00000000000..a4ed55fbf15
--- /dev/null
+++ b/spec/frontend/performance_bar/components/request_selector_spec.js
@@ -0,0 +1,64 @@
+import RequestSelector from '~/performance_bar/components/request_selector.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('request selector', () => {
+ const requests = [
+ {
+ id: '123',
+ url: 'https://gitlab.com/',
+ hasWarnings: false,
+ },
+ {
+ id: '456',
+ url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1',
+ hasWarnings: false,
+ },
+ {
+ id: '789',
+ url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1.json?serializer=widget',
+ hasWarnings: false,
+ },
+ {
+ id: 'abc',
+ url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1/discussions.json',
+ hasWarnings: true,
+ },
+ ];
+
+ const wrapper = shallowMount(RequestSelector, {
+ propsData: {
+ requests,
+ currentRequest: requests[1],
+ },
+ });
+
+ function optionText(requestId) {
+ return wrapper
+ .find(`[value='${requestId}']`)
+ .text()
+ .trim();
+ }
+
+ it('displays the last component of the path', () => {
+ expect(optionText(requests[2].id)).toEqual('1.json?serializer=widget');
+ });
+
+ it('keeps the last two components of the path when the last component is numeric', () => {
+ expect(optionText(requests[1].id)).toEqual('merge_requests/1');
+ });
+
+ it('ignores trailing slashes', () => {
+ expect(optionText(requests[0].id)).toEqual('gitlab.com');
+ });
+
+ it('has a warning icon if any requests have warnings', () => {
+ expect(wrapper.find('span > gl-emoji').element.dataset.name).toEqual('warning');
+ });
+
+ it('adds a warning glyph to requests with warnings', () => {
+ const requestValue = wrapper.find('[value="abc"]').text();
+
+ expect(requestValue).toContain('discussions.json');
+ expect(requestValue).toContain('(!)');
+ });
+});
diff --git a/spec/frontend/performance_bar/components/request_warning_spec.js b/spec/frontend/performance_bar/components/request_warning_spec.js
new file mode 100644
index 00000000000..6d8bfba56f6
--- /dev/null
+++ b/spec/frontend/performance_bar/components/request_warning_spec.js
@@ -0,0 +1,33 @@
+import RequestWarning from '~/performance_bar/components/request_warning.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('request warning', () => {
+ const htmlId = 'request-123';
+
+ describe('when the request has warnings', () => {
+ const wrapper = shallowMount(RequestWarning, {
+ propsData: {
+ htmlId,
+ warnings: ['gitaly calls: 30 over 10', 'gitaly duration: 1500 over 1000'],
+ },
+ });
+
+ it('adds a warning emoji with the correct ID', () => {
+ expect(wrapper.find('span[id]').attributes('id')).toEqual(htmlId);
+ expect(wrapper.find('span[id] gl-emoji').element.dataset.name).toEqual('warning');
+ });
+ });
+
+ describe('when the request does not have warnings', () => {
+ const wrapper = shallowMount(RequestWarning, {
+ propsData: {
+ htmlId,
+ warnings: [],
+ },
+ });
+
+ it('does nothing', () => {
+ expect(wrapper.isEmpty()).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
deleted file mode 100644
index 0486b5fa3db..00000000000
--- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import Vue from 'vue';
-import detailedMetric from '~/performance_bar/components/detailed_metric.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('detailedMetric', () => {
- let vm;
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('when the current request has no details', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(detailedMetric), {
- currentRequest: {},
- metric: 'gitaly',
- header: 'Gitaly calls',
- details: 'details',
- keys: ['feature', 'request'],
- });
- });
-
- it('does not render the element', () => {
- expect(vm.$el.innerHTML).toEqual(undefined);
- });
- });
-
- describe('when the current request has details', () => {
- const requestDetails = [
- { duration: '100', feature: 'find_commit', request: 'abcdef', backtrace: ['hello', 'world'] },
- { duration: '23', feature: 'rebase_in_progress', request: '', backtrace: ['world', 'hello'] },
- ];
-
- beforeEach(() => {
- vm = mountComponent(Vue.extend(detailedMetric), {
- currentRequest: {
- details: {
- gitaly: {
- duration: '123ms',
- calls: '456',
- details: requestDetails,
- },
- },
- },
- metric: 'gitaly',
- header: 'Gitaly calls',
- keys: ['feature', 'request'],
- });
- });
-
- it('diplays details', () => {
- expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456');
- });
-
- it('adds a modal with a table of the details', () => {
- vm.$el
- .querySelectorAll('.performance-bar-modal td:nth-child(1)')
- .forEach((duration, index) => {
- expect(duration.innerText).toContain(requestDetails[index].duration);
- });
-
- vm.$el
- .querySelectorAll('.performance-bar-modal td:nth-child(2)')
- .forEach((feature, index) => {
- expect(feature.innerText).toContain(requestDetails[index].feature);
- });
-
- vm.$el
- .querySelectorAll('.performance-bar-modal td:nth-child(2)')
- .forEach((request, index) => {
- expect(request.innerText).toContain(requestDetails[index].request);
- });
-
- expect(vm.$el.querySelector('.text-expander.js-toggle-button')).not.toBeNull();
-
- vm.$el.querySelectorAll('.performance-bar-modal td:nth-child(2)').forEach(request => {
- expect(request.innerText).toContain('world');
- });
- });
-
- it('displays the metric title', () => {
- expect(vm.$el.innerText).toContain('gitaly');
- });
-
- describe('when using a custom metric title', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(detailedMetric), {
- currentRequest: {
- details: {
- gitaly: {
- duration: '123ms',
- calls: '456',
- details: requestDetails,
- },
- },
- },
- metric: 'gitaly',
- title: 'custom',
- header: 'Gitaly calls',
- keys: ['feature', 'request'],
- });
- });
-
- it('displays the custom title', () => {
- expect(vm.$el.innerText).toContain('custom');
- });
- });
- });
-});
diff --git a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js
deleted file mode 100644
index 7926db44429..00000000000
--- a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import Vue from 'vue';
-import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue';
-import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('performance bar app', () => {
- let vm;
-
- beforeEach(() => {
- const store = new PerformanceBarStore();
-
- vm = mountComponent(Vue.extend(performanceBarApp), {
- store,
- env: 'development',
- requestId: '123',
- peekUrl: '/-/peek/results',
- profileUrl: '?lineprofiler=true',
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('sets the class to match the environment', () => {
- expect(vm.$el.getAttribute('class')).toContain('development');
- });
-});
diff --git a/spec/javascripts/performance_bar/components/request_selector_spec.js b/spec/javascripts/performance_bar/components/request_selector_spec.js
deleted file mode 100644
index 3c2169de877..00000000000
--- a/spec/javascripts/performance_bar/components/request_selector_spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import Vue from 'vue';
-import requestSelector from '~/performance_bar/components/request_selector.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('request selector', () => {
- const requests = [
- { id: '123', url: 'https://gitlab.com/' },
- {
- id: '456',
- url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1',
- },
- {
- id: '789',
- url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1.json?serializer=widget',
- },
- ];
-
- let vm;
-
- beforeEach(() => {
- vm = mountComponent(Vue.extend(requestSelector), {
- requests,
- currentRequest: requests[1],
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- function optionText(requestId) {
- return vm.$el.querySelector(`[value='${requestId}']`).innerText.trim();
- }
-
- it('displays the last component of the path', () => {
- expect(optionText(requests[2].id)).toEqual('1.json?serializer=widget');
- });
-
- it('keeps the last two components of the path when the last component is numeric', () => {
- expect(optionText(requests[1].id)).toEqual('merge_requests/1');
- });
-
- it('ignores trailing slashes', () => {
- expect(optionText(requests[0].id)).toEqual('gitlab.com');
- });
-});
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 538a5b8ef3c..36391543768 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -11,6 +11,10 @@ module KubernetesHelpers
kube_response(kube_pods_body)
end
+ def kube_pod_response
+ kube_response(kube_pod)
+ end
+
def kube_logs_response
kube_response(kube_logs_body)
end
@@ -63,11 +67,30 @@ module KubernetesHelpers
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end
- def stub_kubeclient_logs(pod_name, namespace, status: nil)
+ def stub_kubeclient_pod_details(pod, namespace, status: nil)
stub_kubeclient_discover(service.api_url)
- logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}/log?tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}"
+
+ pod_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod}"
response = { status: status } if status
+ WebMock.stub_request(:get, pod_url).to_return(response || kube_pod_response)
+ end
+
+ def stub_kubeclient_logs(pod_name, namespace, container: nil, status: nil, message: nil)
+ stub_kubeclient_discover(service.api_url)
+
+ if container
+ container_query_param = "container=#{container}&"
+ end
+
+ logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}" \
+ "/log?#{container_query_param}tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}"
+
+ if status
+ response = { status: status }
+ response[:body] = { message: message }.to_json if message
+ end
+
WebMock.stub_request(:get, logs_url).to_return(response || kube_logs_response)
end